hledger-web 0.22.8 → 0.23
raw patch · 8 files changed
+201/−167 lines, 8 filesdep ~hledgerdep ~hledger-libPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: hledger, hledger-lib
API changes (from Hackage documentation)
+ Settings: extraStaticRoot :: Extra -> Maybe Text
- Settings: Extra :: Text -> Maybe Text -> Extra
+ Settings: Extra :: Text -> Maybe Text -> Maybe Text -> Extra
- Settings: staticRoot :: AppConfig DefaultEnv x -> Text
+ Settings: staticRoot :: AppConfig DefaultEnv Extra -> Text
Files
- CHANGES +9/−1
- Foundation.hs +6/−6
- Handler/Common.hs +1/−1
- Hledger/Web/Main.hs +3/−1
- Hledger/Web/Options.hs +8/−3
- Settings.hs +8/−4
- hledger-web.cabal +9/−11
- static/jquery.url.js +157/−140
CHANGES view
@@ -1,3 +1,11 @@+See also the hledger changelog.++0.23 (2014/5/1)++- The --static-root flag has been renamed to --file-url.+- hledger-web now builds with Cabal's default -O, not -O2,+ so may be a little quicker/less memory-hungry to install.+ 0.22.8 (2014/4/29) - allow shakespeare 2.* (#179)@@ -33,4 +41,4 @@ 0.22.1 (2014/1/6) and older -See http://hledger.org/release-notes or doc/CHANGES.md.+See http://hledger.org/release-notes or doc/RELNOTES.md.
Foundation.hs view
@@ -19,7 +19,7 @@ -- import qualified Settings import Settings.Development (development) import Settings.StaticFiles-import Settings (widgetFile, Extra (..))+import Settings (staticRoot, widgetFile, Extra (..)) #ifndef DEVELOPMENT import Settings (staticDir) import Text.Jasmine (minifym)@@ -117,11 +117,11 @@ hamletToRepHtml $(hamletFile "templates/default-layout-wrapper.hamlet") - -- -- This is done to provide an optimization for serving static files from- -- -- a separate domain. Please see the staticRoot setting in Settings.hs- -- urlRenderOverride y (StaticR s) =- -- Just $ uncurry (joinPath y (Settings.staticRoot $ settings y)) $ renderRoute s- -- urlRenderOverride _ _ = Nothing+ -- This is done to provide an optimization for serving static files from+ -- a separate domain. Please see the staticRoot setting in Settings.hs+ urlRenderOverride y (StaticR s) =+ Just $ uncurry (joinPath y (Settings.staticRoot $ settings y)) $ renderRoute s+ urlRenderOverride _ _ = Nothing #ifndef DEVELOPMENT -- This function creates static content files in the static folder
Handler/Common.hs view
@@ -330,7 +330,7 @@ allaccts = isNothing inacctmatcher items = items' -- maybe items' (\m -> filter (matchesAccount m . \(a,_,_,_)->a) items') showacctmatcher itemAsHtml :: ViewData -> BalanceReportItem -> HtmlUrl AppRoute- itemAsHtml _ (acct, adisplay, aindent, abal) = [hamlet|+ itemAsHtml _ ((acct, adisplay, aindent), abal) = [hamlet| <tr.item.#{inacctclass}> <td.account.#{depthclass}> \#{indent}
Hledger/Web/Main.hs view
@@ -21,6 +21,7 @@ import Network.Wai.Handler.Launch (runUrlPort) -- import Prelude hiding (putStrLn)+import Control.Applicative ((<$>)) import Control.Monad (when) import Data.Text (pack) import System.Exit (exitSuccess)@@ -60,12 +61,13 @@ let j' = filterJournalTransactions (queryFromOpts d $ reportopts_ $ cliopts_ opts) j p = port_ opts u = base_url_ opts+ staticRoot = pack <$> static_root_ opts _ <- printf "Starting web app on port %d with base url %s\n" p u app <- makeApplication opts j' AppConfig{appEnv = Development ,appPort = p ,appRoot = pack u ,appHost = fromString "*4"- ,appExtra = Extra "" Nothing+ ,appExtra = Extra "" Nothing staticRoot } if server_ opts then do
Hledger/Web/Options.hs view
@@ -1,6 +1,7 @@ module Hledger.Web.Options where import Prelude+import Control.Applicative ((<$>)) import Data.Maybe import System.Console.CmdArgs import System.Console.CmdArgs.Explicit@@ -23,9 +24,10 @@ webflags :: [Flag [([Char], [Char])]] webflags = [- flagNone ["server"] (setboolopt "server") ("log requests, don't auto-exit")- ,flagReq ["base-url"] (\s opts -> Right $ setopt "base-url" s opts) "URL" ("set the base url (default: "++defbaseurlexample++")")- ,flagReq ["port"] (\s opts -> Right $ setopt "port" s opts) "PORT" ("listen on this tcp port (default: "++show defport++")")+ flagNone ["server"] (setboolopt "server") ("log requests, and don't browse or auto-exit")+ ,flagReq ["port"] (\s opts -> Right $ setopt "port" s opts) "PORT" ("set the tcp port (default: "++show defport++")")+ ,flagReq ["base-url"] (\s opts -> Right $ setopt "base-url" s opts) "BASEURL" ("set the base url (default: "++defbaseurlexample++")")+ ,flagReq ["file-url"] (\s opts -> Right $ setopt "file-url" s opts) "FILEURL" ("set the static files url (default: BASEURL/static)") ] webmode :: Mode [([Char], [Char])]@@ -47,6 +49,7 @@ server_ :: Bool ,base_url_ :: String ,port_ :: Int+ ,static_root_ :: Maybe String ,cliopts_ :: CliOpts } deriving (Show) @@ -56,6 +59,7 @@ def def def+ def -- instance Default WebOpts where def = defwebopts @@ -67,6 +71,7 @@ port_ = p ,server_ = boolopt "server" rawopts ,base_url_ = maybe (defbaseurl p) stripTrailingSlash $ maybestringopt "base-url" rawopts+ ,static_root_ = stripTrailingSlash <$> maybestringopt "static-root" rawopts ,cliopts_ = cliopts } where
Settings.hs view
@@ -54,8 +54,10 @@ -- have to make a corresponding change here. -- -- To see how this value is used, see urlRenderOverride in Foundation.hs-staticRoot :: AppConfig DefaultEnv x -> Text-staticRoot conf = [st|#{appRoot conf}/static|]+staticRoot :: AppConfig DefaultEnv Extra -> Text+staticRoot conf = case extraStaticRoot $ appExtra conf of+ Just root -> root+ Nothing -> [st|#{appRoot conf}/static|] -- | Settings for 'widgetFile', such as which template languages to support and -- default Hamlet settings.@@ -75,11 +77,13 @@ widgetFileSettings data Extra = Extra- { extraCopyright :: Text- , extraAnalytics :: Maybe Text -- ^ Google Analytics+ { extraCopyright :: Text+ , extraAnalytics :: Maybe Text -- ^ Google Analytics+ , extraStaticRoot :: Maybe Text } deriving Show parseExtra :: DefaultEnv -> Object -> Parser Extra parseExtra _ o = Extra <$> o .: "copyright" <*> o .:? "analytics"+ <*> o .:? "staticRoot"
hledger-web.cabal view
@@ -1,6 +1,6 @@ name: hledger-web-version: 0.22.8-stability: beta+version: 0.23+stability: stable category: Finance synopsis: A web interface for the hledger accounting tool. description: @@ -17,7 +17,7 @@ maintainer: Simon Michael <simon@joyful.com> homepage: http://hledger.org bug-reports: http://hledger.org/bugs-tested-with: GHC==7.6.3+tested-with: GHC==7.4.2, GHC==7.6.3, GHC==7.8.2 cabal-version: >= 1.8 build-type: Simple extra-tmp-files:@@ -71,7 +71,7 @@ Default: False library- cpp-options: -DVERSION="0.22.8"+ cpp-options: -DVERSION="0.23" if flag(dev) || flag(library-only) cpp-options: -DDEVELOPMENT @@ -114,8 +114,8 @@ Hledger.Web.Options -- Setup -- stops yesod devel complaining, requires build-depends: Cabal build-depends:- hledger == 0.22.2- , hledger-lib == 0.22.2+ hledger == 0.23+ , hledger-lib == 0.23 , base >= 4 && < 5 , bytestring , clientsession@@ -172,7 +172,7 @@ if flag(library-only) Buildable: False - cpp-options: -DVERSION="0.22.8"+ cpp-options: -DVERSION="0.23" if flag(dev) cpp-options: -DDEVELOPMENT @@ -181,8 +181,6 @@ ghc-options: -threaded if flag(dev) ghc-options: -O0- else- ghc-options: -O2 extensions: CPP@@ -216,8 +214,8 @@ Hledger.Web.Options build-depends:- hledger == 0.22.2- , hledger-lib == 0.22.2+ hledger == 0.23+ , hledger-lib == 0.23 , base >= 4 && < 5 , bytestring , clientsession
static/jquery.url.js view
@@ -1,164 +1,181 @@-// JQuery URL Parser plugin - https://github.com/allmarkedup/jQuery-URL-Parser+// JQuery URL Parser // Written by Mark Perkins, mark@allmarkedup.com // License: http://unlicense.org/ (i.e. do what you want with it!) -;(function($, undefined) {- - var tag2attr = {- a : 'href',- img : 'src',- form : 'action',- base : 'href',- script : 'src',- iframe : 'src',- link : 'href'- },- - key = ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","fragment"], // keys available to query+jQuery.url = function()+{+ var segments = {}; - aliases = { "anchor" : "fragment" }, // aliases for backwards compatability-- parser = {- strict : /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, //less intuitive, more accurate to the specs- loose : /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // more intuitive, fails on relative paths and deviates from specs- },+ var parsed = {}; - querystring_parser = /(?:^|&|;)([^&=;]*)=?([^&;]*)/g, // supports both ampersand and semicolon-delimted query string key/value pairs+ /**+ * Options object. Only the URI and strictMode values can be changed via the setters below.+ */+ var options = { - fragment_parser = /(?:^|&|;)([^&=;]*)=?([^&;]*)/g; // supports both ampersand and semicolon-delimted fragment key/value pairs+ url : window.location, // default URI is the page in which the script is running+ + strictMode: false, // 'loose' parsing by default - function parseUri( url, strictMode )- {- var str = decodeURI( url ),- res = parser[ strictMode || false ? "strict" : "loose" ].exec( str ),- uri = { attr : {}, param : {}, seg : {} },- i = 14;+ key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], // keys available to query - while ( i-- )- {- uri.attr[ key[i] ] = res[i] || "";- }+ q: {+ name: "queryKey",+ parser: /(?:^|&|;)([^&=;]*)=?([^&;]*)/g+ }, - // build query and fragment parameters+ parser: {+ strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, //less intuitive, more accurate to the specs+ loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // more intuitive, fails on relative paths and deviates from specs+ } - uri.param['query'] = {};- uri.param['fragment'] = {};+ };+ + /**+ * Deals with the parsing of the URI according to the regex above.+ * Written by Steven Levithan - see credits at top.+ */ + var parseUri = function()+ {+ str = decodeURI( options.url ); - uri.attr['query'].replace( querystring_parser, function ( $0, $1, $2 ){- if ($1)- {- uri.param['query'][$1] = $2;+ var m = options.parser[ options.strictMode ? "strict" : "loose" ].exec( str );+ var uri = {};+ var i = 14;++ while ( i-- ) {+ uri[ options.key[i] ] = m[i] || "";+ }++ uri[ options.q.name ] = {};+ uri[ options.key[12] ].replace( options.q.parser, function ( $0, $1, $2 ) {+ if ($1) {+ uri[options.q.name][$1] = $2; } });- - uri.attr['fragment'].replace( fragment_parser, function ( $0, $1, $2 ){- if ($1)++ return uri;+ };++ /**+ * Returns the value of the passed in key from the parsed URI.+ * + * @param string key The key whose value is required+ */ + var key = function( key )+ {+ if ( jQuery.isEmptyObject(parsed) )+ {+ setUp(); // if the URI has not been parsed yet then do this first... + } + if ( key == "base" )+ {+ if ( parsed.port !== null && parsed.port !== "" ) {- uri.param['fragment'][$1] = $2;+ return parsed.protocol+"://"+parsed.host+":"+parsed.port+"/"; }- });- - // split path and fragement into segments- - uri.seg['path'] = uri.attr.path.replace(/^\/+|\/+$/g,'').split('/');- - uri.seg['fragment'] = uri.attr.fragment.replace(/^\/+|\/+$/g,'').split('/');- - // compile a 'base' domain attribute- - uri.attr['base'] = uri.attr.host ? uri.attr.protocol+"://"+uri.attr.host + (uri.attr.port ? ":"+uri.attr.port : '') : '';- - return uri;+ else+ {+ return parsed.protocol+"://"+parsed.host+"/";+ }+ }+ + return ( parsed[key] === "" ) ? null : parsed[key]; }; - function getAttrName( elm )+ /**+ * Returns the value of the required query string parameter.+ * + * @param string item The parameter whose value is required+ */ + var param = function( item ) {- var tn = elm.tagName;- if ( tn !== undefined ) return tag2attr[tn.toLowerCase()];- return tn;- }- - $.fn.url = function( strictMode )+ if ( jQuery.isEmptyObject(parsed) )+ {+ setUp(); // if the URI has not been parsed yet then do this first... + }+ if ( item === undefined )+ {+ return parsed.queryKey;+ }+ else+ {+ return ( parsed.queryKey[item] === null ) ? null : parsed.queryKey[item];+ }+ };++ /**+ * 'Constructor' (not really!) function.+ * Called whenever the URI changes to kick off re-parsing of the URI and splitting it up into segments. + */ + var setUp = function() {- var url = '';- - if ( this.length )- {- url = $(this).attr( getAttrName(this[0]) ) || '';- }- - return $.url({ url : url, strict : strictMode });+ parsed = parseUri();+ + getSegments(); }; - $.url = function( opts )+ /**+ * Splits up the body of the URI into segments (i.e. sections delimited by '/')+ */+ var getSegments = function() {- var url = '',- strict = false;-- if ( typeof opts === 'string' )- {- url = opts;- }- else- {- opts = opts || {};- strict = opts.strict || strict;- url = opts.url === undefined ? window.location.toString() : opts.url;- }- - return {- - data : parseUri(url, strict),- - // get various attributes from the URI- attr : function( attr )- {- attr = aliases[attr] || attr;- return attr !== undefined ? this.data.attr[attr] : this.data.attr;- },- - // return query string parameters- param : function( param )- {- return param !== undefined ? this.data.param.query[param] : this.data.param.query;- },- - // return fragment parameters- fparam : function( param )- {- return param !== undefined ? this.data.param.fragment[param] : this.data.param.fragment;- },- - // return path segments- segment : function( seg )- {- if ( seg === undefined )- {- return this.data.seg.path; - }- else- {- seg = seg < 0 ? this.data.seg.path.length + seg : seg - 1; // negative segments count from the end- return this.data.seg.path[seg]; - }- },- - // return fragment segments- fsegment : function( seg )- {- if ( seg === undefined )- {- return this.data.seg.fragment; - }- else- {- seg = seg < 0 ? this.data.seg.fragment.length + seg : seg - 1; // negative segments count from the end- return this.data.seg.fragment[seg]; - }- }- - };- + var p = parsed.path;+ segments = []; // clear out segments array+ segments = parsed.path.length == 1 ? {} : ( p.charAt( p.length - 1 ) == "/" ? p.substring( 1, p.length - 1 ) : path = p.substring( 1 ) ).split("/"); }; -})(jQuery);+ return {+ + /**+ * Sets the parsing mode - either strict or loose. Set to loose by default.+ *+ * @param string mode The mode to set the parser to. Anything apart from a value of 'strict' will set it to loose!+ */+ setMode : function( mode )+ {+ options.strictMode = mode == "strict" ? true : false;+ return this;+ },+ + /**+ * Sets URI to parse if you don't want to to parse the current page's URI.+ * Calling the function with no value for newUri resets it to the current page's URI.+ *+ * @param string newUri The URI to parse.+ */ + setUrl : function( newUri )+ {+ options.url = newUri === undefined ? window.location : newUri;+ setUp();+ return this;+ }, + + /**+ * Returns the value of the specified URI segment. Segments are numbered from 1 to the number of segments.+ * For example the URI http://test.com/about/company/ segment(1) would return 'about'.+ *+ * If no integer is passed into the function it returns the number of segments in the URI.+ *+ * @param int pos The position of the segment to return. Can be empty.+ */ + segment : function( pos )+ {+ if ( jQuery.isEmptyObject(parsed) )+ {+ setUp(); // if the URI has not been parsed yet then do this first... + } + if ( pos === undefined )+ {+ return segments.length;+ }+ return ( segments[pos] === "" || segments[pos] === undefined ) ? null : segments[pos];+ },+ + attr : key, // provides public access to private 'key' function - see above+ + param : param // provides public access to private 'param' function - see above+ + };+ +}();