arbtt 0.4 → 0.4.1
raw patch · 11 files changed
+213/−229 lines, 11 filesdep −setlocaledep −tabular
Dependencies removed: setlocale, tabular
Files
- README +9/−108
- arbtt.cabal +6/−3
- src/Capture.hs +11/−7
- src/Categorize.hs +6/−8
- src/Stats.hs +101/−84
- src/System/Locale/SetLocale.hsc +61/−0
- src/TimeLog.hs +2/−2
- src/UpgradeLog1.hs +1/−1
- src/capture-main.hs +2/−2
- src/dump-main.hs +1/−1
- src/stats-main.hs +13/−13
README view
@@ -28,112 +28,15 @@ If you want to record samples at a different rate than one per minute, you will have to pass the "--sample-rate" parameter to arbtt-capture. -Configuration-=============--Once arbtt-capture is running, it will start recording, without further-configuration. The configuration is only needed to do an analysis of the-recorded data. Thus, if you improve your categorization later, it will-apply even to previous data samples!--The configuration file needs to be placed in ~/.arbtt/categorize.cfg. An-example file is included, which should be more enlighting than this rather-formal description.--Whitespace is non-significant and Haskell-style comments are allowed.--Here is grammar of the file:--Rules := [ "aliases" AliasSpec ] Rule ( ("," Rule)* | (";" Rule)* )--Rule := "{" Rules "}" | Cond "==>" Rule | "if" Cond "then" Rule "else" Rule- | "tag" Tag--Cond := "(" Cond ")" | "!" Cond | Cond "&&" Cond | Cond "||" Cond - | "$" SVar "==" String | "$" SVar "/=" String- | "$" SVar "=~" "/" Regex "/" |- | "$" NVar NOp Number- | "$" TVar NOp TSpec- | "$" BVar- | "current window" Cond- | "any window" Cond--NOp := "<=" | "<" | "==" | ">" | ">="--SVar := "title" | "program"-NVar := "idle"-BVar := "active"-TVar := "time" | "sampleage"--The variables $title, $program, $active refer to the "window in scope". At-first, no window is in scope. Only when evaluating the condition passed to-"current window" or "any window", this changes.--For "current window", the currently active window is in scopre. If there is-no such window, the condition is false.--For "any window", the condition is applied to each window, and if any-window matches the condition, the result is true. If it matches more than-one window, of which match the variables $1,.. (see below) will be taken.--Tags are not enclosed in quotation marks, and contain only letters, dashes-or underscores (e.g. work).--They may be prepended with a category,-separated by a colon (e.g. Project:arbtt). This category can later be used-for pie chart like statistics. For each category, no more than one tags can-be matched per sample. If multiple tags with the same category are-generated by the rules, only the first one will be taken.--A tag can also interpolate variables, including in the category part.-Available variables are:- $1, $2,.. Matches from the last successfully applied regular expression- in the enclosing condition- $current.title- $current.program- The title or the name of the currently active program. If no- program happens to be active, this tag will be ignored.--The variable $time refers to the time-of-day of the sample (i.e. the time-since 0:00 that day), while $sampleage refers to the timespan from when the-sample was created until now, the time of evaluating the statistics. They-can be compared with expressions of the type "hh:mm", for example:- $time >= 8:00 && $time < 12:00 ==> tag time-of-day:morning,--Statistics-==========--Run the statistics program arbtt-stats with the parameter --help to get an-overview of the options.--By default, any sample with the tag "inactive" are excluded from the-statistics, and tags with a percentage lower than one will not be shown.--The tags that affect the selection (-x, -o, --also-inactive) of samples can-be mixed with the report options (-m) and the reports (-i, -t, -c), but-will apply to all reports.--The flag --filter (-f) allows you to sepecify an arbitrary complex-condition using the same syntax for conditions that you can use in the-categorize.cfg file.--Some useful examples:-- # Only consider the time when I was programming in Haskell - arbtt-stats -o Editing-Haskell-- # Tell me what evolution folders I spend my time in when I actually do- # work with e-Mail- arbtt-stats -o Program:evolution -c Evo-Folder-- # Generate statistics about the last hour- arbtt-stats -f '$sampleage < 1:00'--Data Dump-=========+Documentation+============ -If you need to inspect the recorded sample, for example to think about-interesting things to match, use arbtt-dump.+Full documentation is now provided in the user manual in the doc/+directory. If you have the docbook xsl toolchain installed, you can+generate the HTML documentation by entering "make" in that directory.+Otherwise, you can use the online version at+http://darcs.nomeata.de/arbtt/doc/users_guide/+Beware that this will also reflect the latest version. Development ===========@@ -146,15 +49,13 @@ * A graphical viewer that allows you to expore the tags in an appealing, interactive way. Possibly based on the Charts haskell library.- * A better representation of the data in the log, to keep the size and - parsing speed down. * Looking forward and backwards in time when writing rules. (Information is already passed to the categorizing function, but not exposed to the syntax). * $total_idle time, which is the maximum idle time until it is reset. This would allow the user to catch the idle times more exactly. * Rules based on day of time, to create tags for worktime, weekend, late- at night.+ at night. (Partially done) * Statistics based on time, to visualize trends. * Possibly more data sources?
arbtt.cabal view
@@ -1,5 +1,5 @@ name: arbtt-version: 0.4+version: 0.4.1 license: GPL license-file: LICENSE category: Desktop@@ -37,26 +37,29 @@ TimeLog UpgradeLog1 Graphics.X11.XScreenSaver+ System.Locale.SetLocale executable arbtt-stats main-is: stats-main.hs hs-source-dirs: src build-depends:- base == 4.*, parsec == 2.*, containers, pcre-light, tabular, setlocale+ base == 4.*, parsec == 2.*, containers, pcre-light other-modules: Data Categorize TimeLog Stats+ System.Locale.SetLocale executable arbtt-dump main-is: dump-main.hs hs-source-dirs: src build-depends:- base == 4.*, parsec == 2.*, containers, setlocale+ base == 4.*, parsec == 2.*, containers other-modules: Data TimeLog+ System.Locale.SetLocale source-repository head type: darcs
src/Capture.hs view
@@ -8,21 +8,26 @@ import Data.Maybe import Control.Applicative import Data.Time.Clock+import System.IO captureData :: IO CaptureData captureData = do- dpy <- openDisplay ":0"+ dpy <- openDisplay "" xSetErrorHandler let rwin = defaultRootWindow dpy a <- internAtom dpy "_NET_CLIENT_LIST" False p <- getWindowProperty32 dpy a rwin- let cwins = maybe [] (map fromIntegral) p + wins <- case p of+ Just wins -> return (map fromIntegral wins)+ Nothing -> do hPutStrLn stderr "arbtt: ERROR: No _NET_CLIENT_LIST set for the root window"+ return []+ (fsubwin,_) <- getInputFocus dpy- fwin <- followTreeUntil dpy (`elem` cwins) fsubwin+ fwin <- followTreeUntil dpy (`elem` wins) fsubwin - winData <- mapM (\w -> (,,) (w == fwin) <$> getWindowTitle dpy w <*> getProgramName dpy w) cwins+ winData <- mapM (\w -> (,,) (w == fwin) <$> getWindowTitle dpy w <*> getProgramName dpy w) wins it <- fromIntegral `fmap` getXIdleTime dpy @@ -30,10 +35,10 @@ return $ CaptureData winData it getWindowTitle :: Display -> Window -> IO String-getWindowTitle dpy w = fmap (fromMaybe "") $ fetchName dpy w+getWindowTitle dpy = fmap (fromMaybe "") . fetchName dpy getProgramName :: Display -> Window -> IO String-getProgramName dpy w = fmap resName $ getClassHint dpy w+getProgramName dpy = fmap resName . getClassHint dpy -- | Follows the tree of windows up until the condition is met or the root -- window is reached.@@ -43,4 +48,3 @@ | otherwise = do (r,p,_) <- queryTree dpy w if p == 0 then return w else go p -
src/Categorize.hs view
@@ -2,7 +2,6 @@ import Data -import Data.Maybe import qualified Text.Regex.PCRE.Light.Char8 as RE import qualified Data.Map as M import Control.Monad@@ -45,16 +44,15 @@ case parse (do {r <- parseRules; eof ; return r}) filename content of Left err -> do putStrLn "Parser error:"- putStrLn (show err)+ print err exitFailure Right cat -> return ((fmap . fmap) (mkSecond (postpare . cat)) . prepare time) applyCond :: String -> TimeLog (Ctx, ActivityData) -> TimeLog (Ctx, ActivityData) applyCond s = case parse (do {c <- parseCond; eof ; return c}) "commad line parameter" s of- Left err -> do- error (show err)- Right c -> filter (isJust . c . fst . tlData)+ Left err -> error (show err)+ Right c -> filter (isJust . c . fst . tlData) prepare :: UTCTime -> TimeLog CaptureData -> TimeLog Ctx prepare time tl = go' [] tl tl@@ -67,7 +65,7 @@ -- | Here, we filter out tags appearing twice, and make sure that only one of -- each category survives postpare :: ActivityData -> ActivityData-postpare = nubBy $ go+postpare = nubBy go where go (Activity (Just c1) _) (Activity (Just c2) _) = c1 == c2 go a1 a2 = a1 == a2 @@ -184,7 +182,7 @@ time <- parseTime return $ checkTimeCmp op varname time , do guard $ varname == "active"- return $ checkActive+ return checkActive ] , do reserved lang "current window" cond <- parseCond@@ -208,7 +206,7 @@ parseTime :: Parser NominalDiffTime parseTime = fmap fromIntegral $ lexeme lang $ do h <- digitToInt <$> digit- mh <- optionMaybe (do{ digitToInt <$> digit })+ mh <- optionMaybe (digitToInt <$> digit) char ':' m1 <- digitToInt <$> digit m2 <- digitToInt <$> digit
src/Stats.hs view
@@ -5,8 +5,6 @@ import Data.Maybe import Data.List import Data.Ord-import Text.Tabular-import qualified Text.Tabular.AsciiArt as TA import Text.Printf import qualified Data.Map as M import qualified Data.Set as S@@ -24,6 +22,14 @@ data ReportOption = MinPercentage Double deriving Eq +-- Data format semantically representing the result of a report, including the+-- title+data ReportResults =+ ListOfFields String [(String, String)]+ | ListOfTimePercValues String [(String, String, Double)]+ | PieChartOfTimePercValues String [(String, String, Double)]++ applyFilters :: [Filter] -> TimeLog (Ctx, ActivityData) -> TimeLog (Ctx, ActivityData) applyFilters filters tle = foldr (\flag -> case flag of @@ -44,12 +50,12 @@ { firstDate :: UTCTime , lastDate :: UTCTime , timeDiff :: NominalDiffTime- , totalTimeRec :: Integer- , totalTimeSel :: Integer+ , totalTimeRec :: NominalDiffTime+ , totalTimeSel :: NominalDiffTime , fractionRec :: Double , fractionSel :: Double , fractionSelRec :: Double- , sums :: M.Map Activity Integer+ , sums :: M.Map Activity NominalDiffTime , allTags :: TimeLog (Ctx, ActivityData) , tags :: TimeLog (Ctx, ActivityData) }@@ -60,25 +66,25 @@ { firstDate = tlTime (head allTags) , lastDate = tlTime (last allTags) , timeDiff = diffUTCTime (lastDate c) (firstDate c)- , totalTimeRec = sum (map tlRate allTags)- , totalTimeSel = sum (map tlRate tags)- , fractionRec = fromIntegral (totalTimeRec c) / (realToFrac (timeDiff c) * 1000)- , fractionSel = fromIntegral (totalTimeSel c) / (realToFrac (timeDiff c) * 1000)- , fractionSelRec = fromIntegral (totalTimeSel c) / fromIntegral (totalTimeRec c)+ , totalTimeRec = fromInteger (sum (map tlRate allTags))/1000+ , totalTimeSel = fromInteger (sum (map tlRate tags))/1000+ , fractionRec = realToFrac (totalTimeRec c) / (realToFrac (timeDiff c))+ , fractionSel = realToFrac (totalTimeSel c) / (realToFrac (timeDiff c))+ , fractionSelRec = realToFrac (totalTimeSel c) / realToFrac (totalTimeRec c) , sums = sumUp tags , allTags , tags } in c -- | Sums up each occurence of an 'Activity', weighted by the sampling rate-sumUp :: TimeLog (Ctx, ActivityData) -> M.Map Activity Integer-sumUp = foldr go (M.empty) +sumUp :: TimeLog (Ctx, ActivityData) -> M.Map Activity NominalDiffTime+sumUp = foldr go M.empty where go tl m = foldr go' m (snd (tlData tl))- where go' act = M.insertWith (+) act (tlRate tl)+ where go' act = M.insertWith (+) act (fromInteger (tlRate tl)/1000) listCategories :: TimeLog (Ctx, ActivityData) -> [Category]-listCategories = S.toList . foldr go (S.empty) +listCategories = S.toList . foldr go S.empty where go tl m = foldr go' m (snd (tlData tl)) where go' (Activity (Just cat) _) = S.insert cat go' _ = id@@ -88,95 +94,106 @@ putReport :: [ReportOption] -> Calculations -> Report -> IO () putReport opts c EachCategory = putReports opts c (map Category (listCategories (tags c)))-putReport opts c r = let (h,t) = reportToTable opts c r- in putStrLnUnderlined h >> putStr (TA.render id id id t)+putReport opts c r = renderReport $ reportToTable opts c r -reportToTable :: [ReportOption] -> Calculations -> Report -> (String, Table String String String)+reportToTable :: [ReportOption] -> Calculations -> Report -> ReportResults reportToTable opts (Calculations {..}) r = case r of- GeneralInfos -> ("General Information",- empty ^..^ colH "Value"- +.+ row "FirstRecord"- [show firstDate]- +.+ row "LastRecord"- [show lastDate]- +.+ row "Number of records"- [show (length allTags)]- +.+ row "Total time recorded"- [formatSeconds (fromIntegral totalTimeRec / 1000)]- +.+ row "Total time selected"- [formatSeconds (fromIntegral totalTimeSel / 1000)]- +.+ row "Fraction of total time recorded"- [printf "%3.0f%%" (fractionRec * 100) ]- +.+ row "Fraction of total time selected"- [printf "%3.0f%%" (fractionSel * 100) ]- +.+ row "Fraction of recorded time selected"- [printf "%3.0f%%" (fractionSelRec * 100) ]- )+ GeneralInfos -> ListOfFields "General Information" $+ [ ("FirstRecord", show firstDate)+ , ("LastRecord", show lastDate)+ , ("Number of records", show (length allTags))+ , ("Total time recorded", showTimeDiff totalTimeRec)+ , ("Total time selected", showTimeDiff totalTimeSel)+ , ("Fraction of total time recorded", printf "%3.0f%%" (fractionRec * 100))+ , ("Fraction of total time selected", printf "%3.0f%%" (fractionSel * 100))+ , ("Fraction of recorded time selected", printf "%3.0f%%" (fractionSelRec * 100))+ ] - TotalTime -> ("Total time per tag",- foldr (\(tag,time) ->- let perc = fromIntegral time/fromIntegral totalTimeSel*100 in- if perc >= minPercentage- then (+.+ row (show tag) [- formatSeconds (fromIntegral time/1000),- printf "%.1f%%" perc])- else id- )- (empty ^..^ colH "Time" ^..^ colH "Percentage")- (sortBy (comparing snd) $ M.toList sums)- )+ TotalTime -> ListOfTimePercValues "Total time per tag" $+ mapMaybe (\(tag,time) ->+ let perc = realToFrac time/realToFrac totalTimeSel in+ if perc*100 >= minPercentage+ then Just $ ( show tag+ , showTimeDiff time+ , perc)+ else Nothing+ ) $+ reverse $+ sortBy (comparing snd) $+ M.toList sums - Category cat -> ("Statistics for category " ++ cat,+ Category cat -> PieChartOfTimePercValues ("Statistics for category " ++ cat) $ let filteredSums = M.filterWithKey (\a _ -> isCategory cat a) sums uncategorizedTime = totalTimeSel - M.fold (+) 0 filteredSums- tooSmallSums = M.filter (\t -> fromIntegral t / fromIntegral totalTimeSel * 100 < minPercentage) filteredSums+ tooSmallSums = M.filter (\t -> realToFrac t / realToFrac totalTimeSel * 100 < minPercentage) filteredSums tooSmallTimes = M.fold (+) 0 tooSmallSums in - (if uncategorizedTime > 0- then (+.+ row "(unmatched time)" [- formatSeconds (fromIntegral uncategorizedTime/1000),- printf "%.1f%%" (fromIntegral uncategorizedTime/fromIntegral totalTimeSel*100::Double)])- else id- )- . + mapMaybe (\(tag,time) ->+ let perc = realToFrac time/realToFrac totalTimeSel in+ if perc*100 >= minPercentage+ then Just ( show tag+ , showTimeDiff time+ , perc)+ else Nothing+ )+ (reverse $ sortBy (comparing snd) $ M.toList filteredSums)+ ++ ( if tooSmallTimes > 0- then (+.+ row (printf "(%d entries omitted)" (M.size tooSmallSums)) [- formatSeconds (fromIntegral tooSmallTimes/1000),- printf "%.1f%%" (fromIntegral tooSmallTimes/fromIntegral totalTimeSel*100::Double) ])- else id+ then [( printf "(%d entries omitted)" (M.size tooSmallSums)+ , showTimeDiff tooSmallTimes+ , realToFrac tooSmallTimes/realToFrac totalTimeSel+ )]+ else [] )- $ - foldr (\(tag,time) ->- let perc = fromIntegral time/fromIntegral totalTimeSel*100 in- if perc >= minPercentage- then (+.+ row (show tag) [- formatSeconds (fromIntegral time/1000),- printf "%.1f%%" perc])- else id- )-- (empty ^..^ colH "Time" ^..^ colH "Percentage")-- (sortBy (comparing snd) $ M.toList filteredSums)+ ++ + (if uncategorizedTime > 0+ then [( "(unmatched time)"+ , showTimeDiff uncategorizedTime+ , realToFrac uncategorizedTime/realToFrac totalTimeSel+ )]+ else [] ) where minPercentage = last $ mapMaybe (\f -> case f of {MinPercentage m -> Just m {- ; _ -> Nothing -} }) opts -formatSeconds :: Double -> String-formatSeconds s' = go $ zip [days,hours,mins,secs] ["d","h","m","s"]- where s = round s' :: Integer+renderReport (ListOfFields title dats) = do+ putStrLnUnderlined title+ putStr $ tabulate False $ map (\(f,v) -> [f,v]) dats++renderReport (ListOfTimePercValues title dats) = do+ putStrLnUnderlined title+ putStr $ tabulate True $ ["Tag","Time","Percentage"] : map (\(f,t,p) -> [f,t,printf "%.2f" (p*100)]) dats++renderReport (PieChartOfTimePercValues title dats) = do+ putStrLnUnderlined title+ putStr $ tabulate True $ ["Tag","Time","Percentage"] : map (\(f,t,p) -> [f,t,printf "%.2f" (p*100)]) dats+++tabulate :: Bool -> [[String]] -> String+tabulate titlerow rows = unlines $ addTitleRow $ map (intercalate " | " . zipWith (\l s -> take (l - length s) (repeat ' ') ++ s) colwidths) rows+ where cols = transpose rows+ colwidths = map (maximum . map length) cols+ addTitleRow | titlerow = \(l:ls) -> (map (\c -> if c == ' ' then '_' else c) l ++ "_")+ : ls+ -- | titlerow = \(l:ls) -> l : (take (length l) (repeat '-')) : ls+ | otherwise = id++showTimeDiff :: NominalDiffTime -> String+showTimeDiff t = go False $ zip [days,hours,mins,secs] ["d","h","m","s"]+ where s = round t :: Integer days = s `div` (24*60*60) hours = (s `div` (60*60)) `mod` 24- mins = (s `div` (60)) `mod` 60- secs = (s `mod` (60))- go | s == 0 = const "0s"- | otherwise = concat . snd . mapAccumL go' False - go' True (a,u) = (True, printf "%02d%s" a u)- go' False (a,u) | a > 0 = (True, printf "%2d%s" a u)- | otherwise = (False, "")+ mins = (s `div` 60) `mod` 60+ secs = s `mod` 60 + go False [] = "0s"+ go True [] = ""+-- go True vs | all (==0) (map fst vs) = concat (replicate (length vs) " ")+ go True ((a,u):vs) = printf "%02d%s" a u ++ go True vs+ go False ((a,u):vs) | a > 0 = printf "%2d%s" a u ++ go True vs+ | otherwise = go False vs putStrLnUnderlined str = do putStrLn str
+ src/System/Locale/SetLocale.hsc view
@@ -0,0 +1,61 @@+{-# LANGUAGE ForeignFunctionInterface #-}++{-+This file is copied from the setlocale-0.0.3 package. Its author is Lukas Mai+and it is placed in the Public Domain.+-}++module System.Locale.SetLocale (+ Category(..),+ categoryToCInt,+ setLocale+) where++import Foreign.Ptr+import Foreign.C.Types+import Foreign.C.String++import Data.Typeable++-- | A type representing the various locale categories. See @man 7 locale@.+data Category+ = LC_ALL+ | LC_COLLATE+ | LC_CTYPE+ | LC_MESSAGES+ | LC_MONETARY+ | LC_NUMERIC+ | LC_TIME+ deriving (Eq, Ord, Read, Show, Enum, Bounded)++instance Typeable Category where+ typeOf _ = mkTyConApp (mkTyCon "System.Locale.SetLocale.Category") []++#include <locale.h>++-- | Convert a 'Category' to the corresponding system-specific @LC_*@ code.+-- You probably don't need this function.+categoryToCInt :: Category -> CInt+categoryToCInt LC_ALL = #const LC_ALL+categoryToCInt LC_COLLATE = #const LC_COLLATE+categoryToCInt LC_CTYPE = #const LC_CTYPE+categoryToCInt LC_MESSAGES = #const LC_MESSAGES+categoryToCInt LC_MONETARY = #const LC_MONETARY+categoryToCInt LC_NUMERIC = #const LC_NUMERIC+categoryToCInt LC_TIME = #const LC_TIME++ptr2str :: Ptr CChar -> IO (Maybe String)+ptr2str p+ | p == nullPtr = return Nothing+ | otherwise = fmap Just $ peekCString p++str2ptr :: Maybe String -> (Ptr CChar -> IO a) -> IO a+str2ptr Nothing f = f nullPtr+str2ptr (Just s) f = withCString s f++foreign import ccall unsafe "locale.h setlocale" c_setlocale :: CInt -> Ptr CChar -> IO (Ptr CChar)++-- | A Haskell version of @setlocale()@. See @man 3 setlocale@.+setLocale :: Category -> Maybe String -> IO (Maybe String)+setLocale cat str =+ str2ptr str $ \p -> c_setlocale (categoryToCInt cat) p >>= ptr2str
src/TimeLog.hs view
@@ -15,7 +15,7 @@ import qualified Data.ByteString.Lazy as BS -magic = BS.pack $ map (fromIntegral.ord) $ "arbtt-timelog-v1\n"+magic = BS.pack $ map (fromIntegral.ord) "arbtt-timelog-v1\n" -- | Runs the given action each delay milliseconds and appends the TimeLog to the -- given file.@@ -33,7 +33,7 @@ when (not ex || force) $ BS.writeFile filename magic appendTimeLog :: Binary a => FilePath -> TimeLogEntry a -> IO ()-appendTimeLog filename tle = BS.appendFile filename $ encode $ tle+appendTimeLog filename = BS.appendFile filename . encode writeTimeLog :: Binary a => FilePath -> TimeLog a -> IO () writeTimeLog filename tl = do createTimeLog True filename
src/UpgradeLog1.hs view
@@ -48,7 +48,7 @@ renameFile captureFile oldFile captures <- readTimeLog oldFile writeTimeLog captureFile (upgrade captures)- putStrLn $ "done."+ putStrLn "done." where oldFile = captureFile ++ ".old"
src/capture-main.hs view
@@ -38,10 +38,10 @@ [ Option "h?" ["help"] (NoArg Help) "show this help"- , Option ['V'] ["version"]+ , Option "V" ["version"] (NoArg Version) "show the version number"- , Option ['r'] ["sample-rate"]+ , Option "r" ["sample-rate"] (ReqArg (SetRate . read) "RATE") "set the sample rate in seconds (default: 60)" ]
src/dump-main.hs view
@@ -24,7 +24,7 @@ [ Option "h?" ["help"] (NoArg Help) "show this help"- , Option ['V'] ["version"]+ , Option "V" ["version"] (NoArg Version) "show the version number" ]
src/stats-main.hs view
@@ -36,38 +36,38 @@ options :: [OptDescr Flag] options =- [ Option "h?" ["help"]+ [ Option "h?" ["help"] (NoArg Help) "show this help"- , Option ['V'] ["version"]+ , Option "V" ["version"] (NoArg Version) "show the version number" -- , Option ['g'] ["graphical"] (NoArg Graphical) "render the reports as graphical charts"- , Option ['x'] ["exclude"]+ , Option "x" ["exclude"] (ReqArg (Filter . Exclude . Activity Nothing) "TAG") "ignore samples containing this tag"- , Option ['o'] ["only"]+ , Option "o" ["only"] (ReqArg (Filter . Only . read) "TAG") "only consider samples containing this tag"- , Option [] ["also-inactive"]+ , Option "" ["also-inactive"] (NoArg (Filter AlsoInactive)) "include samples with the tag \"inactive\""- , Option ['f'] ["filter"]+ , Option "f" ["filter"] (ReqArg (Filter . GeneralCond) "COND") "only consider samples matching the condition"- , Option ['m'] ["min-percentage"]+ , Option "m" ["min-percentage"] (ReqArg (ReportOption . MinPercentage . read) "PERC") "do not show tags with a percentage lower than PERC% (default: 1)"- , Option ['i'] ["information"]+ , Option "i" ["information"] (NoArg (Report GeneralInfos)) "show general statistics about the data"- , Option ['t'] ["total-time"]+ , Option "t" ["total-time"] (NoArg (Report TotalTime)) "show total time for each tag"- , Option ['c'] ["category"]+ , Option "c" ["category"] (ReqArg (Report . Category) "CATEGORY") "show statistics about category CATEGORY"- , Option [] ["each-category"]+ , Option "" ["each-category"] (NoArg (Report EachCategory)) "show statistics about each category found" ]@@ -91,9 +91,9 @@ let categorizeFilename = dir </> "categorize.cfg" fileEx <- doesFileExist categorizeFilename- unless (fileEx) $ do+ unless fileEx $ do putStrLn $ printf "Configuration file %s does not exist." categorizeFilename- putStrLn $ "Please see the example file and the README for more details"+ putStrLn "Please see the example file and the README for more details" exitFailure categorizer <- readCategorizer categorizeFilename