% Copyright (C) 2003 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; if not, write to the Free Software Foundation,
% Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
\section{darcs annotate}
\label{annotate}
\begin{code}
module Annotate ( annotate ) where
import Monad ( liftM, when )
import List ( sort )
import Workaround ( getCurrentDirectory )
import DarcsCommands ( DarcsCommand(..), nodefaults )
import DarcsArguments ( DarcsFlag(..), working_repo_dir,
verbose, summary, unified, human_readable,
xmloutput,
fix_filepath,
list_registered_files,
match_one,
)
import SlurpDirectory ( slurp )
import Repository ( am_in_repo, read_repo, get_markedup_file )
import Patch ( LineMark(..), patch2patchinfo,
patch_summary, xml_summary,
)
import FastPackedString ( PackedString, unpackPS )
import PrintPatch ( printPatch, contextualPrintPatch )
import PatchInfo ( PatchInfo, human_friendly, to_xml )
import PopulationData ( Population(..), PopTree(..), DirMark(..),
nameI, modifiedByI, modifiedHowI,
createdByI, creationNameI,
)
import Population ( getRepoPopVersion, lookup_pop,
modified_to_xml,
)
import FileName ( fp2fn, fn2fp, norm_path )
import Match ( match_patch, have_nonrange_match, get_first_match )
import Lock ( withTempDir )
#include "impossible.h"
\end{code}
\options{annotate}
\haskell{annotate_description}
\begin{code}
annotate_description :: String
annotate_description = "Display useful information about the repository history.\n"
\end{code}
\haskell{annotate_help}
\begin{code}
annotate_help :: String
annotate_help =
"Annotate allows you to extract all sorts of interesting information from\n"++
"your repository.\n"
\end{code}
\begin{code}
annotate :: DarcsCommand
annotate = DarcsCommand {command_name = "annotate",
command_help = annotate_help,
command_description = annotate_description,
command_extra_args = -1,
command_extra_arg_help = ["[FILE or DIRECTORY]..."],
command_command = annotate_cmd,
command_prereq = am_in_repo,
command_get_arg_possibilities = list_registered_files,
command_argdefaults = nodefaults,
command_darcsoptions = [verbose,summary,unified,
human_readable,
xmloutput,
match_one,
working_repo_dir]}
\end{code}
When called with just a patch name, annotate outputs the patch in darcs
format. The \verb!--summary!, \verb!--human-readable! or \verb!--unified!
options may be used to modify the format with which it is displayed.
\begin{code}
annotate_cmd :: [DarcsFlag] -> [String] -> IO ()
annotate_cmd opts [] = do
when (not $ have_nonrange_match opts) $
fail $ "Annotate requires either a patch pattern or a " ++
"file or directory argument."
p <- match_patch opts `liftM` read_repo "."
repodir <- getCurrentDirectory
if Summary `elem` opts
then do putStrLn $ showpi $ fromJust $ patch2patchinfo p
putStr $ show_summary p
else if Unified `elem` opts
then withTempDir "context" $ \_ ->
do get_first_match repodir opts
c <- slurp "."
contextualPrintPatch c p
else printPatch p
where showpi = if MachineReadable `elem` opts
then show
else if XMLOutput `elem` opts
then to_xml
else human_friendly
show_summary = if XMLOutput `elem` opts
then xml_summary
else patch_summary
\end{code}
If a directory name is given, annotate will output the contents of that
directory. If a patch name is given, the contents of that directory after
that patch was applied will be output. If a tag name is given, the
contents of that directory in the specified tagged version will be output.
\begin{code}
annotate_cmd opts [rel_file_or_directory] = do
r <- read_repo "."
pinfo <- if have_nonrange_match opts
then return $ fromJust $ patch2patchinfo $ match_patch opts r
else case concat r of
[] -> fail "Annotate doesn't yet work right on empty repos."
((x,_):_) -> return x
pop <- getRepoPopVersion "." pinfo
if file_or_directory == ""
then case pop of (Pop _ pt) -> annotate_pop opts pinfo pt
else case lookup_pop file_or_directory pop of
Nothing -> fail $ "There is no file or directory named '"++
file_or_directory++"'"
Just (Pop _ pt@(PopDir i _))
| modifiedHowI i == RemovedDir ->
fail $ "The directory '" ++ rel_file_or_directory ++
"' was removed by\n" ++ human_friendly (modifiedByI i)
| otherwise -> annotate_pop opts pinfo pt
Just (Pop _ pt@(PopFile i))
| modifiedHowI i == RemovedFile ->
fail $ "The file '" ++ rel_file_or_directory ++
"' was removed by\n" ++ human_friendly (modifiedByI i)
| otherwise -> annotate_file opts pinfo file_or_directory pt
where file_or_directory = fn2fp $ norm_path $ fp2fn $
fix_filepath opts rel_file_or_directory
\end{code}
\begin{code}
annotate_cmd _ _ = fail "annotate accepts at most one argument"
\end{code}
\begin{code}
annotate_pop :: [DarcsFlag] -> PatchInfo -> PopTree -> IO ()
annotate_pop opts pinfo pt = putStr $ p2format pinfo pt
where p2format = if XMLOutput `elem` opts
then p2xml
else p2s
\end{code}
\begin{code}
indent :: String -> String
indent = unlines . map i . lines
where i "" = ""
i ('#':s) = ('#':s)
i s = " "++s
-- Annotate a directory listing
p2s :: PatchInfo -> PopTree -> String
p2s pinfo (PopFile info) =
created_str ++ f ++ file_change ++ "\n"
where f = unpackPS $ nameI info
file_created = "Created by " ++ show (fromJust $ createdByI info) ++
" as " ++ unpackPS (fromJust $ creationNameI info)++"\n"
created_str = unlines $ map ("# "++) $ lines file_created
file_change = if modifiedByI info == pinfo
then " "++show (modifiedHowI info)
else ""
p2s pinfo (PopDir info pops) =
created_str ++ dir ++ dir_change ++ "\n" ++
concat (map (indent . (p2s pinfo)) $ sort pops)
where dir = unpackPS (nameI info) ++ "/"
dir_created =
if createdByI info /= Nothing
then "Created by " ++ show (fromJust $ createdByI info) ++
" as " ++ unpackPS (fromJust $ creationNameI info)++"/\n"
else "Root directory"
created_str = unlines $ map ("# "++) $ lines dir_created
dir_change = if modifiedByI info == pinfo
then " "++show (modifiedHowI info)
else ""
\end{code}
\begin{code}
escapeXML :: String -> String
escapeXML = strReplace '\'' "'" . strReplace '"' """ .
strReplace '<' "<" . strReplace '&' "&"
strReplace :: Char -> String -> String -> String
strReplace _ _ [] = []
strReplace x y (z:zs)
| x == z = y ++ (strReplace x y zs)
| otherwise = z : (strReplace x y zs)
created_as_xml :: PatchInfo -> String -> String
created_as_xml pinfo as = "\n"++ to_xml pinfo ++"\n"
--removed_by_xml :: PatchInfo -> String
--removed_by_xml pinfo = "\n"++to_xml pinfo++"\n"
p2xml_open :: PatchInfo -> PopTree -> String
p2xml_open _ (PopFile info) =
"\n" ++
created ++ modified
where f = unpackPS $ nameI info
created = case createdByI info of
Nothing -> ""
Just ci -> created_as_xml ci
(unpackPS $ fromJust $ creationNameI info)
modified = modified_to_xml info
p2xml_open _ (PopDir info _) =
"\n" ++ created ++ modified
where f = unpackPS $ nameI info
created = case createdByI info of
Nothing -> ""
Just ci -> created_as_xml ci
(unpackPS $ fromJust $ creationNameI info)
modified = modified_to_xml info
p2xml_close :: PatchInfo -> PopTree -> String
p2xml_close _(PopFile _) = "\n"
p2xml_close _ (PopDir _ _) = "\n"
p2xml :: PatchInfo -> PopTree -> String
p2xml pinf p@(PopFile _) = p2xml_open pinf p ++ p2xml_close pinf p
p2xml pinf p@(PopDir _ pops) = p2xml_open pinf p ++
concat (map (p2xml pinf) $ sort pops) ++
p2xml_close pinf p
\end{code}
If a file name is given, the contents of that file will be output, along
with markup indicating when each line was last (and perhaps next) modified.
\begin{code}
annotate_file :: [DarcsFlag] -> PatchInfo -> FilePath -> PopTree -> IO ()
annotate_file opts pinfo f (PopFile info) = do
if XMLOutput `elem` opts
then putStr $ p2xml_open pinfo (PopFile info)
else if createdByI info /= Nothing
then putAnn $ "File "++f++" created by "++
show ci ++ " as " ++ createdname
else putAnn $ "File "++f
mk <- get_markedup_file ci createdname
old_pis <- (dropWhile (/= pinfo).map fst.concat) `liftM` read_repo "."
sequence_ $ map (annotate_markedup opts pinfo old_pis) mk
when (XMLOutput `elem` opts) $ putStr $ p2xml_close pinfo (PopFile info)
where ci = fromJust $ createdByI info
createdname = unpackPS $ fromJust $ creationNameI info
annotate_file _ _ _ _ = impossible
annotate_markedup :: [DarcsFlag] -> PatchInfo -> [PatchInfo]
-> (PackedString, LineMark) -> IO ()
annotate_markedup opts | XMLOutput `elem` opts = xml_markedup
| otherwise = text_markedup
text_markedup :: PatchInfo -> [PatchInfo] -> (PackedString, LineMark) -> IO ()
text_markedup _ _ (l,None) = putLine ' ' l
text_markedup pinfo old_pis (l,RemovedLine wheni) =
if wheni == pinfo
then putLine '-' l
else if wheni `elem` old_pis
then return ()
else putLine ' ' l
text_markedup pinfo old_pis (l,AddedLine wheni) =
if wheni == pinfo
then putLine '+' l
else if wheni `elem` old_pis
then do putAnn $ "Following line added by "++show wheni
putLine ' ' l
else return ()
text_markedup pinfo old_pis (l,AddedRemovedLine whenadd whenrem)
| whenadd == pinfo = do putAnn $ "Following line removed by "++show whenrem
putLine '+' l
| whenrem == pinfo = do putAnn $ "Following line added by "++show whenadd
putLine '-' l
| whenadd `elem` old_pis && not (whenrem `elem` old_pis) =
do putAnn $ "Following line removed by "++show whenrem
putAnn $ "Following line added by "++show whenadd
putLine ' ' l
| otherwise = return ()
putLine :: Char -> PackedString -> IO ()
putLine c s = putStrLn $ c : unpackPS s
putAnn :: String -> IO ()
putAnn s = putStr $ unlines $ map ("# "++) $ lines s
xml_markedup :: PatchInfo -> [PatchInfo] -> (PackedString, LineMark) -> IO ()
xml_markedup _ _ (l,None) = putLine ' ' l
xml_markedup pinfo old_pis (l,RemovedLine wheni) =
if wheni == pinfo
then putStr $ "" ++
escapeXML (unpackPS l) ++ "\n"
else if wheni `elem` old_pis
then return ()
else putStr $ "" ++
"" ++ to_xml wheni ++ "" ++
escapeXML (unpackPS l) ++ "\n"
xml_markedup pinfo old_pis (l,AddedLine wheni) =
if wheni == pinfo
then putStr $ "" ++
escapeXML (unpackPS l) ++ "\n"
else if wheni `elem` old_pis
then putStr $ "" ++
"" ++ to_xml wheni ++ "" ++
escapeXML (unpackPS l) ++ "\n"
else return ()
xml_markedup pinfo old_pis (l,AddedRemovedLine whenadd whenrem)
| whenadd == pinfo =
putStr $ "" ++
"" ++ to_xml whenrem ++ "" ++
escapeXML (unpackPS l) ++ "\n"
| whenrem == pinfo =
putStr $ "" ++
"" ++ to_xml whenadd ++ "" ++
escapeXML (unpackPS l) ++ "\n"
| whenadd `elem` old_pis && not (whenrem `elem` old_pis) =
putStr $ "" ++
"" ++ to_xml whenrem ++ "" ++
"" ++ to_xml whenadd ++ "" ++
escapeXML (unpackPS l) ++ "\n"
| otherwise = return ()
\end{code}