module Stats ( -- * Statistics EditStats(..), Stats, Description, Languages, Projects, Children, Matches, Match, SplitPath,statsFromFile, -- ** Options StatOptions(..), -- ** Time Time, sumTime, sumTime', -- * Trees Header, Title, StatsTree(..), StatsTreeAlgebra, foldTree, -- * Misc. -- ** Match parser for Main parseDescription, toMatches, -- ** Handy functions both, startsWith, module Edits ) where import Edits import Control.Applicative import Control.Arrow import Data.Char import Data.List import Data.Maybe import qualified Data.Map as D hiding (map, filter, mapMaybe) import System.FilePath type Description = String type SplitPath = [FilePath] type Match = (SplitPath, Description) type Matches = [Match] type Languages = Matches type Projects = Matches -- | Used to store the amount of time that was spent -- on a certain file while also listing some other -- classifications. -- data EditStats = ES { language :: Maybe (Description, String) , project :: Maybe (Description, String) , fileName :: FilePath , editTime :: Time , edit :: Edit } deriving (Show, Eq) type Stats = [EditStats] -- | Options to be used when creating new Stats. -- data StatOptions = SO { languages :: Languages, -- ^ Languages src locations projects :: Projects, -- ^ Projects src locations homePath :: String -- ^ HOME directory } -- Stats — Build EditStats -- stats :: Edit -> Edit -> StatOptions -> EditStats matchFile :: Edit -> Matches -> Maybe (Description, Match) startsWith :: Eq a => [a] -> [a] -> Bool both :: (a -> b) -> a -> a -> (b, b) -- Create EditStats (ie match languages, projects and -- home directory, count edit time) from two Edits -- stats e e2 (SO lang proj home) = ES l p n t e where (l, p) = both (fmap (second snd)) la pr (la, pr) = both (matchFile e) lang (maybe [] new la ++ proj) new (c, (fs, d)) = if d == slash then [] else [(fs ++ [d ++ slash] , d)] n = if startsWith home f then replaceHome else f (t, f) = (diffEdit e e2, file e) replaceHome = '~' : drop (length home) f -- Try to match a File with a Project or Language -- matchFile edit matches' = snd <$> (listToMaybe . reverse . sort $ matches) where matches = mapMaybe (uncurry match) matches' match fs c = if check then j else Nothing where path = if [last p] == slash then init p else slash check = startsWith fs s && length s > length fs (s, p) = (splitPath (file edit), s !! length fs) j = Just (length fs, (c, (fs, path))) slash :: String slash = [pathSeparator] -- Matches — Description Parsers -- parseDescription :: String -> (String, String) toMatches :: [(String, String)] -> Matches parseDescription [] = ("", "") parseDescription s = maybe noDesc desc getDesc where reversed = reverse s desc (x,y) = (reverse x, reverse y) noDesc = (s, dropTrailingPathSeparator . last . splitPath $ s) getDesc = do c <- elemIndex ')' reversed o <- elemIndex '(' reversed if all isSpace (take c reversed) then return (drop (o + 1) reversed, take (o + 1) (drop (c + 1) reversed)) else Nothing toMatches = map (first $ splitPath . addTrailingPathSeparator) -- Times -- diffEdit :: Edit -> Edit -> Time toSeconds :: Time -> Seconds fromSeconds :: Seconds -> Time sumTime :: Stats -> Time sumTime' :: [Time] -> Time type Seconds = Int type Time = (Int, Int, Int) diffEdit (Edit _ _ _ h m s _ _ _) (Edit _ _ _ h' m' s' _ _ _) = fromSeconds t where t = toSeconds (h', m', s') - toSeconds (h, m , s) toSeconds (h, m, s) = h * 3600 + m * 60 + s fromSeconds t = (div t 3600, div (mod t 3600) 60, mod (mod t 3600) 60) sumTime = fromSeconds . sum . map (toSeconds . editTime) sumTime' = fromSeconds . sum . map toSeconds -- Helper functions -- both f a b = (f a, f b) startsWith [] _ = True startsWith _ [] = False startsWith (a:as) (b:bs) = a == b && startsWith as bs -- Calendar of Stats -- type CalendarS = Calendar Stats calendarEtoS :: StatOptions -> CalendarE -> CalendarS calendarS :: StatOptions -> Edits -> CalendarS statsFromFile :: FilePath -> StatOptions -> IO Stats calendarEtoS so = fmap stat where stat = map (\e -> stats (head e) (last e) so) . fileGroup calendarS so = calendarEtoS so . calendarE statsFromFile f so = concat . flatten . calendarS so <$> editsFromFile f -- Stats — Trees -- type Children = Int -- Total number of children in tree type Header = String type Title = String -- | Trees are used to represent the result of a Query. -- data StatsTree = Root [StatsTree] [Header] Title Time | Node Children Time String [StatsTree] | Leaf Time deriving (Eq, Show) type StatsTreeAlgebra r n = ([n] -> [Header] -> Title -> Time -> r , Int -> Time -> String -> [n] -> n , Time -> n) -- | A fold for StatsTrees. Most of the Printers are -- written using this fold. -- foldTree :: StatsTreeAlgebra r n -> StatsTree -> r foldTree (root, node, leaf) (Root tr h s t) = root (map f tr) h s t where f (Node i t s tr) = node i t s (map f tr) f (Leaf e) = leaf e