ledger-rest 2.0.3 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +2 -0
  3. data/Gemfile +1 -1
  4. data/Guardfile +4 -22
  5. data/{LICENSE.txt → LICENSE} +1 -1
  6. data/README.md +62 -11
  7. data/config.ru +1 -1
  8. data/ledger-rest.gemspec +17 -17
  9. data/ledger-rest.yml.example +3 -0
  10. data/lib/ledger-rest/app.rb +21 -34
  11. data/lib/ledger-rest/core_ext.rb +4 -3
  12. data/lib/ledger-rest/git.rb +8 -7
  13. data/lib/ledger-rest/ledger.rb +29 -24
  14. data/lib/ledger-rest/ledger/balance.rb +48 -15
  15. data/lib/ledger-rest/ledger/budget.rb +35 -16
  16. data/lib/ledger-rest/ledger/entry.rb +1 -4
  17. data/lib/ledger-rest/ledger/parser.rb +41 -37
  18. data/lib/ledger-rest/ledger/register.rb +31 -24
  19. data/lib/ledger-rest/ledger/transaction.rb +33 -28
  20. data/lib/ledger-rest/transactions.py +47 -0
  21. data/lib/ledger-rest/version.rb +2 -1
  22. data/spec/files/append.ledger +13 -0
  23. data/spec/files/budget.ledger +4 -0
  24. data/spec/files/main.ledger +4 -0
  25. data/spec/files/opening_balance.ledger +4 -0
  26. data/spec/files/transactions.ledger +11 -0
  27. data/spec/ledger-rest.yml +3 -0
  28. data/spec/ledger-rest/api/accounts_spec.rb +19 -0
  29. data/spec/ledger-rest/api/balance_spec.rb +117 -0
  30. data/spec/ledger-rest/api/budget_spec.rb +110 -0
  31. data/spec/ledger-rest/api/payees_spec.rb +19 -0
  32. data/spec/ledger-rest/api/register_spec.rb +233 -0
  33. data/spec/ledger-rest/api/transactions_spec.rb +201 -0
  34. data/spec/ledger-rest/api/version_spec.rb +13 -0
  35. data/spec/ledger-rest/ledger/balance_spec.rb +124 -0
  36. data/spec/ledger-rest/ledger/parser_spec.rb +184 -117
  37. data/spec/ledger-rest/ledger/transaction_spec.rb +38 -31
  38. data/spec/spec_helper.rb +22 -0
  39. data/spec/support/deep_eq_matcher.rb +149 -0
  40. metadata +52 -31
  41. data/ledger-rest.org +0 -22
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NjFiMDgwN2U5ZWJhYjdmNjcyMWJmMzk3YmYyZjQwOTU4MmMxYWY4OQ==
5
+ data.tar.gz: !binary |-
6
+ NjUxNDBlNjY4MzkxODNjYzQyMTRjMTIzOTQwMzczMmRjMTU1MjQxNw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ OTQxY2Y5NTgyMmFlNzc3NjU2N2Y0MzlhNWI1YjUzNWQzYWNmNTZkMzk0MmI0
10
+ ZDEyMjE5MGZmMjAyMDg3N2I4ZjljODM2NmI5YzczMTIwZDdjZDI4ZjUyYzNk
11
+ ZjM1NmRjOThlNjk4OTEyZjQ5ZWM1Nzk1NGQ5M2MzYWJkNTkyZmI=
12
+ data.tar.gz: !binary |-
13
+ MDBlNTNhMjBkNmZmMjJhMWY0MWY2OTBlNzllZWYyM2Y2YjQ0ZDg0NjI2NmQ3
14
+ ZDczOWY0ZmY0YTI5YzcyY2QzMzg5YjFkYmRiM2Y1NmI3YTg4OGVkZWQ3NDRi
15
+ NDM2Y2IxM2ZiZGY3N2UwZDI0MTE4YTM4ZjNhZWVmMWU2ZGM2OTU=
data/.gitignore CHANGED
@@ -15,3 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ /.ruby-gemset
19
+ /.ruby-version
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
data/Guardfile CHANGED
@@ -1,24 +1,6 @@
1
- # A sample Guardfile
2
- # More info at https://github.com/guard/guard#readme
3
-
4
1
  guard 'rspec' do
5
- watch(%r{^spec/.+_spec\.rb$})
6
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
- watch('spec/spec_helper.rb') { "spec" }
8
-
9
- # Rails example
10
- watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
11
- watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
12
- watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
13
- watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
14
- watch('config/routes.rb') { "spec/routing" }
15
- watch('app/controllers/application_controller.rb') { "spec/controllers" }
16
-
17
- # Capybara features specs
18
- watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
19
-
20
- # Turnip features and steps
21
- watch(%r{^spec/acceptance/(.+)\.feature$})
22
- watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
2
+ watch(/^spec\/.+_spec\.rb$/)
3
+ watch(/^lib\/(.+)\.rb$/) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { 'spec' }
5
+ watch(%r{^spec/support/(.+)\.rb$}) { 'spec' }
23
6
  end
24
-
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Max Wolter,Arthur Andersen
1
+ Copyright (c) 2012 Max Wolter, Arthur Andersen
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,17 +1,68 @@
1
- # ledger-rest - REST assured your finances are in good hands ;-)
1
+ # ledger-rest
2
2
 
3
- ledger-rest is a REST webservice to access your ledger account data
3
+ > REST assured your finances are in good hands ;-)
4
+
5
+ `ledger-rest` is a REST webservice to access your ledger account data.
6
+
7
+ ## What you need
8
+
9
+ *Please Note:* You will have to install ledger with the `--python` flag, because
10
+ currently ledger-rest uses a most ugly hack, that uses python to query
11
+ complete xacts for the `GET /transactions` endpoint.
12
+
13
+ To install and run ledger-rest, just clone this repository and run
14
+ `bundle`. Everything you need will be installed by bundler. Then run
15
+ `rackup` to start ledger rest.
16
+
17
+ The configuration resides in `ledger-rest.yml` have a look at the
18
+ example file in the repository root.
4
19
 
5
20
  ## What already works
6
21
 
7
- * balance reports (``/balance``)
8
- * register reports (``/register``)
9
- * budget reports (``/budget``)
10
- * version query (``/version``)
22
+ * query balance reports (``GET /balance``)
23
+ * query register reports (``GET /register``)
24
+ * query budget reports (``GET /budget``)
25
+ * query a transactions list (``GET /transactions``)
26
+ * query the ledger and ledger-rest version (``GET /version``)
11
27
 
12
- ## What you need
28
+ # Contribute
29
+
30
+ All help is welcome. Just fork and send pull requests.
31
+
32
+ ## What needs to be done
33
+
34
+ ### Replace the ugly transactions retrieval
35
+
36
+ Replace the ugly transactions retrieval by a better method. This is
37
+ only implemented because I need to get out the specific data fast. Via
38
+ commandline only I do not get the full transactions with all their
39
+ posts when I add a query. Only `ledger print` would do what I want,
40
+ but then I would have to parse the transactions myself.
41
+
42
+ The ideal solution would be to implement ruby ledger bindings, but I
43
+ am not familiar enough with the inner workings of ledger.
44
+
45
+ # License
46
+
47
+ Copyright (c) 2012 Max Wolter, Arthur Andersen
48
+
49
+ MIT License
50
+
51
+ Permission is hereby granted, free of charge, to any person obtaining
52
+ a copy of this software and associated documentation files (the
53
+ "Software"), to deal in the Software without restriction, including
54
+ without limitation the rights to use, copy, modify, merge, publish,
55
+ distribute, sublicense, and/or sell copies of the Software, and to
56
+ permit persons to whom the Software is furnished to do so, subject to
57
+ the following conditions:
58
+
59
+ The above copyright notice and this permission notice shall be
60
+ included in all copies or substantial portions of the Software.
13
61
 
14
- * ruby
15
- * sinatra
16
- * escape
17
- * ledger
62
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
63
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
64
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
65
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
66
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
67
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
68
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/config.ru CHANGED
@@ -1,4 +1,4 @@
1
- $: << "./lib"
1
+ $: << './lib'
2
2
 
3
3
  require 'ledger-rest'
4
4
 
data/ledger-rest.gemspec CHANGED
@@ -4,27 +4,27 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'ledger-rest/version'
5
5
 
6
6
  Gem::Specification.new do |gem|
7
- gem.name = "ledger-rest"
7
+ gem.name = 'ledger-rest'
8
8
  gem.version = LedgerRest::VERSION
9
- gem.authors = ["Prof. MAAD", "Arthur Andersen"]
10
- gem.email = ["leoc.git@gmail.com"]
9
+ gem.authors = ['Prof. MAAD', 'Arthur Andersen']
10
+ gem.email = ['leoc.git@gmail.com']
11
11
  gem.description = %q{Provide a REST web service for ledger.}
12
12
  gem.summary = %q{Ledger via REST.}
13
- gem.homepage = "https://github.com/leoc/ledger-rest"
13
+ gem.homepage = 'https://github.com/leoc/ledger-rest'
14
14
 
15
- gem.files = `git ls-files`.split($/)
16
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
- gem.require_paths = ["lib"]
15
+ gem.files = `git ls-files`.split($RS)
16
+ gem.executables = gem.files.grep(/^bin\//).map { |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(/^(test|spec|features)\//)
18
+ gem.require_paths = ['lib']
19
19
 
20
- gem.add_dependency "escape"
21
- gem.add_dependency "sinatra"
22
- gem.add_dependency "git"
23
-
24
- gem.add_development_dependency "rake"
25
- gem.add_development_dependency "pry"
26
- gem.add_development_dependency "rspec"
27
- gem.add_development_dependency "rb-inotify"
28
- gem.add_development_dependency "guard-rspec"
20
+ gem.add_dependency 'escape'
21
+ gem.add_dependency 'sinatra'
22
+ gem.add_dependency 'git'
29
23
 
24
+ gem.add_development_dependency 'rake'
25
+ gem.add_development_dependency 'pry'
26
+ gem.add_development_dependency 'rspec'
27
+ gem.add_development_dependency 'rack-test'
28
+ gem.add_development_dependency 'rb-inotify'
29
+ gem.add_development_dependency 'guard-rspec'
30
30
  end
@@ -0,0 +1,3 @@
1
+ ledger_file: "/path/to/my/ledger/files.dat"
2
+ ledger_bin: "/bin/ledger"
3
+ ledger_append_file: "/path/to/my/ledger/append_file.dat"
@@ -12,15 +12,14 @@ require 'ledger-rest/core_ext'
12
12
 
13
13
  module LedgerRest
14
14
  class App < Sinatra::Base
15
- CONFIG_FILE = "ledger-rest.yml"
16
-
17
15
  configure do |c|
18
16
  config = {}
17
+ filename = ENV['CONFIG_FILE'] || 'ledger-rest.yml'
19
18
  begin
20
- config = YAML.load_file(CONFIG_FILE)
19
+ config = YAML.load_file(filename)
21
20
  config.symbolize_keys!
22
- rescue Exception => e
23
- puts "Failed to load config."
21
+ rescue Errno::ENOENT
22
+ raise "'#{filename}' not found."
24
23
  end
25
24
 
26
25
  Ledger.configure(config)
@@ -30,14 +29,14 @@ module LedgerRest
30
29
  get '/version' do
31
30
  content_type :json
32
31
  {
33
- "version" => LedgerRest::VERSION,
34
- "ledger-version" => Ledger.version
32
+ 'version' => LedgerRest::VERSION,
33
+ 'ledger-version' => Ledger.version
35
34
  }.to_json
36
35
  end
37
36
 
38
37
  get '/balance/?:query?' do
39
38
  content_type :json
40
- Ledger::Balance.json(params[:query])
39
+ Ledger.balance(params[:query]).to_json
41
40
  end
42
41
 
43
42
  get '/budget/?:query?' do
@@ -63,62 +62,50 @@ module LedgerRest
63
62
  # gets a potential new entry via the entry command
64
63
  get '/transactions/entry/?:desc?' do
65
64
  content_type :json
66
- { :transaction => Ledger::Entry.get(params[:desc]) }.to_json
65
+ { transaction: Ledger::Entry.get(params[:desc]) }.to_json
67
66
  end
68
67
 
69
68
  # creates a new entry based on the
70
69
  post '/transactions/entry/?:desc?' do
71
70
  content_type :json
72
- { :transaction => Ledger::Entry.append(params[:desc]) }.to_json
73
- end
74
-
75
- get '/transactions/:meta' do
76
- "Not yet implemented!"
71
+ { transaction: Ledger::Entry.append(params[:desc]) }.to_json
77
72
  end
78
73
 
79
- get '/transactions' do
80
- "Not yet implemented!"
74
+ get '/transactions/?:query?' do
75
+ content_type :json
76
+ Ledger.transactions(params[:query])
81
77
  end
82
78
 
83
79
  post '/transactions' do
84
80
  content_type :json
85
81
  begin
86
- params = JSON.parse(params[:transaction], :symbolize_names => true)
82
+ request.body.rewind
83
+ request_payload = JSON.parse(request.body.read, symbolize_names: true)
87
84
 
88
- transaction = Transaction.create params
85
+ transaction = Ledger::Transaction.new(request_payload)
89
86
 
90
- raise "Verification error" unless transaction.valid?
87
+ fail transaction.check.join("\n") unless transaction.valid?
91
88
 
92
89
  Git.invoke :before_write
93
90
 
94
- @@ledger.append transaction
91
+ Ledger.append(transaction)
95
92
 
96
93
  Git.invoke :after_write
97
94
 
98
95
  [201, { transaction: transaction }.to_json]
99
96
  rescue JSON::ParserError => e
100
- [422,
101
- {
102
- :error => true,
103
- :message => "Unprocessible Entity: '#{e}'"
104
- }.to_json
105
- ]
97
+ [422, { error: true, message: "Unprocessible Entity: '#{e}'" }.to_json ]
106
98
  rescue RuntimeError => e
107
- [400,
108
- {
109
- :error => true,
110
- :message => "Adding the transaction failed: '#{e}'"
111
- }.to_json
112
- ]
99
+ [400, { error: true, message: "Adding the transaction failed: '#{e}'" }.to_json ]
113
100
  end
114
101
  end
115
102
 
116
103
  put '/transactions/:meta' do
117
- "Not yet implemented!"
104
+ 'Not yet implemented!'
118
105
  end
119
106
 
120
107
  delete '/transactions/:meta' do
121
- "Not yet implemented!"
108
+ 'Not yet implemented!'
122
109
  end
123
110
  end
124
111
  end
@@ -1,15 +1,16 @@
1
+ # -*- coding: utf-8 -*-
1
2
  class Hash
2
3
  def symbolize_keys
3
4
  hash = {}
4
- self.each_pair do |key, val|
5
+ each_pair do |key, val|
5
6
  hash[key.to_sym] = val
6
7
  end
7
8
  hash
8
9
  end
9
10
 
10
11
  def symbolize_keys!
11
- self.keys.each do |key|
12
- self[key.to_sym] = self.delete key
12
+ keys.each do |key|
13
+ self[key.to_sym] = delete key
13
14
  end
14
15
  self
15
16
  end
@@ -1,3 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+
1
3
  require 'git'
2
4
 
3
5
  module LedgerRest
@@ -5,8 +7,8 @@ module LedgerRest
5
7
  class << self
6
8
  attr_reader :repository, :remote, :branch, :read_pull_block_time
7
9
 
8
- def configure options
9
- @repository = options[:git_repository] or File.dirname(options[:ledger_file] || 'main.ledger')
10
+ def configure(options)
11
+ @repository = options[:git_repository] || File.dirname(options[:ledger_file] || 'main.ledger')
10
12
  @pull_before_read = options[:git_pull_before_read] || false
11
13
  @pull_before_write = options[:git_pull_before_write] || false
12
14
  @push_after_write = options[:git_push_after_write] || false
@@ -15,17 +17,17 @@ module LedgerRest
15
17
  @read_pull_block_time = options[:git_read_pull_block_time] || 10*60
16
18
 
17
19
  @last_read_pull = Time.new
18
- if pull_before_read? or pull_before_write? or push_after_write?
20
+ if pull_before_read? || pull_before_write? || push_after_write?
19
21
  @git_repo = ::Git.open(repository)
20
22
  FileUtils.touch(options[:ledger_append_file])
21
23
  @git_repo.add(options[:ledger_append_file])
22
24
  end
23
25
  end
24
26
 
25
- def invoke hook
27
+ def invoke(hook)
26
28
  case hook
27
29
  when :before_read
28
- pull if pull_before_read? and not blocked?
30
+ pull if pull_before_read? && !blocked?
29
31
  when :before_write
30
32
  pull if pull_before_write?
31
33
  when :after_write
@@ -59,12 +61,11 @@ module LedgerRest
59
61
 
60
62
  # Execute the push command after commiting all.
61
63
  def push
62
- @git_repo.commit_all("transaction added via ledger-rest")
64
+ @git_repo.commit_all('Add transaction via ledger-rest')
63
65
  @git_repo.push(remote, branch)
64
66
  rescue Exception => e
65
67
  $stderr.put "Git push failed: #{e}"
66
68
  end
67
-
68
69
  end
69
70
  end
70
71
  end
@@ -4,13 +4,13 @@ require 'ledger-rest/ledger/parser'
4
4
  module LedgerRest
5
5
  class Ledger
6
6
  class << self
7
-
8
7
  attr_accessor :rcfile, :bin, :file, :append_file, :home
9
8
 
10
9
  def configure(options)
11
10
  @bin = options[:ledger_bin] || '/usr/bin/ledger'
12
11
  @file = options[:ledger_file] || ENV['LEDGER_FILE'] || 'main.ledger'
13
- @append_file = options[:ledger_append_file] || ENV['LEDGER_FILE'] || 'append.ledger'
12
+ @append_file =
13
+ options[:ledger_append_file] || ENV['LEDGER_FILE'] || 'append.ledger'
14
14
  @home = options[:ledger_home] || ''
15
15
  end
16
16
 
@@ -22,67 +22,72 @@ module LedgerRest
22
22
  '-f' => @file
23
23
  }.merge(options)
24
24
 
25
- params = ""
25
+ params = ''
26
26
  options.each do |key, val|
27
27
  params << " #{key} #{Escape.shell_single_word(val)}"
28
28
  end
29
29
 
30
30
  command = "#{bin} #{params} #{cmd}"
31
31
 
32
- puts command
33
32
  `#{command}`.rstrip
34
33
  end
35
34
 
36
35
  # Append a new transaction to the append_file.
37
- def append transaction
38
- File.open(append_file, "a+") do |f|
36
+ def append(transaction)
37
+ File.open(append_file, 'a+') do |f|
39
38
  if f.pos == 0
40
39
  last_char = "\n"
41
40
  else
42
- f.pos = f.pos-1
41
+ f.pos = f.pos - 1
43
42
  last_char = f.getc
44
43
  end
45
44
 
46
- f.write "\n" unless last_char == "\n"
47
- f.write(transaction_string)
45
+ f.write "\n"
46
+ f.write(transaction.to_ledger)
48
47
  end
49
48
  end
50
49
 
50
+ # Returns a hash with the balance for given query
51
+ def balance(query = nil)
52
+ Ledger::Balance.get(query)
53
+ end
54
+
51
55
  # Return the ledger version.
52
56
  def version
53
- exec("--version").match(/^Ledger (.*),/)[1]
57
+ exec('--version').match(/^Ledger (.*),/)[1]
54
58
  end
55
59
 
56
60
  # Get a new transaction entry based on previous entries found
57
61
  # in the append file.
58
- def entry description
62
+ def entry(description)
59
63
  result = exec "entry #{description}", '-f' => append_file
60
-
61
-
62
64
  end
63
65
 
64
66
  # Return a list of payees.
65
67
  def payees(query = nil)
66
- result = exec "payees #{query if query}"
67
- { :payees => result.split("\n") }
68
+ exec("payees #{query if query}").split("\n")
68
69
  end
69
70
 
70
71
  # Returns an Array of payees with their respective usage count.
71
- def payees_with_usage
72
-
73
- end
72
+ def payees_with_usage; end
74
73
 
75
74
  # Return an array of accounts.
76
75
  def accounts(query = nil)
77
- result = exec "accounts #{query if query}"
78
- { :accounts => result.split("\n") }
76
+ exec("accounts #{query if query}").split("\n")
79
77
  end
80
78
 
81
- # Return an Array of accounts with their respective usage count.
82
- def accounts_with_usage
83
-
79
+ # Returns a list of transactions as JSON
80
+ def transactions(query)
81
+ # This is a most ugly hack, which gathers the ledger
82
+ # information via python.
83
+ # TODO: write ruby-ledger bindings to retrieve reports with
84
+ # ruby directly!
85
+ query.gsub!('-', '\-') if query
86
+ `#{bin} python #{File.join(File.dirname(__FILE__), 'transactions.py')} "#{@file}" "#{query}"`
84
87
  end
85
- end
86
88
 
89
+ # Return an Array of accounts with their respective usage count.
90
+ def accounts_with_usage; end
91
+ end
87
92
  end
88
93
  end