packages feed

hledger-web 1.32.1 → 1.32.2

raw patch · 16 files changed

+871/−775 lines, 16 filesdep ~hledgerdep ~hledger-libPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: hledger, hledger-lib

API changes (from Hackage documentation)

- Hledger.Web.Application: instance Yesod.Core.Class.Dispatch.YesodDispatch Hledger.Web.Foundation.App
- Hledger.Web.Application: makeFoundation :: AppConfig DefaultEnv Extra -> WebOpts -> IO App
- Hledger.Web.Application: makeFoundationWith :: Journal -> AppConfig DefaultEnv Extra -> WebOpts -> IO App
+ Hledger.Web.Application: instance Yesod.Core.Class.Dispatch.YesodDispatch Hledger.Web.App.App
+ Hledger.Web.Application: makeApp :: AppConfig DefaultEnv Extra -> WebOpts -> IO App
+ Hledger.Web.Application: makeAppWith :: Journal -> AppConfig DefaultEnv Extra -> WebOpts -> IO App

Files

CHANGES.md view
@@ -7,12 +7,12 @@                      Breaking changes +Fixes+ Features  Improvements -Fixes- Docs  API@@ -21,11 +21,31 @@ User-visible changes in hledger-web. See also the hledger changelog. +# 1.32.2 2023-12-31++Fixes++- The --base-url option works again. [#2127], [#2100]++- Startup messages are more accurate and informative, eg with --socket. [#2127]++- The non-working --file-url option has been dropped for now. [#2139]++Improvements++- Allow megaparsec 9.6++- hledger-web's tests now respect and can test command line options.++- hledger-web's tests now run the app at 127.0.0.1 and port 5000,+  rather than "any of our IPv4 or IPv6 addresses" and 3000,+ # 1.32.1 2023-12-07  - Use hledger-1.32.1  # 1.32 2023-12-01+ Features  - The hledger-web app on the Sandstorm cloud platform has been updated to
+ Hledger/Web/App.hs view
@@ -0,0 +1,315 @@+{-|+Most of the definition of the web app is here.+In the usual Yesod style, this defines the web app's core types and configuration,+and then Application.hs completes the job.+-}++{-# OPTIONS_GHC -fno-warn-orphans  #-}+{-# LANGUAGE CPP                   #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE LambdaCase            #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns        #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE QuasiQuotes           #-}+{-# LANGUAGE TemplateHaskell       #-}+{-# LANGUAGE TypeFamilies          #-}+{-# LANGUAGE ViewPatterns          #-}++module Hledger.Web.App where++import Control.Applicative ((<|>))+import Control.Monad (join, when, unless)+-- import Control.Monad.Except (runExceptT)  -- now re-exported by Hledger+import qualified Data.ByteString.Char8 as BC+import Data.Traversable (for)+import Data.IORef (IORef, readIORef, writeIORef)+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Time.Calendar (Day)+import Network.HTTP.Conduit (Manager)+import Network.HTTP.Types (status403)+import Network.Wai (requestHeaders)+import System.Directory (XdgDirectory (..), createDirectoryIfMissing,+                         getXdgDirectory)+import System.FilePath (takeFileName, (</>))+import Text.Blaze (Markup)+import Text.Hamlet (hamletFile)+import Yesod+import Yesod.Static+import Yesod.Default.Config++#ifndef DEVELOPMENT+import Hledger.Web.Settings (staticDir)+import Text.Jasmine (minifym)+import Yesod.Default.Util (addStaticContentExternal)+#endif++import Hledger+import Hledger.Cli (CliOpts(..), journalReloadIfChanged)+import Hledger.Web.Settings (Extra(..), widgetFile)+import Hledger.Web.Settings.StaticFiles+import Hledger.Web.WebOptions+import Hledger.Web.Widget.Common (balanceReportAsHtml)++-- | The site argument for your application. This can be a good place to+-- keep settings and values requiring initialization before your application+-- starts running, such as database connections. Every handler will have+-- access to the data present here.+data App = App+    { settings :: AppConfig DefaultEnv Extra+    , getStatic :: Static -- ^ Settings for static file serving.+    , httpManager :: Manager+      --+    , appOpts    :: WebOpts+    , appJournal :: IORef Journal+        -- ^ the current journal, filtered by the initial command line query+        --   but ignoring any depth limit.+    }+++-- This is where we define all of the routes in our application. For a full+-- explanation of the syntax, please see:+-- http://www.yesodweb.com/book/handler+--+-- This function does three things:+--+-- * Creates the route datatype AppRoute. Every valid URL in your+--   application can be represented as a value of this type.+-- * Creates the associated type:+--       type instance Route App = AppRoute+-- * Creates the value resourcesApp which contains information on the+--   resources declared below. This is used in Handler.hs by the call to+--   mkYesodDispatch+--+-- What this function does *not* do is create a YesodSite instance for App.+-- AppCreating that instance requires all of the handler functions+-- for our application to be in scope. However, the handler functions+-- usually require access to the AppRoute datatype. Therefore, we+-- split these actions into two functions and place the other in a+-- separate file (Application.hs).+-- mkYesodData defines things like:+--+-- * type Handler = HandlerFor App   -- HandlerT App IO, https://www.yesodweb.com/book/routing-and-handlers#routing-and-handlers_handler_monad+-- * type Widget = WidgetFor App ()  -- WidgetT App IO (), https://www.yesodweb.com/book/widgets+--+mkYesodData "App" $(parseRoutesFile "config/routes")++type AppRoute = Route App+type Form a = Html -> MForm Handler (FormResult a, Widget)++-- Please see the documentation for the Yesod typeclass. There are a number+-- of settings which can be configured by overriding methods here.+instance Yesod App where++  -- Configure the app root, AKA base url, which is prepended to relative hyperlinks.+  -- Broadly, we'd like this:+  -- 1. when --base-url has been specified, use that;+  -- 2. otherwise, guess it from request headers, which helps us respond from the+  --    same hostname/IP address when hledger-web is accessible at multiple IPs;+  -- 3. otherwise, leave it empty (relative links stay relative).+  -- But it's hard to see how to achieve this.+  -- For now we do (I believe) 1 or 3, with 2 unfortunately not supported.+  -- Issues include: #2099, #2100, #2127+  approot =+    -- ApprootRelative+    -- ApprootMaster $ appRoot . settings+    -- guessApprootOr (ApprootMaster $ appRoot . settings)+    ApprootMaster $ \(App{settings=AppConfig{appRoot=r}, appOpts=WebOpts{base_url_=bu}}) -> if null bu then r else T.pack bu++  makeSessionBackend _ = do+    hledgerdata <- getXdgDirectory XdgCache "hledger"+    createDirectoryIfMissing True hledgerdata+    let sessionexpirysecs = 120+    Just <$> defaultClientSessionBackend sessionexpirysecs (hledgerdata </> "hledger-web_client_session_key.aes")++  -- defaultLayout :: WidgetFor site () -> HandlerFor site Html+  defaultLayout widget = do++    -- Don't run if server-side UI is disabled.+    -- This single check probably covers all the HTML-returning handlers,+    -- but for now they do the check as well.+    checkServerSideUiEnabled++    master <- getYesod+    here <- fromMaybe RootR <$> getCurrentRoute+    VD{opts, j, qparam, q, qopts, perms} <- getViewData+    msg <- getMessage+    showSidebar <- shouldShowSidebar++    let rspec = reportspec_ (cliopts_ opts)+        ropts = _rsReportOpts rspec+        ropts' = (_rsReportOpts rspec)+          {accountlistmode_ = ALTree  -- force tree mode for sidebar+          ,empty_           = True    -- show zero items by default+          }+        rspec' = rspec{_rsQuery=q,_rsReportOpts=ropts'}++    hideEmptyAccts <- if empty_ ropts+                         then return True+                         else (== Just "1") . lookup "hideemptyaccts" . reqCookies <$> getRequest++    let accounts =+          balanceReportAsHtml (JournalR, RegisterR) here hideEmptyAccts j qparam qopts $+          styleAmounts (journalCommodityStylesWith HardRounding j) $+          balanceReport rspec' j++        topShowmd = if showSidebar then "col-md-4" else "col-any-0" :: Text+        topShowsm = if showSidebar then "col-sm-4" else "" :: Text+        sideShowmd = if showSidebar then "col-md-4" else "col-any-0" :: Text+        sideShowsm = if showSidebar then "col-sm-4" else "" :: Text+        mainShowmd = if showSidebar then "col-md-8" else "col-md-12" :: Text+        mainShowsm = if showSidebar then "col-sm-8" else "col-sm-12" :: Text++    -- We break up the default layout into two components:+    -- default-layout is the contents of the body tag, and+    -- default-layout-wrapper is the entire page. Since the final+    -- value passed to hamletToRepHtml cannot be a widget, this allows+    -- you to use normal widget features in default-layout.+    pc <- widgetToPageContent $ do+      addStylesheet $ StaticR css_bootstrap_min_css+      addStylesheet $ StaticR css_bootstrap_datepicker_standalone_min_css+      -- load these things early, in HEAD:+      toWidgetHead [hamlet|+        <script type="text/javascript" src="@{StaticR js_jquery_min_js}">+        <script type="text/javascript" src="@{StaticR js_typeahead_bundle_min_js}">+      |]+      addScript $ StaticR js_bootstrap_min_js+      addScript $ StaticR js_bootstrap_datepicker_min_js+      addScript $ StaticR js_jquery_url_js+      addScript $ StaticR js_jquery_cookie_js+      addScript $ StaticR js_jquery_hotkeys_js+      addScript $ StaticR js_jquery_flot_min_js+      addScript $ StaticR js_jquery_flot_selection_min_js+      addScript $ StaticR js_jquery_flot_time_min_js+      addScript $ StaticR js_jquery_flot_tooltip_min_js+      toWidget [hamlet| \<!--[if lte IE 8]> <script type="text/javascript" src="@{StaticR js_excanvas_min_js}"></script> <![endif]--> |]+      addStylesheet $ StaticR hledger_css+      addScript $ StaticR hledger_js+      $(widgetFile "default-layout")++    withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")++-- XXX why disabled during development ? Affects ghci, ghcid, tests, #2139 ?+#ifndef DEVELOPMENT+  -- This function creates static content files in the static folder+  -- and names them based on a hash of their content. This allows+  -- expiration dates to be set far in the future without worry of+  -- users receiving stale content.+  addStaticContent = addStaticContentExternal minifym base64md5 staticDir (StaticR . flip StaticRoute [])+#endif++-- This instance is required to use forms. You can modify renderMessage to+-- achieve customized and internationalized form validation messages.+instance RenderMessage App FormMessage where+    renderMessage _ _ = defaultFormMessage+++----------------------------------------------------------------------+-- template and handler utilities++-- view data, used by the add form and handlers+-- XXX Parameter p - show/hide postings++-- | A bundle of data useful for hledger-web request handlers and templates.+data ViewData = VD+  { opts  :: WebOpts    -- ^ the command-line options at startup+  , today :: Day        -- ^ today's date (for queries containing relative dates)+  , j     :: Journal    -- ^ the up-to-date parsed unfiltered journal    -- XXX rename+  , qparam :: Text       -- ^ the current "q" request parameter+  , q     :: Query      -- ^ a query parsed from the q parameter+  , qopts :: [QueryOpt] -- ^ query options parsed from the q parameter+  , perms :: [Permission]  -- ^ permissions enabled for this request (by --allow and/or X-Sandstorm-Permissions)+  } deriving (Show)++instance Show Text.Blaze.Markup where show _ = "<blaze markup>"++-- | Gather data used by handlers and templates in the current request.+getViewData :: Handler ViewData+getViewData = do+  App{+    appOpts=opts@WebOpts{ cliopts_=copts@CliOpts{ reportspec_=rspec@ReportSpec{_rsReportOpts, _rsQuery} } },+    appJournal+  } <- getYesod+  let today = _rsDay rspec++  -- try to read the latest journal content, keeping the old content+  -- if there's an error+  (j, mjerr) <- getCurrentJournal+                appJournal+                copts{reportspec_=rspec{_rsReportOpts=_rsReportOpts{no_elide_=True}}}+                today++  -- Get the query specified by the q request parameter, or no query if this fails.+  qparam <- fromMaybe "" <$> lookupGetParam "q"+  (q1, qopts, mqerr) <- do+    case parseQuery today qparam of+      Right (q0, qopts) -> return (q0, qopts, Nothing)+      Left err         -> return (Any, [], Just err)+  -- To this, add any depth limit from the initial startup query, preserving that.+  let+    initialdepthq = filterQuery queryIsDepth _rsQuery+    q = simplifyQuery $ And [q1, initialdepthq]++  -- if either of the above gave an error, display it+  maybe (pure ()) (setMessage . toHtml) $ mjerr <|> mqerr++  -- find out which permissions are enabled+  perms <- case allow_ opts of+    -- if started with --allow=sandstorm, take permissions from X-Sandstorm-Permissions header+    SandstormAccess -> do+      let h = "X-Sandstorm-Permissions"+      hs <- fmap (BC.split ',' . snd) . filter ((== h) . fst) . requestHeaders <$> waiRequest+      fmap join . for (join hs) $ \x -> case parsePermission x of+        Left  e -> [] <$ addMessage "" ("Unknown permission: " <> toHtml e)+        Right p -> pure [p]+    -- otherwise take them from the access level specified by --allow's access level+    cliaccess -> pure $ accessLevelToPermissions cliaccess++  return VD{opts, today, j, qparam, q, qopts, perms}++checkServerSideUiEnabled :: Handler ()+checkServerSideUiEnabled = do+  VD{opts=WebOpts{serve_api_}} <- getViewData+  when serve_api_ $+    -- this one gives 500 internal server error when called from defaultLayout:+    --  permissionDenied "server-side UI is disabled due to --serve-api"+    sendResponseStatus status403 ("server-side UI is disabled due to --serve-api" :: Text)++-- | Find out if the sidebar should be visible. Show it, unless there is a+-- showsidebar cookie set to "0", or a ?sidebar=0 query parameter.+shouldShowSidebar :: Handler Bool+shouldShowSidebar = do+  msidebarparam <- lookupGetParam "sidebar"+  msidebarcookie <- lookup "showsidebar" . reqCookies <$> getRequest+  return $+    let disablevalues = ["","0"]+    in maybe True (`notElem` disablevalues) $ msidebarparam <|> msidebarcookie++-- | Update our copy of the journal if the file changed. If there is an+-- error while reloading, keep the old one and return the error, and set a+-- ui message.+getCurrentJournal :: IORef Journal -> CliOpts -> Day -> Handler (Journal, Maybe String)+getCurrentJournal jref opts d = do+  -- re-apply any initial filter specified at startup+  let depthlessinitialq = filterQuery (not . queryIsDepth) $ _rsQuery $ reportspec_ opts+  -- XXX put this inside atomicModifyIORef' for thread safety+  j <- liftIO (readIORef jref)+  ej <- liftIO . runExceptT $ journalReloadIfChanged opts d j+  case ej of+    Left e -> do+      setMessage "error while reading journal"+      return (j, Just e)+    Right (j', True) -> do+      liftIO . writeIORef jref $ filterJournalTransactions depthlessinitialq j'+      return (j',Nothing)+    Right (_, False) -> return (j, Nothing)++-- | In a request handler, check for the given permission+-- and fail with a message if it's not present.+require :: Permission -> Handler ()+require p = do+  VD{perms} <- getViewData+  unless (p `elem` perms) $ permissionDenied $+    "Missing the '" <> T.pack (showPermission p) <> "' permission"
Hledger/Web/Application.hs view
@@ -1,3 +1,8 @@+{-|+Complete the definition of the web app begun in App.hs.+This is always done in two files for (TH?) reasons.+-}+ {-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-}@@ -5,8 +10,8 @@  module Hledger.Web.Application   ( makeApplication-  , makeFoundation-  , makeFoundationWith+  , makeApp+  , makeAppWith   ) where  import Data.IORef (newIORef, writeIORef)@@ -26,9 +31,9 @@ import Hledger.Web.Import import Hledger.Web.WebOptions (WebOpts(serve_,serve_api_), corsPolicy) --- This line actually creates our YesodDispatch instance. It is the second half--- of the call to mkYesodData which occurs in Foundation.hs. Please see the--- comments there for more details.+-- mkYesodDispatch creates our YesodDispatch instance. +-- It complements the mkYesodData call in App.hs,+-- but must be in a separate file for (TH?) reasons. mkYesodDispatch "App" resourcesApp  -- This function allocates resources (such as a database connection pool),@@ -37,25 +42,29 @@ -- migrations handled by Yesod. makeApplication :: WebOpts -> Journal -> AppConfig DefaultEnv Extra -> IO Application makeApplication opts' j' conf' = do-    foundation <- makeFoundation conf' opts'-    writeIORef (appJournal foundation) j'-    (logWare . (corsPolicy opts')) <$> toWaiApp foundation+    app <- makeApp conf' opts'+    writeIORef (appJournal app) j'+    (logWare . (corsPolicy opts')) <$> toWaiApp app   where     logWare | development  = logStdoutDev             | serve_ opts' || serve_api_ opts' = logStdout             | otherwise    = id -makeFoundation :: AppConfig DefaultEnv Extra -> WebOpts -> IO App-makeFoundation conf opts' = do-    manager <- newManager defaultManagerSettings-    s <- staticSite-    jref <- newIORef nulljournal-    return $ App conf s manager opts' jref+makeApp :: AppConfig DefaultEnv Extra -> WebOpts -> IO App+makeApp = makeAppWith nulljournal --- Make a Foundation with the given Journal as its state.-makeFoundationWith :: Journal -> AppConfig DefaultEnv Extra -> WebOpts -> IO App-makeFoundationWith j' conf opts' = do-    manager <- newManager defaultManagerSettings-    s <- staticSite-    jref <- newIORef j'-    return $ App conf s manager opts' jref+-- Make an "App" (defined in App.hs), +-- with the given Journal as its state+-- and the given "AppConfig" and "WebOpts" as its configuration.+makeAppWith :: Journal -> AppConfig DefaultEnv Extra -> WebOpts -> IO App+makeAppWith j' aconf wopts = do+  s    <- staticSite+  m    <- newManager defaultManagerSettings+  jref <- newIORef j'+  return App{+      settings    = aconf+    , getStatic   = s+    , httpManager = m+    , appOpts     = wopts+    , appJournal  = jref+    }
− Hledger/Web/Foundation.hs
@@ -1,294 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans  #-}-{-# LANGUAGE CPP                   #-}-{-# LANGUAGE FlexibleInstances     #-}-{-# LANGUAGE LambdaCase            #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE NamedFieldPuns        #-}-{-# LANGUAGE OverloadedStrings     #-}-{-# LANGUAGE QuasiQuotes           #-}-{-# LANGUAGE TemplateHaskell       #-}-{-# LANGUAGE TypeFamilies          #-}-{-# LANGUAGE ViewPatterns          #-}---- | Define the web application's foundation, in the usual Yesod style.---   See a default Yesod app's comments for more details of each part.--module Hledger.Web.Foundation where--import Control.Applicative ((<|>))-import Control.Monad (join, when, unless)--- import Control.Monad.Except (runExceptT)  -- now re-exported by Hledger-import qualified Data.ByteString.Char8 as BC-import Data.Traversable (for)-import Data.IORef (IORef, readIORef, writeIORef)-import Data.Maybe (fromMaybe)-import Data.Text (Text)-import qualified Data.Text as T-import Data.Time.Calendar (Day)-import Network.HTTP.Conduit (Manager)-import Network.HTTP.Types (status403)-import Network.Wai (requestHeaders)-import System.Directory (XdgDirectory (..), createDirectoryIfMissing,-                         getXdgDirectory)-import System.FilePath (takeFileName, (</>))-import Text.Blaze (Markup)-import Text.Hamlet (hamletFile)-import Yesod-import Yesod.Static-import Yesod.Default.Config--#ifndef DEVELOPMENT-import Hledger.Web.Settings (staticDir)-import Text.Jasmine (minifym)-import Yesod.Default.Util (addStaticContentExternal)-#endif--import Hledger-import Hledger.Cli (CliOpts(..), journalReloadIfChanged)-import Hledger.Web.Settings (Extra(..), widgetFile)-import Hledger.Web.Settings.StaticFiles-import Hledger.Web.WebOptions-import Hledger.Web.Widget.Common (balanceReportAsHtml)---- | The site argument for your application. This can be a good place to--- keep settings and values requiring initialization before your application--- starts running, such as database connections. Every handler will have--- access to the data present here.-data App = App-    { settings :: AppConfig DefaultEnv Extra-    , getStatic :: Static -- ^ Settings for static file serving.-    , httpManager :: Manager-      ---    , appOpts    :: WebOpts-    , appJournal :: IORef Journal-        -- ^ the current journal, filtered by the initial command line query-        --   but ignoring any depth limit.-    }----- This is where we define all of the routes in our application. For a full--- explanation of the syntax, please see:--- http://www.yesodweb.com/book/handler------ This function does three things:------ * Creates the route datatype AppRoute. Every valid URL in your---   application can be represented as a value of this type.--- * Creates the associated type:---       type instance Route App = AppRoute--- * Creates the value resourcesApp which contains information on the---   resources declared below. This is used in Handler.hs by the call to---   mkYesodDispatch------ What this function does *not* do is create a YesodSite instance for--- App. Creating that instance requires all of the handler functions--- for our application to be in scope. However, the handler functions--- usually require access to the AppRoute datatype. Therefore, we--- split these actions into two functions and place them in separate files.-mkYesodData "App" $(parseRoutesFile "config/routes")--- ^ defines things like:--- type Handler = HandlerFor App   -- HandlerT App IO, https://www.yesodweb.com/book/routing-and-handlers#routing-and-handlers_handler_monad--- type Widget = WidgetFor App ()  -- WidgetT App IO (), https://www.yesodweb.com/book/widgets--type AppRoute = Route App-type Form a = Html -> MForm Handler (FormResult a, Widget)---- Please see the documentation for the Yesod typeclass. There are a number--- of settings which can be configured by overriding methods here.-instance Yesod App where-  approot = guessApprootOr (ApprootMaster $ appRoot . settings)--  makeSessionBackend _ = do-    hledgerdata <- getXdgDirectory XdgCache "hledger"-    createDirectoryIfMissing True hledgerdata-    let sessionexpirysecs = 120-    Just <$> defaultClientSessionBackend sessionexpirysecs (hledgerdata </> "hledger-web_client_session_key.aes")--  -- defaultLayout :: WidgetFor site () -> HandlerFor site Html-  defaultLayout widget = do--    -- Don't run if server-side UI is disabled.-    -- This single check probably covers all the HTML-returning handlers,-    -- but for now they do the check as well.-    checkServerSideUiEnabled--    master <- getYesod-    here <- fromMaybe RootR <$> getCurrentRoute-    VD{opts, j, qparam, q, qopts, perms} <- getViewData-    msg <- getMessage-    showSidebar <- shouldShowSidebar--    let rspec = reportspec_ (cliopts_ opts)-        ropts = _rsReportOpts rspec-        ropts' = (_rsReportOpts rspec)-          {accountlistmode_ = ALTree  -- force tree mode for sidebar-          ,empty_           = True    -- show zero items by default-          }-        rspec' = rspec{_rsQuery=q,_rsReportOpts=ropts'}--    hideEmptyAccts <- if empty_ ropts-                         then return True-                         else (== Just "1") . lookup "hideemptyaccts" . reqCookies <$> getRequest--    let accounts =-          balanceReportAsHtml (JournalR, RegisterR) here hideEmptyAccts j qparam qopts $-          styleAmounts (journalCommodityStylesWith HardRounding j) $-          balanceReport rspec' j--        topShowmd = if showSidebar then "col-md-4" else "col-any-0" :: Text-        topShowsm = if showSidebar then "col-sm-4" else "" :: Text-        sideShowmd = if showSidebar then "col-md-4" else "col-any-0" :: Text-        sideShowsm = if showSidebar then "col-sm-4" else "" :: Text-        mainShowmd = if showSidebar then "col-md-8" else "col-md-12" :: Text-        mainShowsm = if showSidebar then "col-sm-8" else "col-sm-12" :: Text--    -- We break up the default layout into two components:-    -- default-layout is the contents of the body tag, and-    -- default-layout-wrapper is the entire page. Since the final-    -- value passed to hamletToRepHtml cannot be a widget, this allows-    -- you to use normal widget features in default-layout.-    pc <- widgetToPageContent $ do-      addStylesheet $ StaticR css_bootstrap_min_css-      addStylesheet $ StaticR css_bootstrap_datepicker_standalone_min_css-      -- load these things early, in HEAD:-      toWidgetHead [hamlet|-        <script type="text/javascript" src="@{StaticR js_jquery_min_js}">-        <script type="text/javascript" src="@{StaticR js_typeahead_bundle_min_js}">-      |]-      addScript $ StaticR js_bootstrap_min_js-      addScript $ StaticR js_bootstrap_datepicker_min_js-      addScript $ StaticR js_jquery_url_js-      addScript $ StaticR js_jquery_cookie_js-      addScript $ StaticR js_jquery_hotkeys_js-      addScript $ StaticR js_jquery_flot_min_js-      addScript $ StaticR js_jquery_flot_selection_min_js-      addScript $ StaticR js_jquery_flot_time_min_js-      addScript $ StaticR js_jquery_flot_tooltip_min_js-      toWidget [hamlet| \<!--[if lte IE 8]> <script type="text/javascript" src="@{StaticR js_excanvas_min_js}"></script> <![endif]--> |]-      addStylesheet $ StaticR hledger_css-      addScript $ StaticR hledger_js-      $(widgetFile "default-layout")--    withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")--#ifndef DEVELOPMENT-  -- This function creates static content files in the static folder-  -- and names them based on a hash of their content. This allows-  -- expiration dates to be set far in the future without worry of-  -- users receiving stale content.-  addStaticContent = addStaticContentExternal minifym base64md5 staticDir (StaticR . flip StaticRoute [])-#endif---- This instance is required to use forms. You can modify renderMessage to--- achieve customized and internationalized form validation messages.-instance RenderMessage App FormMessage where-    renderMessage _ _ = defaultFormMessage---------------------------------------------------------------------------- template and handler utilities---- view data, used by the add form and handlers--- XXX Parameter p - show/hide postings---- | A bundle of data useful for hledger-web request handlers and templates.-data ViewData = VD-  { opts  :: WebOpts    -- ^ the command-line options at startup-  , today :: Day        -- ^ today's date (for queries containing relative dates)-  , j     :: Journal    -- ^ the up-to-date parsed unfiltered journal-  , qparam :: Text       -- ^ the current "q" request parameter-  , q     :: Query      -- ^ a query parsed from the q parameter-  , qopts :: [QueryOpt] -- ^ query options parsed from the q parameter-  , perms :: [Permission]  -- ^ permissions enabled for this request (by --allow and/or X-Sandstorm-Permissions)-  } deriving (Show)--instance Show Text.Blaze.Markup where show _ = "<blaze markup>"---- | Gather data used by handlers and templates in the current request.-getViewData :: Handler ViewData-getViewData = do-  App{-    appOpts=opts@WebOpts{ cliopts_=copts@CliOpts{ reportspec_=rspec@ReportSpec{_rsReportOpts, _rsQuery} } },-    appJournal-  } <- getYesod-  let today = _rsDay rspec--  -- try to read the latest journal content, keeping the old content-  -- if there's an error-  (j, mjerr) <- getCurrentJournal-                appJournal-                copts{reportspec_=rspec{_rsReportOpts=_rsReportOpts{no_elide_=True}}}-                today--  -- Get the query specified by the q request parameter, or no query if this fails.-  qparam <- fromMaybe "" <$> lookupGetParam "q"-  (q1, qopts, mqerr) <- do-    case parseQuery today qparam of-      Right (q0, qopts) -> return (q0, qopts, Nothing)-      Left err         -> return (Any, [], Just err)-  -- To this, add any depth limit from the initial startup query, preserving that.-  let-    initialdepthq = filterQuery queryIsDepth _rsQuery-    q = simplifyQuery $ And [q1, initialdepthq]--  -- if either of the above gave an error, display it-  maybe (pure ()) (setMessage . toHtml) $ mjerr <|> mqerr--  -- find out which permissions are enabled-  perms <- case allow_ opts of-    -- if started with --allow=sandstorm, take permissions from X-Sandstorm-Permissions header-    SandstormAccess -> do-      let h = "X-Sandstorm-Permissions"-      hs <- fmap (BC.split ',' . snd) . filter ((== h) . fst) . requestHeaders <$> waiRequest-      fmap join . for (join hs) $ \x -> case parsePermission x of-        Left  e -> [] <$ addMessage "" ("Unknown permission: " <> toHtml e)-        Right p -> pure [p]-    -- otherwise take them from the access level specified by --allow's access level-    cliaccess -> pure $ accessLevelToPermissions cliaccess--  return VD{opts, today, j, qparam, q, qopts, perms}--checkServerSideUiEnabled :: Handler ()-checkServerSideUiEnabled = do-  VD{opts=WebOpts{serve_api_}} <- getViewData-  when serve_api_ $-    -- this one gives 500 internal server error when called from defaultLayout:-    --  permissionDenied "server-side UI is disabled due to --serve-api"-    sendResponseStatus status403 ("server-side UI is disabled due to --serve-api" :: Text)---- | Find out if the sidebar should be visible. Show it, unless there is a--- showsidebar cookie set to "0", or a ?sidebar=0 query parameter.-shouldShowSidebar :: Handler Bool-shouldShowSidebar = do-  msidebarparam <- lookupGetParam "sidebar"-  msidebarcookie <- lookup "showsidebar" . reqCookies <$> getRequest-  return $-    let disablevalues = ["","0"]-    in maybe True (`notElem` disablevalues) $ msidebarparam <|> msidebarcookie---- | Update our copy of the journal if the file changed. If there is an--- error while reloading, keep the old one and return the error, and set a--- ui message.-getCurrentJournal :: IORef Journal -> CliOpts -> Day -> Handler (Journal, Maybe String)-getCurrentJournal jref opts d = do-  -- re-apply any initial filter specified at startup-  let depthlessinitialq = filterQuery (not . queryIsDepth) $ _rsQuery $ reportspec_ opts-  -- XXX put this inside atomicModifyIORef' for thread safety-  j <- liftIO (readIORef jref)-  ej <- liftIO . runExceptT $ journalReloadIfChanged opts d j-  case ej of-    Left e -> do-      setMessage "error while reading journal"-      return (j, Just e)-    Right (j', True) -> do-      liftIO . writeIORef jref $ filterJournalTransactions depthlessinitialq j'-      return (j',Nothing)-    Right (_, False) -> return (j, Nothing)---- | In a request handler, check for the given permission--- and fail with a message if it's not present.-require :: Permission -> Handler ()-require p = do-  VD{perms} <- getViewData-  unless (p `elem` perms) $ permissionDenied $-    "Missing the '" <> T.pack (showPermission p) <> "' permission"
Hledger/Web/Handler/RegisterR.hs view
@@ -109,7 +109,7 @@    colorForCommodity = fromMaybe 0 . flip lookup commoditiesIndex    commoditiesIndex = zip (map fst percommoditytxnreports) [0..] :: [(CommoditySymbol,Int)]    simpleMixedAmountQuantity = maybe 0 aquantity . listToMaybe . amounts . mixedAmountStripPrices-   showZeroCommodity = wbUnpack . showMixedAmountB oneLine{displayPrice=False,displayZeroCommodity=True}+   showZeroCommodity = wbUnpack . showMixedAmountB oneLine{displayCost=False,displayZeroCommodity=True}    shownull c = if null c then " " else c    nodatelink = (RegisterR, [("q", T.unwords $ removeDates q)]) 
Hledger/Web/Import.hs view
@@ -20,7 +20,7 @@ import           Data.Void            as Import (Void) import           Text.Blaze           as Import (Markup) -import           Hledger.Web.Foundation           as Import+import           Hledger.Web.App                  as Import import           Hledger.Web.Settings             as Import import           Hledger.Web.Settings.StaticFiles as Import import           Hledger.Web.WebOptions           as Import (Permission(..))
Hledger/Web/Main.hs view
@@ -1,9 +1,8 @@ {-|+hledger-web - a basic but robust web UI and JSON API server for hledger. -hledger-web - a hledger add-on providing a web interface.-Copyright (c) 2007-2020 Simon Michael <simon@joyful.com>+Copyright (c) 2007-2023 Simon Michael <simon@joyful.com> and contributors. Released under GPL version 3 or later.- -}  {-# LANGUAGE MultiWayIf #-}@@ -72,7 +71,7 @@       h = host_ opts       p = port_ opts       u = base_url_ opts-      staticRoot = T.pack <$> file_url_ opts+      staticRoot = T.pack <$> file_url_ opts  -- XXX not used #2139       appconfig = AppConfig{appEnv = Development                            ,appHost = fromString h                            ,appPort = p@@ -80,9 +79,25 @@                            ,appExtra = Extra "" Nothing staticRoot                            }   app <- makeApplication opts j' appconfig-  -- XXX would like to allow a host name not just an IP address here-  _ <- printf "Serving web %s on %s:%d with base url %s\n"-         (if serve_api_ opts then "API" else "UI and API" :: String) h p u++  -- show configuration+  let+    services | serve_api_ opts = "json API"+             | otherwise       = "web UI and json API"+    prettyip ip+        | ip == "127.0.0.1" = ip ++ " (local access)"+        | ip == "0.0.0.0"   = ip ++ " (all interfaces)"+        | otherwise         = ip+    listenat =+      case socket_ opts of+        Just s  -> printf "socket %s" s+        Nothing -> printf "IP address %s, port %d" (prettyip h) p+  printf "Serving %s at %s\nwith base url %s\n" (services::String) (listenat::String) u+  case file_url_ opts of+    Just fu -> printf "and static files base url %s\n" fu+    Nothing -> pure ()++  -- start server and maybe browser   if serve_ opts || serve_api_ opts     then do       putStrLn "Press ctrl-c to quit"@@ -108,7 +123,9 @@               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..."
Hledger/Web/Settings.hs view
@@ -1,12 +1,13 @@+{-|+Web app settings are centralized, as much as possible, into this file.+This includes database connection settings, static file locations, etc.+In addition, you can configure a number of different aspects of Yesod+by overriding methods in the Yesod typeclass in App.hs.+-}+ {-# LANGUAGE CPP               #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes       #-} --- | Settings are centralized, as much as possible, into this file. This--- includes database connection settings, static file locations, etc.--- In addition, you can configure a number of different aspects of Yesod--- by overriding methods in the Yesod typeclass. That instance is--- declared in the Foundation.hs file. module Hledger.Web.Settings where  import Data.Default (def)@@ -16,7 +17,6 @@ import Data.Yaml import Language.Haskell.TH.Syntax (Q, Exp) import Text.Hamlet-import Text.Shakespeare.Text (st) import Yesod.Default.Config import Yesod.Default.Util @@ -49,33 +49,34 @@  defbaseurl :: String -> Int -> String defbaseurl host port =-  if ':' `elem` host then+  if ':' `elem` host+  then  -- ipv6 address     "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+-- Static file settings. Changing these requires a recompile. --- | The location of static files on your system. This is a file system--- path. The default value works properly with your scaffolded site.+-- | The file path on your machine where static files can be found.+-- StaticFiles.hs uses this (must be separate for TH reasons). staticDir :: FilePath staticDir = "static" --- | The base URL for your static files. As you can see by the default+-- | The base URL for static files. As you can see by the default -- value, this can simply be "static" appended to your application root. -- A powerful optimization can be serving static files from a separate -- domain name. This allows you to use a web server optimized for static -- files, more easily set expires and cache values, and avoid possibly--- costly transference of cookies on static files. For more information,--- please see:---   http://code.google.com/speed/page-speed/docs/request.html#ServeFromCookielessDomain+-- costly transference of cookies on static files. ----- If you change the resource pattern for StaticR in Foundation.hs, you will--- have to make a corresponding change here.+-- If you change the resource pattern for StaticR in App.hs,+-- (or staticDir above), you will have to make a corresponding change here. ----- To see how this value is used, see urlRenderOverride in Foundation.hs+-- To see how this value is used, see urlRenderOverride in App.hs+--+-- XXX Does not respect --file-url #2139 staticRoot :: AppConfig DefaultEnv Extra -> Text-staticRoot conf = fromMaybe [st|#{appRoot conf}/static|] . extraStaticRoot $ appExtra conf+staticRoot conf = fromMaybe (appRoot conf <> "/static") . extraStaticRoot $ appExtra conf  -- | Settings for 'widgetFile', such as which template languages to support and -- default Hamlet settings.
Hledger/Web/Settings/StaticFiles.hs view
@@ -22,7 +22,7 @@ staticSite =   if development    then (do-            putStrLn ("Using web files from: " ++ staticDir ++ "/") >> hFlush stdout+            putStrLn ("Running in dev mode, will read static files from " ++ staticDir ++ "/") >> hFlush stdout             staticDevel staticDir)    else (do             -- putStrLn "Using built-in web files" >> hFlush stdout
Hledger/Web/Test.hs view
@@ -1,101 +1,169 @@+{-|+Test suite for hledger-web.++Dev notes:++http://hspec.github.io/writing-specs.html++https://hackage.haskell.org/package/yesod-test-1.6.10/docs/Yesod-Test.html++"The best way to see an example project using yesod-test is to create a scaffolded Yesod project:+stack new projectname yesodweb/sqlite+(See https://github.com/commercialhaskell/stack-templates/wiki#yesod for the full list of Yesod templates)"+++These tests don't exactly match the production code path, eg these bits are missing:++  withJournalDo copts (web wopts)  -- extra withJournalDo logic (journalTransform..)+  ...+  -- query logic, more options logic+  let depthlessinitialq = filterQuery (not . queryIsDepth) . _rsQuery . reportspec_ $ cliopts_ wopts+      j' = filterJournalTransactions depthlessinitialq j+      h = host_ wopts+      p = port_ wopts+      u = base_url_ wopts+      staticRoot = T.pack <$> file_url_ wopts+      appconfig = AppConfig{appEnv = Development+                           ,appHost = fromString h+                           ,appPort = p+                           ,appRoot = T.pack u+                           ,appExtra = Extra "" Nothing staticRoot+                           }++The production code path, when called in this test context, which I guess is using+yesod's dev mode, needs to read ./config/settings.yml and fails without it (loadConfig).++-}+ {-# LANGUAGE OverloadedStrings #-}  module Hledger.Web.Test (   hledgerWebTest ) where +import Data.String (fromString)+import Data.Function ((&)) import qualified Data.Text as T import Test.Hspec (hspec) import Yesod.Default.Config import Yesod.Test -import Hledger.Web.Application ( makeFoundationWith )-import Hledger.Web.WebOptions ( WebOpts(cliopts_), defwebopts, prognameandversion )+import Hledger.Web.Application ( makeAppWith )+import Hledger.Web.WebOptions  -- ( WebOpts(..), defwebopts, prognameandversion ) import Hledger.Web.Import hiding (get, j) import Hledger.Cli hiding (prognameandversion)  -runHspecTestsWith :: AppConfig DefaultEnv Extra -> WebOpts -> Journal -> YesodSpec App -> IO ()-runHspecTestsWith yesodconf hledgerwebopts j specs = do-  app <- makeFoundationWith j yesodconf hledgerwebopts-  hspec $ yesodSpec app specs+-- | Given a tests description, zero or more raw option name/value pairs,+-- a journal and some hspec tests, parse the options and configure the+-- web app more or less as we normally would (see details above), then run the tests.+--+-- Raw option names are like the long flag without the --, eg "file" or "base-url".+--+-- The journal and raw options should correspond enough to not cause problems.+-- Be cautious - without a [("file", "somepath")], perhaps journalReload could load+-- the user's default journal.+--+runTests :: String -> [(String,String)] -> Journal -> YesodSpec App -> IO ()+runTests testsdesc rawopts j tests = do+  wopts <- rawOptsToWebOpts $ mkRawOpts rawopts+  let yconf = AppConfig{  -- :: AppConfig DefaultEnv Extra+          appEnv = Testing+        -- https://hackage.haskell.org/package/conduit-extra/docs/Data-Conduit-Network.html#t:HostPreference+        -- ,appHost = "*4"  -- "any IPv4 or IPv6 hostname, IPv4 preferred"+        -- ,appPort = 3000  -- force a port for tests ?+        -- Test with the host and port from opts. XXX more fragile, can clash with a running instance ?+        ,appHost = host_ wopts & fromString+        ,appPort = port_ wopts+        ,appRoot = base_url_ wopts & T.pack  -- XXX not sure this or extraStaticRoot get used+        ,appExtra = Extra+                    { extraCopyright  = ""+                    , extraAnalytics  = Nothing+                    , extraStaticRoot = T.pack <$> file_url_ wopts+                    }+        }+  app <- makeAppWith j yconf wopts+  hspec $ yesodSpec app $ ydescribe testsdesc tests    -- https://hackage.haskell.org/package/yesod-test/docs/Yesod-Test.html --- Run hledger-web's built-in tests using the hspec test runner.+-- | Run hledger-web's built-in tests using the hspec test runner. hledgerWebTest :: IO () hledgerWebTest = do   putStrLn $ "Running tests for " ++ prognameandversion -- ++ " (--test --help for options)"--  -- loadConfig fails without ./config/settings.yml; use a hard-coded one-  let conf = AppConfig{-               appEnv = Testing-              ,appPort = 3000  -- will it clash with a production instance ? doesn't seem to-              ,appRoot = "http://localhost:3000"-              ,appHost = "*4"-              ,appExtra = Extra-                          { extraCopyright  = ""-                          , extraAnalytics  = Nothing-                          , extraStaticRoot = Nothing-                          }-                  }--  -- http://hspec.github.io/writing-specs.html-  -- https://hackage.haskell.org/package/yesod-test-1.6.10/docs/Yesod-Test.html-  -- "The best way to see an example project using yesod-test is to create a scaffolded Yesod project:-  -- stack new projectname yesodweb/sqlite-  -- (See https://github.com/commercialhaskell/stack-templates/wiki#yesod for the full list of Yesod templates)"+  let d = fromGregorian 2000 1 1 -  -- Since these tests use makeFoundation, the startup code in Hledger.Web.Main is not tested. XXX-  ---  -- Be aware that unusual combinations of opts/files here could cause problems,-  -- eg if cliopts{file_} is left empty journalReload might reload the user's default journal.+  runTests "hledger-web" [] nulljournal $ do -  -- basic tests-  runHspecTestsWith conf defwebopts nulljournal $ do-    ydescribe "hledger-web" $ do+    yit "serves a reasonable-looking journal page" $ do+      get JournalR+      statusIs 200+      bodyContains "Add a transaction" -      yit "serves a reasonable-looking journal page" $ do-        get JournalR-        statusIs 200-        bodyContains "Add a transaction"+    yit "serves a reasonable-looking register page" $ do+      get RegisterR+      statusIs 200+      bodyContains "accounts" -      yit "serves a reasonable-looking register page" $ do-        get RegisterR-        statusIs 200-        bodyContains "accounts"+    yit "hyperlinks use a base url made from the default host and port" $ do+      get JournalR+      statusIs 200+      let defaultbaseurl = defbaseurl defhost defport+      bodyContains ("href=\"" ++ defaultbaseurl)+      bodyContains ("src=\"" ++ defaultbaseurl) -      -- WIP-      -- yit "shows the add form" $ do-      --   get JournalR-      --   -- printBody-      --   -- let addbutton = "button:contains('add')"-      --   -- bodyContains addbutton-      --   -- htmlAnyContain "button:visible" "add"-      --   printMatches "div#addmodal:visible"-      --   htmlCount "div#addmodal:visible" 0+    -- WIP+    -- yit "shows the add form" $ do+    --   get JournalR+    --   -- printBody+    --   -- let addbutton = "button:contains('add')"+    --   -- bodyContains addbutton+    --   -- htmlAnyContain "button:visible" "add"+    --   printMatches "div#addmodal:visible"+    --   htmlCount "div#addmodal:visible" 0 -      --   -- clickOn "a#addformlink"-      --   -- printBody-      --   -- bodyContains addbutton+    --   -- clickOn "a#addformlink"+    --   -- printBody+    --   -- bodyContains addbutton -      -- yit "can add transactions" $ do+    -- yit "can add transactions" $ do    let-    -- Have forecasting on for testing-    iopts = definputopts{forecast_=Just nulldatespan}-    copts = defcliopts{inputopts_=iopts, file_=[""]}  -- non-empty, see file_ note above-    wopts = defwebopts{cliopts_=copts}+    rawopts = [("forecast","")]+    iopts = rawOptsToInputOpts d $ mkRawOpts rawopts+    f = "fake"  -- need a non-null filename so forecast transactions get index 0   pj <- readJournal' (T.pack $ unlines  -- PARTIAL: readJournal' should not fail     ["~ monthly"     ,"    assets    10"     ,"    income"     ])-  -- Have to give a non-null filename "fake" so forecast transactions get index 0-  j <- fmap (either error id) . runExceptT $ journalFinalise iopts "fake" "" pj  -- PARTIAL: journalFinalise should not fail-  runHspecTestsWith conf wopts j $ do-    ydescribe "hledger-web --forecast" $ do+  j <- fmap (either error id) . runExceptT $ journalFinalise iopts f "" pj  -- PARTIAL: journalFinalise should not fail+  runTests "hledger-web with --forecast" rawopts j $ do -      yit "serves a journal page showing forecasted transactions" $ do-        get JournalR-        statusIs 200-        bodyContains "id=\"transaction-2-1\""-        bodyContains "id=\"transaction-2-2\""+    yit "shows forecasted transactions" $ do+      get JournalR+      statusIs 200+      bodyContains "id=\"transaction-2-1\""+      bodyContains "id=\"transaction-2-2\""++  -- #2127+  -- XXX I'm pretty sure this test lies, ie does not match production behaviour.+  -- (test with curl -s http://localhost:5000/journal | rg '(href)="[\w/].*?"' -o )+  -- App root setup is a maze of twisty passages, all alike.+  -- runTests "hledger-web with --base-url"+  --   [("base-url","https://base")] nulljournal $ do+  --   yit "hyperlinks respect --base-url" $ do+  --     get JournalR+  --     statusIs 200+  --     bodyContains "href=\"https://base"+  --     bodyContains "src=\"https://base"++  -- #2139+  -- XXX Not passing.+  -- Static root setup is a maze of twisty passages, all different.+  -- runTests "hledger-web with --base-url, --file-url"+  --   [("base-url","https://base"), ("file-url","https://files")] nulljournal $ do+  --   yit "static file hyperlinks respect --file-url, others respect --base-url" $ do+  --     get JournalR+  --     statusIs 200+  --     bodyContains "href=\"https://base"+  --     bodyContains "src=\"https://files"+
Hledger/Web/WebOptions.hs view
@@ -45,7 +45,7 @@   , flagNone       ["serve-api"]       (setboolopt "serve-api")-      "like --serve, but serve only the JSON web API, without the server-side web UI"+      "like --serve, but serve only the JSON web API, not the web UI"   , flagReq       ["allow"]       (\s opts -> Right $ setopt "allow" s opts)@@ -57,11 +57,6 @@       "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"@@ -72,15 +67,21 @@       "PORT"       ("listen on this TCP port (default: " ++ show defport ++ ")")   , flagReq+      ["socket"]+      (\s opts -> Right $ setopt "socket" s opts)+      "SOCKET"+      "listen on the given unix socket instead of an IP address and port (unix only; implies --serve)"+  , flagReq       ["base-url"]       (\s opts -> Right $ setopt "base-url" s opts)       "BASEURL"       "set the base url (default: http://IPADDR:PORT)"-  , flagReq-      ["file-url"]-      (\s opts -> Right $ setopt "file-url" s opts)-      "FILEURL"-      "set the static files url (default: BASEURL/static)"+  -- XXX #2139+  -- , flagReq+  --     ["file-url"]+  --     (\s opts -> Right $ setopt "file-url" s opts)+  --     "FILEURL"+  --     "set a different base url for static files (default: `BASEURL/static/`)"   , flagNone       ["test"]       (setboolopt "test")
Hledger/Web/Widget/AddForm.hs view
@@ -25,7 +25,7 @@ import Yesod  import Hledger-import Hledger.Web.Foundation (App, Handler, Widget)+import Hledger.Web.App (App, Handler, Widget) import Hledger.Web.Settings (widgetFile) import Data.Function ((&)) import Control.Arrow (right)
hledger-web.1 view
@@ -1,34 +1,34 @@ -.TH "HLEDGER-WEB" "1" "December 2023" "hledger-web-1.32.1 " "hledger User Manuals"+.TH "HLEDGER\-WEB" "1" "December 2023" "hledger-web-1.32.2 " "hledger User Manuals"    .SH NAME-hledger-web - robust, friendly plain text accounting (Web version)+hledger\-web \- robust, friendly plain text accounting (Web version) .SH SYNOPSIS-\f[CR]hledger-web    [--serve|--serve-api] [OPTS] [ARGS]\f[R]+\f[CR]hledger\-web    [\-\-serve|\-\-serve\-api] [OPTS] [ARGS]\f[R] .PD 0 .P .PD-\f[CR]hledger web -- [--serve|--serve-api] [OPTS] [ARGS]\f[R]+\f[CR]hledger web \-\- [\-\-serve|\-\-serve\-api] [OPTS] [ARGS]\f[R] .SH DESCRIPTION-This manual is for hledger\[aq]s web interface, version 1.32.1.+This manual is for hledger\[aq]s web interface, version 1.32.2. See also the hledger manual for common concepts and file formats. .PP-hledger is a robust, user-friendly, cross-platform set of programs for-tracking money, time, or any other commodity, using double-entry+hledger is a robust, user\-friendly, 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), and largely interconvertible with beancount(1). .PP-hledger-web is a simple web application for browsing and adding+hledger\-web is a simple web application for browsing and adding transactions.-It provides a more user-friendly UI than the hledger CLI or hledger-ui+It provides a more user\-friendly UI than the hledger CLI or hledger\-ui TUI, showing more at once (accounts, the current account register,-balance charts) and allowing history-aware data entry, interactive+balance charts) and allowing history\-aware data entry, interactive searching, and bookmarking. .PP-hledger-web also lets you share a journal with multiple users, or even+hledger\-web also lets you share a journal with multiple users, or even the public web. There is no access control, so if you need that you should put it behind a suitable web proxy.@@ -39,281 +39,262 @@ Like hledger, it reads from (and appends to) a journal file specified by the \f[CR]LEDGER_FILE\f[R] environment variable (defaulting to \f[CR]$HOME/.hledger.journal\f[R]); or you can specify files with-\f[CR]-f\f[R] options.+\f[CR]\-f\f[R] options. It can also read timeclock files, timedot files, or any CSV/SSV/TSV file with a date field.-(See hledger(1) -> Input for details.)+(See hledger(1) \-> Input for details.) .PP-hledger-web can be run in three modes:+hledger\-web can be run in three modes: .IP \[bu] 2 Transient mode (the default): your default web browser will be opened to show the app if possible, and the app exits automatically after two minutes of inactivity (no requests received and no open browser windows viewing it). .IP \[bu] 2-With \f[CR]--serve\f[R]: the app runs without stopping, and without+With \f[CR]\-\-serve\f[R]: the app runs without stopping, and without opening a browser. .IP \[bu] 2-With \f[CR]--serve-api\f[R]: only the JSON API is served.+With \f[CR]\-\-serve\-api\f[R]: only the JSON API is served. .PP-In all cases hledger-web runs as a foreground process, logging requests+In all cases hledger\-web runs as a foreground process, logging requests to stdout. .SH OPTIONS-Command-line options and arguments may be used to set an initial filter-on the data.-These filter options are not shown in the web UI, but it will be applied-in addition to any search query entered there.-.PP-hledger-web provides the following options:-.TP-\f[CR]--serve\f[R]-serve and log requests, don\[aq]t browse or auto-exit after timeout-.TP-\f[CR]--serve-api\f[R]-like --serve, but serve only the JSON web API, without the server-side-web UI-.TP-\f[CR]--host=IPADDR\f[R]-listen on this IP address (default: 127.0.0.1)-.TP-\f[CR]--port=PORT\f[R]-listen on this TCP port (default: 5000)-.TP-\f[CR]--socket=SOCKETFILE\f[R]-use a unix domain socket file to listen for requests instead of a TCP-socket.-Implies \f[CR]--serve\f[R].-It can only be used if the operating system can provide this type of-socket.+hledger\-web provides the following options: .TP-\f[CR]--base-url=URL\f[R]-set the base url (default: http://IPADDR:PORT).-Note: affects url generation but not route parsing.-Can be useful if running behind a reverse web proxy that does path-rewriting.+\f[CR]\-\-serve\f[R]+serve and log requests, don\[aq]t browse or auto\-exit after timeout .TP-\f[CR]--file-url=URL\f[R]-set the static files url (default: BASEURL/static).-hledger-web normally serves static files itself, but if you wanted to-serve them from another server for efficiency, you would set the url-with this.+\f[CR]\-\-serve\-api\f[R]+like \-\-serve, but serve only the JSON web API, not the web UI .TP-\f[CR]--allow=view|add|edit\f[R]+\f[CR]\-\-allow=view|add|edit\f[R] set the user\[aq]s access level for changing data (default: \f[CR]add\f[R]). It also accepts \f[CR]sandstorm\f[R] for use on that platform (reads-permissions from the \f[CR]X-Sandstorm-Permissions\f[R] request header).+permissions from the \f[CR]X\-Sandstorm\-Permissions\f[R] request+header). .TP-\f[CR]--test\f[R]-run hledger-web\[aq]s tests and exit.-hspec test runner args may follow a --, eg: hledger-web --test -- --help+\f[CR]\-\-cors=ORIGIN\f[R]+allow cross\-origin requests from the specified origin; setting ORIGIN+to \[dq]*\[dq] allows requests from any origin+.TP+\f[CR]\-\-host=IPADDR\f[R]+listen on this IP address (default: 127.0.0.1) .PP-By default the server listens on IP address 127.0.0.1, accessible only-to local requests.-You can use \f[CR]--host\f[R] to change this, eg-\f[CR]--host 0.0.0.0\f[R] to listen on all configured addresses.+By default the server listens on IP address \f[CR]127.0.0.1\f[R], which+is accessible only to requests from the local machine..+You can use \f[CR]\-\-host\f[R] to listen on a different address+configured on the machine, eg to allow access from other machines.+The special address \f[CR]0.0.0.0\f[R] causes it to listen on all+addresses configured on the machine.+.TP+\f[CR]\-\-port=PORT\f[R]+listen on this TCP port (default: 5000) .PP-Similarly, use \f[CR]--port\f[R] to set a TCP port other than 5000, eg-if you are running multiple hledger-web instances.+Similarly, you can use \f[CR]\-\-port\f[R] to listen on a TCP port other+than 5000.+This is useful if you want to run multiple hledger\-web instances on a+machine.+.TP+\f[CR]\-\-socket=SOCKETFILE\f[R]+listen on the given unix socket instead of an IP address and port (unix+only; implies \-\-serve) .PP-Both of these options are ignored when \f[CR]--socket\f[R] is used.-In this case, it creates an \f[CR]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[CR]nginx\f[R] as reverse proxy can use the variable-\f[CR]$remote_user\f[R] to derive a path from the username used in a-HTTP basic authentication.-The following \f[CR]proxy_pass\f[R] directive allows access to all-\f[CR]hledger-web\f[R] instances that created a socket in-\f[CR]/tmp/hledger/\f[R]:-.IP-.EX-  proxy_pass http://unix:/tmp/hledger/${remote_user}.socket;-.EE+When \f[CR]\-\-socket\f[R] is used, hledger\-web creates and+communicates via a socket file instead of a TCP port.+This can be more secure, respects unix file permissions, and makes+certain use cases easier, such as running per\-user instances behind an+nginx reverse proxy.+(Eg:+\f[CR]proxy_pass http://unix:/tmp/hledger/${remote_user}.socket;\f[R].)+.TP+\f[CR]\-\-base\-url=URL\f[R]+set the base url (default: http://IPADDR:PORT). .PP-You can use \f[CR]--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.+You can use \f[CR]\-\-base\-url\f[R] to change the protocol, hostname,+port and path that appear in hledger\-web\[aq]s hyperlinks.+This is useful eg when integrating hledger\-web within a larger website. The default is \f[CR]http://HOST:PORT/\f[R] using the server\[aq]s configured host address and TCP port (or \f[CR]http://HOST\f[R] if PORT is 80).-.PP-With \f[CR]--file-url\f[R] you can set a different base url for static-files, eg for better caching or cookie-less serving on high performance-websites.+Note this affects url generation but not route parsing.+.TP+\f[CR]\-\-test\f[R]+run hledger\-web\[aq]s tests and exit.+hspec test runner args may follow a \-\-, eg: hledger\-web \-\-test \-\-+\-\-help .PP-hledger-web also supports many of hledger\[aq]s general options (and the-hledger manual\[aq]s command line tips also apply here):+hledger\-web also supports many of hledger\[aq]s general options.+Query options and arguments may be used to set an initial filter, which+although not shown in the UI, will restrict the data shown, in addition+to any search query entered in the UI. .SS General help options .TP-\f[CR]-h --help\f[R]+\f[CR]\-h \-\-help\f[R] show general or COMMAND help .TP-\f[CR]--man\f[R]+\f[CR]\-\-man\f[R] show general or COMMAND user manual with man .TP-\f[CR]--info\f[R]+\f[CR]\-\-info\f[R] show general or COMMAND user manual with info .TP-\f[CR]--version\f[R]+\f[CR]\-\-version\f[R] show general or ADDONCMD version .TP-\f[CR]--debug[=N]\f[R]-show debug output (levels 1-9, default: 1)+\f[CR]\-\-debug[=N]\f[R]+show debug output (levels 1\-9, default: 1) .SS General input options .TP-\f[CR]-f FILE --file=FILE\f[R]+\f[CR]\-f FILE \-\-file=FILE\f[R] use a different input file.-For stdin, use - (default: \f[CR]$LEDGER_FILE\f[R] or+For stdin, use \- (default: \f[CR]$LEDGER_FILE\f[R] or \f[CR]$HOME/.hledger.journal\f[R]) .TP-\f[CR]--rules-file=RULESFILE\f[R]+\f[CR]\-\-rules\-file=RULESFILE\f[R] Conversion rules file to use when reading CSV (default: FILE.rules) .TP-\f[CR]--separator=CHAR\f[R]+\f[CR]\-\-separator=CHAR\f[R] Field separator to expect when reading CSV (default: \[aq],\[aq]) .TP-\f[CR]--alias=OLD=NEW\f[R]+\f[CR]\-\-alias=OLD=NEW\f[R] rename accounts named OLD to NEW .TP-\f[CR]--anon\f[R]+\f[CR]\-\-anon\f[R] anonymize accounts and payees .TP-\f[CR]--pivot FIELDNAME\f[R]+\f[CR]\-\-pivot FIELDNAME\f[R] use some other field or tag for the account name .TP-\f[CR]-I --ignore-assertions\f[R]+\f[CR]\-I \-\-ignore\-assertions\f[R] disable balance assertion checks (note: does not disable balance assignments) .TP-\f[CR]-s --strict\f[R]+\f[CR]\-s \-\-strict\f[R] do extra error checking (check that all posted accounts are declared) .SS General reporting options .TP-\f[CR]-b --begin=DATE\f[R]+\f[CR]\-b \-\-begin=DATE\f[R] include postings/txns on or after this date (will be adjusted to preceding subperiod start when using a report interval) .TP-\f[CR]-e --end=DATE\f[R]+\f[CR]\-e \-\-end=DATE\f[R] include postings/txns before this date (will be adjusted to following subperiod end when using a report interval) .TP-\f[CR]-D --daily\f[R]+\f[CR]\-D \-\-daily\f[R] multiperiod/multicolumn report by day .TP-\f[CR]-W --weekly\f[R]+\f[CR]\-W \-\-weekly\f[R] multiperiod/multicolumn report by week .TP-\f[CR]-M --monthly\f[R]+\f[CR]\-M \-\-monthly\f[R] multiperiod/multicolumn report by month .TP-\f[CR]-Q --quarterly\f[R]+\f[CR]\-Q \-\-quarterly\f[R] multiperiod/multicolumn report by quarter .TP-\f[CR]-Y --yearly\f[R]+\f[CR]\-Y \-\-yearly\f[R] multiperiod/multicolumn report by year .TP-\f[CR]-p --period=PERIODEXP\f[R]+\f[CR]\-p \-\-period=PERIODEXP\f[R] set start date, end date, and/or reporting interval all at once using period expressions syntax .TP-\f[CR]--date2\f[R]+\f[CR]\-\-date2\f[R] match the secondary date instead (see command help for other effects) .TP-\f[CR]--today=DATE\f[R]+\f[CR]\-\-today=DATE\f[R] override today\[aq]s date (affects relative smart dates, for tests/examples) .TP-\f[CR]-U --unmarked\f[R]-include only unmarked postings/txns (can combine with -P or -C)+\f[CR]\-U \-\-unmarked\f[R]+include only unmarked postings/txns (can combine with \-P or \-C) .TP-\f[CR]-P --pending\f[R]+\f[CR]\-P \-\-pending\f[R] include only pending postings/txns .TP-\f[CR]-C --cleared\f[R]+\f[CR]\-C \-\-cleared\f[R] include only cleared postings/txns .TP-\f[CR]-R --real\f[R]-include only non-virtual postings+\f[CR]\-R \-\-real\f[R]+include only non\-virtual postings .TP-\f[CR]-NUM --depth=NUM\f[R]+\f[CR]\-NUM \-\-depth=NUM\f[R] hide/aggregate accounts or postings more than NUM levels deep .TP-\f[CR]-E --empty\f[R]-show items with zero amount, normally hidden (and vice-versa in-hledger-ui/hledger-web)+\f[CR]\-E \-\-empty\f[R]+show items with zero amount, normally hidden (and vice\-versa in+hledger\-ui/hledger\-web) .TP-\f[CR]-B --cost\f[R]+\f[CR]\-B \-\-cost\f[R] convert amounts to their cost/selling amount at transaction time .TP-\f[CR]-V --market\f[R]+\f[CR]\-V \-\-market\f[R] convert amounts to their market value in default valuation commodities .TP-\f[CR]-X --exchange=COMM\f[R]+\f[CR]\-X \-\-exchange=COMM\f[R] convert amounts to their market value in commodity COMM .TP-\f[CR]--value\f[R]-convert amounts to cost or market value, more flexibly than -B/-V/-X+\f[CR]\-\-value\f[R]+convert amounts to cost or market value, more flexibly than \-B/\-V/\-X .TP-\f[CR]--infer-equity\f[R]+\f[CR]\-\-infer\-equity\f[R] infer conversion equity postings from costs .TP-\f[CR]--infer-costs\f[R]+\f[CR]\-\-infer\-costs\f[R] infer costs from conversion equity postings .TP-\f[CR]--infer-market-prices\f[R]+\f[CR]\-\-infer\-market\-prices\f[R] use costs as additional market prices, as if they were P directives .TP-\f[CR]--forecast\f[R]+\f[CR]\-\-forecast\f[R] generate transactions from periodic rules, between the latest recorded txn and 6 months from today, or during the specified PERIOD (= is required). Auto posting rules will be applied to these transactions as well.-Also, in hledger-ui make future-dated transactions visible.+Also, in hledger\-ui make future\-dated transactions visible. .TP-\f[CR]--auto\f[R]+\f[CR]\-\-auto\f[R] generate extra postings by applying auto posting rules to all txns (not just forecast txns) .TP-\f[CR]--verbose-tags\f[R]+\f[CR]\-\-verbose\-tags\f[R] add visible tags indicating transactions or postings which have been generated/modified .TP-\f[CR]--commodity-style\f[R]+\f[CR]\-\-commodity\-style\f[R] Override the commodity style in the output for the specified commodity. For example \[aq]EUR1.000,00\[aq]. .TP-\f[CR]--color=WHEN (or --colour=WHEN)\f[R]-Should color-supporting commands use ANSI color codes in text output.-\[aq]auto\[aq] (default): whenever stdout seems to be a color-supporting-terminal.+\f[CR]\-\-color=WHEN (or \-\-colour=WHEN)\f[R]+Should color\-supporting commands use ANSI color codes in text output.+\[aq]auto\[aq] (default): whenever stdout seems to be a+color\-supporting terminal. \[aq]always\[aq] or \[aq]yes\[aq]: always, useful eg when piping output-into \[aq]less -R\[aq].+into \[aq]less \-R\[aq]. \[aq]never\[aq] or \[aq]no\[aq]: never. A NO_COLOR environment variable overrides this. .TP-\f[CR]--pretty[=WHEN]\f[R]+\f[CR]\-\-pretty[=WHEN]\f[R] Show prettier output, e.g.-using unicode box-drawing characters.+using unicode box\-drawing characters. Accepts \[aq]yes\[aq] (the default) or \[aq]no\[aq] (\[aq]y\[aq], \[aq]n\[aq], \[aq]always\[aq], \[aq]never\[aq] also work). If you provide an argument you must use \[aq]=\[aq], e.g.-\[aq]--pretty=yes\[aq].+\[aq]\-\-pretty=yes\[aq]. .PP When a reporting option appears more than once in the command line, the last one takes precedence. .PP Some reporting options can also be written as query arguments. .SH 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. .PP You can restrict who can reach it by .IP \[bu] 2-setting the IP address it listens on (see \f[CR]--host\f[R] above).+setting the IP address it listens on (see \f[CR]\-\-host\f[R] above). By default it listens on 127.0.0.1, accessible to all users on the local machine. .IP \[bu] 2@@ -323,22 +304,24 @@ .PP You can restrict what the users who reach it can do, by .IP \[bu] 2-using the \f[CR]--capabilities=CAP[,CAP..]\f[R] flag when you start it,-enabling one or more of the following capabilities.+using the \f[CR]\-\-capabilities=CAP[,CAP..]\f[R] flag when you start+it, enabling one or more of the following capabilities. The default value is \f[CR]view,add\f[R]: .RS 2 .IP \[bu] 2-\f[CR]view\f[R] - allows viewing the journal file and all included files+\f[CR]view\f[R] \- allows viewing the journal file and all included+files .IP \[bu] 2-\f[CR]add\f[R] - allows adding new transactions to the main journal file+\f[CR]add\f[R] \- allows adding new transactions to the main journal+file .IP \[bu] 2-\f[CR]manage\f[R] - allows editing, uploading or downloading the main or-included files+\f[CR]manage\f[R] \- allows editing, uploading or downloading the main+or included files .RE .IP \[bu] 2-using the \f[CR]--capabilities-header=HTTPHEADER\f[R] 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+using the \f[CR]\-\-capabilities\-header=HTTPHEADER\f[R] 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\[aq]s permissions. This is disabled by default. .SH EDITING, UPLOADING, DOWNLOADING@@ -351,35 +334,35 @@ Note, unlike any other hledger command, in this mode you (or any visitor) can alter or wipe the data files. .PP-Normally whenever a file is changed in this way, hledger-web saves a+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, currently; if you+hledger\-web is not aware of version control systems, currently; if you use one, you\[aq]ll have to arrange to commit the changes yourself (eg with a cron job or a file watcher like entr). .PP-Changes which would leave the journal file(s) unparseable or non-valid+Changes which would leave the journal file(s) unparseable or non\-valid (eg with failing balance assertions) are prevented. (Probably.-This needs re-testing.)+This needs re\-testing.) .SH 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 makes a file unparseable, hledger-web will display an error+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 makes a file unparseable, hledger\-web will display an error message until the file has been fixed. .PP (Note: if you are viewing files mounted from another machine, make sure that both machine clocks are roughly in step.) .SH JSON API-In addition to the web UI, hledger-web also serves a JSON API that can+In addition to the web UI, hledger\-web also serves a JSON API that can be used to get data or add new transactions.-If you want the JSON API only, you can use the \f[CR]--serve-api\f[R]+If you want the JSON API only, you can use the \f[CR]\-\-serve\-api\f[R] flag. Eg: .IP .EX-$ hledger-web -f examples/sample.journal --serve-api+$ hledger\-web \-f examples/sample.journal \-\-serve\-api \&... .EE .PP@@ -396,11 +379,11 @@ .EE .PP Eg, all account names in the journal (similar to the accounts command).-(hledger-web\[aq]s JSON does not include newlines, here we use python to-prettify it):+(hledger\-web\[aq]s JSON does not include newlines, here we use python+to prettify it): .IP .EX-$ curl -s http://127.0.0.1:5000/accountnames | python -m json.tool+$ curl \-s http://127.0.0.1:5000/accountnames | python \-m json.tool [     \[dq]assets\[dq],     \[dq]assets:bank\[dq],@@ -421,12 +404,12 @@ Or all transactions: .IP .EX-$ curl -s http://127.0.0.1:5000/transactions | python -m json.tool+$ curl \-s http://127.0.0.1:5000/transactions | python \-m json.tool [     {         \[dq]tcode\[dq]: \[dq]\[dq],         \[dq]tcomment\[dq]: \[dq]\[dq],-        \[dq]tdate\[dq]: \[dq]2008-01-01\[dq],+        \[dq]tdate\[dq]: \[dq]2008\-01\-01\[dq],         \[dq]tdate2\[dq]: null,         \[dq]tdescription\[dq]: \[dq]income\[dq],         \[dq]tindex\[dq]: 1,@@ -457,21 +440,21 @@ and a list of AccountTransactionsReportItem (etc). .PP You can add a new transaction to the journal with a PUT request to-\f[CR]/add\f[R], if hledger-web was started with the \f[CR]add\f[R]+\f[CR]/add\f[R], if hledger\-web was started with the \f[CR]add\f[R] capability (enabled by default). The payload must be the full, exact JSON representation of a hledger transaction (partial data won\[aq]t do).-You can get sample JSON from hledger-web\[aq]s \f[CR]/transactions\f[R]+You can get sample JSON from hledger\-web\[aq]s \f[CR]/transactions\f[R] or \f[CR]/accounttransactions\f[R], or you can export it with-hledger-lib, eg like so:+hledger\-lib, eg like so: .IP .EX-\&.../hledger$ stack ghci hledger-lib+\&.../hledger$ stack ghci hledger\-lib >>> writeJsonFile \[dq]txn.json\[dq] (head $ jtxns samplejournal) >>> :q .EE .PP-Here\[aq]s how it looks as of hledger-1.17 (remember, this JSON+Here\[aq]s how it looks as of hledger\-1.17 (remember, this JSON corresponds to hledger\[aq]s Transaction and related data types): .IP .EX@@ -517,9 +500,9 @@                     \[dq]aprice\[dq]: null,                     \[dq]acommodity\[dq]: \[dq]$\[dq],                     \[dq]aquantity\[dq]: {-                        \[dq]floatingPoint\[dq]: -1,+                        \[dq]floatingPoint\[dq]: \-1,                         \[dq]decimalPlaces\[dq]: 10,-                        \[dq]decimalMantissa\[dq]: -10000000000+                        \[dq]decimalMantissa\[dq]: \-10000000000                     },                     \[dq]aismultiplier\[dq]: false,                     \[dq]astyle\[dq]: {@@ -552,7 +535,7 @@             ]         ]     },-    \[dq]tdate\[dq]: \[dq]2008-01-01\[dq],+    \[dq]tdate\[dq]: \[dq]2008\-01\-01\[dq],     \[dq]tcode\[dq]: \[dq]\[dq],     \[dq]tindex\[dq]: 1,     \[dq]tprecedingcomment\[dq]: \[dq]\[dq],@@ -566,11 +549,11 @@ This should add a new entry to your journal: .IP .EX-$ curl http://127.0.0.1:5000/add -X PUT -H \[aq]Content-Type: application/json\[aq] --data-binary \[at]txn.json+$ curl http://127.0.0.1:5000/add \-X PUT \-H \[aq]Content\-Type: application/json\[aq] \-\-data\-binary \[at]txn.json .EE .SH DEBUG OUTPUT .SS Debug output-You can add \f[CR]--debug[=N]\f[R] to the command line to log debug+You can add \f[CR]\-\-debug[=N]\f[R] to the command line to log debug output. N ranges from 1 (least output, the default) to 9 (maximum output). Typically you would start with 1 and increase until you are seeing@@ -582,10 +565,10 @@ .PD 0 .P .PD-\f[CR]hledger-web --debug=3 2>hledger-web.log\f[R].+\f[CR]hledger\-web \-\-debug=3 2>hledger\-web.log\f[R]. .SH ENVIRONMENT \f[B]LEDGER_FILE\f[R] The main journal file to use when not specified-with \f[CR]-f/--file\f[R].+with \f[CR]\-f/\-\-file\f[R]. Default: \f[CR]$HOME/.hledger.journal\f[R]. .SH BUGS We welcome bug reports in the hledger issue tracker (shortcut:@@ -594,7 +577,7 @@ .PP Some known issues: .PP-Does not work well on small screens, or in text-mode browsers.+Does not work well on small screens, or in text\-mode browsers.   .SH AUTHORS
hledger-web.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           hledger-web-version:        1.32.1+version:        1.32.2 synopsis:       Web user interface for the hledger accounting system description:    A simple web user interface for the hledger accounting system,                 providing a more modern UI than the command-line or terminal interfaces.@@ -136,7 +136,7 @@       Hledger.Web.Import       Hledger.Web.Test   other-modules:-      Hledger.Web.Foundation+      Hledger.Web.App       Hledger.Web.Handler.AddR       Hledger.Web.Handler.EditR       Hledger.Web.Handler.JournalR@@ -151,7 +151,7 @@   hs-source-dirs:       ./   ghc-options: -Wall -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns-  cpp-options: -DVERSION="1.32.1"+  cpp-options: -DVERSION="1.32.2"   build-depends:       Decimal >=0.5.1     , aeson >=1 && <2.3@@ -171,13 +171,13 @@     , extra >=1.6.3     , filepath     , hjsmin-    , hledger >=1.32.1 && <1.33-    , hledger-lib >=1.32.1 && <1.33+    , hledger >=1.32.2 && <1.33+    , hledger-lib >=1.32.2 && <1.33     , hspec     , http-client     , http-conduit     , http-types-    , megaparsec >=7.0.0 && <9.6+    , megaparsec >=7.0.0 && <9.7     , mtl >=2.2.1     , network     , safe >=0.3.19@@ -213,7 +213,7 @@   hs-source-dirs:       app   ghc-options: -Wall -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns-  cpp-options: -DVERSION="1.32.1"+  cpp-options: -DVERSION="1.32.2"   build-depends:       base >=4.14 && <4.19     , hledger-web@@ -233,7 +233,7 @@   hs-source-dirs:       test   ghc-options: -Wall -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns-  cpp-options: -DVERSION="1.32.1"+  cpp-options: -DVERSION="1.32.2"   build-depends:       base >=4.14 && <4.19     , hledger-web
hledger-web.info view
@@ -1,4 +1,4 @@-This is hledger-web.info, produced by makeinfo version 7.0.3 from stdin.+This is hledger-web.info, produced by makeinfo version 7.1 from stdin.  INFO-DIR-SECTION User Applications START-INFO-DIR-ENTRY@@ -16,7 +16,7 @@    'hledger-web [--serve|--serve-api] [OPTS] [ARGS]' 'hledger web -- [--serve|--serve-api] [OPTS] [ARGS]' -   This manual is for hledger's web interface, version 1.32.1.  See also+   This manual is for hledger's web interface, version 1.32.2.  See also the hledger manual for common concepts and file formats.     hledger is a robust, user-friendly, cross-platform set of programs@@ -75,83 +75,72 @@ 1 OPTIONS ********* -Command-line options and arguments may be used to set an initial filter-on the data.  These filter options are not shown in the web UI, but it-will be applied in addition to any search query entered there.--   hledger-web provides the following options:+hledger-web provides the following options:  '--serve'       serve and log requests, don't browse or auto-exit after timeout '--serve-api' -     like -serve, but serve only the JSON web API, without the-     server-side web UI+     like -serve, but serve only the JSON web API, not the web UI+'--allow=view|add|edit'++     set the user's access level for changing data (default: 'add').  It+     also accepts 'sandstorm' for use on that platform (reads+     permissions from the 'X-Sandstorm-Permissions' request header).+'--cors=ORIGIN'++     allow cross-origin requests from the specified origin; setting+     ORIGIN to "*" allows requests from any origin '--host=IPADDR'       listen on this IP address (default: 127.0.0.1)++   By default the server listens on IP address '127.0.0.1', which is+accessible only to requests from the local machine..  You can use+'--host' to listen on a different address configured on the machine, eg+to allow access from other machines.  The special address '0.0.0.0'+causes it to listen on all addresses configured on the machine.+ '--port=PORT'       listen on this TCP port (default: 5000)++   Similarly, you can use '--port' to listen on a TCP port other than+5000.  This is useful if you want to run multiple hledger-web instances+on a machine.+ '--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.+     listen on the given unix socket instead of an IP address and port+     (unix only; implies -serve)++   When '--socket' is used, hledger-web creates and communicates via a+socket file instead of a TCP port.  This can be more secure, respects+unix file permissions, and makes certain use cases easier, such as+running per-user instances behind an nginx reverse proxy.  (Eg:+'proxy_pass http://unix:/tmp/hledger/${remote_user}.socket;'.)+ '--base-url=URL' -     set the base url (default: http://IPADDR:PORT). Note: affects url-     generation but not route parsing.  Can be useful if running behind-     a reverse web proxy that does path rewriting.-'--file-url=URL'+     set the base url (default: http://IPADDR:PORT). -     set the static files url (default: BASEURL/static).  hledger-web-     normally serves static files itself, but if you wanted to serve-     them from another server for efficiency, you would set the url with-     this.-'--allow=view|add|edit'+   You can use '--base-url' to change the protocol, hostname, port and+path that appear in hledger-web's hyperlinks.  This is useful eg when+integrating hledger-web within 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).  Note this affects url generation+but not route parsing. -     set the user's access level for changing data (default: 'add').  It-     also accepts 'sandstorm' for use on that platform (reads-     permissions from the 'X-Sandstorm-Permissions' request header). '--test'       run hledger-web's tests and exit.  hspec test runner args may      follow a -, eg: hledger-web -test - -help -   By default the server listens on IP address 127.0.0.1, accessible-only to local requests.  You can use '--host' to change this, eg '--host-0.0.0.0' to listen on all configured addresses.--   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 variable '$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 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 for better caching or cookie-less serving on high performance-websites.--   hledger-web also supports many of hledger's general options (and the-hledger manual's command line tips also apply here):+   hledger-web also supports many of hledger's general options.  Query+options and arguments may be used to set an initial filter, which+although not shown in the UI, will restrict the data shown, in addition+to any search query entered in the UI.  * Menu: @@ -642,31 +631,31 @@   Tag Table:-Node: Top225-Node: OPTIONS2579-Ref: #options2684-Node: General help options5972-Ref: #general-help-options6122-Node: General input options6404-Ref: #general-input-options6590-Node: General reporting options7292-Ref: #general-reporting-options7457-Node: PERMISSIONS10847-Ref: #permissions10986-Node: EDITING UPLOADING DOWNLOADING12198-Ref: #editing-uploading-downloading12379-Node: RELOADING13213-Ref: #reloading13347-Node: JSON API13780-Ref: #json-api13895-Node: DEBUG OUTPUT19383-Ref: #debug-output19508-Node: Debug output19535-Ref: #debug-output-119636-Node: ENVIRONMENT20053-Ref: #environment20172-Node: BUGS20289-Ref: #bugs20373+Node: Top223+Node: OPTIONS2577+Ref: #options2682+Node: General help options5256+Ref: #general-help-options5406+Node: General input options5688+Ref: #general-input-options5874+Node: General reporting options6576+Ref: #general-reporting-options6741+Node: PERMISSIONS10131+Ref: #permissions10270+Node: EDITING UPLOADING DOWNLOADING11482+Ref: #editing-uploading-downloading11663+Node: RELOADING12497+Ref: #reloading12631+Node: JSON API13064+Ref: #json-api13179+Node: DEBUG OUTPUT18667+Ref: #debug-output18792+Node: Debug output18819+Ref: #debug-output-118920+Node: ENVIRONMENT19337+Ref: #environment19456+Node: BUGS19573+Ref: #bugs19657  End Tag Table 
hledger-web.txt view
@@ -9,7 +9,7 @@        hledger web -- [--serve|--serve-api] [OPTS] [ARGS]  DESCRIPTION-       This  manual  is for hledger's web interface, version 1.32.1.  See also+       This  manual  is for hledger's web interface, version 1.32.2.  See also        the hledger manual for common concepts and file formats.         hledger is a robust, user-friendly, cross-platform set of programs  for@@ -52,79 +52,66 @@        to stdout.  OPTIONS-       Command-line options and arguments may be used to set an initial filter-       on the data.  These filter options are not shown in the web UI, but  it-       will be applied in addition to any search query entered there.-        hledger-web provides the following options:         --serve               serve and log requests, don't browse or auto-exit after timeout         --serve-api-              like  --serve,  but  serve  only  the  JSON web API, without the-              server-side web UI+              like --serve, but serve only the JSON web API, not the web UI +       --allow=view|add|edit+              set the user's access level for changing  data  (default:  add).+              It  also  accepts sandstorm for use on that platform (reads per-+              missions from the X-Sandstorm-Permissions request header).++       --cors=ORIGIN+              allow cross-origin requests from the specified  origin;  setting+              ORIGIN to "*" allows requests from any origin+        --host=IPADDR               listen on this IP address (default: 127.0.0.1) +       By  default the server listens on IP address 127.0.0.1, which is acces-+       sible only to requests from the local machine..  You can use --host  to+       listen  on  a  different address configured on the machine, eg to allow+       access from other machines.  The special address 0.0.0.0 causes  it  to+       listen on all addresses configured on the machine.+        --port=PORT               listen on this TCP port (default: 5000) +       Similarly,  you can use --port to listen on a TCP port other than 5000.+       This is useful if you want to run multiple hledger-web instances  on  a+       machine.+        --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.+              listen  on  the  given  unix socket instead of an IP address and+              port (unix only; implies --serve) -       --base-url=URL-              set the base url (default: http://IPADDR:PORT).   Note:  affects-              url  generation but not route parsing.  Can be useful if running-              behind a reverse web proxy that does path rewriting.+       When --socket is used,  hledger-web  creates  and  communicates  via  a+       socket  file  instead of a TCP port.  This can be more secure, respects+       unix file permissions, and makes certain use cases easier, such as run-+       ning per-user instances behind an nginx reverse proxy.  (Eg: proxy_pass+       http://unix:/tmp/hledger/${remote_user}.socket;.) -       --file-url=URL-              set the static files url (default: BASEURL/static).  hledger-web-              normally serves static files itself, but if you wanted to  serve-              them  from  another server for efficiency, you would set the url-              with this.+       --base-url=URL+              set the base url (default: http://IPADDR:PORT). -       --allow=view|add|edit-              set the user's access level for changing  data  (default:  add).-              It  also  accepts sandstorm for use on that platform (reads per--              missions from the X-Sandstorm-Permissions request header).+       You can use --base-url to change the protocol, hostname, port and  path+       that  appear in hledger-web's hyperlinks.  This is useful eg when inte-+       grating  hledger-web  within  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).  Note this affects url  generation+       but not route parsing. -       --test run hledger-web's tests and exit.  hspec test  runner  args  may+       --test run  hledger-web's  tests  and exit.  hspec test runner args may               follow a --, eg: hledger-web --test -- --help -       By  default the server listens on IP address 127.0.0.1, accessible only-       to local requests.  You can  use  --host  to  change  this,  eg  --host-       0.0.0.0 to listen on all configured addresses.--       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  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 variable $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-       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-       for better caching or cookie-less serving on high performance websites.--       hledger-web also supports many of hledger's general  options  (and  the-       hledger manual's command line tips also apply here):+       hledger-web also supports many of hledger's general options.  Query op-+       tions and arguments may be used to set an  initial  filter,  which  al-+       though  not  shown in the UI, will restrict the data shown, in addition+       to any search query entered in the UI.     General help options        -h --help@@ -146,7 +133,7 @@               $LEDGER_FILE or $HOME/.hledger.journal)         --rules-file=RULESFILE-              Conversion  rules  file  to  use  when  reading  CSV   (default:+              Conversion   rules  file  to  use  when  reading  CSV  (default:               FILE.rules)         --separator=CHAR@@ -165,7 +152,7 @@               assignments)         -s --strict-              do  extra error checking (check that all posted accounts are de-+              do extra error checking (check that all posted accounts are  de-               clared)     General reporting options@@ -193,7 +180,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@@ -201,7 +188,7 @@               fects)         --today=DATE-              override   today's  date  (affects  relative  smart  dates,  for+              override  today's  date  (affects  relative  smart  dates,   for               tests/examples)         -U --unmarked@@ -220,21 +207,21 @@               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/selling amount at transaction time         -V --market-              convert  amounts to their market value in default valuation com-+              convert amounts to their market value in default valuation  com-               modities         -X --exchange=COMM               convert amounts to their market value in commodity COMM         --value-              convert amounts to cost or  market  value,  more  flexibly  than+              convert  amounts  to  cost  or  market value, more flexibly than               -B/-V/-X         --infer-equity@@ -244,32 +231,32 @@               infer costs from conversion equity postings         --infer-market-prices-              use  costs as additional market prices, as if they were P direc-+              use costs as additional market prices, as if they were P  direc-               tives         --forecast-              generate transactions from periodic rules,  between  the  latest-              recorded  txn  and  6 months from today, or during the specified-              PERIOD (= is required).  Auto posting rules will be  applied  to-              these  transactions  as  well.  Also, in hledger-ui make future--              dated transactions visible.+              generate  transactions  from  periodic rules, between the latest+              recorded txn and 6 months from today, or  during  the  specified+              PERIOD  (=  is required).  Auto posting rules will be applied to+              these transactions  as  well.   Also,  in  hledger-ui  make  fu-+              ture-dated transactions visible. -       --auto generate extra postings by applying auto posting  rules  to  all+       --auto generate  extra  postings  by applying auto posting rules to all               txns (not just forecast txns)         --verbose-tags-              add  visible tags indicating transactions or postings which have+              add visible tags indicating transactions or postings which  have               been generated/modified         --commodity-style-              Override the commodity style in the  output  for  the  specified+              Override  the  commodity  style  in the output for the specified               commodity.  For example 'EUR1.000,00'.         --color=WHEN (or --colour=WHEN)-              Should  color-supporting  commands  use ANSI color codes in text-              output.  'auto' (default): whenever stdout seems to be a  color--              supporting  terminal.  'always' or 'yes': always, useful eg when-              piping output into  'less  -R'.   'never'  or  'no':  never.   A+              Should color-supporting commands use ANSI color  codes  in  text+              output.   'auto'  (default):  whenever  stdout  seems  to  be  a+              color-supporting terminal.  'always' or 'yes': always, useful eg+              when piping output into 'less -R'.  'never' or 'no':  never.   A               NO_COLOR environment variable overrides this.         --pretty[=WHEN]@@ -564,4 +551,4 @@ SEE ALSO        hledger(1), hledger-ui(1), hledger-web(1), ledger(1) -hledger-web-1.32.1               December 2023                  HLEDGER-WEB(1)+hledger-web-1.32.2               December 2023                  HLEDGER-WEB(1)