more file reading cleanup, try each known format in turn
Currently this is just the journal and timelog file formats. This is more scalable, but when things go wrong there is no longer just a single parse error. For now we'll just show the first error, the one from the journal file parser.
This commit is contained in:
		
							parent
							
								
									a848a835a2
								
							
						
					
					
						commit
						1ec1f7c4ea
					
				| @ -37,6 +37,7 @@ import Hledger.Cli.Commands.Print | |||||||
| import Hledger.Cli.Commands.Register | import Hledger.Cli.Commands.Register | ||||||
| import Hledger.Data | import Hledger.Data | ||||||
| import Hledger.Read | import Hledger.Read | ||||||
|  | import Hledger.Read.Journal (someamount) | ||||||
| import Hledger.Cli.Options hiding (value) | import Hledger.Cli.Options hiding (value) | ||||||
| #ifdef MAKE | #ifdef MAKE | ||||||
| import Paths_hledger_make (getDataFileName) | import Paths_hledger_make (getDataFileName) | ||||||
|  | |||||||
| @ -36,7 +36,8 @@ import System.Time (ClockTime(TOD)) | |||||||
| import Hledger.Cli.Commands.All | import Hledger.Cli.Commands.All | ||||||
| import Hledger.Data  -- including testing utils in Hledger.Data.Utils | import Hledger.Data  -- including testing utils in Hledger.Data.Utils | ||||||
| import Hledger.Read.Common (emptyCtx) | import Hledger.Read.Common (emptyCtx) | ||||||
| import Hledger.Read (someamount,readJournal) | import Hledger.Read (readJournal) | ||||||
|  | import Hledger.Read.Journal (someamount) | ||||||
| import Hledger.Cli.Options | import Hledger.Cli.Options | ||||||
| import Hledger.Cli.Utils | import Hledger.Cli.Utils | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -321,3 +321,15 @@ isRight = not . isLeft | |||||||
| 
 | 
 | ||||||
| strictReadFile :: FilePath -> IO String | strictReadFile :: FilePath -> IO String | ||||||
| strictReadFile f = readFile f >>= \s -> Control.Exception.evaluate (length s) >> return s | strictReadFile f = readFile f >>= \s -> Control.Exception.evaluate (length s) >> return s | ||||||
|  | 
 | ||||||
|  | -- -- | Expand ~ in a file path (does not handle ~name). | ||||||
|  | -- tildeExpand :: FilePath -> IO FilePath | ||||||
|  | -- tildeExpand ('~':[])     = getHomeDirectory | ||||||
|  | -- tildeExpand ('~':'/':xs) = getHomeDirectory >>= return . (++ ('/':xs)) | ||||||
|  | -- --handle ~name, requires -fvia-C or ghc 6.8: | ||||||
|  | -- --import System.Posix.User | ||||||
|  | -- -- tildeExpand ('~':xs)     =  do let (user, path) = span (/= '/') xs | ||||||
|  | -- --                                pw <- getUserEntryForName user | ||||||
|  | -- --                                return (homeDirectory pw ++ path) | ||||||
|  | -- tildeExpand xs           =  return xs | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -7,35 +7,50 @@ Read hledger data from various data formats, and related utilities. | |||||||
| 
 | 
 | ||||||
| module Hledger.Read ( | module Hledger.Read ( | ||||||
|        tests_Hledger_Read, |        tests_Hledger_Read, | ||||||
|        module Hledger.Read.Common, |  | ||||||
|        Hledger.Read.Journal.someamount, |  | ||||||
|        readJournalFile, |  | ||||||
|        readJournal, |  | ||||||
|        myLedgerPath, |        myLedgerPath, | ||||||
|        myTimelogPath, |        myTimelogPath, | ||||||
|        myJournal, |        myJournal, | ||||||
|        myTimelog, |        myTimelog, | ||||||
|  |        readJournalFile, | ||||||
|  |        readJournal, | ||||||
| ) | ) | ||||||
| where | where | ||||||
| import Hledger.Data.Types (Journal(..)) | import Hledger.Data.Types (Journal(..)) | ||||||
| import Hledger.Data.Utils | import Hledger.Data.Utils | ||||||
| import Hledger.Read.Common | import Hledger.Read.Common | ||||||
| import qualified Hledger.Read.Journal (parseJournal,parseJournalFile,ledgerFile,someamount,tests_Journal) | import qualified Hledger.Read.Journal (parseJournal,ledgerFile,tests_Journal) | ||||||
| import qualified Hledger.Read.Timelog (tests_Timelog) --parseJournal | import qualified Hledger.Read.Timelog (parseJournal,tests_Timelog) | ||||||
| 
 | 
 | ||||||
| import Control.Monad.Error | import Control.Monad.Error | ||||||
|  | import Data.Either (partitionEithers) | ||||||
| import System.Directory (getHomeDirectory) | import System.Directory (getHomeDirectory) | ||||||
| import System.Environment (getEnv) | import System.Environment (getEnv) | ||||||
| import System.FilePath ((</>)) | import System.FilePath ((</>)) | ||||||
| import System.Exit | import System.Exit | ||||||
| import System.IO (stderr) | import System.IO (stderr) | ||||||
| #if __GLASGOW_HASKELL__ <= 610 | #if __GLASGOW_HASKELL__ <= 610 | ||||||
| import System.IO.UTF8 (hPutStrLn) | import Prelude hiding (readFile, putStr, putStrLn, print, getContents) | ||||||
|  | import System.IO.UTF8 | ||||||
| #else | #else | ||||||
| import System.IO (hPutStrLn) | import System.IO (hPutStrLn) | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | formats = [ | ||||||
|  |   "journal" | ||||||
|  |  ,"timelog" | ||||||
|  | -- ,"csv" | ||||||
|  |  ] | ||||||
|  | 
 | ||||||
|  | unknownformatmsg fp = printf "could not recognise %sdata in %s" (fmt formats) fp | ||||||
|  |     where fmt [] = "" | ||||||
|  |           fmt [f] = f ++ " " | ||||||
|  |           fmt fs = intercalate ", " (init fs) ++ " or " ++ last fs ++ " " | ||||||
|  | 
 | ||||||
|  | parsers = [Hledger.Read.Journal.parseJournal | ||||||
|  |           ,Hledger.Read.Timelog.parseJournal | ||||||
|  |           ] | ||||||
|  | 
 | ||||||
| ledgerenvvar           = "LEDGER" | ledgerenvvar           = "LEDGER" | ||||||
| timelogenvvar          = "TIMELOG" | timelogenvvar          = "TIMELOG" | ||||||
| ledgerdefaultfilename  = ".ledger" | ledgerdefaultfilename  = ".ledger" | ||||||
| @ -65,27 +80,28 @@ myJournal = myLedgerPath >>= readJournalFile | |||||||
| myTimelog :: IO Journal | myTimelog :: IO Journal | ||||||
| myTimelog = myTimelogPath >>= readJournalFile | myTimelog = myTimelogPath >>= readJournalFile | ||||||
| 
 | 
 | ||||||
| -- | Read a journal from this file, or throw an error. | -- | Read a journal from this file, trying all known data formats, | ||||||
|  | -- or give an error. | ||||||
| readJournalFile :: FilePath -> IO Journal | readJournalFile :: FilePath -> IO Journal | ||||||
| readJournalFile f = | readJournalFile "-" = getContents >>= journalFromPathAndString "(stdin)" | ||||||
|   (runErrorT . Hledger.Read.Journal.parseJournalFile) f >>= either printerror return | readJournalFile f   = readFile f  >>= journalFromPathAndString f | ||||||
|   where printerror e = hPutStrLn stderr e >> exitWith (ExitFailure 1) |  | ||||||
| 
 | 
 | ||||||
| -- | Read a Journal from this string, or throw an error. | -- | Read a Journal from this string, trying all known data formats, or | ||||||
|  | -- give an error. | ||||||
| readJournal :: String -> IO Journal | readJournal :: String -> IO Journal | ||||||
| readJournal s = | readJournal = journalFromPathAndString "(string)" | ||||||
|   (runErrorT . Hledger.Read.Journal.parseJournal "(from string)") s >>= either error return |  | ||||||
| 
 | 
 | ||||||
| -- -- | Expand ~ in a file path (does not handle ~name). | -- | Read a Journal from this string, trying each known data format in | ||||||
| -- tildeExpand :: FilePath -> IO FilePath | -- turn, or give an error.  The file path is also required. | ||||||
| -- tildeExpand ('~':[])     = getHomeDirectory | journalFromPathAndString :: FilePath -> String -> IO Journal | ||||||
| -- tildeExpand ('~':'/':xs) = getHomeDirectory >>= return . (++ ('/':xs)) | journalFromPathAndString f s = do | ||||||
| -- --handle ~name, requires -fvia-C or ghc 6.8: |   (errors, journals) <- partitionEithers `fmap` mapM try parsers | ||||||
| -- --import System.Posix.User |   case journals of j:_ -> return j | ||||||
| -- -- tildeExpand ('~':xs)     =  do let (user, path) = span (/= '/') xs |                    _   -> hPutStrLn stderr (errmsg errors) >> exitWith (ExitFailure 1) | ||||||
| -- --                                pw <- getUserEntryForName user |     where | ||||||
| -- --                                return (homeDirectory pw ++ path) |       try p = (runErrorT . p f) s | ||||||
| -- tildeExpand xs           =  return xs |       errmsg [] = unknownformatmsg f | ||||||
|  |       errmsg (e:_) = unlines [unknownformatmsg f, e] | ||||||
| 
 | 
 | ||||||
| tests_Hledger_Read = TestList | tests_Hledger_Read = TestList | ||||||
|   [ |   [ | ||||||
|  | |||||||
| @ -9,17 +9,29 @@ module Hledger.Read.Common | |||||||
| where | where | ||||||
| 
 | 
 | ||||||
| import Control.Monad.Error | import Control.Monad.Error | ||||||
| import Data.List | import Hledger.Data.Utils | ||||||
| import Hledger.Data.Types (Journal) | import Hledger.Data.Types (Journal) | ||||||
| import Text.ParserCombinators.Parsec | import Hledger.Data.Journal | ||||||
| import System.Directory (getHomeDirectory) | import System.Directory (getHomeDirectory) | ||||||
| import System.FilePath(takeDirectory,combine) | import System.FilePath(takeDirectory,combine) | ||||||
|  | import System.Time (getClockTime) | ||||||
|  | import Text.ParserCombinators.Parsec | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| -- | A JournalUpdate is some transformation of a "Journal". It can do I/O | -- | A JournalUpdate is some transformation of a "Journal". It can do I/O | ||||||
| -- or raise an error. | -- or raise an error. | ||||||
| type JournalUpdate = ErrorT String IO (Journal -> Journal) | type JournalUpdate = ErrorT String IO (Journal -> Journal) | ||||||
| 
 | 
 | ||||||
|  | -- | Given a JournalUpdate-generating parsec parser, file path and data string, | ||||||
|  | -- parse and post-process a Journal so that it's ready to use, or give an error. | ||||||
|  | parseJournalWith :: (GenParser Char LedgerFileCtx JournalUpdate) -> FilePath -> String -> ErrorT String IO Journal | ||||||
|  | parseJournalWith p f s = do | ||||||
|  |   tc <- liftIO getClockTime | ||||||
|  |   tl <- liftIO getCurrentLocalTime | ||||||
|  |   case runParser p emptyCtx f s of | ||||||
|  |     Right updates -> liftM (journalFinalise tc tl f s) $ updates `ap` return nulljournal | ||||||
|  |     Left err      -> throwError $ show err -- XXX raises an uncaught exception if we have a parsec user error, eg from many ? | ||||||
|  | 
 | ||||||
| -- | Some context kept during parsing. | -- | Some context kept during parsing. | ||||||
| data LedgerFileCtx = Ctx { | data LedgerFileCtx = Ctx { | ||||||
|       ctxYear     :: !(Maybe Integer)  -- ^ the default year most recently specified with Y |       ctxYear     :: !(Maybe Integer)  -- ^ the default year most recently specified with Y | ||||||
|  | |||||||
| @ -103,15 +103,20 @@ i, o, b, h | |||||||
| 
 | 
 | ||||||
| -} | -} | ||||||
| 
 | 
 | ||||||
| module Hledger.Read.Journal {- ( | module Hledger.Read.Journal ( | ||||||
|  |        tests_Journal, | ||||||
|        parseJournal, |        parseJournal, | ||||||
|        parseJournalFile, |        ledgerFile, | ||||||
|        someamount, |        someamount, | ||||||
|        emptyCtx, |        ledgeraccountname, | ||||||
|        ledgeraccountname |        ledgerExclamationDirective, | ||||||
| ) -} |        ledgerHistoricalPrice, | ||||||
|  |        ledgerDefaultYear, | ||||||
|  |        emptyLine, | ||||||
|  |        ledgerdatetime, | ||||||
|  | ) | ||||||
| where | where | ||||||
| import Control.Monad.Error (ErrorT(..), MonadIO, liftIO, throwError, catchError) | import Control.Monad.Error (ErrorT(..), throwError, catchError) | ||||||
| import Text.ParserCombinators.Parsec | import Text.ParserCombinators.Parsec | ||||||
| #if __GLASGOW_HASKELL__ <= 610 | #if __GLASGOW_HASKELL__ <= 610 | ||||||
| import Prelude hiding (readFile, putStr, putStrLn, print, getContents) | import Prelude hiding (readFile, putStr, putStrLn, print, getContents) | ||||||
| @ -127,27 +132,14 @@ import Hledger.Data.Posting | |||||||
| import Hledger.Data.Journal | import Hledger.Data.Journal | ||||||
| import Hledger.Data.Commodity (dollars,dollar,unknown,nonsimplecommoditychars) | import Hledger.Data.Commodity (dollars,dollar,unknown,nonsimplecommoditychars) | ||||||
| import Hledger.Read.Common | import Hledger.Read.Common | ||||||
| import System.Time (getClockTime) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| -- let's get to it | -- let's get to it | ||||||
| 
 | 
 | ||||||
| -- | Parse and post-process a journal file or timelog file to a "Journal", |  | ||||||
| -- or give an error. |  | ||||||
| parseJournalFile :: FilePath -> ErrorT String IO Journal |  | ||||||
| parseJournalFile "-" = liftIO getContents >>= parseJournal "-" |  | ||||||
| parseJournalFile f   = liftIO (readFile f) >>= parseJournal f |  | ||||||
| 
 |  | ||||||
| -- | Parse and post-process a "Journal" from hledger's journal file | -- | Parse and post-process a "Journal" from hledger's journal file | ||||||
| -- format, saving the provided file path and the current time, or give an | -- format, or give an error. | ||||||
| -- error. |  | ||||||
| parseJournal :: FilePath -> String -> ErrorT String IO Journal | parseJournal :: FilePath -> String -> ErrorT String IO Journal | ||||||
| parseJournal f s = do | parseJournal = parseJournalWith ledgerFile | ||||||
|   tc <- liftIO getClockTime |  | ||||||
|   tl <- liftIO getCurrentLocalTime |  | ||||||
|   case runParser ledgerFile emptyCtx f s of |  | ||||||
|     Right m  -> liftM (journalFinalise tc tl f s) $ m `ap` return nulljournal |  | ||||||
|     Left err -> throwError $ show err -- XXX raises an uncaught exception if we have a parsec user error, eg from many ? |  | ||||||
| 
 | 
 | ||||||
| -- | Top-level journal parser. Returns a single composite, I/O performing, | -- | Top-level journal parser. Returns a single composite, I/O performing, | ||||||
| -- error-raising "JournalUpdate" which can be applied to an empty journal | -- error-raising "JournalUpdate" which can be applied to an empty journal | ||||||
| @ -170,7 +162,7 @@ ledgerFile = do items <- many ledgerItem | |||||||
|                           , ledgerTagDirective |                           , ledgerTagDirective | ||||||
|                           , ledgerEndTagDirective |                           , ledgerEndTagDirective | ||||||
|                           , emptyLine >> return (return id) |                           , emptyLine >> return (return id) | ||||||
|                           ] <?> "ledger transaction, timelog entry, or directive" |                           ] <?> "ledger transaction or directive" | ||||||
| 
 | 
 | ||||||
| emptyLine :: GenParser Char st () | emptyLine :: GenParser Char st () | ||||||
| emptyLine = do many spacenonewline | emptyLine = do many spacenonewline | ||||||
|  | |||||||
| @ -42,29 +42,23 @@ o 2007/03/10 17:26:02 | |||||||
| 
 | 
 | ||||||
| -} | -} | ||||||
| 
 | 
 | ||||||
| module Hledger.Read.Timelog {- ( | module Hledger.Read.Timelog ( | ||||||
|  |        tests_Timelog, | ||||||
|        parseJournal, |        parseJournal, | ||||||
|        parseJournalFile | ) | ||||||
| ) -} |  | ||||||
| where | where | ||||||
| import Control.Monad.Error (ErrorT(..), MonadIO, liftIO, throwError) | import Control.Monad.Error (ErrorT(..)) | ||||||
| import Text.ParserCombinators.Parsec | import Text.ParserCombinators.Parsec | ||||||
| import System.Time (getClockTime) |  | ||||||
| import Hledger.Data | import Hledger.Data | ||||||
| import Hledger.Read.Common (LedgerFileCtx,JournalUpdate,emptyCtx,getParentAccount) | import Hledger.Read.Common | ||||||
| import Hledger.Read.Journal hiding (parseJournal, parseJournalFile) | import Hledger.Read.Journal hiding (parseJournal) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| -- | Parse and post-process a "Journal" from timeclock.el's timelog | -- | Parse and post-process a "Journal" from timeclock.el's timelog | ||||||
| -- format, saving the provided file path and the current time, or give an | -- format, saving the provided file path and the current time, or give an | ||||||
| -- error. | -- error. | ||||||
| parseJournal :: FilePath -> String -> ErrorT String IO Journal | parseJournal :: FilePath -> String -> ErrorT String IO Journal | ||||||
| parseJournal f s = do | parseJournal = parseJournalWith timelogFile | ||||||
|   tc <- liftIO getClockTime |  | ||||||
|   tl <- liftIO getCurrentLocalTime |  | ||||||
|   case runParser timelogFile emptyCtx f s of |  | ||||||
|     Right m  -> liftM (journalFinalise tc tl f s) $ m `ap` return nulljournal |  | ||||||
|     Left err -> throwError $ show err -- XXX raises an uncaught exception if we have a parsec user error, eg from many ? |  | ||||||
| 
 | 
 | ||||||
| timelogFile :: GenParser Char LedgerFileCtx JournalUpdate | timelogFile :: GenParser Char LedgerFileCtx JournalUpdate | ||||||
| timelogFile = do items <- many timelogItem | timelogFile = do items <- many timelogItem | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user