@@ -0,0 +1,91 @@
1+-- | Module providing pattern matching and capturing on 'Identifier's.
5+module Hakyll.Core.Identifier.Pattern
13+import Data.List (intercalate)
14+import Control.Monad (msum)
15+import Data.Maybe (isJust)
17+import GHC.Exts (IsString, fromString)
19+import Hakyll.Core.Identifier
21+-- | One base element of a pattern
23+data PatternComponent = CaptureOne
28+instance Show PatternComponent where
29+ show CaptureOne = "*"
30+ show CaptureMany = "**"
33+-- | Type that allows matching on identifiers
35+newtype Pattern = Pattern {unPattern :: [PatternComponent]}
38+instance Show Pattern where
39+ show = intercalate "/" . map show . unPattern
41+instance IsString Pattern where
42+ fromString = parsePattern
44+-- | Parse a pattern from a string
46+parsePattern :: String -> Pattern
47+parsePattern = Pattern . map toPattern . unIdentifier . parseIdentifier
49+ toPattern x | x == "*" = CaptureOne
50+ | x == "**" = CaptureMany
51+ | otherwise = Literal x
53+-- | Match an identifier against a pattern, generating a list of captures
55+match :: Pattern -> Identifier -> Maybe [[String]]
56+match (Pattern p) (Identifier i) = match' p i
58+-- | Check if an identifier matches a pattern
60+doesMatch :: Pattern -> Identifier -> Bool
61+doesMatch p = isJust . match p
63+-- | Given a list of identifiers, retain only those who match the given pattern
65+matches :: Pattern -> [Identifier] -> [Identifier]
66+matches p = filter (doesMatch p)
68+-- | Split a list at every possible point, generate a list of (init, tail) cases
70+splits :: [a] -> [([a], [a])]
71+splits ls = reverse $ splits' [] ls
73+ splits' lx ly = (lx, ly) : case ly of
75+ (y : ys) -> splits' (lx ++ [y]) ys
77+-- | Internal verion of 'match'
79+match' :: [PatternComponent] -> [String] -> Maybe [[String]]
80+match' [] [] = Just [] -- An empty match
81+match' [] _ = Nothing -- No match
82+match' _ [] = Nothing -- No match
83+match' (m : ms) (s : ss) = case m of
84+ -- Take one string and one literal, fail on mismatch
85+ Literal l -> if s == l then match' ms ss else Nothing
86+ -- Take one string and one capture
87+ CaptureOne -> fmap ([s] :) $ match' ms ss
88+ -- Take one string, and one or many captures
90+ let take' (i, t) = fmap (i :) $ match' ms t
91+ in msum $ map take' $ splits (s : ss)