diff --git a/hledger-lib/Hledger/Write/Ods.hs b/hledger-lib/Hledger/Write/Ods.hs new file mode 100644 index 000000000..0b93e4d13 --- /dev/null +++ b/hledger-lib/Hledger/Write/Ods.hs @@ -0,0 +1,109 @@ +{- | +Export table data as OpenDocument Spreadsheet +. +This format supports character encodings, fixed header rows and columns, +number formatting, text styles, merged cells, formulas, hyperlinks. +Currently we support Flat ODS, a plain uncompressed XML format. + +This is derived from +-} +module Hledger.Write.Ods where + +import qualified Data.Text.Lazy as TL +import qualified Data.Text as T +import Data.Text (Text) + +import qualified Data.Map as Map +import Data.Foldable (fold) +import Data.Map (Map) + +import qualified System.IO as IO +import Text.Printf (printf) + + +printFods :: + IO.TextEncoding -> Map Text ((Maybe Int, Maybe Int), [[Text]]) -> TL.Text +printFods encoding tables = + let fileOpen = + map (map (\c -> case c of '\'' -> '"'; _ -> c)) $ + printf "" (show encoding) : + "" : + [] + + fileClose = + "" : + [] + + tableConfig tableNames = + " " : + " " : + " " : + " " : + " " : + (fold $ + flip Map.mapWithKey tableNames $ \tableName (mTopRow,mLeftColumn) -> + printf " " tableName : + (flip foldMap mLeftColumn $ \leftColumn -> + " 2" : + printf " %d" leftColumn : + printf " %d" leftColumn : + []) ++ + (flip foldMap mTopRow $ \topRow -> + " 2" : + printf " %d" topRow : + printf " %d" topRow : + []) ++ + " " : + []) ++ + " " : + " " : + " " : + " " : + " " : + [] + + tableOpen name = + "" : + "" : + printf "" name : + [] + + tableClose = + "" : + "" : + "" : + [] + + in TL.unlines $ map (TL.fromStrict . T.pack) $ + fileOpen ++ + tableConfig (fmap fst tables) ++ + (Map.toAscList tables >>= \(name,(_,table)) -> + tableOpen name ++ + (table >>= \row -> + "" : + (row >>= \cell -> + "" : + printf "%s" cell : + "" : + []) ++ + "" : + []) ++ + tableClose) ++ + fileClose diff --git a/hledger-lib/hledger-lib.cabal b/hledger-lib/hledger-lib.cabal index 33b22247c..da78d3f65 100644 --- a/hledger-lib/hledger-lib.cabal +++ b/hledger-lib/hledger-lib.cabal @@ -86,6 +86,7 @@ library Hledger.Read.TimedotReader Hledger.Read.TimeclockReader Hledger.Write.Csv + Hledger.Write.Ods Hledger.Reports Hledger.Reports.ReportOptions Hledger.Reports.ReportTypes diff --git a/hledger-lib/package.yaml b/hledger-lib/package.yaml index 6fdf07245..a7cbef5c4 100644 --- a/hledger-lib/package.yaml +++ b/hledger-lib/package.yaml @@ -149,6 +149,7 @@ library: - Hledger.Read.TimedotReader - Hledger.Read.TimeclockReader - Hledger.Write.Csv + - Hledger.Write.Ods - Hledger.Reports - Hledger.Reports.ReportOptions - Hledger.Reports.ReportTypes diff --git a/hledger/Hledger/Cli/CliOptions.hs b/hledger/Hledger/Cli/CliOptions.hs index 7ba115893..d79a7cf4c 100644 --- a/hledger/Hledger/Cli/CliOptions.hs +++ b/hledger/Hledger/Cli/CliOptions.hs @@ -718,7 +718,7 @@ defaultOutputFormat = "txt" -- | All the output formats known by any command, for outputFormatFromOpts. -- To automatically infer it from -o/--output-file, it needs to be listed here. outputFormats :: [String] -outputFormats = [defaultOutputFormat, "beancount", "csv", "json", "html", "sql", "tsv"] +outputFormats = [defaultOutputFormat, "beancount", "csv", "json", "html", "sql", "tsv", "fods"] -- | Get the output format from the --output-format option, -- otherwise from a recognised file extension in the --output-file option, diff --git a/hledger/Hledger/Cli/Commands/Balance.hs b/hledger/Hledger/Cli/Commands/Balance.hs index cb7df27c6..745be9a91 100644 --- a/hledger/Hledger/Cli/Commands/Balance.hs +++ b/hledger/Hledger/Cli/Commands/Balance.hs @@ -282,6 +282,7 @@ import Data.Decimal (roundTo) import Data.Default (def) import Data.Function (on) import Data.List (find, transpose, foldl') +import qualified Data.Map as Map import qualified Data.Set as S import Data.Maybe (catMaybes, fromMaybe) import Data.Text (Text) @@ -296,10 +297,13 @@ import Text.Tabular.AsciiWide (Header(..), Align(..), Properties(..), Cell(..), Table(..), TableOpts(..), cellWidth, concatTables, renderColumns, renderRowB, renderTableByRowsB, textCell) +import qualified System.IO as IO + import Hledger import Hledger.Cli.CliOptions import Hledger.Cli.Utils import Hledger.Write.Csv (CSV, printCSV, printTSV) +import Hledger.Write.Ods (printFods) -- | Command line options for this command. @@ -354,7 +358,7 @@ balancemode = hledgerCommandMode ,"'tidy' : every attribute in its own column" ]) -- output: - ,outputFormatFlag ["txt","html","csv","tsv","json"] + ,outputFormatFlag ["txt","html","csv","tsv","json","fods"] ,outputFileFlag ] ) @@ -398,6 +402,7 @@ balance opts@CliOpts{reportspec_=rspec} j = case balancecalc_ of "tsv" -> \ropts1 -> printTSV . balanceReportAsCsv ropts1 -- "html" -> \ropts -> (<>"\n") . L.renderText . multiBalanceReportAsHtml ropts . balanceReportAsMultiBalanceReport ropts "json" -> const $ (<>"\n") . toJsonText + "fods" -> \ropts1 -> printFods IO.localeEncoding . Map.singleton "Hledger" . (,) (Just 1, Nothing) . balanceReportAsCsv ropts1 _ -> error' $ unsupportedOutputFormatError fmt -- PARTIAL: writeOutputLazyText opts $ render ropts report where