1--------------------------------------------------------------------------------
2-- | Module providing pattern matching and capturing on file names.
3-- 'Pattern's come in two kinds:
5-- * Simple glob patterns, like @foo\/*@;
7-- * Custom, arbitrary predicates of the type @Identifier -> Bool@.
9-- They both have advantages and disadvantages. By default, globs are used,
10-- unless you construct your 'Pattern' using the 'predicate' function.
12-- A very simple pattern could be, for example, @foo\/bar@. This pattern will
13-- only match the exact @foo\/bar@ identifier.
15-- To match more than one identifier, there are different captures that one can
18-- * @*@: matches at most one element of an identifier;
20-- * @**@: matches one or more elements of an identifier.
24-- * @foo\/*@ will match @foo\/bar@ and @foo\/foo@, but not @foo\/bar\/qux@;
26-- * @**@ will match any identifier;
28-- * @foo\/**@ will match @foo\/bar@ and @foo\/bar\/qux@, but not @bar\/foo@;
30-- * @foo\/*.html@ will match all HTML files in the @foo\/@ directory.
32-- The 'capture' function allows the user to get access to the elements captured
33-- by the capture elements in the pattern.
35-- Like an 'Identifier', a 'Pattern' also has a type parameter. This is simply
36-- an extra layer of safety, and can be discarded using the 'castPattern'
38module Hakyll.Core.Resource.Pattern
45--------------------------------------------------------------------------------
46import Control.Arrow ((&&&), (>>>))
47import Control.Monad (msum)
48import Data.List (inits, isPrefixOf, tails)
49import GHC.Exts (IsString, fromString)
52--------------------------------------------------------------------------------
53import Hakyll.Core.Resource
56--------------------------------------------------------------------------------
57-- | One base element of a pattern
65--------------------------------------------------------------------------------
66-- | Type that allows matching on identifiers
67newtype Pattern = Pattern [GlobComponent]
71--------------------------------------------------------------------------------
72instance IsString Pattern where
73 fromString = parsePattern
76--------------------------------------------------------------------------------
77-- | Parse a pattern from a string
78parsePattern :: String -> Pattern
79parsePattern = Pattern . parse
82 let (chunk, rest) = break (`elem` "\\*") str
84 ('\\' : x : xs) -> Literal (chunk ++ [x]) : parse xs
85 ('*' : '*' : xs) -> Literal chunk : CaptureMany : parse xs
86 ('*' : xs) -> Literal chunk : Capture : parse xs
87 xs -> Literal chunk : Literal xs : []
90--------------------------------------------------------------------------------
91-- | Split a list at every possible point, generate a list of (init, tail)
92-- cases. The result is sorted with inits decreasing in length.
93splits :: [a] -> [([a], [a])]
94splits = inits &&& tails >>> uncurry zip >>> reverse
97--------------------------------------------------------------------------------
98-- | Match a glob against a pattern, generating a list of captures
99capture :: Pattern -> Resource -> Maybe [String]
100capture (Pattern p) rs = capture' p (unResource rs)
103--------------------------------------------------------------------------------
104-- | Internal verion of 'capture'
105capture' :: [GlobComponent] -> String -> Maybe [String]
106capture' [] [] = Just [] -- An empty match
107capture' [] _ = Nothing -- No match
108capture' (Literal l : ms) str
109 -- Match the literal against the string
110 | l `isPrefixOf` str = capture' ms $ drop (length l) str
111 | otherwise = Nothing
112capture' (Capture : ms) str =
113 -- Match until the next /
114 let (chunk, rest) = break (== '/') str
115 in msum $ [ fmap (i :) (capture' ms (t ++ rest)) | (i, t) <- splits chunk ]
116capture' (CaptureMany : ms) str =
118 msum $ [ fmap (i :) (capture' ms t) | (i, t) <- splits str ]
121--------------------------------------------------------------------------------
122-- | Create an identifier from a pattern by filling in the captures with a given
127-- > fromCapture (parsePattern "tags/*") "foo"
133fromCapture :: Pattern -> String -> Identifier
134fromCapture pattern = fromCaptures pattern . repeat
138--------------------------------------------------------------------------------
139-- | Create an identifier from a pattern by filling in the captures with the
140-- given list of strings
143fromCaptures :: Pattern -> [String] -> String
144fromCaptures (Pattern p) = fromCaptures' p
148--------------------------------------------------------------------------------
149-- | Internally used version of 'fromCaptures'
151fromCaptures' :: [GlobComponent] -> [String] -> String
152fromCaptures' [] _ = mempty
153fromCaptures' (m : ms) [] = case m of
154 Literal l -> l `mappend` fromCaptures' ms []
155 _ -> error $ "Hakyll.Core.Identifier.Pattern.fromCaptures': "
156 ++ "identifier list exhausted"
157fromCaptures' (m : ms) ids@(i : is) = case m of
158 Literal l -> l `mappend` fromCaptures' ms ids
159 _ -> i `mappend` fromCaptures' ms is
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list