1 -- | This program mangles a pseudo-LaTeX document into actual LaTeX.
    2 -- There are three key changes to the input:
    3 --
    4 --   * \\input{foo} is replaced by the contents of the file foo (after
    5 --     it, too, is mangled).  Note that this is relative to the
    6 --     working directory, *not* relative to the file being parsed.
    7 --
    8 --   * Anything between \\begin{code} and \\end{code} is deleted.
    9 --     Note that this is quite unlike normal literate documentation
   10 --     (for which we use Haddock, not LaTeX).
   11 --
   12 --   * Some nonstandard pseudo-LaTeX commands are expanded into actual
   13 --     LaTeX text.  In particular, \\darcsCommand{foo} is replaced by
   14 --     LaTeX markup describing the command @foo@.
   15 module Preproc ( preproc_main ) where
   16 import System.FilePath ( (</>) )
   17 import System.Environment ( getArgs )
   18 import System.Exit ( exitWith, ExitCode(..) )
   19 import Text.Regex ( matchRegex, mkRegex )
   20 import Darcs.Commands ( DarcsCommand(SuperCommand,
   21                         command_sub_commands, command_name,
   22                         command_extra_arg_help, command_basic_options,
   23                         command_advanced_options, command_help,
   24                         command_description),
   25                         extract_commands )
   26 import Darcs.Arguments ( options_latex )
   27 import Darcs.Commands.Help ( command_control_list, environmentHelp )
   28 import English ( andClauses )
   29 import ThisVersion ( darcs_version )
   30 
   31 the_commands :: [DarcsCommand]
   32 the_commands = extract_commands command_control_list
   33 
   34 -- | The entry point for this program.  The path to the TeX master
   35 -- file is supplied as the first argument.  Bootstrapping into
   36 -- 'preproc' then happens by passing it a pseudo-document that
   37 -- contains a single input (include) line.
   38 preproc_main :: [String] -> IO ()
   39 preproc_main args = do
   40   if length args < 1
   41      then exitWith $ ExitFailure 1
   42      else return ()
   43   putStrLn "%% This file was automatically generated by preproc."
   44   c <- preproc ["\\input{"++head args++"}"]
   45   mapM_ putStrLn c
   46 
   47 -- | Depending on whether pdflatex or htlatex is to be used, the LaTeX
   48 -- output of this program must vary subtly.  This procedure returns
   49 -- true iff the command-line arguments contain @--html@.
   50 am_html :: IO Bool
   51 am_html = do args <- getArgs
   52              return $ elem "--html" args
   53 
   54 -- | Given a list of input lines in pseudo-LaTeX, return the same
   55 -- document in LaTeX.  The pseudo-LaTeX lines are replaced, other
   56 -- lines are used unmodified.
   57 preproc :: [String] -> IO [String]
   58 preproc [] = return []              -- Empty input, empty output.
   59 preproc ("\\usepackage{html}":ss) = -- only use html package with latex2html
   60     do rest <- preproc ss
   61        ah <- am_html
   62        if ah then return $ "\\usepackage{html}" : rest
   63              else return $ "\\usepackage{hyperref}" : rest
   64 preproc ("\\begin{code}":ss) = ignore ss
   65     where ignore :: [String] -> IO [String]
   66           ignore ("\\end{code}":ss') = preproc ss'
   67           ignore (_:ss') = ignore ss'
   68           ignore [] = return []
   69 preproc ("\\begin{options}":ss) =
   70     do rest <- preproc ss
   71        ah <- am_html
   72        if ah then return $ "\\begin{rawhtml}" : "<div class=\"cmd-opt-hdr\">" : rest
   73              else return $ ("\\begin{Verbatim}[frame=lines,xleftmargin=1cm," ++
   74                             "xrightmargin=1cm]") : rest
   75 preproc ("\\end{options}":ss) =
   76     do rest <- preproc ss
   77        ah <- am_html
   78        if ah then return $ "</div>" : "\\end{rawhtml}" : rest
   79              else return $ "\\end{Verbatim}" : rest
   80 preproc ("\\darcsVersion":ss) = do
   81   rest <- preproc ss
   82   return $ darcs_version:rest
   83 preproc (s:ss) = do
   84   rest <- preproc ss
   85   let rx = mkRegex "^\\\\(input|darcs(Command|Env))\\{(.+)\\}$"
   86   case matchRegex rx s of
   87     Just ["input", _, path] ->
   88         do cs <- readFile $ "src" </> path -- ratify readFile: not part of darcs executable
   89            this <- preproc $ lines cs
   90            return $ this ++ rest
   91     Just ["darcsCommand", _, command] ->
   92         return $ commandHelp command : rest
   93     Just ["darcsEnv", _, variable] ->
   94         return $ envHelp variable : rest
   95     -- The base case for the whole preproc function.  Nothing to
   96     -- mangle, so this is an ordinary line of TeX, and we append it to
   97     -- the result unmodified.
   98     _ -> return $ s : rest
   99 
  100 commandHelp :: String -> String
  101 commandHelp command = section ++ "{darcs " ++ command ++ "}\n" ++
  102                       "\\label{" ++ command ++ "}\n" ++
  103                       gh ++ get_options command ++ gd
  104     where
  105       section = if ' ' `elem` command then "\\subsubsection" else "\\subsection"
  106       -- | Given a Darcs command name as a string, return that command's (multi-line) help string.
  107       gh :: String
  108       gh =  escape_latex_specials $ command_property command_help the_commands command
  109       -- | Given a Darcs command name as a string, return that command's (one-line) description string.
  110       gd :: String
  111       gd = command_property command_description the_commands command
  112 
  113 get_options :: String -> String
  114 get_options comm = get_com_options $ get_c names the_commands
  115     where names = words comm
  116 
  117 get_c :: [String] -> [DarcsCommand] -> [DarcsCommand]
  118 get_c (name:ns) commands =
  119     case ns of
  120     [] -> [get name commands]
  121     _ -> case get name commands of
  122          c@SuperCommand { } ->
  123              c:(get_c ns $ extract_commands $ command_sub_commands c)
  124          _ ->
  125              error $ "Not a supercommand: " ++ name
  126     where get n (c:cs) | command_name c == n = c
  127                        | otherwise = get n cs
  128           get n [] = error $ "No such command:  "++n
  129 get_c [] _ = error "no command specified"
  130 
  131 get_com_options :: [DarcsCommand] -> String
  132 get_com_options c =
  133     "\\par\\verb!Usage: darcs " ++ cmd ++ " [OPTION]... " ++
  134     args ++ "!\n\n" ++ "Options:\n\n" ++ options_latex opts1 ++
  135     (if null opts2 then "" else "\n\n" ++ "Advanced options:\n\n" ++ options_latex opts2)
  136     where cmd = unwords $ map command_name c
  137           args = unwords $ command_extra_arg_help $ last c
  138           opts1 = command_basic_options $ last c
  139           opts2 = command_advanced_options $ last c
  140 
  141 command_property :: (DarcsCommand -> String) -> [DarcsCommand] -> String
  142                  -> String
  143 command_property property commands name =
  144     property $ last c
  145     where names = words name
  146           c = get_c names commands
  147 
  148 
  149 
  150 envHelp :: String -> String
  151 envHelp var = unlines $ render $ entry environmentHelp
  152     where render (ks, ds) =
  153               ("\\paragraph{" ++ escape_latex_specials (andClauses ks) ++ "}") :
  154               ("\\label{env:" ++ var ++ "}") :
  155               map escape_latex_specials ds
  156           entry [] = undefined
  157           entry (x:xs) | elem var $ fst x = x
  158                        | otherwise = entry xs
  159 
  160 -- | LaTeX treats a number of characters or sequences specially.
  161 -- Therefore when including ordinary help text in a LaTeX document, it
  162 -- is necessary to escape these characters in the way LaTeX expects.
  163 escape_latex_specials :: String -> String
  164 -- Order is important
  165 escape_latex_specials =
  166   (bs2 . amp . percent . carrot . dollar . underscore . rbrace . lbrace . bs1)
  167   where
  168     amp        = replace "&"  "\\&"
  169     bs1        = replace "\\" "\001"
  170     bs2        = replace "\001" "$\\backslash$"
  171     carrot     = replace "^"  "\\^{}"
  172     dollar     = replace "$"  "\\$"
  173     lbrace     = replace "{"  "\\{"
  174     percent    = replace "%"  "\\%"
  175     rbrace     = replace "}"  "\\}"
  176     underscore = replace "_"  "\\_"
  177 
  178     replace :: Eq a => [a] -> [a] -> [a] -> [a]
  179     replace _ _ [] = []
  180     replace find repl s =
  181         if take (length find) s == find
  182             then repl ++ (replace find repl (drop (length find) s))
  183             else [head s] ++ replace find repl (tail s)