thefox-wallet 0.14.0 → 0.15.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/.gitignore +2 -1
- data/.gitlab-ci.yml +4 -5
- data/Gemfile +8 -0
- data/Makefile +7 -2
- data/Makefile.common +8 -6
- data/README.md +1 -1
- data/bin/dev +12 -0
- data/bin/wallet +35 -22
- data/lib/wallet/command.rb +6 -2
- data/lib/wallet/command_add.rb +7 -4
- data/lib/wallet/command_categories.rb +1 -3
- data/lib/wallet/command_csv.rb +5 -4
- data/lib/wallet/command_html.rb +10 -3
- data/lib/wallet/command_list.rb +38 -30
- data/lib/wallet/entry.rb +2 -2
- data/lib/wallet/version.rb +2 -2
- data/lib/wallet/wallet.rb +180 -176
- data/thefox-wallet.gemspec +2 -1
- metadata +20 -6
- data/bin/.gitignore +0 -1
data/lib/wallet/wallet.rb
CHANGED
@@ -5,40 +5,37 @@ require 'logger'
|
|
5
5
|
require 'yaml'
|
6
6
|
require 'yaml/store'
|
7
7
|
require 'csv'
|
8
|
-
|
9
|
-
# OpenStruct use to generate HTML.
|
10
|
-
require 'ostruct'
|
11
|
-
|
8
|
+
require 'pathname'
|
9
|
+
require 'ostruct' # OpenStruct use to generate HTML.
|
12
10
|
# require 'pp'
|
13
11
|
|
14
|
-
|
15
12
|
module TheFox
|
16
13
|
module Wallet
|
17
14
|
|
18
15
|
class Wallet
|
19
16
|
|
20
17
|
attr_writer :logger
|
21
|
-
attr_reader :
|
18
|
+
attr_reader :dir_path
|
22
19
|
|
23
|
-
def initialize(dir_path =
|
20
|
+
def initialize(dir_path = nil)
|
24
21
|
@exit = false
|
25
22
|
@logger = nil
|
26
|
-
@dir_path = dir_path
|
27
|
-
@
|
28
|
-
@
|
29
|
-
@
|
23
|
+
@dir_path = dir_path || Pathname.new('wallet').expand_path
|
24
|
+
@dir_path_basename = @dir_path.basename
|
25
|
+
@dir_path_basename_s = @dir_path_basename.to_s
|
26
|
+
@data_path = Pathname.new('data').expand_path(@dir_path)
|
27
|
+
@tmp_path = Pathname.new('tmp').expand_path(@dir_path)
|
30
28
|
|
31
29
|
@has_transaction = false
|
32
30
|
@transaction_files = Hash.new
|
33
31
|
|
34
32
|
@entries_by_ids = nil
|
35
|
-
@entries_index_file_path =
|
33
|
+
@entries_index_file_path = Pathname.new('index.yml').expand_path(@data_path)
|
36
34
|
@entries_index = Array.new
|
37
35
|
@entries_index_is_loaded = false
|
38
36
|
|
39
37
|
Signal.trap('SIGINT') do
|
40
|
-
|
41
|
-
puts 'received SIGINT. break ...'
|
38
|
+
@logger.warning('received SIGINT. break ...') if @logger
|
42
39
|
@exit = true
|
43
40
|
end
|
44
41
|
end
|
@@ -48,20 +45,18 @@ module TheFox
|
|
48
45
|
raise ArgumentError, 'variable must be a Entry instance'
|
49
46
|
end
|
50
47
|
|
51
|
-
# puts "add, id #{entry.id}"
|
52
|
-
# puts "add, is_unique #{is_unique}"
|
53
|
-
# puts "add, entry_exist? #{entry_exist?(entry)}"
|
54
|
-
# puts
|
55
|
-
|
56
48
|
if is_unique && entry_exist?(entry)
|
57
49
|
return false
|
58
50
|
end
|
59
51
|
|
52
|
+
create_dirs
|
53
|
+
|
60
54
|
date = entry.date
|
61
55
|
date_s = date.to_s
|
62
|
-
|
63
|
-
|
64
|
-
|
56
|
+
dbfile_basename_s = "month_#{date.strftime('%Y_%m')}.yml"
|
57
|
+
dbfile_basename_p = Pathname.new(dbfile_basename_s)
|
58
|
+
dbfile_path = dbfile_basename_p.expand_path(@data_path)
|
59
|
+
tmpfile_path = Pathname.new("#{dbfile_path}.tmp")
|
65
60
|
file = {
|
66
61
|
'meta' => {
|
67
62
|
'version' => 1,
|
@@ -73,24 +68,19 @@ module TheFox
|
|
73
68
|
|
74
69
|
@entries_index << entry.id
|
75
70
|
|
76
|
-
# puts 'dbfile_basename: ' + dbfile_basename
|
77
|
-
# puts 'dbfile_path: ' + dbfile_path
|
78
|
-
# puts 'tmpfile_path: ' + tmpfile_path
|
79
|
-
# puts
|
80
|
-
|
81
71
|
if @has_transaction
|
82
|
-
if @transaction_files
|
83
|
-
file = @transaction_files[
|
72
|
+
if @transaction_files[dbfile_basename_s]
|
73
|
+
file = @transaction_files[dbfile_basename_s]['file']
|
84
74
|
else
|
85
|
-
if
|
75
|
+
if dbfile_path.exist?
|
86
76
|
file = YAML.load_file(dbfile_path)
|
87
77
|
file['meta']['updated_at'] = DateTime.now.to_s
|
88
78
|
end
|
89
79
|
|
90
|
-
@transaction_files[
|
91
|
-
'basename' =>
|
92
|
-
'path' => dbfile_path,
|
93
|
-
'tmp_path' => tmpfile_path,
|
80
|
+
@transaction_files[dbfile_basename_s] = {
|
81
|
+
'basename' => dbfile_basename_s,
|
82
|
+
'path' => dbfile_path.to_s,
|
83
|
+
'tmp_path' => tmpfile_path.to_s,
|
94
84
|
'file' => file,
|
95
85
|
}
|
96
86
|
end
|
@@ -104,11 +94,9 @@ module TheFox
|
|
104
94
|
|
105
95
|
file['days'][date_s].push(entry.to_h)
|
106
96
|
|
107
|
-
@transaction_files[
|
97
|
+
@transaction_files[dbfile_basename_s]['file'] = file
|
108
98
|
else
|
109
|
-
|
110
|
-
|
111
|
-
if File.exist?(dbfile_path)
|
99
|
+
if dbfile_path.exist?
|
112
100
|
file = YAML.load_file(dbfile_path)
|
113
101
|
file['meta']['updated_at'] = DateTime.now.to_s
|
114
102
|
end
|
@@ -130,8 +118,8 @@ module TheFox
|
|
130
118
|
|
131
119
|
save_entries_index_file
|
132
120
|
|
133
|
-
if
|
134
|
-
|
121
|
+
if tmpfile_path.exist?
|
122
|
+
tmpfile_path.rename(dbfile_path)
|
135
123
|
end
|
136
124
|
end
|
137
125
|
|
@@ -157,12 +145,6 @@ module TheFox
|
|
157
145
|
throw :done
|
158
146
|
end
|
159
147
|
|
160
|
-
# puts 'keys left: ' + @transaction_files.keys.count.to_s
|
161
|
-
# puts 'tr_file_key: ' + tr_file_key
|
162
|
-
# puts 'path: ' + tr_file_data['path']
|
163
|
-
# puts 'tmp_path: ' + tr_file_data['tmp_path']
|
164
|
-
# puts
|
165
|
-
|
166
148
|
store = YAML::Store.new(tr_file_data['tmp_path'])
|
167
149
|
store.transaction do
|
168
150
|
store['meta'] = tr_file_data['file']['meta']
|
@@ -259,16 +241,8 @@ module TheFox
|
|
259
241
|
|
260
242
|
category = category.to_s.downcase
|
261
243
|
|
262
|
-
# puts 'glob: ' + glob
|
263
|
-
# puts 'begin_year: ' + '%-10s' % begin_year.class.to_s + ' = "' + begin_year.to_s + '"'
|
264
|
-
# puts 'begin_month: ' + '%-10s' % begin_month.class.to_s + ' = "' + begin_month.to_s + '"'
|
265
|
-
# puts 'begin_day: ' + '%-10s' % begin_day.class.to_s + ' = "' + begin_day.to_s + '"'
|
266
|
-
# puts 'category: ' + '%-10s' % category.class.to_s + ' = "' + category.to_s + '"'
|
267
|
-
# puts
|
268
|
-
|
269
244
|
entries_a = Hash.new
|
270
245
|
Dir[glob].each do |file_path|
|
271
|
-
#puts "path: #{file_path}"
|
272
246
|
|
273
247
|
data = YAML.load_file(file_path)
|
274
248
|
if category.length == 0
|
@@ -306,7 +280,7 @@ module TheFox
|
|
306
280
|
|
307
281
|
def categories
|
308
282
|
categories_h = Hash.new
|
309
|
-
Dir[
|
283
|
+
Dir[Pathname.new('month_*.yml').expand_path(@data_path)].each do |file_path|
|
310
284
|
data = YAML.load_file(file_path)
|
311
285
|
|
312
286
|
data['days'].each do |day_name, day_items|
|
@@ -328,10 +302,14 @@ module TheFox
|
|
328
302
|
categories_a
|
329
303
|
end
|
330
304
|
|
331
|
-
def gen_html
|
305
|
+
def gen_html(html_path, date_start = nil, date_end = nil, category = nil)
|
332
306
|
create_dirs
|
333
307
|
|
334
|
-
|
308
|
+
unless html_path.exist?
|
309
|
+
html_path.mkpath
|
310
|
+
end
|
311
|
+
|
312
|
+
html_options_path = Pathname.new('options.yml').expand_path(html_path)
|
335
313
|
html_options = {
|
336
314
|
'meta' => {
|
337
315
|
'version' => 1,
|
@@ -340,25 +318,30 @@ module TheFox
|
|
340
318
|
},
|
341
319
|
'changes' => Hash.new,
|
342
320
|
}
|
343
|
-
if
|
344
|
-
if
|
321
|
+
if html_path.exist?
|
322
|
+
if html_options_path.exist?
|
345
323
|
html_options = YAML.load_file(html_options_path)
|
346
324
|
html_options['meta']['updated_at'] = DateTime.now.to_s
|
347
325
|
end
|
348
326
|
else
|
349
|
-
|
327
|
+
html_path.mkpath
|
350
328
|
end
|
351
329
|
|
352
330
|
categories_available = categories
|
331
|
+
if category
|
332
|
+
filter_categories = category.split(',')
|
333
|
+
categories_available &= filter_categories
|
334
|
+
end
|
353
335
|
|
354
336
|
categories_total_balance = Hash.new
|
355
337
|
categories_available.map{ |item| categories_total_balance[item] = 0.0 }
|
356
338
|
|
357
|
-
|
339
|
+
gitignore_file_path = Pathname.new('.gitignore').expand_path(html_path)
|
340
|
+
gitignore_file = File.open(gitignore_file_path, 'w')
|
358
341
|
gitignore_file.write('*')
|
359
342
|
gitignore_file.close
|
360
343
|
|
361
|
-
css_file_path =
|
344
|
+
css_file_path = Pathname.new('style.css').expand_path(html_path)
|
362
345
|
css_file = File.open(css_file_path, 'w')
|
363
346
|
css_file.write('
|
364
347
|
html {
|
@@ -383,37 +366,38 @@ module TheFox
|
|
383
366
|
')
|
384
367
|
css_file.close
|
385
368
|
|
386
|
-
index_file_path =
|
369
|
+
index_file_path = Pathname.new('index.html').expand_path(html_path)
|
387
370
|
index_file = File.open(index_file_path, 'w')
|
388
371
|
index_file.write('
|
389
372
|
<html>
|
390
373
|
<head>
|
391
374
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
392
|
-
<title>' << @
|
375
|
+
<title>' << @dir_path_basename_s << '</title>
|
393
376
|
<link rel="stylesheet" href="style.css" type="text/css" />
|
394
377
|
</head>
|
395
378
|
<body>
|
396
|
-
<h1>' << @
|
397
|
-
<p>Generated @ ' << DateTime.now.strftime('%
|
379
|
+
<h1>' << @dir_path_basename_s << '</h1>
|
380
|
+
<p>Generated @ ' << DateTime.now.strftime('%F %T') << ' by <a href="' << HOMEPAGE << '">' << NAME << '</a> v' << VERSION << '</p>
|
398
381
|
')
|
399
382
|
|
400
383
|
years_total = Hash.new
|
401
|
-
years.each do |year|
|
384
|
+
years(date_start, date_end).each do |year|
|
402
385
|
year_s = year.to_s
|
403
|
-
|
404
|
-
|
386
|
+
year_file_name_s = "year_#{year}.html"
|
387
|
+
year_file_name_p = Pathname.new(year_file_name_s)
|
388
|
+
year_file_path = year_file_name_p.expand_path(html_path)
|
405
389
|
|
406
390
|
year_file = File.open(year_file_path, 'w')
|
407
391
|
year_file.write('
|
408
392
|
<html>
|
409
393
|
<head>
|
410
394
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
411
|
-
<title>' << year_s << ' - ' << @
|
395
|
+
<title>' << year_s << ' - ' << @dir_path_basename_s << '</title>
|
412
396
|
<link rel="stylesheet" href="style.css" type="text/css" />
|
413
397
|
</head>
|
414
398
|
<body>
|
415
|
-
<h1><a href="index.html">' << @
|
416
|
-
<p>Generated @ ' << DateTime.now.strftime('%Y-%m-%d %H:%M:%S') << ' by
|
399
|
+
<h1><a href="index.html">' << @dir_path_basename_s << '</a></h1>
|
400
|
+
<p>Generated @ ' << DateTime.now.strftime('%Y-%m-%d %H:%M:%S') << ' by <a href="' << HOMEPAGE << '">' << NAME << '</a> v' << VERSION << '</p>
|
417
401
|
|
418
402
|
<h2>Year: ' << year_s << '</h2>
|
419
403
|
<table class="list">
|
@@ -428,7 +412,7 @@ module TheFox
|
|
428
412
|
<th colspan="4"> </th>
|
429
413
|
')
|
430
414
|
categories_available.each do |category|
|
431
|
-
year_file.write(
|
415
|
+
year_file.write(%(<th class="right">#{category}</th>))
|
432
416
|
end
|
433
417
|
year_file.write('</tr>')
|
434
418
|
|
@@ -439,15 +423,32 @@ module TheFox
|
|
439
423
|
categories_available.map{ |item| categories_year_balance[item] = 0.0 }
|
440
424
|
year_total = Hash.new
|
441
425
|
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
426
|
+
@logger.info("generate year #{year}") if @logger
|
427
|
+
@data_path.each_child do |file_path|
|
428
|
+
file_name_p = file_path.basename
|
429
|
+
file_name_s = file_name_p.to_s
|
430
|
+
|
431
|
+
if file_path.extname != '.yml' || Regexp.new("^month_#{year}_").match(file_name_s).nil?
|
432
|
+
next
|
433
|
+
end
|
434
|
+
|
435
|
+
month_n = file_name_s[11, 2]
|
436
|
+
month_file_name_s = "month_#{year}_#{month_n}.html"
|
437
|
+
month_file_name_p = Pathname.new(month_file_name_s)
|
438
|
+
month_file_path = month_file_name_p.expand_path(html_path)
|
448
439
|
|
449
440
|
month_s = Date.parse("2015-#{month_n}-15").strftime('%B')
|
450
441
|
|
442
|
+
if date_start && date_end
|
443
|
+
file_date_start = Date.parse("#{year}-#{month_n}-01")
|
444
|
+
file_date_end = Date.parse("#{year}-#{month_n}-01").next_month.prev_day
|
445
|
+
|
446
|
+
if date_end < file_date_start ||
|
447
|
+
date_start > file_date_end
|
448
|
+
next
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
451
452
|
revenue_month = 0.0
|
452
453
|
expense_month = 0.0
|
453
454
|
balance_month = 0.0
|
@@ -458,37 +459,37 @@ module TheFox
|
|
458
459
|
data = YAML.load_file(file_path)
|
459
460
|
|
460
461
|
generate_html = false
|
461
|
-
if html_options['changes']
|
462
|
-
if html_options['changes'][
|
463
|
-
html_options['changes'][
|
462
|
+
if html_options['changes'][file_name_s]
|
463
|
+
if html_options['changes'][file_name_s]['updated_at'] != data['meta']['updated_at']
|
464
|
+
html_options['changes'][file_name_s]['updated_at'] = data['meta']['updated_at']
|
464
465
|
generate_html = true
|
465
466
|
end
|
466
467
|
else
|
467
|
-
html_options['changes'][
|
468
|
+
html_options['changes'][file_name_s] = {
|
468
469
|
'updated_at' => data['meta']['updated_at'],
|
469
470
|
}
|
470
471
|
generate_html = true
|
471
472
|
end
|
472
|
-
|
473
|
+
unless month_file_path.exist?
|
473
474
|
generate_html = true
|
474
475
|
end
|
475
476
|
|
476
477
|
if generate_html
|
477
|
-
|
478
|
+
@logger.debug("file: #{month_file_name_s} (from #{file_name_s})") if @logger
|
478
479
|
|
479
480
|
month_file = File.open(month_file_path, 'w')
|
480
481
|
month_file.write('
|
481
482
|
<html>
|
482
483
|
<head>
|
483
484
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
484
|
-
<title>' << month_s << ' ' << year_s << ' - ' << @
|
485
|
+
<title>' << month_s << ' ' << year_s << ' - ' << @dir_path_basename_s << '</title>
|
485
486
|
<link rel="stylesheet" href="style.css" type="text/css" />
|
486
487
|
</head>
|
487
488
|
<body>
|
488
|
-
<h1><a href="index.html">' << @
|
489
|
-
<p>Generated @ ' << DateTime.now.strftime('%Y-%m-%d %H:%M:%S') << ' by <a href="' <<
|
489
|
+
<h1><a href="index.html">' << @dir_path_basename_s << '</a></h1>
|
490
|
+
<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>
|
490
491
|
|
491
|
-
<h2>Month: ' << month_s << ' <a href="' <<
|
492
|
+
<h2>Month: ' << month_s << ' <a href="' << year_file_name_s << '">' << year_s << '</a></h2>
|
492
493
|
<table class="list">
|
493
494
|
<tr>
|
494
495
|
<th class="left">#</th>
|
@@ -505,6 +506,19 @@ module TheFox
|
|
505
506
|
|
506
507
|
data['days'].sort.each do |day_name, day_items|
|
507
508
|
day_items.each do |entry|
|
509
|
+
entry_date = Date.parse(entry['date'])
|
510
|
+
entry_date_s = entry_date.strftime('%d.%m.%y')
|
511
|
+
|
512
|
+
date_start_oor = date_start > entry_date
|
513
|
+
date_end_oor = date_end < entry_date
|
514
|
+
|
515
|
+
if category && !categories_available.include?(entry['category']) ||
|
516
|
+
date_start > entry_date ||
|
517
|
+
date_end < entry_date
|
518
|
+
|
519
|
+
next
|
520
|
+
end
|
521
|
+
|
508
522
|
entry_n += 1
|
509
523
|
revenue_month += entry['revenue']
|
510
524
|
expense_month += entry['expense']
|
@@ -513,8 +527,8 @@ module TheFox
|
|
513
527
|
categories_year_balance[entry['category']] += entry['balance']
|
514
528
|
categories_month_balance[entry['category']] += entry['balance']
|
515
529
|
|
516
|
-
revenue_out = entry['revenue'] > 0 ?
|
517
|
-
expense_out = entry['expense'] < 0 ?
|
530
|
+
revenue_out = entry['revenue'] > 0 ? NUMBER_FORMAT % entry['revenue'] : ' '
|
531
|
+
expense_out = entry['expense'] < 0 ? NUMBER_FORMAT % entry['expense'] : ' '
|
518
532
|
category_out = entry['category'] == 'default' ? ' ' : entry['category']
|
519
533
|
comment_out = entry['comment'] == '' ? ' ' : entry['comment']
|
520
534
|
|
@@ -522,11 +536,11 @@ module TheFox
|
|
522
536
|
month_file.write('
|
523
537
|
<tr>
|
524
538
|
<td valign="top" class="left">' << entry_n.to_s << '</td>
|
525
|
-
<td valign="top" class="left">' <<
|
539
|
+
<td valign="top" class="left">' << entry_date_s << '</td>
|
526
540
|
<td valign="top" class="left">' << entry['title'][0, 50] << '</td>
|
527
541
|
<td valign="top" class="right">' << revenue_out << '</td>
|
528
542
|
<td valign="top" class="right red">' << expense_out << '</td>
|
529
|
-
<td valign="top" class="right ' << (entry['balance'] < 0 ? 'red' : '') << '">' <<
|
543
|
+
<td valign="top" class="right ' << (entry['balance'] < 0 ? 'red' : '') << '">' << NUMBER_FORMAT % entry['balance'] << '</td>
|
530
544
|
<td valign="top" class="right">' << category_out << '</td>
|
531
545
|
<td valign="top" class="left">' << comment_out << '</td>
|
532
546
|
</tr>
|
@@ -561,9 +575,9 @@ module TheFox
|
|
561
575
|
<th> </th>
|
562
576
|
<th> </th>
|
563
577
|
<th class="left"><b>TOTAL</b></th>
|
564
|
-
<th class="right">' <<
|
565
|
-
<th class="right red">' <<
|
566
|
-
<th class="right ' << balance_class << '">' <<
|
578
|
+
<th class="right">' << NUMBER_FORMAT % revenue_month << '</th>
|
579
|
+
<th class="right red">' << NUMBER_FORMAT % expense_month << '</th>
|
580
|
+
<th class="right ' << balance_class << '">' << NUMBER_FORMAT % balance_month << '</th>
|
567
581
|
<th> </th>
|
568
582
|
<th> </th>
|
569
583
|
</tr>
|
@@ -574,13 +588,13 @@ module TheFox
|
|
574
588
|
|
575
589
|
year_file.write('
|
576
590
|
<tr>
|
577
|
-
<td class="left"><a href="' <<
|
578
|
-
<td class="right">' <<
|
579
|
-
<td class="right red">' <<
|
580
|
-
<td class="right ' << balance_class << '">' <<
|
591
|
+
<td class="left"><a href="' << month_file_name_s << '">' << month_s << '</a></td>
|
592
|
+
<td class="right">' << NUMBER_FORMAT % revenue_month << '</td>
|
593
|
+
<td class="right red">' << NUMBER_FORMAT % expense_month << '</td>
|
594
|
+
<td class="right ' << balance_class << '">' << NUMBER_FORMAT % balance_month << '</td>')
|
581
595
|
categories_available.each do |category|
|
582
596
|
category_balance = categories_month_balance[category]
|
583
|
-
year_file.write('<td class="right ' << (category_balance < 0 ? 'red' : '') << '">' <<
|
597
|
+
year_file.write('<td class="right ' << (category_balance < 0 ? 'red' : '') << '">' << NUMBER_FORMAT % category_balance << '</td>')
|
584
598
|
end
|
585
599
|
year_file.write('</tr>')
|
586
600
|
end
|
@@ -590,12 +604,12 @@ module TheFox
|
|
590
604
|
year_file.write('
|
591
605
|
<tr>
|
592
606
|
<th class="left"><b>TOTAL</b></th>
|
593
|
-
<th class="right">' <<
|
594
|
-
<th class="right red">' <<
|
595
|
-
<th class="right ' << (balance_year < 0 ? 'red' : '') << '">' <<
|
607
|
+
<th class="right">' << NUMBER_FORMAT % revenue_year << '</th>
|
608
|
+
<th class="right red">' << NUMBER_FORMAT % expense_year << '</th>
|
609
|
+
<th class="right ' << (balance_year < 0 ? 'red' : '') << '">' << NUMBER_FORMAT % balance_year << '</th>')
|
596
610
|
categories_available.each do |category|
|
597
611
|
category_balance = categories_year_balance[category]
|
598
|
-
year_file.write('<td class="right ' << (category_balance < 0 ? 'red' : '') << '">' <<
|
612
|
+
year_file.write('<td class="right ' << (category_balance < 0 ? 'red' : '') << '">' << NUMBER_FORMAT % category_balance << '</td>')
|
599
613
|
end
|
600
614
|
|
601
615
|
year_file.write('
|
@@ -607,42 +621,15 @@ module TheFox
|
|
607
621
|
year_file.write('</body></html>')
|
608
622
|
year_file.close
|
609
623
|
|
610
|
-
yeardat_file_path =
|
624
|
+
yeardat_file_path = Pathname.new("year_#{year_s}.dat").expand_path(@tmp_path)
|
611
625
|
yeardat_file = File.new(yeardat_file_path, 'w')
|
612
626
|
yeardat_file.write(year_total
|
613
627
|
.map{ |k, m| "#{year_s}-#{m.month_s} #{m.revenue} #{m.expense} #{m.balance} #{m.balance_total} #{m.balance_total}" }
|
614
628
|
.join("\n"))
|
615
629
|
yeardat_file.close
|
616
630
|
|
617
|
-
|
618
|
-
|
619
|
-
# .flatten
|
620
|
-
# .max
|
621
|
-
# .to_i
|
622
|
-
|
623
|
-
# year_min = year_total
|
624
|
-
# .map{ |k, m| [m.expense, m.balance, m.balance_total] }
|
625
|
-
# .flatten
|
626
|
-
# .min
|
627
|
-
# .to_i
|
628
|
-
# .abs
|
629
|
-
|
630
|
-
# year_max_rl = year_max.to_s.length - 2
|
631
|
-
# year_max_r = year_max.round(-year_max_rl)
|
632
|
-
# year_max_d = year_max_r - year_max
|
633
|
-
# year_max_r = year_max_r + 5 * 10 ** (year_max_rl - 1) if year_max_r < year_max
|
634
|
-
# year_max_r += 100
|
635
|
-
|
636
|
-
# year_min_rl = year_min.to_s.length - 2
|
637
|
-
# year_min_r = year_min.round(-year_min_rl)
|
638
|
-
# year_min_d = year_min_r - year_min
|
639
|
-
# year_min_r = year_min_r + 5 * 10 ** (year_min_rl - 1) if year_min_r < year_min
|
640
|
-
# year_min_r += 100
|
641
|
-
|
642
|
-
# puts "#{year_max} #{year_max.to_s.length} #{year_max_r} #{year_max_rl}"
|
643
|
-
# puts "#{year_min} #{year_min.to_s.length} #{year_min_r} #{year_min_rl}"
|
644
|
-
|
645
|
-
gnuplot_file = File.new(File.expand_path("year_#{year_s}.gp", @tmp_path), 'w')
|
631
|
+
gnuplot_file_path = Pathname.new("year_#{year_s}.gp").expand_path(@tmp_path)
|
632
|
+
gnuplot_file = File.new(gnuplot_file_path, 'w')
|
646
633
|
gnuplot_file.puts("set title 'Year #{year_s}'")
|
647
634
|
gnuplot_file.puts("set xlabel 'Months'")
|
648
635
|
gnuplot_file.puts("set ylabel 'Euro'")
|
@@ -665,14 +652,14 @@ module TheFox
|
|
665
652
|
gnuplot_file.puts("set style line 4 linecolor rgb '#0000ff' linewidth 2 linetype 1 pointtype 2")
|
666
653
|
gnuplot_file.puts("set style data linespoints")
|
667
654
|
gnuplot_file.puts("set terminal png enhanced")
|
668
|
-
gnuplot_file.puts("set output '" << File.expand_path("year_#{year_s}.png",
|
655
|
+
gnuplot_file.puts("set output '" << File.expand_path("year_#{year_s}.png", html_path) << "'")
|
669
656
|
gnuplot_file.puts("plot sum = 0, \\")
|
670
657
|
gnuplot_file.puts("\t'#{yeardat_file_path}' using 1:2 linestyle 1 title 'Revenue', \\")
|
671
658
|
gnuplot_file.puts("\t'' using 1:3 linestyle 2 title 'Expense', \\")
|
672
659
|
gnuplot_file.puts("\t'' using 1:4 linestyle 3 title 'Balance', \\")
|
673
660
|
gnuplot_file.puts("\t'' using 1:5 linestyle 4 title '∑ Balance'")
|
674
661
|
gnuplot_file.close
|
675
|
-
system("gnuplot
|
662
|
+
system("gnuplot #{gnuplot_file_path} &> /dev/null")
|
676
663
|
|
677
664
|
years_total[year_s] = ::OpenStruct.new({
|
678
665
|
year: year_s,
|
@@ -697,10 +684,10 @@ module TheFox
|
|
697
684
|
index_file.write('
|
698
685
|
<tr>
|
699
686
|
<td class="left"><a href="year_' << year_name << '.html">' << year_name << '</a></td>
|
700
|
-
<td class="right">' <<
|
701
|
-
<td class="right red">' <<
|
702
|
-
<td class="right ' << (year_data.balance < 0 ? 'red' : '') << '">' <<
|
703
|
-
<td class="right ' << (year_data.balance_total < 0 ? 'red' : '') << '">' <<
|
687
|
+
<td class="right">' << NUMBER_FORMAT % year_data.revenue << '</td>
|
688
|
+
<td class="right red">' << NUMBER_FORMAT % year_data.expense << '</td>
|
689
|
+
<td class="right ' << (year_data.balance < 0 ? 'red' : '') << '">' << NUMBER_FORMAT % year_data.balance << '</td>
|
690
|
+
<td class="right ' << (year_data.balance_total < 0 ? 'red' : '') << '">' << NUMBER_FORMAT % year_data.balance_total << '</td>
|
704
691
|
</tr>')
|
705
692
|
end
|
706
693
|
|
@@ -709,9 +696,9 @@ module TheFox
|
|
709
696
|
index_file.write('
|
710
697
|
<tr>
|
711
698
|
<th class="left"><b>TOTAL</b></th>
|
712
|
-
<th class="right">' <<
|
713
|
-
<th class="right red">' <<
|
714
|
-
<th class="right ' << (balance_total < 0 ? 'red' : '') << '">' <<
|
699
|
+
<th class="right">' << NUMBER_FORMAT % years_total.inject(0.0){ |sum, item| sum + item[1].revenue } << '</th>
|
700
|
+
<th class="right red">' << NUMBER_FORMAT % years_total.inject(0.0){ |sum, item| sum + item[1].expense } << '</th>
|
701
|
+
<th class="right ' << (balance_total < 0 ? 'red' : '') << '">' << NUMBER_FORMAT % balance_total << '</th>
|
715
702
|
<th> </th>
|
716
703
|
</tr>
|
717
704
|
</table>
|
@@ -731,17 +718,20 @@ module TheFox
|
|
731
718
|
end
|
732
719
|
|
733
720
|
totaldat_file_c = years_total.map{ |k, y| "#{y.year} #{y.revenue} #{y.expense} #{y.balance} #{y.balance_total}" }
|
734
|
-
if totaldat_file_c.count >
|
735
|
-
totaldat_file_c = totaldat_file_c.slice(-
|
721
|
+
if totaldat_file_c.count > 10
|
722
|
+
totaldat_file_c = totaldat_file_c.slice(-10, 10)
|
736
723
|
end
|
737
724
|
totaldat_file_c = totaldat_file_c.join("\n")
|
738
725
|
|
739
|
-
totaldat_file_path =
|
726
|
+
totaldat_file_path = Pathname.new('total.dat').expand_path(@tmp_path)
|
740
727
|
totaldat_file = File.new(totaldat_file_path, 'w')
|
741
728
|
totaldat_file.write(totaldat_file_c)
|
742
729
|
totaldat_file.close
|
743
730
|
|
744
|
-
|
731
|
+
png_file_path = Pathname.new('total.png').expand_path(html_path)
|
732
|
+
|
733
|
+
gnuplot_file_path = Pathname.new('total.gp').expand_path(@tmp_path)
|
734
|
+
gnuplot_file = File.new(gnuplot_file_path, 'w')
|
745
735
|
gnuplot_file.puts("set title 'Total'")
|
746
736
|
gnuplot_file.puts("set xlabel 'Years'")
|
747
737
|
gnuplot_file.puts("set ylabel 'Euro'")
|
@@ -756,7 +746,7 @@ module TheFox
|
|
756
746
|
gnuplot_file.puts("set style line 4 linecolor rgb '#0000ff' linewidth 2 linetype 1 pointtype 2")
|
757
747
|
gnuplot_file.puts("set style data linespoints")
|
758
748
|
gnuplot_file.puts("set terminal png enhanced")
|
759
|
-
gnuplot_file.puts("set output '
|
749
|
+
gnuplot_file.puts("set output '#{png_file_path}'")
|
760
750
|
gnuplot_file.puts("plot sum = 0, \\")
|
761
751
|
gnuplot_file.puts("\t'#{totaldat_file_path}' using 1:2 linestyle 1 title 'Revenue', \\")
|
762
752
|
gnuplot_file.puts("\t'' using 1:3 linestyle 2 title 'Expense', \\")
|
@@ -764,7 +754,7 @@ module TheFox
|
|
764
754
|
gnuplot_file.puts("\t'' using 1:5 linestyle 4 title '∑ Balance'")
|
765
755
|
gnuplot_file.close
|
766
756
|
|
767
|
-
system("gnuplot
|
757
|
+
system("gnuplot #{gnuplot_file_path} &> /dev/null")
|
768
758
|
end
|
769
759
|
|
770
760
|
def import_csv_file(file_path)
|
@@ -796,11 +786,10 @@ module TheFox
|
|
796
786
|
|
797
787
|
added = add(Entry.new(id, title, date, revenue, expense, category, comment), true)
|
798
788
|
|
799
|
-
|
789
|
+
@logger.debug("import row '#{id}' -- #{added ? 'YES' : 'NO'}") if @logger
|
800
790
|
end
|
801
791
|
|
802
|
-
|
803
|
-
puts 'save data ...'
|
792
|
+
@logger.info('save data ...') if @logger
|
804
793
|
|
805
794
|
transaction_end
|
806
795
|
end
|
@@ -816,8 +805,8 @@ module TheFox
|
|
816
805
|
# :encoding => 'ISO-8859-1',
|
817
806
|
}
|
818
807
|
CSV.open(file_path, 'wb', csv_options) do |csv|
|
819
|
-
Dir[
|
820
|
-
|
808
|
+
Dir[Pathname.new('month_*.yml').expand_path(@data_path)].each do |yaml_file_path|
|
809
|
+
@logger.info("export #{File.basename(yaml_file_path)}") if @logger
|
821
810
|
|
822
811
|
data = YAML.load_file(yaml_file_path)
|
823
812
|
|
@@ -827,9 +816,9 @@ module TheFox
|
|
827
816
|
entry['id'],
|
828
817
|
entry['date'],
|
829
818
|
entry['title'],
|
830
|
-
|
831
|
-
|
832
|
-
|
819
|
+
NUMBER_FORMAT % entry['revenue'],
|
820
|
+
NUMBER_FORMAT % entry['expense'],
|
821
|
+
NUMBER_FORMAT % entry['balance'],
|
833
822
|
entry['category'],
|
834
823
|
entry['comment'],
|
835
824
|
]
|
@@ -854,9 +843,9 @@ module TheFox
|
|
854
843
|
if @entries_by_ids.nil? || force
|
855
844
|
@logger.debug('build entry-by-id index') if @logger
|
856
845
|
|
857
|
-
glob =
|
846
|
+
glob = Pathname.new('month_*.yml').expand_path(@data_path)
|
858
847
|
|
859
|
-
@entries_by_ids = Dir[glob].map { |file_path|
|
848
|
+
@entries_by_ids = Dir[glob.to_s].map { |file_path|
|
860
849
|
data = YAML.load_file(file_path)
|
861
850
|
data['days'].map{ |day_name, day_items|
|
862
851
|
day_items.map{ |entry|
|
@@ -866,8 +855,6 @@ module TheFox
|
|
866
855
|
}.flatten.map{ |entry|
|
867
856
|
[entry.id, entry]
|
868
857
|
}.to_h
|
869
|
-
|
870
|
-
# pp @entries_by_ids
|
871
858
|
end
|
872
859
|
end
|
873
860
|
|
@@ -880,26 +867,26 @@ module TheFox
|
|
880
867
|
private
|
881
868
|
|
882
869
|
def create_dirs
|
883
|
-
|
884
|
-
|
870
|
+
unless @dir_path.exist?
|
871
|
+
@dir_path.mkpath
|
885
872
|
end
|
886
873
|
|
887
|
-
|
888
|
-
|
874
|
+
unless @data_path.exist?
|
875
|
+
@data_path.mkpath
|
889
876
|
end
|
890
877
|
|
891
|
-
|
892
|
-
|
878
|
+
unless @tmp_path.exist?
|
879
|
+
@tmp_path.mkpath
|
893
880
|
end
|
894
881
|
|
895
|
-
tmp_gitignore_path =
|
896
|
-
|
882
|
+
tmp_gitignore_path = Pathname.new('.gitignore').expand_path(@tmp_path)
|
883
|
+
unless tmp_gitignore_path.exist?
|
897
884
|
gitignore_file = File.open(tmp_gitignore_path, 'w')
|
898
885
|
gitignore_file.write('*')
|
899
886
|
gitignore_file.close
|
900
887
|
end
|
901
888
|
|
902
|
-
if
|
889
|
+
if @entries_index_file_path.exist?
|
903
890
|
load_entries_index_file
|
904
891
|
else
|
905
892
|
build_entry_by_id_index(true)
|
@@ -937,22 +924,39 @@ module TheFox
|
|
937
924
|
}
|
938
925
|
end
|
939
926
|
|
940
|
-
def years
|
941
|
-
|
927
|
+
def years(date_start = nil, date_end = nil)
|
928
|
+
|
929
|
+
files = Array.new
|
930
|
+
@data_path.each_child(false) do |file|
|
931
|
+
if file.extname == '.yml' && /^month_/.match(file.to_s)
|
932
|
+
files << file
|
933
|
+
end
|
934
|
+
end
|
935
|
+
|
936
|
+
date_start_year = 0
|
937
|
+
date_start_year = date_start.year if date_start
|
938
|
+
|
939
|
+
date_end_year = 9999
|
940
|
+
date_end_year = date_end.year if date_end
|
941
|
+
|
942
|
+
files
|
943
|
+
.map{ |file| file.to_s[6, 4].to_i }
|
944
|
+
.uniq
|
945
|
+
.keep_if{ |year| year >= date_start_year && year <= date_end_year }
|
942
946
|
end
|
943
947
|
|
944
948
|
def load_entries_index_file
|
945
949
|
unless @entries_index_is_loaded
|
946
950
|
@entries_index_is_loaded = true
|
947
|
-
if
|
948
|
-
data = YAML.load_file(@entries_index_file_path)
|
951
|
+
if @entries_index_file_path.exist?
|
952
|
+
data = YAML.load_file(@entries_index_file_path.to_s)
|
949
953
|
@entries_index = data['index']
|
950
954
|
end
|
951
955
|
end
|
952
956
|
end
|
953
957
|
|
954
958
|
def save_entries_index_file
|
955
|
-
store = YAML::Store.new(@entries_index_file_path)
|
959
|
+
store = YAML::Store.new(@entries_index_file_path.to_s)
|
956
960
|
store.transaction do
|
957
961
|
store['index'] = @entries_index
|
958
962
|
end
|