-- Copyright (C) 2007 Kevin Quick -- -- 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 02110-1301, USA. module Darcs.UI.Commands.ShowRepo ( showRepo ) where import Prelude () import Darcs.Prelude import Data.Char ( toLower, isSpace ) import Data.List ( intercalate ) import Control.Monad ( when, unless, liftM ) import Text.Html ( tag, stringToHtml ) import Darcs.Util.Path ( AbsolutePath ) import Darcs.UI.Flags ( DarcsFlag, useCache, hasXmlOutput, verbose, enumeratePatches ) import Darcs.UI.Options ( (^), oid, odesc, ocheck, onormalise, defaultFlags, (?) ) import qualified Darcs.UI.Options.All as O import Darcs.UI.Commands ( DarcsCommand(..), withStdOpts, nodefaults, amInRepository ) import Darcs.UI.Completion ( noArgs ) import Darcs.Repository ( Repository , repoFormat , repoLocation , repoPristineType , repoCache , withRepository , RepoJob(..) , readRepo ) import Darcs.Repository.Hashed( repoXor ) import Darcs.Repository.PatchIndex ( isPatchIndexDisabled, doesPatchIndexExist ) import Darcs.Repository.Prefs ( getPreflist, getMotd ) import Darcs.Patch ( IsRepoType, RepoPatch ) import Darcs.Patch.Set ( patchSet2RL ) import Darcs.Patch.Witnesses.Ordered ( lengthRL ) import qualified Data.ByteString.Char8 as BC (unpack) import Darcs.Patch.Apply( ApplyState ) import Darcs.Util.Tree ( Tree ) showRepoHelp :: String showRepoHelp = "The `darcs show repo` command displays statistics about the current\n" ++ "repository, allowing third-party scripts to access this information\n" ++ "without inspecting `_darcs` directly (and without breaking when the\n" ++ "`_darcs` format changes).\n" ++ "\n" ++ "The 'Weak Hash' identifies the set of patches of a repository independently\n" ++ "of ordering. It can be used to easily compare two repositories of a same\n" ++ "project. It is not cryptographically secure.\n" ++ "\n" ++ "By default, output includes statistics that require walking through the patches\n" ++ "recorded in the repository, namely the 'Weak Hash' and the count of patches.\n" ++ "If this data isn't needed, use `--no-enum-patches` to accelerate this command\n" ++ "from O(n) to O(1).\n" ++ "\n" ++ "By default, output is in a human-readable format. The `--xml-output`\n" ++ "option can be used to generate output for machine postprocessing.\n" showRepoDescription :: String showRepoDescription = "Show repository summary information" showRepo :: DarcsCommand [DarcsFlag] showRepo = DarcsCommand { commandProgramName = "darcs" , commandName = "repo" , commandHelp = showRepoHelp , commandDescription = showRepoDescription , commandExtraArgs = 0 , commandExtraArgHelp = [] , commandCommand = repoCmd , commandPrereq = amInRepository , commandCompleteArgs = noArgs , commandArgdefaults = nodefaults , commandAdvancedOptions = [] , commandBasicOptions = odesc showRepoBasicOpts , commandDefaults = defaultFlags showRepoOpts , commandCheckOptions = ocheck showRepoOpts , commandParseOptions = onormalise showRepoOpts } where showRepoBasicOpts = O.repoDir ^ O.xmlOutput ^ O.enumPatches showRepoOpts = showRepoBasicOpts `withStdOpts` oid repoCmd :: (AbsolutePath, AbsolutePath) -> [DarcsFlag] -> [String] -> IO () repoCmd _ opts _ = let put_mode = if hasXmlOutput opts then showInfoXML else showInfoUsr in withRepository (useCache ? opts) $ RepoJob $ \repository -> actuallyShowRepo (putInfo put_mode) repository opts -- Some convenience functions to output a labelled text string or an -- XML tag + value (same API). If no value, output is suppressed -- entirely. Borrow some help from Text.Html to perform XML output. type ShowInfo = String -> String -> String showInfoXML :: ShowInfo showInfoXML t i = show $ tag (safeTag t) $ stringToHtml i safeTag :: String -> String safeTag [] = [] safeTag (' ':cs) = safeTag cs safeTag ('#':cs) = "num_" ++ safeTag cs safeTag (c:cs) = toLower c : safeTag cs -- labelled strings: labels are right-aligned at 15 characters; -- subsequent lines in multi-line output are indented accordingly. showInfoUsr :: ShowInfo showInfoUsr t i = replicate (15 - length t) ' ' ++ t ++ ": " ++ intercalate ('\n' : replicate 17 ' ') (lines i) ++ "\n" type PutInfo = String -> String -> IO () putInfo :: ShowInfo -> PutInfo putInfo m t i = unless (null i) (putStr $ m t i) -- Primary show-repo operation. Determines ordering of output for -- sub-displays. The `out' argument is one of the above operations to -- output a labelled text string or an XML tag and contained value. actuallyShowRepo :: (IsRepoType rt, RepoPatch p, ApplyState p ~ Tree) => PutInfo -> Repository rt p wR wU wR -> [DarcsFlag] -> IO () actuallyShowRepo out r opts = do when (hasXmlOutput opts) (putStr "\n") when (verbose opts) (out "Show" $ show r) out "Format" $ showInOneLine $ repoFormat r let loc = repoLocation r out "Root" loc out "PristineType" $ show $ repoPristineType r out "Cache" $ showInOneLine $ repoCache r piExists <- doesPatchIndexExist loc piDisabled <- isPatchIndexDisabled loc out "PatchIndex" $ case (piExists, piDisabled) of (_, True) -> "disabled" (True, False) -> "enabled" (False, False) -> "enabled, but not yet created" showRepoPrefs out when (enumeratePatches opts) (do numPatches r >>= (out "Num Patches" . show) showXor out r) showRepoMOTD out r when (hasXmlOutput opts) (putStr "\n") showXor :: (IsRepoType rt, RepoPatch p, ApplyState p ~ Tree) => PutInfo -> Repository rt p wR wU wR -> IO () showXor out repo = do theXor <- repoXor repo out "Weak Hash" (show theXor) -- Most of the actual elements being displayed are part of the Show -- class; that's fine for a Haskeller, but not for the common user, so -- the routines below work to provide more human-readable information -- regarding the repository elements. showInOneLine :: Show a => a -> String showInOneLine = intercalate ", " . lines . show showRepoPrefs :: PutInfo -> IO () showRepoPrefs out = do getPreflist "prefs" >>= mapM_ prefOut getPreflist "author" >>= out "Author" . unlines getPreflist "defaultrepo" >>= out "Default Remote" . unlines where prefOut = uncurry out . (\(p,v) -> (p++" Pref", dropWhile isSpace v)) . break isSpace showRepoMOTD :: RepoPatch p => PutInfo -> Repository rt p wR wU wR -> IO () showRepoMOTD out repo = getMotd (repoLocation repo) >>= out "MOTD" . BC.unpack -- Support routines to provide information used by the PutInfo operations above. numPatches :: (IsRepoType rt, RepoPatch p, ApplyState p ~ Tree) => Repository rt p wR wU wR -> IO Int numPatches r = (lengthRL . patchSet2RL) `liftM` readRepo r