packages feed

arbtt 0.1.5 → 0.2.0

raw patch · 7 files changed

+161/−40 lines, 7 files

Files

README view
@@ -25,12 +25,15 @@ full path to arbtt-capture in the Exec line there, if you did not do a system wide installation. +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 there, it will+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@@ -50,13 +53,17 @@       | "$" SVar "==" String | "$" SVar "/=" String       | "$" SVar "=~" "/" Regex "/" |       | "$" NVar NOp Number+      | "$" TVar NOp TSpec       | "$" BVar-      | "current window" $cond-      | "any window" $cond+      | "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@@ -87,6 +94,12 @@ 	    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 ========== @@ -100,6 +113,10 @@ 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 @@ -107,7 +124,10 @@    # Tell me what evolution folders I spend my time in when I actually do   # work with e-Mail- are arbtt-stats -o Program:evolution -c Evo-Folder+  arbtt-stats -o Program:evolution -c Evo-Folder++  # Generate statistics about the last hour+  arbtt-stats -f '$sampleage < 1:00'  Development ===========
arbtt.cabal view
@@ -1,5 +1,5 @@ name:               arbtt-version:            0.1.5+version:            0.2.0 license:            GPL license-file:       LICENSE category:           Desktop
categorize.cfg view
@@ -28,3 +28,14 @@ -- Out of curiosity: what percentage of my time am I actually coding Haskell? current window ($program == "gvim" && $title =~ /^[^ ]+\.hs \(/ )   ==> tag Editing-Haskell,++-- To be able to match on the time of day, I introduce tags for that as well+$time >=  2:00 && $time <  8:00 ==> tag time-of-day:night,+$time >=  8:00 && $time < 12:00 ==> tag time-of-day:morning,+$time >= 12:00 && $time < 14:00 ==> tag time-of-day:lunchtime,+$time >= 14:00 && $time < 18:00 ==> tag time-of-day:afternoon,+$time >= 18:00 && $time < 22:00 ==> tag time-of-day:evening,+$time >= 22:00 || $time <  2:00 ==> tag time-of-day:late-evening,++-- This tag always refers to the last 24h+$sampleage <= 24:00 ==> tag last-day,
src/Categorize.hs view
@@ -3,7 +3,6 @@ import Data  import Data.Maybe--- import Text.Regex import qualified Text.Regex.PCRE.Light.Char8 as RE import qualified Data.Map as M import Control.Monad@@ -18,19 +17,22 @@ import Data.List import Data.Maybe import Data.Char+import Data.Time.Clock import Debug.Trace+import Control.Arrow (second) -type Categorizer = TimeLog CaptureData -> TimeLog ActivityData+type Categorizer = TimeLog CaptureData -> TimeLog (Ctx, ActivityData) type Rule = Ctx -> ActivityData  type Parser a = CharParser () a  data Ctx = Ctx-	{ cNow :: CaptureData-	, cPast :: [CaptureData]-	, cFuture :: [CaptureData]+	{ cNow :: TimeLogEntry CaptureData+	, cPast :: [TimeLogEntry CaptureData]+	, cFuture :: [TimeLogEntry CaptureData] 	, cWindowInScope :: Maybe (Bool, String, String) 	, cSubsts :: [String]+	, cCurrentTime :: UTCTime 	}   deriving (Show) @@ -39,19 +41,27 @@ readCategorizer :: FilePath -> IO Categorizer readCategorizer filename = do 	content <- readFile filename+	time <- getCurrentTime 	case parse (do {r <- parseRules; eof ; return r}) filename content of 	  Left err -> do 	  	putStrLn "Parser error:" 		putStrLn (show err) 		exitFailure-	  Right cat -> return ((fmap . fmap) (postpare . cat) . prepare)+	  Right cat -> return ((fmap . fmap) (mkSecond (postpare . cat)) . prepare time) -prepare :: TimeLog CaptureData -> TimeLog Ctx-prepare tl = go' [] (map tlData tl) tl+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)++prepare :: UTCTime -> TimeLog CaptureData -> TimeLog Ctx+prepare time tl = go' [] tl tl   where go' past [] []   		= []         go' past (this:future) (now:rest)-	        = now {tlData = Ctx this past future Nothing [] } :+	        = now {tlData = Ctx now past future Nothing [] time } : 	          go' (this:past) future rest  -- | Here, we filter out tags appearing twice, and make sure that only one of@@ -86,7 +96,7 @@ 		    s2 <- stringLiteral lang 		    return (s1,s2) -parseRulesBody :: Parser (Ctx -> ActivityData)+parseRulesBody :: Parser Rule parseRulesBody = do  	x <- parseRule 	choice [ do comma lang@@ -137,6 +147,16 @@ checkNot :: Cond -> Cond checkNot = liftM (maybe (Just []) (const Nothing)) +parseCmp :: Ord a => Parser (a -> a -> Bool)+parseCmp = choice $ map (\(s,o) -> reservedOp lang s >> return o)+		     	[(">=",(>=)),+			 (">", (>)),+			 ("=", (==)),+			 ("==",(==)),+			 ("<",(<)),+			 ("<=",(<=))]++ parseCondPrim :: Parser Cond parseCondPrim = choice 	[    parens lang parseCond@@ -156,15 +176,13 @@ 			     return $ checkNot (checkEq varname str) 			] 		, do guard $ varname == "idle"-		     op <- choice $ map (\(s,o) -> reservedOp lang s >> return o)-		     	[(">=",(>=)),-			 (">", (>)),-			 ("=", (==)),-			 ("==",(==)),-			 ("<",(<)),-			 ("<=",(<=))]+		     op <- parseCmp 		     num <- natural lang 		     return $ checkNumCmp op varname num+		, do guard $ varname `elem` ["time","sampleage"]+		     op <- parseCmp +		     time <- parseTime+		     return $ checkTimeCmp op varname time 		, do guard $ varname == "active" 		     return $ checkActive 		]@@ -186,6 +204,16 @@ 	     return str 	] 	     +-- | Parses a day-of-time specification (hh:mm)+parseTime :: Parser NominalDiffTime+parseTime = fmap fromIntegral $ lexeme lang $ do+               h <- digitToInt <$> digit+	       mh <- optionMaybe (do{ digitToInt <$> digit })+	       char ':'+               m1 <- digitToInt <$> digit+               m2 <- digitToInt <$> digit+	       let hour = maybe h ((10*h)+) mh+	       return $ (hour * 60 + m1 * 10 + m2) * 60  parseSetTag :: Parser Rule parseSetTag = lexeme lang $ do@@ -237,7 +265,7 @@ 		listToMaybe (drop (n-1) (cSubsts ctx)) getVar v ctx | "current" `isPrefixOf` v = do 		let var = drop (length "current.") v-		win <- findActive $ cWindows (cNow ctx)+		win <- findActive $ cWindows (tlData (cNow ctx)) 		getVar var (ctx { cWindowInScope = Just win }) getVar "title"   ctx = do 		(_,t,_) <- cWindowInScope ctx@@ -259,11 +287,11 @@ findActive = find (\(a,_,_) -> a)				    checkCurrentwindow :: Cond -> Cond-checkCurrentwindow cond ctx = cond (ctx { cWindowInScope = findActive (cWindows (cNow ctx)) })+checkCurrentwindow cond ctx = cond (ctx { cWindowInScope = findActive (cWindows (tlData (cNow ctx))) })  checkAnyWindow :: Cond -> Cond checkAnyWindow cond ctx = msum $ map (\w -> cond (ctx { cWindowInScope = Just w }))-                                     (cWindows (cNow ctx))+                                     (cWindows (tlData (cNow ctx)))  checkActive :: Cond checkActive ctx = do (a,_,_) <- cWindowInScope ctx@@ -271,11 +299,22 @@ 		     return []  checkNumCmp ::  (Integer -> Integer -> Bool) -> String -> Integer -> Cond-checkNumCmp op "idle" num ctx = [] `justIf` op (cLastActivity (cNow ctx)) (num*1000)+checkNumCmp (<?>) "idle" num ctx = [] `justIf` (cLastActivity (tlData (cNow ctx)) <?> (num*1000)) +checkTimeCmp ::  (NominalDiffTime -> NominalDiffTime -> Bool) -> String -> NominalDiffTime -> Cond+checkTimeCmp (<?>) "time" num ctx =+	let time = tlTime (cNow ctx) `diffUTCTime` (tlTime (cNow ctx)) { utctDayTime = fromIntegral 0}+	in [] `justIf` (time <?> num)+checkTimeCmp (<?>) "sampleage" num ctx =+	let age = cCurrentTime ctx `diffUTCTime` tlTime (cNow ctx)+	in [] `justIf` (age <?> num)+ matchNone :: Rule matchNone = const []  justIf :: a -> Bool -> Maybe a justIf x True = Just x justIf x False = Nothing++mkSecond :: (a -> b) -> a -> (a, b)+mkSecond f a = (a, f a)
src/Stats.hs view
@@ -10,28 +10,30 @@ import qualified Data.Map as M  import Data+import Categorize   data Report = GeneralInfos | TotalTime | Category String         deriving Eq -data Filter = Exclude Activity | Only Activity | AlsoInactive+data Filter = Exclude Activity | Only Activity | AlsoInactive | GeneralCond String         deriving Eq  data ReportOption = MinPercentage Double         deriving Eq -applyFilters :: [Filter] -> TimeLog ActivityData -> TimeLog ActivityData+applyFilters :: [Filter] -> TimeLog (Ctx, ActivityData) -> TimeLog (Ctx, ActivityData) applyFilters filters tle =          foldr (\flag -> case flag of                                  Exclude act  -> excludeTag act                                 Only act     -> onlyTag act                                 AlsoInactive -> id+				GeneralCond s-> applyCond s          ) (if AlsoInactive `elem` filters then tle else defaultFilter tle) filters  -excludeTag act = filter (notElem act . tlData)-onlyTag act = filter (elem act . tlData)+excludeTag act = filter (notElem act . snd . tlData)+onlyTag act = filter (elem act . snd . tlData) defaultFilter = excludeTag inactiveActivity  -- | to be used lazily, to re-use computation when generating more than one@@ -46,11 +48,11 @@ 	, fractionSel :: Double 	, fractionSelRec :: Double 	, sums :: M.Map Activity Integer-	, allTags :: TimeLog ActivityData-	, tags :: TimeLog ActivityData+	, allTags :: TimeLog (Ctx, ActivityData)+	, tags :: TimeLog (Ctx, ActivityData) 	} -prepareCalculations :: TimeLog ActivityData -> TimeLog ActivityData -> Calculations+prepareCalculations :: TimeLog (Ctx, ActivityData) -> TimeLog (Ctx, ActivityData) -> Calculations prepareCalculations allTags tags =   let c = Calculations 	  { firstDate = tlTime (head allTags)@@ -67,9 +69,9 @@ 	  } in c  -- | Sums up each occurence of an 'Activity', weighted by the sampling rate-sumUp :: TimeLog ActivityData -> M.Map Activity Integer+sumUp :: TimeLog (Ctx, ActivityData) -> M.Map Activity Integer sumUp = foldr go (M.empty) -  where go tl m = foldr go' m (tlData tl)+  where go tl m = foldr go' m (snd (tlData tl))           where go' act = M.insertWith (+) act (tlRate tl)  
src/capture-main.hs view
@@ -2,9 +2,6 @@  import Control.Monad import Control.Concurrent--import Capture-import TimeLog import System.Directory import System.FilePath import Graphics.X11.XScreenSaver (compiledWithXScreenSaver)@@ -13,10 +10,41 @@ import System.IO.Error import System.Exit import System.Locale.SetLocale+import System.Console.GetOpt+import System.Environment+import Data.Maybe+import Data.Version (showVersion) --- | sampleRate in seconds-sampleRate = 60 +import Capture+import TimeLog +import Paths_arbtt (version)+++data Conf = Conf {+	cSampleRate :: Integer+	}+defaultConf = Conf 60++data Opts = Help | Version | SetRate Integer+	deriving Eq++versionStr = "arbtt-capture " ++ showVersion version+header = "Usage: arbtt-capture [OPTIONS...]"++options :: [OptDescr Opts]+options = +     [ Option "h?"     ["help"]+              (NoArg Help)+	      "show this help"+     , Option ['V']    ["version"]+              (NoArg Version)+	      "show the version number"+     , Option ['r']    ["sample-rate"]+     	      (ReqArg (SetRate . read) "RATE")+	      "set the sample rate in seconds (default: 60)"+     ]	     + -- | This is very raw, someone ought to improve this lockFile filename = flip catch (\e -> hPutStrLn stderr ("arbtt [Error]: Could not aquire lock for " ++ filename ++"!") >> exitFailure) $ do     fd <- openFd (filename  ++ ".lck") WriteOnly (Just 0644) defaultFileFlags@@ -26,6 +54,24 @@     setLocale LC_ALL (Just "")      unless compiledWithXScreenSaver $     	hPutStrLn stderr "arbtt [Warning]: X11 was compiled without support for XScreenSaver"+    +    args <- getArgs+    flags <- case getOpt Permute options args of+    	(o, [], []) | Help `notElem` o && Version `notElem` o -> return o+	(o, _, _)   | Version `elem` o -> do+		hPutStrLn stderr versionStr+		exitSuccess+	(o, _, _)   | Help `elem` o -> do+                hPutStr stderr (usageInfo header options)+		exitSuccess+	(_,_,errs) -> do+                hPutStr stderr (concat errs ++ usageInfo header options)+                exitFailure++    let sampleRate = foldr (.) id+                     (map (\f -> case f of {SetRate r -> const r; _ -> id}) flags)+		     60+     dir <- getAppUserDataDirectory "arbtt"     createDirectoryIfMissing False dir     let captureFile = dir </> "capture.log"
src/stats-main.hs view
@@ -53,6 +53,9 @@      , Option []        ["also-inactive"]               (NoArg (Filter AlsoInactive)) 	      "include samples with the tag \"inactive\""+     , Option ['f']     ["filter"]+              (ReqArg (Filter . GeneralCond) "COND")+	      "only consider samples matching the condition"      , Option ['m']     ["min-percentage"]               (ReqArg (ReportOption . MinPercentage . read) "PERC") 	      "do not show tags with a percentage lower than PERC% (default: 1)"