ledger_web 1.4.2 → 1.4.3

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.
data/Rakefile CHANGED
@@ -9,3 +9,7 @@ end
9
9
  task :release => :build do
10
10
  system "gem push ledger_web-#{LedgerWeb::VERSION}.gem"
11
11
  end
12
+
13
+ task :test do
14
+ system 'rspec --color --format=documentation test'
15
+ end
data/bin/ledger_web CHANGED
@@ -15,17 +15,17 @@ OptionParser.new do |opts|
15
15
  opts.banner = "Usage: ledger_web [options]"
16
16
 
17
17
  opts.on("-p", "--port PORT", Integer, "Port to expose the web interface") do |p|
18
- CONFIG.set :port, p.to_i
18
+ LedgerWeb::Config.instance.set :port, p.to_i
19
19
  end
20
20
 
21
21
  opts.on("-f", "--ledger-file FILE", String, "Ledger file to watch and load") do |f|
22
- CONFIG.set :ledger_file, f
22
+ LedgerWeb::Config.instance.set :ledger_file, f
23
23
  end
24
24
 
25
25
  opts.on("-d", "--database-url URL", String, "Database URL to load into") do |d|
26
- CONFIG.set :database_url, d
26
+ LedgerWeb::Config.instance.set :database_url, d
27
27
  end
28
28
  end.parse!
29
29
 
30
30
  LedgerWeb::Watcher.run!
31
- LedgerWeb::Application.run!(:port => CONFIG.get(:port))
31
+ LedgerWeb::Application.run!(:port => LedgerWeb::Config.instance.get(:port))
data/ledger_web.gemspec CHANGED
@@ -20,9 +20,12 @@ Gem::Specification.new do |s|
20
20
  s.add_dependency("sinatra")
21
21
  s.add_dependency("sinatra-session")
22
22
  s.add_dependency("sinatra-contrib")
23
+ s.add_dependency("rspec")
24
+ s.add_dependency("database_cleaner")
23
25
 
24
26
  s.bindir = 'bin'
25
27
  s.files = `git ls-files`.split("\n")
28
+ s.test_files = `git ls-files -- test/*`.split("\n")
26
29
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
27
30
  s.require_paths = ["lib"]
28
31
  end
@@ -6,11 +6,13 @@ require 'sinatra/session'
6
6
  module LedgerWeb
7
7
  class Application < Sinatra::Base
8
8
  register Sinatra::Session
9
- set :session_secret, CONFIG.get(:session_secret)
10
- set :session_expire, CONFIG.get(:session_expire)
11
- set :views, CONFIG.get(:report_directories) + [File.join(File.dirname(__FILE__), 'views')]
9
+ set :session_secret, LedgerWeb::Config.instance.get(:session_secret)
10
+ set :session_expire, LedgerWeb::Config.instance.get(:session_expire)
11
+ set :views, LedgerWeb::Config.instance.get(:report_directories) + [File.join(File.dirname(__FILE__), 'views')]
12
12
  set :reload_templates, true
13
13
 
14
+ LedgerWeb::Database.connect
15
+
14
16
  helpers Sinatra::Capture
15
17
  helpers LedgerWeb::Helpers
16
18
 
@@ -55,7 +57,7 @@ module LedgerWeb
55
57
  end
56
58
 
57
59
  get '/' do
58
- index_report = CONFIG.get :index_report
60
+ index_report = LedgerWeb::Config.instance.get :index_report
59
61
  if index_report
60
62
  redirect "/reports/#{index_report.to_s}"
61
63
  else
@@ -3,6 +3,17 @@ module LedgerWeb
3
3
  class Config
4
4
  attr_reader :vars, :hooks
5
5
 
6
+ @@should_load_user_config = true
7
+ @@instance = nil
8
+
9
+ def self.should_load_user_config
10
+ @@should_load_user_config
11
+ end
12
+
13
+ def self.should_load_user_config=(val)
14
+ @@should_load_user_config = val
15
+ end
16
+
6
17
  def initialize
7
18
  @vars = {}
8
19
  @hooks = {}
@@ -55,40 +66,43 @@ module LedgerWeb
55
66
  return eval(file.read, nil, filename)
56
67
  end
57
68
  end
58
- end
59
- end
60
-
61
- CONFIG = LedgerWeb::Config.new do |config|
62
- config.set :database_url, "postgres://localhost/ledger"
63
- config.set :port, "9090"
64
- config.set :ledger_file, ENV['LEDGER_FILE']
65
- config.set :report_directories, ["#{File.dirname(__FILE__)}/reports"]
66
- config.set :session_secret, 'SomethingSecretThisWayPassed'
67
- config.set :session_expire, 60*60
68
- config.set :watch_interval, 5
69
- config.set :watch_stable_count, 3
70
- config.set :ledger_bin_path, "ledger"
71
69
 
72
- config.set :ledger_format, "%(quoted(xact.beg_line)),%(quoted(date)),%(quoted(payee)),%(quoted(account)),%(quoted(commodity)),%(quoted(quantity(scrub(display_amount)))),%(quoted(cleared)),%(quoted(virtual)),%(quoted(join(note | xact.note))),%(quoted(cost))\n"
73
70
 
74
- config.set :price_lookup_skip_symbols, ['$']
75
-
76
- func = Proc.new do |symbol, min_date, max_date|
77
- LedgerWeb::YahooPriceLookup.new(symbol, min_date, max_date).lookup
78
- end
79
- config.set :price_function, func
80
-
81
- ledger_web_dir = "#{ENV['HOME']}/.ledger_web"
82
-
83
- if File.directory? ledger_web_dir
84
- if File.directory? "#{ledger_web_dir}/reports"
85
- dirs = config.get(:report_directories)
86
- dirs.unshift "#{ledger_web_dir}/reports"
87
- config.set :report_directories, dirs
88
- end
89
-
90
- if File.exists? "#{ledger_web_dir}/config.rb"
91
- config.override_with(LedgerWeb::Config.from_file("#{ledger_web_dir}/config.rb"))
71
+ def self.instance
72
+ @@instance ||= LedgerWeb::Config.new do |config|
73
+ config.set :database_url, "postgres://localhost/ledger"
74
+ config.set :port, "9090"
75
+ config.set :ledger_file, ENV['LEDGER_FILE']
76
+ config.set :report_directories, ["#{File.dirname(__FILE__)}/reports"]
77
+ config.set :session_secret, 'SomethingSecretThisWayPassed'
78
+ config.set :session_expire, 60*60
79
+ config.set :watch_interval, 5
80
+ config.set :watch_stable_count, 3
81
+ config.set :ledger_bin_path, "ledger"
82
+
83
+ config.set :ledger_format, "%(quoted(xact.beg_line)),%(quoted(date)),%(quoted(payee)),%(quoted(account)),%(quoted(commodity)),%(quoted(quantity(scrub(display_amount)))),%(quoted(cleared)),%(quoted(virtual)),%(quoted(join(note | xact.note))),%(quoted(cost))\n"
84
+
85
+ config.set :price_lookup_skip_symbols, ['$']
86
+
87
+ func = Proc.new do |symbol, min_date, max_date|
88
+ LedgerWeb::YahooPriceLookup.new(symbol, min_date, max_date).lookup
89
+ end
90
+ config.set :price_function, func
91
+
92
+ ledger_web_dir = "#{ENV['HOME']}/.ledger_web"
93
+
94
+ if LedgerWeb::Config.should_load_user_config && File.directory?(ledger_web_dir)
95
+ if File.directory? "#{ledger_web_dir}/reports"
96
+ dirs = config.get(:report_directories)
97
+ dirs.unshift "#{ledger_web_dir}/reports"
98
+ config.set :report_directories, dirs
99
+ end
100
+
101
+ if File.exists? "#{ledger_web_dir}/config.rb"
102
+ config.override_with(LedgerWeb::Config.from_file("#{ledger_web_dir}/config.rb"))
103
+ end
104
+ end
105
+ end
92
106
  end
93
107
  end
94
108
  end
data/lib/ledger_web/db.rb CHANGED
@@ -1,59 +1,92 @@
1
1
  require 'sequel'
2
2
  require 'sequel/extensions/migration'
3
3
  require 'csv'
4
+ require 'tempfile'
4
5
 
5
- DB = Sequel.connect(CONFIG.get(:database_url))
6
+ module LedgerWeb
7
+ class Database
6
8
 
7
- Sequel::Migrator.apply(DB, File.join(File.dirname(__FILE__), "db/migrate"))
9
+ def self.connect
10
+ @@db = Sequel.connect(LedgerWeb::Config.instance.get(:database_url))
11
+ self.run_migrations()
12
+ end
8
13
 
9
- home_migrations = File.join(ENV['HOME'], '.ledger_web', 'migrate')
10
- if File.directory?(home_migrations)
11
- Sequel::Migrator.run(DB, home_migrations, :table => "user_schema_changes")
12
- end
14
+ def self.close
15
+ @@db.disconnect
16
+ end
13
17
 
14
- module LedgerWeb
15
- class Database
18
+ def self.handle
19
+ @@db
20
+ end
16
21
 
17
- def self.load_database
18
- ledger_format = CONFIG.get :ledger_format
19
- ledger_bin_path = CONFIG.get :ledger_bin_path
20
- ledger_file = CONFIG.get :ledger_file
21
-
22
- # dump ledger to tempfile
22
+ def self.run_migrations
23
+ Sequel::Migrator.apply(@@db, File.join(File.dirname(__FILE__), "db/migrate"))
24
+
25
+ home_migrations = File.join(ENV['HOME'], '.ledger_web', 'migrate')
26
+ if LedgerWeb::Config.should_load_user_config && File.directory?(home_migrations)
27
+ Sequel::Migrator.run(@@db, home_migrations, :table => "user_schema_changes")
28
+ end
29
+ end
30
+
31
+ def self.dump_ledger_to_csv
32
+ ledger_bin_path = LedgerWeb::Config.instance.get :ledger_bin_path
33
+ ledger_file = LedgerWeb::Config.instance.get :ledger_file
34
+ ledger_format = LedgerWeb::Config.instance.get :ledger_format
35
+
23
36
  print " dumping ledger to file...."
24
37
  file = Tempfile.new('ledger')
25
38
  system "#{ledger_bin_path} -f #{ledger_file} --format='#{ledger_format}' reg > #{file.path}"
39
+ replaced_file = Tempfile.new('ledger')
40
+ replaced_file.write(file.read.gsub('\"', '""'))
41
+ replaced_file.flush
42
+
26
43
  puts "done"
44
+ return replaced_file
45
+ end
46
+
47
+ def self.load_database(file)
27
48
  counter = 0
28
- DB.transaction do
49
+ @@db.transaction do
29
50
 
30
- CONFIG.run_hooks(:before_load, DB)
51
+ LedgerWeb::Config.instance.run_hooks(:before_load, @@db)
31
52
 
32
53
  print " clearing ledger table...."
33
- DB["DELETE FROM ledger"].delete
54
+ @@db["DELETE FROM ledger"].delete
34
55
  puts "done"
35
56
 
36
57
  print " loading into database...."
58
+
37
59
  CSV.foreach(file.path) do |row|
38
60
  counter += 1
39
- row = Hash[*[:xtn_id, :xtn_date, :note, :account, :commodity, :amount, :cleared, :virtual, :tags, :cost].zip(row).flatten]
40
-
61
+ row = Hash[*[
62
+ :xtn_id,
63
+ :xtn_date,
64
+ :note,
65
+ :account,
66
+ :commodity,
67
+ :amount,
68
+ :cleared,
69
+ :virtual,
70
+ :tags,
71
+ :cost
72
+ ].zip(row).flatten]
73
+
41
74
  xtn_date = Date.strptime(row[:xtn_date], '%Y/%m/%d')
42
75
 
43
76
  row[:xtn_month] = xtn_date.strftime('%Y/%m/01')
44
77
  row[:xtn_year] = xtn_date.strftime('%Y/01/01')
45
78
  row[:cost] = row[:cost].gsub(/[^\d\.-]/, '')
46
79
 
47
- row = CONFIG.run_hooks(:before_insert_row, row)
48
- DB[:ledger].insert(row)
49
- CONFIG.run_hooks(:after_insert_row, row)
80
+ row = LedgerWeb::Config.instance.run_hooks(:before_insert_row, row)
81
+ @@db[:ledger].insert(row)
82
+ LedgerWeb::Config.instance.run_hooks(:after_insert_row, row)
50
83
  end
51
84
 
52
85
  puts " Running after_load hooks"
53
- CONFIG.run_hooks(:after_load, DB)
86
+ LedgerWeb::Config.instance.run_hooks(:after_load, @@db)
54
87
  end
55
88
  puts " analyzing ledger table"
56
- DB.fetch('VACUUM ANALYZE ledger').all
89
+ @@db.fetch('VACUUM ANALYZE ledger').all
57
90
  puts "done"
58
91
  counter
59
92
  end
@@ -81,11 +114,11 @@ module LedgerWeb
81
114
  HERE
82
115
 
83
116
  puts "Deleting prices"
84
- DB["DELETE FROM prices"].delete
117
+ @@db["DELETE FROM prices"].delete
85
118
 
86
- rows = DB.fetch(query)
87
- proc = CONFIG.get :price_function
88
- skip = CONFIG.get :price_lookup_skip_symbols
119
+ rows = @@db.fetch(query)
120
+ proc = LedgerWeb::Config.instance.get :price_function
121
+ skip = LedgerWeb::Config.instance.get :price_lookup_skip_symbols
89
122
 
90
123
  puts "Loading prices"
91
124
  rows.each do |row|
@@ -95,11 +128,12 @@ HERE
95
128
 
96
129
  prices = proc.call(row[:commodity], row[:min_date], row[:max_date])
97
130
  prices.each do |price|
98
- DB[:prices].insert(:commodity => row[:commodity], :price_date => price[0], :price => price[1])
131
+ @@db[:prices].insert(:commodity => row[:commodity], :price_date => price[0], :price => price[1])
99
132
  end
100
133
  end
101
- DB.fetch("analyze prices").all
134
+ @@db.fetch("analyze prices").all
102
135
  puts "Done loading prices"
103
136
  end
104
137
  end
105
138
  end
139
+
@@ -8,6 +8,12 @@ module LedgerWeb
8
8
  @value_type = value_type
9
9
  @span_class = span_class
10
10
  end
11
+
12
+ def ==(other)
13
+ self.title == other.title && \
14
+ self.value_type == other.value_type && \
15
+ self.span_class == other.span_class
16
+ end
11
17
  end
12
18
 
13
19
  class Value
@@ -28,7 +34,10 @@ module LedgerWeb
28
34
 
29
35
  class Report
30
36
 
31
- attr_accessor :error, :fields
37
+ attr_accessor :error, :fields, :rows
38
+
39
+ @@session = {}
40
+ @@params = {}
32
41
 
33
42
  def self.session=(session)
34
43
  @@session = session
@@ -56,7 +65,7 @@ module LedgerWeb
56
65
  params[key.to_sym] = val
57
66
  end
58
67
 
59
- ds = DB.fetch(query, params)
68
+ ds = LedgerWeb::Database.handle.fetch(query, params)
60
69
  report = self.new
61
70
  begin
62
71
  row = ds.first
@@ -1,3 +1,3 @@
1
1
  module LedgerWeb
2
- VERSION = '1.4.2'
2
+ VERSION = '1.4.3'
3
3
  end
@@ -3,23 +3,26 @@ require 'directory_watcher'
3
3
  module LedgerWeb
4
4
  class Watcher
5
5
  def self.run!
6
- directory = CONFIG.get :watch_directory
6
+ directory = LedgerWeb::Config.instance.get :watch_directory
7
7
  glob = "*"
8
8
 
9
9
  if directory.nil?
10
- directory = File.dirname(CONFIG.get :ledger_file)
11
- glob = File.basename(CONFIG.get :ledger_file)
10
+ directory = File.dirname(LedgerWeb::Config.instance.get :ledger_file)
11
+ glob = File.basename(LedgerWeb::Config.instance.get :ledger_file)
12
12
  end
13
13
 
14
14
  @@dw = DirectoryWatcher.new directory, :glob => glob
15
- @@dw.interval = CONFIG.get :watch_interval
16
- @@dw.stable = CONFIG.get :watch_stable_count
15
+ @@dw.interval = LedgerWeb::Config.instance.get :watch_interval
16
+ @@dw.stable = LedgerWeb::Config.instance.get :watch_stable_count
17
17
 
18
18
  @@dw.add_observer do |*args|
19
19
  args.each do |event|
20
20
  if event[0] == :stable
21
21
  puts "Loading database"
22
- count = LedgerWeb::Database.load_database
22
+ LedgerWeb::Database.connect
23
+ LedgerWeb::Database.run_migrations
24
+ file = LedgerWeb::Database.dump_ledger_to_csv
25
+ count = LedgerWeb::Database.load_database(file)
23
26
  puts "Loaded #{count} records"
24
27
  end
25
28
  end
@@ -0,0 +1,65 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/spec_helper")
2
+ require 'ledger_web/config'
3
+
4
+ describe LedgerWeb::Config do
5
+ describe "#initialize" do
6
+
7
+ it "should get and set simple values" do
8
+ conf = LedgerWeb::Config.new do |config|
9
+ config.set :key_one, "value one"
10
+ config.set :key_two, "value two"
11
+ end
12
+
13
+ conf.get(:key_one).should eq("value one")
14
+ conf.get(:key_two).should eq("value two")
15
+ end
16
+
17
+ it "should get and run simple hooks" do
18
+ conf = LedgerWeb::Config.new do |config|
19
+ config.add_hook :sample do |val|
20
+ val[:foo] = val[:foo] * 2
21
+ end
22
+ end
23
+
24
+ test_val = { :foo => 2 }
25
+ conf.run_hooks(:sample, test_val)
26
+ test_val[:foo].should eq(4)
27
+ end
28
+ end
29
+
30
+ describe "#override_with" do
31
+ it "should override keys" do
32
+ conf_one = LedgerWeb::Config.new do |config|
33
+ config.set :key_one, "value one"
34
+ end
35
+
36
+ conf_two = LedgerWeb::Config.new do |config|
37
+ config.set :key_one, "value two"
38
+ end
39
+
40
+ conf_one.override_with(conf_two)
41
+
42
+ conf_one.get(:key_one).should eq("value two")
43
+ end
44
+
45
+ it "should append hooks" do
46
+ conf_one = LedgerWeb::Config.new do |config|
47
+ config.add_hook(:sample) do |val|
48
+ val[:list] << 'one'
49
+ end
50
+ end
51
+
52
+ conf_two = LedgerWeb::Config.new do |config|
53
+ config.add_hook(:sample) do |val|
54
+ val[:list] << 'two'
55
+ end
56
+ end
57
+
58
+ conf_one.override_with(conf_two)
59
+
60
+ test_val = {:list => []}
61
+ conf_one.run_hooks(:sample, test_val)
62
+ test_val[:list].should eq(['one', 'two'])
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,222 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/spec_helper")
2
+ require 'ledger_web/db'
3
+ require 'ledger_web/config'
4
+ require 'csv'
5
+
6
+ describe LedgerWeb::Database do
7
+ describe "#dump_ledger_to_csv" do
8
+ it "should not die" do
9
+ set_config :ledger_file, fixture('small')
10
+ file = LedgerWeb::Database.dump_ledger_to_csv
11
+ end
12
+
13
+ it "should dump valid csv" do
14
+ set_config :ledger_file, fixture('small')
15
+ file = LedgerWeb::Database.dump_ledger_to_csv
16
+
17
+ rows = CSV.read(file.path)
18
+
19
+ rows.should eq([
20
+ ["1", "2012/01/01", "Transaction One", "Assets:Savings", "$", "100", "true", "false", "", "$100.00"],
21
+ ["1", "2012/01/01", "Transaction One", "Assets:Checking", "$", "200", "true", "false", "", "$200.00"],
22
+ ["1", "2012/01/01", "Transaction One", "Equity:Opening Balances", "$", "-300", "true", "false", "", "$-300.00"],
23
+ ["6", "2012/01/02", "Lunch", "Expenses:Lunch", "$", "10", "true", "false", "", "$10.00"],
24
+ ["6", "2012/01/02", "Lunch", "Assets:Checking", "$", "-10", "true", "false", "", "$-10.00"]
25
+ ])
26
+ end
27
+
28
+ it "should dump valid csv even with quoted commodities" do
29
+ set_config :ledger_file, fixture('quoted')
30
+ file = LedgerWeb::Database.dump_ledger_to_csv
31
+
32
+ rows = CSV.read(file.path)
33
+
34
+ rows.should eq([
35
+ ["1", "2012/01/01", "Transaction One", "Assets:Savings", "\"Foo 123\"", "100", "true", "false", "", "100.00 \"Foo 123\""],
36
+ ["1", "2012/01/01", "Transaction One", "Assets:Checking", "\"Foo 123\"", "200", "true", "false", "", "200.00 \"Foo 123\""],
37
+ ["1", "2012/01/01", "Transaction One", "Equity:Opening Balances", "\"Foo 123\"", "-300", "true", "false", "", "-300.00 \"Foo 123\""],
38
+ ["6", "2012/01/02", "Lunch", "Expenses:Lunch", "\"Foo 123\"", "10", "true", "false", "", "10.00 \"Foo 123\""],
39
+ ["6", "2012/01/02", "Lunch", "Assets:Checking", "\"Foo 123\"", "-10", "true", "false", "", "-10.00 \"Foo 123\""]
40
+ ])
41
+ end
42
+
43
+ end
44
+
45
+ describe "#load_database" do
46
+ it "should load the database from a csv file" do
47
+ set_config :ledger_file, fixture('small')
48
+ file = LedgerWeb::Database.dump_ledger_to_csv
49
+ count = LedgerWeb::Database.load_database(file)
50
+ count.should eq(5)
51
+
52
+ LedgerWeb::Database.handle[:ledger].count().should eq(5)
53
+
54
+ convert_bd_to_string(LedgerWeb::Database.handle[:ledger].all()).should eq([
55
+ {
56
+ :xtn_date => Date.new(2012,1,1),
57
+ :checknum => nil,
58
+ :note => 'Transaction One',
59
+ :account => 'Assets:Savings',
60
+ :commodity => '$',
61
+ :amount => "100.0",
62
+ :tags => '',
63
+ :xtn_month => Date.new(2012,1,1),
64
+ :xtn_year => Date.new(2012,1,1),
65
+ :virtual => false,
66
+ :xtn_id => 1,
67
+ :cleared => true,
68
+ :cost => "100.0"
69
+ },
70
+ {
71
+ :xtn_date => Date.new(2012,1,1),
72
+ :checknum => nil,
73
+ :note => 'Transaction One',
74
+ :account => 'Assets:Checking',
75
+ :commodity => '$',
76
+ :amount => "200.0",
77
+ :tags => '',
78
+ :xtn_month => Date.new(2012,1,1),
79
+ :xtn_year => Date.new(2012,1,1),
80
+ :virtual => false,
81
+ :xtn_id => 1,
82
+ :cleared => true,
83
+ :cost => "200.0"
84
+ },
85
+ {
86
+ :xtn_date => Date.new(2012,1,1),
87
+ :checknum => nil,
88
+ :note => 'Transaction One',
89
+ :account => 'Equity:Opening Balances',
90
+ :commodity => '$',
91
+ :amount => "-300.0",
92
+ :tags => '',
93
+ :xtn_month => Date.new(2012,1,1),
94
+ :xtn_year => Date.new(2012,1,1),
95
+ :virtual => false,
96
+ :xtn_id => 1,
97
+ :cleared => true,
98
+ :cost => "-300.0"
99
+ },
100
+ {
101
+ :xtn_date => Date.new(2012,1,2),
102
+ :checknum => nil,
103
+ :note => 'Lunch',
104
+ :account => 'Expenses:Lunch',
105
+ :commodity => '$',
106
+ :amount => "10.0",
107
+ :tags => '',
108
+ :xtn_month => Date.new(2012,1,1),
109
+ :xtn_year => Date.new(2012,1,1),
110
+ :virtual => false,
111
+ :xtn_id => 6,
112
+ :cleared => true,
113
+ :cost => "10.0"
114
+ },
115
+ {
116
+ :xtn_date => Date.new(2012,1,2),
117
+ :checknum => nil,
118
+ :note => 'Lunch',
119
+ :account => 'Assets:Checking',
120
+ :commodity => '$',
121
+ :amount => "-10.0",
122
+ :tags => '',
123
+ :xtn_month => Date.new(2012,1,1),
124
+ :xtn_year => Date.new(2012,1,1),
125
+ :virtual => false,
126
+ :xtn_id => 6,
127
+ :cleared => true,
128
+ :cost => "-10.0"
129
+ },
130
+ ])
131
+
132
+ end
133
+
134
+ it "should load the database from a csv file containing quoted things" do
135
+ set_config :ledger_file, fixture('quoted')
136
+ file = LedgerWeb::Database.dump_ledger_to_csv
137
+ count = LedgerWeb::Database.load_database(file)
138
+ count.should eq(5)
139
+
140
+ LedgerWeb::Database.handle[:ledger].count().should eq(5)
141
+
142
+ convert_bd_to_string(LedgerWeb::Database.handle[:ledger].all()).should eq([
143
+ {
144
+ :xtn_date => Date.new(2012,1,1),
145
+ :checknum => nil,
146
+ :note => 'Transaction One',
147
+ :account => 'Assets:Savings',
148
+ :commodity => '"Foo 123"',
149
+ :amount => "100.0",
150
+ :tags => '',
151
+ :xtn_month => Date.new(2012,1,1),
152
+ :xtn_year => Date.new(2012,1,1),
153
+ :virtual => false,
154
+ :xtn_id => 1,
155
+ :cleared => true,
156
+ :cost => "100.0"
157
+ },
158
+ {
159
+ :xtn_date => Date.new(2012,1,1),
160
+ :checknum => nil,
161
+ :note => 'Transaction One',
162
+ :account => 'Assets:Checking',
163
+ :commodity => '"Foo 123"',
164
+ :amount => "200.0",
165
+ :tags => '',
166
+ :xtn_month => Date.new(2012,1,1),
167
+ :xtn_year => Date.new(2012,1,1),
168
+ :virtual => false,
169
+ :xtn_id => 1,
170
+ :cleared => true,
171
+ :cost => "200.0"
172
+ },
173
+ {
174
+ :xtn_date => Date.new(2012,1,1),
175
+ :checknum => nil,
176
+ :note => 'Transaction One',
177
+ :account => 'Equity:Opening Balances',
178
+ :commodity => '"Foo 123"',
179
+ :amount => "-300.0",
180
+ :tags => '',
181
+ :xtn_month => Date.new(2012,1,1),
182
+ :xtn_year => Date.new(2012,1,1),
183
+ :virtual => false,
184
+ :xtn_id => 1,
185
+ :cleared => true,
186
+ :cost => "-300.0"
187
+ },
188
+ {
189
+ :xtn_date => Date.new(2012,1,2),
190
+ :checknum => nil,
191
+ :note => 'Lunch',
192
+ :account => 'Expenses:Lunch',
193
+ :commodity => '"Foo 123"',
194
+ :amount => "10.0",
195
+ :tags => '',
196
+ :xtn_month => Date.new(2012,1,1),
197
+ :xtn_year => Date.new(2012,1,1),
198
+ :virtual => false,
199
+ :xtn_id => 6,
200
+ :cleared => true,
201
+ :cost => "10.0"
202
+ },
203
+ {
204
+ :xtn_date => Date.new(2012,1,2),
205
+ :checknum => nil,
206
+ :note => 'Lunch',
207
+ :account => 'Assets:Checking',
208
+ :commodity => '"Foo 123"',
209
+ :amount => "-10.0",
210
+ :tags => '',
211
+ :xtn_month => Date.new(2012,1,1),
212
+ :xtn_year => Date.new(2012,1,1),
213
+ :virtual => false,
214
+ :xtn_id => 6,
215
+ :cleared => true,
216
+ :cost => "-10.0"
217
+ },
218
+ ])
219
+
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,8 @@
1
+ 2012/01/01 * Transaction One
2
+ Assets:Savings 100.00 "Foo 123"
3
+ Assets:Checking 200.00 "Foo 123"
4
+ Equity:Opening Balances
5
+
6
+ 2012/01/02 * Lunch
7
+ Expenses:Lunch 10.00 "Foo 123"
8
+ Assets:Checking
@@ -0,0 +1,8 @@
1
+ 2012/01/01 * Transaction One
2
+ Assets:Savings $100.00
3
+ Assets:Checking $200.00
4
+ Equity:Opening Balances
5
+
6
+ 2012/01/02 * Lunch
7
+ Expenses:Lunch $10.00
8
+ Assets:Checking
@@ -0,0 +1,53 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/spec_helper")
2
+ require 'ledger_web/report'
3
+
4
+ describe LedgerWeb::Report do
5
+ describe "#from_query" do
6
+ it "should run the query" do
7
+
8
+ LedgerWeb::Report.session = {:from => '2012/01/01', :to => '2012/01/01'}
9
+
10
+ load_fixture('small')
11
+
12
+ report = LedgerWeb::Report.from_query("select count(1) as foo from ledger")
13
+ rows = []
14
+ report.each_row do |row|
15
+ rows << row
16
+ end
17
+
18
+ rows[0][0][0].should eq(5)
19
+ rows[0][0][1].should eq(LedgerWeb::Field.new('foo', 'number', 'pull-right'))
20
+ end
21
+ end
22
+
23
+ describe "#pivot" do
24
+ it "should create the correct fields" do
25
+ LedgerWeb::Report.session = {:from => '2012/01/01', :to => '2012/01/01'}
26
+ load_fixture('small')
27
+
28
+ report = LedgerWeb::Report.from_query("select xtn_month, account, sum(amount) from ledger group by xtn_month, account")
29
+ report = report.pivot("account", "asc")
30
+
31
+ report.fields.should eq([
32
+ string_field('xtn_month'),
33
+ number_field('Assets:Checking'),
34
+ number_field('Assets:Savings'),
35
+ number_field('Equity:Opening Balances'),
36
+ number_field('Expenses:Lunch')
37
+ ])
38
+
39
+ end
40
+
41
+ it "should put the values in the right place" do
42
+ LedgerWeb::Report.session = {:from => '2012/01/01', :to => '2012/01/01'}
43
+ load_fixture('small')
44
+
45
+ report = LedgerWeb::Report.from_query("select xtn_month, account, sum(amount)::integer from ledger group by xtn_month, account")
46
+ report = report.pivot("account", "asc")
47
+
48
+ report.rows.should eq([
49
+ [Date.new(2012,1,1), 190, 100, -300, 10]
50
+ ])
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,73 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
2
+
3
+ require 'rspec'
4
+ require 'ledger_web/config'
5
+ require 'ledger_web/db'
6
+ require 'ledger_web/report'
7
+ require 'database_cleaner'
8
+
9
+ RSpec.configure do |config|
10
+
11
+ config.before(:suite) do
12
+
13
+ system 'createdb ledger-test'
14
+ LedgerWeb::Config.should_load_user_config = false
15
+ LedgerWeb::Config.instance.set :database_url, 'postgres://localhost/ledger-test'
16
+ LedgerWeb::Database.connect
17
+ LedgerWeb::Database.run_migrations
18
+
19
+ DatabaseCleaner.strategy = :truncation
20
+ DatabaseCleaner.clean_with(:truncation)
21
+ end
22
+
23
+ config.before(:each) do
24
+ DatabaseCleaner.start
25
+ end
26
+
27
+ config.after(:each) do
28
+ DatabaseCleaner.clean
29
+ end
30
+
31
+ config.after(:suite) do
32
+ LedgerWeb::Database.close
33
+ system 'dropdb ledger-test'
34
+ end
35
+
36
+ end
37
+
38
+ def set_config(key, val)
39
+ LedgerWeb::Config.instance.set key, val
40
+ end
41
+
42
+ def fixture(name)
43
+ File.join(File.dirname(__FILE__), "fixtures", name + ".dat")
44
+ end
45
+
46
+ def convert_bd_to_string(objs)
47
+ objs.map do |obj|
48
+ obj.each do |k,v|
49
+ if v.is_a? BigDecimal
50
+ obj[k] = v.truncate(2).to_s('F')
51
+ end
52
+ end
53
+ obj
54
+ end
55
+ end
56
+
57
+ def load_fixture(name)
58
+ set_config :ledger_file, fixture(name)
59
+ file = LedgerWeb::Database.dump_ledger_to_csv
60
+ LedgerWeb::Database.load_database(file)
61
+ end
62
+
63
+ def field(name, type, css_class)
64
+ LedgerWeb::Field.new(name, type, css_class)
65
+ end
66
+
67
+ def string_field(name)
68
+ field(name, 'string', 'pull-left')
69
+ end
70
+
71
+ def number_field(name)
72
+ field(name, 'number', 'pull-right')
73
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ledger_web
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.4.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-17 00:00:00.000000000Z
12
+ date: 2012-03-18 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pg
16
- requirement: &2152703100 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2152703100
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: sequel
27
- requirement: &2152702540 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: '0'
33
38
  type: :runtime
34
39
  prerelease: false
35
- version_requirements: *2152702540
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: directory_watcher
38
- requirement: &2152701980 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,10 +53,15 @@ dependencies:
43
53
  version: '0'
44
54
  type: :runtime
45
55
  prerelease: false
46
- version_requirements: *2152701980
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: rack
49
- requirement: &2152701140 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ! '>='
@@ -54,10 +69,15 @@ dependencies:
54
69
  version: 1.3.6
55
70
  type: :runtime
56
71
  prerelease: false
57
- version_requirements: *2152701140
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 1.3.6
58
78
  - !ruby/object:Gem::Dependency
59
79
  name: sinatra
60
- requirement: &2152700700 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
61
81
  none: false
62
82
  requirements:
63
83
  - - ! '>='
@@ -65,10 +85,15 @@ dependencies:
65
85
  version: '0'
66
86
  type: :runtime
67
87
  prerelease: false
68
- version_requirements: *2152700700
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
69
94
  - !ruby/object:Gem::Dependency
70
95
  name: sinatra-session
71
- requirement: &2152699580 !ruby/object:Gem::Requirement
96
+ requirement: !ruby/object:Gem::Requirement
72
97
  none: false
73
98
  requirements:
74
99
  - - ! '>='
@@ -76,10 +101,15 @@ dependencies:
76
101
  version: '0'
77
102
  type: :runtime
78
103
  prerelease: false
79
- version_requirements: *2152699580
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
80
110
  - !ruby/object:Gem::Dependency
81
111
  name: sinatra-contrib
82
- requirement: &2152699100 !ruby/object:Gem::Requirement
112
+ requirement: !ruby/object:Gem::Requirement
83
113
  none: false
84
114
  requirements:
85
115
  - - ! '>='
@@ -87,7 +117,44 @@ dependencies:
87
117
  version: '0'
88
118
  type: :runtime
89
119
  prerelease: false
90
- version_requirements: *2152699100
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: rspec
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :runtime
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: database_cleaner
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :runtime
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
91
158
  description: Allows arbitrary reporting on your ledger using easy-to-write SQL queries
92
159
  email:
93
160
  - pete@bugsplat.info
@@ -228,6 +295,12 @@ files:
228
295
  - lib/ledger_web/views/table.erb
229
296
  - lib/ledger_web/views/visualization.erb
230
297
  - lib/ledger_web/watcher.rb
298
+ - test/config_spec.rb
299
+ - test/database_load_spec.rb
300
+ - test/fixtures/quoted.dat
301
+ - test/fixtures/small.dat
302
+ - test/report_spec.rb
303
+ - test/spec_helper.rb
231
304
  homepage: https://github.com/peterkeen/ledger-web
232
305
  licenses: []
233
306
  post_install_message:
@@ -248,9 +321,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
248
321
  version: '0'
249
322
  requirements: []
250
323
  rubyforge_project:
251
- rubygems_version: 1.8.13
324
+ rubygems_version: 1.8.19
252
325
  signing_key:
253
326
  specification_version: 3
254
327
  summary: A web-based, sql-backed front-end for the Ledger command-line accounting
255
328
  system
256
- test_files: []
329
+ test_files:
330
+ - test/config_spec.rb
331
+ - test/database_load_spec.rb
332
+ - test/fixtures/quoted.dat
333
+ - test/fixtures/small.dat
334
+ - test/report_spec.rb
335
+ - test/spec_helper.rb