Created
November 3, 2012 22:29
-
-
Save lifepillar/4009128 to your computer and use it in GitHub Desktop.
Rake tasks for simple personal reporting in Ledger
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Do not forget to adjust the values of the constants at the top of the gist!
Available reports: