gnucash2ledger 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/CHANGELOG +6 -0
  2. data/README +30 -0
  3. data/bin/gnucash2ledger +4 -0
  4. data/lib/input.rb +115 -0
  5. metadata +139 -0
@@ -0,0 +1,6 @@
1
+ = Changelog
2
+
3
+ == 0.1 Initial release
4
+ * Normal transactions
5
+ * Reconciled transactions (if any split in gnucash is marked as reconciled, the entry in ledger is marked as cleared)
6
+ * Commodities (only the initial price is used, doesn't convert price history yet)
data/README ADDED
@@ -0,0 +1,30 @@
1
+ = gnucash2ledger
2
+
3
+ A command-line script that converts GnuCash v2.2.9 data files to human readable ledger format. Ledger v2 did this natively, but it has been removed from the v3 branch with the intention of replacing it with a script just like this one. This project is bridging the gap until such a script exists.
4
+
5
+ == Quickstart
6
+
7
+ gem install gnucash2ledger
8
+ cat your/gnucash/datafile | gunzip | gnucash2ledger > new-ledger.dat
9
+ ledger -f new-ledger.dat bal
10
+
11
+ == Developing
12
+
13
+ You'll need git and bundler. You probably already have them eh.
14
+
15
+ git clone git://github.com/xaviershay/gnucash2ledger.git
16
+ cd gnucash2ledger
17
+ bundle install
18
+ bundle exec rake
19
+
20
+ == Status
21
+
22
+ Works for me on my Snow Leopard mac.
23
+
24
+ * Normal transactions
25
+ * Reconciled transactions (if any split in gnucash is marked as reconciled, the entry in ledger is marked as cleared)
26
+ * Commodities (only the initial price is used, doesn't convert price history yet)
27
+
28
+ == References
29
+ * http://www.gnucash.org/
30
+ * http://wiki.github.com/jwiegley/ledger/
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../lib/input'
3
+
4
+ puts G2L::Input.new(ARGF.read).to_ledger
@@ -0,0 +1,115 @@
1
+ require 'nokogiri'
2
+ require 'date'
3
+ require 'active_support/core_ext'
4
+
5
+ module G2L
6
+ class Input
7
+ attr_accessor :input
8
+
9
+ def initialize(input)
10
+ self.input = input
11
+ end
12
+
13
+ def to_ledger
14
+ doc = Nokogiri::XML(input)
15
+
16
+ accounts = doc.xpath('//gnc:account').inject({}) do |all, account|
17
+ id = account.xpath("act:id")[0].text
18
+ parent = account.xpath("act:parent")[0]
19
+
20
+ all.update(id => {
21
+ :name => account.xpath("act:name")[0].text,
22
+ :commodity => account.xpath("act:commodity/cmdty:id")[0].try(:text),
23
+ :parent => parent ? parent.text : nil
24
+ })
25
+ end
26
+
27
+ accounts = resolve_accounts(accounts)
28
+
29
+ transactions = doc.xpath('//gnc:transaction').map do |transaction|
30
+ {
31
+ :date => Date.parse(transaction.xpath("trn:date-posted/ts:date")[0].text),
32
+ :description => transaction.xpath("trn:description")[0].text,
33
+ :splits => transaction.xpath("trn:splits/trn:split").map {|split| {
34
+ :account => split.xpath("split:account")[0].text,
35
+ :action => split.xpath("split:action")[0].try(:text),
36
+ :value => parse_value(split.xpath("split:value")[0]),
37
+ :quantity => parse_value(split.xpath("split:quantity")[0]),
38
+ :reconciled => split.xpath("split:reconciled-state")[0].text == 'y'
39
+ }}
40
+ }
41
+ end
42
+
43
+ # Generate output
44
+ transactions.sort_by {|x| x[:date] }.map do |tx|
45
+ (["%s %s%s" % [
46
+ tx[:date].strftime("%Y/%m/%d"),
47
+ tx[:splits].any? {|y| y[:reconciled] } ? '* ' : '',
48
+ tx[:description]
49
+ ]] + tx[:splits].map {|split|
50
+ account = accounts[split[:account]]
51
+
52
+ value = if split[:action]
53
+ "%i %s" % [split[:quantity].to_i, account[:commodity]]
54
+ else
55
+ '$' + split[:quantity].to_s
56
+ end
57
+ " %-44s%s" % [account[:name], value]
58
+ }).join("\n")
59
+ end.join("\n\n")
60
+ end
61
+
62
+ private
63
+
64
+ # Shelved
65
+ def to_xml
66
+ builder = Nokogiri::XML::Builder.new do |xml|
67
+ xml.ledger('xmlns:tr' => '', 'xmlns:en' => '') {
68
+ transactions.each do |tx|
69
+ xml.entry {
70
+ xml['en'].date(tx[:date].strftime("%Y/%m/%d"))
71
+ xml['en'].payee(tx[:description])
72
+ xml['en'].cleared if tx[:splits].any? {|x| x[:reconciled]}
73
+ xml['en'].transactions {
74
+ tx[:splits].each do |split|
75
+ xml.transaction {
76
+ xml['tr'].account accounts[split[:account]][:name]
77
+ xml.value(:type => 'amount') {
78
+ xml.commodity(:flags => 'PT') {
79
+ xml.symbol '$'
80
+ }
81
+ xml.quantity split[:value]
82
+ }
83
+ }
84
+ end
85
+ }
86
+ }
87
+ end
88
+ }
89
+ end
90
+ builder.to_xml
91
+ end
92
+
93
+ def parse_value(value)
94
+ if value
95
+ value.text.split("/").map(&:to_f).inject {|a, b| a / b }
96
+ else
97
+ 0
98
+ end
99
+ end
100
+
101
+ def resolve_accounts(accounts)
102
+ resolve_name = lambda do |id|
103
+ if accounts[id][:parent]
104
+ [resolve_name[accounts[id][:parent]], accounts[id][:name]].flatten.join(':')
105
+ else
106
+ []
107
+ end
108
+ end
109
+
110
+ accounts.inject({}) do |a, (id, account)|
111
+ a.update(id => account.merge(:name => resolve_name[id]))
112
+ end
113
+ end
114
+ end
115
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gnucash2ledger
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - Xavier Shay
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-05-25 00:00:00 +10:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ version_requirements: &id001 !ruby/object:Gem::Requirement
22
+ none: false
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ hash: 3
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ requirement: *id001
31
+ name: rake
32
+ type: :development
33
+ prerelease: false
34
+ - !ruby/object:Gem::Dependency
35
+ version_requirements: &id002 !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ hash: 3
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ requirement: *id002
45
+ name: rspec
46
+ type: :development
47
+ prerelease: false
48
+ - !ruby/object:Gem::Dependency
49
+ version_requirements: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ hash: 3
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ requirement: *id003
59
+ name: builder
60
+ type: :runtime
61
+ prerelease: false
62
+ - !ruby/object:Gem::Dependency
63
+ version_requirements: &id004 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ hash: 3
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ requirement: *id004
73
+ name: activesupport
74
+ type: :runtime
75
+ prerelease: false
76
+ - !ruby/object:Gem::Dependency
77
+ version_requirements: &id005 !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ hash: 3
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ requirement: *id005
87
+ name: nokogiri
88
+ type: :runtime
89
+ prerelease: false
90
+ description:
91
+ email:
92
+ - contact@rhnh.net
93
+ executables:
94
+ - gnucash2ledger
95
+ extensions: []
96
+
97
+ extra_rdoc_files: []
98
+
99
+ files:
100
+ - bin/gnucash2ledger
101
+ - lib/input.rb
102
+ - README
103
+ - CHANGELOG
104
+ has_rdoc: true
105
+ homepage: http://github.com/xaviershay/gnucash2ledger
106
+ licenses: []
107
+
108
+ post_install_message:
109
+ rdoc_options: []
110
+
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ hash: 3
119
+ segments:
120
+ - 0
121
+ version: "0"
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ hash: 3
128
+ segments:
129
+ - 0
130
+ version: "0"
131
+ requirements: []
132
+
133
+ rubyforge_project:
134
+ rubygems_version: 1.3.7
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: Convert GnuCash files to a format supported by the ledger command line application
138
+ test_files: []
139
+