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
39import Data.List (intercalate)
40import Control.Monad (msum)
41import Data.Maybe (isJust)
43import GHC.Exts (IsString, fromString)
45import Hakyll.Core.Identifier
47-- | One base element of a pattern
49data PatternComponent = CaptureOne
54instance Show PatternComponent where
56 show CaptureMany = "**"
59-- | Type that allows matching on identifiers
61newtype Pattern = Pattern {unPattern :: [PatternComponent]}
64instance Show Pattern where
65 show = intercalate "/" . map show . unPattern
67instance IsString Pattern where
68 fromString = parsePattern
70-- | Parse a pattern from a string
72parsePattern :: String -> Pattern
73parsePattern = Pattern . map toPattern . unIdentifier . parseIdentifier
75 toPattern x | x == "*" = CaptureOne
76 | x == "**" = CaptureMany
77 | otherwise = Literal x
79-- | Match an identifier against a pattern, generating a list of captures
81match :: Pattern -> Identifier -> Maybe [[String]]
82match (Pattern p) (Identifier i) = match' p i
84-- | Check if an identifier matches a pattern
86doesMatch :: Pattern -> Identifier -> Bool
87doesMatch p = isJust . match p
89-- | Given a list of identifiers, retain only those who match the given pattern
91matches :: Pattern -> [Identifier] -> [Identifier]
92matches p = filter (doesMatch p)
94-- | Split a list at every possible point, generate a list of (init, tail) cases
96splits :: [a] -> [([a], [a])]
97splits ls = reverse $ splits' [] ls
99 splits' lx ly = (lx, ly) : case ly of
101 (y : ys) -> splits' (lx ++ [y]) ys
103-- | Internal verion of 'match'
105match' :: [PatternComponent] -> [String] -> Maybe [[String]]
106match' [] [] = Just [] -- An empty match
107match' [] _ = Nothing -- No match
108match' _ [] = Nothing -- No match
109match' (m : ms) (s : ss) = case m of
110 -- Take one string and one literal, fail on mismatch
111 Literal l -> if s == l then match' ms ss else Nothing
112 -- Take one string and one capture
113 CaptureOne -> fmap ([s] :) $ match' ms ss
114 -- Take one string, and one or many captures
116 let take' (i, t) = fmap (i :) $ match' ms t
117 in msum $ map take' $ splits (s : ss)
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