1-- | Module providing pattern matching and capturing on 'Identifier's.
3-- A very simple pattern could be, for example, @foo\/bar@. This pattern will
4-- only match the exact @foo\/bar@ identifier.
6-- To match more than one identifier, there are different captures that one can
9-- * @*@: matches exactly one element of an identifier;
11-- * @**@: matches one or more elements of an identifier.
15-- * @foo\/*@ will match @foo\/bar@ and @foo\/foo@, but not @foo\/bar\/qux@ nor
18-- * @**@ will match any non-empty identifier;
20-- * @foo\/**@ will match @foo\/bar@ and @foo\/bar\/qux@, but not @bar\/foo@ nor
23-- A small warning: patterns are not globs. Using @foo\/*.markdown@ will not do
24-- what you probably intended, as it will only match the file which is literally
25-- called @foo\/*.markdown@. Remember that these captures only work on elements
26-- of identifiers as a whole; not on parts of these elements.
28-- Furthermore, the 'match' function allows the user to get access to the
29-- elements captured by the capture elements in the pattern.
31module Hakyll.Core.Identifier.Pattern
41import Data.List (intercalate)
42import Control.Monad (msum)
43import Data.Maybe (isJust)
44import Data.Monoid (mempty, mappend)
46import GHC.Exts (IsString, fromString)
48import Hakyll.Core.Identifier
50-- | One base element of a pattern
52data PatternComponent = CaptureOne
57instance Show PatternComponent where
59 show CaptureMany = "**"
62-- | Type that allows matching on identifiers
64newtype Pattern = Pattern {unPattern :: [PatternComponent]}
67instance Show Pattern where
68 show = intercalate "/" . map show . unPattern
70instance IsString Pattern where
71 fromString = parsePattern
73-- | Parse a pattern from a string
75parsePattern :: String -> Pattern
76parsePattern = Pattern . map toPattern . unIdentifier . parseIdentifier
78 toPattern x | x == "*" = CaptureOne
79 | x == "**" = CaptureMany
80 | otherwise = Literal x
82-- | Match an identifier against a pattern, generating a list of captures
84match :: Pattern -> Identifier -> Maybe [Identifier]
85match (Pattern p) (Identifier i) = fmap (map Identifier) $ match' p i
87-- | Check if an identifier matches a pattern
89doesMatch :: Pattern -> Identifier -> Bool
90doesMatch p = isJust . match p
92-- | Given a list of identifiers, retain only those who match the given pattern
94matches :: Pattern -> [Identifier] -> [Identifier]
95matches p = filter (doesMatch p)
97-- | Split a list at every possible point, generate a list of (init, tail) cases
99splits :: [a] -> [([a], [a])]
100splits ls = reverse $ splits' [] ls
102 splits' lx ly = (lx, ly) : case ly of
104 (y : ys) -> splits' (lx ++ [y]) ys
106-- | Internal verion of 'match'
108match' :: [PatternComponent] -> [String] -> Maybe [[String]]
109match' [] [] = Just [] -- An empty match
110match' [] _ = Nothing -- No match
111match' _ [] = Nothing -- No match
112match' (m : ms) (s : ss) = case m of
113 -- Take one string and one literal, fail on mismatch
114 Literal l -> if s == l then match' ms ss else Nothing
115 -- Take one string and one capture
116 CaptureOne -> fmap ([s] :) $ match' ms ss
117 -- Take one string, and one or many captures
119 let take' (i, t) = fmap (i :) $ match' ms t
120 in msum $ map take' $ splits (s : ss)
122-- | Create an identifier from a pattern by filling in the captures with a given
125fromCapture :: Pattern -> Identifier -> Identifier
126fromCapture pattern = fromCaptures pattern . repeat
128-- | Create an identifier from a pattern by filling in the captures with the
129-- given list of strings
131fromCaptures :: Pattern -> [Identifier] -> Identifier
132fromCaptures (Pattern []) _ = mempty
133fromCaptures (Pattern (m : ms)) [] = case m of
134 Literal l -> Identifier [l] `mappend` fromCaptures (Pattern ms) []
135 _ -> error $ "Hakyll.Core.Identifier.Pattern.fromCaptures: "
136 ++ "identifier list exhausted"
137fromCaptures (Pattern (m : ms)) ids@(i : is) = case m of
138 Literal l -> Identifier [l] `mappend` fromCaptures (Pattern ms) ids
139 _ -> i `mappend` fromCaptures (Pattern 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