1-- | Module providing pattern matching and capturing on 'Identifier's.
2-- 'Pattern's come in two kinds:
4-- * Simple glob patterns, like @foo\/*@;
6-- * Custom, arbitrary predicates of the type @Identifier -> Bool@.
8-- They both have advantages and disadvantages. By default, globs are used,
9-- unless you construct your 'Pattern' using the 'predicate' function.
11-- A very simple pattern could be, for example, @foo\/bar@. This pattern will
12-- only match the exact @foo\/bar@ identifier.
14-- To match more than one identifier, there are different captures that one can
17-- * @*@: matches at most one element of an identifier;
19-- * @**@: matches one or more elements of an identifier.
23-- * @foo\/*@ will match @foo\/bar@ and @foo\/foo@, but not @foo\/bar\/qux@;
25-- * @**@ will match any identifier;
27-- * @foo\/**@ will match @foo\/bar@ and @foo\/bar\/qux@, but not @bar\/foo@;
29-- * @foo\/*.html@ will match all HTML files in the @foo\/@ directory.
31-- The 'capture' function allows the user to get access to the elements captured
32-- by the capture elements in the pattern.
34-- Like an 'Identifier', a 'Pattern' also has a type parameter. This is simply
35-- an extra layer of safety, and can be discarded using the 'castPattern'
38module Hakyll.Core.Identifier.Pattern
53import Data.List (isPrefixOf, inits, tails)
54import Control.Arrow ((&&&), (>>>))
55import Control.Monad (msum)
56import Data.Maybe (isJust, fromMaybe)
57import Data.Monoid (Monoid, mempty, mappend)
59import GHC.Exts (IsString, fromString)
60import Text.Regex.PCRE ((=~~))
62import Hakyll.Core.Identifier
64-- | One base element of a pattern
66data GlobComponent = Capture
71-- | Type that allows matching on identifiers
73data Pattern a = Glob [GlobComponent]
74 | Predicate (Identifier a -> Bool)
77instance IsString (Pattern a) where
78 fromString = parseGlob
80instance Monoid (Pattern a) where
81 mempty = Predicate (const True)
82 p1 `mappend` p2 = Predicate $ \i -> matches p1 i && matches p2 i
84-- | Discard the phantom type parameter
86castPattern :: Pattern a -> Pattern b
87castPattern (Glob g) = Glob g
88castPattern (Predicate p) = Predicate $ p . castIdentifier
89castPattern (List l) = List $ map castIdentifier l
90{-# INLINE castPattern #-}
92-- | Parse a pattern from a string
94parseGlob :: String -> Pattern a
95parseGlob = Glob . parse'
98 let (chunk, rest) = break (`elem` "\\*") str
100 ('\\' : x : xs) -> Literal (chunk ++ [x]) : parse' xs
101 ('*' : '*' : xs) -> Literal chunk : CaptureMany : parse' xs
102 ('*' : xs) -> Literal chunk : Capture : parse' xs
103 xs -> Literal chunk : Literal xs : []
105-- | Create a 'Pattern' from an arbitrary predicate
109-- > predicate (\i -> matches "foo/*" i && not (matches "foo/bar" i))
111predicate :: (Identifier a -> Bool) -> Pattern a
114-- | Create a 'Pattern' from a list of 'Identifier's it should match
116list :: [Identifier a] -> Pattern a
119-- | Create a 'Pattern' from a regex
123-- > regex "^foo/[^x]*$
125regex :: String -> Pattern a
126regex str = predicate $ fromMaybe False . (=~~ str) . toFilePath
128-- | Create a 'Pattern' which matches if the identifier is in a certain group
131inGroup :: Maybe String -> Pattern a
132inGroup group = predicate $ (== group) . identifierGroup
134-- | Check if an identifier matches a pattern
136matches :: Pattern a -> Identifier a -> Bool
137matches (Glob p) = isJust . capture (Glob p)
138matches (Predicate p) = (p $)
139matches (List l) = (`elem` l)
141-- | Given a list of identifiers, retain only those who match the given pattern
143filterMatches :: Pattern a -> [Identifier a] -> [Identifier a]
144filterMatches = filter . matches
146-- | Split a list at every possible point, generate a list of (init, tail)
147-- cases. The result is sorted with inits decreasing in length.
149splits :: [a] -> [([a], [a])]
150splits = inits &&& tails >>> uncurry zip >>> reverse
152-- | Match a glob against a pattern, generating a list of captures
154capture :: Pattern a -> Identifier a -> Maybe [String]
155capture (Glob p) (Identifier _ i) = capture' p i
158-- | Internal verion of 'capture'
160capture' :: [GlobComponent] -> String -> Maybe [String]
161capture' [] [] = Just [] -- An empty match
162capture' [] _ = Nothing -- No match
163capture' (Literal l : ms) str
164 -- Match the literal against the string
165 | l `isPrefixOf` str = capture' ms $ drop (length l) str
166 | otherwise = Nothing
167capture' (Capture : ms) str =
168 -- Match until the next /
169 let (chunk, rest) = break (== '/') str
170 in msum $ [ fmap (i :) (capture' ms (t ++ rest)) | (i, t) <- splits chunk ]
171capture' (CaptureMany : ms) str =
173 msum $ [ fmap (i :) (capture' ms t) | (i, t) <- splits str ]
175-- | Create an identifier from a pattern by filling in the captures with a given
180-- > fromCapture (parseGlob "tags/*") "foo"
186fromCapture :: Pattern a -> String -> Identifier a
187fromCapture pattern = fromCaptures pattern . repeat
189-- | Create an identifier from a pattern by filling in the captures with the
190-- given list of strings
192fromCaptures :: Pattern a -> [String] -> Identifier a
193fromCaptures (Glob p) = Identifier Nothing . fromCaptures' p
194fromCaptures _ = error $
195 "Hakyll.Core.Identifier.Pattern.fromCaptures: fromCaptures only works " ++
198-- | Internally used version of 'fromCaptures'
200fromCaptures' :: [GlobComponent] -> [String] -> String
201fromCaptures' [] _ = mempty
202fromCaptures' (m : ms) [] = case m of
203 Literal l -> l `mappend` fromCaptures' ms []
204 _ -> error $ "Hakyll.Core.Identifier.Pattern.fromCaptures': "
205 ++ "identifier list exhausted"
206fromCaptures' (m : ms) ids@(i : is) = case m of
207 Literal l -> l `mappend` fromCaptures' ms ids
208 _ -> 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