% Copyright (C) 2004 David Roundy
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 2, or (at your option)
% any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; see the file COPYING. If not, write to
% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
% Boston, MA 021101301, USA.
\begin{code}
#include "gadts.h"
module Darcs.Patch.Match ( PatchMatch, Matcher, MatchFun,
patch_match, match_pattern,
apply_matcher, make_matcher,
parseMatch,
match_parser, helpOnMatchers,
) where
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Expr
import Text.Regex ( mkRegex, matchRegex )
import Data.Maybe ( isJust )
import System.IO.Unsafe ( unsafePerformIO )
import Darcs.Hopefully ( PatchInfoAnd, hopefully, info )
import Darcs.Patch ( Patch, Patchy, list_touched_files, patchcontents )
import Darcs.Patch.Info ( just_name, just_author, make_filename,
pi_date )
import Darcs.Sealed ( Sealed2(..), seal2 )
import DateMatcher ( parseDateMatcher )
import Darcs.Patch.MatchData ( PatchMatch(..), patch_match )
type MatchFun p = Sealed2 (PatchInfoAnd p) -> Bool
data Matcher p = MATCH String (MatchFun p)
instance Show (Matcher p) where
show (MATCH s _) = '"':s ++ "\""
make_matcher :: String -> (Sealed2 (PatchInfoAnd p) -> Bool) -> Matcher p
make_matcher s m = MATCH s m
apply_matcher :: Matcher p -> PatchInfoAnd p C(x y) -> Bool
apply_matcher (MATCH _ m) = m . seal2
parseMatch :: Patchy p => PatchMatch -> Either String (MatchFun p)
parseMatch (PatternMatch s) =
case parse match_parser "match" s of
Left err -> Left $ "Invalid -"++"-match pattern '"++s++
"'.\n"++ unlines (map (" "++) $ lines $ show err)
Right m -> Right m
match_pattern :: Patchy p => PatchMatch -> Matcher p
match_pattern p@(PatternMatch s) =
case parseMatch p of
Left err -> error err
Right m -> make_matcher s m
trivial :: Patchy p => MatchFun p
trivial = const True
\end{code}
\paragraph{Match}
Currently \verb!--match! accepts six primitive match types, although
there are plans to expand it to match more patterns. Also, note that the
syntax is still preliminary and subject to change.
The first match type accepts a literal string which is checked against
the patch name. The syntax is
\begin{verbatim}
darcs annotate
\end{verbatim}
This is useful for situations where a patch name contains characters that
could be considered special for regular expressions.
In this and the other match types, the argument must be enclosed in double
quotes if it contains spaces. You can escape a quote in the argument with a
backslash; backslash escapes itself, but it is treated literally if followed
by a character other than a double quote or backslash, so it is typically not
necessary to escape a backslash. No such escaping is necessary unless the
argument is enclosed in double quotes.
The second match type accepts a regular expression which is checked against
the patch name. The syntax is
\begin{verbatim}
darcs annotate
\end{verbatim}
Note that to match regexp metacharacters, such as \verb|(|, literally, they
must be escaped with backslash along with any embedded double quotes. To
match a literal backslash it must be written quadrupled in general, but often
it need not be escaped, since backslash is only special in regexps when
followed by a metacharacter. In the following example pairs, the first
literal is matched by the second sequence in the match name:
``\verb|"|'':``\verb|\"|'', ``\verb|\|'':``\verb|\\\\|'',
``\verb|\x|'':``\verb|\x|'', ``\verb|(|'':``\verb|\(|''.
The third match type matches the darcs hash for each patch:
\begin{verbatim}
darcs annotate
'hash 2004040310595853a90c719567e92c3b0ab9eddd5290b705712b8b918ef'
\end{verbatim}
Note you need to provide the full hash string as above.
This is intended to be used, for example, by programs allowing you to view
darcs repositories (e.g.\ CGI scripts like viewCVS).
The fourth match type accepts a regular expression which is checked against
the patch author. The syntax is
\begin{verbatim}
darcs annotate
\end{verbatim}
There is also support for matching by date. This is done using commands such as
\begin{verbatim}
darcs annotate
darcs annotate
darcs annotate
darcs annotate
darcs annotate
darcs changes
\end{verbatim}
Notes: when matching on the ISO format, a partial date is treated as a range.
English dates can either refer to a specific day (``6 months ago',``day before
yesterday''), or to an interval
from some past date (``last month'') to the present. Putting this all
together, if today is ``20040724'' then the following matches should work:
\begin{tabular}{|ll|}
\hline
\textbf{date} & \textbf{patches selected} \\
\hline
2004 & from 20040101 up to and including 20041231 \\
200401 & from 20040101 up to and including 20040131 \\
20040101 & during 20040101 \\
\hline
today & during 20040724 (starting midnight in your timezone) \\
yesterday & during 20040723 \\
6 months ago & during 20040123 \\
\hline
last 6 months & since 20040123 \\
last month & since 20040623 (not 20040601!) \\
last week & since 20040716 \\
\hline
\end{tabular}
For more precise control, you may specify an interval, either
in a small subset of English or
of \htmladdnormallinkfoot{the ISO 8601 format}{http://www.w3.org/TR/NOTEdatetime}.
If you use the ISO format, note that durations, when
specified alone, are interpreted as being relative to the current date and time.
\begin{verbatim}
darcs annotate
darcs annotate
darcs annotate
darcs annotate
darcs annotate
darcs annotate
\end{verbatim}
You may also prefer to combine date matching with a more specific pattern.
\begin{verbatim}
darcs annotate
\end{verbatim}
The sixth match type accepts a regular expression which is checked against
file paths that the patch touches. The syntax is
\begin{verbatim}
darcs annotate
\end{verbatim}
The \verb!--match! pattern can include the logical operators \verb!&&!,
\verb!||! and \verb!not!, as well as grouping of patterns with parentheses.
For example
\begin{verbatim}
darcs annotate
\end{verbatim}
\begin{code}
match_parser :: Patchy p => CharParser st (MatchFun p)
match_parser = do m <- option trivial submatch
eof
return m
submatch :: Patchy p => CharParser st (MatchFun p)
submatch = buildExpressionParser table match <?> "match rule"
table :: OperatorTable Char st (MatchFun p)
table = [ [prefix "not" negate_match,
prefix "!" negate_match ]
, [binary "||" or_match,
binary "or" or_match,
binary "&&" and_match,
binary "and" and_match ]
]
where binary name fun =
Infix (do trystring name
spaces
return fun) AssocLeft
prefix name fun = Prefix $ do trystring name
spaces
return fun
negate_match a p = not (a p)
or_match m1 m2 p = (m1 p) || (m2 p)
and_match m1 m2 p = (m1 p) && (m2 p)
trystring :: String -> CharParser st String
trystring s = try $ string s
match :: Patchy p => CharParser st (MatchFun p)
match = between spaces spaces
(parens submatch
<|> choice matchers_
<?> "simple match")
where matchers_ = map createMatchHelper primitiveMatchers
createMatchHelper :: (String, String, [String], String -> MatchFun p)
-> CharParser st (MatchFun p)
createMatchHelper (key,_,_,matcher) =
do trystring key
spaces
q <- quoted
return $ matcher q
helpOnMatchers :: String
helpOnMatchers =
let blurb :: (String, String, [String], String -> MatchFun Patch) -> String
blurb (key, help, examples, _) =
"'" ++ key ++ "' " ++ help ++
", e.g.:\n" ++ (unlines $ map (mkExample key) examples)
mkExample key x =
" darcs annotate --summary --match '" ++ key ++ " " ++ x ++ "'"
in "Matching patches:\n"
++ (unlines $ map blurb primitiveMatchers) ++ "\n"
++ "You can also use logical operators 'and', '&&', 'or', '||', 'not', '!'"
++ " to combine match expressions, as well as parentheses for grouping. "
++ " For more details on matching, see the manual."
primitiveMatchers :: Patchy p => [(String, String, [String], String -> MatchFun p)]
primitiveMatchers =
[ ("exact", "checks a literal string against the patch name"
, ["\"my most excellent patch\""]
, exactmatch )
, ("name", "checks a regular expression against the patch name"
, ["[eE]xcellent"]
, mymatch )
, ("author", "checks a regular expression against the author name"
, ["foo@bar"]
, authormatch )
, ("hash", "matches the darcs hash for a patch"
, ["20040403105958-53a90-c719567e92c3b0ab9eddd5290b705712b8b918ef"]
, hashmatch )
, ("date", "matches the patch date"
, ["\"tea time yesterday\"", "\"2006-04-02 22:41\""]
, datematch )
, ("touch", "matches file paths for a patch"
, ["\"foo|bar|splotz.*(c|h)\"", "\"some/thing/\""]
, touchmatch ) ]
parens :: CharParser st (MatchFun p)
-> CharParser st (MatchFun p)
parens p = between (string "(") (string ")") p
quoted :: CharParser st String
quoted = between (char '"') (char '"')
(many $ do { char '\\'
; try (oneOf ['\\', '"']) <|> return '\\'
}
<|> noneOf ['"'])
<|> between spaces spaces (many $ noneOf " ()")
<?> "string"
mymatch, exactmatch, authormatch, hashmatch, datematch, touchmatch :: Patchy p => String -> MatchFun p
mymatch r (Sealed2 hp) = isJust $ matchRegex (mkRegex r) $ just_name (info hp)
exactmatch r (Sealed2 hp) = r == (just_name (info hp))
authormatch a (Sealed2 hp) = isJust $ matchRegex (mkRegex a) $ just_author (info hp)
hashmatch h (Sealed2 hp) = let rh = make_filename (info hp) in
(rh == h) || (rh == h++".gz")
datematch d (Sealed2 hp) = let dm = unsafePerformIO $ parseDateMatcher d
in dm $ pi_date (info hp)
touchmatch r (Sealed2 hp) = let files = list_touched_files $ patchcontents $ hopefully hp
in or $ map (isJust . matchRegex (mkRegex r)) files
\end{code}