hledger/hledger-web/Hledger/Web/WebOptions.hs
Simon Michael 1ef8f329c6 lib: simpler more robust version strings, with date/without patchlevel
We no longer rely on git tags and git describe output, since it's
hard to reliably select the correct tag eg for minor releases.
We might use them again in future for dev builds, but it requires
adding git describe --match support to githash. For now,

* Program name, OS and architecture are always shown.
* The package version is always shown.
* If there is git info at build time, the latest commit hash and commit date are shown.

Example outputs:
A homebrew binary, not built in git repo: hledger-ui 1.24, mac-aarch64
A CI release build, built in git repo:    hledger 1.24.1-g455b35293-20211210, mac-x86_64

API changes:

* new type synonyms ProgramName, PackageVersion, VersionString
* versionStringForProgname -> versionString with extra argument
* versionStringFor -> versionStringWith with extra argument
2021-12-10 12:42:40 -10:00

227 lines
6.7 KiB
Haskell

{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
module Hledger.Web.WebOptions where
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BC
import Data.ByteString.UTF8 (fromString)
import Data.CaseInsensitive (CI, mk)
import Data.Default (Default(def))
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import Data.Text (Text)
import System.Environment (getArgs)
import Network.Wai as WAI
import Network.Wai.Middleware.Cors
import Hledger.Cli hiding (packageversion, progname, prognameandversion)
import Hledger.Web.Settings (defhost, defport, defbaseurl)
-- cf Hledger.Cli.Version
packageversion :: PackageVersion
packageversion =
#ifdef VERSION
VERSION
#else
""
#endif
progname :: ProgramName
progname = "hledger-web"
prognameandversion :: VersionString
prognameandversion = versionString progname packageversion
webflags :: [Flag RawOpts]
webflags =
[ flagNone
["serve", "server"]
(setboolopt "serve")
"serve and log requests, don't browse or auto-exit"
, flagNone
["serve-api"]
(setboolopt "serve-api")
"like --serve, but serve only the JSON web API, without the server-side web UI"
, flagReq
["cors"]
(\s opts -> Right $ setopt "cors" s opts)
"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"
("listen on this IP address (default: " ++ defhost ++ ")")
, flagReq
["port"]
(\s opts -> Right $ setopt "port" s opts)
"PORT"
("listen on this TCP port (default: " ++ show defport ++ ")")
, 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)"
, flagReq
["capabilities"]
(\s opts -> Right $ setopt "capabilities" s opts)
"CAP[,CAP..]"
"enable the view, add, and/or manage capabilities (default: view,add)"
, flagReq
["capabilities-header"]
(\s opts -> Right $ setopt "capabilities-header" s opts)
"HTTPHEADER"
"read capabilities to enable from a HTTP header, like X-Sandstorm-Permissions (default: disabled)"
, flagNone
["test"]
(setboolopt "test")
"run hledger-web's tests and exit. hspec test runner args may follow a --, eg: hledger-web --test -- --help"
]
webmode :: Mode RawOpts
webmode =
(mode
"hledger-web"
(setopt "command" "web" def)
"start serving the hledger web interface"
(argsFlag "[PATTERNS]")
[])
{ modeGroupFlags =
Group
{ groupUnnamed = webflags
, groupHidden =
hiddenflags
-- ++
-- [ flagNone
-- ["binary-filename"]
-- (setboolopt "binary-filename")
-- "show the download filename for this executable, and exit"
-- ]
, groupNamed = [generalflagsgroup1]
}
, modeHelpSuffix = []
}
-- hledger-web options, used in hledger-web and above
data WebOpts = WebOpts
{ serve_ :: Bool
, serve_api_ :: Bool
, cors_ :: Maybe String
, host_ :: String
, port_ :: Int
, base_url_ :: String
, file_url_ :: Maybe String
, capabilities_ :: [Capability]
, capabilitiesHeader_ :: Maybe (CI ByteString)
, cliopts_ :: CliOpts
, socket_ :: Maybe String
} deriving (Show)
defwebopts :: WebOpts
defwebopts = WebOpts
{ serve_ = False
, serve_api_ = False
, cors_ = Nothing
, host_ = ""
, port_ = def
, base_url_ = ""
, file_url_ = Nothing
, capabilities_ = [CapView, CapAdd]
, capabilitiesHeader_ = Nothing
, cliopts_ = def
, socket_ = Nothing
}
instance Default WebOpts where def = defwebopts
rawOptsToWebOpts :: RawOpts -> IO WebOpts
rawOptsToWebOpts rawopts =
checkWebOpts <$> do
cliopts <- rawOptsToCliOpts rawopts
let h = fromMaybe defhost $ maybestringopt "host" rawopts
p = fromMaybe defport $ maybeposintopt "port" rawopts
b =
maybe (defbaseurl h p) stripTrailingSlash $
maybestringopt "base-url" rawopts
caps' = T.splitOn "," . T.pack =<< listofstringopt "capabilities" rawopts
caps = case traverse capabilityFromText caps' of
Left e -> error' ("Unknown capability: " ++ T.unpack e) -- PARTIAL:
Right [] -> [CapView, CapAdd]
Right xs -> xs
sock = stripTrailingSlash <$> maybestringopt "socket" rawopts
return
defwebopts
{ serve_ = case sock of
Just _ -> True
Nothing -> boolopt "serve" rawopts
, serve_api_ = boolopt "serve-api" rawopts
, cors_ = maybestringopt "cors" rawopts
, host_ = h
, port_ = p
, base_url_ = b
, file_url_ = stripTrailingSlash <$> maybestringopt "file-url" rawopts
, capabilities_ = caps
, capabilitiesHeader_ = mk . BC.pack <$> maybestringopt "capabilities-header" rawopts
, cliopts_ = cliopts
, socket_ = sock
}
where
stripTrailingSlash = reverse . dropWhile (== '/') . reverse -- yesod don't like it
checkWebOpts :: WebOpts -> WebOpts
checkWebOpts = id
getHledgerWebOpts :: IO WebOpts
getHledgerWebOpts = do
args <- fmap replaceNumericFlags . expandArgsAt =<< getArgs
rawOptsToWebOpts . either usageError id $ process webmode args
data Capability
= CapView
| CapAdd
| CapManage
deriving (Eq, Ord, Bounded, Enum, Show)
capabilityFromText :: Text -> Either Text Capability
capabilityFromText "view" = Right CapView
capabilityFromText "add" = Right CapAdd
capabilityFromText "manage" = Right CapManage
capabilityFromText x = Left x
capabilityFromBS :: ByteString -> Either ByteString Capability
capabilityFromBS "view" = Right CapView
capabilityFromBS "add" = Right CapAdd
capabilityFromBS "manage" = Right CapManage
capabilityFromBS x = Left x
simplePolicyWithOrigin :: Origin -> CorsResourcePolicy
simplePolicyWithOrigin origin =
simpleCorsResourcePolicy { corsOrigins = Just ([origin], False) }
corsPolicyFromString :: String -> WAI.Middleware
corsPolicyFromString origin =
let
policy = case origin of
"*" -> simpleCorsResourcePolicy
url -> simplePolicyWithOrigin $ fromString url
in
cors (const $ Just policy)
corsPolicy :: WebOpts -> (Application -> Application)
corsPolicy opts =
maybe id corsPolicyFromString $ cors_ opts