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 +4 -4
- data/.gitlab-ci.yml +11 -2
- data/.travis.yml +1 -1
- data/Makefile +10 -1
- data/README.md +123 -3
- data/bin/wallet +19 -15
- data/lib/wallet/command.rb +11 -6
- data/lib/wallet/command_add.rb +21 -7
- data/lib/wallet/command_categories.rb +1 -0
- data/lib/wallet/command_clear.rb +1 -0
- data/lib/wallet/command_csv.rb +4 -0
- data/lib/wallet/command_html.rb +3 -0
- data/lib/wallet/command_list.rb +15 -2
- data/lib/wallet/entry.rb +6 -5
- data/lib/wallet/version.rb +2 -2
- data/lib/wallet/wallet.rb +79 -30
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1d2d7b98050da769b3220c1b93ae33fa2d66732
|
4
|
+
data.tar.gz: 901db16b1cc71053ccbe69b11a71b03f27e460a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
39
|
-
image: ruby:2.
|
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
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 '
|
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
|
-
-
|
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.
|
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
|
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
|
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
|
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 <
|
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 <
|
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 <
|
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
|
53
|
-
@
|
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
|
57
|
-
@
|
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
|
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
|
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
|
data/lib/wallet/command.rb
CHANGED
@@ -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
|
-
|
13
|
-
@options
|
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 =
|
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
|
-
|
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
|
|
data/lib/wallet/command_add.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/wallet/command_clear.rb
CHANGED
data/lib/wallet/command_csv.rb
CHANGED
@@ -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]
|
data/lib/wallet/command_html.rb
CHANGED
data/lib/wallet/command_list.rb
CHANGED
@@ -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
|
-
#
|
63
|
+
# String
|
64
64
|
@date = Date.parse(date)
|
65
65
|
when Fixnum
|
66
|
-
#
|
66
|
+
# Fixnum
|
67
67
|
@date = Time.at(date).to_date
|
68
68
|
when Date
|
69
|
-
#
|
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']
|
data/lib/wallet/version.rb
CHANGED
data/lib/wallet/wallet.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
|
2
|
-
#
|
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
|
-
|
11
|
-
#
|
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 =
|
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 ...')
|
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('-')
|
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
|
-
|
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
|
-
|
277
|
+
entries_h[day_key] = data['days'][day_key]
|
255
278
|
end
|
256
279
|
else
|
257
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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} ...")
|
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="
|
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}")
|
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})")
|
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="
|
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')
|
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'}")
|
837
|
+
@logger.debug("import row '#{id}' -- #{added ? 'YES' : 'NO'}")
|
804
838
|
end
|
805
839
|
|
806
|
-
@logger.info('save data ...')
|
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)}")
|
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')
|
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
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
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.
|
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-
|
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.
|
134
|
+
rubygems_version: 2.6.12
|
135
135
|
signing_key:
|
136
136
|
specification_version: 4
|
137
137
|
summary: Finances Tracking
|