packages feed

hledger 0.12.1 → 0.13

raw patch · 40 files changed

+1544/−7621 lines, 40 filesdep +haskelinedep −Chartdep −colourdep −convertible-textdep ~hledger-libdep ~processPVP ok

version bump matches the API change (PVP)

Dependencies added: haskeline

Dependencies removed: Chart, colour, convertible-text, data-object, failure, gtk2hs-buildtools, hamlet, io-storage, vty, yesod

Dependency ranges changed: hledger-lib, process

API changes (from Hackage documentation)

- Hledger.Cli.Commands.Add: add :: [Opt] -> [String] -> Journal -> IO ()
- Hledger.Cli.Commands.Add: appendToJournalFile :: Journal -> String -> IO ()
- Hledger.Cli.Commands.Add: askFor :: String -> Maybe String -> Maybe (String -> Bool) -> IO String
- Hledger.Cli.Commands.Add: compareDescriptions :: [Char] -> [Char] -> Double
- Hledger.Cli.Commands.Add: compareStrings :: String -> String -> Double
- Hledger.Cli.Commands.Add: getAndAddTransactions :: Journal -> [Opt] -> [String] -> Day -> IO ()
- Hledger.Cli.Commands.Add: getPostings :: (AccountName -> Bool) -> Maybe [Posting] -> [Posting] -> IO [Posting]
- Hledger.Cli.Commands.Add: getTransaction :: Journal -> [Opt] -> [String] -> Day -> IO (Transaction, Day)
- Hledger.Cli.Commands.Add: journalAddTransaction :: Journal -> [Opt] -> Transaction -> IO Journal
- Hledger.Cli.Commands.Add: registerFromString :: String -> IO String
- Hledger.Cli.Commands.Add: transactionsSimilarTo :: Journal -> [String] -> String -> [(Double, Transaction)]
- Hledger.Cli.Commands.All: tests_Hledger_Commands :: Test
- Hledger.Cli.Commands.Balance: balance :: [Opt] -> [String] -> Journal -> IO ()
- Hledger.Cli.Commands.Balance: balanceReport :: [Opt] -> FilterSpec -> Journal -> BalanceReport
- Hledger.Cli.Commands.Balance: balanceReportAsText :: [Opt] -> BalanceReport -> String
- Hledger.Cli.Commands.Balance: type BalanceReport = ([BalanceReportItem], MixedAmount)
- Hledger.Cli.Commands.Balance: type BalanceReportItem = (AccountName, AccountName, Int, MixedAmount)
- Hledger.Cli.Commands.Convert: CsvRules :: Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe String -> AccountName -> [AccountRule] -> CsvRules
- Hledger.Cli.Commands.Convert: accountRules :: CsvRules -> [AccountRule]
- Hledger.Cli.Commands.Convert: accountrule :: GenParser Char CsvRules AccountRule
- Hledger.Cli.Commands.Convert: amountField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Commands.Convert: baseAccount :: CsvRules -> AccountName
- Hledger.Cli.Commands.Convert: baseCurrency :: CsvRules -> Maybe String
- Hledger.Cli.Commands.Convert: codeField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Commands.Convert: convert :: [Opt] -> [String] -> Journal -> IO ()
- Hledger.Cli.Commands.Convert: csvrulesfile :: GenParser Char CsvRules CsvRules
- Hledger.Cli.Commands.Convert: currencyField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Commands.Convert: data CsvRules
- Hledger.Cli.Commands.Convert: dateField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Commands.Convert: definitions :: GenParser Char CsvRules ()
- Hledger.Cli.Commands.Convert: descriptionField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Commands.Convert: identify :: [AccountRule] -> String -> String -> (String, String)
- Hledger.Cli.Commands.Convert: initialRulesFileContent :: String
- Hledger.Cli.Commands.Convert: instance Eq CsvRules
- Hledger.Cli.Commands.Convert: instance Show CsvRules
- Hledger.Cli.Commands.Convert: maxFieldIndex :: CsvRules -> Int
- Hledger.Cli.Commands.Convert: normaliseDate :: String -> String
- Hledger.Cli.Commands.Convert: parseCsvRules :: FilePath -> String -> Either ParseError CsvRules
- Hledger.Cli.Commands.Convert: parseCsvRulesFile :: FilePath -> IO (Either ParseError CsvRules)
- Hledger.Cli.Commands.Convert: printTxn :: Bool -> CsvRules -> CsvRecord -> IO ()
- Hledger.Cli.Commands.Convert: rulesFileFor :: FilePath -> FilePath
- Hledger.Cli.Commands.Convert: statusField :: CsvRules -> Maybe FieldPosition
- Hledger.Cli.Commands.Convert: transactionFromCsvRecord :: CsvRules -> CsvRecord -> Transaction
- Hledger.Cli.Commands.Convert: type AccountRule = ([(String, Maybe String)], AccountName)
- Hledger.Cli.Commands.Convert: type CsvRecord = [String]
- Hledger.Cli.Commands.Convert: type FieldPosition = Int
- Hledger.Cli.Commands.Histogram: histogram :: [Opt] -> [String] -> Journal -> IO ()
- Hledger.Cli.Commands.Histogram: showHistogram :: [Opt] -> FilterSpec -> Journal -> String
- Hledger.Cli.Commands.Print: journalReport :: [Opt] -> FilterSpec -> Journal -> JournalReport
- Hledger.Cli.Commands.Print: print' :: [Opt] -> [String] -> Journal -> IO ()
- Hledger.Cli.Commands.Print: showTransactions :: FilterSpec -> Journal -> String
- Hledger.Cli.Commands.Print: type JournalReport = [JournalReportItem]
- Hledger.Cli.Commands.Print: type JournalReportItem = Transaction
- Hledger.Cli.Commands.Register: register :: [Opt] -> [String] -> Journal -> IO ()
- Hledger.Cli.Commands.Register: registerReport :: [Opt] -> FilterSpec -> Journal -> RegisterReport
- Hledger.Cli.Commands.Register: registerReportAsText :: [Opt] -> RegisterReport -> String
- Hledger.Cli.Commands.Register: showPostingWithBalanceForVty :: Bool -> Posting -> MixedAmount -> String
- Hledger.Cli.Commands.Register: tests_Register :: Test
- Hledger.Cli.Commands.Register: type RegisterReport = [RegisterReportItem]
- Hledger.Cli.Commands.Register: type RegisterReportItem = (Maybe (Day, String), Posting, MixedAmount)
- Hledger.Cli.Commands.Stats: showLedgerStats :: [Opt] -> [String] -> Ledger -> Day -> DateSpan -> String
- Hledger.Cli.Commands.Stats: stats :: [Opt] -> [String] -> Journal -> IO ()
- Hledger.Cli.Options: HelpAll :: Opt
- Hledger.Cli.Options: HelpOptions :: Opt
- Hledger.Cli.Options: options :: [OptDescr Opt]
- Hledger.Cli.Options: parseArguments :: IO ([Opt], String, [String])
- Hledger.Cli.Utils: journalFileModificationTime :: Journal -> IO ClockTime
+ Hledger.Cli.Add: accountCompletion :: CompletionCache -> CompletionFunc IO
+ Hledger.Cli.Add: add :: [Opt] -> [String] -> Journal -> IO ()
+ Hledger.Cli.Add: appendToJournalFile :: FilePath -> String -> IO ()
+ Hledger.Cli.Add: askFor :: String -> Maybe String -> Maybe (String -> Bool) -> InputT IO String
+ Hledger.Cli.Add: compareDescriptions :: [Char] -> [Char] -> Double
+ Hledger.Cli.Add: compareStrings :: String -> String -> Double
+ Hledger.Cli.Add: completionCache :: Journal -> CompletionCache
+ Hledger.Cli.Add: getAndAddTransactions :: Journal -> [Opt] -> [String] -> Day -> InputT IO ()
+ Hledger.Cli.Add: getPostings :: JournalContext -> (AccountName -> Bool) -> Maybe [Posting] -> [Posting] -> InputT IO [Posting]
+ Hledger.Cli.Add: getTransaction :: Journal -> [Opt] -> [String] -> Day -> InputT IO (Transaction, Day)
+ Hledger.Cli.Add: journalAddTransaction :: Journal -> [Opt] -> Transaction -> IO Journal
+ Hledger.Cli.Add: registerFromString :: String -> IO String
+ Hledger.Cli.Add: runInteraction :: Journal -> InputT IO a -> IO a
+ Hledger.Cli.Add: transactionsSimilarTo :: Journal -> [String] -> String -> [(Double, Transaction)]
+ Hledger.Cli.Add: type CompletionCache = [AccountName]
+ Hledger.Cli.Balance: balance :: [Opt] -> [String] -> Journal -> IO ()
+ Hledger.Cli.Balance: balanceReport :: [Opt] -> FilterSpec -> Journal -> BalanceReport
+ Hledger.Cli.Balance: balanceReportAsText :: [Opt] -> BalanceReport -> String
+ Hledger.Cli.Balance: type BalanceReport = ([BalanceReportItem], MixedAmount)
+ Hledger.Cli.Balance: type BalanceReportItem = (AccountName, AccountName, Int, MixedAmount)
+ Hledger.Cli.Commands: tests_Hledger_Commands :: Test
+ Hledger.Cli.Convert: CsvRules :: Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe FieldPosition -> Maybe String -> AccountName -> [AccountRule] -> CsvRules
+ Hledger.Cli.Convert: accountRules :: CsvRules -> [AccountRule]
+ Hledger.Cli.Convert: accountrule :: GenParser Char CsvRules AccountRule
+ Hledger.Cli.Convert: amountField :: CsvRules -> Maybe FieldPosition
+ Hledger.Cli.Convert: baseAccount :: CsvRules -> AccountName
+ Hledger.Cli.Convert: baseCurrency :: CsvRules -> Maybe String
+ Hledger.Cli.Convert: codeField :: CsvRules -> Maybe FieldPosition
+ Hledger.Cli.Convert: convert :: [Opt] -> [String] -> Journal -> IO ()
+ Hledger.Cli.Convert: csvrulesfile :: GenParser Char CsvRules CsvRules
+ Hledger.Cli.Convert: currencyField :: CsvRules -> Maybe FieldPosition
+ Hledger.Cli.Convert: data CsvRules
+ Hledger.Cli.Convert: dateField :: CsvRules -> Maybe FieldPosition
+ Hledger.Cli.Convert: definitions :: GenParser Char CsvRules ()
+ Hledger.Cli.Convert: descriptionField :: CsvRules -> Maybe FieldPosition
+ Hledger.Cli.Convert: identify :: [AccountRule] -> String -> String -> (String, String)
+ Hledger.Cli.Convert: initialRulesFileContent :: String
+ Hledger.Cli.Convert: instance Eq CsvRules
+ Hledger.Cli.Convert: instance Show CsvRules
+ Hledger.Cli.Convert: maxFieldIndex :: CsvRules -> Int
+ Hledger.Cli.Convert: normaliseDate :: String -> String
+ Hledger.Cli.Convert: parseCsvRules :: FilePath -> String -> Either ParseError CsvRules
+ Hledger.Cli.Convert: parseCsvRulesFile :: FilePath -> IO (Either ParseError CsvRules)
+ Hledger.Cli.Convert: printTxn :: Bool -> CsvRules -> CsvRecord -> IO ()
+ Hledger.Cli.Convert: rulesFileFor :: FilePath -> FilePath
+ Hledger.Cli.Convert: statusField :: CsvRules -> Maybe FieldPosition
+ Hledger.Cli.Convert: transactionFromCsvRecord :: CsvRules -> CsvRecord -> Transaction
+ Hledger.Cli.Convert: type AccountRule = ([(String, Maybe String)], AccountName)
+ Hledger.Cli.Convert: type CsvRecord = [String]
+ Hledger.Cli.Convert: type FieldPosition = Int
+ Hledger.Cli.Histogram: histogram :: [Opt] -> [String] -> Journal -> IO ()
+ Hledger.Cli.Histogram: showHistogram :: [Opt] -> FilterSpec -> Journal -> String
+ Hledger.Cli.Options: ChartItems :: String -> Opt
+ Hledger.Cli.Options: ChartOutput :: String -> Opt
+ Hledger.Cli.Options: ChartSize :: String -> Opt
+ Hledger.Cli.Options: argsError :: String -> IO ()
+ Hledger.Cli.Options: options_cli :: [OptDescr Opt]
+ Hledger.Cli.Options: parseArgumentsWith :: [OptDescr Opt] -> IO ([Opt], [String])
+ Hledger.Cli.Print: journalReport :: [Opt] -> FilterSpec -> Journal -> JournalReport
+ Hledger.Cli.Print: print' :: [Opt] -> [String] -> Journal -> IO ()
+ Hledger.Cli.Print: showTransactions :: FilterSpec -> Journal -> String
+ Hledger.Cli.Print: type JournalReport = [JournalReportItem]
+ Hledger.Cli.Print: type JournalReportItem = Transaction
+ Hledger.Cli.Register: register :: [Opt] -> [String] -> Journal -> IO ()
+ Hledger.Cli.Register: registerReport :: [Opt] -> FilterSpec -> Journal -> RegisterReport
+ Hledger.Cli.Register: registerReportAsText :: [Opt] -> RegisterReport -> String
+ Hledger.Cli.Register: showPostingWithBalanceForVty :: Bool -> Posting -> MixedAmount -> String
+ Hledger.Cli.Register: tests_Register :: Test
+ Hledger.Cli.Register: type RegisterReport = [RegisterReportItem]
+ Hledger.Cli.Register: type RegisterReportItem = (Maybe (Day, String), Posting, MixedAmount)
+ Hledger.Cli.Stats: showLedgerStats :: [Opt] -> [String] -> Ledger -> Day -> DateSpan -> String
+ Hledger.Cli.Stats: stats :: [Opt] -> [String] -> Journal -> IO ()
+ Hledger.Cli.Utils: fileModificationTime :: FilePath -> IO ClockTime
+ Hledger.Cli.Utils: journalSpecifiedFileIsNewer :: Journal -> FilePath -> IO Bool
+ Hledger.Cli.Utils: readFileStrictly :: FilePath -> IO String
+ Hledger.Cli.Version: binaryfilename :: String -> String
+ Hledger.Cli.Version: progversionstr :: String -> String
+ Hledger.Cli.Version: version :: [Char]

Files

− CONTRIBUTORS.rst
@@ -1,72 +0,0 @@-----title: hledger Contributor List and Agreement-----hledger Contributor List and Agreement-======================================--hledger Contributors----------------------hledger is brought to you by:--- Simon Michael - lead developer, project manager, general dogsbody-- Sergey Astanin - utf8 support-- Nick Ingolia - parser improvements-- Roman Cheplyaka - "chart" command, "add" command improvements-- Michael Snoyman - some additions to the Yesod web interface--Developers who have not yet signed the contributor agreement:--- Tim Docker - parser improvements-- Marko Kocić - hlint cleanup-- Oliver Braun - GHC 6.12 compatibility-- Gwern Branwen - cleanups--Also:--- John Wiegley - created the original ledger program--.. raw:: html-- <br>--------.. raw:: html-- <br>--hledger Contributor Agreement-------------------------------This is the official contributor agreement (v1) and contributor list-for the hledger project. If you have contributed non-trivial code-changes to hledger, please sign this, to help ensure:--1. that there is a clear legal status and audit trail for the project--2. that you get proper credit for your work--3. that we are able to remain license-compatible with related software-   by updating to newer versions of our license when appropriate-   (eg GPLv2 -> GPLv3)--To "sign", read the conditions below and then send a darcs patch to-`this file`_  adding your name to the Contributor List above.  By so-doing you promise that all of your commits to hledger:--- are free of patent violations or copyright violations, to the best of-  your knowledge--- are released under the hledger project's license_; or are released-  under another compatible license (in which case this must be clearly-  noted); or are public domain--- may be relicensed under official future versions of their license -  at the discretion of the project leader--We don't currently gather paper signatures, but this is a good start.-Feel free to update your entry as your contributions grow!---.. _license:   http://joyful.com/repos/hledger/LICENSE-.. _this file: http://joyful.com//darcsweb/darcsweb.cgi?r=hledger;a=filehistory;f=/CONTRIBUTORS-
− DEVELOPMENT.rst
@@ -1,100 +0,0 @@-----title: hledger development-----hledger development-===================--**Techie intro**--hledger_ is a remix, in haskell_, of John Wiegley's excellent ledger_ accounting tool.-It reads a plain text journal_ or timelog_ file describing your transactions-and displays reports via command line, curses or web interfaces.--The hledger project aims to produce:--- a practical, accessible, dependable tool for end users-- a useful library and toolbox for finance-minded haskell programmers-- a successful, time-and-money-solvent project within a thriving ecosystem of financial software projects.--hledger is free software by `Simon Michael`_ & `co.`_, released under GNU GPLv3.--**Learn**- news_, manual_, screenshots_, `some extra tips`_--**Download**- ``cabal install hledger``, - or try these ready-to-run binaries_,- or see the `installing docs <MANUAL.html#installing>`_--**Develop**- ``darcs get http://joyful.com/repos/hledger``, - `browse the repo`_, - `hackage page`_, - `hledger haddock docs`_, - benchmark_\/profile_\/heap_\/coverage_ reports,- `developer notes`_-- .. `hledger-lib sourcegraph report`_, - .. `hledger sourcegraph report`_, --.. raw:: html-- <a name="support" />--**Support**--- chat Simon (sm) on the `#ledger`_ irc channel which we share, or `email me`_-- report problems at `bugs.hledger.org <http://bugs.hledger.org>`_ (`grid <http://bugs.hledger.org/grid>`_)-- share and test journal snippets at paste . hledger.org-- .. raw:: html--     <form action="http://groups.google.com/group/hledger/boxsubscribe" >-       join the hledger mail list at <a href="http://list.hledger.org">list.hledger.org</a>. Your email:-       <input type=text name=email><input type=submit name="sub" value="Subscribe">-     </form>--**Related projects**--- John Wiegley's ledger_ inspired hledger, and we try to stay compatible. You can often use both tools on the same journal file.-- Uwe Hollerbach's umm_ is another haskell tool inspired by h/ledger.-- Tim Docker's ledger-reports_ uses hledger as a library to generate `html reports`_. -- I have a few older bits and pieces `here <http://joyful.com/Ledger>`_.--.. raw:: html-- <a href="http://joyful.com/darcsweb/darcsweb.cgi?r=hledger;a=shortlog"><img src=http://joyful.com/repos/hledger/commits.png border=0></a>- <a href="https://www.google.com/analytics/reporting/?reset=1&id=15489822" accesskey="a"></a>---.. _hledger:              README.html-.. _journal:              http://joyful.com/repos/hledger/data/sample.journal-.. _timelog:              http://joyful.com/repos/hledger/data/sample.timelog-.. _command line:         SCREENSHOTS.html#hledger-screen-1-.. _curses:               SCREENSHOTS.html#sshot-.. _web interface:        http://demo.hledger.org-.. _mail list:            http://list.hledger.org-.. _issue tracker:        http://bugs.hledger.org-.. _binaries:             http://hledger.org/binaries/-.. _manual:               MANUAL.html-.. _news:                 NEWS.html-.. _screenshots:          SCREENSHOTS.html-.. _hledger haddock docs: http://hledger.org/api-doc/-.. _hledger-lib sourcegraph report: http://joyful.com/repos/hledger/hledger-lib/SourceGraph/hledger-lib.html-.. _hledger sourcegraph report: http://joyful.com/repos/hledger/SourceGraph/hledger.html-.. _developer notes:      http://joyful.com/darcsweb/darcsweb.cgi?r=hledger;a=plainblob;f=/NOTES-.. _benchmark:            http://hledger.org/profs/latest.bench-.. _profile:              http://hledger.org/profs/latest.prof-.. _heap:                 http://hledger.org/profs/latest.ps-.. _coverage:             http://hledger.org/profs/coverage/hpc_index_fun.html-.. _browse the repo:      http://joyful.com/darcsweb/darcsweb.cgi?r=hledger-.. _email me:             mailto:simon@joyful.com?subject=hledger:-.. _Simon Michael:        http://joyful.com-.. _co.:                  http://hledger.org/CONTRIBUTORS.html-.. _hackage page:         http://hackage.haskell.org/package/hledger-.. _#ledger:              irc://irc.freenode.net/#ledger-.. _haskell:              http://haskell.org-.. _ledger:               http://wiki.github.com/jwiegley/ledger-.. _umm:                  http://www.korgwal.com/umm/-.. _ledger-reports:       http://dockerz.net/repos/ledger-reports-.. _html reports:         http://dockerz.net/software/hledger_report_sample/report.html-.. _some extra tips:      http://podcastle.org/2009/10/09/pc-miniature-38-accounting-for-dragons/
+ Hledger/Cli/Add.hs view
@@ -0,0 +1,251 @@+{-# LANGUAGE CPP #-}+{-| ++A history-aware add command to help with data entry.++-}++module Hledger.Cli.Add+where+import Hledger.Data+import Hledger.Read.JournalReader (someamount)+import Hledger.Cli.Options+import Hledger.Cli.Register (registerReport, registerReportAsText)+#if __GLASGOW_HASKELL__ <= 610+import Prelude hiding (putStr, putStrLn, getLine, appendFile)+import System.IO.UTF8+import System.IO ( stderr )+#else+import System.IO ( stderr, hPutStrLn, hPutStr )+#endif+import System.IO.Error+import Text.ParserCombinators.Parsec+import Hledger.Cli.Utils (readJournalWithOpts)+import qualified Data.Foldable as Foldable (find)+import System.Console.Haskeline (+            InputT, runInputT, defaultSettings, setComplete, getInputLine)+import Control.Monad.Trans (liftIO)+import System.Console.Haskeline.Completion+import qualified Data.Set as Set+import Safe (headMay)+import Control.Exception (throw)+++-- | Read transactions from the terminal, prompting for each field,+-- and append them to the journal file. If the journal came from stdin, this+-- command has no effect.+add :: [Opt] -> [String] -> Journal -> IO ()+add opts args j+    | f == "-" = return ()+    | otherwise = do+  hPutStrLn stderr $+    "Enter one or more transactions, which will be added to your journal file.\n"+    ++"To complete a transaction, enter . when prompted for an account.\n"+    ++"To quit, press control-d or control-c."+  today <- getCurrentDay+  runInteraction j (getAndAddTransactions j opts args today)+        `catch` (\e -> unless (isEOFError e) $ ioError e)+      where f = journalFilePath j++-- | Read a number of transactions from the command line, prompting,+-- validating, displaying and appending them to the journal file, until+-- end of input (then raise an EOF exception). Any command-line arguments+-- are used as the first transaction's description.+getAndAddTransactions :: Journal -> [Opt] -> [String] -> Day -> InputT IO ()+getAndAddTransactions j opts args defaultDate = do+  (t, d) <- getTransaction j opts args defaultDate+  j <- liftIO $ journalAddTransaction j opts t+  getAndAddTransactions j opts args d++-- | Read a transaction from the command line, with history-aware prompting.+getTransaction :: Journal -> [Opt] -> [String] -> Day+                    -> InputT IO (Transaction,Day)+getTransaction j opts args defaultDate = do+  today <- liftIO getCurrentDay+  datestr <- askFor "date" +            (Just $ showDate defaultDate)+            (Just $ \s -> null s || +             isRight (parse (smartdate >> many spacenonewline >> eof) "" $ lowercase s))+  description <- askFor "description" (Just "") Nothing+  let historymatches = transactionsSimilarTo j args description+      bestmatch | null historymatches = Nothing+                | otherwise = Just $ snd $ head historymatches+      bestmatchpostings = maybe Nothing (Just . tpostings) bestmatch+      date = fixSmartDate today $ fromparse $ (parse smartdate "" . lowercase) datestr+      accept x = x == "." || (not . null) x &&+        if NoNewAccts `elem` opts+            then isJust $ Foldable.find (== x) ant+            else True+        where (ant,_,_,_) = groupPostings $ journalPostings j+      getpostingsandvalidate = do+        ps <- getPostings (jContext j) accept bestmatchpostings []+        let t = nulltransaction{tdate=date+                               ,tstatus=False+                               ,tdescription=description+                               ,tpostings=ps+                               }+            retry msg = do+              liftIO $ hPutStrLn stderr $ "\n" ++ msg ++ "please re-enter."+              getpostingsandvalidate+        either retry (return . flip (,) date) $ balanceTransaction Nothing t -- imprecise balancing+  unless (null historymatches) +       (liftIO $ do+         hPutStrLn stderr "Similar transactions found, using the first for defaults:\n"+         hPutStr stderr $ concatMap (\(n,t) -> printf "[%3d%%] %s" (round $ n*100 :: Int) (show t)) $ take 3 historymatches)+  getpostingsandvalidate++-- fragile+-- | Read postings from the command line until . is entered, using any+-- provided historical postings and the journal context to guess defaults.+getPostings :: JournalContext -> (AccountName -> Bool) -> Maybe [Posting] -> [Posting] -> InputT IO [Posting]+getPostings ctx accept historicalps enteredps = do+  let bestmatch | isNothing historicalps = Nothing+                | n <= length ps = Just $ ps !! (n-1)+                | otherwise = Nothing+                where Just ps = historicalps+      defaultaccount = maybe Nothing (Just . showacctname) bestmatch+  account <- askFor (printf "account %d" n) defaultaccount (Just accept)+  if account=="."+    then return enteredps+    else do+      let defaultacctused = Just account == defaultaccount+          historicalps' = if defaultacctused then historicalps else Nothing+          bestmatch' | isNothing historicalps' = Nothing+                     | n <= length ps = Just $ ps !! (n-1)+                     | otherwise = Nothing+                     where Just ps = historicalps'+          defaultamountstr | isJust bestmatch' = Just historicalamountstr+                           | n > 1             = Just balancingamountstr+                           | otherwise         = Nothing+              where+                historicalamountstr = showMixedAmountWithPrecision maxprecision $ pamount $ fromJust bestmatch'+                balancingamountstr  = showMixedAmountWithPrecision maxprecision $ negate $ sumMixedAmountsPreservingHighestPrecision $ map pamount enteredrealps+      amountstr <- askFor (printf "amount  %d" n) defaultamountstr validateamount+      let amount  = fromparse $ runParser (someamount <|> return missingamt) ctx     "" amountstr+          amount' = fromparse $ runParser (someamount <|> return missingamt) nullctx "" amountstr+          defaultamtused = Just (showMixedAmount amount) == defaultamountstr+          historicalps'' = if defaultamtused then historicalps' else Nothing+          commodityadded | c == cwithnodef = Nothing+                         | otherwise       = c+              where c          = maybemixedamountcommodity amount+                    cwithnodef = maybemixedamountcommodity amount'+                    maybemixedamountcommodity = maybe Nothing (Just . commodity) . headMay . amounts+          p = nullposting{paccount=stripbrackets account,+                          pamount=amount,+                          ptype=postingtype account}+      when (isJust commodityadded) $+           liftIO $ hPutStrLn stderr $ printf "using default commodity (%s)" (symbol $ fromJust commodityadded)+      getPostings ctx accept historicalps'' $ enteredps ++ [p]+    where+      n = length enteredps + 1+      enteredrealps = filter isReal enteredps+      showacctname p = showAccountName Nothing (ptype p) $ paccount p+      postingtype ('[':_) = BalancedVirtualPosting+      postingtype ('(':_) = VirtualPosting+      postingtype _ = RegularPosting+      stripbrackets = dropWhile (`elem` "([") . reverse . dropWhile (`elem` "])") . reverse+      validateamount = Just $ \s -> (null s && not (null enteredrealps))+                                   || isRight (runParser (someamount>>many spacenonewline>>eof) ctx "" s)++-- | Prompt for and read a string value, optionally with a default value+-- and a validator. A validator causes the prompt to repeat until the+-- input is valid. May also raise an EOF exception if control-d is pressed.+askFor :: String -> Maybe String -> Maybe (String -> Bool) -> InputT IO String+askFor prompt def validator = do+  l <- fmap (maybe eofErr id)+            $ getInputLine $ prompt ++ maybe "" showdef def ++ ": "+  let input = if null l then fromMaybe l def else l+  case validator of+    Just valid -> if valid input+                   then return input+                   else askFor prompt def validator+    Nothing -> return input+    where+        showdef s = " [" ++ s ++ "]"+        eofErr = throw $ mkIOError eofErrorType "end of input" Nothing Nothing++-- | Append this transaction to the journal's file, and to the journal's+-- transaction list.+journalAddTransaction :: Journal -> [Opt] -> Transaction -> IO Journal+journalAddTransaction j@Journal{jtxns=ts} opts t = do+  let f = journalFilePath j+  appendToJournalFile f $ showTransaction t+  when (Debug `elem` opts) $ do+    putStrLn $ printf "\nAdded transaction to %s:" f+    putStrLn =<< registerFromString (show t)+  return j{jtxns=ts++[t]}++-- | Append data to a journal file; or if the file is "-", dump it to stdout.+appendToJournalFile :: FilePath -> String -> IO ()+appendToJournalFile f s =+    if f == "-"+    then putStr $ sep ++ s+    else appendFile f $ sep++s+    where +      sep = "\n\n"+      -- sep | null $ strip t = ""+      --     | otherwise = replicate (2 - min 2 (length lastnls)) '\n'+      --     where lastnls = takeWhile (=='\n') $ reverse t++-- | Convert a string of journal data into a register report.+registerFromString :: String -> IO String+registerFromString s = do+  now <- getCurrentLocalTime+  l <- readJournalWithOpts [] s+  return $ registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] now) l+    where opts = [Empty]++-- | Return a similarity measure, from 0 to 1, for two strings.+-- This is Simon White's letter pairs algorithm from+-- http://www.catalysoft.com/articles/StrikeAMatch.html+-- with a modification for short strings.+compareStrings :: String -> String -> Double+compareStrings "" "" = 1+compareStrings (_:[]) "" = 0+compareStrings "" (_:[]) = 0+compareStrings (a:[]) (b:[]) = if toUpper a == toUpper b then 1 else 0+compareStrings s1 s2 = 2.0 * fromIntegral i / fromIntegral u+    where+      i = length $ intersect pairs1 pairs2+      u = length pairs1 + length pairs2+      pairs1 = wordLetterPairs $ uppercase s1+      pairs2 = wordLetterPairs $ uppercase s2+wordLetterPairs = concatMap letterPairs . words+letterPairs (a:b:rest) = [a,b] : letterPairs (b:rest)+letterPairs _ = []++compareDescriptions :: [Char] -> [Char] -> Double+compareDescriptions s t = compareStrings s' t'+    where s' = simplify s+          t' = simplify t+          simplify = filter (not . (`elem` "0123456789"))++transactionsSimilarTo :: Journal -> [String] -> String -> [(Double,Transaction)]+transactionsSimilarTo j apats s =+    sortBy compareRelevanceAndRecency+               $ filter ((> threshold).fst)+               [(compareDescriptions s $ tdescription t, t) | t <- ts]+    where+      compareRelevanceAndRecency (n1,t1) (n2,t2) = compare (n2,tdate t2) (n1,tdate t1)+      ts = jtxns $ filterJournalTransactionsByAccount apats j+      threshold = 0++runInteraction :: Journal -> InputT IO a -> IO a+runInteraction j m = do+    let cc = completionCache j+    runInputT (setComplete (accountCompletion cc) defaultSettings) m++-- A precomputed list of all accounts previously entered into the journal.+type CompletionCache = [AccountName]++completionCache :: Journal -> CompletionCache+completionCache j = -- Only keep unique account names.+                    Set.toList $ Set.fromList+                        [paccount p | t <- jtxns j, p <- tpostings t]++accountCompletion :: CompletionCache -> CompletionFunc IO+accountCompletion cc = completeWord Nothing+                        "" -- don't break words on whitespace, since account names+                           -- can contain spaces.+                        $ \s -> return $ map simpleCompletion+                                        $ filter (s `isPrefixOf`) cc
+ Hledger/Cli/Balance.hs view
@@ -0,0 +1,213 @@+{-# LANGUAGE CPP #-}+{-|++A ledger-compatible @balance@ command.++ledger's balance command is easy to use but not easy to describe+precisely.  In the examples below we'll use sample.journal, which has the+following account tree:++@+ assets+   bank+     checking+     saving+   cash+ expenses+   food+   supplies+ income+   gifts+   salary+ liabilities+   debts+@++The balance command shows accounts with their aggregate balances.+Subaccounts are displayed indented below their parent. Each balance is the+sum of any transactions in that account plus any balances from+subaccounts:++@+ $ hledger -f sample.journal balance+                 $-1  assets+                  $1    bank:saving+                 $-2    cash+                  $2  expenses+                  $1    food+                  $1    supplies+                 $-2  income+                 $-1    gifts+                 $-1    salary+                  $1  liabilities:debts+@++Usually, the non-interesting accounts are elided or omitted. Above,+@checking@ is omitted because it has no subaccounts and a zero balance.+@bank@ is elided because it has only a single displayed subaccount+(@saving@) and it would be showing the same balance as that ($1). Ditto+for @liabilities@. We will return to this in a moment.++The --depth argument can be used to limit the depth of the balance report.+So, to see just the top level accounts:++@+$ hledger -f sample.journal balance --depth 1+                 $-1  assets+                  $2  expenses+                 $-2  income+                  $1  liabilities+@++This time liabilities has no displayed subaccounts (due to --depth) and+is not elided.++With one or more account pattern arguments, the balance command shows+accounts whose name matches one of the patterns, plus their parents+(elided) and subaccounts. So with the pattern o we get:++@+ $ hledger -f sample.journal balance o+                  $1  expenses:food+                 $-2  income+                 $-1    gifts+                 $-1    salary+--------------------+                 $-1+@++The o pattern matched @food@ and @income@, so they are shown. Unmatched+parents of matched accounts are also shown (elided) for context (@expenses@).++Also, the balance report shows the total of all displayed accounts, when+that is non-zero. Here, it is displayed because the accounts shown add up+to $-1.++Here is a more precise definition of \"interesting\" accounts in ledger's+balance report:++- an account which has just one interesting subaccount branch, and which+  is not at the report's maximum depth, is interesting if the balance is+  different from the subaccount's, and otherwise boring.++- any other account is interesting if it has a non-zero balance, or the -E+  flag is used.++-}++module Hledger.Cli.Balance (+  BalanceReport+ ,BalanceReportItem+ ,balance+ ,balanceReport+ ,balanceReportAsText+ -- ,tests_Balance+) where+import Hledger.Data.Utils+import Hledger.Data.Types+import Hledger.Data.Amount+import Hledger.Data.AccountName+import Hledger.Data.Posting+import Hledger.Data.Ledger+import Hledger.Cli.Options+#if __GLASGOW_HASKELL__ <= 610+import Prelude hiding ( putStr )+import System.IO.UTF8+#endif+++-- | A balance report is a chart of accounts with balances, and their grand total.+type BalanceReport = ([BalanceReportItem] -- line items, one per account+                     ,MixedAmount         -- total balance of all accounts+                     )++-- | The data for a single balance report line item, representing one account.+type BalanceReportItem = (AccountName  -- full account name+                         ,AccountName  -- account name elided for display: the leaf name,+                                       -- prefixed by any boring parents immediately above+                         ,Int          -- account depth within this report, excludes boring parents+                         ,MixedAmount) -- account balance, includes subs unless --flat is present++-- | Print a balance report.+balance :: [Opt] -> [String] -> Journal -> IO ()+balance opts args j = do+  t <- getCurrentLocalTime+  putStr $ balanceReportAsText opts $ balanceReport opts (optsToFilterSpec opts args t) j++-- | Render a balance report as plain text suitable for console output.+balanceReportAsText :: [Opt] -> BalanceReport -> String+balanceReportAsText opts (items,total) =+    unlines $+            map (balanceReportItemAsText opts) items+            +++            if NoTotal `elem` opts+             then []+             else ["--------------------"+                  ,padleft 20 $ showMixedAmountWithoutPrice total+                  ]++-- | Render one balance report line item as plain text.+balanceReportItemAsText :: [Opt] -> BalanceReportItem -> String+balanceReportItemAsText opts (a, adisplay, adepth, abal) = concatTopPadded [amt, "  ", name]+    where+      amt = padleft 20 $ showMixedAmountWithoutPrice abal+      name | Flat `elem` opts = accountNameDrop (dropFromOpts opts) a+           | otherwise        = depthspacer ++ adisplay+      depthspacer = replicate (indentperlevel * adepth) ' '+      indentperlevel = 2++-- | Get a balance report with the specified options for this journal.+balanceReport :: [Opt] -> FilterSpec -> Journal -> BalanceReport+balanceReport opts filterspec j = (items, total)+    where+      items = map mkitem interestingaccts+      interestingaccts = filter (isInteresting opts l) acctnames+      acctnames = sort $ tail $ flatten $ treemap aname accttree+      accttree = ledgerAccountTree (fromMaybe 99999 $ depthFromOpts opts) l+      total = sum $ map abalance $ ledgerTopAccounts l+      l = journalToLedger filterspec j+      -- | Get data for one balance report line item.+      mkitem :: AccountName -> BalanceReportItem+      mkitem a = (a, adisplay, indent, abal)+          where+            adisplay | Flat `elem` opts = a+                     | otherwise = accountNameFromComponents $ reverse (map accountLeafName ps) ++ [accountLeafName a]+                where ps = takeWhile boring parents where boring = not . (`elem` interestingparents)+            indent | Flat `elem` opts = 0+                   | otherwise = length interestingparents+            interestingparents = filter (`elem` interestingaccts) parents+            parents = parentAccountNames a+            abal | Flat `elem` opts = exclusiveBalance acct+                 | otherwise = abalance acct+                 where acct = ledgerAccount l a++exclusiveBalance :: Account -> MixedAmount+exclusiveBalance = sumPostings . apostings++-- | Is the named account considered interesting for this ledger's balance report ?+isInteresting :: [Opt] -> Ledger -> AccountName -> Bool+isInteresting opts l a | Flat `elem` opts = isInterestingFlat opts l a+                       | otherwise = isInterestingIndented opts l a++isInterestingFlat :: [Opt] -> Ledger -> AccountName -> Bool+isInterestingFlat opts l a = notempty || emptyflag+    where+      acct = ledgerAccount l a+      notempty = not $ isZeroMixedAmount $ exclusiveBalance acct+      emptyflag = Empty `elem` opts++isInterestingIndented :: [Opt] -> Ledger -> AccountName -> Bool+isInterestingIndented opts l a+    | numinterestingsubs==1 && not atmaxdepth = notlikesub+    | otherwise = notzero || emptyflag+    where+      atmaxdepth = isJust d && Just (accountNameLevel a) == d where d = depthFromOpts opts+      emptyflag = Empty `elem` opts+      acct = ledgerAccount l a+      notzero = not $ isZeroMixedAmount inclbalance where inclbalance = abalance acct+      notlikesub = not $ isZeroMixedAmount exclbalance where exclbalance = sumPostings $ apostings acct+      numinterestingsubs = length $ filter isInterestingTree subtrees+          where+            isInterestingTree = treeany (isInteresting opts l . aname)+            subtrees = map (fromJust . ledgerAccountTreeAt l) $ ledgerSubAccounts l $ ledgerAccount l a+
+ Hledger/Cli/Commands.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE CPP #-}+{-| ++The Commands package defines all the commands offered by the hledger+application, like \"register\" and \"balance\".  This module exports all+the commands; you can also import individual modules if you prefer.++-}++module Hledger.Cli.Commands (+                     module Hledger.Cli.Add,+                     module Hledger.Cli.Balance,+                     module Hledger.Cli.Convert,+                     module Hledger.Cli.Histogram,+                     module Hledger.Cli.Print,+                     module Hledger.Cli.Register,+                     module Hledger.Cli.Stats,+                     tests_Hledger_Commands+              )+where+import Hledger.Cli.Add+import Hledger.Cli.Balance+import Hledger.Cli.Convert+import Hledger.Cli.Histogram+import Hledger.Cli.Print+import Hledger.Cli.Register+import Hledger.Cli.Stats+import Test.HUnit (Test(TestList))+++tests_Hledger_Commands = TestList+    [+--      Hledger.Cli.Add.tests_Add+--     ,Hledger.Cli.Balance.tests_Balance+     Hledger.Cli.Convert.tests_Convert+--     ,Hledger.Cli.Histogram.tests_Histogram+--     ,Hledger.Cli.Print.tests_Print+    ,Hledger.Cli.Register.tests_Register+--     ,Hledger.Cli.Stats.tests_Stats+    ]
− Hledger/Cli/Commands/Add.hs
@@ -1,201 +0,0 @@-{-# LANGUAGE CPP #-}-{-| --A history-aware add command to help with data entry.---}--module Hledger.Cli.Commands.Add-where-import Hledger.Data-import Hledger.Read.Journal (someamount)-import Hledger.Cli.Options-import Hledger.Cli.Commands.Register (registerReport, registerReportAsText)-#if __GLASGOW_HASKELL__ <= 610-import Prelude hiding (putStr, putStrLn, getLine, appendFile)-import System.IO.UTF8-import System.IO ( stderr, hFlush )-#else-import System.IO ( stderr, hFlush, hPutStrLn, hPutStr )-#endif-import System.IO.Error-import Text.ParserCombinators.Parsec-import Hledger.Cli.Utils (readJournalWithOpts)-import qualified Data.Foldable as Foldable (find)---- | Read transactions from the terminal, prompting for each field,--- and append them to the journal file. If the journal came from stdin, this--- command has no effect.-add :: [Opt] -> [String] -> Journal -> IO ()-add opts args j-    | f == "-" = return ()-    | otherwise = do-  hPutStrLn stderr $-    "Enter one or more transactions, which will be added to your journal file.\n"-    ++"To complete a transaction, enter . as account name. To quit, press control-c."-  today <- getCurrentDay-  getAndAddTransactions j opts args today `catch` (\e -> unless (isEOFError e) $ ioError e)-      where f = filepath j---- | Read a number of transactions from the command line, prompting,--- validating, displaying and appending them to the journal file, until--- end of input (then raise an EOF exception). Any command-line arguments--- are used as the first transaction's description.-getAndAddTransactions :: Journal -> [Opt] -> [String] -> Day -> IO ()-getAndAddTransactions j opts args defaultDate = do-  (t, d) <- getTransaction j opts args defaultDate-  j <- journalAddTransaction j opts t-  getAndAddTransactions j opts args d---- | Read a transaction from the command line, with history-aware prompting.-getTransaction :: Journal -> [Opt] -> [String] -> Day -> IO (Transaction,Day)-getTransaction j opts args defaultDate = do-  today <- getCurrentDay-  datestr <- askFor "date" -            (Just $ showDate defaultDate)-            (Just $ \s -> null s || -             isRight (parse (smartdate >> many spacenonewline >> eof) "" $ lowercase s))-  description <- askFor "description" (Just "") Nothing-  let historymatches = transactionsSimilarTo j args description-      bestmatch | null historymatches = Nothing-                | otherwise = Just $ snd $ head historymatches-      bestmatchpostings = maybe Nothing (Just . tpostings) bestmatch-      date = fixSmartDate today $ fromparse $ (parse smartdate "" . lowercase) datestr-      accept x = x == "." || (not . null) x &&-        if NoNewAccts `elem` opts-            then isJust $ Foldable.find (== x) ant-            else True-        where (ant,_,_,_) = groupPostings $ journalPostings j-      getpostingsandvalidate = do-        ps <- getPostings accept bestmatchpostings []-        let t = nulltransaction{tdate=date-                               ,tstatus=False-                               ,tdescription=description-                               ,tpostings=ps-                               }-            retry msg = do-              hPutStrLn stderr $ "\n" ++ msg ++ "please re-enter."-              getpostingsandvalidate-        either retry (return . flip (,) date) $ balanceTransaction t-  unless (null historymatches) -       (do-         hPutStrLn stderr "Similar transactions found, using the first for defaults:\n"-         hPutStr stderr $ concatMap (\(n,t) -> printf "[%3d%%] %s" (round $ n*100 :: Int) (show t)) $ take 3 historymatches)-  getpostingsandvalidate---- | Read postings from the command line until . is entered, using the--- provided historical postings, if any, to guess defaults.-getPostings :: (AccountName -> Bool) -> Maybe [Posting] -> [Posting] -> IO [Posting]-getPostings accept historicalps enteredps = do-  account <- askFor (printf "account %d" n) defaultaccount (Just accept)-  if account=="."-    then return enteredps-    else do-      amountstr <- askFor (printf "amount  %d" n) defaultamount validateamount-      let amount = fromparse $ parse (someamount <|> return missingamt) "" amountstr-      let p = nullposting{paccount=stripbrackets account,-                          pamount=amount,-                          ptype=postingtype account}-      getPostings accept historicalps $ enteredps ++ [p]-    where-      n = length enteredps + 1-      enteredrealps = filter isReal enteredps-      bestmatch | isNothing historicalps = Nothing-                | n <= length ps = Just $ ps !! (n-1)-                | otherwise = Nothing-                where Just ps = historicalps-      defaultaccount = maybe Nothing (Just . showacctname) bestmatch-      showacctname p = showAccountName Nothing (ptype p) $ paccount p-      defaultamount = maybe balancingamount (Just . show . pamount) bestmatch-          where balancingamount = Just $ show $ negate $ sumMixedAmountsPreservingHighestPrecision $ map pamount enteredrealps-      postingtype ('[':_) = BalancedVirtualPosting-      postingtype ('(':_) = VirtualPosting-      postingtype _ = RegularPosting-      stripbrackets = dropWhile (`elem` "([") . reverse . dropWhile (`elem` "])") . reverse-      validateamount = Just $ \s -> (null s && not (null enteredrealps))-                                   || isRight (parse (someamount>>many spacenonewline>>eof) "" s)---- | Prompt for and read a string value, optionally with a default value--- and a validator. A validator causes the prompt to repeat until the--- input is valid. May also raise an EOF exception if control-d is pressed.-askFor :: String -> Maybe String -> Maybe (String -> Bool) -> IO String-askFor prompt def validator = do-  hPutStr stderr $ prompt ++ maybe "" showdef def ++ ": "-  hFlush stderr-  l <- getLine-  let input = if null l then fromMaybe l def else l-  case validator of-    Just valid -> if valid input-                   then return input-                   else askFor prompt def validator-    Nothing -> return input-    where showdef s = " [" ++ s ++ "]"---- | Append this transaction to the journal's file. Also, to the journal's--- transaction list, but we don't bother updating the other fields - this--- is enough to include new transactions in the history matching.-journalAddTransaction :: Journal -> [Opt] -> Transaction -> IO Journal-journalAddTransaction j@Journal{jtxns=ts} opts t = do-  appendToJournalFile j $ showTransaction t-  when (Debug `elem` opts) $ do-    putStrLn $ printf "\nAdded transaction to %s:" (filepath j)-    putStrLn =<< registerFromString (show t)-  return j{jtxns=ts++[t]}---- | Append data to the journal's file, ensuring proper separation from--- any existing data; or if the file is "-", dump it to stdout.-appendToJournalFile :: Journal -> String -> IO ()-appendToJournalFile Journal{filepath=f, jtext=t} s =-    if f == "-"-    then putStr $ sep ++ s-    else appendFile f $ sep++s-    where -      -- XXX we are looking at the original raw text from when the journal-      -- was first read, but that's good enough for now-      sep | null $ strip t = ""-          | otherwise = replicate (2 - min 2 (length lastnls)) '\n'-          where lastnls = takeWhile (=='\n') $ reverse t---- | Convert a string of journal data into a register report.-registerFromString :: String -> IO String-registerFromString s = do-  now <- getCurrentLocalTime-  l <- readJournalWithOpts [] s-  return $ registerReportAsText opts $ registerReport opts (optsToFilterSpec opts [] now) l-    where opts = [Empty]---- | Return a similarity measure, from 0 to 1, for two strings.--- This is Simon White's letter pairs algorithm from--- http://www.catalysoft.com/articles/StrikeAMatch.html--- with a modification for short strings.-compareStrings :: String -> String -> Double-compareStrings "" "" = 1-compareStrings (_:[]) "" = 0-compareStrings "" (_:[]) = 0-compareStrings (a:[]) (b:[]) = if toUpper a == toUpper b then 1 else 0-compareStrings s1 s2 = 2.0 * fromIntegral i / fromIntegral u-    where-      i = length $ intersect pairs1 pairs2-      u = length pairs1 + length pairs2-      pairs1 = wordLetterPairs $ uppercase s1-      pairs2 = wordLetterPairs $ uppercase s2-wordLetterPairs = concatMap letterPairs . words-letterPairs (a:b:rest) = [a,b] : letterPairs (b:rest)-letterPairs _ = []--compareDescriptions :: [Char] -> [Char] -> Double-compareDescriptions s t = compareStrings s' t'-    where s' = simplify s-          t' = simplify t-          simplify = filter (not . (`elem` "0123456789"))--transactionsSimilarTo :: Journal -> [String] -> String -> [(Double,Transaction)]-transactionsSimilarTo j apats s =-    sortBy compareRelevanceAndRecency-               $ filter ((> threshold).fst)-               [(compareDescriptions s $ tdescription t, t) | t <- ts]-    where-      compareRelevanceAndRecency (n1,t1) (n2,t2) = compare (n2,tdate t2) (n1,tdate t1)-      ts = jtxns $ filterJournalTransactionsByAccount apats j-      threshold = 0-
− Hledger/Cli/Commands/All.hs
@@ -1,67 +0,0 @@-{-# LANGUAGE CPP #-}-{-| --The Commands package defines all the commands offered by the hledger-application, like \"register\" and \"balance\".  This module exports all-the commands; you can also import individual modules if you prefer.---}--module Hledger.Cli.Commands.All (-                     module Hledger.Cli.Commands.Add,-                     module Hledger.Cli.Commands.Balance,-                     module Hledger.Cli.Commands.Convert,-                     module Hledger.Cli.Commands.Histogram,-                     module Hledger.Cli.Commands.Print,-                     module Hledger.Cli.Commands.Register,-                     module Hledger.Cli.Commands.Stats,-#ifdef CHART-                     module Hledger.Cli.Commands.Chart,-#endif-#ifdef VTY-                     module Hledger.Cli.Commands.Vty,-#endif-#if defined(WEB)-                     module Hledger.Cli.Commands.Web,-#endif-                     tests_Hledger_Commands-              )-where-import Hledger.Cli.Commands.Add-import Hledger.Cli.Commands.Balance-import Hledger.Cli.Commands.Convert-import Hledger.Cli.Commands.Histogram-import Hledger.Cli.Commands.Print-import Hledger.Cli.Commands.Register-import Hledger.Cli.Commands.Stats-#ifdef CHART-import Hledger.Cli.Commands.Chart-#endif-#ifdef VTY-import Hledger.Cli.Commands.Vty-#endif-#if defined(WEB)-import Hledger.Cli.Commands.Web-#endif-import Test.HUnit (Test(TestList))---tests_Hledger_Commands = TestList-    [---      Hledger.Cli.Commands.Add.tests_Add---     ,Hledger.Cli.Commands.Balance.tests_Balance-     Hledger.Cli.Commands.Convert.tests_Convert---     ,Hledger.Cli.Commands.Histogram.tests_Histogram---     ,Hledger.Cli.Commands.Print.tests_Print-    ,Hledger.Cli.Commands.Register.tests_Register---     ,Hledger.Cli.Commands.Stats.tests_Stats-    ]--- #ifdef CHART---     ,Hledger.Cli.Commands.Chart.tests_Chart--- #endif--- #ifdef VTY---     ,Hledger.Cli.Commands.Vty.tests_Vty--- #endif--- #if defined(WEB)---     ,Hledger.Cli.Commands.Web.tests_Web--- #endif
− Hledger/Cli/Commands/Balance.hs
@@ -1,213 +0,0 @@-{-# LANGUAGE CPP #-}-{-|--A ledger-compatible @balance@ command.--ledger's balance command is easy to use but not easy to describe-precisely.  In the examples below we'll use sample.journal, which has the-following account tree:--@- assets-   bank-     checking-     saving-   cash- expenses-   food-   supplies- income-   gifts-   salary- liabilities-   debts-@--The balance command shows accounts with their aggregate balances.-Subaccounts are displayed indented below their parent. Each balance is the-sum of any transactions in that account plus any balances from-subaccounts:--@- $ hledger -f sample.journal balance-                 $-1  assets-                  $1    bank:saving-                 $-2    cash-                  $2  expenses-                  $1    food-                  $1    supplies-                 $-2  income-                 $-1    gifts-                 $-1    salary-                  $1  liabilities:debts-@--Usually, the non-interesting accounts are elided or omitted. Above,-@checking@ is omitted because it has no subaccounts and a zero balance.-@bank@ is elided because it has only a single displayed subaccount-(@saving@) and it would be showing the same balance as that ($1). Ditto-for @liabilities@. We will return to this in a moment.--The --depth argument can be used to limit the depth of the balance report.-So, to see just the top level accounts:--@-$ hledger -f sample.journal balance --depth 1-                 $-1  assets-                  $2  expenses-                 $-2  income-                  $1  liabilities-@--This time liabilities has no displayed subaccounts (due to --depth) and-is not elided.--With one or more account pattern arguments, the balance command shows-accounts whose name matches one of the patterns, plus their parents-(elided) and subaccounts. So with the pattern o we get:--@- $ hledger -f sample.journal balance o-                  $1  expenses:food-                 $-2  income-                 $-1    gifts-                 $-1    salary----------------------                 $-1-@--The o pattern matched @food@ and @income@, so they are shown. Unmatched-parents of matched accounts are also shown (elided) for context (@expenses@).--Also, the balance report shows the total of all displayed accounts, when-that is non-zero. Here, it is displayed because the accounts shown add up-to $-1.--Here is a more precise definition of \"interesting\" accounts in ledger's-balance report:--- an account which has just one interesting subaccount branch, and which-  is not at the report's maximum depth, is interesting if the balance is-  different from the subaccount's, and otherwise boring.--- any other account is interesting if it has a non-zero balance, or the -E-  flag is used.---}--module Hledger.Cli.Commands.Balance (-  BalanceReport- ,BalanceReportItem- ,balance- ,balanceReport- ,balanceReportAsText- -- ,tests_Balance-) where-import Hledger.Data.Utils-import Hledger.Data.Types-import Hledger.Data.Amount-import Hledger.Data.AccountName-import Hledger.Data.Posting-import Hledger.Data.Ledger-import Hledger.Cli.Options-#if __GLASGOW_HASKELL__ <= 610-import Prelude hiding ( putStr )-import System.IO.UTF8-#endif----- | A balance report is a chart of accounts with balances, and their grand total.-type BalanceReport = ([BalanceReportItem] -- line items, one per account-                     ,MixedAmount         -- total balance of all accounts-                     )---- | The data for a single balance report line item, representing one account.-type BalanceReportItem = (AccountName  -- full account name-                         ,AccountName  -- account name elided for display: the leaf name,-                                       -- prefixed by any boring parents immediately above-                         ,Int          -- account depth within this report, excludes boring parents-                         ,MixedAmount) -- account balance, includes subs unless --flat is present---- | Print a balance report.-balance :: [Opt] -> [String] -> Journal -> IO ()-balance opts args j = do-  t <- getCurrentLocalTime-  putStr $ balanceReportAsText opts $ balanceReport opts (optsToFilterSpec opts args t) j---- | Render a balance report as plain text suitable for console output.-balanceReportAsText :: [Opt] -> BalanceReport -> String-balanceReportAsText opts (items,total) =-    unlines $-            map (balanceReportItemAsText opts) items-            ++-            if NoTotal `elem` opts-             then []-             else ["--------------------"-                  ,padleft 20 $ showMixedAmountWithoutPrice total-                  ]---- | Render one balance report line item as plain text.-balanceReportItemAsText :: [Opt] -> BalanceReportItem -> String-balanceReportItemAsText opts (a, adisplay, adepth, abal) = concatTopPadded [amt, "  ", name]-    where-      amt = padleft 20 $ showMixedAmountWithoutPrice abal-      name | Flat `elem` opts = accountNameDrop (dropFromOpts opts) a-           | otherwise        = depthspacer ++ adisplay-      depthspacer = replicate (indentperlevel * adepth) ' '-      indentperlevel = 2---- | Get a balance report with the specified options for this journal.-balanceReport :: [Opt] -> FilterSpec -> Journal -> BalanceReport-balanceReport opts filterspec j = (items, total)-    where-      items = map mkitem interestingaccts-      interestingaccts = filter (isInteresting opts l) acctnames-      acctnames = sort $ tail $ flatten $ treemap aname accttree-      accttree = ledgerAccountTree (fromMaybe 99999 $ depthFromOpts opts) l-      total = sum $ map abalance $ ledgerTopAccounts l-      l = journalToLedger filterspec j-      -- | Get data for one balance report line item.-      mkitem :: AccountName -> BalanceReportItem-      mkitem a = (a, adisplay, indent, abal)-          where-            adisplay | Flat `elem` opts = a-                     | otherwise = accountNameFromComponents $ reverse (map accountLeafName ps) ++ [accountLeafName a]-                where ps = takeWhile boring parents where boring = not . (`elem` interestingparents)-            indent | Flat `elem` opts = 0-                   | otherwise = length interestingparents-            interestingparents = filter (`elem` interestingaccts) parents-            parents = parentAccountNames a-            abal | Flat `elem` opts = exclusiveBalance acct-                 | otherwise = abalance acct-                 where acct = ledgerAccount l a--exclusiveBalance :: Account -> MixedAmount-exclusiveBalance = sumPostings . apostings---- | Is the named account considered interesting for this ledger's balance report ?-isInteresting :: [Opt] -> Ledger -> AccountName -> Bool-isInteresting opts l a | Flat `elem` opts = isInterestingFlat opts l a-                       | otherwise = isInterestingIndented opts l a--isInterestingFlat :: [Opt] -> Ledger -> AccountName -> Bool-isInterestingFlat opts l a = notempty || emptyflag-    where-      acct = ledgerAccount l a-      notempty = not $ isZeroMixedAmount $ exclusiveBalance acct-      emptyflag = Empty `elem` opts--isInterestingIndented :: [Opt] -> Ledger -> AccountName -> Bool-isInterestingIndented opts l a-    | numinterestingsubs==1 && not atmaxdepth = notlikesub-    | otherwise = notzero || emptyflag-    where-      atmaxdepth = isJust d && Just (accountNameLevel a) == d where d = depthFromOpts opts-      emptyflag = Empty `elem` opts-      acct = ledgerAccount l a-      notzero = not $ isZeroMixedAmount inclbalance where inclbalance = abalance acct-      notlikesub = not $ isZeroMixedAmount exclbalance where exclbalance = sumPostings $ apostings acct-      numinterestingsubs = length $ filter isInterestingTree subtrees-          where-            isInterestingTree = treeany (isInteresting opts l . aname)-            subtrees = map (fromJust . ledgerAccountTreeAt l) $ ledgerSubAccounts l $ ledgerAccount l a-
− Hledger/Cli/Commands/Chart.hs
@@ -1,108 +0,0 @@-{-|--Generate balances pie chart---}--module Hledger.Cli.Commands.Chart-where-import Hledger.Data.Utils-import Hledger.Data.Types-import Hledger.Data.Amount-import Hledger.Data.Ledger-import Hledger.Data.Commodity-import Hledger.Cli.Options--import Control.Monad (liftM3)-import Graphics.Rendering.Chart-import Data.Colour-import Data.Colour.Names-import Data.Colour.RGBSpace-import Data.Colour.RGBSpace.HSL (hsl)-import Data.Colour.SRGB.Linear (rgb)-import Data.List-import Safe (readDef)---- | Generate an image with the pie chart and write it to a file-chart :: [Opt] -> [String] -> Journal -> IO ()-chart opts args j = do-  t <- getCurrentLocalTime-  let chart = genPie opts (optsToFilterSpec opts args t) j-  renderableToPNGFile (toRenderable chart) w h filename-    where-      filename = getOption opts ChartOutput chartoutput-      (w,h) = parseSize $ getOption opts ChartSize chartsize---- | Extract string option value from a list of options or use the default-getOption :: [Opt] -> (String->Opt) -> String -> String-getOption opts opt def = -    case reverse $ optValuesForConstructor opt opts of-        [] -> def-        x:_ -> x---- | Parse image size from a command-line option-parseSize :: String -> (Int,Int)-parseSize str = (read w, read h)-    where-    x = fromMaybe (error' "Size should be in WIDTHxHEIGHT format") $ findIndex (=='x') str-    (w,_:h) = splitAt x str---- | Generate pie chart-genPie :: [Opt] -> FilterSpec -> Journal -> PieLayout-genPie opts filterspec j = defaultPieLayout { pie_background_ = solidFillStyle $ opaque $ white-                                            , pie_plot_ = pie_chart }-    where-      pie_chart = defaultPieChart { pie_data_ = map (uncurry accountPieItem) chartitems'-                                  , pie_start_angle_ = (-90)-                                  , pie_colors_ = mkColours hue-                                  , pie_label_style_ = defaultFontStyle{font_size_=12}-                                  }-      chartitems' = debug "chart" $ top num samesignitems-      (samesignitems, sign) = sameSignNonZero rawitems-      rawitems = debug "raw" $ flatten $ balances $-                 ledgerAccountTree (fromMaybe 99999 $ depthFromOpts opts) $ journalToLedger filterspec j-      top n t = topn ++ [other]-          where-            (topn,rest) = splitAt n $ reverse $ sortBy (comparing snd) t-            other = ("other", sum $ map snd rest)-      num = readDef (fromIntegral chartitems) (getOption opts ChartItems (show chartitems))-      hue = if sign > 0 then red else green where (red, green) = (0, 110)-      debug s = if Debug `elem` opts then ltrace s else id---- | Select the nonzero items with same sign as the first, and make--- them positive. Also return a 1 or -1 corresponding to the original sign.-sameSignNonZero :: [(AccountName, Double)] -> ([(AccountName, Double)], Int)-sameSignNonZero is | null nzs = ([], 1)-                   | otherwise = (map pos $ filter (test.snd) nzs, sign)-                   where-                     nzs = filter ((/=0).snd) is-                     pos (a,b) = (a, abs b)-                     sign = if snd (head nzs) >= 0 then 1 else (-1)-                     test = if sign > 0 then (>0) else (<0)---- | Convert all quantities of MixedAccount to a single commodity-amountValue :: MixedAmount -> Double-amountValue = quantity . convertMixedAmountTo unknown---- | Generate a tree of account names together with their balances.---   The balance of account is decremented by the balance of its subaccounts---   which are drawn on the chart.-balances :: Tree Account -> Tree (AccountName, Double)-balances (Node rootAcc subAccs) = Node newroot newsubs-    where-      newroot = (aname rootAcc,-                 amountValue $-                 abalance rootAcc - (sum . map (abalance . root)) subAccs)-      newsubs = map balances subAccs---- | Build a single pie chart item-accountPieItem :: AccountName -> Double -> PieItem-accountPieItem accname balance = PieItem accname offset balance where offset = 0---- | Generate an infinite color list suitable for charts.-mkColours :: Double -> [AlphaColour Double]-mkColours hue = cycle $ [opaque $ rgbToColour $ hsl h s l | (h,s,l) <- liftM3 (,,)-                         [hue] [0.7] [0.1,0.2..0.7] ]--rgbToColour :: (Fractional a) => RGB a -> Colour a-rgbToColour (RGB r g b) = rgb r g b
− Hledger/Cli/Commands/Convert.hs
@@ -1,387 +0,0 @@-{-|-Convert account data in CSV format (eg downloaded from a bank) to journal-format, and print it on stdout. See the manual for more details.--}--module Hledger.Cli.Commands.Convert where-import Hledger.Cli.Options (Opt(Debug))-import Hledger.Cli.Version (versionstr)-import Hledger.Data.Types (Journal,AccountName,Transaction(..),Posting(..),PostingType(..))-import Hledger.Data.Utils (strip, spacenonewline, restofline, parseWithCtx, assertParse, assertParseEqual, error')-import Hledger.Read.Common (emptyCtx)-import Hledger.Read.Journal (someamount,ledgeraccountname)-import Hledger.Data.Amount (nullmixedamt)-import Safe (atDef, maximumDef)-import System.IO (stderr)-import Text.CSV (parseCSVFromFile, printCSV)-import Text.Printf (hPrintf)-import Text.RegexPR (matchRegexPR, gsubRegexPR)-import Data.Maybe-import Hledger.Data.Dates (firstJust, showDate, parsedate)-import System.Locale (defaultTimeLocale)-import Data.Time.Format (parseTime)-import Control.Monad (when, guard, liftM)-import Safe (readDef, readMay)-import System.Directory (doesFileExist)-import System.Exit (exitFailure)-import System.FilePath (takeBaseName, replaceExtension)-import Text.ParserCombinators.Parsec-import Test.HUnit---{- |-A set of data definitions and account-matching patterns sufficient to-convert a particular CSV data file into meaningful journal transactions. See above.--}-data CsvRules = CsvRules {-      dateField :: Maybe FieldPosition,-      statusField :: Maybe FieldPosition,-      codeField :: Maybe FieldPosition,-      descriptionField :: Maybe FieldPosition,-      amountField :: Maybe FieldPosition,-      currencyField :: Maybe FieldPosition,-      baseCurrency :: Maybe String,-      baseAccount :: AccountName,-      accountRules :: [AccountRule]-} deriving (Show, Eq)--nullrules = CsvRules {-      dateField=Nothing,-      statusField=Nothing,-      codeField=Nothing,-      descriptionField=Nothing,-      amountField=Nothing,-      currencyField=Nothing,-      baseCurrency=Nothing,-      baseAccount="unknown",-      accountRules=[]-}--type FieldPosition = Int--type AccountRule = (-   [(String, Maybe String)] -- list of regex match patterns with optional replacements-  ,AccountName              -- account name to use for a transaction matching this rule-  )--type CsvRecord = [String]----- | Read the CSV file named as an argument and print equivalent journal transactions,--- using/creating a .rules file.-convert :: [Opt] -> [String] -> Journal -> IO ()-convert opts args _ = do-  when (null args) $ error' "please specify a csv data file."-  let csvfile = head args-  csvparse <- parseCSVFromFile csvfile-  let records = case csvparse of-                  Left e -> error' $ show e-                  Right rs -> reverse $ filter (/= [""]) rs-  let debug = Debug `elem` opts-      rulesfile = rulesFileFor csvfile-  exists <- doesFileExist rulesfile-  if (not exists) then do-                  hPrintf stderr "creating conversion rules file %s, edit this file for better results\n" rulesfile-                  writeFile rulesfile initialRulesFileContent-   else-      hPrintf stderr "using conversion rules file %s\n" rulesfile-  rules <- liftM (either (error'.show) id) $ parseCsvRulesFile rulesfile-  when debug $ hPrintf stderr "rules: %s\n" (show rules)-  let requiredfields = max 2 (maxFieldIndex rules + 1)-      badrecords = take 1 $ filter ((< requiredfields).length) records-  if null badrecords-   then mapM_ (printTxn debug rules) records-   else do-     hPrintf stderr (unlines [-                      "Warning, at least one CSV record does not contain a field referenced by the"-                     ,"conversion rules file, or has less than two fields. Are you converting a"-                     ,"valid CSV file ? First bad record:\n%s"-                     ]) (show $ head badrecords)-     exitFailure---- | The highest (0-based) field index referenced in the field--- definitions, or -1 if no fields are defined.-maxFieldIndex :: CsvRules -> Int-maxFieldIndex r = maximumDef (-1) $ catMaybes [-                   dateField r-                  ,statusField r-                  ,codeField r-                  ,descriptionField r-                  ,amountField r-                  ,currencyField r-                  ]--rulesFileFor :: FilePath -> FilePath-rulesFileFor csvfile = replaceExtension csvfile ".rules"--initialRulesFileContent :: String-initialRulesFileContent =-    "# csv conversion rules file generated by hledger "++versionstr++"\n" ++-    "# Add rules to this file for more accurate conversion, see\n"++-    "# http://hledger.org/MANUAL.html#convert\n" ++-    "\n" ++-    "base-account assets:bank:checking\n" ++-    "date-field 0\n" ++-    "description-field 4\n" ++-    "amount-field 1\n" ++-    "currency $\n" ++-    "\n" ++-    "# account-assigning rules\n" ++-    "\n" ++-    "SPECTRUM\n" ++-    "expenses:health:gym\n" ++-    "\n" ++-    "ITUNES\n" ++-    "BLKBSTR=BLOCKBUSTER\n" ++-    "expenses:entertainment\n" ++-    "\n" ++-    "(TO|FROM) SAVINGS\n" ++-    "assets:bank:savings\n"---- rules file parser--parseCsvRulesFile :: FilePath -> IO (Either ParseError CsvRules)-parseCsvRulesFile f = do-  s <- readFile f-  return $ parseCsvRules f s--parseCsvRules :: FilePath -> String -> Either ParseError CsvRules-parseCsvRules rulesfile s = runParser csvrulesfile nullrules{baseAccount=takeBaseName rulesfile} rulesfile s--csvrulesfile :: GenParser Char CsvRules CsvRules-csvrulesfile = do-  many blankorcommentline-  many definitions-  r <- getState-  ars <- many accountrule-  many blankorcommentline-  eof-  return r{accountRules=ars}---- | Real independent parser choice, even when alternative matches share a prefix.-choice' parsers = choice $ map try (init parsers) ++ [last parsers]--definitions :: GenParser Char CsvRules ()-definitions = do-  choice' [-    datefield-   ,statusfield-   ,codefield-   ,descriptionfield-   ,amountfield-   ,currencyfield-   ,basecurrency-   ,baseaccount-   ,commentline-   ] <?> "definition"-  return ()--datefield = do-  string "date-field"-  many1 spacenonewline-  v <- restofline-  r <- getState-  setState r{dateField=readMay v}--codefield = do-  string "code-field"-  many1 spacenonewline-  v <- restofline-  r <- getState-  setState r{codeField=readMay v}--statusfield = do-  string "status-field"-  many1 spacenonewline-  v <- restofline-  r <- getState-  setState r{statusField=readMay v}--descriptionfield = do-  string "description-field"-  many1 spacenonewline-  v <- restofline-  r <- getState-  setState r{descriptionField=readMay v}--amountfield = do-  string "amount-field"-  many1 spacenonewline-  v <- restofline-  r <- getState-  setState r{amountField=readMay v}--currencyfield = do-  string "currency-field"-  many1 spacenonewline-  v <- restofline-  r <- getState-  setState r{currencyField=readMay v}--basecurrency = do-  string "currency"-  many1 spacenonewline-  v <- restofline-  r <- getState-  setState r{baseCurrency=Just v}--baseaccount = do-  string "base-account"-  many1 spacenonewline-  v <- ledgeraccountname-  optional newline-  r <- getState-  setState r{baseAccount=v}--accountrule :: GenParser Char CsvRules AccountRule-accountrule = do-  many blankorcommentline-  pats <- many1 matchreplacepattern-  guard $ length pats >= 2-  let pats' = init pats-      acct = either (fail.show) id $ runParser ledgeraccountname () "" $ fst $ last pats-  many blankorcommentline-  return (pats',acct)- <?> "account rule"--blanklines = many1 blankline >> return ()--blankline = many spacenonewline >> newline >> return () <?> "blank line"--commentchar = oneOf ";#"--commentline = many spacenonewline >> commentchar >> restofline >> return () <?> "comment line"--blankorcommentline = choice' [blankline, commentline]--matchreplacepattern = do-  notFollowedBy commentchar-  matchpat <- many1 (noneOf "=\n")-  replpat <- optionMaybe $ do {char '='; many $ noneOf "\n"}-  newline-  return (matchpat,replpat)--printTxn :: Bool -> CsvRules -> CsvRecord -> IO ()-printTxn debug rules rec = do-  when debug $ hPrintf stderr "record: %s" (printCSV [rec])-  putStr $ show $ transactionFromCsvRecord rules rec---- csv record conversion--transactionFromCsvRecord :: CsvRules -> CsvRecord -> Transaction-transactionFromCsvRecord rules fields =-  let -      date = parsedate $ normaliseDate $ maybe "1900/1/1" (atDef "" fields) (dateField rules)-      status = maybe False (null . strip . (atDef "" fields)) (statusField rules)-      code = maybe "" (atDef "" fields) (codeField rules)-      desc = maybe "" (atDef "" fields) (descriptionField rules)-      comment = ""-      precomment = ""-      amountstr = maybe "" (atDef "" fields) (amountField rules)-      amountstr' = strnegate amountstr where strnegate ('-':s) = s-                                             strnegate s = '-':s-      currency = maybe (fromMaybe "" $ baseCurrency rules) (atDef "" fields) (currencyField rules)-      amountstr'' = currency ++ amountstr'-      amountparse = runParser someamount emptyCtx "" amountstr''-      amount = either (const nullmixedamt) id amountparse-      unknownacct | (readDef 0 amountstr' :: Double) < 0 = "income:unknown"-                  | otherwise = "expenses:unknown"-      (acct,newdesc) = identify (accountRules rules) unknownacct desc-      t = Transaction {-              tdate=date,-              teffectivedate=Nothing,-              tstatus=status,-              tcode=code,-              tdescription=newdesc,-              tcomment=comment,-              tpreceding_comment_lines=precomment,-              tpostings=[-                   Posting {-                     pstatus=False,-                     paccount=acct,-                     pamount=amount,-                     pcomment="",-                     ptype=RegularPosting,-                     ptransaction=Just t-                   },-                   Posting {-                     pstatus=False,-                     paccount=baseAccount rules,-                     pamount=(-amount),-                     pcomment="",-                     ptype=RegularPosting,-                     ptransaction=Just t-                   }-                  ]-            }-  in t---- | Convert some date string with unknown format to YYYY/MM/DD.-normaliseDate :: String -> String-normaliseDate s = maybe "0000/00/00" showDate $-              firstJust-              [parseTime defaultTimeLocale "%Y/%m/%e" s-               -- can't parse a month without leading 0, try adding one-              ,parseTime defaultTimeLocale "%Y/%m/%e" (take 5 s ++ "0" ++ drop 5 s)-              ,parseTime defaultTimeLocale "%Y-%m-%e" s-              ,parseTime defaultTimeLocale "%Y-%m-%e" (take 5 s ++ "0" ++ drop 5 s)-              ,parseTime defaultTimeLocale "%m/%e/%Y" s-              ,parseTime defaultTimeLocale "%m/%e/%Y" ('0':s)-              ,parseTime defaultTimeLocale "%m-%e-%Y" s-              ,parseTime defaultTimeLocale "%m-%e-%Y" ('0':s)-              ]---- | Apply account matching rules to a transaction description to obtain--- the most appropriate account and a new description.-identify :: [AccountRule] -> String -> String -> (String,String)-identify rules defacct desc | null matchingrules = (defacct,desc)-                            | otherwise = (acct,newdesc)-    where-      matchingrules = filter ismatch rules :: [AccountRule]-          where ismatch = any (isJust . flip matchRegexPR (caseinsensitive desc) . fst) . fst-      (prs,acct) = head matchingrules-      p_ms_r = filter (\(_,m,_) -> isJust m) $ map (\(p,r) -> (p, matchRegexPR (caseinsensitive p) desc, r)) prs-      (p,_,r) = head p_ms_r-      newdesc = case r of Just rpat -> gsubRegexPR (caseinsensitive p) rpat desc-                          Nothing   -> desc--caseinsensitive = ("(?i)"++)--tests_Convert = TestList [--   "convert rules parsing: empty file" ~: do-     -- let assertMixedAmountParse parseresult mixedamount =-     --         (either (const "parse error") showMixedAmountDebug parseresult) ~?= (showMixedAmountDebug mixedamount)-    assertParseEqual (parseCsvRules "unknown" "") nullrules--  ,"convert rules parsing: accountrule" ~: do-     assertParseEqual (parseWithCtx nullrules accountrule "A\na\n") -- leading blank line required-                 ([("A",Nothing)], "a")--  ,"convert rules parsing: trailing comments" ~: do-     assertParse (parseWithCtx nullrules csvrulesfile "A\na\n# \n#\n")--  ,"convert rules parsing: trailing blank lines" ~: do-     assertParse (parseWithCtx nullrules csvrulesfile "A\na\n\n  \n")--  -- not supported-  -- ,"convert rules parsing: no final newline" ~: do-  --    assertParse (parseWithCtx nullrules csvrulesfile "A\na")-  --    assertParse (parseWithCtx nullrules csvrulesfile "A\na\n# \n#")-  --    assertParse (parseWithCtx nullrules csvrulesfile "A\na\n\n  ")--                 -- (nullrules{-                 --   -- dateField=Maybe FieldPosition,-                 --   -- statusField=Maybe FieldPosition,-                 --   -- codeField=Maybe FieldPosition,-                 --   -- descriptionField=Maybe FieldPosition,-                 --   -- amountField=Maybe FieldPosition,-                 --   -- currencyField=Maybe FieldPosition,-                 --   -- baseCurrency=Maybe String,-                 --   -- baseAccount=AccountName,-                 --   accountRules=[-                 --        ([("A",Nothing)], "a")-                 --       ]-                 --  })--  ]
− Hledger/Cli/Commands/Histogram.hs
@@ -1,50 +0,0 @@-{-# LANGUAGE CPP #-}-{-| --Print a histogram report.---}--module Hledger.Cli.Commands.Histogram-where-import Hledger.Data-import Hledger.Cli.Options-#if __GLASGOW_HASKELL__ <= 610-import Prelude hiding ( putStr )-import System.IO.UTF8-#endif---barchar = '*'---- | Print a histogram of some statistic per reporting interval, such as--- number of postings per day.-histogram :: [Opt] -> [String] -> Journal -> IO ()-histogram opts args j = do-  t <- getCurrentLocalTime-  putStr $ showHistogram opts (optsToFilterSpec opts args t) j--showHistogram :: [Opt] -> FilterSpec -> Journal -> String-showHistogram opts filterspec j = concatMap (printDayWith countBar) dayps-    where-      i = intervalFromOpts opts-      interval | i == NoInterval = Daily-               | otherwise = i-      fullspan = journalDateSpan j-      days = filter (DateSpan Nothing Nothing /=) $ splitSpan interval fullspan-      dayps = [(s, filter (isPostingInDateSpan s) ps) | s <- days]-      -- same as Register-      -- should count transactions, not postings ?-      ps = sortBy (comparing postingDate) $ filterempties $ filter matchapats $ filterdepth $ journalPostings j-      filterempties-          | Empty `elem` opts = id-          | otherwise = filter (not . isZeroMixedAmount . pamount)-      matchapats = matchpats apats . paccount-      apats = acctpats filterspec-      filterdepth | interval == NoInterval = filter (\p -> accountNameLevel (paccount p) <= depth)-                  | otherwise = id-      depth = fromMaybe 99999 $ depthFromOpts opts--printDayWith f (DateSpan b _, ts) = printf "%s %s\n" (show $ fromJust b) (f ts)--countBar ps = replicate (length ps) barchar
− Hledger/Cli/Commands/Print.hs
@@ -1,43 +0,0 @@-{-# LANGUAGE CPP #-}-{-| --A ledger-compatible @print@ command.---}--module Hledger.Cli.Commands.Print (-  JournalReport- ,JournalReportItem- ,print'- ,journalReport- ,showTransactions-) where-import Hledger.Data-import Hledger.Cli.Options-#if __GLASGOW_HASKELL__ <= 610-import Prelude hiding ( putStr )-import System.IO.UTF8-#endif----- | A "journal report" is just a list of transactions.-type JournalReport = [JournalReportItem]---- | The data for a single journal report item, representing one transaction.-type JournalReportItem = Transaction---- | Print journal transactions in standard format.-print' :: [Opt] -> [String] -> Journal -> IO ()-print' opts args j = do-  t <- getCurrentLocalTime-  putStr $ showTransactions (optsToFilterSpec opts args t) j--showTransactions :: FilterSpec -> Journal -> String-showTransactions fspec j = journalReportAsText [] fspec $ journalReport [] fspec j--journalReportAsText :: [Opt] -> FilterSpec -> JournalReport -> String -- XXX unlike the others, this one needs fspec not opts-journalReportAsText _ fspec items = concatMap (showTransactionForPrint effective) items-    where effective = EffectiveDate == whichdate fspec--journalReport :: [Opt] -> FilterSpec -> Journal -> JournalReport-journalReport _ fspec j = sortBy (comparing tdate) $ jtxns $ filterJournalTransactions fspec j
− Hledger/Cli/Commands/Register.hs
@@ -1,207 +0,0 @@-{-# LANGUAGE CPP #-}-{-| --A ledger-compatible @register@ command.---}--module Hledger.Cli.Commands.Register (-  RegisterReport- ,RegisterReportItem- ,register- ,registerReport- ,registerReportAsText- ,showPostingWithBalanceForVty- ,tests_Register-) where--import Safe (headMay, lastMay)-import Hledger.Data-import Hledger.Cli.Options-#if __GLASGOW_HASKELL__ <= 610-import Prelude hiding ( putStr )-import System.IO.UTF8-#endif-import Text.ParserCombinators.Parsec----- | A register report is a list of postings to an account or set of--- accounts, with a running total. Postings may be actual postings, or--- virtual postings aggregated over a reporting interval.-type RegisterReport = [RegisterReportItem] -- line items, one per posting---- | The data for a single register report line item, representing one posting.-type RegisterReportItem = (Maybe (Day, String) -- transaction date and description if this is the first posting-                          ,Posting             -- the posting-                          ,MixedAmount         -- balance so far-                          )---- | Print a register report.-register :: [Opt] -> [String] -> Journal -> IO ()-register opts args j = do-  t <- getCurrentLocalTime-  putStr $ registerReportAsText opts $ registerReport opts (optsToFilterSpec opts args t) j---- | Render a register report as plain text suitable for console output.-registerReportAsText :: [Opt] -> RegisterReport -> String-registerReportAsText opts = unlines . map (registerReportItemAsText opts)---- | Render one register report line item as plain text. Eg:--- @--- date (10)  description (20)     account (22)            amount (11)  balance (12)--- DDDDDDDDDD dddddddddddddddddddd aaaaaaaaaaaaaaaaaaaaaa  AAAAAAAAAAA AAAAAAAAAAAA--- ^ displayed for first postings^---   only, otherwise blank--- @-registerReportItemAsText :: [Opt] -> RegisterReportItem -> String-registerReportItemAsText _ (dd, p, b) = concatTopPadded [datedesc, pstr, " ", bal]-    where-      datedesc = case dd of Nothing -> replicate datedescwidth ' '-                            Just (da, de) -> printf "%s %s " date desc-                                where-                                  date = showDate da-                                  desc = printf ("%-"++(show descwidth)++"s") $ elideRight descwidth de :: String-          where-            descwidth = datedescwidth - datewidth - 2-            datedescwidth = 32-            datewidth = 10-      pstr = showPostingForRegister p-      bal = padleft 12 (showMixedAmountOrZeroWithoutPrice b)--showPostingWithBalanceForVty showtxninfo p b = registerReportItemAsText [] $ mkitem showtxninfo p b---- | Get a register report with the specified options for this journal.-registerReport :: [Opt] -> FilterSpec -> Journal -> RegisterReport-registerReport opts fspec j = getitems ps nullposting startbal-    where-      ps | interval == NoInterval = displayableps-         | otherwise              = summarisePostingsByInterval interval depth empty filterspan displayableps-      (precedingps, displayableps, _) = postingsMatchingDisplayExpr (displayExprFromOpts opts)-                                        $ depthClipPostings depth-                                        $ journalPostings-                                        $ filterJournalPostings fspec{depth=Nothing} j-      startbal = sumPostings precedingps-      filterspan = datespan fspec-      (interval, depth, empty) = (intervalFromOpts opts, depthFromOpts opts, Empty `elem` opts)---- | Generate register report line items.-getitems :: [Posting] -> Posting -> MixedAmount -> [RegisterReportItem]-getitems [] _ _ = []-getitems (p:ps) pprev b = i:(getitems ps p b')-    where-      i = mkitem isfirst p b'-      isfirst = ptransaction p /= ptransaction pprev-      b' = b + pamount p---- | Generate one register report line item, from a flag indicating--- whether to include transaction info, a posting, and the current running--- balance.-mkitem :: Bool -> Posting -> MixedAmount -> RegisterReportItem-mkitem False p b = (Nothing, p, b)-mkitem True p b = (ds, p, b)-    where ds = case ptransaction p of Just (Transaction{tdate=da,tdescription=de}) -> Just (da,de)-                                      Nothing -> Just (nulldate,"")---- | Date-sort and split a list of postings into three spans - postings matched--- by the given display expression, and the preceding and following postings.-postingsMatchingDisplayExpr :: Maybe String -> [Posting] -> ([Posting],[Posting],[Posting])-postingsMatchingDisplayExpr d ps = (before, matched, after)-    where -      sorted = sortBy (comparing postingDate) ps-      (before, rest) = break (displayExprMatches d) sorted-      (matched, after) = span (displayExprMatches d) rest---- | Does this display expression allow this posting to be displayed ?--- Raises an error if the display expression can't be parsed.-displayExprMatches :: Maybe String -> Posting -> Bool-displayExprMatches Nothing  _ = True-displayExprMatches (Just d) p = (fromparse $ parsewith datedisplayexpr d) p-                        --- | Parse a hledger display expression, which is a simple date test like--- "d>[DATE]" or "d<=[DATE]", and return a "Posting"-matching predicate.-datedisplayexpr :: GenParser Char st (Posting -> Bool)-datedisplayexpr = do-  char 'd'-  op <- compareop-  char '['-  (y,m,d) <- smartdate-  char ']'-  let date    = parsedate $ printf "%04s/%02s/%02s" y m d-      test op = return $ (`op` date) . postingDate-  case op of-    "<"  -> test (<)-    "<=" -> test (<=)-    "="  -> test (==)-    "==" -> test (==)-    ">=" -> test (>=)-    ">"  -> test (>)-    _    -> mzero- where-  compareop = choice $ map (try . string) ["<=",">=","==","<","=",">"]---- XXX confusing, refactor---- | Convert a list of postings into summary postings. Summary postings--- are one per account per interval and aggregated to the specified depth--- if any.-summarisePostingsByInterval :: Interval -> Maybe Int -> Bool -> DateSpan -> [Posting] -> [Posting]-summarisePostingsByInterval interval depth empty filterspan ps = concatMap summarisespan $ splitSpan interval reportspan-    where-      summarisespan s = summarisePostingsInDateSpan s depth empty (postingsinspan s)-      postingsinspan s = filter (isPostingInDateSpan s) ps-      dataspan = postingsDateSpan ps-      reportspan | empty = filterspan `orDatesFrom` dataspan-                 | otherwise = dataspan---- | Given a date span (representing a reporting interval) and a list of--- postings within it: aggregate the postings so there is only one per--- account, and adjust their date/description so that they will render--- as a summary for this interval.--- --- As usual with date spans the end date is exclusive, but for display--- purposes we show the previous day as end date, like ledger.--- --- When a depth argument is present, postings to accounts of greater--- depth are aggregated where possible.--- --- The showempty flag includes spans with no postings and also postings--- with 0 amount.-summarisePostingsInDateSpan :: DateSpan -> Maybe Int -> Bool -> [Posting] -> [Posting]-summarisePostingsInDateSpan (DateSpan b e) depth showempty ps-    | null ps && (isNothing b || isNothing e) = []-    | null ps && showempty = [summaryp]-    | otherwise = summaryps'-    where-      summaryp = summaryPosting b' ("- "++ showDate (addDays (-1) e'))-      b' = fromMaybe (maybe nulldate postingDate $ headMay ps) b-      e' = fromMaybe (maybe (addDays 1 nulldate) postingDate $ lastMay ps) e-      summaryPosting date desc = nullposting{ptransaction=Just nulltransaction{tdate=date,tdescription=desc}}--      summaryps' = (if showempty then id else filter (not . isZeroMixedAmount . pamount)) summaryps-      summaryps = [summaryp{paccount=a,pamount=balancetoshowfor a} | a <- clippedanames]-      anames = sort $ nub $ map paccount ps-      -- aggregate balances by account, like journalToLedger, then do depth-clipping-      (_,_,exclbalof,inclbalof) = groupPostings ps-      clippedanames = nub $ map (clipAccountName d) anames-      isclipped a = accountNameLevel a >= d-      d = fromMaybe 99999 $ depth-      balancetoshowfor a =-          (if isclipped a then inclbalof else exclbalof) (if null a then "top" else a)---- | Clip the account names to the specified depth in a list of postings.-depthClipPostings :: Maybe Int -> [Posting] -> [Posting]-depthClipPostings depth = map (depthClipPosting depth)---- | Clip a posting's account name to the specified depth.-depthClipPosting :: Maybe Int -> Posting -> Posting-depthClipPosting Nothing p = p-depthClipPosting (Just d) p@Posting{paccount=a} = p{paccount=clipAccountName d a}---tests_Register :: Test-tests_Register = TestList [--         "summarisePostingsByInterval" ~: do-           summarisePostingsByInterval Quarterly Nothing False (DateSpan Nothing Nothing) [] ~?= []--        ]
− Hledger/Cli/Commands/Stats.hs
@@ -1,84 +0,0 @@-{-# LANGUAGE CPP #-}-{-|--Print some statistics for the journal.---}--module Hledger.Cli.Commands.Stats-where-import Hledger.Data-import Hledger.Cli.Options-import qualified Data.Map as Map-#if __GLASGOW_HASKELL__ <= 610-import Prelude hiding ( putStr )-import System.IO.UTF8-#endif----- like Register.summarisePostings--- | Print various statistics for the journal.-stats :: [Opt] -> [String] -> Journal -> IO ()-stats opts args j = do-  today <- getCurrentDay-  t <- getCurrentLocalTime-  let filterspec = optsToFilterSpec opts args t-      l = journalToLedger filterspec j-      reportspan = (ledgerDateSpan l) `orDatesFrom` (datespan filterspec)-      intervalspans = splitSpan (intervalFromOpts opts) reportspan-      showstats = showLedgerStats opts args l today-      s = intercalate "\n" $ map showstats intervalspans-  putStr s--showLedgerStats :: [Opt] -> [String] -> Ledger -> Day -> DateSpan -> String-showLedgerStats _ _ l today span =-    unlines (map (uncurry (printf fmt)) stats)-    where-      fmt = "%-" ++ show w1 ++ "s: %-" ++ show w2 ++ "s"-      w1 = maximum $ map (length . fst) stats-      w2 = maximum $ map (length . show . snd) stats-      stats = [-         ("Journal file", filepath $ journal l)-        ,("Transactions span", printf "%s to %s (%d days)" (start span) (end span) days)-        ,("Last transaction", maybe "none" show lastdate ++ showelapsed lastelapsed)-        ,("Transactions", printf "%d (%0.1f per day)" tnum txnrate)-        ,("Transactions last 30 days", printf "%d (%0.1f per day)" tnum30 txnrate30)-        ,("Transactions last 7 days", printf "%d (%0.1f per day)" tnum7 txnrate7)-        ,("Payees/descriptions", show $ length $ nub $ map tdescription ts)-        ,("Accounts", printf "%d (depth %d)" acctnum acctdepth)-        ,("Commodities", printf "%s (%s)" (show $ length cs) (intercalate ", " cs))-      -- Transactions this month     : %(monthtxns)s (last month in the same period: %(lastmonthtxns)s)-      -- Uncleared transactions      : %(uncleared)s-      -- Days since reconciliation   : %(reconcileelapsed)s-      -- Days since last transaction : %(recentelapsed)s-       ]-           where-             ts = sortBy (comparing tdate) $ filter (spanContainsDate span . tdate) $ jtxns $ journal l-             as = nub $ map paccount $ concatMap tpostings ts-             cs = Map.keys $ canonicaliseCommodities $ nub $ map commodity $ concatMap amounts $ map pamount $ concatMap tpostings ts-             lastdate | null ts = Nothing-                      | otherwise = Just $ tdate $ last ts-             lastelapsed = maybe Nothing (Just . diffDays today) lastdate-             showelapsed Nothing = ""-             showelapsed (Just days) = printf " (%d %s)" days' direction-                                       where days' = abs days-                                             direction | days >= 0 = "days ago"-                                                       | otherwise = "days from now"-             tnum = length ts-             start (DateSpan (Just d) _) = show d-             start _ = ""-             end (DateSpan _ (Just d)) = show d-             end _ = ""-             days = fromMaybe 0 $ daysInSpan span-             txnrate | days==0 = 0-                     | otherwise = fromIntegral tnum / fromIntegral days :: Double-             tnum30 = length $ filter withinlast30 ts-             withinlast30 t = d >= addDays (-30) today && (d<=today) where d = tdate t-             txnrate30 = fromIntegral tnum30 / 30 :: Double-             tnum7 = length $ filter withinlast7 ts-             withinlast7 t = d >= addDays (-7) today && (d<=today) where d = tdate t-             txnrate7 = fromIntegral tnum7 / 7 :: Double-             acctnum = length as-             acctdepth | null as = 0-                       | otherwise = maximum $ map accountNameLevel as-
− Hledger/Cli/Commands/Vty.hs
@@ -1,379 +0,0 @@-{-| --A simple text UI for hledger, based on the vty library.---}--module Hledger.Cli.Commands.Vty-where-import Safe (headDef)-import Graphics.Vty-import Hledger.Data-import Hledger.Cli.Options-import Hledger.Cli.Commands.Balance-import Hledger.Cli.Commands.Register-import Hledger.Cli.Commands.Print---helpmsg = "(b)alance, (r)egister, (p)rint, (right) to drill down, (left) to back up, (q)uit"--instance Show Vty where show = const "a Vty"---- | The application state when running the vty command.-data AppState = AppState {-     av :: Vty                   -- ^ the vty context-    ,aw :: Int                  -- ^ window width-    ,ah :: Int                  -- ^ window height-    ,amsg :: String              -- ^ status message-    ,aopts :: [Opt]              -- ^ command-line opts-    ,aargs :: [String]           -- ^ command-line args at startup-    ,ajournal :: Journal         -- ^ parsed journal-    ,abuf :: [String]            -- ^ lines of the current buffered view-    ,alocs :: [Loc]              -- ^ user's navigation trail within the UI-                                -- ^ never null, head is current location-    } deriving (Show)---- | A location within the user interface.-data Loc = Loc {-     scr :: Screen               -- ^ one of the available screens-    ,sy :: Int                   -- ^ viewport y scroll position-    ,cy :: Int                   -- ^ cursor y position-    ,largs :: [String]           -- ^ command-line args, possibly narrowed for this location-    } deriving (Show)---- | The screens available within the user interface.-data Screen = BalanceScreen     -- ^ like hledger balance, shows accounts-            | RegisterScreen    -- ^ like hledger register, shows transaction-postings-            | PrintScreen       -- ^ like hledger print, shows journal transactions-            -- | LedgerScreen      -- ^ shows the raw journal-              deriving (Eq,Show)---- | Run the vty (curses-style) ui.-vty :: [Opt] -> [String] -> Journal -> IO ()-vty opts args j = do-  v <- mkVty-  DisplayRegion w h <- display_bounds $ terminal v-  let opts' = SubTotal:opts-  t <-  getCurrentLocalTime-  let a = enter t BalanceScreen args-          AppState {-                  av=v-                 ,aw=fromIntegral w-                 ,ah=fromIntegral h-                 ,amsg=helpmsg-                 ,aopts=opts'-                 ,aargs=args-                 ,ajournal=j-                 ,abuf=[]-                 ,alocs=[]-                 }-  go a ---- | Update the screen, wait for the next event, repeat.-go :: AppState -> IO ()-go a@AppState{av=av,aopts=opts} = do-  when (notElem DebugVty opts) $ update av (renderScreen a)-  k <- next_event av-  t <- getCurrentLocalTime-  case k of -    EvResize x y                -> go $ resize x y a-    EvKey (KASCII 'l') [MCtrl]  -> refresh av >> go a{amsg=helpmsg}-    EvKey (KASCII 'b') []       -> go $ resetTrailAndEnter t BalanceScreen a-    EvKey (KASCII 'r') []       -> go $ resetTrailAndEnter t RegisterScreen a-    EvKey (KASCII 'p') []       -> go $ resetTrailAndEnter t PrintScreen a-    EvKey KRight []             -> go $ drilldown t a-    EvKey KEnter []             -> go $ drilldown t a-    EvKey KLeft  []             -> go $ backout t a-    EvKey KUp    []             -> go $ moveUpAndPushEdge a-    EvKey KDown  []             -> go $ moveDownAndPushEdge a-    EvKey KHome  []             -> go $ moveToTop a-    EvKey KUp    [MCtrl]        -> go $ moveToTop a-    EvKey KUp    [MShift]       -> go $ moveToTop a-    EvKey KEnd   []             -> go $ moveToBottom a-    EvKey KDown  [MCtrl]        -> go $ moveToBottom a-    EvKey KDown  [MShift]       -> go $ moveToBottom a-    EvKey KPageUp []            -> go $ prevpage a-    EvKey KBS []                -> go $ prevpage a-    EvKey (KASCII ' ') [MShift] -> go $ prevpage a-    EvKey KPageDown []          -> go $ nextpage a-    EvKey (KASCII ' ') []       -> go $ nextpage a-    EvKey (KASCII 'q') []       -> shutdown av >> return ()---    EvKey KEsc   []           -> shutdown av >> return ()-    _                           -> go a---- app state modifiers---- | The number of lines currently available for the main data display area.-pageHeight :: AppState -> Int-pageHeight a = ah a - 1--setLocCursorY, setLocScrollY :: Int -> Loc -> Loc-setLocCursorY y l = l{cy=y}-setLocScrollY y l = l{sy=y}--cursorY, scrollY, posY :: AppState -> Int-cursorY = cy . loc-scrollY = sy . loc-posY a = scrollY a + cursorY a--setCursorY, setScrollY, setPosY :: Int -> AppState -> AppState-setCursorY _ AppState{alocs=[]} = error' "shouldn't happen" -- silence warnings-setCursorY y a@AppState{alocs=(l:locs)} = a{alocs=(l':locs)} where l' = setLocCursorY y l--setScrollY _ AppState{alocs=[]} = error' "shouldn't happen" -- silence warnings-setScrollY y a@AppState{alocs=(l:locs)} = a{alocs=(l':locs)} where l' = setLocScrollY y l--setPosY _ AppState{alocs=[]}    = error' "shouldn't happen" -- silence warnings-setPosY y a@AppState{alocs=(l:locs)} = a{alocs=(l':locs)}-    where -      l' = setLocScrollY sy $ setLocCursorY cy l-      ph = pageHeight a-      cy = y `mod` ph-      sy = y - cy--updateCursorY, updateScrollY, updatePosY :: (Int -> Int) -> AppState -> AppState-updateCursorY f a = setCursorY (f $ cursorY a) a-updateScrollY f a = setScrollY (f $ scrollY a) a-updatePosY f a = setPosY (f $ posY a) a--resize :: Int -> Int -> AppState -> AppState-resize x y a = setCursorY cy' a{aw=x,ah=y}-    where-      cy = cursorY a-      cy' = min cy (y-2)--moveToTop :: AppState -> AppState-moveToTop = setPosY 0--moveToBottom :: AppState -> AppState-moveToBottom a = setPosY (length $ abuf a) a--moveUpAndPushEdge :: AppState -> AppState-moveUpAndPushEdge a-    | cy > 0 = updateCursorY (subtract 1) a-    | sy > 0 = updateScrollY (subtract 1) a-    | otherwise = a-    where Loc{sy=sy,cy=cy} = head $ alocs a--moveDownAndPushEdge :: AppState -> AppState-moveDownAndPushEdge a-    | sy+cy >= bh = a-    | cy < ph-1 = updateCursorY (+1) a-    | otherwise = updateScrollY (+1) a-    where -      Loc{sy=sy,cy=cy} = head $ alocs a-      ph = pageHeight a-      bh = length $ abuf a---- | Scroll down by page height or until we can just see the last line,--- without moving the cursor, or if we are already scrolled as far as--- possible then move the cursor to the last line.-nextpage :: AppState -> AppState-nextpage (a@AppState{abuf=b})-    | sy < bh-jump = setScrollY sy' a-    | otherwise    = setCursorY (bh-sy) a-    where-      sy = scrollY a-      jump = pageHeight a - 1-      bh = length b-      sy' = min (sy+jump) (bh-jump)---- | Scroll up by page height or until we can just see the first line,--- without moving the cursor, or if we are scrolled as far as possible--- then move the cursor to the first line.-prevpage :: AppState -> AppState-prevpage a-    | sy > 0    = setScrollY sy' a-    | otherwise = setCursorY 0 a-    where-      sy = scrollY a-      jump = pageHeight a - 1-      sy' = max (sy-jump) 0---- | Push a new UI location on to the stack.-pushLoc :: Loc -> AppState -> AppState-pushLoc l a = a{alocs=(l:alocs a)}--popLoc :: AppState -> AppState-popLoc a@AppState{alocs=locs}-    | length locs > 1 = a{alocs=drop 1 locs}-    | otherwise = a--clearLocs :: AppState -> AppState-clearLocs a = a{alocs=[]}--exit :: AppState -> AppState -exit = popLoc--loc :: AppState -> Loc-loc = head . alocs---- | Get the filter pattern args in effect for the current ui location.-currentArgs :: AppState -> [String]-currentArgs (AppState {alocs=Loc{largs=as}:_}) = as-currentArgs (AppState {aargs=as}) = as--screen :: AppState -> Screen-screen a = scr where (Loc scr _ _ _) = loc a---- | Enter a new screen, with possibly new args, adding the new ui location to the stack.-enter :: LocalTime -> Screen -> [String] -> AppState -> AppState-enter t scr@BalanceScreen args a  = updateData t $ pushLoc Loc{scr=scr,sy=0,cy=0,largs=args} a-enter t scr@RegisterScreen args a = updateData t $ pushLoc Loc{scr=scr,sy=0,cy=0,largs=args} a-enter t scr@PrintScreen args a    = updateData t $ pushLoc Loc{scr=scr,sy=0,cy=0,largs=args} a--resetTrailAndEnter :: LocalTime -> Screen -> AppState -> AppState-resetTrailAndEnter t scr a = enter t scr (aargs a) $ clearLocs a---- | Regenerate the display data appropriate for the current screen.-updateData :: LocalTime -> AppState -> AppState-updateData t a@AppState{aopts=opts,ajournal=j} =-    case screen a of-      BalanceScreen  -> a{abuf=lines $ balanceReportAsText opts $ balanceReport opts fspec j}-      RegisterScreen -> a{abuf=lines $ registerReportAsText opts $ registerReport opts fspec j}-      PrintScreen    -> a{abuf=lines $ showTransactions fspec j}-    where fspec = optsToFilterSpec opts (currentArgs a) t--backout :: LocalTime -> AppState -> AppState-backout t a | screen a == BalanceScreen = a-            | otherwise = updateData t $ popLoc a--drilldown :: LocalTime -> AppState -> AppState-drilldown t a =-    case screen a of-      BalanceScreen  -> enter t RegisterScreen [currentAccountName a] a-      RegisterScreen -> scrollToTransaction e $ enter t PrintScreen (currentArgs a) a-      PrintScreen   -> a-    where e = currentTransaction a---- | Get the account name currently highlighted by the cursor on the--- balance screen. Results undefined while on other screens.-currentAccountName :: AppState -> AccountName-currentAccountName a = accountNameAt (abuf a) (posY a)---- | Get the full name of the account being displayed at a specific line--- within the balance command's output.-accountNameAt :: [String] -> Int -> AccountName-accountNameAt buf lineno = accountNameFromComponents anamecomponents-    where-      namestohere = map (drop 22) $ take (lineno+1) buf-      (indented, nonindented) = span (" " `isPrefixOf`) $ reverse namestohere-      thisbranch = indented ++ take 1 nonindented-      anamecomponents = reverse $ map strip $ dropsiblings thisbranch-      dropsiblings :: [AccountName] -> [AccountName]-      dropsiblings [] = []-      dropsiblings (x:xs) = x : dropsiblings xs'-          where-            xs' = dropWhile moreindented xs-            moreindented = (>= myindent) . indentof-            myindent = indentof x-            indentof = length . takeWhile (==' ')---- | If on the print screen, move the cursor to highlight the specified entry--- (or a reasonable guess). Doesn't work.-scrollToTransaction :: Maybe Transaction -> AppState -> AppState-scrollToTransaction Nothing a = a-scrollToTransaction (Just t) a@AppState{abuf=buf} = setCursorY cy $ setScrollY sy a-    where-      entryfirstline = head $ lines $ showTransaction t-      halfph = pageHeight a `div` 2-      y = fromMaybe 0 $ findIndex (== entryfirstline) buf-      sy = max 0 $ y - halfph-      cy = y - sy---- | Get the transaction containing the posting currently highlighted by--- the cursor on the register screen (or best guess). Results undefined--- while on other screens.-currentTransaction :: AppState -> Maybe Transaction-currentTransaction a@AppState{ajournal=j,abuf=buf} = ptransaction p-    where-      p = headDef nullposting $ filter ismatch $ journalPostings j-      ismatch p = postingDate p == parsedate (take 10 datedesc)-                  && take 70 (showPostingWithBalanceForVty False p nullmixedamt) == (datedesc ++ acctamt)-      datedesc = take 32 $ fromMaybe "" $ find (not . (" " `isPrefixOf`)) $ headDef "" rest : reverse above-      acctamt = drop 32 $ headDef "" rest-      (above,rest) = splitAt y buf-      y = posY a---- renderers--renderScreen :: AppState -> Picture-renderScreen (a@AppState{aw=w,ah=h,abuf=buf,amsg=msg}) =-    Picture {pic_cursor = Cursor (fromIntegral cx) (fromIntegral cy)-            ,pic_image = mainimg-                         <->-                         renderStatus w msg-            ,pic_background = Background ' ' def_attr-            }-    where -      (cx, cy) = (0, cursorY a)-      sy = scrollY a---       mainimg = (renderString attr $ unlines $ above)---           <->---           (renderString reverseattr $ thisline)---           <->---           (renderString attr $ unlines $ below)---       (above,(thisline:below))---           | null ls   = ([],[""])---           | otherwise = splitAt y ls---       ls = lines $ fitto w (h-1) $ unlines $ drop as $ buf--- trying for more speed-      mainimg = vert_cat (map (string defaultattr) above)-               <->-               string currentlineattr thisline-               <->-               vert_cat (map (string defaultattr) below)-      (thisline,below) | null rest = (blankline,[])-                       | otherwise = (head rest, tail rest)-      (above,rest) = splitAt cy linestorender-      linestorender = map padclipline $ take (h-1) $ drop sy $ buf ++ replicate h blankline-      padclipline = take w . (++ blankline)-      blankline = replicate w ' '--padClipString :: Int -> Int -> String -> [String]-padClipString h w s = rows-    where-      rows = map padclipline $ take h $ lines s ++ replicate h blankline-      padclipline = take w . (++ blankline)-      blankline = replicate w ' '--renderString :: Attr -> String -> Image-renderString attr s = vert_cat $ map (string attr) rows-    where-      rows = lines $ fitto w h s-      w = maximum $ map length ls-      h = length ls-      ls = lines s--renderStatus :: Int -> String -> Image-renderStatus w = string statusattr . take w . (++ repeat ' ')---- the all-important theming engine!--theme = Restrained--data UITheme = Restrained | Colorful | Blood--(defaultattr, - currentlineattr, - statusattr- ) = case theme of-       Restrained -> (def_attr-                    ,def_attr `with_style` bold-                    ,def_attr `with_style` reverse_video-                    )-       Colorful   -> (def_attr `with_style` reverse_video-                    ,def_attr `with_fore_color` white `with_back_color` red-                    ,def_attr `with_fore_color` black `with_back_color` green-                    )-       Blood      -> (def_attr `with_style` reverse_video-                    ,def_attr `with_fore_color` white `with_back_color` red-                    ,def_attr `with_style` reverse_video-                    )--halfbrightattr = def_attr `with_style` dim-reverseattr = def_attr `with_style` reverse_video-redattr = def_attr `with_fore_color` red-greenattr = def_attr `with_fore_color` green-reverseredattr = def_attr `with_style` reverse_video `with_fore_color` red-reversegreenattr= def_attr `with_style` reverse_video `with_fore_color` green
− Hledger/Cli/Commands/Web.hs
@@ -1,890 +0,0 @@-{-# LANGUAGE CPP, TypeFamilies, QuasiQuotes, TemplateHaskell #-}-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, MultiParamTypeClasses, FlexibleContexts #-}-{-| -A web-based UI.--}--module Hledger.Cli.Commands.Web-where-import Control.Concurrent (forkIO, threadDelay)-import Control.Applicative ((<$>), (<*>))-import Control.Failure-import Data.Either-import System.FilePath ((</>), takeFileName)-import System.IO.Storage (withStore, putValue, getValue)-import Text.ParserCombinators.Parsec (parse)-import Yesod-import Yesod.Helpers.Static-import Text.Hamlet-import Text.Hamlet.RT--import Hledger.Cli.Commands.Add (journalAddTransaction)-import Hledger.Cli.Commands.Balance-import Hledger.Cli.Commands.Print-import Hledger.Cli.Commands.Register-import Hledger.Cli.Options hiding (value)-import Hledger.Cli.Utils-import Hledger.Cli.Version (version)-import Hledger.Data hiding (today)-import Hledger.Read (journalFromPathAndString)-import Hledger.Read.Journal (someamount)-#ifdef MAKE-import Paths_hledger_make (getDataFileName)-#else-import Paths_hledger (getDataFileName)-#endif---defhost           = "localhost"-defport           = 5000-browserstartdelay = 100000 -- microseconds-hledgerorgurl     = "http://hledger.org"-manualurl         = hledgerorgurl++"/MANUAL.html"--data HledgerWebApp = HledgerWebApp {-      appRoot    :: String-     ,appDir     :: FilePath-     ,appOpts    :: [Opt]-     ,appArgs    :: [String]-     ,appJournal :: Journal-     ,appStatic  :: Static-     }--type Handler = GHandler HledgerWebApp HledgerWebApp--mkYesod "HledgerWebApp" [$parseRoutes|-/static          StaticR           Static appStatic-/                IndexR            GET-/journalonly     JournalOnlyR      GET POST-/registeronly    RegisterOnlyR     GET-/accounts        AccountsOnlyR     GET-/journal         JournalR          GET POST-/register        RegisterR         GET POST-/addformrt       AddformRTR        GET-|]--style_css       = StaticRoute ["style.css"] []-hledger_js      = StaticRoute ["hledger.js"] []-jquery_js       = StaticRoute ["jquery.js"] []-jquery_url_js   = StaticRoute ["jquery.url.js"] []-dhtmlxcommon_js = StaticRoute ["dhtmlxcommon.js"] []-dhtmlxcombo_js  = StaticRoute ["dhtmlxcombo.js"] []--instance Yesod HledgerWebApp where approot = appRoot--defaultroute = JournalR---- | A bundle of useful data passed to templates.-data TemplateData = TD {-     here         :: HledgerWebAppRoute -- ^ the current page's route-    ,title        :: String             -- ^ page's title-    ,msg          :: Maybe Html         -- ^ transient message-    ,a            :: String             -- ^ a (acct/desc filter pattern) parameter-    ,p            :: String             -- ^ p (period expression) parameter-    ,j            :: Journal            -- ^ the current journal-    ,today        :: Day                -- ^ the current day-    }--mktd = TD {-      here = IndexR-     ,title = "hledger"-     ,msg = Nothing-     ,a = ""-     ,p = ""-     ,j = nulljournal-     ,today = ModifiedJulianDay 0-     }---- | The web command.-web :: [Opt] -> [String] -> Journal -> IO ()-web opts args j = do-  let host    = defhost-      port    = fromMaybe defport $ portFromOpts opts-      baseurl = fromMaybe (printf "http://%s:%d" host port) $ baseUrlFromOpts opts-  unless (Debug `elem` opts) $ forkIO (browser baseurl) >> return ()-  server baseurl port opts args j--browser :: String -> IO ()-browser baseurl = do-  putStrLn "starting web browser"-  threadDelay browserstartdelay-  openBrowserOn baseurl-  return ()--server :: String -> Int -> [Opt] -> [String] -> Journal -> IO ()-server baseurl port opts args j = do-    printf "starting web server on port %d with base url %s\n" port baseurl-    dir <- getDataFileName "web"-    let app = HledgerWebApp{-               appRoot=baseurl-              ,appDir=dir-              ,appStatic=fileLookupDir (dir </> "static") $ typeByExt -- ++[("hamlet","text/plain")]-              ,appOpts=opts-              ,appArgs=args-              ,appJournal=j-              }-    withStore "hledger" $ do-     putValue "hledger" "journal" j-     basicHandler' port Nothing app---- | Gather all the stuff we want for a typical hledger web request handler.-getHandlerParameters :: Handler-                       (String, String, [Opt], FilterSpec, Journal, Maybe Html, HledgerWebAppRoute)-getHandlerParameters = do-  Just here <- getCurrentRoute-  (a, p, opts, fspec) <- getReportParameters-  (j, err) <- getLatestJournal opts-  msg <- getMessage' err-  return (a, p, opts, fspec, j, msg, here)-    where-      -- | Get current report parameters for this request.-      getReportParameters :: Handler (String, String, [Opt], FilterSpec)-      getReportParameters = do-          app <- getYesod-          t <- liftIO $ getCurrentLocalTime-          a <- fromMaybe "" <$> lookupGetParam "a"-          p <- fromMaybe "" <$> lookupGetParam "p"-          let opts = appOpts app ++ [Period p]-              args = appArgs app ++ [a]-              fspec = optsToFilterSpec opts args t-          return (a, p, opts, fspec)--      -- | 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.-      getLatestJournal :: [Opt] -> Handler (Journal, Maybe String)-      getLatestJournal opts = do-        j <- liftIO $ fromJust `fmap` getValue "hledger" "journal"-        (jE, changed) <- liftIO $ journalReloadIfChanged opts j-        if not changed-         then return (j,Nothing)-         else case jE of-                Right j' -> do liftIO $ putValue "hledger" "journal" j'-                               return (j',Nothing)-                Left e  -> do setMessage $ string "error while reading" {- ++ ": " ++ e-}-                              return (j, Just e)--      -- | Helper to work around a yesod feature (can't set and get a message in the same request.)-      getMessage' :: Maybe String -> Handler (Maybe Html)-      getMessage' newmsgstr = do-        oldmsg <- getMessage-        return $ maybe oldmsg (Just . string) newmsgstr--------------------------------------------------------------------------- handlers & templates--getIndexR :: Handler ()-getIndexR = redirect RedirectTemporary defaultroute---------------------------------------------------------------------------- | A combined accounts and journal view.-getJournalR :: Handler RepHtml-getJournalR = do-  (a, p, opts, fspec, j, msg, here) <- getHandlerParameters-  today <- liftIO getCurrentDay-  -- app <- getYesod-  -- t <- liftIO $ getCurrentLocalTime-  let -- args = appArgs app-      -- fspec' = optsToFilterSpec opts args t-      br = balanceReportAsHtml opts td $ balanceReport opts fspec j-      jr = journalReportAsHtml opts td $ journalReport opts fspec j-      td = mktd{here=here, title="hledger journal", msg=msg, a=a, p=p, j=j, today=today}-      editform' = editform td $ jtext j-  hamletToRepHtml $ pageLayout td [$hamlet|-%div.ledger- %div.accounts!style=float:left;  ^br^- ^navlinks.td^- ^addform.td^- ^editform'^- ^importform^- %div#transactions.journal-  ^filterform.td^-  ^jr^-|]--postJournalR :: Handler RepPlain-postJournalR = postJournalOnlyR---------------------------------------------------------------------------- | A combined accounts and register view.-getRegisterR :: Handler RepHtml-getRegisterR = do-  (a, p, opts, fspec, j, msg, here) <- getHandlerParameters-  today <- liftIO getCurrentDay-  -- app <- getYesod-  -- t <- liftIO $ getCurrentLocalTime-  let -- args = appArgs app-      -- opts' = Empty:opts-      -- fspec' = optsToFilterSpec opts' args t-      br = balanceReportAsHtml opts td $ balanceReport opts fspec j-      rr = registerReportAsHtml opts td $ registerReport opts fspec j-      td = mktd{here=here, title="hledger register", msg=msg, a=a, p=p, j=j, today=today}-      editform' = editform td $ jtext j-  hamletToRepHtml $ pageLayout td [$hamlet|-%div.ledger- %div.accounts!style=float:left;  ^br^- ^navlinks.td^- ^addform.td^- ^editform'^- ^importform^- %div#transactions.register-  ^filterform.td^-  ^rr^-|]--postRegisterR :: Handler RepPlain-postRegisterR = postJournalOnlyR---------------------------------------------------------------------------- | A simple accounts and balances view like hledger balance.-getAccountsOnlyR :: Handler RepHtml-getAccountsOnlyR = do-  (a, p, opts, fspec, j, msg, here) <- getHandlerParameters-  today <- liftIO getCurrentDay-  let td = mktd{here=here, title="hledger accounts", msg=msg, a=a, p=p, j=j, today=today}-  hamletToRepHtml $ pageLayout td $ balanceReportAsHtml opts td $ balanceReport opts fspec j---- | Render a balance report as HTML.-balanceReportAsHtml :: [Opt] -> TemplateData -> BalanceReport -> Hamlet HledgerWebAppRoute-balanceReportAsHtml _ td@TD{here=here,a=a,p=p} (items,total) = [$hamlet|-%table.balancereport- %tr-  %td-   ^accountsheading^- $forall items i-  ^itemAsHtml' i^- %tr.totalrule-  %td!colspan=2- %tr-  %td-  %td!align=right $mixedAmountAsHtml.total$-|]- where-   accountsheading = [$hamlet|-                      accounts-                      \ $-                      %span#showmoreaccounts ^showmore^ ^showall^-                      <br />-                      <br />-                      |]-       where-         filteringaccts = not $ null a-         showmore = case (filteringaccts, items) of-                      -- cunning parent account logic-                      (True, ((acct, _, _, _):_)) ->-                          let a' = if isAccountRegex a then a else acct-                              a'' = accountNameToAccountRegex $ parentAccountName $ accountRegexToAccountName a'-                              parenturl = (here, [("a",a''), ("p",p)])-                          in [$hamlet|-                              \ | $-                              %a!href=@?parenturl@ show more &uarr;-                              |]-                      _ -> nulltemplate-         showall = if filteringaccts-                    then [$hamlet|-                          \ | $-                          %a!href=@?allurl@ show all-                          |]-                    else nulltemplate-             where allurl = (here, [("p",p)])-   itemAsHtml' = itemAsHtml td-   itemAsHtml :: TemplateData -> BalanceReportItem -> Hamlet HledgerWebAppRoute-   itemAsHtml TD{p=p} (acct, adisplay, adepth, abal) = [$hamlet|-     %tr.item.$current$-      %td.account-       $indent$-       %a!href=$aurl$ $adisplay$-      %td.balance!align=right $mixedAmountAsHtml.abal$-     |] where-       current = "" -- if not (null a) && containsRegex a acct then "current" else ""-       indent = preEscapedString $ concat $ replicate (2 * adepth) "&nbsp;"-       aurl = printf ".?a=%s%s" (accountNameToAccountRegex acct) p' :: String-       p' = if null p then "" else printf "&p=%s" p--accountNameToAccountRegex :: String -> String-accountNameToAccountRegex "" = ""-accountNameToAccountRegex a = printf "^%s(:|$)" a--accountRegexToAccountName :: String -> String-accountRegexToAccountName = gsubRegexPR "^\\^(.*?)\\(:\\|\\$\\)$" "\\1"--isAccountRegex  :: String -> Bool-isAccountRegex s = take 1 s == "^" && (take 5 $ reverse s) == ")$|:("---------------------------------------------------------------------------- | A basic journal view, like hledger print, with editing.-getJournalOnlyR :: Handler RepHtml-getJournalOnlyR = do-  (a, p, opts, fspec, j, msg, here) <- getHandlerParameters-  today <- liftIO getCurrentDay-  let td = mktd{here=here, title="hledger journal", msg=msg, a=a, p=p, j=j, today=today}-      editform' = editform td $ jtext j-      txns = journalReportAsHtml opts td $ journalReport opts fspec j-  hamletToRepHtml $ pageLayout td [$hamlet|-%div.journal- %div.nav2-  %a#addformlink!href!onclick="return addformToggle()" add one transaction-  \ | $-  %a#editformlink!href!onclick="return editformToggle()" edit the whole journal- ^addform.td^- ^editform'^- #transactions ^txns^-|]---- | Render a journal report as HTML.-journalReportAsHtml :: [Opt] -> TemplateData -> JournalReport -> Hamlet HledgerWebAppRoute-journalReportAsHtml _ td items = [$hamlet|-%table.journalreport- $forall number.items i-  ^itemAsHtml' i^-|]- where-   number = zip [1..]-   itemAsHtml' = itemAsHtml td-   itemAsHtml :: TemplateData -> (Int, JournalReportItem) -> Hamlet HledgerWebAppRoute-   itemAsHtml _ (n, t) = [$hamlet|-     %tr.item.$evenodd$-      %td.transaction-       %pre $txn$-     |] where-       evenodd = if even n then "even" else "odd"-       txn = trimnl $ showTransaction t where trimnl = reverse . dropWhile (=='\n') . reverse--addform :: TemplateData -> Hamlet HledgerWebAppRoute-addform td = [$hamlet|-%script!type=text/javascript- $$(document).ready(function() {-    /* dhtmlxcombo setup */-    window.dhx_globalImgPath="../static/images/";-    var desccombo  = new dhtmlXCombo("description");-    var acct1combo = new dhtmlXCombo("account1");-    var acct2combo = new dhtmlXCombo("account2");-    desccombo.enableFilteringMode(true);-    acct1combo.enableFilteringMode(true);-    acct2combo.enableFilteringMode(true);-    desccombo.setSize(300);-    acct1combo.setSize(300);-    acct2combo.setSize(300);-    /* desccombo.enableOptionAutoHeight(true, 20); */-    /* desccombo.setOptionHeight(200); */- });-%form#addform!method=POST!style=display:none;-  %table.form-   %tr-    %td!colspan=4-     %table-      %tr#descriptionrow-       %td-        Date:-       %td-        %input.textinput!size=15!name=date!value=$date$-       %td!style=padding-left:1em;-        Description:-       %td-        %select!id=description!name=description-         %option-         $forall descriptions d-          %option!value=$d$ $d$-      %tr.helprow-       %td-       %td-        .help $datehelp$ $-       %td-       %td-        .help $deschelp$-   ^postingsfields.td^-   %tr#addbuttonrow-    %td!colspan=4-     %input!type=hidden!name=action!value=add-     %input!type=submit!name=submit!value="add transaction"-|]- where-  -- datehelplink = helplink "dates" "..."-  datehelp = "eg: 2010/7/20"-  deschelp = "eg: supermarket (optional)"-  date = "today"-  descriptions = sort $ nub $ map tdescription $ jtxns $ j td--postingsfields :: TemplateData -> Hamlet HledgerWebAppRoute-postingsfields td = [$hamlet|- ^p1^- ^p2^-|]-  where-    p1 = postingfields td 1-    p2 = postingfields td 2--postingfields :: TemplateData -> Int -> Hamlet HledgerWebAppRoute-postingfields td n = [$hamlet|- %tr#postingrow-  %td!align=right $acctlabel$:-  %td-   %select!id=$acctvar$!name=$acctvar$-    %option-    $forall acctnames a-     %option!value=$a$ $a$-  ^amtfield^- %tr.helprow-  %td-  %td-   .help $accthelp$-  %td-  %td-   .help $amthelp$-|]- where-  numbered = (++ show n)-  acctvar = numbered "account"-  amtvar = numbered "amount"-  acctnames = sort $ journalAccountNamesUsed $ j td-  (acctlabel, accthelp, amtfield, amthelp)-       | n == 1     = ("To account"-                     ,"eg: expenses:food"-                     ,[$hamlet|-                       %td!style=padding-left:1em;-                        Amount:-                       %td-                        %input.textinput!size=15!name=$amtvar$!value=""-                       |]-                     ,"eg: $6"-                     )-       | otherwise = ("From account"-                     ,"eg: assets:bank:checking"-                     ,nulltemplate-                     ,""-                     )--editform :: TemplateData -> String -> Hamlet HledgerWebAppRoute-editform _ content = [$hamlet|- %form#editform!method=POST!style=display:none;-  %table.form#editform-   %tr-    %td!colspan=2-     %textarea!name=text!rows=30!cols=80-      $content$-   %tr#addbuttonrow-    %td-     %span.help ^formathelp^-    %td!align=right-     %span.help Are you sure ? This will overwrite the journal. $-     %input!type=hidden!name=action!value=edit-     %input!type=submit!name=submit!value="save journal"-     \ or $-     %a!href!onclick="return editformToggle()" cancel-|]-  where-    formathelp = helplink "file-format" "file format help"--importform :: Hamlet HledgerWebAppRoute-importform = [$hamlet|- %form#importform!method=POST!style=display:none;-  %table.form-   %tr-    %td-     %input!type=file!name=file-     %input!type=hidden!name=action!value=import-     %input!type=submit!name=submit!value="import from file"-     \ or $-     %a!href!onclick="return importformToggle()" cancel-|]--postJournalOnlyR :: Handler RepPlain-postJournalOnlyR = do-  action <- runFormPost' $ maybeStringInput "action"-  case action of Just "edit"   -> postEditForm-                 Just "import" -> postImportForm-                 _             -> postAddForm---- | Handle a journal add form post.-postAddForm :: Handler RepPlain-postAddForm = do-  (_, _, opts, _, _, _, _) <- getHandlerParameters-  today <- liftIO getCurrentDay-  -- get form input values. M means a Maybe value.-  (dateM, descM, acct1M, amt1M, acct2M, amt2M) <- runFormPost'-    $ (,,,,,)-    <$> maybeStringInput "date"-    <*> maybeStringInput "description"-    <*> maybeStringInput "account1"-    <*> maybeStringInput "amount1"-    <*> maybeStringInput "account2"-    <*> maybeStringInput "amount2"-  -- supply defaults and parse date and amounts, or get errors.-  let dateE = maybe (Left "date required") (either (\e -> Left $ showDateParseError e) Right . fixSmartDateStrEither today) dateM-      descE = Right $ fromMaybe "" descM-      acct1E = maybe (Left "to account required") Right acct1M-      acct2E = maybe (Left "from account required") Right acct2M-      amt1E = maybe (Left "amount required") (either (const $ Left "could not parse amount") Right . parse someamount "") amt1M-      amt2E = maybe (Right missingamt)       (either (const $ Left "could not parse amount") Right . parse someamount "") amt2M-      strEs = [dateE, descE, acct1E, acct2E]-      amtEs = [amt1E, amt2E]-      [date,desc,acct1,acct2] = rights strEs-      [amt1,amt2] = rights amtEs-      errs = lefts strEs ++ lefts amtEs-      -- if no errors so far, generate a transaction and balance it or get the error.-      tE | not $ null errs = Left errs-         | otherwise = either (\e -> Left ["unbalanced postings: " ++ (head $ lines e)]) Right-                        (balanceTransaction $ nulltransaction {-                           tdate=parsedate date-                          ,teffectivedate=Nothing-                          ,tstatus=False-                          ,tcode=""-                          ,tdescription=desc-                          ,tcomment=""-                          ,tpostings=[-                            Posting False acct1 amt1 "" RegularPosting Nothing-                           ,Posting False acct2 amt2 "" RegularPosting Nothing-                           ]-                          ,tpreceding_comment_lines=""-                          })-  -- display errors or add transaction-  case tE of-   Left errs -> do-    -- save current form values in session-    setMessage $ string $ intercalate "; " errs-    redirect RedirectTemporary RegisterR--   Right t -> do-    let t' = txnTieKnot t -- XXX move into balanceTransaction-    j <- liftIO $ fromJust `fmap` getValue "hledger" "journal"-    liftIO $ journalAddTransaction j opts t'-    setMessage $ string $ printf "Added transaction:\n%s" (show t')-    redirect RedirectTemporary RegisterR---- | Handle a journal edit form post.-postEditForm :: Handler RepPlain-postEditForm = do-  -- get form input values, or basic validation errors. E means an Either value.-  textM  <- runFormPost' $ maybeStringInput "text"-  let textE = maybe (Left "No value provided") Right textM-  -- display errors or add transaction-  case textE of-   Left errs -> do-    -- XXX should save current form values in session-    setMessage $ string errs-    redirect RedirectTemporary JournalR--   Right t' -> do-    -- try to avoid unnecessary backups or saving invalid data-    j <- liftIO $ fromJust `fmap` getValue "hledger" "journal"-    filechanged' <- liftIO $ journalFileIsNewer j-    let f = filepath j-        told = jtext j-        tnew = filter (/= '\r') t'-        changed = tnew /= told || filechanged'---    changed <- liftIO $ writeFileWithBackupIfChanged f t''-    if not changed-     then do-       setMessage $ string $ "No change"-       redirect RedirectTemporary JournalR-     else do-      jE <- liftIO $ journalFromPathAndString Nothing f tnew-      either-       (\e -> do-          setMessage $ string e-          redirect RedirectTemporary JournalR)-       (const $ do-          liftIO $ writeFileWithBackup f tnew-          setMessage $ string $ printf "Saved journal %s\n" (show f)-          redirect RedirectTemporary JournalR)-       jE---- | Handle an import page post.-postImportForm :: Handler RepPlain-postImportForm = do-  setMessage $ string $ "can't handle file upload yet"-  redirect RedirectTemporary JournalR-  -- -- get form input values, or basic validation errors. E means an Either value.-  -- fileM <- runFormPost' $ maybeFileInput "file"-  -- let fileE = maybe (Left "No file provided") Right fileM-  -- -- display errors or import transactions-  -- case fileE of-  --  Left errs -> do-  --   setMessage $ string errs-  --   redirect RedirectTemporary JournalR--  --  Right s -> do-  --    setMessage $ string $ s-  --    redirect RedirectTemporary JournalR---------------------------------------------------------------------------- | A simple postings view like hledger register.-getRegisterOnlyR :: Handler RepHtml-getRegisterOnlyR = do-  (a, p, opts, fspec, j, msg, here) <- getHandlerParameters-  today <- liftIO getCurrentDay-  let td = mktd{here=here, title="hledger register", msg=msg, a=a, p=p, j=j, today=today}-  hamletToRepHtml $ pageLayout td $ registerReportAsHtml opts td $ registerReport opts fspec j---- | Render a register report as HTML.-registerReportAsHtml :: [Opt] -> TemplateData -> RegisterReport -> Hamlet HledgerWebAppRoute-registerReportAsHtml _ td items = [$hamlet|-%table.registerreport- %tr.headings-  ^headings^- $forall number.items i-  ^itemAsHtml' i^-|]- where-   number = zip [1..]-   headings = [$hamlet|-               %th.date!align=left Date-               %th.description!align=left Description-               %th.account!align=left Account-               %th.amount!align=right Amount-               %th.balance!align=right Balance-               |]-   itemAsHtml' = itemAsHtml td-   itemAsHtml :: TemplateData -> (Int, RegisterReportItem) -> Hamlet HledgerWebAppRoute-   itemAsHtml TD{p=p} (n, (ds, posting, b)) = [$hamlet|-     %tr.item.$evenodd$.$firstposting$-      %td.date $date$-      %td.description $desc$-      %td.account-       %a!href=$aurl$ $acct$-      %td.amount!align=right $mixedAmountAsHtml.pamount.posting$-      %td.balance!align=right $mixedAmountAsHtml.b$-     |] where-       evenodd = if even n then "even" else "odd"-       (firstposting, date, desc) = case ds of Just (da, de) -> ("firstposting", show da, de)-                                               Nothing -> ("", "", "")-       acct = paccount posting-       aurl = printf ".?a=%s%s" (accountNameToAccountRegex acct) p' :: String-       p' = if null p then "" else printf "&p=%s" p--mixedAmountAsHtml b = preEscapedString $ addclass $ intercalate "<br>" $ lines $ show b-    where addclass = printf "<span class=\"%s\">%s</span>" c-          c = case isNegativeMixedAmount b of Just True -> "negative amount"-                                              _         -> "positive amount"---------------------------------------------------------------------------- | A standalone journal edit form page.-getEditR :: Handler RepHtml-getEditR = do-  (a, p, _, _, _, msg, here) <- getHandlerParameters-  today <- liftIO getCurrentDay-  -- reload journal's text without parsing, if changed     -- XXX are we doing this right ?-  j <- liftIO $ fromJust `fmap` getValue "hledger" "journal"-  changed <- liftIO $ journalFileIsNewer j-  s <- liftIO $ if changed then readFile (filepath j) else return (jtext j) -- XXX readFile may throw an error-  let td = mktd{here=here, title="hledger journal edit", msg=msg, a=a, p=p, j=j, today=today}-  hamletToRepHtml $ pageLayout td $ editform td s---------------------------------------------------------------------------- | Get the add form from template files reloaded at run-time.-getAddformRTR :: Handler RepHtml-getAddformRTR = do-  (a, p, _, _, j, msg, here) <- getHandlerParameters-  today <- liftIO getCurrentDay-  let td = mktd{here=here, title="hledger add transaction", msg=msg, a=a, p=p, j=j, today=today}-      descriptions = sort $ nub $ map tdescription $ jtxns j-      acctnames = sort $ journalAccountNamesUsed j-      postingData n = [-                       (["acctlabel"], hdstring acctlabel)-                      ,(["acctvar"],   hdstring acctvar)-                      ,(["acctnames"], hdstringlist acctnames)-                      ,(["amtfield"],  HDHtml $ renderHamlet' amtfield)-                      ,(["accthelp"],  hdstring accthelp)-                      ,(["amthelp"],   hdstring amthelp)-                      ] :: HamletMap HledgerWebAppRoute-          where-            numbered = (++ show n)-            acctvar = numbered "account"-            amtvar = numbered "amount"-            (acctlabel, accthelp, amtfield, amthelp)-                | n == 1     = ("To account"-                              ,"eg: expenses:food"-                              ,[$hamlet|-                                %td!style=padding-left:1em;-                                 Amount:-                                %td-                                 %input.textinput!size=15!name=$amtvar$!value=""-                                |]-                              ,"eg: $6"-                              )-                | otherwise = ("From account"-                              ,"eg: assets:bank:checking"-                              ,nulltemplate-                              ,""-                              )-  pfields1 <- renderHamletFile "addformpostingfields.hamlet" (postingData 1)-  pfields2 <- renderHamletFile "addformpostingfields.hamlet" (postingData 2)-  addform  <- renderHamletFile "addform.hamlet" ([-                                                 (["date"], hdstring "today")-                                                ,(["desc"], hdstring "")-                                                ,(["descriptions"], hdstringlist descriptions)-                                                ,(["datehelp"], hdstring "eg: 2010/7/20")-                                                ,(["deschelp"], hdstring "eg: supermarket (optional)")-                                                ,(["postingfields1"], HDHtml pfields1)-                                                ,(["postingfields2"], HDHtml pfields2)-                                                ] :: HamletMap HledgerWebAppRoute)-  hamletToRepHtml $ pageLayout td $ htmlAsHamlet addform---- | Convert a string to a hamlet HDHtml data item.-hdstring :: String -> HamletData HledgerWebAppRoute-hdstring = HDHtml . string---- | Convert a simple list of strings to hamlet's complicated HDList type.-hdstringlist :: [String] -> HamletData HledgerWebAppRoute-hdstringlist ss = HDList [ [([], hdstring s)] | s <- ss ]--instance Failure HamletException Handler-    where failure = error' . show--renderHamletFile :: FilePath -> HamletMap HledgerWebAppRoute -> Handler Html-renderHamletFile hfile hmap = do-  hrt <- readHamletFile hfile >>= parseHamletRT defaultHamletSettings-  renderHamletRT hrt hmap renderurlwithparams--renderurlwithparams u [] = show u-renderurlwithparams u ps = show u ++ "?" ++ intercalate "&" [k++"="++v | (k,v) <- ps]--readHamletFile :: FilePath -> Handler String-readHamletFile hfile = do-  dir <- ((</> "templates") . appDir) `fmap` getYesod-  liftIO $ readFile $ dir </> hfile--htmlAsHamlet :: Html -> Hamlet HledgerWebAppRoute-htmlAsHamlet h = [$hamlet|$h$|]--parseHamletRT' :: Failure HamletException m => String -> m HamletRT-parseHamletRT' s = parseHamletRT defaultHamletSettings s--renderHamletRT' :: Failure HamletException m => HamletMap HledgerWebAppRoute -> HamletRT -> m Html-renderHamletRT' m h = renderHamletRT h m renderurlwithparams--renderHamlet' :: Hamlet HledgerWebAppRoute -> Html-renderHamlet' h = h renderurlwithparams---- hamletToHamletRT ::  Failure HamletException m => Hamlet HledgerWebAppRoute -> m HamletRT--- hamletToHamletRT h = stringToHamletRT $ show $ unsafeByteString $ renderHamlet show h---------------------------------------------------------------------------- | Wrap a template with the standard hledger web ui page layout.-pageLayout :: TemplateData -> Hamlet HledgerWebAppRoute -> Hamlet HledgerWebAppRoute-pageLayout td@TD{title=basetitle, msg=msg, p=p, j=j, today=today} content = [$hamlet|-!!!-%html- %head-  %title $title'$-  %meta!http-equiv=Content-Type!content=$metacontent$-  %script!type=text/javascript!src=@StaticR.jquery_js@-  %script!type=text/javascript!src=@StaticR.jquery_url_js@-  %script!type=text/javascript!src=@StaticR.dhtmlxcommon_js@-  %script!type=text/javascript!src=@StaticR.dhtmlxcombo_js@-  %script!type=text/javascript!src=@StaticR.hledger_js@-  %link!rel=stylesheet!type=text/css!media=all!href=@StaticR.style_css@- %body-  ^navbar.td^-  #messages $m$-  #content-   ^content^-|]- where m = fromMaybe (string "") msg-       metacontent = "text/html; charset=utf-8"-       (journaltitle, _) = journalTitleInfo j p today-       title' = basetitle ++ " - " ++ journaltitle--navbar :: TemplateData -> Hamlet HledgerWebAppRoute-navbar TD{p=p,j=j,today=today} = [$hamlet|- #navbar-  %a.topleftlink!href=$hledgerorgurl$-   hledger-   <br />-   $version$-  %a.toprightlink!href=$manualurl$!target=hledgerhelp manual-  %h1 $journaltitle$-  \ $-  %span#journalinfo $journalinfo$-|]-  where (journaltitle, journalinfo) = journalTitleInfo j p today---- | Generate journal- and context-specific title and info strings for display.-journalTitleInfo :: Journal -> String -> Day -> (String, String)-journalTitleInfo j p today = (journaltitle, journalinfo)-  where-    journaltitle = printf "%s" (takeFileName $ filepath j) :: String-    journalinfo  = printf "%s" (showspan span) :: String-    span = either (const $ DateSpan Nothing Nothing) snd (parsePeriodExpr today p)-    showspan (DateSpan Nothing Nothing) = ""-    showspan s = " (" ++ dateSpanAsText s ++ ")"--navlinks :: TemplateData -> Hamlet HledgerWebAppRoute-navlinks td = [$hamlet|- #navlinks-  ^accountsjournallink^-  \ | $-  ^accountsregisterlink^-  \ | $-  %a#addformlink!href!onclick="return addformToggle()" add transaction-  %a#importformlink!href!onclick="return importformToggle()"!style=display:none; import transactions-  \ | $-  %a#editformlink!href!onclick="return editformToggle()" edit journal-|]---  \ | $- where-   accountsjournallink  = navlink td "journal" JournalR-   accountsregisterlink = navlink td "register" RegisterR--navlink :: TemplateData -> String -> HledgerWebAppRoute -> Hamlet HledgerWebAppRoute-navlink TD{here=here,a=a,p=p} s dest = [$hamlet|%a#$s$link.$style$!href=@?u@ $s$|]- where u = (dest, concat [(if null a then [] else [("a", a)])-                         ,(if null p then [] else [("p", p)])])-       style | dest == here = "navlinkcurrent"-             | otherwise    = "navlink"--filterform :: TemplateData -> Hamlet HledgerWebAppRoute-filterform TD{here=here,a=a,p=p} = [$hamlet|- #filterformdiv-  %form#filterform.form!method=GET!style=display:$visible$;-   %table.form-    %tr.$filteringperiodclass$-     %td-      filter by period:-      \ $-     %td-      %input!name=p!size=60!value=$p$-      ^phelp^-      \ $-     %td!align=right-      ^stopfilteringperiod^-    %tr.$filteringclass$-     %td-      filter by account/description:-      \ $-     %td-      %input!name=a!size=60!value=$a$-      ^ahelp^-      \ $-      %input!type=submit!value=filter $-      \ $-     %td!align=right-      ^stopfiltering^-|]- where-  ahelp = helplink "filter-patterns" "?"-  phelp = helplink "period-expressions" "?"-  filtering = not $ null a-  filteringperiod = not $ null p-  visible = "block"-  filteringclass = if filtering then "filtering" else ""-  filteringperiodclass = if filteringperiod then "filtering" else ""-  stopfiltering = if filtering then [$hamlet|%a#stopfilterlink!href=@?u@ stop filtering acct/desc|] else nulltemplate-      where u = (here, if filteringperiod then [("p", p)] else [])-  stopfilteringperiod = if filteringperiod then [$hamlet|%a#stopfilterlink!href=@?u@ stop filtering period|] else nulltemplate-      where u = (here, if filtering then [("a", a)] else [])--helplink :: String -> String -> Hamlet HledgerWebAppRoute-helplink topic label = [$hamlet|%a!href=$u$!target=hledgerhelp $label$|]-    where u = manualurl ++ if null topic then "" else '#':topic--nulltemplate = [$hamlet||]-
+ Hledger/Cli/Convert.hs view
@@ -0,0 +1,390 @@+{-|+Convert account data in CSV format (eg downloaded from a bank) to journal+format, and print it on stdout. See the manual for more details.+-}++module Hledger.Cli.Convert where+import Hledger.Cli.Options (Opt(Debug), progname_cli)+import Hledger.Cli.Version (progversionstr)+import Hledger.Data.Types (Journal,AccountName,Transaction(..),Posting(..),PostingType(..))+import Hledger.Data.Utils (strip, spacenonewline, restofline, parseWithCtx, assertParse, assertParseEqual, error')+import Hledger.Read.JournalReader (someamount,ledgeraccountname)+import Hledger.Data.Journal (nullctx)+import Hledger.Data.Amount (nullmixedamt)+import Safe (atDef, maximumDef)+import System.IO (stderr)+import Text.CSV (parseCSVFromFile, printCSV)+import Text.Printf (hPrintf)+import Text.RegexPR (matchRegexPR, gsubRegexPR)+import Data.Maybe+import Hledger.Data.Dates (firstJust, showDate, parsedate)+import System.Locale (defaultTimeLocale)+import Data.Time.Format (parseTime)+import Control.Monad (when, guard, liftM)+import Safe (readDef, readMay)+import System.Directory (doesFileExist)+import System.Exit (exitFailure)+import System.FilePath (takeBaseName, replaceExtension)+import Text.ParserCombinators.Parsec+import Test.HUnit+++{- |+A set of data definitions and account-matching patterns sufficient to+convert a particular CSV data file into meaningful journal transactions. See above.+-}+data CsvRules = CsvRules {+      dateField :: Maybe FieldPosition,+      statusField :: Maybe FieldPosition,+      codeField :: Maybe FieldPosition,+      descriptionField :: Maybe FieldPosition,+      amountField :: Maybe FieldPosition,+      currencyField :: Maybe FieldPosition,+      baseCurrency :: Maybe String,+      baseAccount :: AccountName,+      accountRules :: [AccountRule]+} deriving (Show, Eq)++nullrules = CsvRules {+      dateField=Nothing,+      statusField=Nothing,+      codeField=Nothing,+      descriptionField=Nothing,+      amountField=Nothing,+      currencyField=Nothing,+      baseCurrency=Nothing,+      baseAccount="unknown",+      accountRules=[]+}++type FieldPosition = Int++type AccountRule = (+   [(String, Maybe String)] -- list of regex match patterns with optional replacements+  ,AccountName              -- account name to use for a transaction matching this rule+  )++type CsvRecord = [String]+++-- | Read the CSV file named as an argument and print equivalent journal transactions,+-- using/creating a .rules file.+convert :: [Opt] -> [String] -> Journal -> IO ()+convert opts args _ = do+  when (null args) $ error' "please specify a csv data file."+  let csvfile = head args+  csvparse <- parseCSVFromFile csvfile+  let records = case csvparse of+                  Left e -> error' $ show e+                  Right rs -> reverse $ filter (/= [""]) rs+  let debug = Debug `elem` opts+      rulesfile = rulesFileFor csvfile+  exists <- doesFileExist rulesfile+  if (not exists) then do+                  hPrintf stderr "creating conversion rules file %s, edit this file for better results\n" rulesfile+                  writeFile rulesfile initialRulesFileContent+   else+      hPrintf stderr "using conversion rules file %s\n" rulesfile+  rules <- liftM (either (error'.show) id) $ parseCsvRulesFile rulesfile+  when debug $ hPrintf stderr "rules: %s\n" (show rules)+  let requiredfields = max 2 (maxFieldIndex rules + 1)+      badrecords = take 1 $ filter ((< requiredfields).length) records+  if null badrecords+   then mapM_ (printTxn debug rules) records+   else do+     hPrintf stderr (unlines [+                      "Warning, at least one CSV record does not contain a field referenced by the"+                     ,"conversion rules file, or has less than two fields. Are you converting a"+                     ,"valid CSV file ? First bad record:\n%s"+                     ]) (show $ head badrecords)+     exitFailure++-- | The highest (0-based) field index referenced in the field+-- definitions, or -1 if no fields are defined.+maxFieldIndex :: CsvRules -> Int+maxFieldIndex r = maximumDef (-1) $ catMaybes [+                   dateField r+                  ,statusField r+                  ,codeField r+                  ,descriptionField r+                  ,amountField r+                  ,currencyField r+                  ]++rulesFileFor :: FilePath -> FilePath+rulesFileFor csvfile = replaceExtension csvfile ".rules"++initialRulesFileContent :: String+initialRulesFileContent =+    "# csv conversion rules file generated by "++(progversionstr progname_cli)++"\n" +++    "# Add rules to this file for more accurate conversion, see\n"+++    "# http://hledger.org/MANUAL.html#convert\n" +++    "\n" +++    "base-account assets:bank:checking\n" +++    "date-field 0\n" +++    "description-field 4\n" +++    "amount-field 1\n" +++    "currency $\n" +++    "\n" +++    "# account-assigning rules\n" +++    "\n" +++    "SPECTRUM\n" +++    "expenses:health:gym\n" +++    "\n" +++    "ITUNES\n" +++    "BLKBSTR=BLOCKBUSTER\n" +++    "expenses:entertainment\n" +++    "\n" +++    "(TO|FROM) SAVINGS\n" +++    "assets:bank:savings\n"++-- rules file parser++parseCsvRulesFile :: FilePath -> IO (Either ParseError CsvRules)+parseCsvRulesFile f = do+  s <- readFile f+  return $ parseCsvRules f s++parseCsvRules :: FilePath -> String -> Either ParseError CsvRules+parseCsvRules rulesfile s = runParser csvrulesfile nullrules{baseAccount=takeBaseName rulesfile} rulesfile s++csvrulesfile :: GenParser Char CsvRules CsvRules+csvrulesfile = do+  many blankorcommentline+  many definitions+  r <- getState+  ars <- many accountrule+  many blankorcommentline+  eof+  return r{accountRules=ars}++-- | Real independent parser choice, even when alternative matches share a prefix.+choice' parsers = choice $ map try (init parsers) ++ [last parsers]++definitions :: GenParser Char CsvRules ()+definitions = do+  choice' [+    datefield+   ,statusfield+   ,codefield+   ,descriptionfield+   ,amountfield+   ,currencyfield+   ,basecurrency+   ,baseaccount+   ,commentline+   ] <?> "definition"+  return ()++datefield = do+  string "date-field"+  many1 spacenonewline+  v <- restofline+  r <- getState+  setState r{dateField=readMay v}++codefield = do+  string "code-field"+  many1 spacenonewline+  v <- restofline+  r <- getState+  setState r{codeField=readMay v}++statusfield = do+  string "status-field"+  many1 spacenonewline+  v <- restofline+  r <- getState+  setState r{statusField=readMay v}++descriptionfield = do+  string "description-field"+  many1 spacenonewline+  v <- restofline+  r <- getState+  setState r{descriptionField=readMay v}++amountfield = do+  string "amount-field"+  many1 spacenonewline+  v <- restofline+  r <- getState+  setState r{amountField=readMay v}++currencyfield = do+  string "currency-field"+  many1 spacenonewline+  v <- restofline+  r <- getState+  setState r{currencyField=readMay v}++basecurrency = do+  string "currency"+  many1 spacenonewline+  v <- restofline+  r <- getState+  setState r{baseCurrency=Just v}++baseaccount = do+  string "base-account"+  many1 spacenonewline+  v <- ledgeraccountname+  optional newline+  r <- getState+  setState r{baseAccount=v}++accountrule :: GenParser Char CsvRules AccountRule+accountrule = do+  many blankorcommentline+  pats <- many1 matchreplacepattern+  guard $ length pats >= 2+  let pats' = init pats+      acct = either (fail.show) id $ runParser ledgeraccountname () "" $ fst $ last pats+  many blankorcommentline+  return (pats',acct)+ <?> "account rule"++blanklines = many1 blankline >> return ()++blankline = many spacenonewline >> newline >> return () <?> "blank line"++commentchar = oneOf ";#"++commentline = many spacenonewline >> commentchar >> restofline >> return () <?> "comment line"++blankorcommentline = choice' [blankline, commentline]++matchreplacepattern = do+  notFollowedBy commentchar+  matchpat <- many1 (noneOf "=\n")+  replpat <- optionMaybe $ do {char '='; many $ noneOf "\n"}+  newline+  return (matchpat,replpat)++printTxn :: Bool -> CsvRules -> CsvRecord -> IO ()+printTxn debug rules rec = do+  when debug $ hPrintf stderr "record: %s" (printCSV [rec])+  putStr $ show $ transactionFromCsvRecord rules rec++-- csv record conversion++transactionFromCsvRecord :: CsvRules -> CsvRecord -> Transaction+transactionFromCsvRecord rules fields =+  let +      date = parsedate $ normaliseDate $ maybe "1900/1/1" (atDef "" fields) (dateField rules)+      status = maybe False (null . strip . (atDef "" fields)) (statusField rules)+      code = maybe "" (atDef "" fields) (codeField rules)+      desc = maybe "" (atDef "" fields) (descriptionField rules)+      comment = ""+      precomment = ""+      amountstr = maybe "" (atDef "" fields) (amountField rules)+      amountstr' = strnegate amountstr where strnegate ('-':s) = s+                                             strnegate s = '-':s+      currency = maybe (fromMaybe "" $ baseCurrency rules) (atDef "" fields) (currencyField rules)+      amountstr'' = currency ++ amountstr'+      amountparse = runParser someamount nullctx "" amountstr''+      amount = either (const nullmixedamt) id amountparse+      unknownacct | (readDef 0 amountstr' :: Double) < 0 = "income:unknown"+                  | otherwise = "expenses:unknown"+      (acct,newdesc) = identify (accountRules rules) unknownacct desc+      t = Transaction {+              tdate=date,+              teffectivedate=Nothing,+              tstatus=status,+              tcode=code,+              tdescription=newdesc,+              tcomment=comment,+              tpreceding_comment_lines=precomment,+              tmetadata=[],+              tpostings=[+                   Posting {+                     pstatus=False,+                     paccount=acct,+                     pamount=amount,+                     pcomment="",+                     ptype=RegularPosting,+                     pmetadata=[],+                     ptransaction=Just t+                   },+                   Posting {+                     pstatus=False,+                     paccount=baseAccount rules,+                     pamount=(-amount),+                     pcomment="",+                     ptype=RegularPosting,+                     pmetadata=[],+                     ptransaction=Just t+                   }+                  ]+            }+  in t++-- | Convert some date string with unknown format to YYYY/MM/DD.+normaliseDate :: String -> String+normaliseDate s = maybe "0000/00/00" showDate $+              firstJust+              [parseTime defaultTimeLocale "%Y/%m/%e" s+               -- can't parse a month without leading 0, try adding one+              ,parseTime defaultTimeLocale "%Y/%m/%e" (take 5 s ++ "0" ++ drop 5 s)+              ,parseTime defaultTimeLocale "%Y-%m-%e" s+              ,parseTime defaultTimeLocale "%Y-%m-%e" (take 5 s ++ "0" ++ drop 5 s)+              ,parseTime defaultTimeLocale "%m/%e/%Y" s+              ,parseTime defaultTimeLocale "%m/%e/%Y" ('0':s)+              ,parseTime defaultTimeLocale "%m-%e-%Y" s+              ,parseTime defaultTimeLocale "%m-%e-%Y" ('0':s)+              ]++-- | Apply account matching rules to a transaction description to obtain+-- the most appropriate account and a new description.+identify :: [AccountRule] -> String -> String -> (String,String)+identify rules defacct desc | null matchingrules = (defacct,desc)+                            | otherwise = (acct,newdesc)+    where+      matchingrules = filter ismatch rules :: [AccountRule]+          where ismatch = any (isJust . flip matchRegexPR (caseinsensitive desc) . fst) . fst+      (prs,acct) = head matchingrules+      p_ms_r = filter (\(_,m,_) -> isJust m) $ map (\(p,r) -> (p, matchRegexPR (caseinsensitive p) desc, r)) prs+      (p,_,r) = head p_ms_r+      newdesc = case r of Just rpat -> gsubRegexPR (caseinsensitive p) rpat desc+                          Nothing   -> desc++caseinsensitive = ("(?i)"++)++tests_Convert = TestList [++   "convert rules parsing: empty file" ~: do+     -- let assertMixedAmountParse parseresult mixedamount =+     --         (either (const "parse error") showMixedAmountDebug parseresult) ~?= (showMixedAmountDebug mixedamount)+    assertParseEqual (parseCsvRules "unknown" "") nullrules++  ,"convert rules parsing: accountrule" ~: do+     assertParseEqual (parseWithCtx nullrules accountrule "A\na\n") -- leading blank line required+                 ([("A",Nothing)], "a")++  ,"convert rules parsing: trailing comments" ~: do+     assertParse (parseWithCtx nullrules csvrulesfile "A\na\n# \n#\n")++  ,"convert rules parsing: trailing blank lines" ~: do+     assertParse (parseWithCtx nullrules csvrulesfile "A\na\n\n  \n")++  -- not supported+  -- ,"convert rules parsing: no final newline" ~: do+  --    assertParse (parseWithCtx nullrules csvrulesfile "A\na")+  --    assertParse (parseWithCtx nullrules csvrulesfile "A\na\n# \n#")+  --    assertParse (parseWithCtx nullrules csvrulesfile "A\na\n\n  ")++                 -- (nullrules{+                 --   -- dateField=Maybe FieldPosition,+                 --   -- statusField=Maybe FieldPosition,+                 --   -- codeField=Maybe FieldPosition,+                 --   -- descriptionField=Maybe FieldPosition,+                 --   -- amountField=Maybe FieldPosition,+                 --   -- currencyField=Maybe FieldPosition,+                 --   -- baseCurrency=Maybe String,+                 --   -- baseAccount=AccountName,+                 --   accountRules=[+                 --        ([("A",Nothing)], "a")+                 --       ]+                 --  })++  ]
+ Hledger/Cli/Histogram.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE CPP #-}+{-| ++Print a histogram report.++-}++module Hledger.Cli.Histogram+where+import Hledger.Data+import Hledger.Cli.Options+#if __GLASGOW_HASKELL__ <= 610+import Prelude hiding ( putStr )+import System.IO.UTF8+#endif+++barchar = '*'++-- | Print a histogram of some statistic per reporting interval, such as+-- number of postings per day.+histogram :: [Opt] -> [String] -> Journal -> IO ()+histogram opts args j = do+  t <- getCurrentLocalTime+  putStr $ showHistogram opts (optsToFilterSpec opts args t) j++showHistogram :: [Opt] -> FilterSpec -> Journal -> String+showHistogram opts filterspec j = concatMap (printDayWith countBar) dayps+    where+      i = intervalFromOpts opts+      interval | i == NoInterval = Daily+               | otherwise = i+      fullspan = journalDateSpan j+      days = filter (DateSpan Nothing Nothing /=) $ splitSpan interval fullspan+      dayps = [(s, filter (isPostingInDateSpan s) ps) | s <- days]+      -- same as Register+      -- should count transactions, not postings ?+      ps = sortBy (comparing postingDate) $ filterempties $ filter matchapats $ filterdepth $ journalPostings j+      filterempties+          | Empty `elem` opts = id+          | otherwise = filter (not . isZeroMixedAmount . pamount)+      matchapats = matchpats apats . paccount+      apats = acctpats filterspec+      filterdepth | interval == NoInterval = filter (\p -> accountNameLevel (paccount p) <= depth)+                  | otherwise = id+      depth = fromMaybe 99999 $ depthFromOpts opts++printDayWith f (DateSpan b _, ts) = printf "%s %s\n" (show $ fromJust b) (f ts)++countBar ps = replicate (length ps) barchar
Hledger/Cli/Main.hs view
@@ -39,33 +39,30 @@ -}  module Hledger.Cli.Main where-#if defined(WEB)-import System.Info (os)-#endif+ #if __GLASGOW_HASKELL__ <= 610 import Prelude hiding (putStr, putStrLn) import System.IO.UTF8 #endif -import Hledger.Cli.Commands.All import Hledger.Data+import Hledger.Cli.Commands import Hledger.Cli.Options import Hledger.Cli.Tests import Hledger.Cli.Utils (withJournalDo)-import Hledger.Cli.Version (versionmsg, binaryfilename)+import Hledger.Cli.Version (progversionstr, binaryfilename)  main :: IO () main = do-  (opts, cmd, args) <- parseArguments-  run cmd opts args+  (opts, args) <- parseArgumentsWith options_cli+  run opts args     where-      run cmd opts args-       | Help `elem` opts             = putStr help1-       | HelpOptions `elem` opts      = putStr help2-       | HelpAll `elem` opts          = putStr $ help1 ++ "\n" ++ help2-       | Version `elem` opts          = putStrLn versionmsg-       | BinaryFilename `elem` opts   = putStrLn binaryfilename-       | null cmd                     = maybe (putStr help1) (withJournalDo opts args cmd) defaultcmd+      run opts _+       | Help `elem` opts             = putStr usage_cli+       | Version `elem` opts          = putStrLn $ progversionstr progname_cli+       | BinaryFilename `elem` opts   = putStrLn $ binaryfilename progname_cli+      run _ []                        = argsError "a command is required."+      run opts (cmd:args)        | cmd `isPrefixOf` "balance"   = withJournalDo opts args cmd balance        | cmd `isPrefixOf` "convert"   = withJournalDo opts args cmd convert        | cmd `isPrefixOf` "print"     = withJournalDo opts args cmd print'@@ -73,22 +70,5 @@        | cmd `isPrefixOf` "histogram" = withJournalDo opts args cmd histogram        | cmd `isPrefixOf` "add"       = withJournalDo opts args cmd add        | cmd `isPrefixOf` "stats"     = withJournalDo opts args cmd stats-#ifdef VTY-       | cmd `isPrefixOf` "vty"       = withJournalDo opts args cmd vty-#endif-#if defined(WEB)-       | cmd `isPrefixOf` "web"       = withJournalDo opts args cmd web-#endif-#ifdef CHART-       | cmd `isPrefixOf` "chart"       = withJournalDo opts args cmd chart-#endif        | cmd `isPrefixOf` "test"      = runtests opts args >> return ()-       | otherwise                    = putStr help1---- in a web-enabled build on windows, run the web ui by default-#if defined(WEB)-      defaultcmd | os=="mingw32" = Just web-                 | otherwise = Nothing-#else-      defaultcmd = Nothing-#endif+       | otherwise                    = argsError $ "command "++cmd++" is unrecognized."
Hledger/Cli/Options.hs view
@@ -5,70 +5,58 @@  module Hledger.Cli.Options where+import Codec.Binary.UTF8.String (decodeString) import System.Console.GetOpt import System.Environment-import Hledger.Cli.Version (timeprogname)-import Hledger.Read (myJournalPath, myTimelogPath)+ import Hledger.Data.Utils import Hledger.Data.Types import Hledger.Data.Dates-import Codec.Binary.UTF8.String (decodeString)+import Hledger.Read (myJournalPath, myTimelogPath) -#ifdef CHART-chartoutput   = "hledger.png"-chartitems    = 10-chartsize     = "600x400"-#endif -help1 =+progname_cli = "hledger"++-- | The program name which, if we are invoked as (via symlink or+-- renaming), causes us to default to reading the user's time log instead+-- of their journal.+progname_cli_time  = "hours"++usage_preamble_cli =   "Usage: hledger [OPTIONS] COMMAND [PATTERNS]\n" ++   "       hledger [OPTIONS] convert CSVFILE\n" ++-  "       hledger [OPTIONS] stats\n" ++   "\n" ++-  "hledger reads your ~/.journal file, or another specified with $LEDGER or -f\n" +++  "Reads your ~/.journal file, or another specified by $LEDGER or -f, and\n" +++  "runs the specified command (may be abbreviated):\n" ++   "\n" ++-  "COMMAND is one of (may be abbreviated):\n" ++   "  add       - prompt for new transactions and add them to the journal\n" ++   "  balance   - show accounts, with balances\n" ++-  "  convert   - read CSV bank data and display in journal format\n" +++  "  convert   - show the specified CSV file as a hledger journal\n" ++   "  histogram - show a barchart of transactions per day or other interval\n" ++   "  print     - show transactions in journal format\n" ++   "  register  - show transactions as a register with running balance\n" ++   "  stats     - show various statistics for a journal\n" ++-  "  vty       - run a simple curses-style UI" ++-#ifdef VTY-  "\n" ++-#else-  " (DISABLED, install with -fvty)\n" ++-#endif-  "  web       - run a simple web-based UI" ++-#if defined(WEB)-  "\n" ++-#else-  " (DISABLED, install with -fweb)\n" ++-#endif-  "  chart     - generate balances pie charts" ++-#ifdef CHART-  "\n" ++-#else-  " (DISABLED, install with -fchart)\n" ++-#endif   "  test      - run self-tests\n" ++-  "\n" ++-  "PATTERNS are regular expressions which filter by account name.\n" ++-  "Prefix with desc: to filter by transaction description instead.\n" ++-  "Prefix with not: to negate a pattern. When using both, not: comes last.\n" ++-  "\n" ++-  "DATES can be y/m/d or ledger-style smart dates like \"last month\".\n" ++-  "\n" ++-  "Use --help-options to see OPTIONS, or --help-all/-H.\n" ++-  ""+  "\n" -help2 = usageInfo "Options:\n" options+usage_options_cli = usageInfo "hledger options:" options_cli +usage_postscript_cli =+ "\n" +++ "DATES can be y/m/d or smart dates like \"last month\".  PATTERNS are regular\n" +++ "expressions which filter by account name.  Prefix a pattern with desc: to\n" +++ "filter by transaction description instead, prefix with not: to negate it.\n" +++ "When using both, not: comes last.\n"++usage_cli = concat [+             usage_preamble_cli+            ,usage_options_cli+            ,usage_postscript_cli+            ]+ -- | Command-line options we accept.-options :: [OptDescr Opt]-options = [+options_cli :: [OptDescr Opt]+options_cli = [   Option "f" ["file"]         (ReqArg File "FILE")   "use a different journal/timelog file; - means stdin"  ,Option ""  ["no-new-accounts"] (NoArg NoNewAccts)  "don't allow to create new accounts"  ,Option "b" ["begin"]        (ReqArg Begin "DATE")  "report on transactions on or after this date"@@ -92,66 +80,52 @@  ,Option "M" ["monthly"]      (NoArg  MonthlyOpt)    "register, stats: report by month"  ,Option "Q" ["quarterly"]    (NoArg  QuarterlyOpt)  "register, stats: report by quarter"  ,Option "Y" ["yearly"]       (NoArg  YearlyOpt)     "register, stats: report by year"-#ifdef CHART- ,Option "o" ["output"]  (ReqArg ChartOutput "FILE")    ("chart: output filename (default: "++chartoutput++")")- ,Option ""  ["items"]  (ReqArg ChartItems "N")         ("chart: number of accounts to show (default: "++show chartitems++")")- ,Option ""  ["size"] (ReqArg ChartSize "WIDTHxHEIGHT") ("chart: image size (default: "++chartsize++")")-#endif-#ifdef VTY- ,Option ""  ["debug-vty"]    (NoArg  DebugVty)      "vty: run with no terminal output, showing console"-#endif-#ifdef WEB- ,Option ""  ["base-url"]     (ReqArg BaseUrl "URL") "web: use this base url (default http://localhost:PORT)"- ,Option ""  ["port"]         (ReqArg Port "N")      "web: serve on tcp port N (default 5000)"-#endif  ,Option "v" ["verbose"]      (NoArg  Verbose)       "show more verbose output"  ,Option ""  ["debug"]        (NoArg  Debug)         "show extra debug output; implies verbose"  ,Option ""  ["binary-filename"] (NoArg BinaryFilename) "show the download filename for this hledger build"  ,Option "V" ["version"]      (NoArg  Version)       "show version information"- ,Option "h" ["help"]         (NoArg  Help)          "show basic command-line usage"- ,Option ""  ["help-options"] (NoArg  HelpOptions)   "show command-line options"- ,Option "H" ["help-all"]     (NoArg  HelpAll)       "show command-line usage and options"+ ,Option "h" ["help"]         (NoArg  Help)          "show command-line usage"  ]  -- | An option value from a command-line flag. data Opt = -    File    {value::String} | -    NoNewAccts |-    Begin   {value::String} | -    End     {value::String} | -    Period  {value::String} | -    Cleared | -    UnCleared | -    CostBasis | -    Depth   {value::String} | -    Display {value::String} | -    Effective | -    Empty | -    Real | -    Flat |-    Drop   {value::String} |-    NoTotal |-    SubTotal |-    DailyOpt |-    WeeklyOpt |-    MonthlyOpt |-    QuarterlyOpt |-    YearlyOpt |-    BaseUrl {value::String} |-    Port    {value::String} |-    Help |-    HelpOptions |-    HelpAll |-    Verbose |-    Version+    File          {value::String}+    | NoNewAccts+    | Begin       {value::String}+    | End         {value::String}+    | Period      {value::String}+    | Cleared+    | UnCleared+    | CostBasis+    | Depth       {value::String}+    | Display     {value::String}+    | Effective+    | Empty+    | Real+    | Flat+    | Drop        {value::String}+    | NoTotal+    | SubTotal+    | DailyOpt+    | WeeklyOpt+    | MonthlyOpt+    | QuarterlyOpt+    | YearlyOpt+    | Help+    | Verbose+    | Version     | BinaryFilename     | Debug+    -- XXX add-on options, must be defined here for now+    -- vty     | DebugVty-#ifdef CHART+    -- web+    | BaseUrl     {value::String}+    | Port        {value::String}+    -- chart     | ChartOutput {value::String}     | ChartItems  {value::String}     | ChartSize   {value::String}-#endif     deriving (Show,Eq)  -- these make me nervous@@ -167,20 +141,24 @@ optValuesForConstructors fs opts = concatMap get opts     where get o = [v | any (\f -> f v == o) fs] where v = value o --- | Parse the command-line arguments into options, command name, and--- command arguments. Any dates in the options are converted to explicit--- YYYY/MM/DD format based on the current time.-parseArguments :: IO ([Opt], String, [String])-parseArguments = do-  args <- liftM (map decodeString) getArgs-  let (os,as,es) = getOpt Permute options args-  os' <- fixOptDates os-  let os'' = if Debug `elem` os' then Verbose:os' else os'-  case (as,es) of-    (cmd:args,[])   -> return (os'',cmd,args)-    ([],[])         -> return (os'',"",[])-    (_,errs)        -> ioError (userError' (concat errs ++ help1))+-- | Parse the command-line arguments into options and arguments using the+-- specified option descriptors. Any smart dates in the options are+-- converted to explicit YYYY/MM/DD format based on the current time. If+-- parsing fails, raise an error, displaying the problem along with the+-- provided usage string.+parseArgumentsWith :: [OptDescr Opt] -> IO ([Opt], [String])+parseArgumentsWith options = do+  rawargs <- map decodeString `fmap` getArgs+  let (opts,args,errs) = getOpt Permute options rawargs+  opts' <- fixOptDates opts+  let opts'' = if Debug `elem` opts' then Verbose:opts' else opts'+  if null errs+   then return (opts'',args)+   else argsError (concat errs) >> return ([],[]) +argsError :: String -> IO ()+argsError = ioError . userError' . (++ " Run with --help to see usage.")+ -- | Convert any fuzzy dates within these option values to explicit ones, -- based on today's date. fixOptDates :: [Opt] -> IO [Opt]@@ -277,7 +255,7 @@ usingTimeProgramName :: IO Bool usingTimeProgramName = do   progname <- getProgName-  return $ map toLower progname == timeprogname+  return $ map toLower progname == progname_cli_time  -- | Get the journal file path from options, an environment variable, or a default journalFilePathFromOpts :: [Opt] -> IO String@@ -317,4 +295,3 @@ --     where --       listtomaybe [] = Nothing --       listtomaybe vs = Just $ last vs-
+ Hledger/Cli/Print.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE CPP #-}+{-| ++A ledger-compatible @print@ command.++-}++module Hledger.Cli.Print (+  JournalReport+ ,JournalReportItem+ ,print'+ ,journalReport+ ,showTransactions+) where+import Hledger.Data+import Hledger.Cli.Options+#if __GLASGOW_HASKELL__ <= 610+import Prelude hiding ( putStr )+import System.IO.UTF8+#endif+++-- | A "journal report" is just a list of transactions.+type JournalReport = [JournalReportItem]++-- | The data for a single journal report item, representing one transaction.+type JournalReportItem = Transaction++-- | Print journal transactions in standard format.+print' :: [Opt] -> [String] -> Journal -> IO ()+print' opts args j = do+  t <- getCurrentLocalTime+  putStr $ showTransactions (optsToFilterSpec opts args t) j++showTransactions :: FilterSpec -> Journal -> String+showTransactions fspec j = journalReportAsText [] fspec $ journalReport [] fspec j++journalReportAsText :: [Opt] -> FilterSpec -> JournalReport -> String -- XXX unlike the others, this one needs fspec not opts+journalReportAsText _ fspec items = concatMap (showTransactionForPrint effective) items+    where effective = EffectiveDate == whichdate fspec++journalReport :: [Opt] -> FilterSpec -> Journal -> JournalReport+journalReport _ fspec j = sortBy (comparing tdate) $ jtxns $ filterJournalTransactions fspec j
+ Hledger/Cli/Register.hs view
@@ -0,0 +1,207 @@+{-# LANGUAGE CPP #-}+{-| ++A ledger-compatible @register@ command.++-}++module Hledger.Cli.Register (+  RegisterReport+ ,RegisterReportItem+ ,register+ ,registerReport+ ,registerReportAsText+ ,showPostingWithBalanceForVty+ ,tests_Register+) where++import Safe (headMay, lastMay)+import Hledger.Data+import Hledger.Cli.Options+#if __GLASGOW_HASKELL__ <= 610+import Prelude hiding ( putStr )+import System.IO.UTF8+#endif+import Text.ParserCombinators.Parsec+++-- | A register report is a list of postings to an account or set of+-- accounts, with a running total. Postings may be actual postings, or+-- virtual postings aggregated over a reporting interval.+type RegisterReport = [RegisterReportItem] -- line items, one per posting++-- | The data for a single register report line item, representing one posting.+type RegisterReportItem = (Maybe (Day, String) -- transaction date and description if this is the first posting+                          ,Posting             -- the posting+                          ,MixedAmount         -- balance so far+                          )++-- | Print a register report.+register :: [Opt] -> [String] -> Journal -> IO ()+register opts args j = do+  t <- getCurrentLocalTime+  putStr $ registerReportAsText opts $ registerReport opts (optsToFilterSpec opts args t) j++-- | Render a register report as plain text suitable for console output.+registerReportAsText :: [Opt] -> RegisterReport -> String+registerReportAsText opts = unlines . map (registerReportItemAsText opts)++-- | Render one register report line item as plain text. Eg:+-- @+-- date (10)  description (20)     account (22)            amount (11)  balance (12)+-- DDDDDDDDDD dddddddddddddddddddd aaaaaaaaaaaaaaaaaaaaaa  AAAAAAAAAAA AAAAAAAAAAAA+-- ^ displayed for first postings^+--   only, otherwise blank+-- @+registerReportItemAsText :: [Opt] -> RegisterReportItem -> String+registerReportItemAsText _ (dd, p, b) = concatTopPadded [datedesc, pstr, " ", bal]+    where+      datedesc = case dd of Nothing -> replicate datedescwidth ' '+                            Just (da, de) -> printf "%s %s " date desc+                                where+                                  date = showDate da+                                  desc = printf ("%-"++(show descwidth)++"s") $ elideRight descwidth de :: String+          where+            descwidth = datedescwidth - datewidth - 2+            datedescwidth = 32+            datewidth = 10+      pstr = showPostingForRegister p+      bal = padleft 12 (showMixedAmountOrZeroWithoutPrice b)++showPostingWithBalanceForVty showtxninfo p b = registerReportItemAsText [] $ mkitem showtxninfo p b++-- | Get a register report with the specified options for this journal.+registerReport :: [Opt] -> FilterSpec -> Journal -> RegisterReport+registerReport opts fspec j = getitems ps nullposting startbal+    where+      ps | interval == NoInterval = displayableps+         | otherwise              = summarisePostingsByInterval interval depth empty filterspan displayableps+      (precedingps, displayableps, _) = postingsMatchingDisplayExpr (displayExprFromOpts opts)+                                        $ depthClipPostings depth+                                        $ journalPostings+                                        $ filterJournalPostings fspec{depth=Nothing} j+      startbal = sumPostings precedingps+      filterspan = datespan fspec+      (interval, depth, empty) = (intervalFromOpts opts, depthFromOpts opts, Empty `elem` opts)++-- | Generate register report line items.+getitems :: [Posting] -> Posting -> MixedAmount -> [RegisterReportItem]+getitems [] _ _ = []+getitems (p:ps) pprev b = i:(getitems ps p b')+    where+      i = mkitem isfirst p b'+      isfirst = ptransaction p /= ptransaction pprev+      b' = b + pamount p++-- | Generate one register report line item, from a flag indicating+-- whether to include transaction info, a posting, and the current running+-- balance.+mkitem :: Bool -> Posting -> MixedAmount -> RegisterReportItem+mkitem False p b = (Nothing, p, b)+mkitem True p b = (ds, p, b)+    where ds = case ptransaction p of Just (Transaction{tdate=da,tdescription=de}) -> Just (da,de)+                                      Nothing -> Just (nulldate,"")++-- | Date-sort and split a list of postings into three spans - postings matched+-- by the given display expression, and the preceding and following postings.+postingsMatchingDisplayExpr :: Maybe String -> [Posting] -> ([Posting],[Posting],[Posting])+postingsMatchingDisplayExpr d ps = (before, matched, after)+    where +      sorted = sortBy (comparing postingDate) ps+      (before, rest) = break (displayExprMatches d) sorted+      (matched, after) = span (displayExprMatches d) rest++-- | Does this display expression allow this posting to be displayed ?+-- Raises an error if the display expression can't be parsed.+displayExprMatches :: Maybe String -> Posting -> Bool+displayExprMatches Nothing  _ = True+displayExprMatches (Just d) p = (fromparse $ parsewith datedisplayexpr d) p+                        +-- | Parse a hledger display expression, which is a simple date test like+-- "d>[DATE]" or "d<=[DATE]", and return a "Posting"-matching predicate.+datedisplayexpr :: GenParser Char st (Posting -> Bool)+datedisplayexpr = do+  char 'd'+  op <- compareop+  char '['+  (y,m,d) <- smartdate+  char ']'+  let date    = parsedate $ printf "%04s/%02s/%02s" y m d+      test op = return $ (`op` date) . postingDate+  case op of+    "<"  -> test (<)+    "<=" -> test (<=)+    "="  -> test (==)+    "==" -> test (==)+    ">=" -> test (>=)+    ">"  -> test (>)+    _    -> mzero+ where+  compareop = choice $ map (try . string) ["<=",">=","==","<","=",">"]++-- XXX confusing, refactor++-- | Convert a list of postings into summary postings. Summary postings+-- are one per account per interval and aggregated to the specified depth+-- if any.+summarisePostingsByInterval :: Interval -> Maybe Int -> Bool -> DateSpan -> [Posting] -> [Posting]+summarisePostingsByInterval interval depth empty filterspan ps = concatMap summarisespan $ splitSpan interval reportspan+    where+      summarisespan s = summarisePostingsInDateSpan s depth empty (postingsinspan s)+      postingsinspan s = filter (isPostingInDateSpan s) ps+      dataspan = postingsDateSpan ps+      reportspan | empty = filterspan `orDatesFrom` dataspan+                 | otherwise = dataspan++-- | Given a date span (representing a reporting interval) and a list of+-- postings within it: aggregate the postings so there is only one per+-- account, and adjust their date/description so that they will render+-- as a summary for this interval.+-- +-- As usual with date spans the end date is exclusive, but for display+-- purposes we show the previous day as end date, like ledger.+-- +-- When a depth argument is present, postings to accounts of greater+-- depth are aggregated where possible.+-- +-- The showempty flag includes spans with no postings and also postings+-- with 0 amount.+summarisePostingsInDateSpan :: DateSpan -> Maybe Int -> Bool -> [Posting] -> [Posting]+summarisePostingsInDateSpan (DateSpan b e) depth showempty ps+    | null ps && (isNothing b || isNothing e) = []+    | null ps && showempty = [summaryp]+    | otherwise = summaryps'+    where+      summaryp = summaryPosting b' ("- "++ showDate (addDays (-1) e'))+      b' = fromMaybe (maybe nulldate postingDate $ headMay ps) b+      e' = fromMaybe (maybe (addDays 1 nulldate) postingDate $ lastMay ps) e+      summaryPosting date desc = nullposting{ptransaction=Just nulltransaction{tdate=date,tdescription=desc}}++      summaryps' = (if showempty then id else filter (not . isZeroMixedAmount . pamount)) summaryps+      summaryps = [summaryp{paccount=a,pamount=balancetoshowfor a} | a <- clippedanames]+      anames = sort $ nub $ map paccount ps+      -- aggregate balances by account, like journalToLedger, then do depth-clipping+      (_,_,exclbalof,inclbalof) = groupPostings ps+      clippedanames = nub $ map (clipAccountName d) anames+      isclipped a = accountNameLevel a >= d+      d = fromMaybe 99999 $ depth+      balancetoshowfor a =+          (if isclipped a then inclbalof else exclbalof) (if null a then "top" else a)++-- | Clip the account names to the specified depth in a list of postings.+depthClipPostings :: Maybe Int -> [Posting] -> [Posting]+depthClipPostings depth = map (depthClipPosting depth)++-- | Clip a posting's account name to the specified depth.+depthClipPosting :: Maybe Int -> Posting -> Posting+depthClipPosting Nothing p = p+depthClipPosting (Just d) p@Posting{paccount=a} = p{paccount=clipAccountName d a}+++tests_Register :: Test+tests_Register = TestList [++         "summarisePostingsByInterval" ~: do+           summarisePostingsByInterval Quarterly Nothing False (DateSpan Nothing Nothing) [] ~?= []++        ]
+ Hledger/Cli/Stats.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE CPP #-}+{-|++Print some statistics for the journal.++-}++module Hledger.Cli.Stats+where+import Hledger.Data+import Hledger.Cli.Options+import qualified Data.Map as Map+#if __GLASGOW_HASKELL__ <= 610+import Prelude hiding ( putStr )+import System.IO.UTF8+#endif+++-- like Register.summarisePostings+-- | Print various statistics for the journal.+stats :: [Opt] -> [String] -> Journal -> IO ()+stats opts args j = do+  today <- getCurrentDay+  t <- getCurrentLocalTime+  let filterspec = optsToFilterSpec opts args t+      l = journalToLedger filterspec j+      reportspan = (ledgerDateSpan l) `orDatesFrom` (datespan filterspec)+      intervalspans = splitSpan (intervalFromOpts opts) reportspan+      showstats = showLedgerStats opts args l today+      s = intercalate "\n" $ map showstats intervalspans+  putStr s++showLedgerStats :: [Opt] -> [String] -> Ledger -> Day -> DateSpan -> String+showLedgerStats _ _ l today span =+    unlines (map (uncurry (printf fmt)) stats)+    where+      fmt = "%-" ++ show w1 ++ "s: %-" ++ show w2 ++ "s"+      w1 = maximum $ map (length . fst) stats+      w2 = maximum $ map (length . show . snd) stats+      stats = [+         ("Journal file", journalFilePath $ journal l)+        ,("Transactions span", printf "%s to %s (%d days)" (start span) (end span) days)+        ,("Last transaction", maybe "none" show lastdate ++ showelapsed lastelapsed)+        ,("Transactions", printf "%d (%0.1f per day)" tnum txnrate)+        ,("Transactions last 30 days", printf "%d (%0.1f per day)" tnum30 txnrate30)+        ,("Transactions last 7 days", printf "%d (%0.1f per day)" tnum7 txnrate7)+        ,("Payees/descriptions", show $ length $ nub $ map tdescription ts)+        ,("Accounts", printf "%d (depth %d)" acctnum acctdepth)+        ,("Commodities", printf "%s (%s)" (show $ length cs) (intercalate ", " cs))+      -- Transactions this month     : %(monthtxns)s (last month in the same period: %(lastmonthtxns)s)+      -- Uncleared transactions      : %(uncleared)s+      -- Days since reconciliation   : %(reconcileelapsed)s+      -- Days since last transaction : %(recentelapsed)s+       ]+           where+             ts = sortBy (comparing tdate) $ filter (spanContainsDate span . tdate) $ jtxns $ journal l+             as = nub $ map paccount $ concatMap tpostings ts+             cs = Map.keys $ canonicaliseCommodities $ nub $ map commodity $ concatMap amounts $ map pamount $ concatMap tpostings ts+             lastdate | null ts = Nothing+                      | otherwise = Just $ tdate $ last ts+             lastelapsed = maybe Nothing (Just . diffDays today) lastdate+             showelapsed Nothing = ""+             showelapsed (Just days) = printf " (%d %s)" days' direction+                                       where days' = abs days+                                             direction | days >= 0 = "days ago"+                                                       | otherwise = "days from now"+             tnum = length ts+             start (DateSpan (Just d) _) = show d+             start _ = ""+             end (DateSpan _ (Just d)) = show d+             end _ = ""+             days = fromMaybe 0 $ daysInSpan span+             txnrate | days==0 = 0+                     | otherwise = fromIntegral tnum / fromIntegral days :: Double+             tnum30 = length $ filter withinlast30 ts+             withinlast30 t = d >= addDays (-30) today && (d<=today) where d = tdate t+             txnrate30 = fromIntegral tnum30 / 30 :: Double+             tnum7 = length $ filter withinlast7 ts+             withinlast7 t = d >= addDays (-7) today && (d<=today) where d = tdate t+             txnrate7 = fromIntegral tnum7 / 7 :: Double+             acctnum = length as+             acctdepth | null as = 0+                       | otherwise = maximum $ map accountNameLevel as+
Hledger/Cli/Tests.hs view
@@ -12,10 +12,10 @@  hledger's doctests are shell tests defined in literal blocks in haddock documentation in the source, run by doing @make doctest@ in the hledger-source tree. They hardly used, but here is an example:+source tree. are They hardly used, but here is an example:  @-$ hledger -f data/sample.journal balance o+$ bin/hledger -f data/sample.journal balance o                   $1  expenses:food                  $-2  income                  $-1    gifts@@ -32,11 +32,10 @@ import System.Exit (exitFailure, exitWith, ExitCode(ExitSuccess)) -- base 3 compatible import System.Time (ClockTime(TOD)) -import Hledger.Cli.Commands.All import Hledger.Data  -- including testing utils in Hledger.Data.Utils-import Hledger.Read.Common (emptyCtx) import Hledger.Read (readJournal)-import Hledger.Read.Journal (someamount)+import Hledger.Read.JournalReader (someamount)+import Hledger.Cli.Commands import Hledger.Cli.Options import Hledger.Cli.Utils @@ -63,7 +62,7 @@    "account directive" ~:    let sameParse str1 str2 = do j1 <- readJournal Nothing str1 >>= either error' return                                 j2 <- readJournal Nothing str2 >>= either error' return-                                j1 `is` j2{filereadtime=filereadtime j1, jtext=jtext j1}+                                j1 `is` j2{filereadtime=filereadtime j1, files=files j1, jContext=jContext j1}    in TestList    [     "account directive 1" ~: sameParse @@ -272,20 +271,20 @@    ,"balanceTransaction" ~: do      assertBool "detect unbalanced entry, sign error"-                    (isLeft $ balanceTransaction-                           (Transaction (parsedate "2007/01/28") Nothing False "" "test" ""-                            [Posting False "a" (Mixed [dollars 1]) "" RegularPosting Nothing, -                             Posting False "b" (Mixed [dollars 1]) "" RegularPosting Nothing+                    (isLeft $ balanceTransaction Nothing+                           (Transaction (parsedate "2007/01/28") Nothing False "" "test" "" []+                            [Posting False "a" (Mixed [dollars 1]) "" RegularPosting [] Nothing, +                             Posting False "b" (Mixed [dollars 1]) "" RegularPosting [] Nothing                             ] ""))      assertBool "detect unbalanced entry, multiple missing amounts"-                    (isLeft $ balanceTransaction-                           (Transaction (parsedate "2007/01/28") Nothing False "" "test" ""-                            [Posting False "a" missingamt "" RegularPosting Nothing, -                             Posting False "b" missingamt "" RegularPosting Nothing+                    (isLeft $ balanceTransaction Nothing+                           (Transaction (parsedate "2007/01/28") Nothing False "" "test" "" []+                            [Posting False "a" missingamt "" RegularPosting [] Nothing, +                             Posting False "b" missingamt "" RegularPosting [] Nothing                             ] ""))-     let e = balanceTransaction (Transaction (parsedate "2007/01/28") Nothing False "" "test" ""-                           [Posting False "a" (Mixed [dollars 1]) "" RegularPosting Nothing, -                            Posting False "b" missingamt "" RegularPosting Nothing+     let e = balanceTransaction Nothing (Transaction (parsedate "2007/01/28") Nothing False "" "test" "" []+                           [Posting False "a" (Mixed [dollars 1]) "" RegularPosting [] Nothing, +                            Posting False "b" missingamt "" RegularPosting [] Nothing                            ] "")      assertBool "one missing amount should be ok" (isRight e)      assertEqual "balancing amount is added" @@ -340,43 +339,43 @@     "my assets" `isAccountNamePrefixOf` "assets:bank" `is` False    ,"isTransactionBalanced" ~: do-     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" ""-             [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting (Just t)-             ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting (Just t)+     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []+             [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting [] (Just t)+             ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting [] (Just t)              ] ""-     assertBool "detect balanced" (isTransactionBalanced t)-     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" ""-             [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting (Just t)-             ,Posting False "c" (Mixed [dollars (-1.01)]) "" RegularPosting (Just t)+     assertBool "detect balanced" (isTransactionBalanced Nothing t)+     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []+             [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting [] (Just t)+             ,Posting False "c" (Mixed [dollars (-1.01)]) "" RegularPosting [] (Just t)              ] ""-     assertBool "detect unbalanced" (not $ isTransactionBalanced t)-     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" ""-             [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting (Just t)+     assertBool "detect unbalanced" (not $ isTransactionBalanced Nothing t)+     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []+             [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting [] (Just t)              ] ""-     assertBool "detect unbalanced, one posting" (not $ isTransactionBalanced t)-     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" ""-             [Posting False "b" (Mixed [dollars 0]) "" RegularPosting (Just t)+     assertBool "detect unbalanced, one posting" (not $ isTransactionBalanced Nothing t)+     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []+             [Posting False "b" (Mixed [dollars 0]) "" RegularPosting [] (Just t)              ] ""-     assertBool "one zero posting is considered balanced for now" (isTransactionBalanced t)-     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" ""-             [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting (Just t)-             ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting (Just t)-             ,Posting False "d" (Mixed [dollars 100]) "" VirtualPosting (Just t)+     assertBool "one zero posting is considered balanced for now" (isTransactionBalanced Nothing t)+     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []+             [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting [] (Just t)+             ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting [] (Just t)+             ,Posting False "d" (Mixed [dollars 100]) "" VirtualPosting [] (Just t)              ] ""-     assertBool "virtual postings don't need to balance" (isTransactionBalanced t)-     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" ""-             [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting (Just t)-             ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting (Just t)-             ,Posting False "d" (Mixed [dollars 100]) "" BalancedVirtualPosting (Just t)+     assertBool "virtual postings don't need to balance" (isTransactionBalanced Nothing t)+     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []+             [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting [] (Just t)+             ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting [] (Just t)+             ,Posting False "d" (Mixed [dollars 100]) "" BalancedVirtualPosting [] (Just t)              ] ""-     assertBool "balanced virtual postings need to balance among themselves" (not $ isTransactionBalanced t)-     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" ""-             [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting (Just t)-             ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting (Just t)-             ,Posting False "d" (Mixed [dollars 100]) "" BalancedVirtualPosting (Just t)-             ,Posting False "e" (Mixed [dollars (-100)]) "" BalancedVirtualPosting (Just t)+     assertBool "balanced virtual postings need to balance among themselves" (not $ isTransactionBalanced Nothing t)+     let t = Transaction (parsedate "2009/01/01") Nothing False "" "a" "" []+             [Posting False "b" (Mixed [dollars 1.00]) "" RegularPosting [] (Just t)+             ,Posting False "c" (Mixed [dollars (-1.00)]) "" RegularPosting [] (Just t)+             ,Posting False "d" (Mixed [dollars 100]) "" BalancedVirtualPosting [] (Just t)+             ,Posting False "e" (Mixed [dollars (-100)]) "" BalancedVirtualPosting [] (Just t)              ] ""-     assertBool "balanced virtual postings need to balance among themselves (2)" (isTransactionBalanced t)+     assertBool "balanced virtual postings need to balance among themselves (2)" (isTransactionBalanced Nothing t)    ,"isSubAccountNameOf" ~: do     "assets" `isSubAccountNameOf` "assets" `is` False@@ -895,6 +894,7 @@              tcode="*",              tdescription="opening balance",              tcomment="",+             tmetadata=[],              tpostings=[               Posting {                 pstatus=False,@@ -902,6 +902,7 @@                 pamount=(Mixed [dollars 4.82]),                 pcomment="",                 ptype=RegularPosting,+                pmetadata=[],                 ptransaction=Nothing               },               Posting {@@ -910,6 +911,7 @@                 pamount=(Mixed [dollars (-4.82)]),                 pcomment="",                 ptype=RegularPosting,+                pmetadata=[],                 ptransaction=Nothing               }              ],@@ -923,6 +925,7 @@              tcode="*",              tdescription="ayres suites",              tcomment="",+             tmetadata=[],              tpostings=[               Posting {                 pstatus=False,@@ -930,6 +933,7 @@                 pamount=(Mixed [dollars 179.92]),                 pcomment="",                 ptype=RegularPosting,+                pmetadata=[],                 ptransaction=Nothing               },               Posting {@@ -938,6 +942,7 @@                 pamount=(Mixed [dollars (-179.92)]),                 pcomment="",                 ptype=RegularPosting,+                pmetadata=[],                 ptransaction=Nothing               }              ],@@ -951,6 +956,7 @@              tcode="*",              tdescription="auto transfer to savings",              tcomment="",+             tmetadata=[],              tpostings=[               Posting {                 pstatus=False,@@ -958,6 +964,7 @@                 pamount=(Mixed [dollars 200]),                 pcomment="",                 ptype=RegularPosting,+                pmetadata=[],                 ptransaction=Nothing               },               Posting {@@ -966,6 +973,7 @@                 pamount=(Mixed [dollars (-200)]),                 pcomment="",                 ptype=RegularPosting,+                pmetadata=[],                 ptransaction=Nothing               }              ],@@ -979,6 +987,7 @@              tcode="*",              tdescription="poquito mas",              tcomment="",+             tmetadata=[],              tpostings=[               Posting {                 pstatus=False,@@ -986,6 +995,7 @@                 pamount=(Mixed [dollars 4.82]),                 pcomment="",                 ptype=RegularPosting,+                pmetadata=[],                 ptransaction=Nothing               },               Posting {@@ -994,6 +1004,7 @@                 pamount=(Mixed [dollars (-4.82)]),                 pcomment="",                 ptype=RegularPosting,+                pmetadata=[],                 ptransaction=Nothing               }              ],@@ -1007,6 +1018,7 @@              tcode="*",              tdescription="verizon",              tcomment="",+             tmetadata=[],              tpostings=[               Posting {                 pstatus=False,@@ -1014,6 +1026,7 @@                 pamount=(Mixed [dollars 95.11]),                 pcomment="",                 ptype=RegularPosting,+                pmetadata=[],                 ptransaction=Nothing               },               Posting {@@ -1022,6 +1035,7 @@                 pamount=(Mixed [dollars (-95.11)]),                 pcomment="",                 ptype=RegularPosting,+                pmetadata=[],                 ptransaction=Nothing               }              ],@@ -1035,6 +1049,7 @@              tcode="*",              tdescription="discover",              tcomment="",+             tmetadata=[],              tpostings=[               Posting {                 pstatus=False,@@ -1042,6 +1057,7 @@                 pamount=(Mixed [dollars 80]),                 pcomment="",                 ptype=RegularPosting,+                pmetadata=[],                 ptransaction=Nothing               },               Posting {@@ -1050,6 +1066,7 @@                 pamount=(Mixed [dollars (-80)]),                 pcomment="",                 ptype=RegularPosting,+                pmetadata=[],                 ptransaction=Nothing               }              ],@@ -1059,9 +1076,9 @@           []           []           ""-          ""+          nullctx+          []           (TOD 0 0)-          ""  ledger7 = journalToLedger nullfilterspec journal7 @@ -1091,8 +1108,8 @@         []         []         ""-        ""+        nullctx+        []         (TOD 0 0)-        ""-    where parse = fromparse . parseWithCtx emptyCtx someamount+    where parse = fromparse . parseWithCtx nullctx someamount 
Hledger/Cli/Utils.hs view
@@ -13,15 +13,18 @@      journalReload,      journalReloadIfChanged,      journalFileIsNewer,-     journalFileModificationTime,+     journalSpecifiedFileIsNewer,+     fileModificationTime,      openBrowserOn,      writeFileWithBackup,      writeFileWithBackupIfChanged,+     readFileStrictly,     ) where import Hledger.Data import Hledger.Read import Hledger.Cli.Options (Opt(..),journalFilePathFromOpts) -- ,optsToFilterSpec)+import Control.Exception import Safe (readMay) import System.Directory (getModificationTime, getDirectoryContents, copyFile) import System.Exit@@ -38,7 +41,7 @@   -- We kludgily read the file before parsing to grab the full text, unless   -- it's stdin, or it doesn't exist and we are adding. We read it strictly   -- to let the add command work.-  journalFilePathFromOpts opts >>= readJournalFile Nothing >>= either (error'.trace "BBB") runcmd+  journalFilePathFromOpts opts >>= readJournalFile Nothing >>= either error' runcmd     where       costify = (if CostBasis `elem` opts then journalConvertAmountsToCost else id)       runcmd = cmd opts args . costify@@ -52,7 +55,7 @@  -- | Re-read a journal from its data file, or return an error string. journalReload :: Journal -> IO (Either String Journal)-journalReload Journal{filepath=f} = readJournalFile Nothing f+journalReload j = readJournalFile Nothing $ journalFilePath j  -- | Re-read a journal from its data file mostly, only if the file has -- changed since last read (or if there is no file, ie data read from@@ -60,26 +63,36 @@ -- the error message while reading it, and a flag indicating whether it -- was re-read or not. journalReloadIfChanged :: [Opt] -> Journal -> IO (Either String Journal, Bool)-journalReloadIfChanged opts j@Journal{filepath=f} = do-  changed <- journalFileIsNewer j-  if changed+journalReloadIfChanged opts j = do+  let maybeChangedFilename f = do newer <- journalSpecifiedFileIsNewer j f+                                  return $ if newer then Just f else Nothing+  changedfiles <- catMaybes `fmap` mapM maybeChangedFilename (journalFilePaths j)+  if not $ null changedfiles    then do-     when (Verbose `elem` opts) $ printf "%s has changed, reloading\n" f+     when (Verbose `elem` opts) $ printf "%s has changed, reloading\n" (head changedfiles)      jE <- journalReload j      return (jE, True)    else      return (Right j, False) --- | Has the journal's data file changed since last parsed ?+-- | Has the journal's main data file changed since the journal was last+-- read ? journalFileIsNewer :: Journal -> IO Bool journalFileIsNewer j@Journal{filereadtime=tread} = do-  tmod <- journalFileModificationTime j+  tmod <- fileModificationTime $ journalFilePath j   return $ diffClockTimes tmod tread > (TimeDiff 0 0 0 0 0 0 0) --- | Get the last modified time of the journal's data file (or if there is no--- file, the current time).-journalFileModificationTime :: Journal -> IO ClockTime-journalFileModificationTime Journal{filepath=f}+-- | Has the specified file (presumably one of journal's data files)+-- changed since journal was last read ?+journalSpecifiedFileIsNewer :: Journal -> FilePath -> IO Bool+journalSpecifiedFileIsNewer Journal{filereadtime=tread} f = do+  tmod <- fileModificationTime f+  return $ diffClockTimes tmod tread > (TimeDiff 0 0 0 0 0 0 0)++-- | Get the last modified time of the specified file, or if it does not+-- exist or there is some other error, the current time.+fileModificationTime :: FilePath -> IO ClockTime+fileModificationTime f     | null f = getClockTime     | otherwise = getModificationTime f `Prelude.catch` \_ -> getClockTime @@ -119,6 +132,9 @@ -- overwrite it with this new text, or give an error. writeFileWithBackup :: FilePath -> String -> IO () writeFileWithBackup f t = backUpFile f >> writeFile f t++readFileStrictly :: FilePath -> IO String+readFileStrictly f = readFile f >>= \s -> Control.Exception.evaluate (length s) >> return s  -- | Back up this file with a (incrementing) numbered suffix, or give an error. backUpFile :: FilePath -> IO ()
Hledger/Cli/Version.hs view
@@ -4,29 +4,62 @@ numbering policy. -} -module Hledger.Cli.Version+module Hledger.Cli.Version (+                            version+                           ,progversionstr+                           ,binaryfilename+) where import System.Info (os, arch)+ import Hledger.Data.Utils --- version and PATCHLEVEL are set by the makefile-version       = "0.12.1" +-- version and PATCHLEVEL are set by the make process++version       = "0.13.0"+ #ifdef PATCHLEVEL patchlevel = "." ++ show PATCHLEVEL -- must be numeric ! #else patchlevel = "" #endif -progname      = "hledger"-timeprogname  = "hours"- buildversion  = version ++ patchlevel :: String -binaryfilename = prettify $ splitAtElement '.' buildversion :: String+-- | Given a program name, return a human-readable version string.  For+-- development builds, at least non-cabal builds, the patch level (ie the+-- number of patches applied since last release tag) will also be+-- included.+progversionstr :: String -> String+progversionstr progname = progname ++ "-" ++ versionstr ++ configmsg+    where+      versionstr = prettify $ splitAtElement '.' buildversion+          where+            prettify (major:minor:bugfix:patches:[]) =+                printf "%s.%s%s%s" major minor bugfix' patches'+                    where+                      bugfix'+                          | bugfix `elem` ["0"{-,"98","99"-}] = ""+                          | otherwise = '.' : bugfix+                      patches'+                          | patches/="0" = "+"++patches+                          | otherwise = ""+            prettify s = intercalate "." s++      configmsg | null buildflags = ""+                | otherwise       = " with " ++ intercalate ", " buildflags++      buildflags = []++-- | Given a program name, return a precise platform-specific executable+-- name suitable for naming downloadable binaries.  Can raise an error if+-- the version and patch level was not defined correctly at build time.+binaryfilename :: String -> String+binaryfilename progname = prettify $ splitAtElement '.' buildversion                 where                   prettify (major:minor:bugfix:patches:[]) =-                      printf "hledger-%s.%s%s%s-%s-%s%s" major minor bugfix' patches' os' arch suffix+                      printf "%s-%s.%s%s%s-%s-%s%s" progname major minor bugfix' patches' os' arch suffix                           where                             bugfix'                                 | bugfix `elem` ["0"{-,"98","99"-}] = ""@@ -43,33 +76,3 @@                   prettify (major:[])              = prettify [major,"0","0","0"]                   prettify []                      = error' "VERSION is empty, please fix"                   prettify _                       = error' "VERSION has too many components, please fix"--versionstr    = prettify $ splitAtElement '.' buildversion :: String-                where-                  prettify (major:minor:bugfix:patches:[]) =-                      printf "%s.%s%s%s" major minor bugfix' patches'-                          where-                            bugfix'-                                | bugfix `elem` ["0"{-,"98","99"-}] = ""-                                | otherwise = '.' : bugfix-                            patches'-                                | patches/="0" = "+"++patches-                                | otherwise = ""-                  prettify s = intercalate "." s--versionmsg    = progname ++ "-" ++ versionstr ++ configmsg :: String-    where configmsg-              | null configflags = " with no extras"-              | otherwise = " with " ++ intercalate ", " configflags--configflags   = tail [""-#ifdef CHART-  ,"chart"-#endif-#ifdef VTY-  ,"vty"-#endif-#if defined(WEB)-  ,"web"-#endif- ]
− MANUAL.markdown
@@ -1,1121 +0,0 @@-----title: hledger manual------# hledger manual--This is the official hledger manual, for version 0.12.1.  You may also-want to visit the rest of [hledger.org](http://hledger.org), and for-background,-[c++ ledger's manual](http://joyful.com/repos/ledger/doc/ledger.html).--## User Guide--### Introduction--hledger is a program for tracking money, time, or any other commodity,-using a plain text file format and the simple but powerful principles of-double-entry accounting.--It is modelled closely on-[John Wiegley's ledger](http://wiki.github.com/jwiegley/ledger) (aka "c++-ledger"), with some features removed and some new ones added. I wrote-hledger because I wanted to build financial tools in the Haskell-programming language rather than in C++.--hledger's basic function is to generate register and balance reports from-a plain text general journal file, at the command line or via the web or-curses interface. You can use it to, eg,---   track spending and income--   see time reports by day/week/month/project--   get accurate numbers for client billing and tax filing--   track invoices--hledger aims to help both computer experts and every-day users gain-clarity in their finances and time management. For now though, it is most-useful to technically-minded folks who are comfortable with command-line-tools.--hledger is copyright (c) 2007-2009 Simon Michael-<[simon@joyful.com](mailto:simon@joyful.com)\> and contributors and-released as Free Software under GPL version 3 or later.--### Installing--hledger works on all major platforms. Here are the [release-notes](http://hledger.org/NEWS.html), and pre-built-[binaries](http://hledger.org/binaries/) you should be able to-download and run. If not, or if you prefer an up-to-date build,-open a shell/terminal/command window and:--1. **ensure you have GHC (6.10 or newer) and cabal-install**--        $ ghc --version-        $ cabal --version--    If not, download and install the [Haskell-    Platform](http://hackage.haskell.org/platform/).  Or, you may be-    able to use your platform's packaging system, eg on ubuntu lucid-    do `apt-get install ghc6 cabal-install zlib1g-dev`.  Also make-    sure `~/.cabal/bin` is in your PATH.--2. **install hledger with cabal-install**--        $ cabal update-        $ cabal install hledger--#### Install options--You can add the following options to the basic `cabal install hledger`-command to build extra features. I recommend you get the basic install-working first, then try these one at a time:--- `-fweb` builds the [web](#web) command, enabling a web-based user-  interface. This requires GHC 6.12 or greater.--- `-fvty` builds the [vty](#vty) command, enabling a basic-  curses-style user interface. This does not work on microsoft-  windows, unless possibly with cygwin.--- `-fchart` builds the [chart](#chart) command, enabling rudimentary-  pie chart generation. This requires additional GTK-related libraries-  (on ubuntu: `apt-get install libghc6-gtk-dev`) and possibly [other-  things](http://code.haskell.org/gtk2hs/INSTALL).--#### If you have trouble..--proceed to [Troubleshooting](#troubleshooting) for help!--### Basic usage--Basic usage is:--    $ hledger [OPTIONS] [COMMAND [PATTERNS]]--[OPTIONS](#overview) may appear anywhere on the command line.-[COMMAND](#commands) is one of: add, balance, chart, convert, histogram,-print, register, stats, ui, web, test (defaulting to balance). The-optional [PATTERNS](#filter-patterns) are regular expressions which select-a subset of the journal data.--hledger looks for data in a journal file, usually `.journal` in your home-directory. You can specify a different file with the -f option (use - for-standard input) or `LEDGER` environment variable.--To get started, make yourself a journal file containing some-transactions. You can copy the sample file below (or-[sample.journal](http://joyful.com/repos/hledger/data/sample.journal)) and save-it as `.journal` in your home directory. Or, just run `hledger add` and-enter a few transactions. Now you can try some of these commands, or read-on:--    $ hledger --help                        # show command-line help-    $ hledger balance                       # all accounts with aggregated balances-    $ hledger bal --depth 1                 # only top-level accounts-    $ hledger register                      # transaction register-    $ hledger reg income                    # transactions to/from an income account-    $ hledger reg checking                  # checking transactions-    $ hledger reg desc:shop                 # transactions with shop in the description-    $ hledger histogram                     # transactions per day, or other interval-    $ hledger add                           # add some new transactions to the journal file-    $ hledger vty                           # curses ui, if installed with -fvty-    $ hledger web                           # web ui, if installed with -fweb-    $ hledger chart                         # make a balance chart, if installed with -fchart--You'll find more examples below.--<a name="file-format" />--#### Journal file--hledger reads data from a plain text file, called a *journal* because-it represents a standard accounting [general-journal](http://en.wikipedia.org/wiki/General_journal).  It contains a-number of transactions, each describing a transfer of money (or-any commodity) between two or more named accounts, in a simple-format readable by both hledger and humans.--You can use hledger without learning any more about this file; just-use the [add](#add) or [web](#web) commands.--Many users, though, edit the journal file directly with a text-editor. This is a distinguishing feature of hledger (and c++ ledger.)-You can even do this while the web interface is running, and see the-changes right away.--Here's an example:--    ; A sample journal file. This is a comment.-    -    2008/01/01 income               ; <- transaction's first line starts in column 0, contains date and description-        assets:bank:checking  $1    ; <- posting lines start with whitespace, each contains an account name-        income:salary        $-1    ;    followed by at least two spaces and an amount-    -    2008/06/01 gift-        assets:bank:checking  $1    ; <- at least two postings in a transaction-        income:gifts         $-1    ; <- their amounts must balance to 0-    -    2008/06/02 save-        assets:bank:saving    $1-        assets:bank:checking        ; <- one amount may be omitted; here $-1 is inferred-    -    2008/06/03 eat & shop           ; <- description can be anything-        expenses:food         $1-        expenses:supplies     $1    ; <- this transaction debits two expense accounts-        assets:cash                 ; <- $-2 inferred-    -    2008/12/31 * pay off            ; <- an optional * after the date means "cleared" (or anything you want)-        liabilities:debts     $1-        assets:bank:checking--Each transaction has a date, optional description, and two or more-postings, of some amount to some account. The amounts within a transaction must balance,-ie add up to 0. Or, you can leave one amount blank and it will be inferred.--Note that account names may contain single spaces, while the amount must-be separated from the account name by at least two spaces.--An amount is a number, with an optional currency symbol or commodity name-on either the left or right. Commodity names which contain more than just-letters should be enclosed in double quotes. Negative amounts usually have-the minus sign next to the number (`$-1`), but it may also go before the-currency symbol/commodity name (`-$1`).--hledger's file format aims to be compatible with c++ ledger, so you-can use both tools on your journal. For more details, see [File format-compatibility](#file-format-compatibility).--## Reference--### Overview--This version of hledger mimics a subset of ledger 3.x, and adds some-features of its own. We currently support regular journal transactions, timelog-entries, multiple commodities, (fixed) price history, virtual postings,-filtering by account and description, the familiar print, register &-balance commands and several new commands. We handle (almost) the full-period expression syntax, and very limited display expressions consisting-of a simple date predicate.--Here is the command-line help:--    Usage: hledger [OPTIONS] COMMAND [PATTERNS]-           hledger [OPTIONS] convert CSVFILE-           hledger [OPTIONS] stats--    hledger reads your ~/.journal file, or another specified with $LEDGER or -f FILE--    COMMAND is one of (may be abbreviated):-      add       - prompt for new transactions and add them to the journal-      balance   - show accounts, with balances-      convert   - read CSV bank data and display in journal format-      histogram - show a barchart of transactions per day or other interval-      print     - show transactions in journal format-      register  - show transactions as a register with running balance-      stats     - show various statistics for a journal-      vty       - run a simple curses-style UI (if installed with -fvty)-      web       - run a simple web-based UI (if installed with -fweb-      chart     - generate balances pie charts (if installed with -fchart)-      test      - run self-tests--    PATTERNS are regular expressions which filter by account name.-    Prefix with desc: to filter by transaction description instead.-    Prefix with not: to negate a pattern. When using both, not: comes last.--    DATES can be y/m/d or ledger-style smart dates like "last month".--    Use --help-options to see OPTIONS, or --help-all/-H.--    Options:--      -f FILE  --file=FILE        use a different journal/timelog file; - means stdin-               --no-new-accounts  don't allow to create new accounts-      -b DATE  --begin=DATE       report on transactions on or after this date-      -e DATE  --end=DATE         report on transactions before this date-      -p EXPR  --period=EXPR      report on transactions during the specified period-                                  and/or with the specified reporting interval-      -C       --cleared          report only on cleared transactions-      -U       --uncleared        report only on uncleared transactions-      -B       --cost, --basis    report cost of commodities-               --depth=N          hide sub-accounts deeper than this-      -d EXPR  --display=EXPR     show only transactions matching EXPR (where-                                  EXPR is 'dOP[DATE]' and OP is <, <=, =, >=, >)-               --effective        use transactions' effective dates, if any-      -E       --empty            show empty/zero things which are normally elided-      -R       --real             report only on real (non-virtual) transactions-               --flat             balance: show full account names, unindented-               --drop=N           balance: with --flat, elide first N account name components-               --no-total         balance: hide the final total-      -D       --daily            register, stats: report by day-      -W       --weekly           register, stats: report by week-      -M       --monthly          register, stats: report by month-      -Q       --quarterly        register, stats: report by quarter-      -Y       --yearly           register, stats: report by year-      -v       --verbose          show more verbose output-               --debug            show extra debug output; implies verbose-               --binary-filename  show the download filename for this hledger build-      -V       --version          show version information-      -h       --help             show basic command-line usage-               --help-options     show command-line options-      -H       --help-all         show command-line usage and options--### Commands--#### Reporting commands--These commands are read-only, that is they never modify your data.--##### print--The print command displays full transactions from the journal file, tidily-formatted and showing all amounts explicitly. The output of print is-always a valid hledger journal.--hledger's print command also shows all unit prices in effect, or (with--B/--cost) shows cost amounts.--Examples:--    $ hledger print-    $ hledger print employees:bob | hledger -f- register expenses--##### register--The register command displays postings, one per line, and their running total.-With no [filter patterns](#filter-patterns), this is not all that different from [print](#print):--    $ hledger register--More typically, use it to see a specific account's activity:--    $ hledger register assets:bank:checking--The `--depth` option limits the amount of sub-account detail displayed:--    $ hledger register assets:bank:checking --depth 2--With a [reporting interval](#reporting-interval) it shows aggregated-summary postings within each interval:--    $ hledger register --monthly rent-    $ hledger register --monthly -E food --depth 4--##### balance--The balance command displays accounts and their balances, indented to show the account hierarchy.-Examples:--    $ hledger balance-    $ hledger balance food -p 'last month'--A final total is displayed, use `--no-total` to suppress this. Also, the-`--depth N` option shows accounts only to the specified depth, useful for-an overview:--    $ for y in 2006 2007 2008 2009 2010; do echo; echo $y; hledger -f $y.journal balance ^expenses --depth 2; done--With `--flat`, a non-hierarchical list of full account names is displayed-instead. This mode shows just the accounts actually contributing to the-balance, making the arithmetic a little more obvious to non-hledger users.-In this mode you can also use `--drop N` to elide the first few account-name components. Note `--depth` doesn't work too well with `--flat` currently;-it hides deeper accounts rather than aggregating them.--##### chart--The chart command saves a pie chart of your top account balances to an-image file (usually "hledger.png", or use -o/--output FILE). You can-adjust the image resolution with --size=WIDTHxHEIGHT, and the number of-accounts with --items=N.--Note that positive and negative balances will not be displayed together in-the same chart; any balances not matching the sign of the first one will-be omitted.--To show only accounts above a certain depth, use the --depth-option. Otherwise, the chart can include accounts at any depth. If a-parent and child account are both displayed, the parent's balance excludes-the child's.--Examples:--    $ hledger chart assets --depth 2-    $ hledger chart liabilities --depth 2-    $ hledger chart ^expenses -o balance.png --size 1000x600 --items 20-    $ for m in 01 02 03 04 05 06 07 08 09 10 11 12; do hledger -p 2009/$m chart ^expenses --depth 2 -o expenses-2009$m.png --size 400x300; done--This is an optional feature; see [installing](#installing).--##### histogram--The histogram command displays a quick bar chart showing transaction-counts, per day, week, month or other reporting interval. It is-experimental.--Examples:--    $ hledger histogram -p weekly dining--##### stats--The stats command displays quick summary information for the whole journal,-or by period.--Examples:--    $ hledger stats-    $ hledger stats -p 'monthly in 2009'--##### vty--The vty command starts hledger's curses (full-screen, text) user interface,-which allows interactive navigation of the print/register/balance-reports. This lets you browse around your numbers and get quick insights-with less typing.--Examples:--    $ hledger vty-    $ hledger vty -BE food--This is an optional feature; see [installing](#installing).--#### Modifying commands--The following commands can alter your journal file.--##### add--The add command prompts interactively for new transactions, and adds them-to the journal. It is experimental.--Examples:--    $ hledger add-    $ hledger add accounts:personal:bob--##### web--The web command starts hledger's web interface, and tries to open a web-browser to view it. (If this fails, you'll have to manually visit the url-it displays.) The web interface combines the features of the print,-register, balance and add commands, and adds a general edit command.--This is an optional feature. Note there is also an older implementation of-the web command which does not provide edit. See [installing](#installing).--Examples:--    $ hledger web-    $ hledger web -E -B --depth 2-    $ hledger web --port 5010 --base-url http://some.vhost.com --debug -f my.journal--Warning: unlike all other hledger features, the edit form can alter your-existing journal data.  You can edit, or erase, the journal file through-the web ui. There is currently no access control. A numbered backup of the-file will be saved at each edit, in normal circumstances (eg if file-permissions allow, disk is not full, etc.)--There are some options specific to the web server:--    --port=N           web: serve on tcp port N (default 5000)--hledger will serve pages on port 5000 by default.--    --base-url=URL     web: use this base url (default http://localhost:PORT)--Hyperlinks in the web interface all point to "localhost" by default, so if-you want to visit the hledger web server from other machines, you'll need-to use this option. Just give your machine's host name or ip address-instead of localhost. This option may also be useful when running hledger-behind a reverse proxy, to conform to your url scheme. Note that the PORT-in the base url need not be the same as the `--port` argument.--#### Other commands--##### convert--The convert command reads a-[CSV](http://en.wikipedia.org/wiki/Comma-separated_values) file you have-downloaded from your bank, and prints out the transactions in journal-format, suitable for adding to your journal. It does not alter your journal-directly.--This can be a lot quicker than entering every transaction by hand.  (The-downside is that you are less likely to notice if your bank makes an-error!) Use it like this:--    $ hledger convert FILE.csv >FILE.journal--where FILE.csv is your downloaded csv file. This will convert the csv data-using conversion rules defined in FILE.rules (auto-creating this file if-needed), and save the output into a temporary journal file. Then you should-review FILE.journal for problems; update the rules and convert again if-needed; and finally copy/paste transactions which are new into your main-journal.--###### .rules file--convert requires a \*.rules file containing data definitions and rules for-assigning destination accounts to transactions; it will be auto-created if-missing. Typically you will have one csv file and one rules file per bank-account. Here's an example rules file for converting csv data from a Wells-Fargo checking account:--    base-account assets:bank:checking-    date-field 0-    description-field 4-    amount-field 1-    currency $-    -    ; account-assigning rules:-    -    SPECTRUM-    expenses:health:gym-    -    ITUNES-    BLKBSTR=BLOCKBUSTER-    expenses:entertainment-    -    (TO|FROM) SAVINGS-    assets:bank:savings--This says:---   the account corresponding to this csv file is-    assets:bank:checking--   the first csv field is the date, the second is the amount, the-    fifth is the description--   prepend a dollar sign to the amount field--   if description contains SPECTRUM (case-insensitive), the-    transaction is a gym expense--   if description contains ITUNES or BLKBSTR, the transaction is-    an entertainment expense; also rewrite BLKBSTR as BLOCKBUSTER--   if description contains TO SAVINGS or FROM SAVINGS, the-    transaction is a savings transfer--Notes:---   Lines beginning with ; or \# are ignored (but avoid using inside an-    account rule)---   Definitions must come first, one per line, all in one-    paragraph. Each is a name and a value separated by whitespace.-    Supported names are: base-account, date-field, status-field,-    code-field, description-field, amount-field, currency-field,-    currency. All are optional and will use defaults if not specified.---   The remainder of the file is account-assigning rules. Each is a-    paragraph consisting of one or more description-matching patterns-    (case-insensitive regular expressions), one per line, followed by-    the account name to use when the transaction's description matches-    any of these patterns.---   A match pattern may be followed by a replacement pattern,-    separated by `=`, which rewrites the matched part of the-    description. Use this if you want to clean up messy bank data. To-    rewrite the entire description, use a match pattern like-    `.*PAT.*=REPL`. Within a replacement pattern, you can refer to the-    matched text with `\0` and any regex groups with `\1`, `\2` in the-    usual way.---##### test--This command runs hledger's internal self-tests and displays a quick-report. The -v option shows more detail, and a pattern can be provided to-filter tests by name. It's mainly used in development, but it's also nice-to be able to run a sanity check at any time..--Examples:--    $ hledger test-    $ hledger test -v balance--### Other features--#### Filter patterns--Most commands accept one or more filter pattern arguments after the-command name, to select a subset of transactions or postings. There are-two kinds of pattern:---   an account pattern, which is a regular expression. This is-    matched against postings' accounts. Optionally, it may be prefixed-    with `not:` in which case the match is negated.---   a description pattern, like the above but prefixed with-    `desc:`. This is matched against transactions' descriptions. Note,-    when negating a desc: pattern, not: goes last, eg:-    `desc:not:someregexp`.---When you specify multiple filter patterns, hledger generally selects the-transactions or postings which match (or negatively match)--> *any of the account patterns* AND-> *any of the description patterns*--The [print](#print) command selects transactions which--> *match any of the description patterns* AND-> *have any postings matching any of the positive account patterns*-> AND-> *have no postings matching any of the negative account patterns*--#### Dates--##### Simple dates--Within a journal file, dates must follow a fairly simple year/month/day-format. Examples:--> `2010/01/31` or `2010/1/31` or `2010-1-31` or `2010.1.31`--The year is optional if you define a [default year](#default-year).--##### Smart dates--In [period expressions](#period-expressions), the `-b` and `-e` options,-the [add](#add) command and the [web](#web) add form, more flexible "smart-dates" are allowed. Here are some examples:---   `2009/1/1`, `2009/01/01`, `2009-1-1`, `2009.1.1`, `2009/1`,-    `2009` (january 1, 2009)--   `1/1`, `january`, `jan`, `this year` (january 1, this year)--   `next year` (january 1, next year)--   `this month` (the 1st of the current month)--   `this week` (the most recent monday)--   `last week` (the monday of the week before this one)--   `today`, `yesterday`, `tomorrow`--Spaces are optional, so eg: `-b lastmonth` is valid.--##### Actual & effective dates--Real-life transactions sometimes have more than one date.  For-example, you might buy a movie ticket on friday with a bank debit-card, and the transaction might appear in your bank account on monday.-hledger and ledger users call these the *effective date* and *actual-date* respectively. We say:--> *"The ticket purchase took EFFECT on friday, but ACTUALly appeared in my bank balance on monday."*--You can often think of effective date as "my date" and actual date as "bank's date".--When these dates differ, as in the example above, hledger's daily-balances will not exactly match your bank's. But exact daily-reconciling can be quite useful, to see precisely when disagreements-arise. There are several ways you can handle this:--1. don't bother with exact daily reconciling; accept temporary-   disagreements between hledger and bank balances.--2. adjust manually recorded transactions to actual bank dates when necessary.-   Your hledger balance will match your bank's exactly, but you no longer have-   a record of when transactions *really* happened.--3. record both dates separated by an equals sign: the *actual date* on-   the left and the *effective date* on the right. The year is-   optional in the second date.--Here's an example of the last approach: on friday 19 you record:--        2010/2/19 movie-          expenses:cinema          $10-          assets:checking--hledger shows $10 less in your checking account through saturday and-sunday.. but your online bank statement does not. It shows the-transaction clearing a few days later, on monday 23. So you then-insert that actual date:--        ;  ACTUAL=EFFECTIVE-        2010/2/23=2010/2/19 movie-          expenses:cinema          $10-          assets:checking--Now your hledger reports match your bank's daily balance exactly,-since they use the actual date by preference. To report based on-effective dates instead, use the `--effective` flag.--##### Default year--You can set a default year with a `Y` directive in the journal, then-subsequent dates may be written as month/day. Eg:--    Y2009-    -    12/15  ; <- equivalent to 2009/12/15-    -    Y2010-    -    1/31  ; <- equivalent to 2010/1/31--#### Period expressions--hledger supports flexible "period expressions" with the `-p/--period`-option to select transactions within a period of time (eg in 2009) and/or-with a reporting interval (eg weekly). hledger period expressions are-similar but not identical to c++ ledger's.--Here is a basic period expression specifying the first quarter of 2009-(start date is always included, end date is always excluded):--    -p "from 2009/1/1 to 2009/4/1"--Keywords like "from" and "to" are optional, and so are the spaces.  Just-don't run two dates together:--    -p2009/1/1to2009/4/1-    -p"2009/1/1 2009/4/1"--Dates are [smart dates](#smart-dates), so if the current year is 2009, the-above can also be written as:--    -p "1/1 to 4/1"-    -p "january to apr"-    -p "this year to 4/1"--If you specify only one date, the missing start or end date will be the-earliest or latest transaction in your journal:--    -p "from 2009/1/1"  (everything after january 1, 2009)-    -p "from 2009/1"    (the same)-    -p "from 2009"      (the same)-    -p "to 2009"        (everything before january 1, 2009)--A single date with no "from" or "to" defines both the start and end date-like so:--    -p "2009"           (the year 2009;    equivalent to "2009/1/1 to 2010/1/1")-    -p "2009/1"         (the month of jan; equivalent to "2009/1/1 to 2009/2/1")-    -p "2009/1/1"       (just that day;    equivalent to "2009/1/1 to 2009/1/2")--The `-b/--begin` and `-e/--end` options may be used as a shorthand for-`-p 'from ...'` and `-p 'to ...'` respectively. But note [-p overrides other flags](#p-overrides-other-flags).--##### Reporting interval--You can also specify a reporting interval, which causes the "register"-command to summarise the transactions in each interval.  It goes before-the dates, and can be: "daily", "weekly", "monthly", "quarterly", or-"yearly". An "in" keyword is optional, and so are the dates:--    -p "weekly from 2009/1/1 to 2009/4/1"-    -p "monthly in 2008"-    -p "monthly from 2008"-    -p "quarterly"--A reporting interval may also be specified with the `-D/--daily`,-`-W/--weekly`, `-M/--monthly`, `-Q/--quarterly`, and `-Y/--yearly`-options. But note...--##### -p overrides other flags--A `-p/--period` option on the command line will cause any-`-b`/`-e`/`-D`/`-W`/`-M`/`-Q`/`-Y` flags to be ignored.--#### Display expressions--A display expression with the `-d/--display` option selects which-transactions will be displayed (unlike a-[period expression](#period-expressions), which selects the transactions-to be used for calculation).--hledger currently supports a very small subset of c++ ledger's display-expressions, namely: transactions before or after a date.  This is-useful for displaying a portion of your checking register with an-accurate running total. Eg, to show the balance since the first of the month:--    $ hledger register -d "d>=[1]"--#### Depth limiting--With the `--depth N` option, reports will show only the uppermost accounts-in the account tree, down to level N. See the [balance](#balance),-[register](#register) and [chart](#chart) examples.--#### Prices--You can specify a commodity's unit price, or exchange rate, in terms of-another commodity. There are two ways.--First, you can set the price explicitly for a single posting by writing `@-PRICE` after the amount. PRICE is another amount in a different-commodity. Eg, here one hundred euros was purchased at $1.35 per euro:--    2009/1/2 x-     expenses:foreign currency       €100 @ $1.35-     assets--Secondly, you can set the price for a commodity as of a certain date, by-entering a historical price record. These are lines beginning with "P",-appearing anywhere in the journal between transactions. Eg, here we say the-exchange rate for 1 euro is $1.35 on 2009/1/1 (and thereafter, until a-newer price record is found):--    P 2009/1/1 € $1.35  ; <- historical price: P, date, commodity symbol, price in 2nd commodity (space-separated)-    -    2009/1/2 x-     expenses:foreign currency       €100-     assets--The print command shows any unit prices in effect. Either example above-will show:--    $ hledger print-    2009/01/02 x-        expenses:foreign currency  €100 @ $1.35-        assets                     €-100 @ $1.35--To see amounts converted to their total cost, use the `--cost/-B` flag-with any command:--    $ hledger print --cost-    2009/01/02 x-        expenses:foreign currency       $135.00-        assets                         $-135.00--The `--cost/-B` flag does only one lookup step, ie it will not look up the-price of a price's commodity.--Note hledger handles prices differently from c++ ledger in one important-respect: we assume unit prices do not vary over time.  This is good for-simple reporting of foreign currency transactions, but not for tracking-fluctuating-value investments or capital gains.--#### Including other files--You can pull in the content of additional journal files, by writing lines like this:--    !include path/to/file.journal--The `!include` directive may only be used in journal files, and currently-it may only include other journal files (eg, not timelog files.)--#### Default parent account--You can specify a default parent account within a section of the journal with-the `!account` directive:--    !account home-    -    2010/1/1-        food    $10-        cash-    -    !end--If `!end` is omitted, the effect lasts to the end of the file.-The above is equivalent to:--    2010/01/01-        home:food           $10-        home:cash          $-10--Included files are also affected, eg:--    !account business-    !include biz.journal-    !end-    !account personal-    !include personal.journal-    !end--#### Timelog reporting--hledger will also read timelog files in timeclock.el format. As a-convenience, if you invoke hledger via an "hours" symlink or copy, it uses-your timelog file (`~/.timelog` or `$TIMELOG`) by default, rather than your-journal.--Timelog entries look like this:--    i 2009/03/31 22:21:45 some:project-    o 2009/04/01 02:00:34--The clockin description is treated as an account name. Here are some-queries to try (after downloading-[sample.timelog](http://joyful.com/repos/hledger/sample.timelog)):--    ln -s `which hledger` ~/bin/hours            # set up "hours" in your path-    export TIMELOG=sample.timelog-    hours                                        # show all time balances-    hours -p 'last week'                         # last week-    hours -p thismonth                           # the space is optional-    hours -p 'from 1/15' register project        # project sessions since jan 15-    hours -p 'weekly' reg --depth 1 -E           # weekly time summary--This is a useful feature, if you can find a way to efficiently record-timelog entries. The "ti" and "to" scripts may be available from the c++-ledger 2.x repository. I use-[timeclock-x.el](http://www.emacswiki.org/emacs/timeclock-x.el) and-[ledgerutils.el](http://joyful.com/repos/ledgertools/ledgerutils.el) in-emacs.--### Compatibility with c++ ledger--#### Implementation--Unlike c++ ledger, hledger is written in the Haskell programming-language. Haskell enables a coding style known as pure lazy functional-programming, which holds the promise of more robust and maintainable-software built with fewer lines of code. Haskell also provides a more-abstracted, portable platform which can make deployment and installation-easier in some cases. Haskell also brings some new challenges such as-managing memory growth.--#### File format compatibility--hledger's file format is mostly identical with that of c++ ledger version-2, with some features (like modifier and periodic entries) being accepted,-but ignored. There are also some subtle differences in parser behaviour-(eg comments may be permissible in different places.) C++ ledger version 3-has introduced additional syntax, which current hledger probably fails to-parse.--Generally, it's easy to keep a journal file that works with both hledger-and c++ledger if you avoid the more esoteric syntax.  Occasionally you'll-need to make small edits to restore compatibility for one or the other.--hledger does not allow separate dates for individual postings, unlike c++-ledger.--#### Features not supported--c++ ledger features not currently supported include: modifier and periodic-entries, and the following c++ ledger options and commands:--    Basic options:-    -o, --output FILE      write output to FILE-    -i, --init-file FILE   initialize ledger using FILE (default: ~/.ledgerrc)-    -a, --account NAME     use NAME for the default account (useful with QIF)-    -    Report filtering:-    -c, --current          show only current and past entries (not future)-        --period-sort EXPR sort each report period's entries by EXPR-    -L, --actual           consider only actual (non-automated) transactions-    -r, --related          calculate report using related transactions-        --budget           generate budget entries based on periodic entries-        --add-budget       show all transactions plus the budget-        --unbudgeted       show only unbudgeted transactions-        --forecast EXPR    generate forecast entries while EXPR is true-    -l, --limit EXPR       calculate only transactions matching EXPR-    -t, --amount EXPR      use EXPR to calculate the displayed amount-    -T, --total EXPR       use EXPR to calculate the displayed total-    -    Output customization:-    -n, --collapse         Only show totals in the top-most accounts.-    -s, --subtotal         other: show subtotals-    -P, --by-payee         show summarized totals by payee-    -x, --comm-as-payee    set commodity name as the payee, for reporting-        --dow              show a days-of-the-week report-    -S, --sort EXPR        sort report according to the value expression EXPR-    -w, --wide             for the default register report, use 132 columns-        --head COUNT       show only the first COUNT entries (negative inverts)-        --tail COUNT       show only the last COUNT entries (negative inverts)-        --pager PAGER      send all output through the given PAGER program-    -A, --average          report average transaction amount-    -D, --deviation        report deviation from the average-    -%, --percentage       report balance totals as a percentile of the parent-        --totals           in the "xml" report, include running total-    -j, --amount-data      print only raw amount data (useful for scripting)-    -J, --total-data       print only raw total data-    -y, --date-format STR  use STR as the date format (default: %Y/%m/%d)-    -F, --format STR       use STR as the format; for each report type, use:-        --balance-format      --register-format       --print-format-        --plot-amount-format  --plot-total-format     --equity-format-        --prices-format       --wide-register-format-    -    Commodity reporting:-        --price-db FILE    sets the price database to FILE (def: ~/.pricedb)-    -L, --price-exp MINS   download quotes only if newer than MINS (def: 1440)-    -Q, --download         download price information when needed-    -O, --quantity         report commodity totals (this is the default)-    -V, --market           report last known market value-    -g, --performance      report gain/loss for each displayed transaction-    -G, --gain             report net gain/loss-    -    Commands:-    xml      [REGEXP]...   print matching entries in XML format-    equity   [REGEXP]...   output equity entries for matching accounts-    prices   [REGEXP]...   display price history for matching commodities-    entry DATE PAYEE AMT   output a derived entry, based on the arguments--#### Other differences---   hledger recognises description and negative patterns by "desc:"-    and "not:" prefixes, unlike ledger 3's free-form parser---   hledger doesn't require a space before command-line option-    values, eg either `-f-` or `-f -` is fine---   hledger's weekly reporting intervals always start on mondays---   hledger shows start and end dates of the intervals requested,-    not just the span containing data---   hledger period expressions don't support "biweekly",-    "bimonthly", or "every N days/weeks/..."---   hledger always shows timelog balances in hours---   hledger splits multi-day timelog sessions at midnight---   hledger doesn't track the value of commodities with varying-    price; prices are fixed as of the transaction date---   hledger print shows amounts for all postings, and shows unit-    prices for amounts which have them. (This currently means that-    it does not print multi-commodity transactions in valid journal format.)--### More examples and recipes---   Here's a bash function that will run hledger chart and display-    the image in your (graphical) emacs:--        function chart () {-          hledger chart $* && emacsclient -n hledger.png-        }--    Example:--        $ chart food --depth 2 -p jan--### Troubleshooting--#### Installation issues--cabal builds a lot of fast-evolving software, and it's not always smooth-sailing.  Here are some known issues and things to try:--- **Ask for help on [#hledger](irc://freenode.net/#hledger) or [#haskell](irc://freenode.net/#haskell).**-  Eg: join the #hledger channel with your IRC client and type: "sm: I did ... and ... happened", then leave-  that window open until you get helped.--- **Did you cabal update ?** If you didn't already, `cabal update` and try again.--- **Do you have a new enough version of GHC ?** hledger supports GHC 6.10-  and 6.12. Building with the `-fweb` flag requires 6.12 or greater.--- **An error while building non-hledger packages.**-  Resolve these problem packages one at a time. Eg, cabal install pkg1.-  Look for the cause of the failure near the end of the output. If it's-  not apparent, try again with `-v2` or `-v3` for more verbose output.--- **Could not run happy.**-  A package (eg haskell-src-exts) needs to run the `happy` executable.-  If not using the haskell platform, install the appropriate platform-  package which provides it (eg apt-get install happy).--- <a name="iconv" />**Undefined symbols: ... _iconv ...**-  If cabal gives this error:--        Linking dist/build/hledger/hledger ...-        Undefined symbols:-          "_iconv_close", referenced from:-              _hs_iconv_close in libHSbase-4.2.0.2.a(iconv.o)-          "_iconv", referenced from:-              _hs_iconv in libHSbase-4.2.0.2.a(iconv.o)-          "_iconv_open", referenced from:-              _hs_iconv_open in libHSbase-4.2.0.2.a(iconv.o)--    you are probably on a mac with macports libraries installed, causing-    [this issue](http://hackage.haskell.org/trac/ghc/ticket/4068).-    To work around temporarily, add this --extra-lib-dirs flag:--        $ cabal install hledger --extra-lib-dirs=/usr/lib--    or permanently, add this to ~/.cabal/config:-    -        extra-lib-dirs: /usr/lib--- **A ghc: panic! (the 'impossible' happened)** might be-    [this issue](http://hackage.haskell.org/trac/ghc/ticket/3862)--- **This package indirectly depends on multiple versions of the same package.**-  You may have previously installed some of hledger's dependencies-  depending on different versions of (eg) parsec. Then cabal install hledger gives-  an error like this:--        Warning: This package indirectly depends on multiple versions of the same-        package. This is highly likely to cause a compile failure.-        package yesod-0.5.0.3 requires parsec-2.1.0.1-        package csv-0.1.1 requires parsec-3.1.0-        ...--    The above example could be resolved by, eg:--        $ cabal install yesod --reinstall --constraint 'parsec == 3.1.0"--- **Another error while building a hledger package.**-    The current hledger release might have a coding error, or dependency-    error. You could try installing the-    [previous version](http://hackage.haskell.org/package/hledger):--        $ cabal install hledger-0.x--    or (preferably) the latest development version: install-    [darcs](http://darcs.net) and then:--        $ darcs get --lazy http://joyful.com/repos/hledger-        $ cd hledger/hledger-lib-        $ cabal install-        $ cd ..-        $ cabal install [-f...]--- **Do you have a new enough version of cabal-install ?**-  Recent versions tend to be better at resolving dependencies.  The error-  `setup: failed to parse output of 'ghc-pkg dump'` is another symptom of-  this.  To update, do:-  -        $ cabal update-        $ cabal install cabal-install-        $ cabal clean-        -    then try installing hledger again.--- **cabal fails to resolve dependencies.**-  It's possible for cabal to get confused, eg if you have-  installed/updated many cabal package versions or GHC itself. You can-  sometimes work around this by using cabal install's `--constraint`-  option. Another (drastic) way is to purge all unnecessary package-  versions by removing (or renaming) ~/.ghc, then trying cabal install-  again.--#### Usage issues--Here are some issues you might encounter when you run hledger:--- <a name="locale" />**hledger: ... hGetContents: invalid argument (Illegal byte sequence)**-  You may get this error when running hledger built with GHC 6.12 on a-  machine using the default C locale, eg a mac:-  -        $ locale-        LANG=-        LC_COLLATE="C"-        LC_CTYPE="C"-        LC_MESSAGES="C"-        LC_MONETARY="C"-        LC_NUMERIC="C"-        LC_TIME="C"-        LC_ALL=--    if there is non-ascii text in your journal file:--        $ file my.journal-        .../.journal: UTF-8 Unicode C++ program text-  -    In this case you need to set the `LANG` environment variable to a-    locale suitable for the encoding shown (probably UTF-8). You-    can set it temporarily when you run hledger:-  -        $ LANG=en_US.UTF-8 hledger ...-      -    or permanently:-  -        $ echo "export LANG=en_US.UTF-8" >>~/.bash_profile-        $ bash --login-
− NEWS.markdown
@@ -1,453 +0,0 @@-----title: hledger news------# hledger news--## 2010/9/6 hledger 0.12.1--  * web: fix account filtering breakage-  * installing: tighten up utf8-string dependency--## 2010/9/5 hledger 0.12--  * web: new, better web ui; accounts are now a permanent sidebar; add form uses auto-completing combo fields-  * installing: fix a build error with parsec 3 (#22)-  * installing: require exactly matching hledger-lib version for more robust builds-  * installing: explicit data-object dependency to ensure hledger and hledger-lib use the same time version-  * installing: explicit hamlet dependency for more robust building-  * installing: build threaded and with warnings-  * installing: drop -fweb610 flag-  * installing: add gtk2hs-buildtools dependency needed to build with -fchart-  * installing: require cabal 1.6 or greater-  * add -D/--daily flag-  * register: with --depth, clip account names or aggregate postings rather than excluding them-  * fix !include with deeply nested directories (#21)-  * fix obscured date parse errors with parsec 3-  * handle unicode better in errors-  * fix a ghc 6.12.3 error when running interpreted--Stats: 50 days and 90 commits since last release, now at 5741-lines of code with 136 tests and 41% unit test coverage.--## 2010/07/17 hledger 0.11.1--  * fix --version output--## 2010/07/17 hledger 0.11--  * split --help, adding --help-options and --help-all/-H, and make-    it the default command-  * use "journal" instead of "ledger file"; default suffix is-    .journal, default file is \~/.journal-  * auto-create missing journal files rather than giving an error-  * new format-detecting file reader (mixed journal transactions-    and timelog entries are no longer supported)-  * work around for first real-world rounding issue (test zero to 8-    decimal places instead of 10)-  * when reporting a balancing error, convert the error amount to-    cost-  * parsing: support double-quoted commodity symbols, containing-    anything but a newline or double quote-  * parsing: allow minus sign before commodity symbol as well as-    after (also fixes a convert bug)-  * parsing: fix wrong parse error locations within postings-  * parsing: don't let trailing whitespace in a timelog description-    mess up account names-  * add: allow blank descriptions-  * balance: --flat provides a simple non-hierarchical format-  * balance: --drop removes leading account name components from a-    --flat report-  * print, register, balance: fix layout issues with-    mixed-commodity amounts-  * print: display non-simple commodity names with double-quotes-  * stats: layout tweaks, add payee/description count-  * stats: don't break on an empty file-  * stats: -p/--period support; a reporting interval generates-    multiple reports-  * test: drop verbose test runner and testpack dependency-  * web: a new web ui based on yesod, requires ghc 6.12; old ghc-    6.10-compatible version remains as -fweb610-  * web: allow wiki-like journal editing-  * web: warn and keep running if reloading the journal gives an-    error-  * web: --port and --base-url options set the webserver's tcp port-    and base url-  * web: slightly better browser opening on microsoft windows,-    should find a standard firefox install now-  * web: in a web-enabled build on microsoft windows, run the web-    ui by default--Stats: 55 days and 136 commits since last release. Now at 5552-lines of code with 132 tests and 54% unit test coverage.--## 2010/05/23 hledger 0.10--  * fix too-loose testpack dependency, missing safe dependency-  * fix ghc 6.12 compatibility with -fweb-  * fix handling of non-ascii arguments with ghc 6.12-  * fix "0.8" in --version output-  * fix an occasional stack overflow error due to infinite-    recursion in Posting/Transaction equality tests-  * the -fwebhappstack build flag is gone for now, to avoid a cabal-    problem-  * parsing: if there is no description, don't require a space-    after the transaction date-  * parsing: balance balanced-virtual postings separately, allow-    them to have an implicit amount-  * parsing: timelog entries now generate balanced transactions,-    using virtual postings-  * parsing: simpler high-level parse error message-  * parsing: clearer bad date errors-  * add: fix wrongful program exit on bad dates-  * print: negative account patterns now exclude transactions-    containing any posting to a matched account-  * vty: rename the ui command to vty for consistency-  * vty: fix restricted account scope when backing up to top level-  * web: fix non-ascii handling with ghc 6.12-  * web: fix a bug possibly affecting reload-on-change-  * consolidate module namespace under Hledger, api cleanups--Stats: 44 days, 81 commits since last release. Now at 4904 lines of-code including tests, 144 tests, 53% coverage.--## 2010/04/10 hledger 0.9--  * ghc 6.12 support-  * split off hledger-lib package, containing core types & utils-  * parsing: ignore D, C, N, tag, end tag directives; we should now-    accept any ledger 2.6 file-  * parsing: allow numbers in commodities if double-quoted, like-    ledger-  * parsing: allow transactions with empty descriptions-  * parsing: show a better error for illegal month/day numbers in-    dates-  * parsing: don't ignore trailing junk in a smart date, eg in web-    add form-  * parsing: don't ignore unparsed text following an amount-  * parsing: @ was being treated as a currency symbol-  * add: fix precision handling in default amounts (\#19)-  * add: elide last amount in added transactions-  * convert: keep original description by default, allow-    backreferences in replace pattern-  * convert: basic csv file checking, warn instead of dying when it-    looks wrong-  * convert: allow blank/comment lines at end of rules file-  * print: always show zero amounts as 0, hiding any-    commodity/decimal places/price, like ledger-  * register: fix bad layout with years < 1000-  * register: fix a Prelude.head error with reporting interval,-    --empty, and --depth-  * register: fix a regression, register should not show posting-    comments-  * register: with --empty, intervals should continue to ends of-    the specified period-  * stats: better output when last transaction is in the future-  * stats: show commodity symbols, account tree depth, reorder-    slightly-  * web: -fweb now builds with simpleserver; to get happstack, use-    -fwebhappstack instead-  * web: pre-fill the add form with today's date-  * web: help links, better search form wording-  * web: show a proper error for a bad date in add form (\#17)-  * web: fix for unicode search form values-  * web: fix stack overflow caused by regexpr, and handle requests-    faster (\#14)-  * web: look for more-generic browser executables-  * web: more robust browser starting (\#6)-  * error message cleanups-  * more tests, refactoring, docs--Stats: 58 days, 2 contributors, 102 commits since last release. Now-at 3983 lines of non-test code, 139 tests, 53% coverage.--## 2010/02/11 hledger 0.8--  * parsing: in date=date2, use first date's year as a default for-    the second-  * add: ctrl-d doesn't work on windows, suggest ctrl-c instead-  * add: --no-new-accounts option disallows new accounts (Roman-    Cheplyaka)-  * add: re-use the previous transaction's date as default (Roman-    Cheplyaka)-  * add: a command-line argument now filters by account during-    history matching (Roman Cheplyaka)-  * chart: new command, generates balances pie chart (requires-    -fchart flag, gtk2hs) (Roman Cheplyaka, Simon Michael)-  * register: make reporting intervals honour a display expression-    (\#18)-  * web: fix help link-  * web: use today as default when adding with a blank date-  * web: re-enable account/period fields, they seem to be fixed,-    along with file re-reading (\#16)-  * web: get static files from the cabal data dir, or the current-    dir when using make (\#13)-  * web: preserve encoding during add, assuming it's utf-8 (\#15)-  * fix some non-utf8-aware file handling (\#15)-  * filter ledger again for each command, not just once at program-    start-  * refactoring, clearer data types--Stats: 62 days, 2 contributors, 76 commits since last release. Now-at 3464 lines of non-test code, 97 tests, 53% test coverage.--## 2009/12/11 hledger 0.7--  * price history support (first cut): P directives now work,-    though differently from c++ ledger. Each posting amount takes its-    fixed unit price from the price history (or-    @) when available. This is simple and useful for things like-    foreign currency expenses (but not investment tracking). Like-    ledger, balance and register don't show amount prices any more, and-    don't separate differently-priced amounts. Unlike ledger, print-    shows all amount prices, and supports -B.-  * --effective option, will use transactions' effective dates if-    any-  * convert: new rules file format, find/create rules file-    automatically, more robust parsing, more useful --debug output-  * print: always sort by date, fix long account name truncation,-    align amounts, show end of line comments, show all amounts for-    clarity (don't elide the final balancing amount)-  * ui: use vty 4, fixes non-ascii and gnome terminal problems-    (issues \#3, \#4)-  * web: allow data entry, react to data file changes, better-    layout, help links, remove histogram command and filter fields for-    now, fix bad localhost redirect, filter form did not work in eg-    firefox (issue \#7), reset link did not work in all browsers-  * parsing: require whitespace between date and status code, allow-    (and ignore) a time in price records, better error messages,-    non-zero exit code on parse failure-  * display non-ascii error messages properly (issue \#5)-  * fix an arithmetic bug that occasionally rejected valid-    transactions-  * fix a regex bug in showtree-  * don't break if HOME is undefined-  * --debug now implies --verbose-  * add functional tests like ledger's, use test-framework for-    speedy running, release shelltestrunner as a separate package-  * many hlint cleanups (Marko Kocić)-  * many site and documentation updates--Stats: 60 days, 1 contributor, 50 commits since last release. Now-at 3377 lines of non-test code, 97 tests, 53% test coverage.--## 2009/06/22 hledger 0.6.1--  * avoid use of exitSuccess which was breaking ghc 6.8/base 3-    compatibility (issue \#2)--## 2009/06/13 hledger 0.6--  * now cabal-installable on unix, mac, and windows, with Haskell-    Platform-  * provide experimental platform binaries-  * parsing: fix a silly failure to open ledger file paths-    containing \~-  * parsing: show better errors for unbalanced transaction and-    missing default year-  * parsing: allow parentheses and brackets inside account names,-    as ledger does-  * parsing: fail on empty account name components, don't just-    ignore-  * add: description passed as arguments now affects first-    transaction only-  * add: better handling of virtual postings and default amounts-  * print, register: show virtual accounts bracketed/parenthesised-  * web: improved web ui supporting full patterns & period-    expressions-  * new "stats" command reports some ledger statistics-  * many dev/doc/deployment infrastructure improvements-  * move website into darcs repo, update home page-  * move issue tracker to google code--Release stats:--  * Contributors: Simon Michael-  * Days since last release: 21-  * Commits: 94-  * Lines of non-test code: 2865-  * Tests: 82-  * Test coverage: 53% expressions-  * Known errors: 3 (inconsistent eliding, vty-related failures)-  * Performance: similar-    (http://hledger.org/profs/200906131120.bench)--## 2009/05/23 hledger 0.5.1--  * two fixes: really disable vty flag by default, and include-    ConvertCommand in cabal file--## 2009/05/23 hledger 0.5--  * the vty flag is disabled by default again, to ease installation-    on windows-  * use ledger 3 terminology: a ledger contains transactions which-    contain postings-  * new "add" command prompts for transactions interactively and-    adds them to the ledger-  * new "convert" command transforms bank CSV exports to ledger-    format, with rule-based cleanup-  * new "histogram" command shows transaction counts per day or-    other reporting interval-  * most commands now work properly with UTF8-encoded text (Sergey-    Astanin)-  * invoking as "hours" is now less different: it just uses your-    timelog, not your ledger-  * ..quarterly/-Q option summarises by quarter-  * ..uncleared/-U option looks only at uncleared transactions-  * be more accurate about checking balanced amounts, don't rely on-    display precision-  * enforce balancing for bracketed virtual postings-  * fix bug in eliding of posting amounts-  * don't show trailing spaces on amountless postings-  * parse null input as an empty ledger-  * don't treat comments as part of transaction descriptions-  * require some postings in ledger transactions-  * require a non-empty description in ledger transactions-  * don't fail when matching an empty pattern, as in "not:"-  * make the web server handle the null path-  * code, api and documentation updates-  * add a contributor agreement/list--Release stats:--  * Contributors: Simon Michael, Sergey Astanin-  * Days since last release: 51-  * Commits: 101-  * Lines of non-test code: 2795-  * Tests: 76-  * Known errors: 0--## 2009/04/03 hledger 0.4--  * new "web" command serves reports in a web browser (install with-    -f happs to build this)-  * make the vty-based curses ui a cabal build option, which will-    be ignored on MS windows-  * drop the ..options-anywhere flag, that is now the default-  * patterns now use not: and desc: prefixes instead of \^ and \^\^-  * patterns are now case-insensitive, like ledger-  * !include directives are now relative to the including file (Tim-    Docker)-  * "Y2009" default year directives are now supported, allowing m/d-    dates in ledger-  * individual transactions now have a cleared status-  * unbalanced entries now cause a proper warning-  * balance report now passes all ledger compatibility tests-  * balance report now shows subtotals by default, like ledger 3-  * balance report shows the final zero total when -E is used-  * balance report hides the final total when ..no-total is used-  * ..depth affects print and register reports (aggregating with a-    reporting interval, filtering otherwise)-  * register report sorts transactions by date-  * register report shows zero-amount transactions when -E is used-  * provide more convenient timelog querying when invoked as-    "hours"-  * multi-day timelog sessions are split at midnight-  * unterminated timelog sessions are now counted. Accurate time-    reports at last!-  * the test command gives better ..verbose output-  * ..version gives more detailed version numbers including-    patchlevel for dev builds-  * new make targets include: ghci, haddocktest, doctest, unittest,-    view-api-docs-  * a doctest-style framework for functional/shell tests has been-    added--Release stats:--  * Contributors: Simon Michael, Tim Docker; thanks to the HAppS,-    happstack and testpack developers-  * Days since release: 76-  * Commits: 144-  * Lines of non-test code: 2367-  * Tests: 56-  * Known errors: 0--## 2009/01/17 hledger 0.3--  * count timelog sessions on the day they end, like ledger, for-    now-  * when options are repeated, use the last instead of the first-  * builds with ghc 6.10 as well as 6.8-  * a simple ui for interactive report browsing: hledger ui-  * accept smart dates everywhere (YYYYMMDD, Y/M/D, Y, M/D, D, jan,-    today, last week etc.)-  * ..period/-p flag accepting period expressions like "in 2008",-    "weekly from last month"..-  * -W/-M/-Y convenience flags to summarise register weekly,-    monthly, yearly-  * ..depth and -E flags also affect summarised register reports-    (including depth=0)-  * ..display/-d flag supporting date predicates (like "d<[DATE]",-    "d\>=[DATE]")-  * !include directive to include additional ledger files-  * !account directive to set a default parent account-  * Added support for reading historical prices from files-  * timelog and ledger entries can be intermixed in one file-  * modifier and periodic entries can appear anywhere (but are-    still ignored)-  * help and readme improvements-  * runs much faster than 0.2--Release stats:--  * Contributors: Simon Michael, Nick Ingolia, Tim Docker; thanks-    to Corey O'Connor & the vty team-  * Lines of non-test code: 2123-  * Tests: 58-  * Known errors: 1--## 2008/11/23 hledger 0.2--  * fix balance report totals when filtering by account-  * fix balance report selection of accounts when filtering by-    account-  * fix a bug with account name eliding in balance report-  * if we happen to be showing a not-yet-auto-balanced entry, hide-    the AUTO marker-  * fix print command filtering by account-  * omit transactions with zero amount from register report-  * Fix bug in parsing of timelogs-  * rename ..showsubs to ..subtotal, like ledger-  * drop ..usage flag-  * don't require quickcheck-  * priced amounts (eg "10h @ $50") and ..basis/..cost/-B flag to-    show them with cost basis-  * easy ..depth option, equivalent to c++ ledger's -d 'l<=N'-  * smarter y/m/d date parsing for -b and -e (any number of digits,-    month and day default to 1, separator can be / - or .)-  * -n flag for balance command-  * ..empty/-E flag-  * build a library, as well as the exe-  * new home page url (http://joyful.com/hledger)-  * publish html and pdf versions of README-  * detect display preferences for each commodity like c++ ledger-  * support amounts with multiple currencies/commodities-  * support ..real/-R flag-  * support -C/..cleared flag to filter by entry status (not-    transaction status)-  * support virtual and balanced virtual transactions-  * parse comment lines beginning with a space, as from M-; in-    emacs ledger-mode-  * allow any non-whitespace in account names, perhaps avoiding-    misleading missing amounts errors-  * clearer error message when we can't balance an entry-  * when we fail because of more than one missing amount in an-    entry, show the full entry-  * document the built-in test runner in ..help-  * add a ..verbose/-v flag, use it to show more test-running-    detail--Release stats:--  * Contributors: Simon Michael, Tim Docker-  * Lines of non-test code: 1350-  * Tests: 43-  * Known errors: 0--## 2008/10/15 hledger 0.1--Release stats:--  * Contributors: Simon Michael
− README.rst
@@ -1,34 +0,0 @@-----title: hledger-----hledger-=======--hledger is a computer program for easily tracking money, time, or other-commodities, using standard accounting principles. It is quite limited in-features, but reliable.  For some, it is a bare-bones, less complex, less-expensive alternative to Quicken or Microsoft Money.-hledger is available for free under the GNU General Public License.--hledger aims to help both computer experts and every-day users gain clarity in their finances and time management.-I use it every day to: --- track spending and income-- see time reports by day/week/month/project-- get accurate numbers for client billing and tax filing-- track invoices--The manual_ and screenshots_ will explain more. There's also a demo_ of hledger's web interface.-To get started tracking your own numbers, download_ a copy.--For support and more technical info, see `development`_ or `email me`_.-I appreciate feedback on all aspects of hledger. Thanks!---.. _manual:        MANUAL.html-.. _screenshots:   SCREENSHOTS.html-.. _demo:          http://demo.hledger.org-.. _web interface: MANUAL.html#web-.. _download:      DOWNLOAD.html-.. _development:   DEVELOPMENT.html-.. _email me:      mailto:simon@joyful.com?subject=hledger:
− data/sample.journal
@@ -1,40 +0,0 @@-; A sample journal file.-;-; Sets up this account tree:-; assets-;   bank-;     checking-;     saving-;   cash-; expenses-;   food-;   supplies-; income-;   gifts-;   salary-; liabilities-;   debts--2008/01/01 income-    assets:bank:checking  $1-    income:salary--2008/06/01 gift-    assets:bank:checking  $1-    income:gifts--2008/06/02 save-    assets:bank:saving  $1-    assets:bank:checking--2008/06/03 * eat & shop-    expenses:food      $1-    expenses:supplies  $1-    assets:cash--2008/12/31 * pay off-    liabilities:debts  $1-    assets:bank:checking---;final comment
− data/sample.rules
@@ -1,17 +0,0 @@-base-account assets:bank:checking-date-field 0-description-field 4-amount-field 1-currency $--; account-assigning rules:--SPECTRUM-expenses:health:gym--ITUNES-BLKBSTR=BLOCKBUSTER-expenses:entertainment--(TO|FROM) SAVINGS-assets:bank:savings
− data/sample.timelog
@@ -1,6 +0,0 @@-i 2009/03/27 09:00:00 projects:a-o 2009/03/27 17:00:34-i 2009/03/31 22:21:45 personal:reading:online-o 2009/04/01 02:00:34-i 2009/04/02 09:00:00 projects:b-o 2009/04/02 17:00:34
− data/web/static/dhtmlxcombo.js
@@ -1,1359 +0,0 @@-//v.2.6 build 100722--/*
-Copyright DHTMLX LTD. http://www.dhtmlx.com
-You allowed to use this component or parts of it under GPL terms
-To use it on other terms or get Professional edition of the component please contact us at sales@dhtmlx.com
-*/-
-/*_TOPICS_
-@0:Initialization
-@1:Selection control
-@2:Add/delete
-@3:Reserved
-@4:Methods of Option object
-*/
-
-/**
-*  Build combobox from existing select control.
-*
-*
-*  @param   parent      {string} id of existing select control
-*  @param   size      {int } size of control, optional
-*  @return            {object} combobox object
-*  @type   public
-*  @topic   0
-*
-*/
-
-function dhtmlXComboFromSelect(parent,size){
-   if (typeof(parent)=="string")
-      parent=document.getElementById(parent);
-   
-   
-   size=size||parent.getAttribute("width")||(window.getComputedStyle?window.getComputedStyle(parent,null)["width"]:(parent.currentStyle?parent.currentStyle["width"]:0));
-   if ((!size)||(size=="auto"))
-   		size=parent.offsetWidth||100;
-
-   var z=document.createElement("SPAN");
-   
-	
-  
-   parent.parentNode.insertBefore(z,parent);
-   parent.style.display='none';
-
-    var s_type = parent.getAttribute('opt_type');
-	
-   var w= new dhtmlXCombo(z,parent.name,size,s_type,parent.tabIndex);
-   
-   var x=new Array();
-   var sel=0;
-   for (var i=0; i<parent.options.length; i++){
-      if (parent.options[i].selected) sel=i;
-      var label=parent.options[i].innerHTML;
-      var val=parent.options[i].getAttribute("value");
-      if ((typeof(val)=="undefined")||(val===null)) val=label;
-      x[i]={value:val,text:label,img_src:parent.options[i].getAttribute("img_src")};
-   }
-    if(x.length)
-    w.addOption(x);
-   
-   
-	parent.parentNode.removeChild(parent);
-	w.selectOption(sel,null,true);
-	if (parent.onchange) 
-		w.attachEvent("onChange",parent.onchange);
-   		
-   	if(parent.style.direction=="rtl" && w.setRTL) 
-   		w.setRTL(true);
-   	
-   return w;
-}
-
-var dhtmlXCombo_optionTypes = [];
-/**
-*     @desc: build combobox
-*     @param: parent - (string) id of existing object which will be used as container
-*     @param: name - (string) name of combobox - will be used in FORM
-*     @param: width - (int) size of combobox
-*     @param: tabIndex - (int) tab index, optional
-*     @type: public
-*     @topic: 0
-*/
-function dhtmlXCombo(parent,name,width,optionType,tabIndex){
-      if (typeof(parent)=="string")
-         parent=document.getElementById(parent);
-
-      this.dhx_Event();
-      this.optionType = (optionType != window.undefined && dhtmlXCombo_optionTypes[optionType]) ? optionType : 'default';
-      this._optionObject = dhtmlXCombo_optionTypes[this.optionType];
-
-      this._disabled = false;
-	  
-	  if (!window.dhx_glbSelectAr){
-          window.dhx_glbSelectAr=new Array();
-          window.dhx_openedSelect=null;
-          window.dhx_SelectId=1;
-          dhtmlxEvent(document.body,"click",this.closeAll);
-          dhtmlxEvent(document.body,"keydown",function(e){ try { if ((e||event).keyCode==9)  window.dhx_glbSelectAr[0].closeAll(); } catch(e) {} return true; } );
-      }
-		
-     if (parent.tagName=="SELECT")
-         return dhtmlXComboFromSelect(parent);
-      else
-         this._createSelf(parent,name,width,tabIndex);
-      dhx_glbSelectAr.push(this);
-}
-
-/**
-*     @desc: change control size
-*     @param: new_size - (int) new size value
-*     @type: public
-*     @topic: 0
-*/
-   dhtmlXCombo.prototype.setSize = function(new_size){
-      this.DOMlist.style.width=new_size+"px";
-      if (this.DOMlistF) this.DOMlistF.style.width=new_size+"px";
-      this.DOMelem.style.width=new_size+"px";
-      this.DOMelem_input.style.width = Math.max(0,(new_size-19))+'px';
-   }      
-/**
-*     @desc: switch between combobox and auto-filter modes
-*     @param: mode - (boolean) enable filtering mode
-*     @param: url - (string) url for filtering from XML, optional
-*     @param: cache - (boolean) XML caching, optional
-*     @param: autosubload - (boolean) enable auto load additional suggestions on selecting last loaded option
-*     @type: public
-*     @topic: 0
-*/
-   dhtmlXCombo.prototype.enableFilteringMode = function(mode,url,cache,autosubload){
-      this._filter=convertStringToBoolean(mode);
-
-      if (url){
-         this._xml=url;
-         this._autoxml=convertStringToBoolean(autosubload);
-      }
-      if (convertStringToBoolean(cache)) this._xmlCache=[];
-      //this.DOMelem_button.style.display=(this._filter?"none":"");
-   }
-   
-   dhtmlXCombo.prototype.setFilteringParam=function(name,value){
-   		if (!this._prs) this._prs=[];
-   		this._prs.push([name,value]);
-   }
-/**
-*     @desc: disable combobox
-*     @param: mode - (boolean) disable combobox
-*     @type: public
-*     @topic: 1
-*/
-   dhtmlXCombo.prototype.disable = function(mode){
-      var z=convertStringToBoolean(mode);
-      if (this._disabled==z) return;
-      this.DOMelem_input.disabled=z;
-      this._disabled=z;
-   }
-/**
-*     @desc: switch to readonly mode
-*     @param: mode - (boolean) readonly mode
-*     @param: autosearch - (boolean) true by default 
-*     @type: public
-*     @topic: 1
-*/
-   dhtmlXCombo.prototype.readonly = function(mode,autosearch){
-        this.DOMelem_input.readOnly=mode ? true : false;
-		if(autosearch===false || mode===false){
-			this.DOMelem.onkeyup=function(ev){ }
-		} else {
-			var that = this;
-			this.DOMelem.onkeyup=function(ev){ 
-				ev=ev||window.event; 
-				if (ev.keyCode!=9) ev.cancelBubble=true;
-				if((ev.keyCode >= 48 && ev.keyCode <= 57)||(ev.keyCode >= 65 && ev.keyCode <= 90)){
-					for(var i=0; i<that.optionsArr.length; i++){
-						var text = that.optionsArr[i].text;
-						if(text.toString().toUpperCase().indexOf(String.fromCharCode(ev.keyCode)) == 0){
-								that.selectOption(i);
-								break;
-						}
-					}
-					ev.cancelBubble=true;
-				}
-			}
-		}
-	}	
-   
-/**
-*     @desc: get Option by value
-*     @param: value - (string) value of option in question
-*     @type: public
-*     @return: option object
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.getOption = function(value)
-   {
-      for(var i=0; i<this.optionsArr.length; i++)
-         if(this.optionsArr[i].value==value)
-            return this.optionsArr[i];
-      return null;
-   }
-/**
-*     @desc: get Option by label
-*     @param: label - (string) label of option in question
-*     @type: public
-*     @return: option object
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.getOptionByLabel = function(value)
-   {
-	  //value=value.replace(/&/g,"&amp;")
-      for(var i=0; i<this.optionsArr.length; i++)
-         if(this.optionsArr[i].text==value || this.optionsArr[i]._ctext==value)
-            return this.optionsArr[i];
-      return null;
-   }
-/**
-*     @desc: get Option by index
-*     @param: ind - (int) index of option in question
-*     @type: public
-*     @return: option object
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.getOptionByIndex = function(ind){
-      return this.optionsArr[ind];
-   }
-/**
-*     @desc: clear all options from combobox
-*     @type: public
-*     @param: value - (bool) clear current value as well
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.clearAll = function(all)
-   {
-	  if (all) this.setComboText("");   	  
-      this.optionsArr=new Array();
-      this.redrawOptions();
-      if (all) this._confirmSelection();
-   }
-/**
-*     @desc: delete option by value
-*     @param: value - (string) value of option in question
-*     @type: public
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.deleteOption = function(value)
-   {
-        var ind=this.getIndexByValue(value);
-      if(ind<0) return;                            
-      if (this.optionsArr[ind]==this._selOption) this._selOption=null;
-      this.optionsArr.splice(ind, 1);
-      this.redrawOptions();
-   }
-
-/**
-*     @desc: enable/disable immideatly rendering after changes in combobox
-*     @param: mode - (boolean) enable/disable
-*     @type: public
-*     @topic: 1
-*/
-   dhtmlXCombo.prototype.render=function(mode){
-      this._skiprender=(!convertStringToBoolean(mode));
-      this.redrawOptions();
-   }
-
-/**
-*     @desc: update option in combobox
-*     @param: oldvalue - (string) index of option in question
-*     @param: avalue - (variable)
-*     @type: public
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.updateOption = function(oldvalue, avalue, atext, acss)
-   {
-      var dOpt=this.getOption(oldvalue);
-      if (typeof(avalue)!="object") avalue={text:atext,value:avalue,css:acss};
-      dOpt.setValue(avalue);
-        this.redrawOptions();
-   }
-/**
-*     @desc: add new option
-*     @param: value - (variable) - different input for different kinds of options - please refer to examples
-*     @type: public
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.addOption = function(options)
-   {
-      if (!arguments[0].length || typeof(arguments[0])!="object")
-         args = [arguments];
-      else
-         args = options;
-
-      this.render(false);
-        for (var i=0; i<args.length; i++) {
-            var attr = args[i];
-         if (attr.length){
-               attr.value = attr[0]||"";
-               attr.text = attr[1]||"";
-               attr.css = attr[2]||"";
-         }
-            this._addOption(attr);
-        }
-      this.render(true);
-   }
-
-   dhtmlXCombo.prototype._addOption = function(attr)
-   {
-         dOpt = new this._optionObject();
-         this.optionsArr.push(dOpt);
-         dOpt.setValue.apply(dOpt,[attr]);
-         this.redrawOptions();
-   }
-
-
-/**
-*     @desc: return index of item by value
-*     @param: value - (string) value of option in question
-*     @type: public
-*     @return: option index
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.getIndexByValue = function(val){
-      for(var i=0; i<this.optionsArr.length; i++)
-         if(this.optionsArr[i].value == val) return i;
-      return -1;
-   }
-
-/**
-*     @desc: get value of selected item
-*     @type: public
-*     @return: option value
-*     @topic: 2 
-*/
-   dhtmlXCombo.prototype.getSelectedValue = function(){
-      return (this._selOption?this._selOption.value:null);
-   }
-/**
-*     @desc: get current text in combobox
-*     @type: public
-*     @return: combobox text
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.getComboText = function(){
-      return this.DOMelem_input.value;
-   }
-/**
-*     @desc: set text in covmbobox
-*     @param: text - (string) new text label
-*     @type: public
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.setComboText = function(text){
-      this.DOMelem_input.value=text;
-   }
-   
-/**
-*     @desc: set text in covmbobox
-*     @param: text - (string) new text label
-*     @type: public
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.setComboValue = function(text){
-      this.setComboText(text);
-	  for(var i=0; i<this.optionsArr.length; i++)
-         if (this.optionsArr[i].data()[0]==text)
-         return this.selectOption(i,null,true);      
-      this.DOMelem_hidden_input.value=text;
-   }
-
-/**
-*     @desc: get value which will be sent with form
-*     @type: public
-*     @return: combobox value
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.getActualValue = function(){
-      return this.DOMelem_hidden_input.value;
-   }
-/**
-*     @desc: get text of selected option
-*     @type: public
-*     @return: text of option
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.getSelectedText = function(){
-      return (this._selOption?this._selOption.text:"");
-   }
-/**
-*     @desc: get index of selected option
-*     @type: public
-*     @return: option index
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.getSelectedIndex = function(){
-      for(var i=0; i<this.optionsArr.length; i++)
-         if(this.optionsArr[i] == this._selOption) return i;
-      return -1;
-   }
-/**
-*     @desc: set name used while form submit
-*     @param: name - (string) new combobox name
-*     @type: public
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.setName = function(name){  
-        this.DOMelem_hidden_input.name = name;
-        this.DOMelem_hidden_input2 = name.replace(/(\]?)$/, "_new_value$1");
-
-        this.name = name;
-   }
-/**
-*     @desc: show combox ( reversion to hide command )
-*     @param: mode - (boolean) enable/disable
-*     @type: public
-*     @topic: 1
-*/
-   dhtmlXCombo.prototype.show = function(mode){
-      if (convertStringToBoolean(mode))
-         this.DOMelem.style.display = "";
-      else
-         this.DOMelem.style.display = "none";
-   }
-
-/**
-*     @desc: destroy object and any related HTML elements
-*     @type: public
-*     @topic: 0
-*/
-   dhtmlXCombo.prototype.destructor = function()
-   {
-      this.DOMParent.removeChild(this.DOMelem);
-      this.DOMlist.parentNode.removeChild(this.DOMlist);
-      var s=dhx_glbSelectAr;
-      this.DOMParent=this.DOMlist=this.DOMelem=0;
-      this.DOMlist.combo=this.DOMelem.combo=0;
-      for(var i=0; i<s.length; i++)
-      {
-         if(s[i] == this)
-         {
-            s[i] = null;
-            s.splice(i,1);
-            return;
-         }
-      }
-   }
-
-/**
-*     @desc: create self HTML
-*     @type: private
-*     @topic: 0
-*/
-      dhtmlXCombo.prototype._createSelf = function(selParent, name, width, tab)
-      {
-         if (width.toString().indexOf("%")!=-1){ 
-            var self = this;
-            var resWidht=parseInt(width)/100;
-            window.setInterval(function(){ 
-               if (!selParent.parentNode) return;
-               var ts=selParent.parentNode.offsetWidth*resWidht-2;
-               if (ts<0) return;
-               if (ts==self._lastTs) return;
-               self.setSize(self._lastTs=ts);},500);
-            var width=parseInt(selParent.offsetWidth); //mm
-         }
-
-         var width=parseInt(width||100); //mm
-         this.ListPosition = "Bottom"; //set optionlist positioning
-         this.DOMParent = selParent;
-         this._inID = null;
-         this.name = name;
-
-         this._selOption = null; //selected option object pointer
-         this.optionsArr = Array();
-
-            var opt = new this._optionObject();
-            opt.DrawHeader(this,name, width,tab);
-         //HTML select part 2 - options list DIV element
-         this.DOMlist = document.createElement("DIV");
-         this.DOMlist.className = 'dhx_combo_list '+(dhtmlx.skin?dhtmlx.skin+"_list":"");
-         this.DOMlist.style.width=width-(_isIE?0:0)+"px";
-		 if (_isOpera || _isKHTML ) 
-		 		this.DOMlist.style.overflow="auto";      
-         this.DOMlist.style.display = "none";
-         document.body.insertBefore(this.DOMlist,document.body.firstChild);         
-         if (_isIE)    {
-            this.DOMlistF = document.createElement("IFRAME");
-            this.DOMlistF.style.border="0px";
-            this.DOMlistF.className = 'dhx_combo_list';
-            this.DOMlistF.style.width=width-(_isIE?0:0)+"px";
-            this.DOMlistF.style.display = "none";
-            this.DOMlistF.src="javascript:false;"; 
-            document.body.insertBefore(this.DOMlistF,document.body.firstChild);
-            }
-
-
-
-         this.DOMlist.combo=this.DOMelem.combo=this;
-
-         this.DOMelem_input.onkeydown = this._onKey;
-          this.DOMelem_input.onkeypress = this._onKeyF;
-         this.DOMelem_input.onblur = this._onBlur;
-         this.DOMelem.onclick = this._toggleSelect;
-         this.DOMlist.onclick = this._selectOption;
-         this.DOMlist.onmousedown = function(){
-         	this._skipBlur=true;
-     	 }
-         
-         this.DOMlist.onkeydown = function(e){
-         	this.combo.DOMelem_input.focus();
-         	(e||event).cancelBubble=true;
-         	this.combo.DOMelem_input.onkeydown(e)
-     	 }
-		  this.DOMlist.onmouseover = this._listOver;
-     	 
-      }
-
-      dhtmlXCombo.prototype._listOver = function(e)
-      { 
-         e = e||event;
-         e.cancelBubble = true;
-         var node = (_isIE?event.srcElement:e.target);
-         var that = this.combo;
-         if ( node.parentNode == that.DOMlist ) {
-            if(that._selOption) that._selOption.deselect(); 
-			if(that._tempSel) that._tempSel.deselect();
-           
-		    var i=0;
-            for (i; i<that.DOMlist.childNodes.length; i++) {
-               if (that.DOMlist.childNodes[i]==node) break;
-            }
-            var z=that.optionsArr[i];
-           that._tempSel=z;
-           that._tempSel.select();
-        	  
-			if ((that._autoxml)&&((i+1)==that._lastLength)){
-				that._fetchOptions(i+1,that._lasttext||""); 
-            }           
-         }
-
-      }
-
-/**
-*     @desc: place option list in necessary place on screen
-*     @type: private
-*     @topic: 0
-*/
-      dhtmlXCombo.prototype._positList = function()
-      {
-	  	//if(this.ListAutoPosit) this.enableOptionAutoPositioning(true); //mm auto posit
-         var pos=this.getPosition(this.DOMelem);
-         if(this.ListPosition == 'Bottom'){
-            this.DOMlist.style.top = pos[1]+this.DOMelem.offsetHeight-1+"px";
-            this.DOMlist.style.left = pos[0]+"px";
-         }
-         else if(this.ListPosition == 'Top'){ //mm
-				this.DOMlist.style.top = pos[1] - this.DOMlist.offsetHeight+"px";
-            	
-				this.DOMlist.style.left = pos[0]+"px"; //mm
-          }
-		  else{
-			this.DOMlist.style.top = pos[1]+"px";
-            this.DOMlist.style.left = pos[0]+this.DOMelem.offsetWidth+"px";
-         }
-     
-      }
-	  
-	dhtmlXCombo.prototype.getPosition = function(oNode,pNode){
-		if (_isChrome){ 
-			if(!pNode)
-			var pNode = document.body
-			
-			var oCurrentNode=oNode;
-			var iLeft=0;
-			var iTop=0;
-			while ((oCurrentNode)&&(oCurrentNode!=pNode)){
-			iLeft+=oCurrentNode.offsetLeft-oCurrentNode.scrollLeft;
-			iTop+=oCurrentNode.offsetTop-oCurrentNode.scrollTop;
-			oCurrentNode=oCurrentNode.offsetParent;
-			}
-			if (pNode == document.body ){
-			if (_isIE && _isIE<8){
-			
-			if (document.documentElement.scrollTop)
-			iTop+=document.documentElement.scrollTop;
-			if (document.documentElement.scrollLeft)
-			iLeft+=document.documentElement.scrollLeft;
-			}
-			else
-			if (!_isFF){
-			iLeft+=document.body.offsetLeft;
-			iTop+=document.body.offsetTop;
-			}
-			}
-			return new Array(iLeft,iTop);
-		}
-		var pos = getOffset(oNode);
-        return [pos.left, pos.top];
-        
-	}
-/**
-*     @desc: correct current selection ( move it to first visible option )
-*     @type: private
-*     @topic: 2
-*/
-      dhtmlXCombo.prototype._correctSelection = function(){
-		 if (this.getComboText()!="")
-         for (var i=0; i<this.optionsArr.length; i++)
-            if (!this.optionsArr[i].isHidden()){
-               return this.selectOption(i,true,false);
-           }
-         this.unSelectOption();
-      }
-/**
-*     @desc: select next option in combobox
-*     @param: step - (int) step size
-*     @type: private
-*     @topic: 2
-*/
-      dhtmlXCombo.prototype.selectNext = function(step){
-         var z=this.getSelectedIndex()+step;
-         while (this.optionsArr[z]){
-            if (!this.optionsArr[z].isHidden())
-               return this.selectOption(z,false,false);
-            z+=step;
-         }
-      }
-/**
-*     @desc: on keypressed handler
-*     @type: private
-*     @topic: 0
-*/
-      dhtmlXCombo.prototype._onKeyF = function(e){
-         var that=this.parentNode.combo;
-         var ev=e||event;
-         ev.cancelBubble=true;
-         if (ev.keyCode=="13" || ev.keyCode=="9" ){
-         	that._confirmSelection();
-         	that.closeAll();
-     	} else
-        if (ev.keyCode=="27" ){
-         	that._resetSelection();
-         	that.closeAll();
-     	} else that._activeMode=true;
-         if (ev.keyCode=="13" || ev.keyCode=="27" ){ //enter
-            that.callEvent("onKeyPressed",[ev.keyCode])
-            return false;
-         }
-         return true;
-      }
-/**
-*     @desc: on keyup handler
-*     @type: private
-*     @topic: 0
-*/
-      dhtmlXCombo.prototype._onKey = function(e){
-         var that=this.parentNode.combo;
-         (e||event).cancelBubble=true;
-         var ev=(e||event).keyCode;
-         if (ev>15 && ev<19) return true; //shift,alt,ctrl
-        if (ev==27) return;
-        if ((that.DOMlist.style.display!="block")&&(ev!="13")&&(ev!="9")&&((!that._filter)||(that._filterAny)))
-            that.DOMelem.onclick(e||event);
-       
-		if ((ev!="13")&&(ev!="9")){
-         	window.setTimeout(function(){ that._onKeyB(ev); },1);
-         	if (ev=="40" || ev=="38")
-         		return false;
-     	}
-         else if (ev==9){
-         	that.closeAll();
-         	(e||event).cancelBubble=false;
-         }
-      }
-      dhtmlXCombo.prototype._onKeyB = function(ev)
-      {
-         if (ev=="40"){  //down
-            var z=this.selectNext(1);
-         } else if (ev=="38"){ //up
-            this.selectNext(-1);
-         } else{
-            this.callEvent("onKeyPressed",[ev])
-            if (this._filter) return this.filterSelf((ev==8)||(ev==46));
-            for(var i=0; i<this.optionsArr.length; i++)
-               if (this.optionsArr[i].data()[1]==this.DOMelem_input.value){
-//                  ev.cancelBubble=true;
-                  this.selectOption(i,false,false);
-                  return false;
-                  }
-            this.unSelectOption();
-         }
-         return true;
-      }
-
-
-/**
-*     @desc: on data change handler
-*     @type: private
-*     @topic: 0
-*/
-      dhtmlXCombo.prototype._onBlur = function()
-      {
-          var self = this.parentNode._self;
-          window.setTimeout(function(){
-          	if (self.DOMlist._skipBlur) return !(self.DOMlist._skipBlur=false);
-          	self._confirmSelection();        
-          	self.callEvent("onBlur",[]);
-          },100)
-          
-      }
-/**
-*     @desc: redraw combobox options
-*     @type: private
-*     @topic: 2
-*/
-      dhtmlXCombo.prototype.redrawOptions = function(){
-         if (this._skiprender) return;
-         for(var i=this.DOMlist.childNodes.length-1; i>=0; i--)
-            this.DOMlist.removeChild(this.DOMlist.childNodes[i]);
-         for(var i=0; i<this.optionsArr.length; i++)
-            this.DOMlist.appendChild(this.optionsArr[i].render());
-			
-		
-		
-						
-
-      }
-/**
-*     @desc: load list of options from XML
-*     @param: url - (string) xml url
-*     @type: public
-*     @topic: 0
-*/
-      dhtmlXCombo.prototype.loadXML = function(url,afterCall){
-         this._load=true;
-         this.callEvent("onXLS",[]);
-		 if (this._prs)
-           	for (var i=0; i<this._prs.length; i++)
-           		url+=[getUrlSymbol(url),escape(this._prs[i][0]),"=",escape(this._prs[i][1])].join("");         
-         if ((this._xmlCache)&&(this._xmlCache[url])){
-            this._fillFromXML(this,null,null,null,this._xmlCache[url]);
-            if (afterCall) afterCall();
-        }
-         else{
-            var xml=(new dtmlXMLLoaderObject(this._fillFromXML,this,true,true));
-            if (afterCall) xml.waitCall=afterCall;
-            xml._cPath=url;
-            xml.loadXML(url);
-         }
-      }
-
-/**
-*     @desc: load list of options from XML string
-*     @param: astring - (string) xml string
-*     @type: public
-*     @topic: 0
-*/
-      dhtmlXCombo.prototype.loadXMLString = function(astring){
-         var xml=(new dtmlXMLLoaderObject(this._fillFromXML,this,true,true));
-         xml.loadXMLString(astring);
-      }
-
-/**
-*     @desc: on XML load handler
-*     @type: private
-*     @topic: 0
-*/
-      dhtmlXCombo.prototype._fillFromXML = function(obj,b,c,d,xml){
-         if (obj._xmlCache) obj._xmlCache[xml._cPath]=xml;
-
-         //check that XML is correct 
-         var toptag=xml.getXMLTopNode("complete");
-         if (toptag.tagName!="complete") return;
-         var top=xml.doXPath("//complete");
-         var options=xml.doXPath("//option");
-         
-         var add = false;
-         
-         obj.render(false);
-         if ((!top[0])||(!top[0].getAttribute("add"))){
-            obj.clearAll();
-            obj._lastLength=options.length;
-	         if (obj._xml){
-	         if ((!options) || (!options.length)) 
-	         	obj.closeAll();
-	         else {
-	         		if (obj._activeMode){
-	         			obj._positList();
-	        			obj.DOMlist.style.display="block";
-	         			if (_isIE) obj._IEFix(true);
-         			}
-	     	 }}            
-         } else {
-            obj._lastLength+=options.length||Infinity;
-	        add = true;
-        }
-
-         for (var i=0; i<options.length; i++) {
-            var attr = new Object();
-            attr.text = options[i].firstChild?options[i].firstChild.nodeValue:"";
-            for (var j=0; j<options[i].attributes.length; j++) {
-               var a = options[i].attributes[j];
-               if (a)
-                  attr[a.nodeName] = a.nodeValue;
-            }
-            obj._addOption(attr);
-         }
-         obj.render(add!=true || (!!options.length));
-         	
-         if ((obj._load)&&(obj._load!==true))
-            obj.loadXML(obj._load);
-         else{
-            obj._load=false;
-             if ((!obj._lkmode)&&(obj._filter))
-               obj._correctSelection();
-            }
-
-         var selected=xml.doXPath("//option[@selected]");
-         if (selected.length)
-         	obj.selectOption(obj.getIndexByValue(selected[0].getAttribute("value")),false,true);
-
-         obj.callEvent("onXLE",[]);
-
-      }
-/**
-*     @desc: deselect option
-*     @type: public
-*     @topic: 1
-*/
-      dhtmlXCombo.prototype.unSelectOption = function(){
-         if (this._selOption) this._selOption.deselect();
-         if(this._tempSel) this._tempSel.deselect();
-         this._tempSel=this._selOption=null;
-      }
-	  
-/**
-*     @desc: confirms combo value (recommended to be called on form submit)
-*     @type: public
-*     @topic: 1
-*/
-	 dhtmlXCombo.prototype.confirmValue = function(){
-         this._confirmSelection();
-	 }
-	  
-	  
-      dhtmlXCombo.prototype._confirmSelection = function(data,status){
-      	if(arguments.length==0){
-      	    var z=this.getOptionByLabel(this.DOMelem_input.value);
-          	data = z?z.value:this.DOMelem_input.value;
-          	status = (z==null);
-          	if (data==this.getActualValue()) return;
-      	}
-          	
-      	this.DOMelem_hidden_input.value=data;
-        this.DOMelem_hidden_input2.value = (status?"true":"false");
-      	this.callEvent("onChange",[]);
-      	this._activeMode=false;
-  	  }
-      dhtmlXCombo.prototype._resetSelection = function(data,status){
-      		var z=this.getOption(this.DOMelem_hidden_input.value);
-      		this.setComboValue(z?z.data()[0]:this.DOMelem_hidden_input.value)
-      		this.setComboText(z?z.data()[1]:this.DOMelem_hidden_input.value)
-  	  }  	  
-  	  
-/**
-*     @desc: select option
-*     @param: ind - (int) index of option in question
-*     @param: filter - (boolean) enable autocomplit range, optional
-*     @param: conf - (boolean) true for real selection, false for pre-selection
-*     @type: public
-*     @topic: 1
-*/
-      dhtmlXCombo.prototype.selectOption = function(ind,filter,conf){
-      	 if (arguments.length<3) conf=true;
-         this.unSelectOption();
-         var z=this.optionsArr[ind];
-         if (!z)  return;
-         this._selOption=z;
-         this._selOption.select();
-
-         var corr=this._selOption.content.offsetTop+this._selOption.content.offsetHeight-this.DOMlist.scrollTop-this.DOMlist.offsetHeight;
-         if (corr>0) this.DOMlist.scrollTop+=corr;
-            corr=this.DOMlist.scrollTop-this._selOption.content.offsetTop;
-         if (corr>0) this.DOMlist.scrollTop-=corr;
-         var data=this._selOption.data();
-
-	 	 if (conf){
-	 	 	this.setComboText(data[1]);
-	 	 	this._confirmSelection(data[0],false);
-		 
-         	if ((this._autoxml)&&((ind+1)==this._lastLength))
-            	this._fetchOptions(ind+1,this._lasttext||"");
-		}
-
-         if (filter){
-            var text=this.getComboText();
-            if (text!=data[1]){
-               this.setComboText(data[1]);
-               dhtmlXRange(this.DOMelem_input,text.length+1,data[1].length);
-            }
-         }
-         else
-            this.setComboText(data[1]);
-         this._selOption.RedrawHeader(this);
-
-         /*Event*/
-         this.callEvent("onSelectionChange",[]);
-      }
-/**
-*     @desc: option on select handler
-*     @type: private
-*     @topic: 2
-*/
-      dhtmlXCombo.prototype._selectOption = function(e)
-      {
-         (e||event).cancelBubble = true;
-         var node=(_isIE?event.srcElement:e.target);
-         var that=this.combo;
-         while (!node._self) {
-            node = node.parentNode;
-            if (!node)
-               return;
-         }
-
-         var i=0;
-         for (i; i<that.DOMlist.childNodes.length; i++) {
-            if (that.DOMlist.childNodes[i]==node) break;
-         }
-         that.selectOption(i,false,true);
-         that.closeAll();
-         that.callEvent("onBlur",[])
-         that._activeMode=false;
-      }
-/**
-*     @desc: open list of options 
-*     @type: public
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype.openSelect = function(){ 
-   	
-	
-    
-	  if (this._disabled) return;
-      this.closeAll();
-      this._positList();
-      this.DOMlist.style.display="block";
-      this.callEvent("onOpen",[]);
-	  if(this._tempSel) this._tempSel.deselect();
-	  if(this._selOption) this._selOption.select();
-	  if(this._selOption){
-	 	var corr=this._selOption.content.offsetTop+this._selOption.content.offsetHeight-this.DOMlist.scrollTop-this.DOMlist.offsetHeight;
-         if (corr>0) this.DOMlist.scrollTop+=corr;
-            corr=this.DOMlist.scrollTop-this._selOption.content.offsetTop;
-         if (corr>0) this.DOMlist.scrollTop-=corr;
-	  }
-	  /* if (this.autoOptionSize){
-        	var x=this.DOMlist.offsetWidth; 
-			
-        	for ( var i=0; i<this.optionsArr.length; i++){
-				if(i==0) alert("this.DOMlist.childNodes[i].scrollWidth ="+ this.DOMlist.childNodes[i].scrollWidth + "> x= "+ x);
-        		if (this.DOMlist.childNodes[i].scrollWidth > x)
-        			x=this.DOMlist.childNodes[i].scrollWidth;
-			}
-        			
-			this.DOMlist.style.width=x+"px";
-		}*/
-		
-		      
-      if (_isIE) this._IEFix(true);
-      this.DOMelem_input.focus();
-	
-      if (this._filter) this.filterSelf();
-   }
-/**
-*     @desc: open(close) list
-*     @type: private
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype._toggleSelect = function(e)
-   {
-      var that=this.combo;
-      if ( that.DOMlist.style.display == "block" ) {
-      that.closeAll();
-      } else {
-         that.openSelect();
-      }
-      (e||event).cancelBubble = true;
-   }
-
-    dhtmlXCombo.prototype._fetchOptions=function(ind,text){
-         if (text=="") { this.closeAll();  return this.clearAll();   }
-         var url=this._xml+((this._xml.indexOf("?")!=-1)?"&":"?")+"pos="+ind+"&mask="+encodeURIComponent(text);
-         this._lasttext=text;
-         if (this._load) this._load=url;
-         else {
-			if (!this.callEvent("onDynXLS",[text,ind])) return;
-			this.loadXML(url);
-     	 }
-    }
-/**
-*     @desc: filter list of options
-*     @type: private
-*     @topic: 2
-*/
-    dhtmlXCombo.prototype.filterSelf = function(mode)
-   {
-      var text=this.getComboText();
-      if (this._xml){
-         this._lkmode=mode;
-         this._fetchOptions(0,text);
-      }
-      try{ var filter=new RegExp("^"+text,"i"); } catch (e){ var filter=new RegExp("^"+text.replace(/([\[\]\{\}\(\)\+\*\\])/g,"\\$1")); }
-      this.filterAny=false;
-      for(var i=0; i<this.optionsArr.length; i++){
-      	 var z=filter.test(this.optionsArr[i].text);
-      	 this.filterAny|=z;
-         this.optionsArr[i].hide(!z);
-      }
-      if (!this.filterAny) {
-		  this.closeAll();
-		  this._activeMode=true;
-	  }
-      else {
-      	  if (this.DOMlist.style.display!="block")
-      	   		this.openSelect();
-	      if (_isIE) this._IEFix(true);
-  		}
-         
-        if (!mode)
-         this._correctSelection();   
-        else this.unSelectOption();
-   }
- 
- 
-
-
-/**
-*     @desc: set hidden iframe for IE
-*     @type: private
-*     @topic: 2
-*/
-   dhtmlXCombo.prototype._IEFix = function(mode){
-      this.DOMlistF.style.display=(mode?"block":"none");
-        this.DOMlistF.style.top=this.DOMlist.style.top;
-        this.DOMlistF.style.left=this.DOMlist.style.left;
-   }
-/**
-*     @desc: close opened combobox list
-*     @type: public
-*     @topic: 1
-*/
-   dhtmlXCombo.prototype.closeAll = function()
-   {
-      if(window.dhx_glbSelectAr)
-         for (var i=0; i<dhx_glbSelectAr.length; i++){
-            if (dhx_glbSelectAr[i].DOMlist.style.display=="block") {
-               dhx_glbSelectAr[i].DOMlist.style.display = "none";
-               if (_isIE) dhx_glbSelectAr[i]._IEFix(false);
-            }
-            dhx_glbSelectAr[i]._activeMode=false;
-        }
-   }
- 
-		
-/**
-*     @desc: create selection range in input control
-*     @param: InputId - (string) id of input ( object can be used as well )
-*     @param: Start - (int) start selection position
-*     @param: End - (int) end selection position
-*     @type: public
-*     @topic: 0
-*/
-function dhtmlXRange(InputId, Start, End)
-{
-   var Input = typeof(InputId)=='object' ? InputId : document.getElementById(InputId);
-   try{    Input.focus();   } catch(e){};
-   var Length = Input.value.length;
-   Start--;
-   if (Start < 0 || Start > End || Start > Length)
-      Start = 0;
-   if (End > Length)
-      End = Length;
-   if (Start==End) return;
-   if (Input.setSelectionRange) {
-      Input.setSelectionRange(Start, End);
-   } else if (Input.createTextRange) {
-      var range = Input.createTextRange();
-      range.moveStart('character', Start);
-      range.moveEnd('character', End-Length);
-	  try{
-         range.select();
-	  }
-	  catch(e){
-	  }
-   }
-}
-/**
-*     @desc: combobox option object constructor
-*     @type: public
-*     @topic: 0
-*/
-      dhtmlXCombo_defaultOption = function(){
-         this.init();
-      }
-/**
-*     @desc: option initialization function
-*     @type: private
-*     @topic: 4
-*/
-      dhtmlXCombo_defaultOption.prototype.init = function(){
-         this.value = null;
-         this.text = "";
-         this.selected = false;
-         this.css = "";
-      }
-/**
-*     @desc: mark option as selected
-*     @type: public
-*     @topic: 4
-*/
-      dhtmlXCombo_defaultOption.prototype.select = function(){
-         if (this.content){
-            this.content.className="dhx_selected_option"+(dhtmlx.skin?" combo_"+dhtmlx.skin+"_sel":"");
-		}
-      }
-/**
-*     @desc: hide option
-*     @param: mode - (boolean)
-*     @type: public
-*     @topic: 4
-*/
-      dhtmlXCombo_defaultOption.prototype.hide = function(mode){
-         this.render().style.display=mode?"none":"";
-      }
-/**
-*     @desc: return hide state of option
-*     @type: public
-*     @return: hide state of option
-*     @topic: 4
-*/
-      dhtmlXCombo_defaultOption.prototype.isHidden = function(){
-         return (this.render().style.display=="none");
-      }
-/**
-*     @desc: mark option as not selected
-*     @type: public
-*     @topic: 4
-*/
-      dhtmlXCombo_defaultOption.prototype.deselect = function(){
-         if (this.content) this.render();
-            this.content.className="";
-      }
-/**
-*     @desc: set value of option
-*     @param: value - (string) value
-*     @param: text - (string) text
-*     @param: css - (string) css style string
-*     @type: public
-*     @topic: 4
-*/
-dhtmlXCombo_defaultOption.prototype.setValue = function(attr){
-    this.value = attr.value||"";
-    this.text = attr.text||"";
-    this.css = attr.css||"";
-   this.content=null;
-}
-
-				
-/**
-*     @desc: render option
-*     @type: private
-*     @topic: 4
-*/
-      dhtmlXCombo_defaultOption.prototype.render = function(){
-         if (!this.content){
-            this.content=document.createElement("DIV");
-        this.content._self = this;
-
-            this.content.style.cssText='width:100%; overflow:hidden;'+this.css;
-            if (_isOpera || _isKHTML ) this.content.style.padding="2px 0px 2px 0px";
-            this.content.innerHTML=this.text;
-            this._ctext=_isIE?this.content.innerText:this.content.textContent;
-         }
-         return this.content;
-      }
-/**
-*     @desc: return option data
-*     @type: public
-*     @return: array of data related to option
-*     @topic: 4
-*/
-      dhtmlXCombo_defaultOption.prototype.data = function(){
-         if (this.content)
-            return [this.value,this._ctext ? this._ctext : this.text];
-      }
-
-dhtmlXCombo_defaultOption.prototype.DrawHeader = function(self, name, width, tab)
-{
-    var z=document.createElement("DIV");
-    z.style.width = width+"px";
-    z.className = 'dhx_combo_box '+(dhtmlx.skin||"");
-    z._self = self;
-	self.DOMelem = z;
-   	this._DrawHeaderInput(self, name, width,tab);
-	this._DrawHeaderButton(self, name, width);
-    self.DOMParent.appendChild(self.DOMelem);
-}
-
-dhtmlXCombo_defaultOption.prototype._DrawHeaderInput = function(self, name, width,tab)
-{
-
-	var z=document.createElement('input');
-	z.setAttribute("autocomplete","off"); 
-	z.type = 'text';
-	z.className = 'dhx_combo_input';
-
-   if (tab) z.tabIndex=tab;
-   z.style.width = width-19-(document.compatMode=="BackCompat"?0:3)+'px';
-   self.DOMelem.appendChild(z);
-   self.DOMelem_input = z;
-
-   z = document.createElement('input');
-   z.type = 'hidden';
-   z.name = name;
-   self.DOMelem.appendChild(z);
-   self.DOMelem_hidden_input = z;
-
-   z = document.createElement('input');
-   z.type = 'hidden';
-   z.name = (name||"").replace(/(\]?)$/, "_new_value$1");
-   z.value="true";
-   self.DOMelem.appendChild(z);
-   self.DOMelem_hidden_input2 = z;
-}
-
-dhtmlXCombo_defaultOption.prototype._DrawHeaderButton = function(self, name, width)
-{
-   var z=document.createElement('img');
-   z.className = 'dhx_combo_img';
-  if(dhtmlx.image_path) dhx_globalImgPath = dhtmlx.image_path;
-   z.src = (window.dhx_globalImgPath?dhx_globalImgPath:"")+'combo_select'+(dhtmlx.skin?"_"+dhtmlx.skin:"")+'.gif';
-   self.DOMelem.appendChild(z);
-   self.DOMelem_button=z;
-}
-
-dhtmlXCombo_defaultOption.prototype.RedrawHeader = function(self)
-{
-}
-
-
-dhtmlXCombo_optionTypes['default'] = dhtmlXCombo_defaultOption;
-
-dhtmlXCombo.prototype.dhx_Event=function()
-{
-   this.dhx_SeverCatcherPath="";
-
-   this.attachEvent = function(original, catcher, CallObj)
-   {
-      CallObj = CallObj||this;
-      original = 'ev_'+original;
-       if ( ( !this[original] ) || ( !this[original].addEvent ) ) {
-           var z = new this.eventCatcher(CallObj);
-           z.addEvent( this[original] );
-           this[original] = z;
-       }
-       return ( original + ':' + this[original].addEvent(catcher) );   //return ID (event name & event ID)
-   }
-   this.callEvent=function(name,arg0){
-         if (this["ev_"+name]) return this["ev_"+name].apply(this,arg0);
-       return true;
-   }
-   this.checkEvent=function(name){
-         if (this["ev_"+name]) return true;
-       return false;
-   }
-
-   this.eventCatcher = function(obj)
-   {
-       var dhx_catch = new Array();
-       var m_obj = obj;
-       var func_server = function(catcher,rpc)
-                         {
-                           catcher = catcher.split(":");
-                     var postVar="";
-                     var postVar2="";
-                           var target=catcher[1];
-                     if (catcher[1]=="rpc"){
-                           postVar='<?xml version="1.0"?><methodCall><methodName>'+catcher[2]+'</methodName><params>';
-                        postVar2="</params></methodCall>";
-                        target=rpc;
-                     }
-                           var z = function() {
-                                   }
-                           return z;
-                         }
-       var z = function()
-             {
-                   if (dhx_catch)
-                      var res=true;
-                   for (var i=0; i<dhx_catch.length; i++) {
-                      if (dhx_catch[i] != null) {
-                           var zr = dhx_catch[i].apply( m_obj, arguments );
-                           res = res && zr;
-                      }
-                   }
-                   return res;
-                }
-       z.addEvent = function(ev)
-                {
-                       if ( typeof(ev) != "function" )
-                           if (ev && ev.indexOf && ev.indexOf("server:") == 0)
-                               ev = new func_server(ev,m_obj.rpcServer);
-                           else
-                               ev = eval(ev);
-                       if (ev)
-                           return dhx_catch.push( ev ) - 1;
-                       return false;
-                }
-       z.removeEvent = function(id)
-                   {
-                     dhx_catch[id] = null;
-                   }
-       return z;
-   }
-
-   this.detachEvent = function(id)
-   {
-      if (id != false) {
-         var list = id.split(':');            //get EventName and ID
-         this[ list[0] ].removeEvent( list[1] );   //remove event
-      }
-   }
-};
-
-
-//combo
-(function(){
-	dhtmlx.extend_api("dhtmlXCombo",{
-		_init:function(obj){
-			if (obj.image_path) 
-				dhx_globalImgPath=obj.image_path;
-			return [obj.parent, obj.name, (obj.width||"100%"), obj.type, obj.index ];
-		},
-		filter:"filter_command",
-		auto_height:"enableOptionAutoHeight",
-		auto_position:"enableOptionAutoPositioning",
-		auto_width:"enableOptionAutoWidth",
-		xml:"loadXML",
-		readonly:"readonly",
-		items:"addOption"
-	},{
-		filter_command:function(data){
-			if (typeof data == "string")
-				this.enableFilteringMode(true,data);
-			else
-				this.enableFilteringMode(data);
-		}
-	});
-})();
-
-
-//(c)dhtmlx ltd. www.dhtmlx.com
− data/web/static/dhtmlxcommon.js
@@ -1,940 +0,0 @@-//v.2.6 build 100722--/*
-Copyright DHTMLX LTD. http://www.dhtmlx.com
-You allowed to use this component or parts of it under GPL terms
-To use it on other terms or get Professional edition of the component please contact us at sales@dhtmlx.com
-*/-dhtmlx=function(obj){
-	for (var a in obj) dhtmlx[a]=obj[a];
-	return dhtmlx; //simple singleton
-};
-dhtmlx.extend_api=function(name,map,ext){
-	var t = window[name];
-	if (!t) return; //component not defined
-	window[name]=function(obj){
-		if (obj && typeof obj == "object" && !obj.tagName){
-			var that = t.apply(this,(map._init?map._init(obj):arguments));
-			//global settings
-			for (var a in dhtmlx)
-				if (map[a]) this[map[a]](dhtmlx[a]);			
-			//local settings
-			for (var a in obj){
-				if (map[a]) this[map[a]](obj[a]);
-				else if (a.indexOf("on")==0){
-					this.attachEvent(a,obj[a]);
-				}
-			}
-		} else
-			var that = t.apply(this,arguments);
-		if (map._patch) map._patch(this);
-		return that||this;
-	};
-	window[name].prototype=t.prototype;
-	if (ext)
-		dhtmlXHeir(window[name].prototype,ext);
-};
-
-dhtmlxAjax={
-	get:function(url,callback){
-		var t=new dtmlXMLLoaderObject(true);
-		t.async=(arguments.length<3);
-		t.waitCall=callback;
-		t.loadXML(url)
-		return t;
-	},
-	post:function(url,post,callback){
-		var t=new dtmlXMLLoaderObject(true);
-		t.async=(arguments.length<4);
-		t.waitCall=callback;
-		t.loadXML(url,true,post)
-		return t;
-	},
-	getSync:function(url){
-		return this.get(url,null,true)
-	},
-	postSync:function(url,post){
-		return this.post(url,post,null,true);		
-	}
-}
-
-/**
-  *     @desc: xmlLoader object
-  *     @type: private
-  *     @param: funcObject - xml parser function
-  *     @param: object - jsControl object
-  *     @param: async - sync/async mode (async by default)
-  *     @param: rSeed - enable/disable random seed ( prevent IE caching)
-  *     @topic: 0
-  */
-function dtmlXMLLoaderObject(funcObject, dhtmlObject, async, rSeed){
-	this.xmlDoc="";
-
-	if (typeof (async) != "undefined")
-		this.async=async;
-	else
-		this.async=true;
-
-	this.onloadAction=funcObject||null;
-	this.mainObject=dhtmlObject||null;
-	this.waitCall=null;
-	this.rSeed=rSeed||false;
-	return this;
-};
-/**
-  *     @desc: xml loading handler
-  *     @type: private
-  *     @param: dtmlObject - xmlLoader object
-  *     @topic: 0
-  */
-dtmlXMLLoaderObject.prototype.waitLoadFunction=function(dhtmlObject){
-	var once = true;
-	this.check=function (){
-		if ((dhtmlObject)&&(dhtmlObject.onloadAction != null)){
-			if ((!dhtmlObject.xmlDoc.readyState)||(dhtmlObject.xmlDoc.readyState == 4)){
-				if (!once)
-					return;
-
-				once=false; //IE 5 fix
-				if (typeof dhtmlObject.onloadAction == "function")
-					dhtmlObject.onloadAction(dhtmlObject.mainObject, null, null, null, dhtmlObject);
-
-				if (dhtmlObject.waitCall){
-					dhtmlObject.waitCall.call(this,dhtmlObject);
-					dhtmlObject.waitCall=null;
-				}
-			}
-		}
-	};
-	return this.check;
-};
-
-/**
-  *     @desc: return XML top node
-  *     @param: tagName - top XML node tag name (not used in IE, required for Safari and Mozilla)
-  *     @type: private
-  *     @returns: top XML node
-  *     @topic: 0  
-  */
-dtmlXMLLoaderObject.prototype.getXMLTopNode=function(tagName, oldObj){
-	if (this.xmlDoc.responseXML){
-		var temp = this.xmlDoc.responseXML.getElementsByTagName(tagName);
-		if(temp.length==0 && tagName.indexOf(":")!=-1)
-			var temp = this.xmlDoc.responseXML.getElementsByTagName((tagName.split(":"))[1]);
-		var z = temp[0];
-	} else
-		var z = this.xmlDoc.documentElement;
-
-	if (z){
-		this._retry=false;
-		return z;
-	}
-
-	if ((_isIE)&&(!this._retry)){
-		//fall back to MS.XMLDOM
-		var xmlString = this.xmlDoc.responseText;
-		var oldObj = this.xmlDoc;
-		this._retry=true;
-		this.xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
-		this.xmlDoc.async=false;
-		this.xmlDoc["loadXM"+"L"](xmlString);
-
-		return this.getXMLTopNode(tagName, oldObj);
-	}
-	dhtmlxError.throwError("LoadXML", "Incorrect XML", [
-		(oldObj||this.xmlDoc),
-		this.mainObject
-	]);
-
-	return document.createElement("DIV");
-};
-
-/**
-  *     @desc: load XML from string
-  *     @type: private
-  *     @param: xmlString - xml string
-  *     @topic: 0  
-  */
-dtmlXMLLoaderObject.prototype.loadXMLString=function(xmlString){
-	{
-		try{
-			var parser = new DOMParser();
-			this.xmlDoc=parser.parseFromString(xmlString, "text/xml");
-		}
-		catch (e){
-			this.xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
-			this.xmlDoc.async=this.async;
-			this.xmlDoc["loadXM"+"L"](xmlString);
-		}
-	}
-
-	this.onloadAction(this.mainObject, null, null, null, this);
-
-	if (this.waitCall){
-		this.waitCall();
-		this.waitCall=null;
-	}
-}
-/**
-  *     @desc: load XML
-  *     @type: private
-  *     @param: filePath - xml file path
-  *     @param: postMode - send POST request
-  *     @param: postVars - list of vars for post request
-  *     @topic: 0
-  */
-dtmlXMLLoaderObject.prototype.loadXML=function(filePath, postMode, postVars, rpc){
-	if (this.rSeed)
-		filePath+=((filePath.indexOf("?") != -1) ? "&" : "?")+"a_dhx_rSeed="+(new Date()).valueOf();
-	this.filePath=filePath;
-
-	if ((!_isIE)&&(window.XMLHttpRequest))
-		this.xmlDoc=new XMLHttpRequest();
-	else {
-		if (document.implementation&&document.implementation.createDocument){
-			this.xmlDoc=document.implementation.createDocument("", "", null);
-			this.xmlDoc.onload=new this.waitLoadFunction(this);
-			this.xmlDoc.load(filePath);
-			return;
-		} else
-			this.xmlDoc=new ActiveXObject("Microsoft.XMLHTTP");
-	}
-
-	if (this.async)
-		this.xmlDoc.onreadystatechange=new this.waitLoadFunction(this);
-	this.xmlDoc.open(postMode ? "POST" : "GET", filePath, this.async);
-
-	if (rpc){
-		this.xmlDoc.setRequestHeader("User-Agent", "dhtmlxRPC v0.1 ("+navigator.userAgent+")");
-		this.xmlDoc.setRequestHeader("Content-type", "text/xml");
-	}
-
-	else if (postMode)
-		this.xmlDoc.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
-		
-	this.xmlDoc.setRequestHeader("X-Requested-With","XMLHttpRequest");
-	this.xmlDoc.send(null||postVars);
-
-	if (!this.async)
-		(new this.waitLoadFunction(this))();
-};
-/**
-  *     @desc: destructor, cleans used memory
-  *     @type: private
-  *     @topic: 0
-  */
-dtmlXMLLoaderObject.prototype.destructor=function(){
-	this._filterXPath = null;
-	this._getAllNamedChilds = null;
-	this._retry = null;
-	this.async = null;
-	this.rSeed = null;
-	this.filePath = null;
-	this.onloadAction = null;
-	this.mainObject = null;
-	this.xmlDoc = null;
-	this.doXPath = null;
-	this.doXPathOpera = null;
-	this.doXSLTransToObject = null;
-	this.doXSLTransToString = null;
-	this.loadXML = null;
-	this.loadXMLString = null;
-	// this.waitLoadFunction = null;
-	this.doSerialization = null;
-	this.xmlNodeToJSON = null;
-	this.getXMLTopNode = null;
-	this.setXSLParamValue = null;
-	return null;
-}
-
-dtmlXMLLoaderObject.prototype.xmlNodeToJSON = function(node){
-        var t={};
-        for (var i=0; i<node.attributes.length; i++)
-            t[node.attributes[i].name]=node.attributes[i].value;
-        t["_tagvalue"]=node.firstChild?node.firstChild.nodeValue:"";
-        for (var i=0; i<node.childNodes.length; i++){
-            var name=node.childNodes[i].tagName;
-            if (name){
-                if (!t[name]) t[name]=[];
-                t[name].push(this.xmlNodeToJSON(node.childNodes[i]));
-            }            
-        }        
-        return t;
-    }
-
-/**  
-  *     @desc: Call wrapper
-  *     @type: private
-  *     @param: funcObject - action handler
-  *     @param: dhtmlObject - user data
-  *     @returns: function handler
-  *     @topic: 0  
-  */
-function callerFunction(funcObject, dhtmlObject){
-	this.handler=function(e){
-		if (!e)
-			e=window.event;
-		funcObject(e, dhtmlObject);
-		return true;
-	};
-	return this.handler;
-};
-
-/**  
-  *     @desc: Calculate absolute position of html object
-  *     @type: private
-  *     @param: htmlObject - html object
-  *     @topic: 0  
-  */
-function getAbsoluteLeft(htmlObject){
-	return getOffset(htmlObject).left;
-}
-/**
-  *     @desc: Calculate absolute position of html object
-  *     @type: private
-  *     @param: htmlObject - html object
-  *     @topic: 0  
-  */
-function getAbsoluteTop(htmlObject){
-	return getOffset(htmlObject).top;
-}
-
-function getOffsetSum(elem) {
-	var top=0, left=0;
-	while(elem) {
-		top = top + parseInt(elem.offsetTop);
-		left = left + parseInt(elem.offsetLeft);
-		elem = elem.offsetParent;
-	}
-	return {top: top, left: left};
-}
-function getOffsetRect(elem) {
-	var box = elem.getBoundingClientRect();
-	var body = document.body;
-	var docElem = document.documentElement;
-	var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
-	var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
-	var clientTop = docElem.clientTop || body.clientTop || 0;
-	var clientLeft = docElem.clientLeft || body.clientLeft || 0;
-	var top  = box.top +  scrollTop - clientTop;
-	var left = box.left + scrollLeft - clientLeft;
-	return { top: Math.round(top), left: Math.round(left) };
-}
-function getOffset(elem) {
-	if (elem.getBoundingClientRect) {
-		return getOffsetRect(elem);
-	} else {
-		return getOffsetSum(elem);
-	}
-}
-
-/**  
-*     @desc: Convert string to it boolean representation
-*     @type: private
-*     @param: inputString - string for covertion
-*     @topic: 0
-*/
-function convertStringToBoolean(inputString){
-	if (typeof (inputString) == "string")
-		inputString=inputString.toLowerCase();
-
-	switch (inputString){
-		case "1":
-		case "true":
-		case "yes":
-		case "y":
-		case 1:
-		case true:
-			return true;
-			break;
-
-		default: return false;
-	}
-}
-
-/**  
-*     @desc: find out what symbol to use as url param delimiters in further params
-*     @type: private
-*     @param: str - current url string
-*     @topic: 0  
-*/
-function getUrlSymbol(str){
-	if (str.indexOf("?") != -1)
-		return "&"
-	else
-		return "?"
-}
-
-function dhtmlDragAndDropObject(){
-	if (window.dhtmlDragAndDrop)
-		return window.dhtmlDragAndDrop;
-
-	this.lastLanding=0;
-	this.dragNode=0;
-	this.dragStartNode=0;
-	this.dragStartObject=0;
-	this.tempDOMU=null;
-	this.tempDOMM=null;
-	this.waitDrag=0;
-	window.dhtmlDragAndDrop=this;
-
-	return this;
-};
-
-dhtmlDragAndDropObject.prototype.removeDraggableItem=function(htmlNode){
-	htmlNode.onmousedown=null;
-	htmlNode.dragStarter=null;
-	htmlNode.dragLanding=null;
-}
-dhtmlDragAndDropObject.prototype.addDraggableItem=function(htmlNode, dhtmlObject){
-	htmlNode.onmousedown=this.preCreateDragCopy;
-	htmlNode.dragStarter=dhtmlObject;
-	this.addDragLanding(htmlNode, dhtmlObject);
-}
-dhtmlDragAndDropObject.prototype.addDragLanding=function(htmlNode, dhtmlObject){
-	htmlNode.dragLanding=dhtmlObject;
-}
-dhtmlDragAndDropObject.prototype.preCreateDragCopy=function(e){
-	if ((e||window.event) && (e||event).button == 2)
-		return;
-
-	if (window.dhtmlDragAndDrop.waitDrag){
-		window.dhtmlDragAndDrop.waitDrag=0;
-		document.body.onmouseup=window.dhtmlDragAndDrop.tempDOMU;
-		document.body.onmousemove=window.dhtmlDragAndDrop.tempDOMM;
-		return false;
-	}
-
-	window.dhtmlDragAndDrop.waitDrag=1;
-	window.dhtmlDragAndDrop.tempDOMU=document.body.onmouseup;
-	window.dhtmlDragAndDrop.tempDOMM=document.body.onmousemove;
-	window.dhtmlDragAndDrop.dragStartNode=this;
-	window.dhtmlDragAndDrop.dragStartObject=this.dragStarter;
-	document.body.onmouseup=window.dhtmlDragAndDrop.preCreateDragCopy;
-	document.body.onmousemove=window.dhtmlDragAndDrop.callDrag;
-	window.dhtmlDragAndDrop.downtime = new Date().valueOf();
-	
-
-	if ((e)&&(e.preventDefault)){
-		e.preventDefault();
-		return false;
-	}
-	return false;
-};
-dhtmlDragAndDropObject.prototype.callDrag=function(e){
-	if (!e)
-		e=window.event;
-	dragger=window.dhtmlDragAndDrop;
-	if ((new Date()).valueOf()-dragger.downtime<100) return;
-
-	if ((e.button == 0)&&(_isIE))
-		return dragger.stopDrag();
-
-	if (!dragger.dragNode&&dragger.waitDrag){
-		dragger.dragNode=dragger.dragStartObject._createDragNode(dragger.dragStartNode, e);
-
-		if (!dragger.dragNode)
-			return dragger.stopDrag();
-
-		dragger.dragNode.onselectstart=function(){return false;}
-		dragger.gldragNode=dragger.dragNode;
-		document.body.appendChild(dragger.dragNode);
-		document.body.onmouseup=dragger.stopDrag;
-		dragger.waitDrag=0;
-		dragger.dragNode.pWindow=window;
-		dragger.initFrameRoute();
-	}
-
-	if (dragger.dragNode.parentNode != window.document.body){
-		var grd = dragger.gldragNode;
-
-		if (dragger.gldragNode.old)
-			grd=dragger.gldragNode.old;
-
-		//if (!document.all) dragger.calculateFramePosition();
-		grd.parentNode.removeChild(grd);
-		var oldBody = dragger.dragNode.pWindow;
-
-		if (grd.pWindow &&	grd.pWindow.dhtmlDragAndDrop.lastLanding)
-			grd.pWindow.dhtmlDragAndDrop.lastLanding.dragLanding._dragOut(grd.pWindow.dhtmlDragAndDrop.lastLanding);	
-			
-		//		var oldp=dragger.dragNode.parentObject;
-		if (_isIE){
-			var div = document.createElement("Div");
-			div.innerHTML=dragger.dragNode.outerHTML;
-			dragger.dragNode=div.childNodes[0];
-		} else
-			dragger.dragNode=dragger.dragNode.cloneNode(true);
-
-		dragger.dragNode.pWindow=window;
-		//		dragger.dragNode.parentObject=oldp;
-
-		dragger.gldragNode.old=dragger.dragNode;
-		document.body.appendChild(dragger.dragNode);
-		oldBody.dhtmlDragAndDrop.dragNode=dragger.dragNode;
-	}
-
-	dragger.dragNode.style.left=e.clientX+15+(dragger.fx
-		? dragger.fx*(-1)
-		: 0)
-		+(document.body.scrollLeft||document.documentElement.scrollLeft)+"px";
-	dragger.dragNode.style.top=e.clientY+3+(dragger.fy
-		? dragger.fy*(-1)
-		: 0)
-		+(document.body.scrollTop||document.documentElement.scrollTop)+"px";
-
-	if (!e.srcElement)
-		var z = e.target;
-	else
-		z=e.srcElement;
-	dragger.checkLanding(z, e);
-}
-
-dhtmlDragAndDropObject.prototype.calculateFramePosition=function(n){
-	//this.fx = 0, this.fy = 0;
-	if (window.name){
-		var el = parent.frames[window.name].frameElement.offsetParent;
-		var fx = 0;
-		var fy = 0;
-
-		while (el){
-			fx+=el.offsetLeft;
-			fy+=el.offsetTop;
-			el=el.offsetParent;
-		}
-
-		if ((parent.dhtmlDragAndDrop)){
-			var ls = parent.dhtmlDragAndDrop.calculateFramePosition(1);
-			fx+=ls.split('_')[0]*1;
-			fy+=ls.split('_')[1]*1;
-		}
-
-		if (n)
-			return fx+"_"+fy;
-		else
-			this.fx=fx;
-		this.fy=fy;
-	}
-	return "0_0";
-}
-dhtmlDragAndDropObject.prototype.checkLanding=function(htmlObject, e){
-	if ((htmlObject)&&(htmlObject.dragLanding)){
-		if (this.lastLanding)
-			this.lastLanding.dragLanding._dragOut(this.lastLanding);
-		this.lastLanding=htmlObject;
-		this.lastLanding=this.lastLanding.dragLanding._dragIn(this.lastLanding, this.dragStartNode, e.clientX,
-			e.clientY, e);
-		this.lastLanding_scr=(_isIE ? e.srcElement : e.target);
-	} else {
-		if ((htmlObject)&&(htmlObject.tagName != "BODY"))
-			this.checkLanding(htmlObject.parentNode, e);
-		else {
-			if (this.lastLanding)
-				this.lastLanding.dragLanding._dragOut(this.lastLanding, e.clientX, e.clientY, e);
-			this.lastLanding=0;
-
-			if (this._onNotFound)
-				this._onNotFound();
-		}
-	}
-}
-dhtmlDragAndDropObject.prototype.stopDrag=function(e, mode){
-	dragger=window.dhtmlDragAndDrop;
-
-	if (!mode){
-		dragger.stopFrameRoute();
-		var temp = dragger.lastLanding;
-		dragger.lastLanding=null;
-
-		if (temp)
-			temp.dragLanding._drag(dragger.dragStartNode, dragger.dragStartObject, temp, (_isIE
-				? event.srcElement
-				: e.target));
-	}
-	dragger.lastLanding=null;
-
-	if ((dragger.dragNode)&&(dragger.dragNode.parentNode == document.body))
-		dragger.dragNode.parentNode.removeChild(dragger.dragNode);
-	dragger.dragNode=0;
-	dragger.gldragNode=0;
-	dragger.fx=0;
-	dragger.fy=0;
-	dragger.dragStartNode=0;
-	dragger.dragStartObject=0;
-	document.body.onmouseup=dragger.tempDOMU;
-	document.body.onmousemove=dragger.tempDOMM;
-	dragger.tempDOMU=null;
-	dragger.tempDOMM=null;
-	dragger.waitDrag=0;
-}
-
-dhtmlDragAndDropObject.prototype.stopFrameRoute=function(win){
-	if (win)
-		window.dhtmlDragAndDrop.stopDrag(1, 1);
-
-	for (var i = 0; i < window.frames.length; i++){
-		try{
-		if ((window.frames[i] != win)&&(window.frames[i].dhtmlDragAndDrop))
-			window.frames[i].dhtmlDragAndDrop.stopFrameRoute(window);
-		} catch(e){}
-	}
-
-	try{
-	if ((parent.dhtmlDragAndDrop)&&(parent != window)&&(parent != win))
-		parent.dhtmlDragAndDrop.stopFrameRoute(window);
-	} catch(e){}
-}
-dhtmlDragAndDropObject.prototype.initFrameRoute=function(win, mode){
-	if (win){
-		window.dhtmlDragAndDrop.preCreateDragCopy();
-		window.dhtmlDragAndDrop.dragStartNode=win.dhtmlDragAndDrop.dragStartNode;
-		window.dhtmlDragAndDrop.dragStartObject=win.dhtmlDragAndDrop.dragStartObject;
-		window.dhtmlDragAndDrop.dragNode=win.dhtmlDragAndDrop.dragNode;
-		window.dhtmlDragAndDrop.gldragNode=win.dhtmlDragAndDrop.dragNode;
-		window.document.body.onmouseup=window.dhtmlDragAndDrop.stopDrag;
-		window.waitDrag=0;
-
-		if (((!_isIE)&&(mode))&&((!_isFF)||(_FFrv < 1.8)))
-			window.dhtmlDragAndDrop.calculateFramePosition();
-	}
-	try{
-	if ((parent.dhtmlDragAndDrop)&&(parent != window)&&(parent != win))
-		parent.dhtmlDragAndDrop.initFrameRoute(window);
-	}catch(e){}
-
-	for (var i = 0; i < window.frames.length; i++){
-		try{
-		if ((window.frames[i] != win)&&(window.frames[i].dhtmlDragAndDrop))
-			window.frames[i].dhtmlDragAndDrop.initFrameRoute(window, ((!win||mode) ? 1 : 0));
-		} catch(e){}
-	}
-}
-
-var _isFF = false;
-var _isIE = false;
-var _isOpera = false;
-var _isKHTML = false;
-var _isMacOS = false;
-var _isChrome = false;
-
-if (navigator.userAgent.indexOf('Macintosh') != -1)
-	_isMacOS=true;
-
-
-if (navigator.userAgent.toLowerCase().indexOf('chrome')>-1)
-	_isChrome=true;
-
-if ((navigator.userAgent.indexOf('Safari') != -1)||(navigator.userAgent.indexOf('Konqueror') != -1)){
-	var _KHTMLrv = parseFloat(navigator.userAgent.substr(navigator.userAgent.indexOf('Safari')+7, 5));
-
-	if (_KHTMLrv > 525){ //mimic FF behavior for Safari 3.1+
-		_isFF=true;
-		var _FFrv = 1.9;
-	} else
-		_isKHTML=true;
-} else if (navigator.userAgent.indexOf('Opera') != -1){
-	_isOpera=true;
-	_OperaRv=parseFloat(navigator.userAgent.substr(navigator.userAgent.indexOf('Opera')+6, 3));
-}
-
-
-else if (navigator.appName.indexOf("Microsoft") != -1){
-	_isIE=true;
-	if (navigator.appVersion.indexOf("MSIE 8.0")!= -1 && document.compatMode != "BackCompat") _isIE=8;
-} else {
-	_isFF=true;
-	var _FFrv = parseFloat(navigator.userAgent.split("rv:")[1])
-}
-
-
-//multibrowser Xpath processor
-dtmlXMLLoaderObject.prototype.doXPath=function(xpathExp, docObj, namespace, result_type){
-	if (_isKHTML || (!_isIE && !window.XPathResult))
-		return this.doXPathOpera(xpathExp, docObj);
-
-	if (_isIE){ //IE
-		if (!docObj)
-			if (!this.xmlDoc.nodeName)
-				docObj=this.xmlDoc.responseXML
-			else
-				docObj=this.xmlDoc;
-
-		if (!docObj)
-			dhtmlxError.throwError("LoadXML", "Incorrect XML", [
-				(docObj||this.xmlDoc),
-				this.mainObject
-			]);
-
-		if (namespace != null)
-			docObj.setProperty("SelectionNamespaces", "xmlns:xsl='"+namespace+"'"); //
-
-		if (result_type == 'single'){
-			return docObj.selectSingleNode(xpathExp);
-		}
-		else {
-			return docObj.selectNodes(xpathExp)||new Array(0);
-		}
-	} else { //Mozilla
-		var nodeObj = docObj;
-
-		if (!docObj){
-			if (!this.xmlDoc.nodeName){
-				docObj=this.xmlDoc.responseXML
-			}
-			else {
-				docObj=this.xmlDoc;
-			}
-		}
-
-		if (!docObj)
-			dhtmlxError.throwError("LoadXML", "Incorrect XML", [
-				(docObj||this.xmlDoc),
-				this.mainObject
-			]);
-
-		if (docObj.nodeName.indexOf("document") != -1){
-			nodeObj=docObj;
-		}
-		else {
-			nodeObj=docObj;
-			docObj=docObj.ownerDocument;
-		}
-		var retType = XPathResult.ANY_TYPE;
-
-		if (result_type == 'single')
-			retType=XPathResult.FIRST_ORDERED_NODE_TYPE
-		var rowsCol = new Array();
-		var col = docObj.evaluate(xpathExp, nodeObj, function(pref){
-			return namespace
-		}, retType, null);
-
-		if (retType == XPathResult.FIRST_ORDERED_NODE_TYPE){
-			return col.singleNodeValue;
-		}
-		var thisColMemb = col.iterateNext();
-
-		while (thisColMemb){
-			rowsCol[rowsCol.length]=thisColMemb;
-			thisColMemb=col.iterateNext();
-		}
-		return rowsCol;
-	}
-}
-
-function _dhtmlxError(type, name, params){
-	if (!this.catches)
-		this.catches=new Array();
-
-	return this;
-}
-
-_dhtmlxError.prototype.catchError=function(type, func_name){
-	this.catches[type]=func_name;
-}
-_dhtmlxError.prototype.throwError=function(type, name, params){
-	if (this.catches[type])
-		return this.catches[type](type, name, params);
-
-	if (this.catches["ALL"])
-		return this.catches["ALL"](type, name, params);
-
-	alert("Error type: "+arguments[0]+"\nDescription: "+arguments[1]);
-	return null;
-}
-
-window.dhtmlxError=new _dhtmlxError();
-
-
-//opera fake, while 9.0 not released
-//multibrowser Xpath processor
-dtmlXMLLoaderObject.prototype.doXPathOpera=function(xpathExp, docObj){
-	//this is fake for Opera
-	var z = xpathExp.replace(/[\/]+/gi, "/").split('/');
-	var obj = null;
-	var i = 1;
-
-	if (!z.length)
-		return [];
-
-	if (z[0] == ".")
-		obj=[docObj]; else if (z[0] == ""){
-		obj=(this.xmlDoc.responseXML||this.xmlDoc).getElementsByTagName(z[i].replace(/\[[^\]]*\]/g, ""));
-		i++;
-	} else
-		return [];
-
-	for (i; i < z.length; i++)obj=this._getAllNamedChilds(obj, z[i]);
-
-	if (z[i-1].indexOf("[") != -1)
-		obj=this._filterXPath(obj, z[i-1]);
-	return obj;
-}
-
-dtmlXMLLoaderObject.prototype._filterXPath=function(a, b){
-	var c = new Array();
-	var b = b.replace(/[^\[]*\[\@/g, "").replace(/[\[\]\@]*/g, "");
-
-	for (var i = 0; i < a.length; i++)
-		if (a[i].getAttribute(b))
-			c[c.length]=a[i];
-
-	return c;
-}
-dtmlXMLLoaderObject.prototype._getAllNamedChilds=function(a, b){
-	var c = new Array();
-
-	if (_isKHTML)
-		b=b.toUpperCase();
-
-	for (var i = 0; i < a.length; i++)for (var j = 0; j < a[i].childNodes.length; j++){
-		if (_isKHTML){
-			if (a[i].childNodes[j].tagName&&a[i].childNodes[j].tagName.toUpperCase() == b)
-				c[c.length]=a[i].childNodes[j];
-		}
-
-		else if (a[i].childNodes[j].tagName == b)
-			c[c.length]=a[i].childNodes[j];
-	}
-
-	return c;
-}
-
-function dhtmlXHeir(a, b){
-	for (var c in b)
-		if (typeof (b[c]) == "function")
-			a[c]=b[c];
-	return a;
-}
-
-function dhtmlxEvent(el, event, handler){
-	if (el.addEventListener)
-		el.addEventListener(event, handler, false);
-
-	else if (el.attachEvent)
-		el.attachEvent("on"+event, handler);
-}
-
-//============= XSL Extension ===================================
-
-dtmlXMLLoaderObject.prototype.xslDoc=null;
-dtmlXMLLoaderObject.prototype.setXSLParamValue=function(paramName, paramValue, xslDoc){
-	if (!xslDoc)
-		xslDoc=this.xslDoc
-
-	if (xslDoc.responseXML)
-		xslDoc=xslDoc.responseXML;
-	var item =
-		this.doXPath("/xsl:stylesheet/xsl:variable[@name='"+paramName+"']", xslDoc,
-			"http:/\/www.w3.org/1999/XSL/Transform", "single");
-
-	if (item != null)
-		item.firstChild.nodeValue=paramValue
-}
-dtmlXMLLoaderObject.prototype.doXSLTransToObject=function(xslDoc, xmlDoc){
-	if (!xslDoc)
-		xslDoc=this.xslDoc;
-
-	if (xslDoc.responseXML)
-		xslDoc=xslDoc.responseXML
-
-	if (!xmlDoc)
-		xmlDoc=this.xmlDoc;
-
-	if (xmlDoc.responseXML)
-		xmlDoc=xmlDoc.responseXML
-
-	//MOzilla
-	if (!_isIE){
-		if (!this.XSLProcessor){
-			this.XSLProcessor=new XSLTProcessor();
-			this.XSLProcessor.importStylesheet(xslDoc);
-		}
-		var result = this.XSLProcessor.transformToDocument(xmlDoc);
-	} else {
-		var result = new ActiveXObject("Msxml2.DOMDocument.3.0");
-		try{
-			xmlDoc.transformNodeToObject(xslDoc, result);
-		}catch(e){
-			result = xmlDoc.transformNode(xslDoc);
-		}
-	}
-	return result;
-}
-
-dtmlXMLLoaderObject.prototype.doXSLTransToString=function(xslDoc, xmlDoc){
-	var res = this.doXSLTransToObject(xslDoc, xmlDoc);
-	if(typeof(res)=="string")
-		return res;
-	return this.doSerialization(res);
-}
-
-dtmlXMLLoaderObject.prototype.doSerialization=function(xmlDoc){
-	if (!xmlDoc)
-			xmlDoc=this.xmlDoc;
-	if (xmlDoc.responseXML)
-			xmlDoc=xmlDoc.responseXML
-	if (!_isIE){
-		var xmlSerializer = new XMLSerializer();
-		return xmlSerializer.serializeToString(xmlDoc);
-	} else
-		return xmlDoc.xml;
-}
-
-/**
-*   @desc: 
-*   @type: private
-*/
-dhtmlxEventable=function(obj){
-		obj.attachEvent=function(name, catcher, callObj){
-			name='ev_'+name.toLowerCase();
-			if (!this[name])
-				this[name]=new this.eventCatcher(callObj||this);
-				
-			return(name+':'+this[name].addEvent(catcher)); //return ID (event name & event ID)
-		}
-		obj.callEvent=function(name, arg0){ 
-			name='ev_'+name.toLowerCase();
-			if (this[name])
-				return this[name].apply(this, arg0);
-			return true;
-		}
-		obj.checkEvent=function(name){
-			return (!!this['ev_'+name.toLowerCase()])
-		}
-		obj.eventCatcher=function(obj){
-			var dhx_catch = [];
-			var z = function(){
-				var res = true;
-				for (var i = 0; i < dhx_catch.length; i++){
-					if (dhx_catch[i] != null){
-						var zr = dhx_catch[i].apply(obj, arguments);
-						res=res&&zr;
-					}
-				}
-				return res;
-			}
-			z.addEvent=function(ev){
-				if (typeof (ev) != "function")
-					ev=eval(ev);
-				if (ev)
-					return dhx_catch.push(ev)-1;
-				return false;
-			}
-			z.removeEvent=function(id){
-				dhx_catch[id]=null;
-			}
-			return z;
-		}
-		obj.detachEvent=function(id){
-			if (id != false){
-				var list = id.split(':');           //get EventName and ID
-				this[list[0]].removeEvent(list[1]); //remove event
-			}
-		}
-		obj.detachAllEvents = function(){
-			for (var name in this){
-				if (name.indexOf("ev_")==0) 
-					delete this[name];
-			}
-		}
-}
− data/web/static/hledger.js
@@ -1,138 +0,0 @@-/* hledger web ui javascripts */-/* depends on jquery, other support libs, and additional js inserted inline */--$(document).ready(function() {--    /* maybe show forms */-    if ($.url.param('add')) addformToggle();-    else if ($.url.param('edit')) editformToggle();--});--function filterformToggle() {- var a = document.getElementById('addform');- var e = document.getElementById('editform');- var f = document.getElementById('filterform');- var i = document.getElementById('importform');- var t = document.getElementById('transactions');- var alink = document.getElementById('addformlink');- var elink = document.getElementById('editformlink');- var flink = document.getElementById('filterformlink');- var ilink = document.getElementById('importformlink');- var jlink = document.getElementById('journallink');- var rlink = document.getElementById('registerlink');-- if (f.style.display == 'none') {-  flink.style['font-weight'] = 'bold';-  f.style.display = 'block';- } else {-  flink.style['font-weight'] = 'normal';-  f.style.display = 'none';- }- return false;-}--function addformToggle() {- var a = document.getElementById('addform');- var e = document.getElementById('editform');- var f = document.getElementById('filterform');- var i = document.getElementById('importform');- var t = document.getElementById('transactions');- var alink = document.getElementById('addformlink');- var elink = document.getElementById('editformlink');- var flink = document.getElementById('filterformlink');- var ilink = document.getElementById('importformlink');- var jlink = document.getElementById('journallink');- var rlink = document.getElementById('registerlink');-- if (a.style.display == 'none') {-  alink.style['font-weight'] = 'bold';-  elink.style['font-weight'] = 'normal';-  ilink.style['font-weight'] = 'normal';-  jlink.style['font-weight'] = 'normal';-  rlink.style['font-weight'] = 'normal';-  a.style.display = 'block';-  e.style.display = 'none';-  i.style.display = 'none';-  t.style.display = 'none';- } else {-  alink.style['font-weight'] = 'normal';-  elink.style['font-weight'] = 'normal';-  ilink.style['font-weight'] = 'normal';-  a.style.display = 'none';-  e.style.display = 'none';-  i.style.display = 'none';-  t.style.display = 'block';- }- return false;-}--function editformToggle() {- var a = document.getElementById('addform');- var e = document.getElementById('editform');- var f = document.getElementById('filterform');- var i = document.getElementById('importform');- var t = document.getElementById('transactions');- var alink = document.getElementById('addformlink');- var elink = document.getElementById('editformlink');- var flink = document.getElementById('filterformlink');- var ilink = document.getElementById('importformlink');- var jlink = document.getElementById('journallink');- var rlink = document.getElementById('registerlink');-- if (e.style.display == 'none') {-  alink.style['font-weight'] = 'normal';-  elink.style['font-weight'] = 'bold';-  ilink.style['font-weight'] = 'normal';-  jlink.style['font-weight'] = 'normal';-  rlink.style['font-weight'] = 'normal';-  a.style.display = 'none';-  e.style.display = 'block';-  i.style.display = 'none';-  t.style.display = 'none';- } else {-  alink.style['font-weight'] = 'normal';-  elink.style['font-weight'] = 'normal';-  ilink.style['font-weight'] = 'normal';-  a.style.display = 'none';-  e.style.display = 'none';-  i.style.display = 'none';-  t.style.display = 'block';- }- return false;-}--function importformToggle() {- var a = document.getElementById('addform');- var e = document.getElementById('editform');- var f = document.getElementById('filterform');- var i = document.getElementById('importform');- var t = document.getElementById('transactions');- var alink = document.getElementById('addformlink');- var elink = document.getElementById('editformlink');- var flink = document.getElementById('filterformlink');- var ilink = document.getElementById('importformlink');- var jlink = document.getElementById('journallink');- var rlink = document.getElementById('registerlink');-- if (i.style.display == 'none') {-  alink.style['font-weight'] = 'normal';-  elink.style['font-weight'] = 'normal';-  ilink.style['font-weight'] = 'bold';-  jlink.style['font-weight'] = 'normal';-  rlink.style['font-weight'] = 'normal';-  a.style.display = 'none';-  e.style.display = 'none';-  i.style.display = 'block';-  t.style.display = 'none';- } else {-  alink.style['font-weight'] = 'normal';-  elink.style['font-weight'] = 'normal';-  ilink.style['font-weight'] = 'normal';-  a.style.display = 'none';-  e.style.display = 'none';-  i.style.display = 'none';-  t.style.display = 'block';- }- return false;-}
− data/web/static/images/combo_select.gif

binary file changed (309 → absent bytes)

− data/web/static/jquery.js
@@ -1,154 +0,0 @@-/*!- * jQuery JavaScript Library v1.4.2- * http://jquery.com/- *- * Copyright 2010, John Resig- * Dual licensed under the MIT or GPL Version 2 licenses.- * http://jquery.org/license- *- * Includes Sizzle.js- * http://sizzlejs.com/- * Copyright 2010, The Dojo Foundation- * Released under the MIT, BSD, and GPL Licenses.- *- * Date: Sat Feb 13 22:33:48 2010 -0500- */-(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?-e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=-j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,-"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=-true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,-Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&-(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,-a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===-"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,-function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||-c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",-L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,-"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+-a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],-d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===-a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&-!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=-true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML="   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";-var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,-parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=-false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=-s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,-applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];-else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,-a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===-w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,-cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",-i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",-" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=-this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=-e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=-c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");-a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,-function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");-k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),-C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=-null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=-e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&-f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;-if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),-fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||-d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,-"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=-a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,-isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=-{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};-if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",-e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,-"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,-d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&-!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},-toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,-u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),-function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];-if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,-e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();-t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||-g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];-for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length--1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,-CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},-relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=-l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];-h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},-CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,-g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},-text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},-setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=-h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=-m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===-"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,-h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||-!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=-h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&-q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";-if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();-(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:-function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,-gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;-c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=-{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===-"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",-d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?-a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===-1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?-a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=-c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},-wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},-prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,-this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);-return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,-""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&-this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||-u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===-1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);-return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",-""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=-c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?-c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=-function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=-Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,-"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=-a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=-a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==-"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},-serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),-function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,-global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&-e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?-"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===-false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=-false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",-c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||-d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);-g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===-1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===-"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;-if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");-this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],-"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},-animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=-j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);-this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===-"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||-c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;-this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=-this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,-e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||-c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?-function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=-this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;-k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&-f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";-a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);-c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,-d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top--f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":-"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in-e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);
− data/web/static/jquery.url.js
@@ -1,1 +0,0 @@-jQuery.url=function(){var segments={};var parsed={};var options={url:window.location,strictMode:false,key:["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],q:{name:"queryKey",parser:/(?:^|&)([^&=]*)=?([^&]*)/g},parser:{strict:/^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,loose:/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/}};var parseUri=function(){str=decodeURI(options.url);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}});return uri};var key=function(key){if(!parsed.length){setUp()}if(key=="base"){if(parsed.port!==null&&parsed.port!==""){return parsed.protocol+"://"+parsed.host+":"+parsed.port+"/"}else{return parsed.protocol+"://"+parsed.host+"/"}}return(parsed[key]==="")?null:parsed[key]};var param=function(item){if(!parsed.length){setUp()}return(parsed.queryKey[item]===null)?null:parsed.queryKey[item]};var setUp=function(){parsed=parseUri();getSegments()};var getSegments=function(){var p=parsed.path;segments=[];segments=parsed.path.length==1?{}:(p.charAt(p.length-1)=="/"?p.substring(1,p.length-1):path=p.substring(1)).split("/")};return{setMode:function(mode){strictMode=mode=="strict"?true:false;return this},setUrl:function(newUri){options.url=newUri===undefined?window.location:newUri;setUp();return this},segment:function(pos){if(!parsed.length){setUp()}if(pos===undefined){return segments.length}return(segments[pos]===""||segments[pos]===undefined)?null:segments[pos]},attr:key,param:param}}();
− data/web/static/style.css
@@ -1,226 +0,0 @@-/* hledger web ui styles */--/* font families & sizes */-/* overspecified for cross-browser robustness */-body                        { font-family:helvetica,arial,"sans serif"; }-pre                         { font-family:courier,"courier new",monospace; }-input.textinput, .dhx_combo_input, .dhx_combo_list    { font-size:small; }-#editform textarea          { font-family:courier,"courier new",monospace; font-size:small; }-.nav2                       { font-size:small; }-#filterform                 { font-size:small; }-.topleftlink                { font-size:small; }-.toprightlink               { font-size:small; }-#journalinfo                { font-size:small; }-.help                       { font-size:smaller; }-.form                       { font-size:small; }-.journalreport              { font-size:small; }-.balancereport              { font-size:small; }-.registerreport             { font-size:small; }-#showmoreaccounts           { font-size:small; }-/* #addformlink                { font-size:small; } */-/* #editformlink               { font-size:small; } */--body                        { margin:0; }-#navbar                     { padding:2px; }-.topleftlink                { float:left; margin-right:1em; padding:2px; }-.toprightlink               { float:right; margin-left:1em; padding:2px; }-#navbar h1                  { display:inline-block; vertical-align:top; margin:0; }-#journalinfo                { vertical-align:middle; margin:0; }-/* #navbar                  { padding:4px; background-color:#eee; border-bottom:2px solid #ddd; } */-#messages                   { color:red; background-color:#ffeeee; margin:0.5em;}-.help                       { font-style: italic; }-.helprow td                 { padding-bottom:8px; }-#content                    { margin:1em; }-#navlinks                   { margin-bottom:1em; }-.navlink                    { }-.navlinkcurrent             { font-weight:bold; }-.form                       { margin:1em; }--#filterformdiv              { margin:0 0 1em 0; white-space:nowrap; }-#filterform                 { margin:0; }-#filterform span            { padding:4px; }-#stopfilterlink             { font-weight:bold; }-.filtering                  { background-color:#ddd; }--.current                    { font-weight:bold; background-color:#ddd; }-.description                { padding-left:1em; }-.account                    { white-space:nowrap; padding-left:1em; }-.amount                     { white-space:nowrap; padding-left:1em; }-.balance                    { white-space:nowrap; padding-left:1em; }-.account, .amount, .balance { width:2em; }  /* minimum width */-.positive                   { }-.negative                   { color:#800; }-/* .odd                     { background-color:#e8e8e8; } */-/* .even                    { background-color:#e8f8e8; } */-/* .even                    { background-color:#f0fff0; } */--table.journalreport         { border-spacing: 0; }-.journalreport td           { border-top:thin solid #eee; }-.journalreport pre          { margin-top:0; }--.ledger .accounts           {padding-right:1em; margin-right:1em; border-right:thin solid #eee; }-.ledger .register           { }--div.accounts                { margin-bottom:5em; }--.balancereport tr           { vertical-align:top; }-table.balancereport         { border-spacing:0; }-.ledger .balancereport td   { padding:0; }-.totalrule td               { border-top:thin solid black; }--table.registerreport        { border-spacing:0; }-.registerreport tr          { vertical-align:top; }-.registerreport td          { padding-bottom:0.2em; }-.registerreport .date       { white-space:nowrap; }-.firstposting td            { }-.registerreport .odd        { background-color:#f0f0f0; }--#addform input.textinput, #addform .dhx_combo_input, .dhx_combo_list   { background-color:#eee; padding:4px; }-#addform table              { }-#addform #addbuttonrow      { text-align:right; }-/* #editform                   { width:95%; } */-#editform textarea          { width:100%; background-color:#eee; padding:4px; }-#filterform table           { border-spacing:0; padding-left:1em; }---/*------------------------------------------------------------------------------------------*/--.dhx_combo_input{-/* color:#333333; */-/* font-family: Arial; */-/* font-size: 9pt; */-/* border:0px; */-/* padding:2px 2px 2px 2px; */-/* position:absolute; */-/* top:0px; */-}--/* table {border:thin solid red} */-/* div {border:thin solid yellow} */--.dhx_combo_box{-    position:relative;-    display:inline-block;-    /* text-align:left; */-    /* height:20px; */-    /* _height:22px; */-    /* overflow:hidden; */-    /* background-color: white; */-}--.dhx_combo_list{-    position:absolute;-    z-index:230;-    overflow-y:auto;-    overflow-x:hidden;-    white-space:nowrap;-    border:1px solid black;-    height:50%;-    /* background-color: white; */-}--.dhx_combo_list div{-    cursor:default;-    padding:2px 2px 2px 2px;-}--.dhx_selected_option{-    background-color:navy;-    color:white;-}--.dhx_combo_img{-    /* display:none; */-    width:18px;-    height:20px;-    position:absolute;-    top:12px;-    right:-10px;-}--.dhx_combo_option_img{-	position:relative;-	top:1px;-	margin:0px;-	margin-left:2px;-	left:0px;-	width:18px; height:18px;-}--/* .combo_dhx_sel{ */-/* .dhx_selected_option{ */-/*    background-image: url("../static/images/bg_selection.gif") !important; */-/*    background-position: bottom; */-/*    background-repeat: repeat-x; */-/*    color:black; */-/* } */----/* .dhx_combo_img_rtl{ */-/* 	position:absolute; */-/* 	top:0px; */-/* 	left:1px; */-/* 	width:17px; */-/* 	height:20px; */-/* } */-/* .dhx_combo_option_img_rtl{ */-/* 	float:right; */-/* 	margin-right :0px; */-/* 	width:18px; height:18px; */-/* } */--/* .dhx_combo_list_rtl{ */-/* 	direction: rtl; */-/* 	unicode-bidi : bidi-override; */-/*    position:absolute; */-/*    z-index:230; */-/*    overflow-y:auto; */-/*    overflow-x:hidden; */-/*    border:1px solid black; */-/*    height:100px; */-/*    /\* font-family: Arial; *\/ */-/*    font-size: 9pt; */-/*    background-color: white; */-/* } */-/* .dhx_combo_list_rtl div{ */-/* 	direction: rtl; */-/* 	unicode-bidi : bidi-override; */-/* 	padding:2px 2px 2px 2px; */-/* } */-/* .dhx_combo_list_rtl div div{ */-/* 	float :right !important; */-/* 	cursor:default; */-/* } */-/* .dhx_combo_list_rtl div img{ */-/* 	float :right !important; */-/* } */-/* .dhx_combo_list_rtl div input{ */-/* 	float :right !important; */-/* } */--/* .dhx_combo_box.dhx_skyblue{ */-/* 	border:1px solid #a4bed4; */-/* } */-/* .dhx_combo_box.dhx_skyblue .dhx_combo_input { */-/* 	font-family:Tahoma; */-/* 	font-size: 11px; */-/* 	padding:3px; */-/* } */-/* .dhx_combo_list.dhx_skyblue_list{ */-/* 	background-color: #eaf2fb; */-/* 	border:1px solid #a4bed4; */-/* 	font-family:Tahoma; */-/* 	font-size: 11px; */-/* } */-/* .dhx_combo_list.dhx_skyblue_list div{ */-/* 	cursor:default; */-/* 	padding:3px 4px; */-/* } */-/* .dhx_combo_list_rtl.dhx_skyblue_list{ */-/*    background-color: #eaf2fb; */-/* 	border:1px solid #a4bed4; */-/* 	font-family:Tahoma; */-/* 	font-size: 11px; */-/* } */-
hledger.cabal view
@@ -1,15 +1,15 @@ name:           hledger-version: 0.12.1+version: 0.13 category:       Finance-synopsis:       A command-line (or curses or web-based) double-entry accounting tool.+synopsis:       A command-line double-entry accounting tool. description:                 hledger reads a plain text general journal or time log                 describing your transactions and displays precise-                balance and register reports via command-line, curses-                or web interface.  It is a remix, in haskell, of John-                Wiegley's excellent c++ ledger.  hledger aims to be a-                practical, accessible tool for end users and a useful-                library for finance-minded haskell programmers.+                balance and register reports on the console.+                It is a remix, in haskell, of John Wiegley's excellent c+++                ledger.  hledger aims to be a practical, accessible tool+                for end users and a useful library for finance-minded+                haskell programmers.  license:        GPL license-file:   LICENSE@@ -21,41 +21,18 @@ tested-with:    GHC==6.10, GHC==6.12 cabal-version:  >= 1.6 build-type:     Custom-data-dir:       data-data-files:-                web/static/style.css-                web/static/hledger.js-                web/static/jquery.js-                web/static/jquery.url.js-                web/static/dhtmlxcommon.js-                web/static/dhtmlxcombo.js-                web/static/images/combo_select.gif+-- data-dir:       data+-- data-files: extra-tmp-files: extra-source-files:-  README.rst-  DEVELOPMENT.rst-  MANUAL.markdown-  NEWS.markdown-  CONTRIBUTORS.rst-  data/sample.journal-  data/sample.timelog-  data/sample.rules -flag chart-  description: enable simple balance pie chart generation-  default:     False--flag vty-  description: enable the curses-style ui-  default:     False--flag web-  description: enable the web ui (using yesod/hamlet/simpleserver, requires ghc 6.12)-  default:     False+source-repository head+  type:     darcs+  location: http://joyful.com/repos/hledger  executable hledger   main-is:        hledger.hs-  -- should set patchlevel here as in Makefile+  -- XXX set patchlevel here as in Makefile   cpp-options:    -DPATCHLEVEL=0   ghc-options:    -threaded -W   other-modules:@@ -64,16 +41,16 @@                   Hledger.Cli.Tests                   Hledger.Cli.Utils                   Hledger.Cli.Version-                  Hledger.Cli.Commands.Add-                  Hledger.Cli.Commands.All-                  Hledger.Cli.Commands.Balance-                  Hledger.Cli.Commands.Convert-                  Hledger.Cli.Commands.Histogram-                  Hledger.Cli.Commands.Print-                  Hledger.Cli.Commands.Register-                  Hledger.Cli.Commands.Stats+                  Hledger.Cli.Commands+                  Hledger.Cli.Add+                  Hledger.Cli.Balance+                  Hledger.Cli.Convert+                  Hledger.Cli.Histogram+                  Hledger.Cli.Print+                  Hledger.Cli.Register+                  Hledger.Cli.Stats   build-depends:-                  hledger-lib == 0.12.1+                  hledger-lib == 0.13                  ,HUnit                  ,base >= 3 && < 5                  ,containers@@ -84,40 +61,15 @@                  ,old-locale                  ,old-time                  ,parsec-                 ,process+                 ,process >= 1.0.1.4 && < 1.1                  ,regexpr >= 0.5.1                  ,safe >= 0.2                  ,split == 0.1.*                  ,time                  ,utf8-string >= 0.3.5 && < 0.4--  if flag(chart)-    cpp-options: -DCHART-    other-modules:Hledger.Cli.Commands.Chart-    build-depends:-                  Chart >= 0.11-                 ,colour-                 ,gtk2hs-buildtools--  if flag(vty)-    cpp-options: -DVTY-    other-modules:Hledger.Cli.Commands.Vty-    build-depends:-                  vty >= 4.0.0.1--  if flag(web)-    cpp-options: -DWEB-    other-modules:Hledger.Cli.Commands.Web-    build-depends:-                  io-storage >= 0.3 && < 0.4-                 ,yesod >= 0.5.0.3 && < 0.6-                 ,hamlet == 0.5.*-                 ,convertible-text >= 0.3.0.1 && < 0.4-                 ,data-object >= 0.3.1.2 && < 0.4-                 ,failure >= 0.1 && < 0.2+                 ,haskeline == 0.6.* --- modules and dependencies below should be as above, except--- chart, vty, web etc. are not presently exposed as library functions+-- modules and dependencies below should be as above library   -- should set patchlevel here as in Makefile   cpp-options:    -DPATCHLEVEL=0@@ -128,16 +80,16 @@                   Hledger.Cli.Tests                   Hledger.Cli.Utils                   Hledger.Cli.Version-                  Hledger.Cli.Commands.Add-                  Hledger.Cli.Commands.All-                  Hledger.Cli.Commands.Balance-                  Hledger.Cli.Commands.Convert-                  Hledger.Cli.Commands.Histogram-                  Hledger.Cli.Commands.Print-                  Hledger.Cli.Commands.Register-                  Hledger.Cli.Commands.Stats+                  Hledger.Cli.Commands+                  Hledger.Cli.Add+                  Hledger.Cli.Balance+                  Hledger.Cli.Convert+                  Hledger.Cli.Histogram+                  Hledger.Cli.Print+                  Hledger.Cli.Register+                  Hledger.Cli.Stats   build-depends:-                  hledger-lib == 0.12.1+                  hledger-lib == 0.13                  ,HUnit                  ,base >= 3 && < 5                  ,containers@@ -154,13 +106,3 @@                  ,split == 0.1.*                  ,time                  ,utf8-string >= 0.3.5 && < 0.4--source-repository head-  type:     darcs-  location: http://joyful.com/repos/hledger---- cf http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html---- Additional dependencies:--- required for make test: test-framework, test-framework-hunit--- required for make bench: tabular-0.1