Skip to content

Instantly share code, notes, and snippets.

@lifepillar
Created November 3, 2012 22:29
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lifepillar/4009128 to your computer and use it in GitHub Desktop.
Save lifepillar/4009128 to your computer and use it in GitHub Desktop.
Rake tasks for simple personal reporting in Ledger
# -*- 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
@lifepillar
Copy link
Author

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment