% Copyright (C) 2002,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. \begin{code} module DarcsCommands ( DarcsCommand( DarcsCommand, command_name, command_help, command_description, command_darcsoptions, command_command, command_prereq, command_extra_arg_help, command_extra_args, command_argdefaults, command_get_arg_possibilities ), usage, extended_usage, run_the_command, command_options, nodefaults, ) where import System.Console.GetOpt import Monad ( liftM ) import DarcsArguments import ArgumentDefaults ( get_default_flag ) \end{code} The general format of a darcs command is \begin{verbatim} % darcs COMMAND OPTIONS ARGUMENTS ... \end{verbatim} Here {\tt COMMAND} is a command such as {\tt add} or {\tt record}, which of course may have one or more arguments. Options have the form \verb!--option! or \verb!-o!, while arguments vary from command to command. There are many options which are common to a number of different commands, which will be summarized here. If you wish, you may use any unambiguous beginning of a command name as a shortcut: for \verb!darcs record!, you could type \verb!darcs recor! or \verb!darcs rec!, but not \verb!darcs re! since that could be confused with \verb!darcs replace!, \verb!darcs revert! and \verb!darcs remove!. \paragraph{Command overview} Not all commands modify the "patches" of your repository (that is, the named patches which other users can pull), some commands only affect the copy of the source tree you're working on (your "working directory"), and some affect both. This table summarizes what you should expect from each one and will hopefully serve as guide when you're having doubts about which command to use. \begin{center} \footnotetext[1]{But it affects the repository and working directory targeted by the push} \footnotetext[2]{As for the other end, see apply} \begin{tabular}{|c|c|c|} \hline affects & patches & working directory\\ \hline record & yes & no\\ \hline pull & yes & yes\\ \hline apply & yes & yes\\ \hline rollback & yes & no\\ \hline unrecord & yes & no\\ \hline unpull & yes & yes\\ \hline revert & no & yes\\ \hline unrevert & no & yes\\ \hline push\footnote{But it affects the repository and working directory targeted by the push} & no & no\\ \hline send\footnote{As for the other end, see apply} & no & no\\ \hline \end{tabular} \end{center} \begin{code} run_the_command :: String -> [String] -> [DarcsCommand] -> IO () run_the_command cmd args cs = case filter ((==cmd).take (length cmd).command_name) cs of [] -> fail $ "Invalid command '"++cmd++"'!\n\n" ++ usage cs [c] -> run_command c args cs' -> fail $ "Ambiguous command...\n\n" ++ "The command '"++cmd++"' could mean one of:\n" ++ unwords (map command_name cs') \end{code} \input{DarcsArguments.lhs} \begin{code} data DarcsCommand = DarcsCommand {command_name, command_help, command_description :: String, command_extra_args :: Int, command_extra_arg_help :: [String], command_command :: [DarcsFlag] -> [String] -> IO (), command_prereq :: [DarcsFlag] -> IO (Maybe FilePath, String), command_get_arg_possibilities :: IO [String], command_argdefaults :: [String] -> IO [String], command_darcsoptions :: [DarcsOption]} command_options :: DarcsCommand -> [OptDescr DarcsFlag] command_options c = concat $ map option_from_darcsoption $ command_darcsoptions c nodefaults :: [String] -> IO [String] nodefaults as = return as \end{code} \begin{code} extended_usage :: String extended_usage = "Usage: darcs COMMAND ..." ++ "\n" ++ "\nExtended Help:" ++ "\n" ++ "\nA darcs repository consists of:" ++ "\n" ++ "\n - a set of PATCHES" ++ "\n - a WORKING directory" ++ "\n" ++ "\nHere is a description of which of these components is altered by each" ++ "\ncommand, and how it is used or altered:" ++ "\n" ++ "\n whatsnew Show the differences between WORKING and the \"recorded\"" ++ "\n version, that is, the result of applying all PATCHES in the" ++ "\n repository. This difference, we will call LOCAL CHANGES." ++ "\n" ++ "\n record Add a patch to PATCHES representing the LOCAL CHANGES." ++ "\n" ++ "\n unrecord Delete a patch from PATCHES, but *do not* alter WORKING." ++ "\n This works for any patch, not just one that was previously " ++ "\n \"record\"ed" ++ "\n" ++ "\n revert Remove LOCAL CHANGES. Note that this command is interactive," ++ "\n so you can use it to revert only some of these changes." ++ "\n" ++ "\n unrevert Undo the last revert operation." ++ "\n" ++ "\n unpull Delete a patch from PATCHES and unapply it from WORKING." ++ "\n Note that this command works for any patch, not just one that" ++ "\n was previously \"pull\"ed. If there are no LOCAL CHANGES," ++ "\n this command is equivalent to \"darcs unrecord; darcs revert\"" ++ "\n" ++ "\n rollback Create the inverse of a particular patch and add it to PATCHES," ++ "\n but DO NOT apply it to WORKING. Note that this command is the" ++ "\n only way to wind up with a patch in PATCHES which has not been" ++ "\n applied to WORKING." ++ "\n" \end{code} \begin{code} usage :: [DarcsCommand] -> String usage cs = "Usage: darcs COMMAND ...\nCommands:\n" ++ usage_helper cs ++ "Type \"darcs --extended-help\" for a more technical " ++ "description of some of these commands.\n" ++ "Type \"darcs COMMAND --help\" for help on a particular command.\n" ++ "Type \"darcs --version\" to see the darcs version number and" ++ " type \"darcs --exact-version\"\n to get the precise contents" ++ " of the repository from which this darcs was compiled.\n" usage_helper :: [DarcsCommand] -> String usage_helper [] = "" usage_helper (c:cs) = " "++pad_spaces (command_name c) 14 ++ chomp_newline (command_description c)++"\n"++usage_helper cs where chomp_newline "" = "" chomp_newline s = if last s == '\n' then init s else s pad_spaces :: String -> Int -> String pad_spaces s n = s ++ replicate (n - length s) ' ' \end{code} \begin{comment} This is the actual heavy lifter code, which is responsible for parsing the arguments and then running the command itself. \end{comment} \begin{code} run_command :: DarcsCommand -> [String] -> IO () run_command cmd args = case getOpt RequireOrder (option_from_darcsoption help++ option_from_darcsoption list_options++options) args of (opts,extra,[]) -> case opts of [Help] -> putStr $ get_command_help cmd (ListOptions:_) -> do (maybe_fix, complaint) <- command_prereq cmd opts fix <- case maybe_fix of Just f -> return f Nothing -> fail $ "Can't run command " ++ command_name cmd ++" here.\n" ++ complaint command_args <- unfix_filepaths [FixFilePath fix] `liftM` command_get_arg_possibilities cmd putStr $ get_command_options cmd++unlines command_args++"\n" _ -> consider_running cmd opts extra (_,_,ermsgs) -> do fail $ unlines ermsgs where options = command_options cmd consider_running :: DarcsCommand -> [DarcsFlag] -> [String] -> IO () consider_running cmd opts old_extra = do (is_ok, complaint) <- (command_prereq cmd) opts case is_ok of Nothing -> fail $ "Unable to \"darcs "++ command_name cmd ++ "\" here.\n\n" ++ complaint Just fix_path -> do extra <- (command_argdefaults cmd) old_extra specops <- add_command_defaults cmd $ map (fix_flag fix_path) opts if command_extra_args cmd < 0 then (command_command cmd) (FixFilePath fix_path:specops) extra else if length extra > command_extra_args cmd then fail $ "Bad argument: `"++unwords extra++"'\n"++ get_command_help cmd else if length extra < command_extra_args cmd then fail $ "Missing argument: " ++ nth_arg (length extra + 1) ++ "\n" ++ get_command_help cmd else (command_command cmd) (FixFilePath fix_path:specops) extra where nth_arg n = nth_of n (command_extra_arg_help cmd) nth_of 1 (h:_) = h nth_of n (_:hs) = nth_of (n-1) hs nth_of _ [] = "UNDOCUMENTED" add_command_defaults :: DarcsCommand -> [DarcsFlag] -> IO [DarcsFlag] add_command_defaults cmd already = acd (command_name cmd) already (command_darcsoptions cmd) acd :: String -> [DarcsFlag] -> [DarcsOption] -> IO [DarcsFlag] acd _ flags [] = return flags acd c flags (dao:dos) = case dao of DarcsNoArgOption _ _ f _ -> if f `elem` flags then acd c flags dos else do flags' <- get_default_flag c dao acd c (flags++flags') dos DarcsArgOption _ _ f _ _ -> if f `isin` flags then acd c flags dos else do flags' <- get_default_flag c dao acd c (flags++flags') dos DarcsMultipleChoiceOption os -> if os `arein` flags then acd c flags dos else do flags' <- get_default_flag c dao acd c (flags++flags') dos where f `isin` fs = any (`isa` f) fs (DarcsNoArgOption _ _ f _ : dos') `arein` fs = f `elem` fs || dos' `arein` fs (DarcsArgOption _ _ f _ _ : dos') `arein` fs = f `isin` fs || dos' `arein` fs (DarcsMultipleChoiceOption os: dos') `arein` fs = os `arein` fs || dos' `arein` fs [] `arein` _ = False get_command_options :: DarcsCommand -> String get_command_options cmd = (get_options_options options) ++ "--help\n" where options = command_options cmd get_options_options :: [OptDescr DarcsFlag] -> String get_options_options [] = "" get_options_options (o:os) = get_long_option o ++"\n"++ get_options_options os get_long_option :: OptDescr DarcsFlag -> String get_long_option (Option _ [] _ _) = "" get_long_option (Option a (o:os) b c) = "--"++o++ get_long_option (Option a os b c) get_command_help :: DarcsCommand -> String get_command_help cmd = (usageInfo ("Usage: darcs "++name++" [OPTION]... " ++ args_help ++ "\n"++ description++"\nOptions:") (options++option_from_darcsoption help)) ++ help_message where options = command_options cmd name = command_name cmd description = command_description cmd help_message = command_help cmd args_help = unwords (command_extra_arg_help cmd) \end{code}