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
		
			
				
	
	
		
			227 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Haskell
		
	
	
	
	
	
			
		
		
	
	
			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
 |