hledger-web 1.16.2 → 1.17
raw patch · 17 files changed
+384/−285 lines, 17 filesdep +extradep +networkdep +unix-compatdep ~basedep ~hledgerdep ~hledger-libPVP ok
version bump matches the API change (PVP)
Dependencies added: extra, network, unix-compat
Dependency ranges changed: base, hledger, hledger-lib, wai-handler-launch
API changes (from Hackage documentation)
- Hledger.Web.Import: askUnliftIO :: MonadUnliftIO m => m (UnliftIO m)
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON (Data.Decimal.DecimalRaw GHC.Integer.Type.Integer)
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.Account
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.AccountDeclarationInfo
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.Amount
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.AmountPrice
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.AmountStyle
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.BalanceAssertion
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.DigitGroupStyle
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.GenericSourcePos
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.MarketPrice
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.MixedAmount
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.Posting
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.PostingType
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.Side
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.Status
- Hledger.Web.Json: instance Data.Aeson.Types.FromJSON.FromJSON Hledger.Data.Types.Transaction
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Data.Decimal.Decimal
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.Account
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.Amount
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.AmountPrice
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.AmountStyle
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.BalanceAssertion
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.DigitGroupStyle
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.GenericSourcePos
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.MarketPrice
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.MixedAmount
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.Posting
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.PostingType
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.Side
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.Status
- Hledger.Web.Json: instance Data.Aeson.Types.ToJSON.ToJSON Hledger.Data.Types.Transaction
- Hledger.Web.Json: instance GHC.Generics.Generic (Data.Decimal.DecimalRaw a)
- Hledger.Web.Json: readJsonFile :: FromJSON a => FilePath -> IO a
- Hledger.Web.Json: writeJsonFile :: ToJSON a => FilePath -> a -> IO ()
- Hledger.Web.Widget.Common: writeValidJournal :: MonadHandler m => FilePath -> Text -> m (Either String ())
+ Hledger.Web.Import: fieldError :: Text -> Text -> Text -> Text
+ Hledger.Web.WebOptions: [socket_] :: WebOpts -> Maybe String
+ Hledger.Web.Widget.Common: writeJournalTextIfValidAndChanged :: MonadHandler m => FilePath -> Text -> m (Either String ())
- Hledger.Web.WebOptions: WebOpts :: Bool -> Bool -> Maybe String -> String -> Int -> String -> Maybe String -> [Capability] -> Maybe (CI ByteString) -> CliOpts -> WebOpts
+ Hledger.Web.WebOptions: WebOpts :: Bool -> Bool -> Maybe String -> String -> Int -> String -> Maybe String -> [Capability] -> Maybe (CI ByteString) -> CliOpts -> Maybe String -> WebOpts
Files
- CHANGES.md +36/−2
- Hledger/Web/Handler/AddR.hs +0/−1
- Hledger/Web/Handler/EditR.hs +3/−3
- Hledger/Web/Handler/MiscR.hs +0/−1
- Hledger/Web/Handler/UploadR.hs +4/−4
- Hledger/Web/Json.hs +0/−167
- Hledger/Web/Main.hs +29/−5
- Hledger/Web/Settings.hs +4/−1
- Hledger/Web/WebOptions.hs +13/−7
- Hledger/Web/Widget/AddForm.hs +3/−2
- Hledger/Web/Widget/Common.hs +20/−6
- hledger-web.1 +59/−9
- hledger-web.cabal +12/−10
- hledger-web.info +112/−19
- hledger-web.txt +82/−47
- static/hledger.css +6/−0
- static/js/jquery.flot.js +1/−1
CHANGES.md view
@@ -1,6 +1,40 @@ User-visible changes in hledger-web. See also the hledger changelog. +# 1.17 2020-03-01++- Fonts have been improved on certain platforms. (David Zhang)++- IPv6 is supported (Amarandus) (#1145)++- The --host option can now take a local hostname (Amarandus) (#1145)++- New --socket option to run hledger-web over an AF_UNIX socket file. (Carl Richard Theodor Schneider)+ This allows running multiple instances of hledger-web on the same+ system without having to manually choose a port for each instance,+ which is helpful for running individual instances for multiple+ users. In this scenario, the socket path is predictable, as it can+ be derived from the username.++- The edit and upload forms now normalise line endings, avoiding parse+ errors (#1194). Summary of current behaviour:++ - hledger add and import commands will append with (at least some)+ unix line endings, possibly causing the file to have mixed line+ endings++ - hledger-web edit and upload forms will write the file with+ the current system's native line endings, ie changing all+ line endings if the file previously used foreign line endings.++- Numbers in JSON output now provide a floating point Number+ representation as well as our native Decimal object representation,+ since the later can sometimes contain 255-digit integers. The+ floating point numbers can have up to 10 decimal digits (and an+ unbounded number of integer digits.)+ Experimental, suggestions needed. (#1195)++ # 1.16.2 2020-01-14 - add support for megaparsec 8 (#1175)@@ -9,9 +43,9 @@ # 1.16.1 2019-12-03 -- Drop old json dependency (#1190)+- Drop unnecessary json (#1190), mtl-compat dependencies -- Drop unnecessary mtl-compat dependency+- use hledger 1.16.1, fixing GHC 8.0/8.2 build # 1.16 2019-12-01
Hledger/Web/Handler/AddR.hs view
@@ -19,7 +19,6 @@ import Hledger import Hledger.Cli.Commands.Add (appendToJournalFileOrStdout, journalAddTransaction) import Hledger.Web.Import-import Hledger.Web.Json () import Hledger.Web.WebOptions (WebOpts(..)) import Hledger.Web.Widget.AddForm (addForm)
Hledger/Web/Handler/EditR.hs view
@@ -12,7 +12,7 @@ import Hledger.Web.Import import Hledger.Web.Widget.Common- (fromFormSuccess, helplink, journalFile404, writeValidJournal)+ (fromFormSuccess, helplink, journalFile404, writeJournalTextIfValidAndChanged) editForm :: FilePath -> Text -> Markup -> MForm Handler (FormResult Text, Widget) editForm f txt =@@ -35,8 +35,8 @@ (f', txt) <- journalFile404 f j ((res, view), enctype) <- runFormPost (editForm f' txt)- text <- fromFormSuccess (showForm view enctype) res- writeValidJournal f text >>= \case+ newtxt <- fromFormSuccess (showForm view enctype) res+ writeJournalTextIfValidAndChanged f newtxt >>= \case Left e -> do setMessage $ "Failed to load journal: " <> toHtml e showForm view enctype
Hledger/Web/Handler/MiscR.hs view
@@ -25,7 +25,6 @@ import Yesod.Default.Handlers (getFaviconR, getRobotsR) import Hledger-import Hledger.Web.Json () import Hledger.Web.Import import Hledger.Web.Widget.Common (journalFile404)
Hledger/Web/Handler/UploadR.hs view
@@ -15,7 +15,7 @@ import qualified Data.Text.Encoding as TE import Hledger.Web.Import-import Hledger.Web.Widget.Common (fromFormSuccess, journalFile404, writeValidJournal)+import Hledger.Web.Widget.Common (fromFormSuccess, journalFile404, writeJournalTextIfValidAndChanged) uploadForm :: FilePath -> Markup -> MForm Handler (FormResult FileInfo, Widget) uploadForm f =@@ -44,15 +44,15 @@ -- Try to parse as UTF-8 -- XXX Unfortunate - how to parse as system locale?- text <- case TE.decodeUtf8' lbs of+ newtxt <- case TE.decodeUtf8' lbs of Left e -> do setMessage $ "Encoding error: '" <> toHtml (show e) <> "'. " <> "If your file is not UTF-8 encoded, try the 'edit form', " <> "where the transcoding should be handled by the browser." showForm view enctype- Right text -> return text- writeValidJournal f text >>= \case+ Right newtxt -> return newtxt+ writeJournalTextIfValidAndChanged f newtxt >>= \case Left e -> do setMessage $ "Failed to load journal: " <> toHtml e showForm view enctype
− Hledger/Web/Json.hs
@@ -1,167 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}----{-# LANGUAGE CPP #-}---{-# LANGUAGE DataKinds #-}---{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}---{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}---{-# LANGUAGE NamedFieldPuns #-}---{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE OverloadedStrings #-}---{-# LANGUAGE PolyKinds #-}---{-# LANGUAGE QuasiQuotes #-}---{-# LANGUAGE QuasiQuotes #-}---{-# LANGUAGE Rank2Types #-}---{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE RecordWildCards #-}---{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE StandaloneDeriving #-}---{-# LANGUAGE TemplateHaskell #-}---{-# LANGUAGE TypeFamilies #-}---{-# LANGUAGE TypeOperators #-}--module Hledger.Web.Json (- -- * Instances- -- * Utilities- readJsonFile- ,writeJsonFile-) where--import Data.Aeson---import Data.Aeson.TH-import qualified Data.ByteString.Lazy as BL-import Data.Decimal-import Data.Maybe-import GHC.Generics (Generic)--import Hledger.Data---- JSON instances. Should they be in hledger-lib Types.hs ?---- To JSON--instance ToJSON Status-instance ToJSON GenericSourcePos-instance ToJSON Decimal-instance ToJSON Amount-instance ToJSON AmountStyle-instance ToJSON Side-instance ToJSON DigitGroupStyle-instance ToJSON MixedAmount-instance ToJSON BalanceAssertion-instance ToJSON AmountPrice-instance ToJSON MarketPrice-instance ToJSON PostingType--instance ToJSON Posting where- toJSON Posting{..} = object- ["pdate" .= toJSON pdate- ,"pdate2" .= toJSON pdate2- ,"pstatus" .= toJSON pstatus- ,"paccount" .= toJSON paccount- ,"pamount" .= toJSON pamount- ,"pcomment" .= toJSON pcomment- ,"ptype" .= toJSON ptype- ,"ptags" .= toJSON ptags- ,"pbalanceassertion" .= toJSON pbalanceassertion- -- To avoid a cycle, show just the parent transaction's index number- -- in a dummy field. When re-parsed, there will be no parent.- ,"ptransaction_" .= toJSON (maybe "" (show.tindex) ptransaction)- -- This is probably not wanted in json, we discard it.- ,"poriginal" .= toJSON (Nothing :: Maybe Posting)- ]--instance ToJSON Transaction--instance ToJSON Account where- toJSON a = object- ["aname" .= toJSON (aname a)- ,"aebalance" .= toJSON (aebalance a)- ,"aibalance" .= toJSON (aibalance a)- ,"anumpostings" .= toJSON (anumpostings a)- ,"aboring" .= toJSON (aboring a)- -- To avoid a cycle, show just the parent account's name- -- in a dummy field. When re-parsed, there will be no parent.- ,"aparent_" .= toJSON (maybe "" aname $ aparent a)- -- Just the names of subaccounts, as a dummy field, ignored when parsed.- ,"asubs_" .= toJSON (map aname $ asubs a)- -- The actual subaccounts (and their subs..), making a (probably highly redundant) tree- -- ,"asubs" .= toJSON (asubs a)- -- Omit the actual subaccounts- ,"asubs" .= toJSON ([]::[Account])- ]---- From JSON--instance FromJSON Status-instance FromJSON GenericSourcePos-instance FromJSON Amount-instance FromJSON AmountStyle-instance FromJSON Side-instance FromJSON DigitGroupStyle-instance FromJSON MixedAmount-instance FromJSON BalanceAssertion-instance FromJSON AmountPrice-instance FromJSON MarketPrice-instance FromJSON PostingType-instance FromJSON Posting-instance FromJSON Transaction-instance FromJSON AccountDeclarationInfo--- XXX The ToJSON instance replaces subaccounts with just names.--- Here we should try to make use of those to reconstruct the--- parent-child relationships.-instance FromJSON Account---- Decimal, various attempts------ https://stackoverflow.com/questions/40331851/haskell-data-decimal-as-aeson-type-----instance FromJSON Decimal where parseJSON =----- A.withScientific "Decimal" (return . right . eitherFromRational . toRational)------ https://github.com/bos/aeson/issues/474--- http://hackage.haskell.org/package/aeson-1.4.2.0/docs/Data-Aeson-TH.html--- $(deriveFromJSON defaultOptions ''Decimal) -- doesn't work--- $(deriveFromJSON defaultOptions ''DecimalRaw) -- works; requires TH, but gives better parse error messages------ https://github.com/PaulJohnson/Haskell-Decimal/issues/6---deriving instance Generic Decimal---instance FromJSON Decimal-deriving instance Generic (DecimalRaw a)-instance FromJSON (DecimalRaw Integer)------ @simonmichael, I think the code in your first comment should work if it compiles—though “work” doesn’t mean you can parse a JSON number directly into a `Decimal` using the generic instance, as you’ve discovered.------Error messages with these extensions are always rather cryptic, but I’d prefer them to Template Haskell. Typically you’ll want to start by getting a generic `ToJSON` instance working, then use that to figure out what the `FromJSON` instance expects to parse: for a correct instance, `encode` and `decode` should give you an isomorphism between your type and a subset of `Bytestring` (up to the `Maybe` wrapper that `decode` returns).------I don’t have time to test it right now, but I think it will also work without `DeriveAnyClass`, just using `DeriveGeneric` and `StandAloneDeriving`. It should also work to use the [`genericParseJSON`](http://hackage.haskell.org/package/aeson/docs/Data-Aeson.html#v:genericParseJSON) function to implement the class explicitly, something like this:------{-# LANGUAGE DeriveGeneric #-}---{-# LANGUAGE StandAloneDeriving #-}---import GHC.Generics---import Data.Aeson---deriving instance Generic Decimal---instance FromJSON Decimal where--- parseJSON = genericParseJSON defaultOptions------And of course you can avoid `StandAloneDeriving` entirely if you’re willing to wrap `Decimal` in your own `newtype`.----- Utilities---- | Read a json from a file and decode/parse it as the target type, if we can.--- Example:--- >>> readJsonFile "in.json" :: IO MixedAmount-readJsonFile :: FromJSON a => FilePath -> IO a-readJsonFile f = do- bs <- BL.readFile f- let v = fromMaybe (error "could not decode bytestring as json value") (decode bs :: Maybe Value)- case fromJSON v :: FromJSON a => Result a of- Error e -> error e- Success t -> return t---- | Write some to-JSON-convertible haskell value to a json file, if we can.--- Example:--- >>> writeJsonFile "out.json" nullmixedamt-writeJsonFile :: ToJSON a => FilePath -> a -> IO ()-writeJsonFile f v = BL.writeFile f (encode $ toJSON v)
Hledger/Web/Main.hs view
@@ -9,15 +9,19 @@ module Hledger.Web.Main where +import Control.Exception (bracket) import Control.Monad (when) import Data.String (fromString) import qualified Data.Text as T+import Network.Socket import Network.Wai (Application)-import Network.Wai.Handler.Warp (runSettings, defaultSettings, setHost, setPort)-import Network.Wai.Handler.Launch (runHostPortUrl)+import Network.Wai.Handler.Warp (runSettings, runSettingsSocket, defaultSettings, setHost, setPort)+import Network.Wai.Handler.Launch (runHostPortFullUrl) import Prelude hiding (putStrLn)-import System.Exit (exitSuccess)+import System.Directory (removeFile)+import System.Exit (exitSuccess, exitFailure) import System.IO (hFlush, stdout)+import System.PosixCompat.Files (getFileStatus, isSocket) import Text.Printf (printf) import Yesod.Default.Config import Yesod.Default.Main (defaultDevelApp)@@ -76,11 +80,31 @@ putStrLn "Press ctrl-c to quit" hFlush stdout let warpsettings = setHost (fromString h) (setPort p defaultSettings)- Network.Wai.Handler.Warp.runSettings warpsettings app+ case socket_ opts of+ Just s -> do+ if isUnixDomainSocketAvailable then+ bracket+ (do+ sock <- socket AF_UNIX Stream 0+ setSocketOption sock ReuseAddr 1+ bind sock $ SockAddrUnix s+ listen sock maxListenQueue+ return sock+ )+ (\_ -> do+ sockstat <- getFileStatus s+ when (isSocket sockstat) $ removeFile s+ )+ (\sock -> Network.Wai.Handler.Warp.runSettingsSocket warpsettings sock app)+ else do+ putStrLn "Unix domain sockets are not available on your operating system"+ putStrLn "Please try again without --socket"+ exitFailure+ Nothing -> Network.Wai.Handler.Warp.runSettings warpsettings app else do putStrLn "This server will exit after 2m with no browser windows open (or press ctrl-c)" putStrLn "Opening web browser..." hFlush stdout -- exits after 2m of inactivity (hardcoded)- Network.Wai.Handler.Launch.runHostPortUrl h p "" app+ Network.Wai.Handler.Launch.runHostPortFullUrl h p u app
Hledger/Web/Settings.hs view
@@ -45,7 +45,10 @@ defbaseurl :: String -> Int -> String defbaseurl host port =- "http://" ++ host ++ if port /= 80 then ":" ++ show port else ""+ if ':' `elem` host then+ "http://[" ++ host ++ "]" ++ if port /= 80 then ":" ++ show port else ""+ else+ "http://" ++ host ++ if port /= 80 then ":" ++ show port else "" -- Static setting below. Changing these requires a recompile
Hledger/Web/WebOptions.hs view
@@ -44,6 +44,11 @@ "ORIGIN" ("allow cross-origin requests from the specified origin; setting ORIGIN to \"*\" allows requests from any origin") , flagReq+ ["socket"]+ (\s opts -> Right $ setopt "socket" s opts)+ "SOCKET"+ "use the given socket instead of the given IP and port (implies --serve)"+ , flagReq ["host"] (\s opts -> Right $ setopt "host" s opts) "IPADDR"@@ -110,10 +115,11 @@ , capabilities_ :: [Capability] , capabilitiesHeader_ :: Maybe (CI ByteString) , cliopts_ :: CliOpts+ , socket_ :: Maybe String } deriving (Show) defwebopts :: WebOpts-defwebopts = WebOpts def def Nothing def def def def [CapView, CapAdd] Nothing def+defwebopts = WebOpts def def Nothing def def def def [CapView, CapAdd] Nothing def Nothing instance Default WebOpts where def = defwebopts @@ -131,9 +137,12 @@ Left e -> error' ("Unknown capability: " ++ T.unpack e) Right [] -> [CapView, CapAdd] Right xs -> xs+ sock = stripTrailingSlash <$> maybestringopt "socket" rawopts return defwebopts- { serve_ = boolopt "serve" rawopts+ { serve_ = case sock of+ Just _ -> True+ Nothing -> boolopt "serve" rawopts , serve_api_ = boolopt "serve-api" rawopts , cors_ = maybestringopt "cors" rawopts , host_ = h@@ -143,16 +152,13 @@ , capabilities_ = caps , capabilitiesHeader_ = mk . BC.pack <$> maybestringopt "capabilities-header" rawopts , cliopts_ = cliopts+ , socket_ = sock } where stripTrailingSlash = reverse . dropWhile (== '/') . reverse -- yesod don't like it checkWebOpts :: WebOpts -> WebOpts-checkWebOpts wopts = do- let h = host_ wopts- if any (`notElem` (".0123456789" :: String)) h- then usageError $ "--host requires an IP address, not " ++ show h- else wopts+checkWebOpts = id getHledgerWebOpts :: IO WebOpts getHledgerWebOpts = do
Hledger/Web/Widget/AddForm.hs view
@@ -13,7 +13,8 @@ import Control.Monad.State.Strict (evalStateT) import Data.Bifunctor (first)-import Data.List (dropWhileEnd, intercalate, nub, sort, unfoldr)+import Data.List (dropWhileEnd, intercalate, unfoldr)+import Data.List.Extra (nubSort) import Data.Maybe (isJust) #if !(MIN_VERSION_base(4,13,0)) import Data.Semigroup ((<>))@@ -71,7 +72,7 @@ let (postRes, displayRows) = validatePostings acctRes amtRes -- bindings used in add-form.hamlet- let descriptions = sort $ nub $ tdescription <$> jtxns j+ let descriptions = nubSort $ tdescription <$> jtxns j journals = fst <$> jfiles j pure (validateTransaction dateRes descRes postRes, $(widgetFile "add-form"))
Hledger/Web/Widget/Common.hs view
@@ -12,7 +12,7 @@ , helplink , mixedAmountAsHtml , fromFormSuccess- , writeValidJournal+ , writeJournalTextIfValidAndChanged , journalFile404 ) where @@ -50,14 +50,28 @@ fromFormSuccess h (FormFailure _) = h fromFormSuccess _ (FormSuccess a) = pure a -writeValidJournal :: MonadHandler m => FilePath -> Text -> m (Either String ())-writeValidJournal f txt =- liftIO (readJournal def (Just f) txt) >>= \case+-- | A helper for postEditR/postUploadR: check that the given text+-- parses as a Journal, and if so, write it to the given file, if the+-- text has changed. Or, return any error message encountered.+--+-- As a convenience for data received from web forms, which does not+-- have normalised line endings, line endings will be normalised (to \n)+-- before parsing.+--+-- The file will be written (if changed) with the current system's native+-- line endings (see writeFileWithBackupIfChanged).+--+writeJournalTextIfValidAndChanged :: MonadHandler m => FilePath -> Text -> m (Either String ())+writeJournalTextIfValidAndChanged f t = do+ -- Ensure unix line endings, since both readJournal (cf+ -- formatdirectivep, #1194) writeFileWithBackupIfChanged require them.+ -- XXX klunky. Any equivalent of "hSetNewlineMode h universalNewlineMode" for form posts ?+ let t' = T.pack $ regexReplace "\r" "" $ T.unpack t+ liftIO (readJournal def (Just f) t') >>= \case Left e -> return (Left e) Right _ -> do- _ <- liftIO (writeFileWithBackupIfChanged f txt)+ _ <- liftIO (writeFileWithBackupIfChanged f t') return (Right ())- -- | Link to a topic in the manual. helplink :: Text -> Text -> HtmlUrl r
hledger-web.1 view
@@ -1,5 +1,5 @@ -.TH "hledger-web" "1" "January 2020" "hledger-web 1.16.2" "hledger User Manuals"+.TH "hledger-web" "1" "March 2020" "hledger-web 1.17" "hledger User Manuals" @@ -15,9 +15,9 @@ \f[C]hledger web -- [OPTIONS]\f[R] .SH DESCRIPTION .PP-hledger is a cross-platform program for tracking money, time, or any-other commodity, using double-entry accounting and a simple, editable-file format.+hledger is a reliable, cross-platform set of programs for tracking+money, time, or any other commodity, using double-entry accounting and a+simple, editable file format. hledger is inspired by and largely compatible with ledger(1). .PP hledger-web is hledger\[aq]s web interface.@@ -64,6 +64,13 @@ \f[B]\f[CB]--port=PORT\f[B]\f[R] listen on this TCP port (default: 5000) .TP+\f[B]\f[CB]--socket=SOCKETFILE\f[B]\f[R]+use a unix domain socket file to listen for requests instead of a TCP+socket.+Implies \f[C]--serve\f[R].+It can only be used if the operating system can provide this type of+socket.+.TP \f[B]\f[CB]--base-url=URL\f[B]\f[R] set the base url (default: http://IPADDR:PORT). You would change this when sharing over the network, or integrating@@ -105,7 +112,8 @@ use some other field or tag for the account name .TP \f[B]\f[CB]-I --ignore-assertions\f[B]\f[R]-ignore any failing balance assertions+disable balance assertion checks (note: does not disable balance+assignments) .PP hledger reporting options: .TP@@ -168,8 +176,9 @@ apply automated posting rules to modify transactions. .TP \f[B]\f[CB]--forecast\f[B]\f[R]-apply periodic transaction rules to generate future transactions, to 6-months from now or report end date.+generate future transactions from periodic transaction rules, for the+next 6 months or till report end date.+In hledger-ui, also make ordinary future transactions visible. .PP When a reporting option appears more than once in the command line, the last one takes precedence.@@ -209,6 +218,26 @@ Similarly, use \f[C]--port\f[R] to set a TCP port other than 5000, eg if you are running multiple hledger-web instances. .PP+Both of these options are ignored when \f[C]--socket\f[R] is used.+In this case, it creates an \f[C]AF_UNIX\f[R] socket file at the+supplied path and uses that for communication.+This is an alternative way of running multiple hledger-web instances+behind a reverse proxy that handles authentication for different users.+The path can be derived in a predictable way, eg by using the username+within the path.+As an example, \f[C]nginx\f[R] as reverse proxy can use the variabel+\f[C]$remote_user\f[R] to derive a path from the username used in a HTTP+basic authentication.+The following \f[C]proxy_pass\f[R] directive allows access to all+\f[C]hledger-web\f[R] instances that created a socket in+\f[C]/tmp/hledger/\f[R]:+.IP+.nf+\f[C]+ proxy_pass http://unix:/tmp/hledger/${remote_user}.socket;+\f[R]+.fi+.PP You can use \f[C]--base-url\f[R] to change the protocol, hostname, port and path that appear in hyperlinks, useful eg for integrating hledger-web within a larger website.@@ -314,8 +343,8 @@ .PP Another way to generate test data is with the \f[C]readJsonFile\f[R]/\f[C]writeJsonFile\f[R] helpers in-Hledger.Web.Json, which read or write any of hledger\[aq]s JSON-capable-types from or to a file.+Hledger.Web.Json, which can write or read most of hledger\[aq]s data+types to or from a file. Eg here we write the first transaction of a sample journal: .IP .nf@@ -347,6 +376,27 @@ \f[C]-f\f[R]. Default: \f[C]\[ti]/.hledger.journal\f[R] (on windows, perhaps \f[C]C:/Users/USER/.hledger.journal\f[R]).+.PP+A typical value is \f[C]\[ti]/DIR/YYYY.journal\f[R], where DIR is a+version-controlled finance directory and YYYY is the current year.+Or \f[C]\[ti]/DIR/current.journal\f[R], where current.journal is a+symbolic link to YYYY.journal.+.PP+On Mac computers, you can set this and other environment variables in a+more thorough way that also affects applications started from the GUI+(say, an Emacs dock icon).+Eg on MacOS Catalina I have a \f[C]\[ti]/.MacOSX/environment.plist\f[R]+file containing+.IP+.nf+\f[C]+{+ \[dq]LEDGER_FILE\[dq] : \[dq]\[ti]/finance/current.journal\[dq]+}+\f[R]+.fi+.PP+To see the effect you may need to \f[C]killall Dock\f[R], or reboot. .SH FILES .PP Reads data from one or more files in hledger journal, timeclock,
hledger-web.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 4c599d719d293cac5bb59f0e672d9aaf251a5a548a7010c5b8155f557bd56895+-- hash: 450997bd04bd661f348425755f2045c35d11a0765dd32552b961d62d05aa458b name: hledger-web-version: 1.16.2+version: 1.17 synopsis: Web interface for the hledger accounting tool description: This is hledger's web interface. It provides a more user-friendly and collaborative UI than the@@ -27,7 +27,7 @@ maintainer: Simon Michael <simon@joyful.com> license: GPL-3 license-file: LICENSE-tested-with: GHC==8.0.2, GHC==8.2.2, GHC==8.4.3, GHC==8.6.5, GHC==8.8.1+tested-with: GHC==8.2.2, GHC==8.4.3, GHC==8.6.5, GHC==8.8.2, GHC==8.10 build-type: Simple extra-source-files: CHANGES.md@@ -139,7 +139,6 @@ Hledger.Web.Handler.RegisterR Hledger.Web.Handler.UploadR Hledger.Web.Import- Hledger.Web.Json Hledger.Web.Main Hledger.Web.Settings Hledger.Web.Settings.StaticFiles@@ -151,11 +150,11 @@ hs-source-dirs: ./. ghc-options: -Wall -fwarn-tabs -Wcompat -Wincomplete-uni-patterns -Wincomplete-record-updates -Wredundant-constraints- cpp-options: -DVERSION="1.16.2"+ cpp-options: -DVERSION="1.17" build-depends: Decimal , aeson- , base >=4.9 && <4.14+ , base >=4.9 && <4.15 , blaze-html , blaze-markup , bytestring@@ -167,26 +166,29 @@ , containers , data-default , directory+ , extra >=1.6.3 , filepath , hjsmin- , hledger >=1.16.2 && <1.17- , hledger-lib >=1.16.2 && <1.17+ , hledger >=1.17 && <1.18+ , hledger-lib >=1.17 && <1.18 , http-client , http-conduit , http-types , megaparsec >=7.0.0 && <8.1 , mtl >=2.2.1+ , network , semigroups , shakespeare >=2.0.2.2 , template-haskell , text >=1.2 , time >=1.5 , transformers+ , unix-compat , utf8-string , wai , wai-cors , wai-extra- , wai-handler-launch >=1.3+ , wai-handler-launch >=3.0.3 , warp , yaml , yesod >=1.4 && <1.7@@ -206,7 +208,7 @@ hs-source-dirs: app ghc-options: -Wall -fwarn-tabs -Wcompat -Wincomplete-uni-patterns -Wincomplete-record-updates -Wredundant-constraints- cpp-options: -DVERSION="1.16.2"+ cpp-options: -DVERSION="1.17" build-depends: base , hledger-web
hledger-web.info view
@@ -3,10 +3,20 @@ File: hledger-web.info, Node: Top, Next: OPTIONS, Up: (dir) -hledger-web(1) hledger-web 1.16.2-*********************************+hledger-web(1) hledger-web 1.17+******************************* -hledger-web is hledger's web interface. It starts a simple web+hledger-web - web interface for the hledger accounting tool++ 'hledger-web [OPTIONS]'+'hledger web -- [OPTIONS]'++ hledger is a reliable, cross-platform set of programs for tracking+money, time, or any other commodity, using double-entry accounting and a+simple, editable file format. hledger is inspired by and largely+compatible with ledger(1).++ hledger-web is hledger's web interface. It starts a simple web application for browsing and adding transactions, and optionally opens it in a web browser window if possible. It provides a more user-friendly UI than the hledger CLI or hledger-ui interface, showing@@ -33,6 +43,9 @@ * EDITING UPLOADING DOWNLOADING:: * RELOADING:: * JSON API::+* ENVIRONMENT::+* FILES::+* BUGS:: File: hledger-web.info, Node: OPTIONS, Next: PERMISSIONS, Prev: Top, Up: Top@@ -60,6 +73,11 @@ '--port=PORT' listen on this TCP port (default: 5000)+'--socket=SOCKETFILE'++ use a unix domain socket file to listen for requests instead of a+ TCP socket. Implies '--serve'. It can only be used if the+ operating system can provide this type of socket. '--base-url=URL' set the base url (default: http://IPADDR:PORT). You would change@@ -103,7 +121,8 @@ use some other field or tag for the account name '-I --ignore-assertions' - ignore any failing balance assertions+ disable balance assertion checks (note: does not disable balance+ assignments) hledger reporting options: @@ -168,8 +187,9 @@ apply automated posting rules to modify transactions. '--forecast' - apply periodic transaction rules to generate future transactions,- to 6 months from now or report end date.+ generate future transactions from periodic transaction rules, for+ the next 6 months or till report end date. In hledger-ui, also+ make ordinary future transactions visible. When a reporting option appears more than once in the command line, the last one takes precedence.@@ -208,6 +228,19 @@ Similarly, use '--port' to set a TCP port other than 5000, eg if you are running multiple hledger-web instances. + Both of these options are ignored when '--socket' is used. In this+case, it creates an 'AF_UNIX' socket file at the supplied path and uses+that for communication. This is an alternative way of running multiple+hledger-web instances behind a reverse proxy that handles authentication+for different users. The path can be derived in a predictable way, eg+by using the username within the path. As an example, 'nginx' as+reverse proxy can use the variabel '$remote_user' to derive a path from+the username used in a HTTP basic authentication. The following+'proxy_pass' directive allows access to all 'hledger-web' instances that+created a socket in '/tmp/hledger/':++ proxy_pass http://unix:/tmp/hledger/${remote_user}.socket;+ You can use '--base-url' to change the protocol, hostname, port and path that appear in hyperlinks, useful eg for integrating hledger-web within a larger website. The default is 'http://HOST:PORT/' using the@@ -292,7 +325,7 @@ sure that both machine clocks are roughly in step.) -File: hledger-web.info, Node: JSON API, Prev: RELOADING, Up: Top+File: hledger-web.info, Node: JSON API, Next: ENVIRONMENT, Prev: RELOADING, Up: Top 5 JSON API **********@@ -317,8 +350,8 @@ what you get from '/transactions' or '/accounttransactions'. Another way to generate test data is with the-'readJsonFile'/'writeJsonFile' helpers in Hledger.Web.Json, which read-or write any of hledger's JSON-capable types from or to a file. Eg here+'readJsonFile'/'writeJsonFile' helpers in Hledger.Web.Json, which can+write or read most of hledger's data types to or from a file. Eg here we write the first transaction of a sample journal: $ make ghci-web@@ -338,18 +371,78 @@ to serve the API. +File: hledger-web.info, Node: ENVIRONMENT, Next: FILES, Prev: JSON API, Up: Top++6 ENVIRONMENT+*************++*LEDGER_FILE* The journal file path when not specified with '-f'.+Default: '~/.hledger.journal' (on windows, perhaps+'C:/Users/USER/.hledger.journal').++ A typical value is '~/DIR/YYYY.journal', where DIR is a+version-controlled finance directory and YYYY is the current year. Or+'~/DIR/current.journal', where current.journal is a symbolic link to+YYYY.journal.++ On Mac computers, you can set this and other environment variables in+a more thorough way that also affects applications started from the GUI+(say, an Emacs dock icon). Eg on MacOS Catalina I have a+'~/.MacOSX/environment.plist' file containing++{+ "LEDGER_FILE" : "~/finance/current.journal"+}++ To see the effect you may need to 'killall Dock', or reboot.+++File: hledger-web.info, Node: FILES, Next: BUGS, Prev: ENVIRONMENT, Up: Top++7 FILES+*******++Reads data from one or more files in hledger journal, timeclock,+timedot, or CSV format specified with '-f', or '$LEDGER_FILE', or+'$HOME/.hledger.journal' (on windows, perhaps+'C:/Users/USER/.hledger.journal').+++File: hledger-web.info, Node: BUGS, Prev: FILES, Up: Top++8 BUGS+******++The need to precede options with '--' when invoked from hledger is+awkward.++ '-f-' doesn't work (hledger-web can't read from stdin).++ Query arguments and some hledger options are ignored.++ Does not work in text-mode browsers.++ Does not work well on small screens.++ Tag Table: Node: Top72-Node: OPTIONS1359-Ref: #options1464-Node: PERMISSIONS6743-Ref: #permissions6882-Node: EDITING UPLOADING DOWNLOADING8094-Ref: #editing-uploading-downloading8275-Node: RELOADING9109-Ref: #reloading9243-Node: JSON API9676-Ref: #json-api9770+Node: OPTIONS1746+Ref: #options1851+Node: PERMISSIONS8195+Ref: #permissions8334+Node: EDITING UPLOADING DOWNLOADING9546+Ref: #editing-uploading-downloading9727+Node: RELOADING10561+Ref: #reloading10695+Node: JSON API11128+Ref: #json-api11242+Node: ENVIRONMENT12680+Ref: #environment12796+Node: FILES13529+Ref: #files13629+Node: BUGS13842+Ref: #bugs13920 End Tag Table
hledger-web.txt view
@@ -11,10 +11,10 @@ hledger web -- [OPTIONS] DESCRIPTION- hledger is a cross-platform program for tracking money, time, or any- other commodity, using double-entry accounting and a simple, editable- file format. hledger is inspired by and largely compatible with- ledger(1).+ hledger is a reliable, cross-platform set of programs for tracking+ money, time, or any other commodity, using double-entry accounting and+ a simple, editable file format. hledger is inspired by and largely+ compatible with ledger(1). hledger-web is hledger's web interface. It starts a simple web appli- cation for browsing and adding transactions, and optionally opens it in@@ -56,6 +56,11 @@ --port=PORT listen on this TCP port (default: 5000) + --socket=SOCKETFILE+ use a unix domain socket file to listen for requests instead of+ a TCP socket. Implies --serve. It can only be used if the op-+ erating system can provide this type of socket.+ --base-url=URL set the base url (default: http://IPADDR:PORT). You would change this when sharing over the network, or integrating within@@ -97,7 +102,8 @@ use some other field or tag for the account name -I --ignore-assertions- ignore any failing balance assertions+ disable balance assertion checks (note: does not disable balance+ assignments) hledger reporting options: @@ -123,7 +129,7 @@ multiperiod/multicolumn report by year -p --period=PERIODEXP- set start date, end date, and/or reporting interval all at once+ set start date, end date, and/or reporting interval all at once using period expressions syntax --date2@@ -146,22 +152,23 @@ hide/aggregate accounts or postings more than NUM levels deep -E --empty- show items with zero amount, normally hidden (and vice-versa in+ show items with zero amount, normally hidden (and vice-versa in hledger-ui/hledger-web) -B --cost- convert amounts to their cost at transaction time (using the+ convert amounts to their cost at transaction time (using the transaction price, if any) -V --value- convert amounts to their market value on the report end date+ convert amounts to their market value on the report end date (using the most recent applicable market price, if any) --auto apply automated posting rules to modify transactions. --forecast- apply periodic transaction rules to generate future transac-- tions, to 6 months from now or report end date.+ generate future transactions from periodic transaction rules,+ for the next 6 months or till report end date. In hledger-ui,+ also make ordinary future transactions visible. When a reporting option appears more than once in the command line, the last one takes precedence.@@ -199,22 +206,35 @@ Similarly, use --port to set a TCP port other than 5000, eg if you are running multiple hledger-web instances. - You can use --base-url to change the protocol, hostname, port and path+ Both of these options are ignored when --socket is used. In this case,+ it creates an AF_UNIX socket file at the supplied path and uses that+ for communication. This is an alternative way of running multiple+ hledger-web instances behind a reverse proxy that handles authentica-+ tion for different users. The path can be derived in a predictable+ way, eg by using the username within the path. As an example, nginx as+ reverse proxy can use the variabel $remote_user to derive a path from+ the username used in a HTTP basic authentication. The following+ proxy_pass directive allows access to all hledger-web instances that+ created a socket in /tmp/hledger/:++ proxy_pass http://unix:/tmp/hledger/${remote_user}.socket;++ You can use --base-url to change the protocol, hostname, port and path that appear in hyperlinks, useful eg for integrating hledger-web within- a larger website. The default is http://HOST:PORT/ using the server's+ a larger website. The default is http://HOST:PORT/ using the server's configured host address and TCP port (or http://HOST if PORT is 80). - With --file-url you can set a different base url for static files, eg+ With --file-url you can set a different base url for static files, eg for better caching or cookie-less serving on high performance websites. PERMISSIONS- By default, hledger-web allows anyone who can reach it to view the+ By default, hledger-web allows anyone who can reach it to view the journal and to add new transactions, but not to change existing data. You can restrict who can reach it by - o setting the IP address it listens on (see --host above). By default- it listens on 127.0.0.1, accessible to all users on the local ma-+ o setting the IP address it listens on (see --host above). By default+ it listens on 127.0.0.1, accessible to all users on the local ma- chine. o putting it behind an authenticating proxy, using eg apache or nginx@@ -224,44 +244,44 @@ You can restrict what the users who reach it can do, by o using the --capabilities=CAP[,CAP..] flag when you start it, enabling- one or more of the following capabilities. The default value is+ one or more of the following capabilities. The default value is view,add: o view - allows viewing the journal file and all included files o add - allows adding new transactions to the main journal file - o manage - allows editing, uploading or downloading the main or in-+ o manage - allows editing, uploading or downloading the main or in- cluded files - o using the --capabilities-header=HTTPHEADER flag to specify a HTTP- header from which it will read capabilities to enable. hledger-web- on Sandstorm uses the X-Sandstorm-Permissions header to integrate+ o using the --capabilities-header=HTTPHEADER flag to specify a HTTP+ header from which it will read capabilities to enable. hledger-web+ on Sandstorm uses the X-Sandstorm-Permissions header to integrate with Sandstorm's permissions. This is disabled by default. EDITING, UPLOADING, DOWNLOADING- If you enable the manage capability mentioned above, you'll see a new- "spanner" button to the right of the search form. Clicking this will- let you edit, upload, or download the journal file or any files it in-+ If you enable the manage capability mentioned above, you'll see a new+ "spanner" button to the right of the search form. Clicking this will+ let you edit, upload, or download the journal file or any files it in- cludes. - Note, unlike any other hledger command, in this mode you (or any visi-+ Note, unlike any other hledger command, in this mode you (or any visi- tor) can alter or wipe the data files. - Normally whenever a file is changed in this way, hledger-web saves a- numbered backup (assuming file permissions allow it, the disk is not- full, etc.) hledger-web is not aware of version control systems, cur-- rently; if you use one, you'll have to arrange to commit the changes+ Normally whenever a file is changed in this way, hledger-web saves a+ numbered backup (assuming file permissions allow it, the disk is not+ full, etc.) hledger-web is not aware of version control systems, cur-+ rently; if you use one, you'll have to arrange to commit the changes yourself (eg with a cron job or a file watcher like entr). - Changes which would leave the journal file(s) unparseable or non-valid- (eg with failing balance assertions) are prevented. (Probably. This+ Changes which would leave the journal file(s) unparseable or non-valid+ (eg with failing balance assertions) are prevented. (Probably. This needs re-testing.) RELOADING hledger-web detects changes made to the files by other means (eg if you- edit it directly, outside of hledger-web), and it will show the new- data when you reload the page or navigate to a new page. If a change+ edit it directly, outside of hledger-web), and it will show the new+ data when you reload the page or navigate to a new page. If a change makes a file unparseable, hledger-web will display an error message un- til the file has been fixed. @@ -269,8 +289,8 @@ that both machine clocks are roughly in step.) JSON API- In addition to the web UI, hledger-web provides some API routes that- serve JSON in response to GET requests. (And when started with+ In addition to the web UI, hledger-web provides some API routes that+ serve JSON in response to GET requests. (And when started with --serve-api, it provides only these routes.): /accountnames@@ -280,17 +300,17 @@ /accounts /accounttransactions/#AccountName - Also, you can append a new transaction to the journal by sending a PUT- request to /add (hledger-web only). As with the web UI's add form,- hledger-web must be started with the add capability for this (enabled+ Also, you can append a new transaction to the journal by sending a PUT+ request to /add (hledger-web only). As with the web UI's add form,+ hledger-web must be started with the add capability for this (enabled by default). - The payload should be a valid hledger transaction as JSON, similar to+ The payload should be a valid hledger transaction as JSON, similar to what you get from /transactions or /accounttransactions. - Another way to generate test data is with the readJsonFile/writeJson-- File helpers in Hledger.Web.Json, which read or write any of hledger's- JSON-capable types from or to a file. Eg here we write the first+ Another way to generate test data is with the readJsonFile/writeJson-+ File helpers in Hledger.Web.Json, which can write or read most of+ hledger's data types to or from a file. Eg here we write the first transaction of a sample journal: $ make ghci-web@@ -305,15 +325,30 @@ $ curl -s http://127.0.0.1:5000/add -X PUT -H 'Content-Type: application/json' --data-binary @txn.pretty.json; echo - By default, both the server-side HTML UI and the JSON API are served.- Running with --serve-api disables the former, useful if you only want+ By default, both the server-side HTML UI and the JSON API are served.+ Running with --serve-api disables the former, useful if you only want to serve the API. ENVIRONMENT LEDGER_FILE The journal file path when not specified with -f. Default:- ~/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.jour-+ ~/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.jour- nal). + A typical value is ~/DIR/YYYY.journal, where DIR is a version-con-+ trolled finance directory and YYYY is the current year. Or ~/DIR/cur-+ rent.journal, where current.journal is a symbolic link to YYYY.journal.++ On Mac computers, you can set this and other environment variables in a+ more thorough way that also affects applications started from the GUI+ (say, an Emacs dock icon). Eg on MacOS Catalina I have a ~/.MacOSX/en-+ vironment.plist file containing++ {+ "LEDGER_FILE" : "~/finance/current.journal"+ }++ To see the effect you may need to killall Dock, or reboot.+ FILES Reads data from one or more files in hledger journal, timeclock, time- dot, or CSV format specified with -f, or $LEDGER_FILE, or@@ -357,4 +392,4 @@ -hledger-web 1.16.2 January 2020 hledger-web(1)+hledger-web 1.17 March 2020 hledger-web(1)
static/hledger.css view
@@ -18,6 +18,12 @@ } /*------------------------------------------------------------------------------------------*/+/* 2. fonts */+body {+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif;+}++/*------------------------------------------------------------------------------------------*/ /* 4. typeahead styles */ .tt-hint {
static/js/jquery.flot.js view
@@ -517,7 +517,7 @@ colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], legend: { show: true,- noColumns: 1, // number of colums in legend table+ noColumns: 1, // number of columns in legend table labelFormatter: null, // fn: string -> string labelBoxBorderColor: "#ccc", // border color for the little label boxes container: null, // container (as jQuery object) to put legend in, null means default on top of graph