public
Last active

Rake tasks for simple personal reporting in Ledger

  • Download Gist
Rakefile
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
# -*- coding: utf-8 -*-
# 2012-11-12
require 'rubygems' if RUBY_VERSION < "1.9"
require 'shellwords'
 
DEFAULT_CURRENCY = '$'
ASSETS = '^asset'
LIABILITIES = '^liab'
CREDIT_CARD = 'liabilities:credit card'
INCOME = '^income'
EXPENSES = '^expenses'
PAYABLE = 'accounts payable'
RECEIVABLE = 'accounts receivable'
TAXES = 'taxes'
LEDGER_BIN = 'ledger'
# INIT_FILE = '' # Leave blank to use default location
INIT_FILE = File.join(File.dirname(__FILE__), 'ledgerrc')
LEDGER_OPTIONS = [] # Common command-line options
LEDGER_OPTIONS << '--init-file' << INIT_FILE if INIT_FILE
#LEDGER_OPTIONS << '-f' << 'path/to/my-journal'
#LEDGER_OPTIONS << '--pedantic'
#LEDGER_OPTIONS << '--check-payees'
 
#####################################################################
 
# Builds the command-line string from the given arguments.
# Does not really execute the command.
def ledger_cmd *args
cmd = LEDGER_BIN + ' '
cmd << (LEDGER_OPTIONS + args).map { |arg| arg.to_s }.shelljoin
return cmd
end
 
# Executes Ledger with the given arguments. Does not return anything.
def ledger *args
# verbose is true when rake is run with -v
sh ledger_cmd(*args), :verbose => (true == verbose)
end
 
# Executes Ledger with the given arguments and returns the command output.
def ledger_output *args
cmd = ledger_cmd(*args)
puts cmd if (true == verbose)
%x|#{cmd}|
end
 
def average periodicity, account, period = 'this year'
ledger '-p', period, '--'+periodicity, '-A', '--collapse', 'reg', account,
'-X', DEFAULT_CURRENCY, '-O'
end
 
def networth periodicity, start_date
ledger '--real', '--'+periodicity, '--collapse', '-d', "d>=[#{start_date}]",
'-F', '%10(date) %14(display_total)\n', 'reg', ASSETS, LIABILITIES,
'-X', DEFAULT_CURRENCY
end
 
def expenses periodicity, period = 'this month'
ledger '--'+periodicity, '--period-sort', '(amount)', 'reg', EXPENSES, 'and',
'not', TAXES, '-p', period, '-X', DEFAULT_CURRENCY
puts "Related accounts:\n\n"
ledger '-r', '--'+periodicity, '--period-sort', '(amount)', 'reg',
EXPENSES, 'and', 'not', TAXES, '-p', period, '-X', DEFAULT_CURRENCY
end
 
def color n, t
return t unless $stdout.tty?
"\033[#{n}m" + t + "\033[0m"
end
 
def red t; color 31, t; end
def green t; color 32, t; end
def cyan t; color 36, t; end
 
#########
# Tasks #
#########
 
desc "Invoke 'rake rbal[#{DEFAULT_CURRENCY}]'."
task :default do
Rake::Task[:rbal].invoke(DEFAULT_CURRENCY)
end
 
desc 'Balance report.'
task :bal, [:currency] do |t,args|
args.with_defaults(:currency => ENV['currency'])
largs = ['cleared', ASSETS, LIABILITIES]
largs << '-X' << args.currency if args.currency
ledger *largs
end
 
desc 'Budget expense report for the current year.'
task :budget, [:depth] do |t,args|
args.with_defaults(:depth => ENV['depth'] || nil)
opts = ['-p', 'this year', '-X', DEFAULT_CURRENCY]
opts << '--depth' << args.depth unless args.depth.nil?
ledger 'budget', EXPENSES, PAYABLE, *opts
puts '=' * 44
ledger 'budget', INCOME, RECEIVABLE, *opts
end
 
desc 'Debit/credit report for credit card account (by default, since two months ago).'
task :cc, [:since] do |t,args|
args.with_defaults(:since => ENV['since'] || '2 months ago')
ledger 'register', '--sort', 'date', '--dc', CREDIT_CARD, '-d', "d>=[#{args.since}]"
end
 
desc 'Net income (gross income minus taxes) for the given period (default: this month).'
task :net, [:period] do |t,args|
args.with_defaults(:period => ENV['period'] || 'this month')
ledger 'balance', INCOME, TAXES, '-p', args.period, '-X', DEFAULT_CURRENCY
puts "=" * 20
ledger 'balance', EXPENSES, 'and', 'not', TAXES, '-p', args.period,
'--collapse', '-X', DEFAULT_CURRENCY
end
 
desc 'Like \'rake bal\', but without virtual transactions.'
task :rbal, [:currency] do |t,args|
args.with_defaults(:currency => ENV['currency'])
largs = ['cleared', '--real', ASSETS, LIABILITIES]
largs << '-X' << args.currency if args.currency
ledger *largs
end
 
desc 'Some statistics about your ledger.'
task :stat do
ledger 'stats'
end
 
desc "Total income and expenses for the specified period (default: 'this month')."
task :tot, [:period] do |t,args|
args.with_defaults(:period => ENV['period'] || 'this month')
opts = ['--collapse', '-p', args.period, '-X', DEFAULT_CURRENCY]
ledger 'balance', INCOME, EXPENSES, *opts
# Compute savings percentage
opts << '-F' << '%(quantity(display_total))'
income = (ledger_output 'balance', INCOME, *opts).to_f
expenses = (ledger_output 'balance', EXPENSES, *opts).to_f
total = (income + expenses) / income
if total.nan? or (not total.infinite?.nil?)
puts cyan 'Savings rate cannot be determined'
elsif total < 0.0
puts red('Savings rate: ' + ("%.2f" % (100*total)) + '%')
else
puts green('Savings rate: +' + ("%.2f" % (100*total)) + '%')
end
end
 
namespace :monthly do
 
desc 'Monthly net worth (by default, since the beginning of this year).'
task :networth, [:since] do |t,args|
args.with_defaults(:since => ENV['since'] || `date '+%Y'`.chomp!)
networth 'monthly', args.since
end
 
desc "Monthly average for an account during this year (default account: #{EXPENSES})."
task :avg, [:account] do |t,args|
args.with_defaults(:account => ENV['account'] || EXPENSES)
average 'monthly', args.account
end
 
desc "Monthly expenses, sorted by amount (default period: 'this month'). Taxes are excluded."
task :expenses, [:period] do |t,args|
args.with_defaults(:period => ENV['period'] || 'this month')
expenses 'monthly', args.period
end
 
end # namespace :monthly
 
namespace :weekly do
 
desc 'Weekly net worth (by default, since the beginning of this year).'
task :networth, [:since] do |t,args|
args.with_defaults(:since => ENV['since'] || `date '+%Y'`.chomp!)
networth 'weekly', args.since
end
 
desc "Weekly average for an account during this year (default account: #{EXPENSES})."
task :avg, [:account] do |t,args|
args.with_defaults(:account => ENV['account'] || EXPENSES)
average 'weekly', args.account
end
 
desc 'Weekly expenses, sorted by amount (default period: this week). Taxes are excluded.'
task :expenses, [:period] do |t,args|
args.with_defaults(:period => ENV['period'] || 'this week')
expenses 'weekly', args.period
end
 
end # namespace :weekly

Do not forget to adjust the values of the constants at the top of the gist!

Available reports:

  • Balance reports (with real or virtual accounts, with or without conversion to a given currency).
  • Budget reports for expenses and income.
  • Debit/credit report for credit card account.
  • Savings rate report for a given period.
  • Weekly/monthly expenses in a given period.
  • Weekly/monthly average for the given account(s).
  • Weekly/monthly net worth.
  • Net income and expenses (i.e., gross income/expenses minus taxes).

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.