ledger-rest 2.0.3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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