thefox-wallet 0.16.0 → 0.17.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 371beb2557b5d4b13a93fa29ec2a4314d3988253
4
- data.tar.gz: 557b1b3fd12d91150875cae58ca6a7226f2aaa1a
3
+ metadata.gz: f1d2d7b98050da769b3220c1b93ae33fa2d66732
4
+ data.tar.gz: 901db16b1cc71053ccbe69b11a71b03f27e460a0
5
5
  SHA512:
6
- metadata.gz: 626f9cc9eb31732942d00247bed4059b0652923a11b842e46b54803817c6abd7a3ca489ad0acc441d44f6d158910e0553c0afc2d9654ea51920287e29063f378
7
- data.tar.gz: 7fe39b9adf2b48a9d95d1cc89e1e4b3f2c7152ff3159442c3191b36b2962bc4cae53f12787540df836ea26b005efd20fff1d0d2db45364dff18dccd8d4c3ad0d
6
+ metadata.gz: 0102a8e412d49f20f5b2123c45c4834546b82037cdbecd564f44fb696734f174bae911e802fb6d297c7b379b7fc9ff091b4ac1d706a5eef4f1fb37f3461f50c0
7
+ data.tar.gz: bbf235d00f3b8aad963226867ee385c713ffa02ee77c6e49fbbf58e9a5186e1b09e75ef82637e45bf3c39b8e0e3c658aaa8fe6395a4923104707b8b792ecf592
data/.gitlab-ci.yml CHANGED
@@ -35,8 +35,17 @@ test_23:
35
35
  script:
36
36
  - make test
37
37
 
38
- release_gem:
39
- image: ruby:2.3
38
+ test_24:
39
+ image: ruby:2.4
40
+ stage: test
41
+ environment: test
42
+ only:
43
+ - tags
44
+ script:
45
+ - make test
46
+
47
+ release:
48
+ image: ruby:2.4
40
49
  stage: release
41
50
  environment: gem
42
51
  only:
data/.travis.yml CHANGED
@@ -3,7 +3,7 @@ rvm:
3
3
  - 2.1
4
4
  - 2.2
5
5
  - 2.3
6
- - 2.4.0-preview2
6
+ - 2.4.0
7
7
  - ruby-head
8
8
  sudo: required
9
9
  before_install:
data/Makefile CHANGED
@@ -5,7 +5,7 @@ include Makefile.common
5
5
 
6
6
  .PHONY: dev
7
7
  dev:
8
- $(BUNDLER) exec ./bin/wallet -w wallet_test2 add -t 'test' -e 5 -c 'test'
8
+ $(BUNDLER) exec ./bin/wallet -w wallet_test2 add -t 'Taxes Q1' -e 5 -c Company
9
9
 
10
10
  .PHONY: test
11
11
  test:
@@ -18,3 +18,12 @@ cov:
18
18
  .PHONY: cov_local
19
19
  cov_local:
20
20
  RUBYOPT=-w TZ=Europe/Vienna SIMPLECOV_PHPUNIT_LOAD_PATH=../simplecov-phpunit COVERAGE=1 $(BUNDLER) exec ./test/suite_all.rb -v
21
+
22
+ README.html: README.md
23
+ mkreadme
24
+
25
+ watch_readme:
26
+ while true; do \
27
+ $(MAKE) README.html; \
28
+ sleep 1; \
29
+ done
data/README.md CHANGED
@@ -13,7 +13,7 @@ While conventionally programs like Microsoft Excel or [LibreOffice](https://www.
13
13
  - Import CSV files.
14
14
  - Export data to CSV.
15
15
  - Generate HTML summary.
16
- - VI/editor import mode. (CSV)
16
+ - Categories
17
17
 
18
18
  ## Install
19
19
 
@@ -24,16 +24,136 @@ The preferred method of installation is via RubyGems.org:
24
24
 
25
25
  or via `Gemfile`:
26
26
 
27
- gem 'thefox-wallet', '~>0.15'
27
+ gem 'thefox-wallet', '~>0.16'
28
28
 
29
29
  Use it in your sources:
30
30
 
31
31
  require 'thefox-wallet'
32
32
 
33
+ ## Options
34
+
35
+ - `-w`, `--wallet <path>`
36
+ Base path to a wallet directory. Each wallet has its own directory storing all informations. This option can be used for all comamnds.
37
+ - `--id <id>`
38
+ ID used for a new entry. If an ID is provided no new entry will be added if an entry with the same ID already exists. Use `--force` to overwrite this.
39
+ - `-t`, `--title <title>`
40
+ Title used for a new entry.
41
+ - `-d`, `--date <date>`
42
+ Date used for a new entry.
43
+ - `--start <date>`
44
+ Start-date used for a range.
45
+ - `--end <date>`
46
+ End-date used for a range.
47
+ - `-r`, `--revenue <revenue>`
48
+ Revenue used for a new entry.
49
+ - `-e`, `--expense <expense>`
50
+ Expense used for a new entry.
51
+ - `-c`, `--category <category>`
52
+ Category used for a new entry.
53
+ - `-o`, `--comment <comment>`
54
+ Comment used for a new entry.
55
+ - `--import`, `--export`
56
+ Import/Export CSV
57
+ - `-p`, `--path <path>`
58
+ Path used for `csv` import/export and `html` directory path.
59
+ - `-i`, `--interactive`
60
+ Use some commands interactively.
61
+ - `-f`, `--force`, `--no-force`
62
+ Force (or no force) `add` command. See `--id` option.
63
+ - `-v`, `--verbose`
64
+ Log on debug level.
65
+ - `-V`, `--version`
66
+ Show version.
67
+ - `-h`, `--help`
68
+ Show help page.
69
+
70
+ ## Commands
71
+
72
+ ### Add Command
73
+
74
+ Add a new entry.
75
+
76
+ $ wallet add [-w <path>] [--id <id>] [-r <revenue>] [-e <expense>] [-c <category>] [-o <comment>] [-i] [-f|--no-force] -t|--title <title>
77
+
78
+ When `--interactive` (`-i`) option is used, parse `%d` with `printf`. Separate multiple `%`-variables with `,`. This feature can be used on template scripts that run the `wallet add` command with pre-defined texts.
79
+
80
+ For example. To use the following in a template script
81
+
82
+ $ wallet add --title 'Income tax %d/Q%d' --interactive
83
+
84
+ and set the values on interactive input when the command is running.
85
+
86
+ ```bash
87
+ $ wallet add --title 'Income tax %d/Q%d' --interactive
88
+ title: [Income tax %d/Q%d] 2017,1
89
+ ```
90
+
91
+ This would set the title to `Income tax 2017/Q1`. So the input will not be set as value rather than replaced by the variables in the template text. Acting like `printf`.
92
+
93
+ Expenses are always converted to minus.
94
+
95
+ Calculations will be `eval`ed as Ruby code. For example:
96
+
97
+ $ wallet add --title Test --expense 14+7
98
+
99
+ The expense will be `-21`. Expenses are always minus.
100
+
101
+ In the following example the expense will be `-3`:
102
+
103
+ $ wallet add --title Test --expense 10-7
104
+
105
+ The same applies to revenue.
106
+
107
+ See `AddCommand::revenue` and `AddCommand::expense` functions.
108
+
109
+ ### Categories Command
110
+
111
+ List all used categories.
112
+
113
+ $ wallet categories [-w <path>]
114
+
115
+ Each entry can have one category. It's planned to implement [Multiple Categories](https://github.com/TheFox/wallet/issues/3) for entries.
116
+
117
+ You can define the categories yourself. The `list` command has a filter option `-c` to list all entries of a certain category. The `html` command (for generating a HTML output) will also sum all entries for each category. If the category is not set on `add` command the category will be set to `default`. The `default` category will not be shown in list views.
118
+
119
+ ### Clear Command
120
+
121
+ Clear temp and cache files.
122
+
123
+ $ wallet clear [-w <path>]
124
+
125
+ If the html directory path (`-p`) provided to the `html` command is outside of the wallet base path (`-w`) this directory will **NOT** be deleted by the `clear` command. If the default html directory (`wallet/html`) is used this directory will be removed. This command does **NOT** delete any entries stored at `wallet/data`.
126
+
127
+ ### CSV Command
128
+
129
+ Import or export to/from CSV file format.
130
+
131
+ $ wallet csv [-w <path>] [--import|--export] -p <path>
132
+
133
+ ### HTML Command
134
+
135
+ Exports a wallet as HTML. List all years in an index HTML file and all months for each year. Generates a HTML file for each month based on entries.
136
+
137
+ $ wallet html [-w <path>] [--start <date>] [--end <date>] [-c <category,...>] [-p <path>] [-v]
138
+
139
+ Option `-c` can take multiple categories separated by `,`.
140
+
141
+ ### List Command
142
+
143
+ List entries. Per default this command lists all entries of today's date.
144
+
145
+ $ wallet list [-w <path>] [-d <YYYY>[-<MM>[-<DD>]]] [-c <category>]
146
+
147
+ You can either provide a year `YYYY`, a month `YYYY-MM` or a day `YYYY-MM-DD`.
148
+
149
+ ## Dates
150
+
151
+ Dates (`<date>` or `YYYY-MM-DD`) used in this documentation are not limited to `YYYY-MM-DD` format. You can also use `MM/DD/YYYY`, `DD.MM.YYYY`, `YYYYMMDD`, etc. Dates are parsed by [`Date::parse`](https://ruby-doc.org/stdlib-1.9.3/libdoc/date/rdoc/DateTime.html#method-c-parse).
152
+
33
153
  ## Project Links
34
154
 
35
155
  - [Blog Post about Wallet](http://blog.fox21.at/2015/07/09/wallet.html)
36
- - [Gem](https://rubygems.org/gems/thefox-wallet)
156
+ - [Wallet Gem](https://rubygems.org/gems/thefox-wallet)
37
157
  - [Travis CI Repository](https://travis-ci.org/TheFox/wallet)
38
158
 
39
159
  ## License
data/bin/wallet CHANGED
@@ -20,56 +20,59 @@ opts = OptionParser.new do |o|
20
20
  o.separator(' categories List categories')
21
21
  o.separator(' clear Clear temp and cache files')
22
22
  o.separator(' csv Import/Export CSV file')
23
- o.separator(' html Generate HTML files')
23
+ o.separator(' html Exports a wallet as HTML')
24
24
  o.separator(' list List entries')
25
- o.separator(' vi Import per VIM editor')
26
25
  o.separator('')
27
26
 
28
27
  o.on('-w', '--wallet <path>', 'Path to wallet directory.') do |path|
29
28
  @options[:wallet_path] = Pathname.new(path).expand_path
30
29
  end
31
30
 
32
- o.on('--id <id>', 'ID used for the entry.') do |id|
31
+ o.on('--id <id>', 'ID used for a new entry.') do |id|
33
32
  @options[:entry_id] = id
34
33
  end
35
34
 
36
- o.on('-t', '--title <title>', 'Title used for the entry.') do |title|
35
+ o.on('-t', '--title <title>', 'Title used for a new entry.') do |title|
37
36
  @options[:entry_title] = title
38
37
  end
39
38
 
40
- o.on('-d', '--date <date>', 'Date used for the entry.') do |date|
39
+ o.on('-d', '--date <YYYY-MM-DD>', 'Date used for a new entry.') do |date|
41
40
  @options[:entry_date] = date
42
41
  end
43
42
 
44
- o.on('--start <date>', 'Start date used for a range.') do |date|
43
+ o.on('--start <YYYY-MM-DD>', 'Start-date used for a range.') do |date|
45
44
  @options[:entry_date_start] = Date.parse(date)
46
45
  end
47
46
 
48
- o.on('--end <date>', 'End date used for a range.') do |date|
47
+ o.on('--end <YYYY-MM-DD>', 'End-date used for a range.') do |date|
49
48
  @options[:entry_date_end] = Date.parse(date)
50
49
  end
51
50
 
52
- o.on('-r', '--revenue <revenue>', 'Revenue used for the entry.') do |revenue|
53
- @options[:entry_revenue] = revenue.to_s.sub(/,/, '.').to_f.abs
51
+ o.on('-r', '--revenue <revenue>', 'Revenue used for a new entry.') do |revenue|
52
+ # @TODO replace with Wumber.
53
+ # @options[:entry_revenue] = revenue.to_s.sub(/,/, '.').to_f.abs
54
+ @options[:entry_revenue] = eval(revenue.to_s.to_s.gsub(/,/, '.')).to_f.round(TheFox::Wallet::NUMBER_ROUND).abs
54
55
  end
55
56
 
56
- o.on('-e', '--expense <expense>', 'Expense used for the entry.') do |expense|
57
- @options[:entry_expense] = -expense.to_s.sub(/,/, '.').to_f.abs
57
+ o.on('-e', '--expense <expense>', 'Expense used for a new entry.') do |expense|
58
+ # @TODO replace with Wumber.
59
+ # @options[:entry_expense] = -expense.to_s.sub(/,/, '.').to_f.abs
60
+ @options[:entry_expense] = -eval(expense.to_s.gsub(/,/, '.')).to_f.round(TheFox::Wallet::NUMBER_ROUND).abs
58
61
  end
59
62
 
60
- o.on('-c', '--category <category>', 'Category used for the entry.') do |category|
63
+ o.on('-c', '--category <category>', 'Category used for a new entry.') do |category|
61
64
  @options[:entry_category] = category
62
65
  end
63
66
 
64
- o.on('-o', '--comment <comment>', 'Comment used for the entry.') do |comment|
67
+ o.on('-o', '--comment <comment>', 'Comment used for a new entry.') do |comment|
65
68
  @options[:entry_comment] = comment
66
69
  end
67
70
 
68
- o.on('--import', 'Import') do
71
+ o.on('--import', 'Import CSV') do
69
72
  @options[:is_import] = true
70
73
  end
71
74
 
72
- o.on('--export', 'Export') do
75
+ o.on('--export', 'Export CSV') do
73
76
  @options[:is_export] = true
74
77
  end
75
78
 
@@ -118,5 +121,6 @@ if command_name
118
121
  exit 1
119
122
  end
120
123
  else
124
+ # Show help page when no argument was given.
121
125
  opts.parse(['-h'])
122
126
  end
@@ -1,4 +1,7 @@
1
1
 
2
+ # Basic Command
3
+ # All other commands subclass from this class.
4
+
2
5
  require 'date'
3
6
  require 'pathname'
4
7
 
@@ -9,9 +12,9 @@ module TheFox::Wallet
9
12
  NAME = 'default'
10
13
 
11
14
  def initialize(options = Hash.new)
12
- @options = options || Hash.new
13
- @options[:logger] ||= nil
14
-
15
+ # Initialize all options.
16
+ @options = options
17
+ @options[:logger] ||= Logger.new(IO::NULL)
15
18
  @options[:wallet_path] ||= Pathname.new('wallet')
16
19
  @options[:entry_id] ||= nil
17
20
  @options[:entry_title] ||= nil
@@ -32,7 +35,7 @@ module TheFox::Wallet
32
35
  def run
33
36
  end
34
37
 
35
- def self.create_by_name(name, options = nil)
38
+ def self.create_by_name(name, options = Hash.new)
36
39
  classes = [
37
40
  AddCommand,
38
41
  CategoriesCommand,
@@ -42,10 +45,12 @@ module TheFox::Wallet
42
45
  ListCommand,
43
46
  ]
44
47
 
48
+ # Search class by name.
45
49
  classes.each do |cclass|
46
- #puts "class: '#{cclass::NAME}' '#{cclass.is_matching_class(name)}'"
47
50
  if cclass.is_matching_class(name)
48
- return cclass.new(options)
51
+ # Create a new Object from the found Class.
52
+ cmd = cclass.new(options)
53
+ return cmd
49
54
  end
50
55
  end
51
56
 
@@ -3,6 +3,7 @@ require 'date'
3
3
 
4
4
  module TheFox::Wallet
5
5
 
6
+ # Add a new Entry.
6
7
  class AddCommand < Command
7
8
 
8
9
  NAME = 'add'
@@ -13,15 +14,18 @@ module TheFox::Wallet
13
14
  end
14
15
 
15
16
  if @options[:is_interactively]
17
+ # Interactive User Input
18
+
16
19
  print "title: [#{@options[:entry_title]}] "
17
20
  title_t = STDIN.gets.strip
18
21
  if title_t.length > 0
22
+ # Search for '%d' in title.
19
23
  if @options[:entry_title] =~ /%d/
24
+ # Like sprintf.
20
25
  @options[:entry_title] = @options[:entry_title] % title_t.split(',').map{ |s| s.strip }
21
26
  else
22
27
  @options[:entry_title] = title_t
23
28
  end
24
- puts "title: #{@options[:entry_title]}"
25
29
  end
26
30
 
27
31
  print "date: [#{@options[:entry_date]}] "
@@ -61,6 +65,8 @@ module TheFox::Wallet
61
65
  raise "Option --title is required for command '#{NAME}'"
62
66
  end
63
67
 
68
+ # --force option
69
+ # --id option
64
70
  is_unique = !@options[:force] && @options[:entry_id]
65
71
 
66
72
  puts "id: '#{@options[:entry_id]}'"
@@ -74,8 +80,13 @@ module TheFox::Wallet
74
80
  puts "force: #{@options[:force] ? 'YES' : 'NO'}"
75
81
  puts "unique: #{is_unique ? 'YES' : 'NO'} (#{is_unique})"
76
82
 
83
+ # Create new Entry.
77
84
  entry = Entry.new(@options[:entry_id], @options[:entry_title], @options[:entry_date], @options[:entry_revenue], @options[:entry_expense], @options[:entry_category], @options[:entry_comment])
85
+
86
+ # Initialize Wallet.
78
87
  wallet = Wallet.new(@options[:wallet_path])
88
+
89
+ # Add Entry to Wallet.
79
90
  added = wallet.add(entry, is_unique)
80
91
 
81
92
  puts "added: #{added ? 'YES' : 'NO'}"
@@ -83,20 +94,23 @@ module TheFox::Wallet
83
94
  added
84
95
  end
85
96
 
97
+ # @TODO replace with Wumber.
86
98
  def revenue(revenue_s)
87
- rv = nil
88
99
  if !revenue_s.nil? && revenue_s.length > 0
89
- rv = eval(revenue_s.to_s.gsub(/,/, '.')).to_f.round(NUMBER_ROUND).abs
100
+ # Replace , with . in numbers. '1,23' means '1.23'.
101
+ # eval the revenue so calculations are solved.
102
+ eval(revenue_s.to_s.gsub(/,/, '.')).to_f.round(NUMBER_ROUND).abs
90
103
  end
91
- rv
92
104
  end
93
105
 
106
+ # @TODO replace with Wumber.
94
107
  def expense(expense_s)
95
- rv = nil
96
108
  if !expense_s.nil? && expense_s.length > 0
97
- rv = -eval(expense_s.to_s.gsub(/,/, '.')).to_f.round(NUMBER_ROUND).abs
109
+ # Replace , with . in numbers. '1,23' means '1.23'.
110
+ # eval the revenue so calculations are solved.
111
+ # Expenses are always minus.
112
+ -eval(expense_s.to_s.gsub(/,/, '.')).to_f.round(NUMBER_ROUND).abs
98
113
  end
99
- rv
100
114
  end
101
115
 
102
116
  end
@@ -1,6 +1,7 @@
1
1
 
2
2
  module TheFox::Wallet
3
3
 
4
+ # List all used categories.
4
5
  class CategoriesCommand < Command
5
6
 
6
7
  NAME = 'categories'
@@ -3,6 +3,7 @@ require 'pathname'
3
3
 
4
4
  module TheFox::Wallet
5
5
 
6
+ # Clear temp and cache files.
6
7
  class ClearCommand < Command
7
8
 
8
9
  NAME = 'clear'
@@ -1,6 +1,7 @@
1
1
 
2
2
  module TheFox::Wallet
3
3
 
4
+ # Import or export to/from CSV file format.
4
5
  class CsvCommand < Command
5
6
 
6
7
  NAME = 'csv'
@@ -13,11 +14,14 @@ module TheFox::Wallet
13
14
  wallet = Wallet.new(@options[:wallet_path])
14
15
  wallet.logger = @options[:logger]
15
16
 
17
+ # Import by default.
16
18
  if @options[:is_import] || !@options[:is_export]
19
+ # Import
17
20
  @options[:logger].info("import csv #{@options[:path]} ...") if @options[:logger]
18
21
  wallet.import_csv_file(@options[:path])
19
22
  @options[:logger].info("import csv #{@options[:path]} done") if @options[:logger]
20
23
  elsif @options[:is_export]
24
+ # Export
21
25
  @options[:logger].info("export csv #{@options[:path]} ...") if @options[:logger]
22
26
  wallet.export_csv_file(@options[:path])
23
27
  @options[:logger].info("export csv #{@options[:path]} done") if @options[:logger]
@@ -3,6 +3,9 @@ require 'pathname'
3
3
 
4
4
  module TheFox::Wallet
5
5
 
6
+ # Exports a wallet as HTML.
7
+ # List all years in an index HTML file and all months for each year.
8
+ # Generates a HTML file for each month based on entries.
6
9
  class HtmlCommand < Command
7
10
 
8
11
  NAME = 'html'
@@ -1,6 +1,8 @@
1
1
 
2
2
  module TheFox::Wallet
3
3
 
4
+ # List entries. Per default this command lists all entries of today.
5
+ # @TODO Use terminal-table for output? https://github.com/tj/terminal-table
4
6
  class ListCommand < Command
5
7
 
6
8
  NAME = 'list'
@@ -10,8 +12,8 @@ module TheFox::Wallet
10
12
 
11
13
  wallet = Wallet.new(@options[:wallet_path])
12
14
  entries = wallet.entries(@options[:entry_date], @options[:entry_category].to_s)
13
- #entries = wallet.entries(@options[:entry_date], @options[:entry_end_date], @options[:entry_category].to_s)
14
15
 
16
+ # Get max length of all columns.
15
17
  entries_l = entries
16
18
  .map{ |day_name, day_items| day_items.count }
17
19
  .inject{ |sum, n| sum + n }
@@ -55,6 +57,7 @@ module TheFox::Wallet
55
57
  .select{ |i| i != '' }
56
58
  .count > 0
57
59
 
60
+ # Limit some columns to a maximum length.
58
61
  if title_l < 6
59
62
  title_l = 6
60
63
  end
@@ -78,6 +81,7 @@ module TheFox::Wallet
78
81
  comment_l = 25
79
82
  end
80
83
 
84
+ # Format String for each column.
81
85
  entries_f = "%#{entries_l}s"
82
86
  title_f = "%-#{title_l}s"
83
87
  revenue_f = "%#{revenue_l}s"
@@ -86,6 +90,7 @@ module TheFox::Wallet
86
90
  category_f = "%-#{category_l}s"
87
91
  comment_f = "%-#{comment_l}s"
88
92
 
93
+ # Create a table header.
89
94
  header = ''
90
95
  header << '#' * entries_l << ' '
91
96
  header << 'Date ' << ' ' * 7
@@ -102,15 +107,24 @@ module TheFox::Wallet
102
107
 
103
108
  header_l = header.length
104
109
  header.sub!(/ +$/, '')
110
+
111
+ # Print table header.
105
112
  puts header
106
113
  puts '-' * header_l
107
114
 
115
+ # Sums
108
116
  revenue_total = 0.0
109
117
  expense_total = 0.0
110
118
  balance_total = 0.0
119
+
120
+ # Do not repeat the same date over and over again.
111
121
  previous_date = ''
122
+
112
123
  entry_no = 0
124
+
125
+ # Iterate all days.
113
126
  entries.sort.each do |day_name, day_items|
127
+ # Iterate all entries of a day.
114
128
  day_items.each do |entry|
115
129
  entry_no += 1
116
130
 
@@ -124,7 +138,6 @@ module TheFox::Wallet
124
138
  balance_total += entry['balance']
125
139
 
126
140
  category = entry['category'] == 'default' ? '' : entry['category']
127
- # has_category = category != ''
128
141
 
129
142
  comment = entry['comment']
130
143
  if comment.length >= 25
data/lib/wallet/entry.rb CHANGED
@@ -37,6 +37,7 @@ module TheFox
37
37
  revenue_t = revenue.to_f
38
38
  expense_t = expense.to_f
39
39
  if revenue_t < 0 && expense_t == 0
40
+ # Revenue is minus and no expense was provided.
40
41
  self.revenue = 0.0
41
42
  self.expense = revenue_t
42
43
  else
@@ -57,19 +58,17 @@ module TheFox
57
58
  end
58
59
 
59
60
  def date=(date)
60
- # puts "class: #{date.class}"
61
61
  case date
62
62
  when String
63
- # puts 'String'
63
+ # String
64
64
  @date = Date.parse(date)
65
65
  when Fixnum
66
- # puts 'Fixnum'
66
+ # Fixnum
67
67
  @date = Time.at(date).to_date
68
68
  when Date
69
- # puts 'Date'
69
+ # Date
70
70
  @date = date
71
71
  else
72
- # puts 'RAISE'
73
72
  raise ArgumentError, "Wrong class: #{date.class}"
74
73
  end
75
74
  end
@@ -104,6 +103,7 @@ module TheFox
104
103
  @comment = comment.nil? ? '' : comment.to_s
105
104
  end
106
105
 
106
+ # Convert Entry to a Hash.
107
107
  def to_h
108
108
  {
109
109
  'id' => @id,
@@ -117,6 +117,7 @@ module TheFox
117
117
  }
118
118
  end
119
119
 
120
+ # Restore a Entry from a Hash.
120
121
  def self.from_h(h)
121
122
  id = h['id']
122
123
  title = h['title']
@@ -2,8 +2,8 @@
2
2
  module TheFox
3
3
  module Wallet
4
4
  NAME = 'Wallet'
5
- VERSION = '0.16.0'
6
- DATE = '2017-02-19'
5
+ VERSION = '0.17.0'
6
+ DATE = '2017-08-12'
7
7
  HOMEPAGE = 'https://github.com/TheFox/wallet'
8
8
 
9
9
  NUMBER_FORMAT = '%.2f'
data/lib/wallet/wallet.rb CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- # Schmeisst die Fuffies durch den Club und schreit Boah Boah!
2
+ # Wallet Main Class
3
3
 
4
4
  require 'logger'
5
5
  require 'yaml'
@@ -7,8 +7,9 @@ require 'yaml/store'
7
7
  require 'csv'
8
8
  require 'pathname'
9
9
  require 'fileutils'
10
- require 'ostruct' # OpenStruct use to generate HTML.
11
- # require 'pp'
10
+
11
+ # OpenStruct use to generate HTML.
12
+ require 'ostruct'
12
13
 
13
14
  module TheFox
14
15
  module Wallet
@@ -20,12 +21,14 @@ module TheFox
20
21
 
21
22
  def initialize(dir_path = nil)
22
23
  @exit = false
23
- @logger = nil
24
+ @logger = Logger.new(IO::NULL)
24
25
  @dir_path = dir_path || Pathname.new('wallet').expand_path
25
26
  @dir_path_basename = @dir_path.basename
26
27
  @dir_path_basename_s = @dir_path_basename.to_s
27
28
  @data_path = Pathname.new('data').expand_path(@dir_path)
28
29
  @tmp_path = Pathname.new('tmp').expand_path(@dir_path)
30
+
31
+ # Internal path. Not the same as provided by --path option.
29
32
  @html_path = Pathname.new('html').expand_path(@dir_path)
30
33
 
31
34
  @has_transaction = false
@@ -37,11 +40,13 @@ module TheFox
37
40
  @entries_index_is_loaded = false
38
41
 
39
42
  Signal.trap('SIGINT') do
40
- #@logger.warn('received SIGINT. break ...') if @logger
43
+ #@logger.warn('received SIGINT. break ...')
41
44
  @exit = true
42
45
  end
43
46
  end
44
47
 
48
+ # Add an Entry to the wallet.
49
+ # Used by Add Command.
45
50
  def add(entry, is_unique = false)
46
51
  if !entry.is_a?(Entry)
47
52
  raise ArgumentError, 'variable must be a Entry instance'
@@ -166,6 +171,7 @@ module TheFox
166
171
  @transaction_files = Hash.new
167
172
  end
168
173
 
174
+ # Sums a year, a month, a day or a certain category.
169
175
  def sum(year = nil, month = nil, day = nil, category = nil)
170
176
  year_s = year.to_i.to_s
171
177
  month_f = '%02d' % month.to_i
@@ -225,8 +231,25 @@ module TheFox
225
231
  sum(nil, nil, nil, category)
226
232
  end
227
233
 
234
+ # Get all entries.
235
+ # Used by List Command.
228
236
  def entries(begin_date, category = nil)
229
- begin_year, begin_month, begin_day = begin_date.split('-').map{ |n| n.to_i }
237
+ begin_year, begin_month, begin_day = begin_date.split('-') #.map{ |n| n.to_i }
238
+
239
+ if begin_year.length > 4
240
+ # When begin_date got not splitted by '-'.
241
+ # YYYYM[MD[D]]
242
+
243
+ begin_month = begin_year[4..-1]
244
+ begin_year = begin_year[0..3]
245
+
246
+ if begin_month.length > 2
247
+ # YYYYMMD[D]
248
+
249
+ begin_day = begin_month[2..-1]
250
+ begin_month = begin_month[0..1]
251
+ end
252
+ end
230
253
 
231
254
  begin_year_s = begin_year.to_i.to_s
232
255
  begin_month_f = '%02d' % begin_month.to_i
@@ -243,7 +266,7 @@ module TheFox
243
266
 
244
267
  category = category.to_s.downcase
245
268
 
246
- entries_a = Hash.new
269
+ entries_h = Hash.new
247
270
  Dir[glob].each do |file_path|
248
271
 
249
272
  data = YAML.load_file(file_path)
@@ -251,21 +274,21 @@ module TheFox
251
274
  if begin_day
252
275
  day_key = "#{begin_year_s}-#{begin_month_f}-#{begin_day_f}"
253
276
  if data['days'].has_key?(day_key)
254
- entries_a[day_key] = data['days'][day_key]
277
+ entries_h[day_key] = data['days'][day_key]
255
278
  end
256
279
  else
257
- entries_a.merge!(data['days'])
280
+ entries_h.merge!(data['days'])
258
281
  end
259
282
  else
260
283
  if begin_day
261
284
  day_key = "#{begin_year_s}-#{begin_month_f}-#{begin_day_f}"
262
285
  if data['days'].has_key?(day_key)
263
- entries_a[day_key] = data['days'][day_key].keep_if{ |day_item|
286
+ entries_h[day_key] = data['days'][day_key].keep_if{ |day_item|
264
287
  day_item['category'].downcase == category
265
288
  }
266
289
  end
267
290
  else
268
- entries_a.merge!(data['days'].map{ |day_name, day_items|
291
+ entries_h.merge!(data['days'].map{ |day_name, day_items|
269
292
  day_items.keep_if{ |day_item|
270
293
  day_item['category'].downcase == category
271
294
  }
@@ -277,9 +300,11 @@ module TheFox
277
300
  end
278
301
 
279
302
  end
280
- entries_a
303
+ entries_h
281
304
  end
282
305
 
306
+ # Get all used categories.
307
+ # Used by Categories Command.
283
308
  def categories
284
309
  categories_h = Hash.new
285
310
  Dir[Pathname.new('month_*.yml').expand_path(@data_path)].each do |file_path|
@@ -304,14 +329,14 @@ module TheFox
304
329
  categories_a
305
330
  end
306
331
 
307
- ##
332
+ # Used by HTML Command.
308
333
  # Generate HTML files from date_start to date_end.
309
334
  def generate_html(html_path = nil, date_start = nil, date_end = nil, category = nil)
310
335
  # @FIXME use @exit on all loops in this function
311
336
 
312
337
  html_path ||= @html_path
313
338
 
314
- @logger.info("generate html to #{html_path} ...") if @logger
339
+ @logger.info("generate html to #{html_path} ...")
315
340
 
316
341
  create_dirs
317
342
 
@@ -352,6 +377,7 @@ module TheFox
352
377
  gitignore_file.write('*')
353
378
  gitignore_file.close
354
379
 
380
+ # Write CSS file.
355
381
  css_file_path = Pathname.new('style.css').expand_path(html_path)
356
382
  css_file = File.open(css_file_path, 'w')
357
383
  css_file.write('
@@ -396,7 +422,7 @@ module TheFox
396
422
  <link rel="stylesheet" href="style.css" type="text/css" />
397
423
  </head>
398
424
  <body>
399
- <h1><a href="index.html">' << @dir_path_basename_s << '</a></h1>
425
+ <h1><a href=".">' << @dir_path_basename_s << '</a></h1>
400
426
  <p>Generated @ ' << DateTime.now.strftime('%Y-%m-%d %H:%M:%S') << ' by <a href="' << HOMEPAGE << '">' << NAME << '</a> v' << VERSION << '</p>
401
427
 
402
428
  <h2>Year: ' << year_s << '</h2>
@@ -423,7 +449,7 @@ module TheFox
423
449
  categories_available.map{ |item| categories_year_balance[item] = 0.0 }
424
450
  year_total = Hash.new
425
451
 
426
- @logger.info("generate year #{year}") if @logger
452
+ @logger.info("generate year #{year}")
427
453
  @data_path.each_child do |file_path|
428
454
  file_name_p = file_path.basename
429
455
  file_name_s = file_name_p.to_s
@@ -476,7 +502,7 @@ module TheFox
476
502
  end
477
503
 
478
504
  if write_html
479
- @logger.debug("file: #{month_file_name_s} (from #{file_name_s})") if @logger
505
+ @logger.debug("file: #{month_file_name_s} (from #{file_name_s})")
480
506
 
481
507
  month_file = File.open(month_file_path, 'w')
482
508
  month_file.write('
@@ -487,7 +513,7 @@ module TheFox
487
513
  <link rel="stylesheet" href="style.css" type="text/css" />
488
514
  </head>
489
515
  <body>
490
- <h1><a href="index.html">' << @dir_path_basename_s << '</a></h1>
516
+ <h1><a href=".">' << @dir_path_basename_s << '</a></h1>
491
517
  <p>Generated @ ' << DateTime.now.strftime('%Y-%m-%d %H:%M:%S') << ' by <a href="' << HOMEPAGE << '">' << NAME << '</a> v' << VERSION << ' from <code>' << file_name_s << '</code></p>
492
518
 
493
519
  <h2>Month: ' << month_s << ' <a href="' << year_file_name_s << '">' << year_s << '</a></h2>
@@ -619,8 +645,10 @@ module TheFox
619
645
  yeardat_file_path = Pathname.new("year_#{year_s}.dat").expand_path(@tmp_path)
620
646
  yeardat_file = File.new(yeardat_file_path, 'w')
621
647
  yeardat_file.write(year_total
648
+ .sort{ |a, b| a[0] <=> b[0] }
622
649
  .map{ |k, m| "#{year_s}-#{m.month_s} #{m.revenue} #{m.expense} #{m.balance} #{m.balance_total} #{m.balance_total}" }
623
650
  .join("\n"))
651
+ yeardat_file.write("\n")
624
652
  yeardat_file.close
625
653
 
626
654
  gnuplot_file_path = Pathname.new("year_#{year_s}.gp").expand_path(@tmp_path)
@@ -727,10 +755,15 @@ module TheFox
727
755
  store['changes'] = html_options['changes']
728
756
  end
729
757
 
758
+ # Convert Years Totals to DAT file rows.
730
759
  totaldat_file_c = years_total.map{ |k, y| "#{y.year} #{y.revenue} #{y.expense} #{y.balance} #{y.balance_total}" }
760
+
761
+ # Print maximal 10 years on GNUPlot.
731
762
  if totaldat_file_c.count > 10
732
763
  totaldat_file_c = totaldat_file_c.slice(-10, 10)
733
764
  end
765
+
766
+ # Convert DAT file rows to one String.
734
767
  totaldat_file_c = totaldat_file_c.join("\n")
735
768
 
736
769
  # DAT file for GNUPlot.
@@ -768,9 +801,10 @@ module TheFox
768
801
 
769
802
  system("gnuplot #{gnuplot_file_path} &> /dev/null")
770
803
 
771
- @logger.info('generate html done') if @logger
804
+ @logger.info('generate html done')
772
805
  end
773
806
 
807
+ # Used by CSV Command.
774
808
  def import_csv_file(file_path)
775
809
  transaction_start
776
810
 
@@ -800,14 +834,15 @@ module TheFox
800
834
 
801
835
  added = add(Entry.new(id, title, date, revenue, expense, category, comment), true)
802
836
 
803
- @logger.debug("import row '#{id}' -- #{added ? 'YES' : 'NO'}") if @logger
837
+ @logger.debug("import row '#{id}' -- #{added ? 'YES' : 'NO'}")
804
838
  end
805
839
 
806
- @logger.info('save data ...') if @logger
840
+ @logger.info('save data ...')
807
841
 
808
842
  transaction_end
809
843
  end
810
844
 
845
+ # Used by CSV Command.
811
846
  def export_csv_file(file_path)
812
847
  csv_options = {
813
848
  :col_sep => ',',
@@ -820,7 +855,7 @@ module TheFox
820
855
  }
821
856
  CSV.open(file_path, 'wb', csv_options) do |csv|
822
857
  Dir[Pathname.new('month_*.yml').expand_path(@data_path)].each do |yaml_file_path|
823
- @logger.info("export #{File.basename(yaml_file_path)}") if @logger
858
+ @logger.info("export #{File.basename(yaml_file_path)}")
824
859
 
825
860
  data = YAML.load_file(yaml_file_path)
826
861
 
@@ -853,9 +888,11 @@ module TheFox
853
888
  @entries_index.include?(entry.id)
854
889
  end
855
890
 
891
+ # Build an entry-by-id Hash.
892
+ # ID => Entry
856
893
  def build_entry_by_id_index(force = false)
857
894
  if @entries_by_ids.nil? || force
858
- @logger.debug('build entry-by-id index') if @logger
895
+ @logger.debug('build entry-by-id index')
859
896
 
860
897
  glob = Pathname.new('month_*.yml').expand_path(@data_path)
861
898
 
@@ -872,12 +909,14 @@ module TheFox
872
909
  end
873
910
  end
874
911
 
912
+ # Find an Entry by a given ID.
875
913
  def find_entry_by_id(id)
876
914
  build_entry_by_id_index
877
915
 
878
916
  @entries_by_ids[id]
879
917
  end
880
918
 
919
+ # Used by Clear Command.
881
920
  def clear
882
921
  c = 0
883
922
 
@@ -907,6 +946,7 @@ module TheFox
907
946
 
908
947
  private
909
948
 
949
+ # Create all needed subdirectories for this wallet.
910
950
  def create_dirs
911
951
  unless @dir_path.exist?
912
952
  @dir_path.mkpath
@@ -920,6 +960,7 @@ module TheFox
920
960
  @tmp_path.mkpath
921
961
  end
922
962
 
963
+ # Ignore all files in wallet/tmp.
923
964
  tmp_gitignore_path = Pathname.new('.gitignore').expand_path(@tmp_path)
924
965
  unless tmp_gitignore_path.exist?
925
966
  gitignore_file = File.open(tmp_gitignore_path, 'w')
@@ -930,12 +971,14 @@ module TheFox
930
971
  if @entries_index_file_path.exist?
931
972
  load_entries_index_file
932
973
  else
974
+ # When the entries index file does not exist from an older Wallet version.
933
975
  build_entry_by_id_index(true)
934
976
  @entries_index = @entries_by_ids.keys
935
977
  save_entries_index_file
936
978
  end
937
979
  end
938
980
 
981
+ # Get the sums for a given day.
939
982
  def calc_day(day, category = nil)
940
983
  revenue = 0
941
984
  expense = 0
@@ -965,8 +1008,9 @@ module TheFox
965
1008
  }
966
1009
  end
967
1010
 
1011
+ # Get all used years.
1012
+ # Range is optional.
968
1013
  def years(date_start = nil, date_end = nil)
969
-
970
1014
  files = Array.new
971
1015
  @data_path.each_child(false) do |file|
972
1016
  if file.extname == '.yml' && /^month_/.match(file.to_s)
@@ -984,18 +1028,23 @@ module TheFox
984
1028
  .map{ |file| file.to_s[6, 4].to_i }
985
1029
  .uniq
986
1030
  .keep_if{ |year| year >= date_start_year && year <= date_end_year }
1031
+ .sort
987
1032
  end
988
1033
 
1034
+ # Load the entries index file only once.
989
1035
  def load_entries_index_file
990
- unless @entries_index_is_loaded
991
- @entries_index_is_loaded = true
992
- if @entries_index_file_path.exist?
993
- data = YAML.load_file(@entries_index_file_path.to_s)
994
- @entries_index = data['index']
995
- end
1036
+ if @entries_index_is_loaded
1037
+ return
1038
+ end
1039
+
1040
+ @entries_index_is_loaded = true
1041
+ if @entries_index_file_path.exist?
1042
+ data = YAML.load_file(@entries_index_file_path.to_s)
1043
+ @entries_index = data['index']
996
1044
  end
997
1045
  end
998
1046
 
1047
+ # Save the entries index file.
999
1048
  def save_entries_index_file
1000
1049
  store = YAML::Store.new(@entries_index_file_path.to_s)
1001
1050
  store.transaction do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thefox-wallet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Mayer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-19 00:00:00.000000000 Z
11
+ date: 2017-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -131,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
131
131
  version: '0'
132
132
  requirements: []
133
133
  rubyforge_project:
134
- rubygems_version: 2.6.10
134
+ rubygems_version: 2.6.12
135
135
  signing_key:
136
136
  specification_version: 4
137
137
  summary: Finances Tracking