thefox-wallet 0.14.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|