total_recall 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 162939627043b1aea142c3c8a6dedc9c0557801a
4
- data.tar.gz: fd307f5b53406336a46225d36b9138d11d228bfd
3
+ metadata.gz: 4f485de3326ba5aa633added9df0493ab450c74d
4
+ data.tar.gz: 592cba4e119cf53c307c9734b6d518bb96b90ac5
5
5
  SHA512:
6
- metadata.gz: a78dcb6fa9e0d5d533dcc8bef4d34769ced4f8809c8f074158c4a3c3de2cd45a53cacd2364d0e0b941330cc7fccf35c8f443106eada6f6dfa7c2b48cc555db3f
7
- data.tar.gz: 1ac9db0b80f7c7e08be72ccadb399d1eb209b56bbfa40bafcc0fa3c7037c691e3e329c6a9e4147bcda57b6893058a8b2f8a1b10459ebb31ca7fb428ec701a80f
6
+ metadata.gz: f4e8ef75a9d06eef5b482edd546535d85472ae299bb004216d44b75a4d072e0eabfc9e430203f273bc86ffefdf5eb2f28cd06433da2ae2f30887d63c9439bd63
7
+ data.tar.gz: 38ee00655b8c99b10855fb7bfca8d4c8625886e5e0cb218f47241797b9aa19560da19e45b48936ca09ca6aff607a9150e0eda1b3a701fdfd91de8b9735d76f83
data/CHANGELOG.md ADDED
@@ -0,0 +1,48 @@
1
+ # 0.5.0 / unreleased
2
+
3
+ * extend the ledger subcommand by passing it a file with customizations
4
+
5
+ ```
6
+ $ cat my_extension.rb
7
+ module MyExtension
8
+ def ask_account(*args)
9
+ # some custom stuff
10
+ super
11
+ end
12
+ end
13
+ TotalRecall::SessionHelper.include MyExtension
14
+
15
+ $ total_recall ledger -c sample.yml -r ./my_extension.rb
16
+ ```
17
+
18
+ * add version subcommand
19
+
20
+ * add default-helper
21
+
22
+ Let's you point to the default-value of an attribute:
23
+
24
+ ```yaml
25
+ :context:
26
+ :transactions:
27
+ :__defaults__:
28
+ :a: 1
29
+ :a: !!proc |
30
+ ask("What value has a?", default: default)
31
+ ```
32
+
33
+ * add transaction-helper
34
+
35
+ This allows you to use already set attributes of the transaction:
36
+
37
+ ```yaml
38
+ :context:
39
+ :transactions:
40
+ :a: 1
41
+ :b: !!proc |
42
+ transaction.a.succ
43
+ ```
44
+
45
+ # 0.4.0 / 2014-06-04
46
+
47
+ * Add yaml-config.
48
+
data/README.md CHANGED
@@ -2,13 +2,19 @@
2
2
 
3
3
  Turn **any** csv-file into a [Ledger](http://ledger-cli.org/) journal.
4
4
 
5
+ ## Install
6
+
7
+ ```bash
8
+ gem install total_recall
9
+ ```
10
+
5
11
  ## Quickstart
6
12
 
7
13
  ### Generate a sample config
8
14
 
9
15
  ```bash
10
16
  total_recall sample
11
- # An annotated config 'sample.yml' will be written to CWD.
17
+ # An annotated config 'sample.yml' will be written the current directory.
12
18
  ```
13
19
 
14
20
  ### Generate a ledger
@@ -58,10 +64,11 @@ May I suggest:
58
64
  total_recall
59
65
 
60
66
  # Commands:
61
- # total_recall help [COMMAND] # Describe available commands or one specific command
62
- # total_recall init NAME # Generate a minimal config NAME.yml
63
- # total_recall ledger -c, --config=CONFIG # Convert the config to a ledger
64
- # total_recall sample # Generate an annotated config
67
+ # total_recall help [COMMAND] # Describe available commands or one specific command
68
+ # total_recall init NAME # Generate a minimal config NAME.yml
69
+ # total_recall ledger -c, --config=CONFIG # Convert CONFIG to a ledger
70
+ # total_recall sample # Generate an annotated config
71
+ # total_recall version # Show total_recall version
65
72
 
66
73
  # typically you would do:
67
74
  total_recall init my-bank
@@ -78,6 +85,35 @@ total_recall ledger -c my-bank.yml > my-bank.dat
78
85
  ledger -f my-bank.dat bal
79
86
  ```
80
87
 
88
+ ## Extensibility
89
+
90
+ You can extend the ledger subcommand by passing a file with additions to it:
91
+
92
+ ```
93
+ total_recall ledger -c my-bank.yml -r ./my_extension.rb
94
+ ```
95
+
96
+ This makes it possible to add helpers or redefine existing ones:
97
+
98
+ ```
99
+ cat my_extension.rb
100
+ module MyExtension
101
+ # adding some options to an existing helper:
102
+ def ask_account(question, options = {})
103
+ question.upcase! if options.delete(:scream)
104
+ super
105
+ end
106
+
107
+ # a new helper:
108
+ def guess_account(question, options = {})
109
+ guess = Guesser.new.guess
110
+ ask_account(question, default: guess)
111
+ end
112
+ end
113
+
114
+ TotalRecall::SessionHelper.include MyExtension
115
+ ```
116
+
81
117
  ## Develop
82
118
 
83
119
  ```bash
@@ -36,12 +36,14 @@
36
36
  #
37
37
  #:file: sample.csv
38
38
  :raw: |-
39
- "03.11.2013";"Foo";"04.11.2013";"1.638,00";""
40
- "03.11.2013";"Bar";"04.11.2013";"-492,93";""
39
+ "date";"description";"effective_data";"amount"
40
+ "03.11.2013";"Foo";"04.11.2013";"1.638,00"
41
+ "03.11.2013";"Bar";"04.11.2013";"-492,93"
41
42
  :options:
42
43
  # Any option accepted by CSV#new (http://www.ruby-doc.org/stdlib-2.1.1/libdoc/csv/rdoc/CSV.html#method-c-new).
43
44
  :col_sep: ";"
44
- :headers: false
45
+ :headers: true
46
+ :header_converters: :symbol # row[:date] rather than row[0]
45
47
 
46
48
  :context:
47
49
  # 3). Define the context
@@ -56,15 +58,19 @@
56
58
  default: 'Assets:Checking')
57
59
  :currency: $
58
60
  # ...and fields that vary per transaction.
59
- # Helper method exist such as `row`, `ask_account` (see `TotalRecall::Helper` for more).
60
- :date: !!proc |
61
- # For weird date formats consider using Date#strptime (http://www.ruby-doc.org/stdlib-2.1.1/libdoc/date/rdoc/Date.html#method-c-strptime)
62
- Date.parse(row[0])
63
- :description: !!proc row[1]
64
- :amount: !!proc row[3]
61
+ # Helper method exist such as `row`, `ask_account` (see `TotalRecall::SessionHelper` for more).
62
+ :date: !!proc row[:date]
63
+ :description: !!proc row[:description]
64
+ :amount: !!proc row[:amount]
65
+ :currency: !!proc |
66
+ case row[:description]
67
+ when /some regex to detect non-dollar/ then '€'
68
+ else
69
+ default
70
+ end
65
71
  :to: !!proc |
66
72
  # show the row and let the user decide
67
- render_row(columns: [0, 1, 3])
73
+ render_row(columns: [:date, :description, :amount])
68
74
  ask_account("To what account did the money go?")
69
75
  :generated_at: !!proc Time.now
70
76
 
@@ -1,3 +1,3 @@
1
1
  module TotalRecall
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
data/lib/total_recall.rb CHANGED
@@ -4,26 +4,24 @@ require "mustache"
4
4
  require 'csv'
5
5
 
6
6
  module TotalRecall
7
- class Helper
7
+ module DefaultHelper
8
8
  require 'highline/import'
9
9
  require "terminal-table"
10
10
 
11
- attr_reader :config
12
- attr_accessor :row
11
+ def transaction
12
+ self
13
+ end
13
14
 
14
- def initialize(config = {})
15
- @config = config
15
+ def config
16
+ @config
16
17
  end
17
18
 
18
- def with_row(row, &block)
19
- @row = row
20
- instance_eval(&block)
21
- ensure
22
- @row = nil
19
+ def row
20
+ @row
23
21
  end
24
22
 
25
- def highline
26
- @highline ||= HighLine.new($stdin, $stderr)
23
+ def default
24
+ @default
27
25
  end
28
26
 
29
27
  def ask(question, &block)
@@ -53,16 +51,61 @@ module TotalRecall
53
51
 
54
52
  def render_row(options = {})
55
53
  options = { columns: [] }.merge(options)
56
- _row = options[:columns].map{|i| row[i] }
54
+ _row = options[:columns].map {|i| row[i] }
57
55
  $stderr.puts Terminal::Table.new(rows: [ _row ])
58
56
  end
57
+
58
+ def extract_transaction(row)
59
+ @row = row
60
+ transactions_config.each do |k,v|
61
+ next if k[/^__/]
62
+ self[k] = value_for(k, v)
63
+ end
64
+ self
65
+ end
66
+
67
+ protected
68
+ def value_for(key, v)
69
+ if v.respond_to?(:call)
70
+ @default = self[key.to_sym]
71
+ instance_eval(&v)
72
+ else
73
+ v
74
+ end
75
+ ensure
76
+ @default = nil
77
+ end
78
+
79
+ def transactions_config
80
+ config[:context][:transactions]
81
+ end
82
+
83
+ def highline
84
+ @highline ||= HighLine.new($stdin, $stderr)
85
+ end
86
+ end
87
+
88
+ # Include your module into {SessionHelper} if you want to
89
+ # add helpers or redefine existing ones.
90
+ #
91
+ # @example
92
+ # module MyExtension
93
+ # def guess_account(question, options = {})
94
+ # guess = AccountGuesser.new.guess
95
+ # ask_account("What acount provided this?", default: guess)
96
+ # end
97
+ # end
98
+ #
99
+ # TotalRecall::SessionHelper.include MyExtension
100
+ module SessionHelper
101
+ include DefaultHelper
59
102
  end
60
103
 
61
104
  class Config
62
- YAML::add_builtin_type('proc') {|_, val| eval("proc{ #{val} }") }
105
+ YAML::add_builtin_type('proc') {|_, val| eval("proc { #{val} }") }
63
106
 
64
107
  def initialize(options = {})
65
- options = {file: 'total_recall.yml'}.merge(options)
108
+ options = { file: 'total_recall.yml' }.merge(options)
66
109
  @config_file = File.expand_path(options[:file])
67
110
  end
68
111
 
@@ -93,30 +136,40 @@ module TotalRecall
93
136
  end
94
137
  end
95
138
 
139
+ def context
140
+ @context ||= config[:context].merge(transactions: transactions)
141
+ end
142
+
96
143
  def session
97
- @session ||= Helper.new(config)
144
+ @session ||= session_class.new(transactions_config_defaults, :config => config)
98
145
  end
99
146
 
100
- def context
101
- @context ||= config[:context].merge(transactions: transactions)
147
+ def transaction_attributes
148
+ @transaction_attributes ||= begin
149
+ transactions_config.dup.delete_if{|k,_| k[/__/]}.keys |
150
+ transactions_config_defaults.keys
151
+ end
102
152
  end
103
153
 
104
- def transactions
105
- @transactions ||= begin
106
- csv.each_with_object([]) do |row, result|
107
- result << transaction_defaults.merge(transactions_config).each_with_object({}) do |(k,v), cfg|
108
- next if k[/^__/]
109
- cfg[k] = v.respond_to?(:call) ? session.with_row(row, &v) : v
154
+ def session_class
155
+ @session_class ||= begin
156
+ Class.new(Struct.new(*transaction_attributes)) do
157
+ include SessionHelper
158
+
159
+ def initialize(values = {}, options = {})
160
+ @config = options[:config]
161
+ values.each do |k,v|
162
+ self[k] = value_for(k, v)
163
+ end
110
164
  end
111
165
  end
112
166
  end
113
167
  end
114
168
 
115
- def transaction_defaults
116
- @transaction_defaults ||= begin
117
- defaults = transactions_config[:__defaults__] || {}
118
- defaults.each_with_object({}) do |(k,v), result|
119
- result[k] = v.respond_to?(:call) ? session.with_row(nil, &v) : v
169
+ def transactions
170
+ @transactions ||= begin
171
+ csv.each_with_object([]) do |row, transactions|
172
+ transactions << Hash[session.extract_transaction(row).each_pair.to_a]
120
173
  end
121
174
  end
122
175
  end
@@ -125,6 +178,10 @@ module TotalRecall
125
178
  config[:context][:transactions]
126
179
  end
127
180
 
181
+ def transactions_config_defaults
182
+ transactions_config[:__defaults__] || {}
183
+ end
184
+
128
185
  def ledger
129
186
  Mustache.render(template, context)
130
187
  end
@@ -136,10 +193,14 @@ module TotalRecall
136
193
  include Thor::Actions
137
194
  source_root File.expand_path('../total_recall/templates', __FILE__)
138
195
 
139
- desc "ledger", "Convert the config to a ledger"
196
+ desc "ledger", "Convert CONFIG to a ledger"
140
197
  method_option :config, :aliases => "-c", :desc => "Config file", :required => true
198
+ method_option :require, :aliases => "-r", :desc => "File to load"
141
199
  def ledger
142
- puts TotalRecall::Config.new(file: File.expand_path(options[:config])).ledger
200
+ load(options[:require]) if options[:require]
201
+
202
+ config_path = File.expand_path(options[:config])
203
+ puts TotalRecall::Config.new(file: config_path).ledger
143
204
  end
144
205
 
145
206
  desc "sample", "Generate an annotated config"
@@ -158,5 +219,10 @@ module TotalRecall
158
219
  @name = name
159
220
  template("simple.yml.tt", destination)
160
221
  end
222
+
223
+ desc "version", "Show total_recall version"
224
+ def version
225
+ puts TotalRecall::VERSION
226
+ end
161
227
  end
162
228
  end
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,4 @@
1
- require "rubygems"
2
- require "bundler"
3
- Bundler.setup
1
+ require "bundler/setup"
2
+ Bundler.require :default, :test
4
3
 
5
- $:.unshift File.expand_path("../../lib", __FILE__)
6
- require "total_recall"
7
4
  require 'fakefs/spec_helpers'
@@ -173,4 +173,57 @@ describe TotalRecall::Config do
173
173
  expect(instance.context).to match(transactions: [{from: 'From'}], a: 1)
174
174
  end
175
175
  end
176
+
177
+ describe 'helper methods' do
178
+ describe '#transaction' do
179
+ it 'gives access to the existing attributes' do
180
+ instance = instance_with_config(<<-CONFIG)
181
+ :csv:
182
+ :raw: some csv
183
+ :context:
184
+ :transactions:
185
+ :a: attribute a
186
+ :b: !!proc |
187
+ %|not %s| % transaction.a
188
+ CONFIG
189
+
190
+ expect(instance.transactions.first).to match({a: 'attribute a', b: 'not attribute a'})
191
+ end
192
+ end
193
+
194
+ describe '#config' do
195
+ it 'gives access to the full config' do
196
+ instance = instance_with_config(<<-CONFIG)
197
+ :csv:
198
+ :raw: some csv
199
+ :context:
200
+ :transactions:
201
+ :a: !!proc |
202
+ config[:a]
203
+ :a: 1
204
+ CONFIG
205
+
206
+ expect(instance.transactions.first).to match({a: 1})
207
+ end
208
+ end
209
+
210
+ describe '#default' do
211
+ it 'gives access to the default' do
212
+ instance = instance_with_config(<<-CONFIG)
213
+ :csv:
214
+ :raw: some csv
215
+ :context:
216
+ :transactions:
217
+ :__defaults__:
218
+ :a: !!proc 2
219
+ :b?: true
220
+ :a: !!proc |
221
+ default.succ
222
+ :b?: true
223
+ CONFIG
224
+
225
+ expect(instance.transactions.first).to match({a: 3, b?: true})
226
+ end
227
+ end
228
+ end
176
229
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: total_recall
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gert Goet
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-04 00:00:00.000000000 Z
11
+ date: 2014-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -133,6 +133,7 @@ extra_rdoc_files: []
133
133
  files:
134
134
  - ".gitignore"
135
135
  - ".travis.yml"
136
+ - CHANGELOG.md
136
137
  - Gemfile
137
138
  - README.md
138
139
  - Rakefile