lib: split up Utils more
This commit is contained in:
		
							parent
							
								
									69c870c6f0
								
							
						
					
					
						commit
						7aecbac851
					
				| @ -1,4 +1,3 @@ | |||||||
| {-# LANGUAGE FlexibleContexts #-} |  | ||||||
| {-| | {-| | ||||||
| 
 | 
 | ||||||
| Standard imports and utilities which are useful everywhere, or needed low | Standard imports and utilities which are useful everywhere, or needed low | ||||||
| @ -20,7 +19,11 @@ module Hledger.Utils (---- provide these frequently used modules - or not, for c | |||||||
|                           ---- all of this one: |                           ---- all of this one: | ||||||
|                           module Hledger.Utils, |                           module Hledger.Utils, | ||||||
|                           module Hledger.Utils.Debug, |                           module Hledger.Utils.Debug, | ||||||
|  |                           module Hledger.Utils.Parse, | ||||||
|                           module Hledger.Utils.Regex, |                           module Hledger.Utils.Regex, | ||||||
|  |                           module Hledger.Utils.String, | ||||||
|  |                           module Hledger.Utils.Test, | ||||||
|  |                           module Hledger.Utils.Tree, | ||||||
|                           -- Debug.Trace.trace, |                           -- Debug.Trace.trace, | ||||||
|                           -- module Data.PPrint, |                           -- module Data.PPrint, | ||||||
|                           -- module Hledger.Utils.UTF8IOCompat |                           -- module Hledger.Utils.UTF8IOCompat | ||||||
| @ -30,204 +33,28 @@ module Hledger.Utils (---- provide these frequently used modules - or not, for c | |||||||
| where | where | ||||||
| import Control.Monad (liftM) | import Control.Monad (liftM) | ||||||
| import Control.Monad.IO.Class (MonadIO, liftIO) | import Control.Monad.IO.Class (MonadIO, liftIO) | ||||||
| import Data.Char | -- import Data.Char | ||||||
| import Data.List | -- import Data.List | ||||||
| import qualified Data.Map as M |  | ||||||
| -- import Data.Maybe | -- import Data.Maybe | ||||||
| -- import Data.PPrint | -- import Data.PPrint | ||||||
| import Data.Time.Clock | import Data.Time.Clock | ||||||
| import Data.Time.LocalTime | import Data.Time.LocalTime | ||||||
| import Data.Tree |  | ||||||
| import System.Directory (getHomeDirectory) | import System.Directory (getHomeDirectory) | ||||||
| import System.FilePath((</>), isRelative) | import System.FilePath((</>), isRelative) | ||||||
| import System.IO | import System.IO | ||||||
| import Test.HUnit | -- import Text.Printf | ||||||
| import Text.Parsec |  | ||||||
| import Text.Printf |  | ||||||
| -- import qualified Data.Map as Map | -- import qualified Data.Map as Map | ||||||
| 
 | 
 | ||||||
| import Hledger.Utils.Debug | import Hledger.Utils.Debug | ||||||
|  | import Hledger.Utils.Parse | ||||||
| import Hledger.Utils.Regex | import Hledger.Utils.Regex | ||||||
|  | import Hledger.Utils.String | ||||||
|  | import Hledger.Utils.Test | ||||||
|  | import Hledger.Utils.Tree | ||||||
| -- import Prelude hiding (readFile,writeFile,appendFile,getContents,putStr,putStrLn) | -- import Prelude hiding (readFile,writeFile,appendFile,getContents,putStr,putStrLn) | ||||||
| -- import Hledger.Utils.UTF8IOCompat   (readFile,writeFile,appendFile,getContents,putStr,putStrLn) | -- import Hledger.Utils.UTF8IOCompat   (readFile,writeFile,appendFile,getContents,putStr,putStrLn) | ||||||
| import Hledger.Utils.UTF8IOCompat (SystemString,fromSystemString,toSystemString,error',userError') | import Hledger.Utils.UTF8IOCompat (SystemString,fromSystemString,toSystemString,error',userError') | ||||||
| 
 | 
 | ||||||
| -- strings |  | ||||||
| 
 |  | ||||||
| lowercase, uppercase :: String -> String |  | ||||||
| lowercase = map toLower |  | ||||||
| uppercase = map toUpper |  | ||||||
| 
 |  | ||||||
| -- | Remove leading and trailing whitespace. |  | ||||||
| strip :: String -> String |  | ||||||
| strip = lstrip . rstrip |  | ||||||
| 
 |  | ||||||
| -- | Remove leading whitespace. |  | ||||||
| lstrip :: String -> String |  | ||||||
| lstrip = dropWhile (`elem` " \t") :: String -> String -- XXX isSpace ? |  | ||||||
| 
 |  | ||||||
| -- | Remove trailing whitespace. |  | ||||||
| rstrip :: String -> String |  | ||||||
| rstrip = reverse . lstrip . reverse |  | ||||||
| 
 |  | ||||||
| -- | Remove trailing newlines/carriage returns. |  | ||||||
| chomp :: String -> String |  | ||||||
| chomp = reverse . dropWhile (`elem` "\r\n") . reverse |  | ||||||
| 
 |  | ||||||
| stripbrackets :: String -> String |  | ||||||
| stripbrackets = dropWhile (`elem` "([") . reverse . dropWhile (`elem` "])") . reverse :: String -> String |  | ||||||
| 
 |  | ||||||
| elideLeft :: Int -> String -> String |  | ||||||
| elideLeft width s = |  | ||||||
|     if length s > width then ".." ++ reverse (take (width - 2) $ reverse s) else s |  | ||||||
| 
 |  | ||||||
| elideRight :: Int -> String -> String |  | ||||||
| elideRight width s = |  | ||||||
|     if length s > width then take (width - 2) s ++ ".." else s |  | ||||||
| 
 |  | ||||||
| underline :: String -> String |  | ||||||
| underline s = s' ++ replicate (length s) '-' ++ "\n" |  | ||||||
|     where s' |  | ||||||
|             | last s == '\n' = s |  | ||||||
|             | otherwise = s ++ "\n" |  | ||||||
| 
 |  | ||||||
| -- | Wrap a string in double quotes, and \-prefix any embedded single |  | ||||||
| -- quotes, if it contains whitespace and is not already single- or |  | ||||||
| -- double-quoted. |  | ||||||
| quoteIfSpaced :: String -> String |  | ||||||
| quoteIfSpaced s | isSingleQuoted s || isDoubleQuoted s = s |  | ||||||
|                 | not $ any (`elem` s) whitespacechars = s |  | ||||||
|                 | otherwise = "'"++escapeSingleQuotes s++"'" |  | ||||||
| 
 |  | ||||||
| -- | Double-quote this string if it contains whitespace, single quotes |  | ||||||
| -- or double-quotes, escaping the quotes as needed. |  | ||||||
| quoteIfNeeded :: String -> String |  | ||||||
| quoteIfNeeded s | any (`elem` s) (quotechars++whitespacechars) = "\"" ++ escapeDoubleQuotes s ++ "\"" |  | ||||||
|                 | otherwise = s |  | ||||||
| 
 |  | ||||||
| -- | Single-quote this string if it contains whitespace or double-quotes. |  | ||||||
| -- No good for strings containing single quotes. |  | ||||||
| singleQuoteIfNeeded :: String -> String |  | ||||||
| singleQuoteIfNeeded s | any (`elem` s) whitespacechars = "'"++s++"'" |  | ||||||
|                       | otherwise = s |  | ||||||
| 
 |  | ||||||
| quotechars, whitespacechars :: [Char] |  | ||||||
| quotechars      = "'\"" |  | ||||||
| whitespacechars = " \t\n\r" |  | ||||||
| 
 |  | ||||||
| escapeDoubleQuotes :: String -> String |  | ||||||
| escapeDoubleQuotes = regexReplace "\"" "\"" |  | ||||||
| 
 |  | ||||||
| escapeSingleQuotes :: String -> String |  | ||||||
| escapeSingleQuotes = regexReplace "'" "\'" |  | ||||||
| 
 |  | ||||||
| escapeQuotes :: String -> String |  | ||||||
| escapeQuotes = regexReplace "([\"'])" "\\1" |  | ||||||
| 
 |  | ||||||
| -- | Quote-aware version of words - don't split on spaces which are inside quotes. |  | ||||||
| -- NB correctly handles "a'b" but not "''a''". Can raise an error if parsing fails. |  | ||||||
| words' :: String -> [String] |  | ||||||
| words' "" = [] |  | ||||||
| words' s  = map stripquotes $ fromparse $ parsewith p s |  | ||||||
|     where |  | ||||||
|       p = do ss <- (singleQuotedPattern <|> doubleQuotedPattern <|> pattern) `sepBy` many1 spacenonewline |  | ||||||
|              -- eof |  | ||||||
|              return ss |  | ||||||
|       pattern = many (noneOf whitespacechars) |  | ||||||
|       singleQuotedPattern = between (char '\'') (char '\'') (many $ noneOf "'") |  | ||||||
|       doubleQuotedPattern = between (char '"') (char '"') (many $ noneOf "\"") |  | ||||||
| 
 |  | ||||||
| -- | Quote-aware version of unwords - single-quote strings which contain whitespace |  | ||||||
| unwords' :: [String] -> String |  | ||||||
| unwords' = unwords . map quoteIfNeeded |  | ||||||
| 
 |  | ||||||
| -- | Strip one matching pair of single or double quotes on the ends of a string. |  | ||||||
| stripquotes :: String -> String |  | ||||||
| stripquotes s = if isSingleQuoted s || isDoubleQuoted s then init $ tail s else s |  | ||||||
| 
 |  | ||||||
| isSingleQuoted s@(_:_:_) = head s == '\'' && last s == '\'' |  | ||||||
| isSingleQuoted _ = False |  | ||||||
| 
 |  | ||||||
| isDoubleQuoted s@(_:_:_) = head s == '"' && last s == '"' |  | ||||||
| isDoubleQuoted _ = False |  | ||||||
| 
 |  | ||||||
| unbracket :: String -> String |  | ||||||
| unbracket s |  | ||||||
|     | (head s == '[' && last s == ']') || (head s == '(' && last s == ')') = init $ tail s |  | ||||||
|     | otherwise = s |  | ||||||
| 
 |  | ||||||
| -- | Join multi-line strings as side-by-side rectangular strings of the same height, top-padded. |  | ||||||
| concatTopPadded :: [String] -> String |  | ||||||
| concatTopPadded strs = intercalate "\n" $ map concat $ transpose padded |  | ||||||
|     where |  | ||||||
|       lss = map lines strs |  | ||||||
|       h = maximum $ map length lss |  | ||||||
|       ypad ls = replicate (difforzero h (length ls)) "" ++ ls |  | ||||||
|       xpad ls = map (padleft w) ls where w | null ls = 0 |  | ||||||
|                                            | otherwise = maximum $ map length ls |  | ||||||
|       padded = map (xpad . ypad) lss |  | ||||||
| 
 |  | ||||||
| -- | Join multi-line strings as side-by-side rectangular strings of the same height, bottom-padded. |  | ||||||
| concatBottomPadded :: [String] -> String |  | ||||||
| concatBottomPadded strs = intercalate "\n" $ map concat $ transpose padded |  | ||||||
|     where |  | ||||||
|       lss = map lines strs |  | ||||||
|       h = maximum $ map length lss |  | ||||||
|       ypad ls = ls ++ replicate (difforzero h (length ls)) "" |  | ||||||
|       xpad ls = map (padright w) ls where w | null ls = 0 |  | ||||||
|                                             | otherwise = maximum $ map length ls |  | ||||||
|       padded = map (xpad . ypad) lss |  | ||||||
| 
 |  | ||||||
| -- | Compose strings vertically and right-aligned. |  | ||||||
| vConcatRightAligned :: [String] -> String |  | ||||||
| vConcatRightAligned ss = intercalate "\n" $ map showfixedwidth ss |  | ||||||
|     where |  | ||||||
|       showfixedwidth = printf (printf "%%%ds" width) |  | ||||||
|       width = maximum $ map length ss |  | ||||||
| 
 |  | ||||||
| -- | Convert a multi-line string to a rectangular string top-padded to the specified height. |  | ||||||
| padtop :: Int -> String -> String |  | ||||||
| padtop h s = intercalate "\n" xpadded |  | ||||||
|     where |  | ||||||
|       ls = lines s |  | ||||||
|       sh = length ls |  | ||||||
|       sw | null ls = 0 |  | ||||||
|          | otherwise = maximum $ map length ls |  | ||||||
|       ypadded = replicate (difforzero h sh) "" ++ ls |  | ||||||
|       xpadded = map (padleft sw) ypadded |  | ||||||
| 
 |  | ||||||
| -- | Convert a multi-line string to a rectangular string bottom-padded to the specified height. |  | ||||||
| padbottom :: Int -> String -> String |  | ||||||
| padbottom h s = intercalate "\n" xpadded |  | ||||||
|     where |  | ||||||
|       ls = lines s |  | ||||||
|       sh = length ls |  | ||||||
|       sw | null ls = 0 |  | ||||||
|          | otherwise = maximum $ map length ls |  | ||||||
|       ypadded = ls ++ replicate (difforzero h sh) "" |  | ||||||
|       xpadded = map (padleft sw) ypadded |  | ||||||
| 
 |  | ||||||
| -- | Convert a multi-line string to a rectangular string left-padded to the specified width. |  | ||||||
| padleft :: Int -> String -> String |  | ||||||
| padleft w "" = concat $ replicate w " " |  | ||||||
| padleft w s = intercalate "\n" $ map (printf (printf "%%%ds" w)) $ lines s |  | ||||||
| 
 |  | ||||||
| -- | Convert a multi-line string to a rectangular string right-padded to the specified width. |  | ||||||
| padright :: Int -> String -> String |  | ||||||
| padright w "" = concat $ replicate w " " |  | ||||||
| padright w s = intercalate "\n" $ map (printf (printf "%%-%ds" w)) $ lines s |  | ||||||
| 
 |  | ||||||
| -- | Clip a multi-line string to the specified width and height from the top left. |  | ||||||
| cliptopleft :: Int -> Int -> String -> String |  | ||||||
| cliptopleft w h = intercalate "\n" . take h . map (take w) . lines |  | ||||||
| 
 |  | ||||||
| -- | Clip and pad a multi-line string to fill the specified width and height. |  | ||||||
| fitto :: Int -> Int -> String -> String |  | ||||||
| fitto w h s = intercalate "\n" $ take h $ rows ++ repeat blankline |  | ||||||
|     where |  | ||||||
|       rows = map (fit w) $ lines s |  | ||||||
|       fit w = take w . (++ repeat ' ') |  | ||||||
|       blankline = replicate w ' ' |  | ||||||
| 
 | 
 | ||||||
| -- tuples | -- tuples | ||||||
| 
 | 
 | ||||||
| @ -246,11 +73,6 @@ third5  (_,_,x,_,_) = x | |||||||
| fourth5 (_,_,_,x,_) = x | fourth5 (_,_,_,x,_) = x | ||||||
| fifth5  (_,_,_,_,x) = x | fifth5  (_,_,_,_,x) = x | ||||||
| 
 | 
 | ||||||
| -- math |  | ||||||
| 
 |  | ||||||
| difforzero :: (Num a, Ord a) => a -> a -> a |  | ||||||
| difforzero a b = maximum [(a - b), 0] |  | ||||||
| 
 |  | ||||||
| -- lists | -- lists | ||||||
| 
 | 
 | ||||||
| splitAtElement :: Eq a => a -> [a] -> [[a]] | splitAtElement :: Eq a => a -> [a] -> [[a]] | ||||||
| @ -263,120 +85,6 @@ splitAtElement x l = | |||||||
|     split es = let (first,rest) = break (x==) es |     split es = let (first,rest) = break (x==) es | ||||||
|                in first : splitAtElement x rest |                in first : splitAtElement x rest | ||||||
| 
 | 
 | ||||||
| -- trees |  | ||||||
| 
 |  | ||||||
| -- standard tree helpers |  | ||||||
| 
 |  | ||||||
| root = rootLabel |  | ||||||
| subs = subForest |  | ||||||
| branches = subForest |  | ||||||
| 
 |  | ||||||
| -- | List just the leaf nodes of a tree |  | ||||||
| leaves :: Tree a -> [a] |  | ||||||
| leaves (Node v []) = [v] |  | ||||||
| leaves (Node _ branches) = concatMap leaves branches |  | ||||||
| 
 |  | ||||||
| -- | get the sub-tree rooted at the first (left-most, depth-first) occurrence |  | ||||||
| -- of the specified node value |  | ||||||
| subtreeat :: Eq a => a -> Tree a -> Maybe (Tree a) |  | ||||||
| subtreeat v t |  | ||||||
|     | root t == v = Just t |  | ||||||
|     | otherwise = subtreeinforest v $ subs t |  | ||||||
| 
 |  | ||||||
| -- | get the sub-tree for the specified node value in the first tree in |  | ||||||
| -- forest in which it occurs. |  | ||||||
| subtreeinforest :: Eq a => a -> [Tree a] -> Maybe (Tree a) |  | ||||||
| subtreeinforest _ [] = Nothing |  | ||||||
| subtreeinforest v (t:ts) = case (subtreeat v t) of |  | ||||||
|                              Just t' -> Just t' |  | ||||||
|                              Nothing -> subtreeinforest v ts |  | ||||||
| 
 |  | ||||||
| -- | remove all nodes past a certain depth |  | ||||||
| treeprune :: Int -> Tree a -> Tree a |  | ||||||
| treeprune 0 t = Node (root t) [] |  | ||||||
| treeprune d t = Node (root t) (map (treeprune $ d-1) $ branches t) |  | ||||||
| 
 |  | ||||||
| -- | apply f to all tree nodes |  | ||||||
| treemap :: (a -> b) -> Tree a -> Tree b |  | ||||||
| treemap f t = Node (f $ root t) (map (treemap f) $ branches t) |  | ||||||
| 
 |  | ||||||
| -- | remove all subtrees whose nodes do not fulfill predicate |  | ||||||
| treefilter :: (a -> Bool) -> Tree a -> Tree a |  | ||||||
| treefilter f t = Node |  | ||||||
|                  (root t) |  | ||||||
|                  (map (treefilter f) $ filter (treeany f) $ branches t) |  | ||||||
| 
 |  | ||||||
| -- | is predicate true in any node of tree ? |  | ||||||
| treeany :: (a -> Bool) -> Tree a -> Bool |  | ||||||
| treeany f t = f (root t) || any (treeany f) (branches t) |  | ||||||
| 
 |  | ||||||
| -- treedrop -- remove the leaves which do fulfill predicate. |  | ||||||
| -- treedropall -- do this repeatedly. |  | ||||||
| 
 |  | ||||||
| -- | show a compact ascii representation of a tree |  | ||||||
| showtree :: Show a => Tree a -> String |  | ||||||
| showtree = unlines . filter (regexMatches "[^ \\|]") . lines . drawTree . treemap show |  | ||||||
| 
 |  | ||||||
| -- | show a compact ascii representation of a forest |  | ||||||
| showforest :: Show a => Forest a -> String |  | ||||||
| showforest = concatMap showtree |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| -- | An efficient-to-build tree suggested by Cale Gibbard, probably |  | ||||||
| -- better than accountNameTreeFrom. |  | ||||||
| newtype FastTree a = T (M.Map a (FastTree a)) |  | ||||||
|   deriving (Show, Eq, Ord) |  | ||||||
| 
 |  | ||||||
| emptyTree = T M.empty |  | ||||||
| 
 |  | ||||||
| mergeTrees :: (Ord a) => FastTree a -> FastTree a -> FastTree a |  | ||||||
| mergeTrees (T m) (T m') = T (M.unionWith mergeTrees m m') |  | ||||||
| 
 |  | ||||||
| treeFromPath :: [a] -> FastTree a |  | ||||||
| treeFromPath []     = T M.empty |  | ||||||
| treeFromPath (x:xs) = T (M.singleton x (treeFromPath xs)) |  | ||||||
| 
 |  | ||||||
| treeFromPaths :: (Ord a) => [[a]] -> FastTree a |  | ||||||
| treeFromPaths = foldl' mergeTrees emptyTree . map treeFromPath |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| -- parsing |  | ||||||
| 
 |  | ||||||
| -- | Backtracking choice, use this when alternatives share a prefix. |  | ||||||
| -- Consumes no input if all choices fail. |  | ||||||
| choice' :: Stream s m t => [ParsecT s u m a] -> ParsecT s u m a |  | ||||||
| choice' = choice . map Text.Parsec.try |  | ||||||
| 
 |  | ||||||
| parsewith :: Parsec [Char] () a -> String -> Either ParseError a |  | ||||||
| parsewith p = runParser p () "" |  | ||||||
| 
 |  | ||||||
| parseWithCtx :: Stream s m t => u -> ParsecT s u m a -> s -> m (Either ParseError a) |  | ||||||
| parseWithCtx ctx p = runParserT p ctx "" |  | ||||||
| 
 |  | ||||||
| fromparse :: Either ParseError a -> a |  | ||||||
| fromparse = either parseerror id |  | ||||||
| 
 |  | ||||||
| parseerror :: ParseError -> a |  | ||||||
| parseerror e = error' $ showParseError e |  | ||||||
| 
 |  | ||||||
| showParseError :: ParseError -> String |  | ||||||
| showParseError e = "parse error at " ++ show e |  | ||||||
| 
 |  | ||||||
| showDateParseError :: ParseError -> String |  | ||||||
| showDateParseError e = printf "date parse error (%s)" (intercalate ", " $ tail $ lines $ show e) |  | ||||||
| 
 |  | ||||||
| nonspace :: (Stream [Char] m Char) => ParsecT [Char] st m Char |  | ||||||
| nonspace = satisfy (not . isSpace) |  | ||||||
| 
 |  | ||||||
| spacenonewline :: (Stream [Char] m Char) => ParsecT [Char] st m Char |  | ||||||
| spacenonewline = satisfy (`elem` " \v\f\t") |  | ||||||
| 
 |  | ||||||
| restofline :: (Stream [Char] m Char) => ParsecT [Char] st m String |  | ||||||
| restofline = anyChar `manyTill` newline |  | ||||||
| 
 |  | ||||||
| eolof :: (Stream [Char] m Char) => ParsecT [Char] st m () |  | ||||||
| eolof = (newline >> return ()) <|> eof |  | ||||||
| 
 |  | ||||||
| -- time | -- time | ||||||
| 
 | 
 | ||||||
| getCurrentLocalTime :: IO LocalTime | getCurrentLocalTime :: IO LocalTime | ||||||
| @ -385,44 +93,6 @@ getCurrentLocalTime = do | |||||||
|   tz <- getCurrentTimeZone |   tz <- getCurrentTimeZone | ||||||
|   return $ utcToLocalTime tz t |   return $ utcToLocalTime tz t | ||||||
| 
 | 
 | ||||||
| -- testing |  | ||||||
| 
 |  | ||||||
| -- | Get a Test's label, or the empty string. |  | ||||||
| testName :: Test -> String |  | ||||||
| testName (TestLabel n _) = n |  | ||||||
| testName _ = "" |  | ||||||
| 
 |  | ||||||
| -- | Flatten a Test containing TestLists into a list of single tests. |  | ||||||
| flattenTests :: Test -> [Test] |  | ||||||
| flattenTests (TestLabel _ t@(TestList _)) = flattenTests t |  | ||||||
| flattenTests (TestList ts) = concatMap flattenTests ts |  | ||||||
| flattenTests t = [t] |  | ||||||
| 
 |  | ||||||
| -- | Filter TestLists in a Test, recursively, preserving the structure. |  | ||||||
| filterTests :: (Test -> Bool) -> Test -> Test |  | ||||||
| filterTests p (TestLabel l ts) = TestLabel l (filterTests p ts) |  | ||||||
| filterTests p (TestList ts) = TestList $ filter (any p . flattenTests) $ map (filterTests p) ts |  | ||||||
| filterTests _ t = t |  | ||||||
| 
 |  | ||||||
| -- | Simple way to assert something is some expected value, with no label. |  | ||||||
| is :: (Eq a, Show a) => a -> a -> Assertion |  | ||||||
| a `is` e = assertEqual "" e a |  | ||||||
| 
 |  | ||||||
| -- | Assert a parse result is successful, printing the parse error on failure. |  | ||||||
| assertParse :: (Either ParseError a) -> Assertion |  | ||||||
| assertParse parse = either (assertFailure.show) (const (return ())) parse |  | ||||||
| 
 |  | ||||||
| -- | Assert a parse result is successful, printing the parse error on failure. |  | ||||||
| assertParseFailure :: (Either ParseError a) -> Assertion |  | ||||||
| assertParseFailure parse = either (const $ return ()) (const $ assertFailure "parse should not have succeeded") parse |  | ||||||
| 
 |  | ||||||
| -- | Assert a parse result is some expected value, printing the parse error on failure. |  | ||||||
| assertParseEqual :: (Show a, Eq a) => (Either ParseError a) -> a -> Assertion |  | ||||||
| assertParseEqual parse expected = either (assertFailure.show) (`is` expected) parse |  | ||||||
| 
 |  | ||||||
| printParseError :: (Show a) => a -> IO () |  | ||||||
| printParseError e = do putStr "parse error at "; print e |  | ||||||
| 
 |  | ||||||
| -- misc | -- misc | ||||||
| 
 | 
 | ||||||
| isLeft :: Either a b -> Bool | isLeft :: Either a b -> Bool | ||||||
|  | |||||||
							
								
								
									
										45
									
								
								hledger-lib/Hledger/Utils/Parse.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								hledger-lib/Hledger/Utils/Parse.hs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | |||||||
|  | {-# LANGUAGE FlexibleContexts #-} | ||||||
|  | module Hledger.Utils.Parse where | ||||||
|  | 
 | ||||||
|  | import Data.Char | ||||||
|  | import Data.List | ||||||
|  | import Text.Parsec | ||||||
|  | import Text.Printf | ||||||
|  | 
 | ||||||
|  | import Hledger.Utils.UTF8IOCompat (error') | ||||||
|  | 
 | ||||||
|  | -- | Backtracking choice, use this when alternatives share a prefix. | ||||||
|  | -- Consumes no input if all choices fail. | ||||||
|  | choice' :: Stream s m t => [ParsecT s u m a] -> ParsecT s u m a | ||||||
|  | choice' = choice . map Text.Parsec.try | ||||||
|  | 
 | ||||||
|  | parsewith :: Parsec [Char] () a -> String -> Either ParseError a | ||||||
|  | parsewith p = runParser p () "" | ||||||
|  | 
 | ||||||
|  | parseWithCtx :: Stream s m t => u -> ParsecT s u m a -> s -> m (Either ParseError a) | ||||||
|  | parseWithCtx ctx p = runParserT p ctx "" | ||||||
|  | 
 | ||||||
|  | fromparse :: Either ParseError a -> a | ||||||
|  | fromparse = either parseerror id | ||||||
|  | 
 | ||||||
|  | parseerror :: ParseError -> a | ||||||
|  | parseerror e = error' $ showParseError e | ||||||
|  | 
 | ||||||
|  | showParseError :: ParseError -> String | ||||||
|  | showParseError e = "parse error at " ++ show e | ||||||
|  | 
 | ||||||
|  | showDateParseError :: ParseError -> String | ||||||
|  | showDateParseError e = printf "date parse error (%s)" (intercalate ", " $ tail $ lines $ show e) | ||||||
|  | 
 | ||||||
|  | nonspace :: (Stream [Char] m Char) => ParsecT [Char] st m Char | ||||||
|  | nonspace = satisfy (not . isSpace) | ||||||
|  | 
 | ||||||
|  | spacenonewline :: (Stream [Char] m Char) => ParsecT [Char] st m Char | ||||||
|  | spacenonewline = satisfy (`elem` " \v\f\t") | ||||||
|  | 
 | ||||||
|  | restofline :: (Stream [Char] m Char) => ParsecT [Char] st m String | ||||||
|  | restofline = anyChar `manyTill` newline | ||||||
|  | 
 | ||||||
|  | eolof :: (Stream [Char] m Char) => ParsecT [Char] st m () | ||||||
|  | eolof = (newline >> return ()) <|> eof | ||||||
|  | 
 | ||||||
							
								
								
									
										227
									
								
								hledger-lib/Hledger/Utils/String.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								hledger-lib/Hledger/Utils/String.hs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,227 @@ | |||||||
|  | module Hledger.Utils.String ( | ||||||
|  |  -- * misc | ||||||
|  |  lowercase, | ||||||
|  |  uppercase, | ||||||
|  |  underline, | ||||||
|  |  stripbrackets, | ||||||
|  |  unbracket, | ||||||
|  |  -- quoting | ||||||
|  |  quoteIfSpaced, | ||||||
|  |  quoteIfNeeded, | ||||||
|  |  singleQuoteIfNeeded, | ||||||
|  |  -- quotechars, | ||||||
|  |  -- whitespacechars, | ||||||
|  |  escapeDoubleQuotes, | ||||||
|  |  escapeSingleQuotes, | ||||||
|  |  escapeQuotes, | ||||||
|  |  words', | ||||||
|  |  unwords', | ||||||
|  |  stripquotes, | ||||||
|  |  isSingleQuoted, | ||||||
|  |  isDoubleQuoted, | ||||||
|  |  -- * single-line layout | ||||||
|  |  strip, | ||||||
|  |  lstrip, | ||||||
|  |  rstrip, | ||||||
|  |  chomp, | ||||||
|  |  elideLeft, | ||||||
|  |  elideRight, | ||||||
|  |  -- * multi-line layout | ||||||
|  |  concatTopPadded, | ||||||
|  |  concatBottomPadded, | ||||||
|  |  vConcatRightAligned, | ||||||
|  |  padtop, | ||||||
|  |  padbottom, | ||||||
|  |  padleft, | ||||||
|  |  padright, | ||||||
|  |  cliptopleft, | ||||||
|  |  fitto | ||||||
|  |  ) where | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | import Data.Char | ||||||
|  | import Data.List | ||||||
|  | import Text.Parsec | ||||||
|  | import Text.Printf | ||||||
|  | 
 | ||||||
|  | import Hledger.Utils.Parse | ||||||
|  | import Hledger.Utils.Regex | ||||||
|  | 
 | ||||||
|  | lowercase, uppercase :: String -> String | ||||||
|  | lowercase = map toLower | ||||||
|  | uppercase = map toUpper | ||||||
|  | 
 | ||||||
|  | -- | Remove leading and trailing whitespace. | ||||||
|  | strip :: String -> String | ||||||
|  | strip = lstrip . rstrip | ||||||
|  | 
 | ||||||
|  | -- | Remove leading whitespace. | ||||||
|  | lstrip :: String -> String | ||||||
|  | lstrip = dropWhile (`elem` " \t") :: String -> String -- XXX isSpace ? | ||||||
|  | 
 | ||||||
|  | -- | Remove trailing whitespace. | ||||||
|  | rstrip :: String -> String | ||||||
|  | rstrip = reverse . lstrip . reverse | ||||||
|  | 
 | ||||||
|  | -- | Remove trailing newlines/carriage returns. | ||||||
|  | chomp :: String -> String | ||||||
|  | chomp = reverse . dropWhile (`elem` "\r\n") . reverse | ||||||
|  | 
 | ||||||
|  | stripbrackets :: String -> String | ||||||
|  | stripbrackets = dropWhile (`elem` "([") . reverse . dropWhile (`elem` "])") . reverse :: String -> String | ||||||
|  | 
 | ||||||
|  | elideLeft :: Int -> String -> String | ||||||
|  | elideLeft width s = | ||||||
|  |     if length s > width then ".." ++ reverse (take (width - 2) $ reverse s) else s | ||||||
|  | 
 | ||||||
|  | elideRight :: Int -> String -> String | ||||||
|  | elideRight width s = | ||||||
|  |     if length s > width then take (width - 2) s ++ ".." else s | ||||||
|  | 
 | ||||||
|  | underline :: String -> String | ||||||
|  | underline s = s' ++ replicate (length s) '-' ++ "\n" | ||||||
|  |     where s' | ||||||
|  |             | last s == '\n' = s | ||||||
|  |             | otherwise = s ++ "\n" | ||||||
|  | 
 | ||||||
|  | -- | Wrap a string in double quotes, and \-prefix any embedded single | ||||||
|  | -- quotes, if it contains whitespace and is not already single- or | ||||||
|  | -- double-quoted. | ||||||
|  | quoteIfSpaced :: String -> String | ||||||
|  | quoteIfSpaced s | isSingleQuoted s || isDoubleQuoted s = s | ||||||
|  |                 | not $ any (`elem` s) whitespacechars = s | ||||||
|  |                 | otherwise = "'"++escapeSingleQuotes s++"'" | ||||||
|  | 
 | ||||||
|  | -- | Double-quote this string if it contains whitespace, single quotes | ||||||
|  | -- or double-quotes, escaping the quotes as needed. | ||||||
|  | quoteIfNeeded :: String -> String | ||||||
|  | quoteIfNeeded s | any (`elem` s) (quotechars++whitespacechars) = "\"" ++ escapeDoubleQuotes s ++ "\"" | ||||||
|  |                 | otherwise = s | ||||||
|  | 
 | ||||||
|  | -- | Single-quote this string if it contains whitespace or double-quotes. | ||||||
|  | -- No good for strings containing single quotes. | ||||||
|  | singleQuoteIfNeeded :: String -> String | ||||||
|  | singleQuoteIfNeeded s | any (`elem` s) whitespacechars = "'"++s++"'" | ||||||
|  |                       | otherwise = s | ||||||
|  | 
 | ||||||
|  | quotechars, whitespacechars :: [Char] | ||||||
|  | quotechars      = "'\"" | ||||||
|  | whitespacechars = " \t\n\r" | ||||||
|  | 
 | ||||||
|  | escapeDoubleQuotes :: String -> String | ||||||
|  | escapeDoubleQuotes = regexReplace "\"" "\"" | ||||||
|  | 
 | ||||||
|  | escapeSingleQuotes :: String -> String | ||||||
|  | escapeSingleQuotes = regexReplace "'" "\'" | ||||||
|  | 
 | ||||||
|  | escapeQuotes :: String -> String | ||||||
|  | escapeQuotes = regexReplace "([\"'])" "\\1" | ||||||
|  | 
 | ||||||
|  | -- | Quote-aware version of words - don't split on spaces which are inside quotes. | ||||||
|  | -- NB correctly handles "a'b" but not "''a''". Can raise an error if parsing fails. | ||||||
|  | words' :: String -> [String] | ||||||
|  | words' "" = [] | ||||||
|  | words' s  = map stripquotes $ fromparse $ parsewith p s | ||||||
|  |     where | ||||||
|  |       p = do ss <- (singleQuotedPattern <|> doubleQuotedPattern <|> pattern) `sepBy` many1 spacenonewline | ||||||
|  |              -- eof | ||||||
|  |              return ss | ||||||
|  |       pattern = many (noneOf whitespacechars) | ||||||
|  |       singleQuotedPattern = between (char '\'') (char '\'') (many $ noneOf "'") | ||||||
|  |       doubleQuotedPattern = between (char '"') (char '"') (many $ noneOf "\"") | ||||||
|  | 
 | ||||||
|  | -- | Quote-aware version of unwords - single-quote strings which contain whitespace | ||||||
|  | unwords' :: [String] -> String | ||||||
|  | unwords' = unwords . map quoteIfNeeded | ||||||
|  | 
 | ||||||
|  | -- | Strip one matching pair of single or double quotes on the ends of a string. | ||||||
|  | stripquotes :: String -> String | ||||||
|  | stripquotes s = if isSingleQuoted s || isDoubleQuoted s then init $ tail s else s | ||||||
|  | 
 | ||||||
|  | isSingleQuoted s@(_:_:_) = head s == '\'' && last s == '\'' | ||||||
|  | isSingleQuoted _ = False | ||||||
|  | 
 | ||||||
|  | isDoubleQuoted s@(_:_:_) = head s == '"' && last s == '"' | ||||||
|  | isDoubleQuoted _ = False | ||||||
|  | 
 | ||||||
|  | unbracket :: String -> String | ||||||
|  | unbracket s | ||||||
|  |     | (head s == '[' && last s == ']') || (head s == '(' && last s == ')') = init $ tail s | ||||||
|  |     | otherwise = s | ||||||
|  | 
 | ||||||
|  | -- | Join multi-line strings as side-by-side rectangular strings of the same height, top-padded. | ||||||
|  | concatTopPadded :: [String] -> String | ||||||
|  | concatTopPadded strs = intercalate "\n" $ map concat $ transpose padded | ||||||
|  |     where | ||||||
|  |       lss = map lines strs | ||||||
|  |       h = maximum $ map length lss | ||||||
|  |       ypad ls = replicate (difforzero h (length ls)) "" ++ ls | ||||||
|  |       xpad ls = map (padleft w) ls where w | null ls = 0 | ||||||
|  |                                            | otherwise = maximum $ map length ls | ||||||
|  |       padded = map (xpad . ypad) lss | ||||||
|  | 
 | ||||||
|  | -- | Join multi-line strings as side-by-side rectangular strings of the same height, bottom-padded. | ||||||
|  | concatBottomPadded :: [String] -> String | ||||||
|  | concatBottomPadded strs = intercalate "\n" $ map concat $ transpose padded | ||||||
|  |     where | ||||||
|  |       lss = map lines strs | ||||||
|  |       h = maximum $ map length lss | ||||||
|  |       ypad ls = ls ++ replicate (difforzero h (length ls)) "" | ||||||
|  |       xpad ls = map (padright w) ls where w | null ls = 0 | ||||||
|  |                                             | otherwise = maximum $ map length ls | ||||||
|  |       padded = map (xpad . ypad) lss | ||||||
|  | 
 | ||||||
|  | -- | Compose strings vertically and right-aligned. | ||||||
|  | vConcatRightAligned :: [String] -> String | ||||||
|  | vConcatRightAligned ss = intercalate "\n" $ map showfixedwidth ss | ||||||
|  |     where | ||||||
|  |       showfixedwidth = printf (printf "%%%ds" width) | ||||||
|  |       width = maximum $ map length ss | ||||||
|  | 
 | ||||||
|  | -- | Convert a multi-line string to a rectangular string top-padded to the specified height. | ||||||
|  | padtop :: Int -> String -> String | ||||||
|  | padtop h s = intercalate "\n" xpadded | ||||||
|  |     where | ||||||
|  |       ls = lines s | ||||||
|  |       sh = length ls | ||||||
|  |       sw | null ls = 0 | ||||||
|  |          | otherwise = maximum $ map length ls | ||||||
|  |       ypadded = replicate (difforzero h sh) "" ++ ls | ||||||
|  |       xpadded = map (padleft sw) ypadded | ||||||
|  | 
 | ||||||
|  | -- | Convert a multi-line string to a rectangular string bottom-padded to the specified height. | ||||||
|  | padbottom :: Int -> String -> String | ||||||
|  | padbottom h s = intercalate "\n" xpadded | ||||||
|  |     where | ||||||
|  |       ls = lines s | ||||||
|  |       sh = length ls | ||||||
|  |       sw | null ls = 0 | ||||||
|  |          | otherwise = maximum $ map length ls | ||||||
|  |       ypadded = ls ++ replicate (difforzero h sh) "" | ||||||
|  |       xpadded = map (padleft sw) ypadded | ||||||
|  | 
 | ||||||
|  | difforzero :: (Num a, Ord a) => a -> a -> a | ||||||
|  | difforzero a b = maximum [(a - b), 0] | ||||||
|  | 
 | ||||||
|  | -- | Convert a multi-line string to a rectangular string left-padded to the specified width. | ||||||
|  | padleft :: Int -> String -> String | ||||||
|  | padleft w "" = concat $ replicate w " " | ||||||
|  | padleft w s = intercalate "\n" $ map (printf (printf "%%%ds" w)) $ lines s | ||||||
|  | 
 | ||||||
|  | -- | Convert a multi-line string to a rectangular string right-padded to the specified width. | ||||||
|  | padright :: Int -> String -> String | ||||||
|  | padright w "" = concat $ replicate w " " | ||||||
|  | padright w s = intercalate "\n" $ map (printf (printf "%%-%ds" w)) $ lines s | ||||||
|  | 
 | ||||||
|  | -- | Clip a multi-line string to the specified width and height from the top left. | ||||||
|  | cliptopleft :: Int -> Int -> String -> String | ||||||
|  | cliptopleft w h = intercalate "\n" . take h . map (take w) . lines | ||||||
|  | 
 | ||||||
|  | -- | Clip and pad a multi-line string to fill the specified width and height. | ||||||
|  | fitto :: Int -> Int -> String -> String | ||||||
|  | fitto w h s = intercalate "\n" $ take h $ rows ++ repeat blankline | ||||||
|  |     where | ||||||
|  |       rows = map (fit w) $ lines s | ||||||
|  |       fit w = take w . (++ repeat ' ') | ||||||
|  |       blankline = replicate w ' ' | ||||||
|  | 
 | ||||||
							
								
								
									
										41
									
								
								hledger-lib/Hledger/Utils/Test.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								hledger-lib/Hledger/Utils/Test.hs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | |||||||
|  | module Hledger.Utils.Test where | ||||||
|  | 
 | ||||||
|  | import Test.HUnit | ||||||
|  | import Text.Parsec | ||||||
|  | 
 | ||||||
|  | -- | Get a Test's label, or the empty string. | ||||||
|  | testName :: Test -> String | ||||||
|  | testName (TestLabel n _) = n | ||||||
|  | testName _ = "" | ||||||
|  | 
 | ||||||
|  | -- | Flatten a Test containing TestLists into a list of single tests. | ||||||
|  | flattenTests :: Test -> [Test] | ||||||
|  | flattenTests (TestLabel _ t@(TestList _)) = flattenTests t | ||||||
|  | flattenTests (TestList ts) = concatMap flattenTests ts | ||||||
|  | flattenTests t = [t] | ||||||
|  | 
 | ||||||
|  | -- | Filter TestLists in a Test, recursively, preserving the structure. | ||||||
|  | filterTests :: (Test -> Bool) -> Test -> Test | ||||||
|  | filterTests p (TestLabel l ts) = TestLabel l (filterTests p ts) | ||||||
|  | filterTests p (TestList ts) = TestList $ filter (any p . flattenTests) $ map (filterTests p) ts | ||||||
|  | filterTests _ t = t | ||||||
|  | 
 | ||||||
|  | -- | Simple way to assert something is some expected value, with no label. | ||||||
|  | is :: (Eq a, Show a) => a -> a -> Assertion | ||||||
|  | a `is` e = assertEqual "" e a | ||||||
|  | 
 | ||||||
|  | -- | Assert a parse result is successful, printing the parse error on failure. | ||||||
|  | assertParse :: (Either ParseError a) -> Assertion | ||||||
|  | assertParse parse = either (assertFailure.show) (const (return ())) parse | ||||||
|  | 
 | ||||||
|  | -- | Assert a parse result is successful, printing the parse error on failure. | ||||||
|  | assertParseFailure :: (Either ParseError a) -> Assertion | ||||||
|  | assertParseFailure parse = either (const $ return ()) (const $ assertFailure "parse should not have succeeded") parse | ||||||
|  | 
 | ||||||
|  | -- | Assert a parse result is some expected value, printing the parse error on failure. | ||||||
|  | assertParseEqual :: (Show a, Eq a) => (Either ParseError a) -> a -> Assertion | ||||||
|  | assertParseEqual parse expected = either (assertFailure.show) (`is` expected) parse | ||||||
|  | 
 | ||||||
|  | printParseError :: (Show a) => a -> IO () | ||||||
|  | printParseError e = do putStr "parse error at "; print e | ||||||
|  | 
 | ||||||
							
								
								
									
										87
									
								
								hledger-lib/Hledger/Utils/Tree.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								hledger-lib/Hledger/Utils/Tree.hs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,87 @@ | |||||||
|  | module Hledger.Utils.Tree where | ||||||
|  | 
 | ||||||
|  | -- import Data.Char | ||||||
|  | import Data.List (foldl') | ||||||
|  | import qualified Data.Map as M | ||||||
|  | import Data.Tree | ||||||
|  | -- import Text.Parsec | ||||||
|  | -- import Text.Printf | ||||||
|  | 
 | ||||||
|  | import Hledger.Utils.Regex | ||||||
|  | -- import Hledger.Utils.UTF8IOCompat (error') | ||||||
|  | 
 | ||||||
|  | -- standard tree helpers | ||||||
|  | 
 | ||||||
|  | root = rootLabel | ||||||
|  | subs = subForest | ||||||
|  | branches = subForest | ||||||
|  | 
 | ||||||
|  | -- | List just the leaf nodes of a tree | ||||||
|  | leaves :: Tree a -> [a] | ||||||
|  | leaves (Node v []) = [v] | ||||||
|  | leaves (Node _ branches) = concatMap leaves branches | ||||||
|  | 
 | ||||||
|  | -- | get the sub-tree rooted at the first (left-most, depth-first) occurrence | ||||||
|  | -- of the specified node value | ||||||
|  | subtreeat :: Eq a => a -> Tree a -> Maybe (Tree a) | ||||||
|  | subtreeat v t | ||||||
|  |     | root t == v = Just t | ||||||
|  |     | otherwise = subtreeinforest v $ subs t | ||||||
|  | 
 | ||||||
|  | -- | get the sub-tree for the specified node value in the first tree in | ||||||
|  | -- forest in which it occurs. | ||||||
|  | subtreeinforest :: Eq a => a -> [Tree a] -> Maybe (Tree a) | ||||||
|  | subtreeinforest _ [] = Nothing | ||||||
|  | subtreeinforest v (t:ts) = case (subtreeat v t) of | ||||||
|  |                              Just t' -> Just t' | ||||||
|  |                              Nothing -> subtreeinforest v ts | ||||||
|  | 
 | ||||||
|  | -- | remove all nodes past a certain depth | ||||||
|  | treeprune :: Int -> Tree a -> Tree a | ||||||
|  | treeprune 0 t = Node (root t) [] | ||||||
|  | treeprune d t = Node (root t) (map (treeprune $ d-1) $ branches t) | ||||||
|  | 
 | ||||||
|  | -- | apply f to all tree nodes | ||||||
|  | treemap :: (a -> b) -> Tree a -> Tree b | ||||||
|  | treemap f t = Node (f $ root t) (map (treemap f) $ branches t) | ||||||
|  | 
 | ||||||
|  | -- | remove all subtrees whose nodes do not fulfill predicate | ||||||
|  | treefilter :: (a -> Bool) -> Tree a -> Tree a | ||||||
|  | treefilter f t = Node | ||||||
|  |                  (root t) | ||||||
|  |                  (map (treefilter f) $ filter (treeany f) $ branches t) | ||||||
|  | 
 | ||||||
|  | -- | is predicate true in any node of tree ? | ||||||
|  | treeany :: (a -> Bool) -> Tree a -> Bool | ||||||
|  | treeany f t = f (root t) || any (treeany f) (branches t) | ||||||
|  | 
 | ||||||
|  | -- treedrop -- remove the leaves which do fulfill predicate. | ||||||
|  | -- treedropall -- do this repeatedly. | ||||||
|  | 
 | ||||||
|  | -- | show a compact ascii representation of a tree | ||||||
|  | showtree :: Show a => Tree a -> String | ||||||
|  | showtree = unlines . filter (regexMatches "[^ \\|]") . lines . drawTree . treemap show | ||||||
|  | 
 | ||||||
|  | -- | show a compact ascii representation of a forest | ||||||
|  | showforest :: Show a => Forest a -> String | ||||||
|  | showforest = concatMap showtree | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | -- | An efficient-to-build tree suggested by Cale Gibbard, probably | ||||||
|  | -- better than accountNameTreeFrom. | ||||||
|  | newtype FastTree a = T (M.Map a (FastTree a)) | ||||||
|  |   deriving (Show, Eq, Ord) | ||||||
|  | 
 | ||||||
|  | emptyTree = T M.empty | ||||||
|  | 
 | ||||||
|  | mergeTrees :: (Ord a) => FastTree a -> FastTree a -> FastTree a | ||||||
|  | mergeTrees (T m) (T m') = T (M.unionWith mergeTrees m m') | ||||||
|  | 
 | ||||||
|  | treeFromPath :: [a] -> FastTree a | ||||||
|  | treeFromPath []     = T M.empty | ||||||
|  | treeFromPath (x:xs) = T (M.singleton x (treeFromPath xs)) | ||||||
|  | 
 | ||||||
|  | treeFromPaths :: (Ord a) => [[a]] -> FastTree a | ||||||
|  | treeFromPaths = foldl' mergeTrees emptyTree . map treeFromPath | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @ -109,7 +109,11 @@ library | |||||||
|       Hledger.Reports.TransactionsReports |       Hledger.Reports.TransactionsReports | ||||||
|       Hledger.Utils |       Hledger.Utils | ||||||
|       Hledger.Utils.Debug |       Hledger.Utils.Debug | ||||||
|  |       Hledger.Utils.Parse | ||||||
|       Hledger.Utils.Regex |       Hledger.Utils.Regex | ||||||
|  |       Hledger.Utils.String | ||||||
|  |       Hledger.Utils.Test | ||||||
|  |       Hledger.Utils.Tree | ||||||
|       Hledger.Utils.UTF8IOCompat |       Hledger.Utils.UTF8IOCompat | ||||||
|   default-language: Haskell2010 |   default-language: Haskell2010 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -122,7 +122,11 @@ library: | |||||||
|     - Hledger.Reports.TransactionsReports |     - Hledger.Reports.TransactionsReports | ||||||
|     - Hledger.Utils |     - Hledger.Utils | ||||||
|     - Hledger.Utils.Debug |     - Hledger.Utils.Debug | ||||||
|  |     - Hledger.Utils.Parse | ||||||
|     - Hledger.Utils.Regex |     - Hledger.Utils.Regex | ||||||
|  |     - Hledger.Utils.String | ||||||
|  |     - Hledger.Utils.Test | ||||||
|  |     - Hledger.Utils.Tree | ||||||
|     - Hledger.Utils.UTF8IOCompat |     - Hledger.Utils.UTF8IOCompat | ||||||
| 
 | 
 | ||||||
| tests: | tests: | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user