Merge commit 'c235b59a68d04ff22a16d317e4d67b69e570a1e4' into kitchen-sink

This commit is contained in:
Craig Earls 2013-01-29 13:45:44 -07:00
commit 870c62bf14
9 changed files with 1281 additions and 62 deletions

View file

@ -0,0 +1,429 @@
#!/usr/bin/perl
use warnings;
use strict;
use Getopt::Long; # Options processing
use Smart::Comments -ENV, "###"; # Ignore my dividers, and use
# Smart_Comments=1 to activate
use Cwd;
use File::Basename;
use 5.10.0;
use POSIX qw(strftime);
use Date::Calc qw(Add_Delta_Days);
use Template;
my $TT = Template->new( { POST_CHOMP => 1 } );
######################################################################
# TODO
#
# DONE Meal summaries are broken for multi-week reports
######################################################################
# Options
# Is this an internal report?
my $ExpenseReportCode = undef;
my $Internal = undef;
my $SuppressMeals = 0;
my $ViewAfter = 0;
my $ImageDir = ".";
my $Anonymize = 0;
my $Help = undef;
GetOptions( 'c' => \$Internal,
'm' => \$SuppressMeals,
'v' => \$ViewAfter,
'a' => \$Anonymize,
'I' => \$ImageDir,
'e:s' => \$ExpenseReportCode,
'h|help' => \$Help
);
# Help
defined $Help && do {
print <<EOF;
Usage: GenerateLatexExpenseReport.pl [OPTION] -e ERCode
Options:
-c Internal report
-m Suppress meals
-v View PDF on completion
-a Anonymous, omit header/footer
-I Image directory
-e ER Code (AISER0001)
EOF
exit -1;
};
die "Pass -e <ExpenseReportCode>" unless defined $ExpenseReportCode;
######################################################################
# Report items
my @ItemizedExpenses;
my $ItemizedTotal = 0.00;
my @ItemizedReceipts;
my @MealsReport;
######################################################################
# Gather required data about this expense report from the directory name
# ie: ./AISER0015 20090419-20090425 AGIL2078 Shands HACMP/
#
# ExpenseReportCode = AISER0015
# DateRange = 20090419-20090425
# ProjectCode = AGIL2078
# Description = Shands HACMP
######################################################################
# Remaining options
# Where is the ledger binary?
my $LedgerBin = "./ledger -f ./.ledger -V";
# -E Show empty accounts
# -S d Sort by date
# -V Convert mileage to $
my $LedgerOpts = "--no-color -S d";
my $LedgerAcct = "^Dest:Projects";
my $LedgerCriteria = "%" . "ER=$ExpenseReportCode";
# Internal report?
if ( $Internal ) {
# No mileage on an internal report
# $LedgerCriteria .= "&!/Mileage/"; # This shouldn't matter, just don't put metadata for ER on mileage
$LedgerAcct = "^Dest:Internal";
}
my $CmdLine = "$LedgerBin reg $LedgerOpts -E \"$LedgerCriteria\" and ^Stub "
. "--format \"%(tag('ER'))~%(tag('PROJECT'))~%(tag('NOTE'))\n\"";
### $CmdLine
my @TempLine = `$CmdLine`;
# Match all remaining items
$TempLine[0] =~ m,^(?<Er>.*?)~
(?<Project>.*?)~
(?<Note>.*?)\s*$,x;
my $ProjectCode= $+{'Project'};
my $Description= $+{'Note'};
### $ExpenseReportCode
### $ProjectCode
### $Description
### $LedgerAcct
### $Internal
### $Anonymize
### $LedgerAcct
### $LedgerOpts
### $LedgerCriteria
######################################################################
# Pull main ledger report of line items for the expense report
# Using ~ as a delimiter
#
# Example:
# '2009/04/25~AR:Projects:AGIL2078:PersMealsLunch~:AISER0015: PILOT 00004259 MIDWAY, FL~ 8.68~Receipts/AGIL2078/20090425_Pilot_8_68_21204.jpg\n'
#
#./ledger --no-color reg %ER=AISER0040 and ^Projects -y "%Y/%m/%d" -V --format "%(date)~%(account)~%(payee)~%(amount)~%(tag('NOTE'))\n"
$CmdLine = "$LedgerBin reg $LedgerOpts \"$LedgerCriteria\" and \"$LedgerAcct\" "
. "-y \"%Y/%m/%d\" "
. "--format \"%(date)~%(tag('CATEGORY'))~%(payee)~%(display_amount)~%(tag('NOTE'))~%(tag('RECEIPT'))\\n\"";
### $CmdLine
my @MainReport = `$CmdLine`;
### MainReport: @MainReport
# Remove any project codes and linefeeds
#map { chomp(); s/(:AISER[0-9][0-9][0-9][0-9])+://g; } @MainReport; # No need, thats now metadata
foreach my $line (@MainReport) { ### Processing Main Report... done
# Remove bad chars (#&)
$line =~ tr/#&//d;
# Match all remaining items
$line =~ m,^(?<Date>[0-9]{4}/[0-9]{2}/[0-9]{2})~
(?<Category>.*?)~
(?<Vendor>.*?)~
(?<Amount>.*?)~
(?<Note>.*?)~
(?<Receipts>.*?)\s*$,x;
my %Record = %+;
$Record{'Amount'}=~tr/$,//d;
foreach (keys %Record) {
$Record{$_} =~ s/^\s+//g;
}
# Grab images from <<file:///dir/filename.jpg>>
my $ImageList = $Record{'Receipts'};
$ImageList //= '';
my @Images = split( /,/, $ImageList );
# Cleanup
# Take last word of account name as category
$Record{'Category'} = ( split( /:/, $Record{'Category'} ) )[-1];
# If no images, italicise the line item.
$Record{'Italics'} = 1;
# Test images
foreach my $Image (@Images) {
# Turn off italics because there is an image
$Record{'Italics'} = 0;
if (! -r $ImageDir . "/" . $Image) {
print STDERR "Missing $ImageDir/$Image\n";
}
}
# Add to itemized expenses to be printed
push( @ItemizedExpenses, \%Record );
$ItemizedTotal += $Record{'Amount'};
# Add to itemized reciepts for printing
push( @ItemizedReceipts, { 'Vendor' => $Record{'Vendor'},
'Amount' => $Record{'Amount'},
'Date' => $Record{'Date'},
'Images' => \@Images } )
if $ImageList;
}
### @ItemizedExpenses
######################################################################
# Meals report
# Summarize total spent on meals by day
$CmdLine = "$LedgerBin reg $LedgerOpts "
. "\"$LedgerCriteria\" and \"$LedgerAcct\" and \%CATEGORY=Meals "
. "-D -n "
. "--format \"%(account)~%(payee)~%(display_amount)~%(total)\n\" "
. "| grep -v '<None>'";
### $CmdLine
my @MealsOutput = `$CmdLine`;
### @MealsOutput
foreach my $line (@MealsOutput) {
# Match all remaining items
$line =~ m,^(?<Account>.*?)~
(?<DOW>.*?)~\$
(?<Amount>\s*[0-9]+\.[0-9]+)~\$
(?<RunningTotal>.*?)\s*$,x;
my %TRecord=%+;
$TRecord{'Account'}=~s/^Projects://g;
$TRecord{'DOW'}=~s/^- //g;
# Add to itemized expenses to be printed
push( @MealsReport, \%TRecord );
}
######################################################################
# Total by category
$CmdLine = "$LedgerBin bal $LedgerOpts "
. "\"$LedgerCriteria\" and \"$LedgerAcct\" '--account=tag(\"CATEGORY\")' "
. "--format \"%(account)~%(display_total)\\n\"";
### $CmdLine
my @CategoryOutput = `$CmdLine`;
### @CategoryOutput
my @CategoryReport;
foreach my $line (@CategoryOutput) {
chomp $line;
$line =~ tr/\$,//d;
# Match all remaining items
my @Temp = split(/~/,$line);
my %TRecord= ( 'Category' => $Temp[0],
'Amount' => $Temp[1]);
if ($TRecord{'Category'} eq '') {
$TRecord{'Category'} = '\\hline \\bf Total';
}
# Cleanup
# Take last word of account name as category
$TRecord{'Category'} = ( split( /:/, $TRecord{'Category'} ) )[0];
# Add to itemized expenses to be printed
push( @CategoryReport, \%TRecord );
}
### @CategoryReport
######################################################################
# Output
######################################################################
my $TTVars = {
'Internal' => $Internal,
'SuppressMeals' => $SuppressMeals,
'ExpenseReportCode' => $ExpenseReportCode,
'ProjectCode' => $ProjectCode,
'Description' => $Description,
'ItemizedExpenses' => \@ItemizedExpenses,
'ItemizedTotal' => $ItemizedTotal,
'MealsReport' => \@MealsReport,
'CategoryReport' => \@CategoryReport,
'ItemizedReceipts' => \@ItemizedReceipts,
'ImageDir' => $ImageDir,
'Anonymize' => $Anonymize
};
#### $TTVars
my $LatexTemplate = <<EOF;
[% USE format %][% ToDollars = format('\\\$%.2f') %]
%%%%%%%%%%% Header
\\documentclass[10pt,letterpaper]{article}
\\usepackage[letterpaper,includeheadfoot,top=0.5in,bottom=0.5in,left=0.75in,right=0.75in]{geometry}
\\usepackage[utf8]{inputenc}
\\usepackage[T1]{fontenc}
\\usepackage[scaled]{helvet}
\\renewcommand*\\familydefault{\\sfdefault}
\\usepackage{lastpage}
\\usepackage{fancyhdr}
\\usepackage{graphicx}
\\usepackage{multicol}
\\usepackage[colorlinks,linkcolor=blue]{hyperref}
\\pagestyle{fancy}
\\renewcommand{\\headrulewidth}{1pt}
\\renewcommand{\\footrulewidth}{0.5pt}
\\geometry{headheight=48pt}
\\begin{document}
%%%%%%%%%% Itemized table
\\section{Itemized Expenses}
[% FOREACH Expense IN ItemizedExpenses %]
[% IF loop.first() or ( ( loop.count mod 20 ) == 0 ) %]
\\begin{tabular}{|l|l|p{2in}|r|p{2in}|}
\\hline
\\hline
\\bf Date & \\bf Category & \\bf Expense & \\bf Amount & \\bf Notes \\\\
\\hline \\hline
[% END %]
[% IF Expense.Italics %]\\it[% END %] [% Expense.Date %] & [% IF Expense.Italics %]\\it[% END %] [% Expense.Category %] & [% IF Expense.Italics %]\\it[% END %] [% Expense.Vendor %] & [% IF Expense.Italics %]\\it[% END %] [% ToDollars(Expense.Amount) %] & [% IF Expense.Italics %]\\it[% END %] [% Expense.Note %] \\\\ \\hline
[% IF loop.last() %]
\\hline
& & \\bf Total & \\bf [% ToDollars(ItemizedTotal) %] & \\\\
[% END %]
[% IF ( ( (loop.count + 1) mod 20 ) == 0 ) or loop.last() %]
\\hline
\\hline
\\end{tabular}
[% IF ( ( (loop.count + 1) mod 20 ) == 0 ) %]\\newline {\\it Continued on next page...}[% END %]
\\newpage
[% END %]
[% END %]
[% IF ! Internal %][% IF ! SuppressMeals %]
%%%%%%%%%% Meals summary table
\\section{Meals Summary By Day}
\\begin{tabular}{|l|l|p{2in}|p{2in}|}
\\hline
\\hline
\\bf DOW & \\bf Daily Total & \\bf Running Total \\\\
\\hline \\hline
[% FOREACH Meal IN MealsReport %]
[% Meal.DOW %] & [% ToDollars(Meal.Amount) %] & [% ToDollars(Meal.RunningTotal) %] \\\\ \\hline
[% END %]
\\hline
\\hline
\\end{tabular}
[% END %][% END %]
%%%%%%%%%% Category summary
\\section{Expenses Summary}
\\begin{tabular}{|l|l|}
\\hline
\\hline
\\bf Category & \\bf Total \\\\
\\hline \\hline
[% FOREACH Category IN CategoryReport %]
[% Category.Category %] & [% ToDollars(Category.Amount) %] \\\\ \\hline
[% END %]
\\hline
\\hline
\\end{tabular}
%%%%%%%%%% Begin receipts
\\section{Scanned Receipts}
[% FOREACH Receipt IN ItemizedReceipts %]
\\subsection{[% Receipt.Date %] [% Receipt.Vendor %]: [% ToDollars(Receipt.Amount) %]}
[% FOREACH Image IN Receipt.Images %]
\\includegraphics[angle=90,width=\\textwidth,keepaspectratio]{[% ImageDir %]/[% Image %]} \\\\
[% END %]
[% END %]
%%%%%%%%%% Footer
\\end{document}
EOF
my $LatexFN = $ExpenseReportCode . "-" . $ProjectCode . ".tex";
### $LatexFN
$TT->process( \$LatexTemplate, $TTVars, "./tmp/" . $LatexFN ) || do {
my $error = $TT->error();
print "error type: ", $error->type(), "\n";
print "error info: ", $error->info(), "\n";
die $error;
};
my $LatexOutput = `pdflatex -interaction batchmode -output-directory ./tmp "$LatexFN"`;
### $LatexOutput
$LatexOutput = `pdflatex -interaction batchmode -output-directory ./tmp "$LatexFN"`;
### $LatexOutput
if ($ViewAfter) {
my $ViewFN = $LatexFN;
$ViewFN =~ s/\.tex$/.pdf/;
`acroread "./tmp/$ViewFN"`;
}

View file

@ -0,0 +1,111 @@
; TAG key: value
2009/09/27 * (09/28/2009) HUDSON NEWS HOUSTN HBB HOUSTON, TX
Source:Visa -$6.55
Projects:Meals
; ER: AISER0033
; PROJECT: PROJXXXX
2009/09/27 * (09/28/2009) PEET'S COFFEE & TEA KINGWOOD, TX
Source:Visa -$2.44
Projects:Meals
; ER: AISER0033
; PROJECT: PROJXXXX
2009/09/28 * (09/29/2009) FUSIA NEW YORK, NY
Source:Visa -$15.25
Projects:Meals
; ER: AISER0033
; PROJECT: PROJXXXX
2009/09/29 * (09/30/2009) BALUCHI'S NEW YORK, NY
Source:Visa
Projects:Meals $20.00
; ER: AISER0033
; PROJECT: PROJXXXX
Internal:Travel $10.44
; ER: AISER0036
; PROJECT: Internal
2009/10/01 * Reimbursing AISER0036
Bank:AISChecking
; ER: AISER0036
; PROJECT: Internal
Source:Visa $10.44
----------
$ ./ledger -Ef AISER0033.dat bal '--account=tag("ER")'
$-44.24
$44.24 AISER0033
0 AISER0036
--------------------
0
$ ./ledger -Ef AISER0033.dat bal
$-10.44 Bank:AISChecking
$10.44 Internal:Travel
$44.24 Projects:Meals
$-44.24 Source:Visa
--------------------
0
$ ./ledger -Ef AISER0033.dat bal '--account=tag("PROJECT")'
$-44.24
0 Internal
$44.24 PROJXXXX
--------------------
0
$ ./ledger -f AISER0033.dat reg '--account=tag("PROJECT")'
09-Sep-27 HUDSON NEWS HOUSTN .. $-6.55 $-6.55
09-Sep-27 HUDSON NEWS HOUSTN .. PROJXXXX $6.55 0
09-Sep-27 PEET'S COFFEE & TEA.. $-2.44 $-2.44
09-Sep-27 PEET'S COFFEE & TEA.. PROJXXXX $2.44 0
09-Sep-28 FUSIA NEW YORK, NY $-15.25 $-15.25
09-Sep-28 FUSIA NEW YORK, NY PROJXXXX $15.25 0
09-Sep-29 BALUCHI'S NEW YORK,.. $-30.44 $-30.44
09-Sep-29 BALUCHI'S NEW YORK,.. PROJXXXX $20.00 $-10.44
09-Sep-29 BALUCHI'S NEW YORK,.. Internal $10.44 0
09-Oct-01 Reimbursing AISER0036 Internal $-10.44 $-10.44
09-Oct-01 Reimbursing AISER0036 $10.44 0
$ ./ledger -f AISER0033.dat reg '--account=tag("ER")'
09-Sep-27 HUDSON NEWS HOUSTN .. $-6.55 $-6.55
09-Sep-27 HUDSON NEWS HOUSTN .. AISER0033 $6.55 0
09-Sep-27 PEET'S COFFEE & TEA.. $-2.44 $-2.44
09-Sep-27 PEET'S COFFEE & TEA.. AISER0033 $2.44 0
09-Sep-28 FUSIA NEW YORK, NY $-15.25 $-15.25
09-Sep-28 FUSIA NEW YORK, NY AISER0033 $15.25 0
09-Sep-29 BALUCHI'S NEW YORK,.. $-30.44 $-30.44
09-Sep-29 BALUCHI'S NEW YORK,.. AISER0033 $20.00 $-10.44
09-Sep-29 BALUCHI'S NEW YORK,.. AISER0036 $10.44 0
09-Oct-01 Reimbursing AISER0036 AISER0036 $-10.44 $-10.44
09-Oct-01 Reimbursing AISER0036 $10.44 0
$ ./ledger -f AISER0033.dat reg %ER=AISER0033
09-Sep-27 HUDSON NEWS HOUSTN .. Projects:Meals $6.55 $6.55
09-Sep-27 PEET'S COFFEE & TEA.. Projects:Meals $2.44 $8.99
09-Sep-28 FUSIA NEW YORK, NY Projects:Meals $15.25 $24.24
09-Sep-29 BALUCHI'S NEW YORK,.. Projects:Meals $20.00 $44.24
$ ./ledger -f AISER0033.dat reg %ER=AISER0036
09-Sep-29 BALUCHI'S NEW YORK,.. Internal:Travel $10.44 $10.44
09-Oct-01 Reimbursing AISER0036 Bank:AISChecking $-10.44 0
$ ./ledger -f AISER0033.dat reg %PROJECT=PROJXXXX
09-Sep-27 HUDSON NEWS HOUSTN .. Projects:Meals $6.55 $6.55
09-Sep-27 PEET'S COFFEE & TEA.. Projects:Meals $2.44 $8.99
09-Sep-28 FUSIA NEW YORK, NY Projects:Meals $15.25 $24.24
09-Sep-29 BALUCHI'S NEW YORK,.. Projects:Meals $20.00 $44.24
$ ./ledger -f AISER0033.dat --prepend-format='%(tag("IMG")) ' reg %ER=0033
image1.jpg 09-Sep-27 HUDSON NEWS HOUSTN .. Projects:Meals $6.55 $6.55
image2.jpg 09-Sep-27 PEET'S COFFEE & TEA.. Projects:Meals $2.44 $8.99
image3.jpg 09-Sep-28 FUSIA NEW YORK, NY Projects:Meals $15.25 $24.24
image4.jpg 09-Sep-29 BALUCHI'S NEW YORK,.. Projects:Meals $20.00 $44.24

5
contrib/raw/README Normal file
View file

@ -0,0 +1,5 @@
These scripts are from my (rladams) local ledger customizations.
They are intended as examples for features that can be made generic to benefit other Ledger users.
As they become refined, the raw files will be removed and replaced by suitable sources in contrib.

14
contrib/raw/VerifyImages.sh Executable file
View file

@ -0,0 +1,14 @@
#!/bin/sh
grep -h '; RECEIPT: ' \
*.dat \
*/*.dat \
| sed 's,\W*; RECEIPT: ,,g' \
| tr , '\n' \
| sort -u \
| while read X
do
[ -f "$X" ] \
&& echo OK $X \
|| echo XX $X
done

201
contrib/raw/dotemacs.el Normal file
View file

@ -0,0 +1,201 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Ledger
;; Maybe later add this to the expense repo once it settles
(add-to-list 'load-path "/home/adamsrl/.emacs.d/addons/ledger")
(add-to-list 'load-path "/home/adamsrl/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell/bin")
(autoload 'ledger-mode "ldg-new" nil t)
(add-to-list 'auto-mode-alist '("\\.dat$" . ledger-mode))
(add-hook 'ledger-mode-hook
(lambda ()
(setq truncate-lines 1)
(url-handler-mode 1) ; Enable hyperlinks
(require 'ledger-matching) ; Requires ldg-report anyway
(load-file "/home/adamsrl/.emacs.d/addons/ledger/ldg-xact.el")
(let ((map (current-local-map)))
(define-key map (kbd "\C-c o") 'find-file-at-point) ; Open images
(define-key map (kbd "<f8>") 'ledger-expense-shortcut)
(define-key map (kbd "M-i") 'ledger-expense-internal)
(define-key map (kbd "M-o") 'ledger-expense-personal)
(define-key map (kbd "M-'") 'ledger-expense-split)
(define-key map (kbd "M-n") '(lambda ()
(interactive)
(ledger-post-next-xact)
(recenter)
(when (get-buffer "*Receipt*")
(ledger-expense-show-receipt))))
(define-key map (kbd "M-p") '(lambda () (interactive)
(ledger-post-prev-xact)
(recenter)
(when (get-buffer "*Receipt*")
(ledger-expense-show-receipt))))
(local-unset-key [tab]) ; Ideally this turns off pcomplete
(local-unset-key [(control ?i)]) ; Ideally this turns off pcomplete
)
;(defface ledger-report-face-account-ok '((t (:foreground "Cyan"))) "Derp")
;(defface ledger-report-face-account-bad '((t (:foreground "Red"))) "Derp")
(font-lock-add-keywords
'ledger-mode
'(("Unassigned\\|Unknown\\|; RECEIPT:$" 0 'highlight prepend))) ))
;; My customizations to make receipt image matching work with ledger-report mode
(add-hook 'ledger-report-mode-hook
(lambda ()
(hl-line-mode 1)
(local-set-key (kbd "<RET>") 'ledger-report-visit-source) ; Make return jump to the right txn
(local-set-key (kbd "<tab>") 'ledger-report-visit-source) ; Make tab jump to the right txn
(local-set-key (kbd "n") '(lambda ()
(interactive)
(save-selected-window
(next-line)
(ledger-report-visit-source)))) ; Update a txn window but keep focus
(local-set-key (kbd "p") '(lambda ()
(interactive)
(save-selected-window
(previous-line)
(ledger-report-visit-source)))) ; Update a txn window but keep focus
(local-set-key (kbd "M-r") 'ledger-receipt-matching) ; Link receipt to current item
(local-set-key (kbd "M-l") 'ledger-matching-tie-receipt-to-txn) ; Link receipt to current item
(local-set-key (kbd "M-n") '(lambda ()
(interactive)
(ledger-matching-image-offset-adjust 1))) ; Next receipt image
(local-set-key (kbd "M-p") '(lambda ()
(interactive)
(ledger-matching-image-offset-adjust -1))) ; prev receipt image
(local-set-key (kbd "M-s") '(lambda ()
(interactive)
(ledger-receipt-skip))) ; Skip receipt image
(local-set-key (kbd "C-c C-e") '(lambda () (interactive)
(save-selected-window
(ledger-report-visit-source)
(ledger-toggle-current-entry) ))) ; Toggle entry
))
(defvar *ledger-expense-shortcut-ER*
"Current expense report number, just last four digits (ie: 1234 results in AISER1234).")
(defvar *ledger-expense-shortcut-split-ER*
"Split (ie: internal) expense report number, just last four digits (ie: 1234 results in AISER1234).")
(defvar *ledger-expense-shortcut-Proj* ""
"Current export report project code (ie: AGIL1292)")
(defun ledger-expense-shortcut-ER-format-specifier () *ledger-expense-shortcut-ER*)
(defun ledger-expense-shortcut-setup (ER Split Proj)
"Sets the variables expanded into the transaction."
(interactive "MER Number (4 digit number only): \nMSplit ER Number (4 digit number only): \nMProject: ")
(setq *ledger-expense-shortcut-ER*
(concatenate 'string "AISER" ER))
(setq *ledger-expense-shortcut-split-ER*
(concatenate 'string "AISER" Split))
(setq *ledger-expense-shortcut-Proj* Proj)
(setq ledger-matching-project Proj)
(message "Set Proj to %s and ER to %s, split to %s"
*ledger-expense-shortcut-Proj*
*ledger-expense-shortcut-ER*
*ledger-expense-shortcut-split-ER*))
(defun ledger-expense-shortcut ()
"Updates the ER and Project metadata with the current values of the shortcut variables."
(interactive)
(when (eq major-mode 'ledger-mode)
(if (or (eql *ledger-expense-shortcut-ER* "")
(eql *ledger-expense-shortcut-Proj* ""))
(message "Run ledger-expense-shortcut-setup first.")
(save-excursion
(search-forward "; ER:")
(kill-line nil)
(insert " " *ledger-expense-shortcut-ER*))
(save-excursion
(search-forward "; PROJECT:")
(kill-line nil)
(insert " " *ledger-expense-shortcut-Proj*)))))
(defun ledger-expense-split ()
"Splits the current transaction between internal and projects."
(interactive)
(when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
(save-excursion
(end-of-line)
(re-search-backward "^[0-9]\\{4\\}/")
(re-search-forward "^ +Dest:Projects")
(move-beginning-of-line nil)
(let ((begin (point))
(end (re-search-forward "^$")))
(goto-char end)
(insert (buffer-substring begin end))
(goto-char end)
(re-search-forward "^ Dest:Projects")
(replace-match " Dest:Internal")
(re-search-forward "; ER: +[A-Za-z0-9]+")
(replace-match (concat "; ER: " *ledger-expense-shortcut-split-ER*) t)
(when (re-search-forward "; CATEGORY: Meals" (save-excursion (re-search-forward "^$")) t)
(replace-match "; CATEGORY: Travel" t))))
(re-search-backward "^[0-9]\\{4\\}/")
(re-search-forward "^ +Dest:Projects")
(insert-string " $") ))
(defun ledger-expense-internal ()
"Makes the expense an internal one."
(interactive)
(when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
(save-excursion
(end-of-line)
(re-search-backward "^[0-9]\\{4\\}/")
(let ((begin (point))
(end (save-excursion (re-search-forward "^$"))))
(when (re-search-forward "^ Dest:Projects" end t)
(replace-match " Dest:Internal") )
(when (re-search-forward "; CATEGORY: Meals" (save-excursion (re-search-forward "^$")) t)
(replace-match "; CATEGORY: Travel" t))))))
(defun ledger-expense-personal ()
"Makes the expense an personal one, eliminating metadata and receipts."
(interactive)
(when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
(save-excursion
(end-of-line)
(re-search-backward "^[0-9]\\{4\\}/")
(let ((begin (point))
(end (save-excursion (re-search-forward "^$"))))
(when (re-search-forward "^ Dest:Projects" end t)
(replace-match " Other:Personal"))
(goto-char begin)
(save-excursion
(when (re-search-forward "^ +; ER:" end t)
(beginning-of-line)
(kill-line 1)))
(save-excursion
(when (re-search-forward "^ +; PROJECT:" end t)
(beginning-of-line)
(kill-line 1)))
(save-excursion
(when (re-search-forward "^ +; CATEGORY:" end t)
(beginning-of-line)
(kill-line 1)))
(save-excursion
(when (re-search-forward "^ +; RECEIPT:" end t)
(beginning-of-line)
(kill-line 1)))
(ledger-toggle-current-entry)))))
(defun ledger-expense-show-receipt ()
"Uses the Receipt buffer to show the receipt of the txn we're on."
(when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
(save-excursion
(end-of-line)
(re-search-backward "^[0-9]\\{4\\}/")
(let ((begin (point))
(end (save-excursion (re-search-forward "^$"))))
(save-excursion
(when (re-search-forward "^\\( +; RECEIPT: +\\)\\([^,]+?.jpg\\).*$" end t)
(ledger-matching-display-image
(concat "/home/adamsrl/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell/"
(match-string 2))) ))))))

View file

@ -0,0 +1,342 @@
;; This library is intended to allow me to view a receipt on one panel, and tie it to ledger transactions in another
(require 'ldg-report)
(defgroup ledger-matching nil
"Ledger image matching")
(defcustom ledger-matching-sourcedir "~/AdamsInfoServ/BusinessDocuments/Ledger/Incoming"
"Source directory for images to process, ie: the incoming queue of images."
:group 'ledger-matching)
(defcustom ledger-matching-destdir "~/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell/Receipts"
"Destination directory for images when matched, will still have a project directory appended to it."
:group 'ledger-matching)
(defcustom ledger-matching-relative-receipt-dir "Receipts"
"Relative directory root for destination images used in Ledger entries, will have the project directory appended and receipt filename."
:group 'ledger-matching)
(defcustom ledger-matching-convert-binary "/usr/bin/convert"
"Path to the Imagemagick convert command."
:group 'ledger-matching)
(defcustom ledger-matching-scale 50
"Scaling parameter to Imagemagick's convert to resize an image for viewing."
:group 'ledger-matching)
(defcustom ledger-matching-rotation 0
"Rotation parameter to Imagemagick's convert to rotate an image for viewing. Images on disk should always be upright for reading."
:group 'ledger-matching)
(defconst ledger-matching-image-buffer "*Receipt*"
"Buffer name we load images into. Created if it doesn't exist, and persists across image loads.")
(defvar ledger-matching-project "Internal"
"The directory appended to the destination for the project code where receipts will be stored.")
(defvar ledger-matching-image-offset 0
"The index of the current file from the SORTED source directory contents.")
(defvar ledger-matching-image-name nil
"The filename only of the current image.")
(defun ledger-matching-display-image (image-filename)
"Resize the image and load it into our viewing buffer."
;; Create our viewing buffer if needed, and set it. Do NOT switch,
;; this buffer isn't the primary. Let the user leave it where they
;; place it.
(unless (get-buffer ledger-matching-image-buffer)
(get-buffer-create ledger-matching-image-buffer))
(set-buffer ledger-matching-image-buffer)
(erase-buffer)
(goto-char (point-min))
(insert-string image-filename "\n")
;; Convert the source to the temporary dest applying resizing and rotation
(let* ((source (expand-file-name image-filename ledger-matching-sourcedir))
(dest (make-temp-file "ledger-matching-" nil ".jpg"))
(result (call-process ledger-matching-convert-binary nil (get-buffer "*Messages*") nil
source
"-scale" (concat (number-to-string ledger-matching-scale) "%")
"-rotate" (number-to-string ledger-matching-rotation)
dest)))
(if (/= 0 result)
;; Bomb out if the convert fails
(message "Error running convert, see *Messages* buffer for details.")
;; Insert scaled image into the viewing buffer, replacing
;; current contents Temp buffer is to force sync reading into
;; memory of the jpeg due to async race condition with display
;; and file deletion
(let ((image (create-image (with-temp-buffer
(insert-file-contents-literally dest)
(string-as-unibyte (buffer-string)))
'jpeg t)))
(insert-image image)
(goto-char (point-min))
;; Redisplay is required to prevent a race condition between displaying the image and the deletion. Apparently its async.
;; Either redisplay or the above string method work, both together can't hurt.
(redisplay)
))
;; Delete our temporary file
(delete-file dest)))
(defun ledger-matching-update-current-image ()
"Grab the image from the source directory by offset and display"
(let* ((file-listing (directory-files ledger-matching-sourcedir nil "\.jpg$" nil))
(len (safe-length file-listing)))
;; Ensure our offset doesn't exceed the file list
(cond ((= len 0)
(message "No files found in source directory."))
((< len 0)
(message "Error, list of files should never be negative. Epic fail."))
((>= ledger-matching-image-offset len)
(message "Hit end of list. Last image.")
(setq ledger-matching-image-offset (1- len)))
((< ledger-matching-image-offset 0)
(message "Beginning of list. First image.")
(setq ledger-matching-image-offset 0)))
;; Get the name for the offset
(setq ledger-matching-image-name (nth ledger-matching-image-offset file-listing))
(ledger-matching-display-image ledger-matching-image-name)))
(defun ledger-matching-image-offset-adjust (amount)
"Incr/decr the offset and update the receipt buffer."
(setq ledger-matching-image-offset (+ ledger-matching-image-offset amount))
(ledger-matching-update-current-image))
(defun ledger-receipt-matching ()
"Open the receipt buffer and start with the first image."
(interactive)
(setq ledger-matching-image-offset 0)
(ledger-matching-update-current-image))
(defun ledger-matching-tie-receipt-to-txn ()
(interactive)
(save-selected-window
(ledger-report-visit-source)
;; Assumes we're in a narrowed buffer with ONLY this txn
(backward-paragraph)
(beginning-of-line)
;; ;; Update the ER and Project while I'm there
;; (save-excursion
;; (search-forward "; ER:")
;; (kill-line nil)
;; (insert " " *ledger-expense-shortcut-ER*))
;; Just do the project for now.
(save-excursion
(search-forward "; PROJECT:")
(kill-line nil)
(insert " " *ledger-expense-shortcut-Proj*))
;; Goto the receipt line, unless their isn't one then add one
(unless (search-forward "RECEIPT:" nil t)
;; Still at date line if that failed
(next-line)
(newline)
(insert-string " ; RECEIPT:"))
;; Point immediately after : on tag
;; Check for existing jpg file
(if (search-forward ".jpg" (line-end-position) t)
;; if present make it a comma delimited list
(insert-string ",")
;; otherwise just add a space to pad
(insert-string " "))
;; Add our relative filename as the value of the RECEIPT tag
(insert-string (concat ledger-matching-relative-receipt-dir "/"
ledger-matching-project "/"
ledger-matching-image-name))
;; Create the destination project dir if it doesn't exist.
(let ((full-destination (concat ledger-matching-destdir "/" ledger-matching-project )))
(unless (file-accessible-directory-p full-destination)
(make-directory full-destination t)))
;; Rename the file from the source directory to its permanent home
(rename-file (concat ledger-matching-sourcedir "/"
ledger-matching-image-name)
(concat ledger-matching-destdir "/"
ledger-matching-project "/"
ledger-matching-image-name))
;; Update the receipt screen
(ledger-matching-update-current-image)
(message "Filed %s to project %s" ledger-matching-image-name ledger-matching-project)))
(defun ledger-receipt-skip ()
"Move the current image to the Skip directory because its not relevant."
(rename-file (concat ledger-matching-sourcedir "/"
ledger-matching-image-name)
(concat ledger-matching-sourcedir "/Skip/"
ledger-matching-image-name))
;; Update the receipt screen at the same offset
(ledger-matching-update-current-image))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Items below are speed entry macros, and should eventually migrate to their own file.
(defvar *ledger-expense-shortcut-ER*
"Current expense report number, just last four digits (ie: 1234 results in AISER1234).")
(defvar *ledger-expense-shortcut-split-ER*
"Split (ie: internal) expense report number, just last four digits (ie: 1234 results in AISER1234).")
(defvar *ledger-expense-shortcut-Proj* ""
"Current export report project code (ie: AGIL1292)")
(defun ledger-expense-shortcut-ER-format-specifier () *ledger-expense-shortcut-ER*)
(defun ledger-expense-shortcut-Project-format-specifier () *ledger-expense-shortcut-Proj*)
(defun ledger-expense-shortcut-setup (ER Split Proj)
"Sets the variables expanded into the transaction."
(interactive "MER Number (ER or IN and 4 digit number only): \nMSplit ER Number (ER or IN and 4 digit number only): \nMProject: ")
(setq *ledger-expense-shortcut-ER*
(concatenate 'string "AIS" ER))
(setq *ledger-expense-shortcut-split-ER*
(concatenate 'string "AIS" Split))
(setq *ledger-expense-shortcut-Proj* Proj)
(setq ledger-matching-project Proj)
(message "Set Proj to %s and ER to %s, split to %s"
*ledger-expense-shortcut-Proj*
*ledger-expense-shortcut-ER*
*ledger-expense-shortcut-split-ER*))
(defun ledger-expense-shortcut ()
"Updates the ER and Project metadata with the current values of the shortcut variables."
(interactive)
(when (eq major-mode 'ledger-mode)
(if (or (eql *ledger-expense-shortcut-ER* "")
(eql *ledger-expense-shortcut-Proj* ""))
(message "Run ledger-expense-shortcut-setup first.")
(save-excursion
(search-forward "; ER:")
(kill-line nil)
(insert " " *ledger-expense-shortcut-ER*))
(save-excursion
(search-forward "; PROJECT:")
(kill-line nil)
(insert " " *ledger-expense-shortcut-Proj*)))))
(defun ledger-expense-split ()
"Splits the current transaction between internal and projects."
(interactive)
(when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
(save-excursion
(end-of-line)
(re-search-backward "^[0-9]\\{4\\}/")
(re-search-forward "^ +Dest:Projects")
(move-beginning-of-line nil)
(let ((begin (point))
(end (re-search-forward "^$")))
(goto-char end)
(insert (buffer-substring begin end))
(goto-char end)
(re-search-forward "^ Dest:Projects")
(replace-match " Dest:Internal")
(re-search-forward "; ER: +[A-Za-z0-9]+")
(replace-match (concat "; ER: " *ledger-expense-shortcut-split-ER*) t)
(when (re-search-forward "; CATEGORY: Meals" (save-excursion (re-search-forward "^$")) t)
(replace-match "; CATEGORY: Travel" t))))
(re-search-backward "^[0-9]\\{4\\}/")
(re-search-forward "^ +Dest:Projects")
(insert-string " $") ))
(defun ledger-expense-internal ()
"Makes the expense an internal one."
(interactive)
(when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
(save-excursion
(end-of-line)
(re-search-backward "^[0-9]\\{4\\}/")
(let ((begin (point))
(end (save-excursion (re-search-forward "^$"))))
(when (re-search-forward "^ Dest:Projects" end t)
(replace-match " Dest:Internal") )
(when (re-search-forward "; CATEGORY: Meals" (save-excursion (re-search-forward "^$")) t)
(replace-match "; CATEGORY: Travel" t))))))
(defun ledger-expense-personal ()
"Makes the expense an personal one, eliminating metadata and receipts."
(interactive)
(when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
(save-excursion
(end-of-line)
(re-search-backward "^[0-9]\\{4\\}/")
(let ((begin (point))
(end (save-excursion (re-search-forward "^$"))))
(when (re-search-forward "^ Dest:Projects" end t)
(replace-match " Other:Personal"))
(goto-char begin)
(save-excursion
(when (re-search-forward "^ +; ER:" end t)
(beginning-of-line)
(kill-line 1)))
(save-excursion
(when (re-search-forward "^ +; PROJECT:" end t)
(beginning-of-line)
(kill-line 1)))
(save-excursion
(when (re-search-forward "^ +; CATEGORY:" end t)
(beginning-of-line)
(kill-line 1)))
(save-excursion
(when (re-search-forward "^ +; RECEIPT:" end t)
(beginning-of-line)
(kill-line 1)))
(ledger-toggle-current-entry)))))
(defun ledger-expense-show-receipt ()
"Uses the Receipt buffer to show the receipt of the txn we're on."
(when (eq major-mode 'ledger-mode) ; I made this local now, should only trigger in ldg-mode
(save-excursion
(end-of-line)
(re-search-backward "^[0-9]\\{4\\}/")
(let ((begin (point))
(end (save-excursion (re-search-forward "^$"))))
(save-excursion
(when (re-search-forward "^\\( +; RECEIPT: +\\)\\([^,]+?.jpg\\).*$" end t)
(ledger-matching-display-image
(concat "/home/adamsrl/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell/"
(match-string 2))) ))))))
(provide 'ledger-matching)

View file

@ -0,0 +1,90 @@
# Environment for ledger expenses
[ $(whoami) == "adamsrl" ] \
&& export LEDGER_HOME="/home/adamsrl/AdamsInfoServ/BusinessDocuments/Ledger/AdamsRussell" \
|| export LEDGER_HOME="/home/Heather/AdamsRussell"
[ $(hostname) == "cardamom" ] \
&& export LEDGER_BIN="${LEDGER_HOME}/ledger" \
|| export LEDGER_BIN="${LEDGER_HOME}/ledger.exe"
[ $(whoami) == "andersonll" ] \
&& export LEDGER_HOME="/home/andersonll/AdamsInfoServ/Expenses" \
&& export LEDGER_BIN="${LEDGER_HOME}/ledger"
# Common reports
alias ledger='${LEDGER_BIN} -f "${LEDGER_HOME}/.ledger" -VE '
alias ERSummary='ledger --pivot ER bal | egrep "AIS(ER|IN)[0-9]+|Unassigned"'
function ERTxns() {
[ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
ledger reg "%ER=${1}"
}
function ERCategorySummary() {
[ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
ledger bal --pivot CATEGORY "%ER=${1}"
}
function ERMealSummary() {
[ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
ledger reg "%ER=${1}" and %CATEGORY=Meals -D
}
function ERMeals() {
[ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
ledger reg "%ER=${1}" and %CATEGORY=Meals
}
function ERUncleared() {
[ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
ledger reg "%ER=${1}" -U
}
function ERMissingReceipts() {
[ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
ledger reg "%ER=${1}" and not %RECEIPT
}
function ERVerify() {
[ -z "$1" ] && echo "Please specify an ER number (ie: AISER0042)." && return
echo "========== Uncleared txns below =========="
ERUncleared "$1"
echo "========== Missing receipts below (miles and stubs ok) =========="
ERMissingReceipts "$1"
echo "========== Category Summary (airline? mileage? car? hotel? =========="
ERCategorySummary "$1"
echo "========== Meal summary (<\$50 / day unless otherwise specified) =========="
ERMealSummary "$1"
echo "========== Account Verification (Internal vs Project ER should be ONE type) =========="
echo $1 | grep AISIN >/dev/null 2>&1 \
|| { ledger reg "%ER=${1}" | grep Dest:Internal ; } \
&& { ledger reg "%ER=${1}" | grep Dest:Projects ; }
echo "========== Project Verification (only one project code should be listed) =========="
ledger print "%ER=${1}" | grep PROJECT | sort -u
echo "========== Receipts missing =========="
ledger print "%ER=${1}" | grep -h '; RECEIPT: ' \
| sed 's,\W*; RECEIPT: ,,g' \
| tr , '\n' \
| sort -u \
| while read X ; do
[ -f "${LEDGER_HOME}/${X}" ] \
|| echo XX $X
done
}
function ERListing() {
ledger reg Stub --register-format="%(tag('ER')) %(tag('NOTE'))\n" | sort -u
}
function ERQueue() {
ledger reg %ER=Unassigned --prepend-format="%(filename) "
}

View file

@ -2325,7 +2325,9 @@ doing it.
Journal files are simple free text files easily modified by any text
editor. A special mode for EMACS is included with the source
distribution.
distribution. This mode provides syntax highlighting, a reconcile mode
and a report mode. This makes it quote possible to use ledger without
ever leaving EMACS.
@cindex EMACS .emacs file
@ -2335,7 +2337,7 @@ Add the following line to your @file{.emacs} (or equivalent,
(load "ldg-new")
@end smallexample
Copy the several lisp files from the source lisp directory your your
Copy the several lisp files (@file{ldg-*.el}) from the source lisp directory your your
@file{site-lisp} directory, or add the ledger lisp source directory to
your EMACS load path by adding:
@smallexample
@ -2352,7 +2354,8 @@ To enter ledger-mode on a new file, type @command{M-x ledger-mode}.
Once you have loaded a Journal file into EMACS, you have several
commands available to make entering, clearing and reconciling
transactions and producing reports:
transactions and producing reports (these are also available from the
menu if you can't remember the key combinations):
@cindex EMACS commands
@table @code
@ -2431,10 +2434,11 @@ will be interpreted as a new account by ledger.
@code{C-c C-a} will run the @code{ledger entry} command (@pxref{entry
and xact}) from within EMACS. When typed, the mini-buffer will appear
with the current year and month, waiting for you to enter the day and
the payee. Ledger will generate a new entry based on the most recent
entry for that payee, using the amount and accounts from that
transaction. If you have a new amount simply type the amount after the
payee. For example, if your journal contains an entry
the payee, and optionally, a commoditized amount. Ledger will generate
a new entry based on the most recent entry for that payee, using the
amount and accounts from that transaction. If you have a new amount
simply type the amount after the payee. For example, if your journal
contains an entry
@smallexample
2011/11/25 Viva Italiano
Expenses:Food $12.45
@ -2454,7 +2458,10 @@ Entry: 2011/11/28 viva food 34 tip 7 <enter>
Liabilities:MasterCard
@end smallexample
@noindent Notice that the entry will appear at the correct place in the journal
ordered by date, not necessarily at the bottom of the file.
ordered by date, not at the bottom of the file. If you need to include
spaces in the payee name, then surrond the name of the payee with double
quotes, otherwise ledger will interpret the second part of the name as
an account.
@node Clearing Transactions, , Automagically Adding new entries, Working with entries
@subsubsection Clearing Transactions and Postings
@cindex clearing transactions in EMACS
@ -2483,21 +2490,46 @@ toggled.
@node Reconciling accounts, Generating Reports, Working with entries, Using EMACS
@subsection Reconciling accounts
In the reconcile buffer, use SPACE to toggle the cleared status of a
transaction, C-x C-s to save changes (to the ledger file as well).
Enter the reconcile mode using the menu entry, or @code{C-c C-r}. Emacs
will prompt you for an account name, then display a second buffer with
all of the uncleared transactions. The reconcile buffer has several functions:
@table @code
@item SPACE
toggles the cleared status of a transaction, and show cleared balance inthe minibuffer
@item RETURN
moves the cursor to that transaction in the ledger.
@item C-x C-s
to save changes (to the ledger file as well).
@item q
quite the reconcile mode
@item n p
next line or previous line
@item A
add entry
@item D
delete entry
@item C-l
refresh display
@end table
@node Generating Reports, , Reconciling accounts, Using EMACS
@subsection Generating Reports
The ledger reports command asks the user to select a report to run then
creates a report buffer containing the results of running the associated
command line. Its' behavior is modified by a prefix argument which,
when given, causes the generated command line that will be used to
create the report to be presented for editing before the report is
actually run. Arbitrary unnamed command lines can be run by specifying
an empty name for the report. The command line used can later be named
and saved for future use as a named report from the generated reports
buffer.
command line. This allows you to run frequently used reports without
retyping the command line, or writing shell scripts for simple one line
commands.
To generate a report, select the @code{Run Reports} menu, or type
@code{C-c C-o C-r}. Emacs will prompt for a report name. If it
recognizes the name it will run the report again. If it is a new name,
or blank it will respond by giving you an example command line to edit.
Hitting return willrun the report and present it in a new buffer.
If you have given it a new name, then @code{s} will save the report for
future use.
In a report buffer, the following keys are available:
@table @code
@ -2542,7 +2574,7 @@ kill the report buffer
* Lot dates::
* Lot notes::
* Lot value expressions::
* Automated Transactions::
* Automated Transactions::
@end menu
@node Basic format, Eliding amounts, Transactions , Transactions
@ -4744,6 +4776,7 @@ database files.
@menu
* accounts::
* commodities::
* tags::
* entry and xact::
* payees::
@end menu
@ -4753,15 +4786,27 @@ database files.
The @command{accounts} reports all of the accounts in the journal.
Following the command with a regular expression will limit the output to
accounts matching the regex.
accounts matching the regex. The output is sorted by name. Using the
@code{--count} option will tell you haw many entries use each account.
@node commodities, entry and xact, accounts, Reports about your Journals
@node commodities, tags, accounts, Reports about your Journals
@subsection @command{commodities}
Report all commodities present in the journals under consideration.
Report all commodities present in the journals under consideration. The
output is sorted by name. Using the @code{--count} option will tell
you haw many entries use each commodity.
@node tags, entry and xact, commodities, Reports about your Journals
@subsection @command{tags}
The @command{tags} reports all of the tags in the journal. The output
is sorted by name. Using the @code{--count} option will tell you haw
many entries use each tag. Using the @code{--values} option will report
the values used by each tag.
@node entry and xact, payees, commodities, Reports about your Journals
@node entry and xact, payees, tags, Reports about your Journals
@subsection @command{draft}, @command{entry} and @command{xact}
The @code{draft}, @code{entry} and @command{xact} commands simplify the

View file

@ -246,72 +246,54 @@ the default."
(ledger-reports-custom-save))
report-cmd))
(defvar ledger-report-patch-alist nil)
(defun ledger-report-patch-reports (buf)
(when ledger-report-patch-alist
(let ((entry (assoc (expand-file-name (buffer-file-name buf))
ledger-report-patch-alist)))
(when entry
(dolist (b (cdr entry))
(if (buffer-live-p b)
(with-current-buffer b
(save-excursion
(goto-char (point-min))
(while (not (eobp))
(let ((record (get-text-property (point) 'ledger-source)))
(if (and record (not (markerp (cdr record))))
(setcdr record (with-current-buffer buf
(save-excursion
(goto-char (point-min))
(forward-line (cdr record))
(point-marker))))))
(forward-line 1))))))))))
(defun ledger-do-report (cmd)
"Run a report command line."
(goto-char (point-min))
(insert (format "Report: %s\n" ledger-report-name)
(format "Command: %s\n" cmd)
(make-string (- (window-width) 1) ?=)
"\n")
(let ((register-report (string-match " reg\\(ister\\)? " cmd))
"\n\n")
(let ((data-pos (point))
(register-report (string-match " reg\\(ister\\)? " cmd))
files-in-report)
(shell-command
(if register-report
(concat cmd " --prepend-format='%(filename):%(beg_line):'")
cmd) t nil)
(when register-report
(goto-char (point-min))
(goto-char data-pos)
(while (re-search-forward "^\\([^:]+\\)?:\\([0-9]+\\)?:" nil t)
(let ((file (match-string 1))
(line (string-to-number (match-string 2))))
(line (string-to-number (match-string 2))))
(delete-region (match-beginning 0) (match-end 0))
(set-text-properties (line-beginning-position) (line-end-position)
(list 'ledger-source (cons file line)))
(let* ((fullpath (expand-file-name file))
(entry (assoc fullpath ledger-report-patch-alist)))
(if entry
(nconc (cdr entry) (list (current-buffer)))
(push (cons (expand-file-name file)
(list (current-buffer)))
ledger-report-patch-alist))
(add-to-list 'files-in-report fullpath)))
(list 'ledger-source (cons file (save-window-excursion
(save-excursion
(find-file file)
(widen)
(goto-char (point-min))
(forward-line (1- line))
(point-marker))))))
(end-of-line))))
(goto-char data-pos)))
(dolist (path files-in-report)
(let ((buf (get-file-buffer path)))
(if (and buf (buffer-live-p buf))
(ledger-report-patch-reports buf))))))))
(defun ledger-report-visit-source ()
(interactive)
(let ((prop (get-text-property (point) 'ledger-source)))
(destructuring-bind (file . line-or-marker) prop
(find-file-other-window file)
(widen)
(if (markerp line-or-marker)
(goto-char line-or-marker)
(goto-char (point-min))
(forward-line (1- line-or-marker))))))
(forward-line (1- line-or-marker))
(re-search-backward "^[0-9]+")
(beginning-of-line)
(let ((start-of-txn (point)))
(forward-paragraph)
(narrow-to-region start-of-txn (point))
(backward-paragraph))))))
(defun ledger-report-goto ()
"Goto the ledger report buffer."