lucarecord 0.2.20 → 0.2.25
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/CHANGELOG.md +23 -0
- data/lib/luca_record/base.rb +2 -0
- data/lib/luca_record/dict.rb +36 -52
- data/lib/luca_record/io.rb +30 -9
- data/lib/luca_record/version.rb +1 -1
- data/lib/luca_support.rb +1 -11
- data/lib/luca_support/code.rb +77 -22
- data/lib/luca_support/config.rb +13 -11
- data/lib/luca_support/view.rb +10 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1982b60b00eddc3d201368a2f436835fa51c6ba58538de3fcd0ce962c4529246
|
4
|
+
data.tar.gz: 894ef1778f5a8be1f2091575ec86e84ceb61d0d158ba0cb4b8bc297c7070593a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 396bfbd54619361753b576fdda9f715b115ab274df10dce07abb1a5f180ed161946c72bae02c4926ff05d828e20af6920c689a412c71234c5baef072f870b528
|
7
|
+
data.tar.gz: 633d8920f547c893b2b1ac6645bdd108439d3568cfa3ec738c70ec61f1f658374e598bce0144a5a85142a15e4353d102e35b48ea84988f4dae06b0658a3952f7
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
## LucaRecord 0.2.25
|
2
|
+
|
3
|
+
* Implement `dir_digest()` for data validation.
|
4
|
+
* support defunct without effective history record
|
5
|
+
|
6
|
+
## LucaRecord 0.2.24
|
7
|
+
|
8
|
+
* Digit delimiter for `delimit_num` can be customized through `thousands_separator` and `decimal_separator` in config.yml.
|
9
|
+
* Const `CONFIG` and `PJDIR` is defined at `LucaRecord::Base`.
|
10
|
+
* add `LucaSupport::Code.keys_stringify()`
|
11
|
+
|
12
|
+
## LucaRecord 0.2.23
|
13
|
+
|
14
|
+
* Enhance Dictionary, supporting extensible options.
|
15
|
+
|
16
|
+
## LucaRecord 0.2.22
|
17
|
+
|
18
|
+
* add `LucaSupport::View.nushell()`, render nushell table directly.
|
19
|
+
|
20
|
+
## LucaRecord 0.2.21
|
21
|
+
|
22
|
+
* Enhance `LucaSupport::Code.delimit_num()`. Handle with BigDecimal, decimal length & delmiter customization.
|
23
|
+
|
1
24
|
## LucaRecord 0.2.20
|
2
25
|
|
3
26
|
* UUID completion framework on prefix match
|
data/lib/luca_record/base.rb
CHANGED
data/lib/luca_record/dict.rb
CHANGED
@@ -6,7 +6,6 @@ require 'yaml'
|
|
6
6
|
require 'pathname'
|
7
7
|
require 'luca_support'
|
8
8
|
|
9
|
-
#
|
10
9
|
# Low level API
|
11
10
|
#
|
12
11
|
module LucaRecord
|
@@ -14,66 +13,40 @@ module LucaRecord
|
|
14
13
|
include LucaSupport::Code
|
15
14
|
|
16
15
|
def initialize(file = @filename)
|
17
|
-
#@path = file
|
18
16
|
@path = self.class.dict_path(file)
|
19
17
|
set_driver
|
20
18
|
end
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
# Search word with n-gram.
|
21
|
+
# If dictionary has Hash or Array, it returns [label, options].
|
22
|
+
#
|
23
|
+
def search(word, default_word = nil, main_key: 'label', options: nil)
|
24
|
+
res, score = max_score_code(word.gsub(/[[:space:]]/, ''))
|
25
|
+
return default_word if score < 0.4
|
26
|
+
|
27
|
+
case res
|
28
|
+
when Hash
|
29
|
+
hash2multiassign(res, main_key, options: options)
|
30
|
+
when Array
|
31
|
+
res.map { |item| hash2multiassign(item, main_key, options: options) }
|
26
32
|
else
|
27
|
-
|
33
|
+
res
|
28
34
|
end
|
29
35
|
end
|
30
36
|
|
37
|
+
# Separate main item from other options.
|
38
|
+
# If options specified as Array of string, it works as safe list filter.
|
31
39
|
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
# for double entry data
|
40
|
-
# * debit_value
|
41
|
-
# :credit_label
|
42
|
-
# for double entry data
|
43
|
-
# * credit_value
|
44
|
-
# :note
|
45
|
-
# can be the same column as another label
|
46
|
-
#
|
47
|
-
# :encoding
|
48
|
-
# file encoding
|
49
|
-
#
|
50
|
-
def csv_config
|
51
|
-
{}.tap do |config|
|
52
|
-
if @config.dig('label')
|
53
|
-
config[:label] = @config['label'].to_i
|
54
|
-
if @config.dig('counter_label')
|
55
|
-
config[:counter_label] = @config['counter_label']
|
56
|
-
config[:type] = 'single'
|
57
|
-
end
|
58
|
-
elsif @config.dig('debit_label')
|
59
|
-
config[:debit_label] = @config['debit_label'].to_i
|
60
|
-
if @config.dig('credit_label')
|
61
|
-
config[:credit_label] = @config['credit_label'].to_i
|
62
|
-
config[:type] = 'double'
|
63
|
-
end
|
40
|
+
def hash2multiassign(obj, main_key = 'label', options: nil)
|
41
|
+
options = {}.tap do |opt|
|
42
|
+
obj.map do |k, v|
|
43
|
+
next if k == main_key
|
44
|
+
next if !options.nil? && !options.include?(k)
|
45
|
+
|
46
|
+
opt[k.to_sym] = v
|
64
47
|
end
|
65
|
-
config[:type] ||= 'invalid'
|
66
|
-
config[:debit_value] = @config['debit_value'].to_i if @config.dig('debit_value')
|
67
|
-
config[:credit_value] = @config['credit_value'].to_i if @config.dig('credit_value')
|
68
|
-
config[:note] = @config['note'] if @config.dig('note')
|
69
|
-
config[:encoding] = @config['encoding'] if @config.dig('encoding')
|
70
|
-
|
71
|
-
config[:year] = @config['year'] if @config.dig('year')
|
72
|
-
config[:month] = @config['month'] if @config.dig('month')
|
73
|
-
config[:day] = @config['day'] if @config.dig('day')
|
74
|
-
config[:default_debit] = @config['default_debit'] if @config.dig('default_debit')
|
75
|
-
config[:default_credit] = @config['default_credit'] if @config.dig('default_credit')
|
76
48
|
end
|
49
|
+
[obj[main_key], options.compact]
|
77
50
|
end
|
78
51
|
|
79
52
|
#
|
@@ -122,6 +95,17 @@ module LucaRecord
|
|
122
95
|
end
|
123
96
|
end
|
124
97
|
|
98
|
+
def self.validate(filename, target_key = :label)
|
99
|
+
errors = load(filename).map { |k, v| v[target_key].nil? ? k : nil }.compact
|
100
|
+
if errors.empty?
|
101
|
+
puts 'No error detected.'
|
102
|
+
nil
|
103
|
+
else
|
104
|
+
"Key #{errors.join(', ')} has nil #{target_key}."
|
105
|
+
errors.count
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
125
109
|
private
|
126
110
|
|
127
111
|
def set_driver
|
@@ -131,7 +115,7 @@ module LucaRecord
|
|
131
115
|
end
|
132
116
|
|
133
117
|
def self.dict_path(filename)
|
134
|
-
Pathname(LucaSupport::
|
118
|
+
Pathname(LucaSupport::PJDIR) / 'dict' / filename
|
135
119
|
end
|
136
120
|
|
137
121
|
def self.reverse(dict)
|
@@ -140,7 +124,7 @@ module LucaRecord
|
|
140
124
|
|
141
125
|
def max_score_code(str)
|
142
126
|
res = @definitions.map do |k, v|
|
143
|
-
[v,
|
127
|
+
[v, match_score(str, k, 3)]
|
144
128
|
end
|
145
129
|
res.max { |x, y| x[1] <=> y[1] }
|
146
130
|
end
|
data/lib/luca_record/io.rb
CHANGED
@@ -62,7 +62,7 @@ module LucaRecord # :nodoc:
|
|
62
62
|
def term(start_year, start_month, end_year, end_month, code = nil, basedir = @dirname)
|
63
63
|
return enum_for(:term, start_year, start_month, end_year, end_month, code, basedir) unless block_given?
|
64
64
|
|
65
|
-
LucaSupport::Code.encode_term(start_year, start_month, end_year, end_month).each do |subdir|
|
65
|
+
LucaSupport::Code.encode_term(start_year, start_month, end_year, end_month).each do |subdir|
|
66
66
|
open_records(basedir, subdir, nil, code) do |f, path|
|
67
67
|
if @record_type == 'raw'
|
68
68
|
yield f, path
|
@@ -243,7 +243,7 @@ module LucaRecord # :nodoc:
|
|
243
243
|
end
|
244
244
|
|
245
245
|
# test if having required dirs/files under exec path
|
246
|
-
def valid_project?(path = LucaSupport::
|
246
|
+
def valid_project?(path = LucaSupport::PJDIR)
|
247
247
|
project_dir = Pathname(path)
|
248
248
|
FileTest.file?((project_dir + 'config.yml').to_s) and FileTest.directory?( (project_dir + 'data').to_s)
|
249
249
|
end
|
@@ -252,6 +252,17 @@ module LucaRecord # :nodoc:
|
|
252
252
|
LucaSupport::Code.encode_txid(new_record_no(basedir, date_obj))
|
253
253
|
end
|
254
254
|
|
255
|
+
# Calculate md5sum under specific month directory.
|
256
|
+
#
|
257
|
+
def dir_digest(year, month, basedir = @dirname)
|
258
|
+
subdir = year.to_s + LucaSupport::Code.encode_month(month)
|
259
|
+
digest = String.new
|
260
|
+
open_records(basedir, subdir).each do |f, path|
|
261
|
+
digest = update_digest(digest, f.read, path[1])
|
262
|
+
end
|
263
|
+
digest
|
264
|
+
end
|
265
|
+
|
255
266
|
private
|
256
267
|
|
257
268
|
# define new transaction ID & write data at once
|
@@ -339,12 +350,14 @@ module LucaRecord # :nodoc:
|
|
339
350
|
# If specific decode is needed, override this method in each class.
|
340
351
|
#
|
341
352
|
def load_data(io, path = nil)
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
353
|
+
if @record_type
|
354
|
+
case @record_type
|
355
|
+
when 'raw'
|
356
|
+
# TODO: raw may be unneeded in favor of override
|
357
|
+
io
|
358
|
+
when 'json'
|
359
|
+
# TODO: implement JSON parse
|
360
|
+
end
|
348
361
|
else
|
349
362
|
LucaSupport::Code.decimalize(YAML.load(io.read)).tap { |obj| validate_keys(obj) }
|
350
363
|
end
|
@@ -362,7 +375,7 @@ module LucaRecord # :nodoc:
|
|
362
375
|
|
363
376
|
# TODO: replace with data_dir method
|
364
377
|
def abs_path(base_dir)
|
365
|
-
Pathname(LucaSupport::
|
378
|
+
Pathname(LucaSupport::PJDIR) / 'data' / base_dir
|
366
379
|
end
|
367
380
|
|
368
381
|
# true when file doesn't have record on code
|
@@ -420,5 +433,13 @@ module LucaRecord # :nodoc:
|
|
420
433
|
{}
|
421
434
|
end
|
422
435
|
end
|
436
|
+
|
437
|
+
# Calculate md5sum with original digest, file content and filename(optional).
|
438
|
+
#
|
439
|
+
def update_digest(digest, str, filename = nil)
|
440
|
+
str = filename.nil? ? str : filename + str
|
441
|
+
content = Digest::MD5.new.update(str).hexdigest
|
442
|
+
Digest::MD5.new.update(digest + content).hexdigest
|
443
|
+
end
|
423
444
|
end
|
424
445
|
end
|
data/lib/luca_record/version.rb
CHANGED
data/lib/luca_support.rb
CHANGED
@@ -2,18 +2,8 @@
|
|
2
2
|
|
3
3
|
module LucaSupport
|
4
4
|
autoload :Code, 'luca_support/code'
|
5
|
+
autoload :CONFIG, 'luca_support/config'
|
5
6
|
autoload :Config, 'luca_support/config'
|
6
7
|
autoload :Mail, 'luca_support/mail'
|
7
8
|
autoload :View, 'luca_support/view'
|
8
|
-
|
9
|
-
def self.match_score(a, b, n = 2)
|
10
|
-
v_a = to_ngram(a, n)
|
11
|
-
v_b = to_ngram(b, n)
|
12
|
-
|
13
|
-
v_a.map { |item| v_b.include?(item) ? 1 : 0 }.sum / v_a.length.to_f
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.to_ngram(str, n = 2)
|
17
|
-
str.each_char.each_cons(n).map(&:join)
|
18
|
-
end
|
19
9
|
end
|
data/lib/luca_support/code.rb
CHANGED
@@ -48,8 +48,30 @@ module LucaSupport
|
|
48
48
|
'0123456789ABCDEFGHIJKLMNOPQRSTUV'.index(char)
|
49
49
|
end
|
50
50
|
|
51
|
-
|
52
|
-
|
51
|
+
# Format number in 3-digit-group.
|
52
|
+
# Long decimal is just ommitted with floor().
|
53
|
+
#
|
54
|
+
def delimit_num(num, decimal: nil, delimiter: nil)
|
55
|
+
return nil if num.nil?
|
56
|
+
|
57
|
+
decimal ||= LucaSupport::CONFIG['decimal_num']
|
58
|
+
delimiter ||= LucaSupport::CONFIG['thousands_separator']
|
59
|
+
case num
|
60
|
+
when BigDecimal
|
61
|
+
if decimal == 0
|
62
|
+
num.floor.to_s.reverse!.gsub(/(\d{3})(?=\d)/, '\1 ').reverse!
|
63
|
+
.gsub(/\s/, delimiter)
|
64
|
+
else
|
65
|
+
fragments = num.floor(decimal).to_s('F').split('.')
|
66
|
+
fragments[0].reverse!.gsub!(/(\d{3})(?=\d)/, '\1 ')
|
67
|
+
fragments[0].reverse!.gsub!(/\s/, delimiter)
|
68
|
+
fragments[1].gsub!(/(\d{3})(?=\d)/, '\1 ')
|
69
|
+
fragments.join(LucaSupport::CONFIG['decimal_separator'])
|
70
|
+
end
|
71
|
+
else
|
72
|
+
num.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\1 ').reverse!
|
73
|
+
.gsub(/\s/, delimiter)
|
74
|
+
end
|
53
75
|
end
|
54
76
|
|
55
77
|
# encode directory name from year and month.
|
@@ -82,7 +104,7 @@ module LucaSupport
|
|
82
104
|
(start_year..end_year).to_a.map do |y|
|
83
105
|
g1 = y == start_year ? encode_month(start_month) : encode_month(1)
|
84
106
|
g2 = y == end_year ? encode_month(end_month) : encode_month(12)
|
85
|
-
"#{y}[#{g1}-#{g2}]"
|
107
|
+
g1 == g2 ? "#{y}#{g1}" : "#{y}[#{g1}-#{g2}]"
|
86
108
|
end
|
87
109
|
end
|
88
110
|
|
@@ -95,6 +117,31 @@ module LucaSupport
|
|
95
117
|
Digest::SHA1.hexdigest(SecureRandom.uuid)
|
96
118
|
end
|
97
119
|
|
120
|
+
# Convert Hash keys to string recursively.
|
121
|
+
# Required for YAML compatibility.
|
122
|
+
#
|
123
|
+
def keys_stringify(dat)
|
124
|
+
case dat
|
125
|
+
when Array
|
126
|
+
dat.map { |d| keys_stringify(d) }
|
127
|
+
when Hash
|
128
|
+
dat.map { |k, v| [k.to_s, keys_stringify(v)] }.to_h
|
129
|
+
else
|
130
|
+
dat
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def match_score(a, b, n = 2)
|
135
|
+
v_a = to_ngram(a, n)
|
136
|
+
v_b = to_ngram(b, n)
|
137
|
+
|
138
|
+
v_a.map { |item| v_b.include?(item) ? 1 : 0 }.sum / v_a.length.to_f
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_ngram(str, n = 2)
|
142
|
+
str.each_char.each_cons(n).map(&:join)
|
143
|
+
end
|
144
|
+
|
98
145
|
def decimalize(obj)
|
99
146
|
case obj.class.name
|
100
147
|
when 'Array'
|
@@ -112,15 +159,19 @@ module LucaSupport
|
|
112
159
|
end
|
113
160
|
end
|
114
161
|
|
115
|
-
def readable(obj, len = LucaSupport::
|
116
|
-
case obj
|
117
|
-
when
|
162
|
+
def readable(obj, len = LucaSupport::CONFIG['decimal_num'])
|
163
|
+
case obj
|
164
|
+
when Array
|
118
165
|
obj.map { |i| readable(i) }
|
119
|
-
when
|
166
|
+
when Hash
|
120
167
|
obj.inject({}) { |h, (k, v)| h[k] = readable(v); h }
|
121
|
-
when
|
122
|
-
|
123
|
-
|
168
|
+
when BigDecimal
|
169
|
+
if len == 0
|
170
|
+
obj.round # Integer is precise
|
171
|
+
else
|
172
|
+
parts = obj.round(len).to_s('F').split('.')
|
173
|
+
"#{parts[0]}.#{parts[1][0, len]}"
|
174
|
+
end
|
124
175
|
else
|
125
176
|
obj
|
126
177
|
end
|
@@ -136,7 +187,6 @@ module LucaSupport
|
|
136
187
|
end
|
137
188
|
end
|
138
189
|
|
139
|
-
#
|
140
190
|
# return current value with effective/defunct on target @date
|
141
191
|
# For multiple attribues, return hash on other than 'val'. Examples are as bellows:
|
142
192
|
#
|
@@ -149,18 +199,23 @@ module LucaSupport
|
|
149
199
|
# point: 1000
|
150
200
|
# => { 'effective' => 2020-1-1, 'rank' => 5, 'point' => 1000 }
|
151
201
|
#
|
202
|
+
# - defunct: 2020-1-1
|
203
|
+
# val: 3000
|
204
|
+
# => nil
|
205
|
+
#
|
152
206
|
def take_current(dat, item)
|
153
|
-
target = dat
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
207
|
+
target = dat&.dig(item)
|
208
|
+
return target unless target.is_a?(Array)
|
209
|
+
|
210
|
+
keys = target.map(&:keys).flatten
|
211
|
+
return target if !keys.include?('effective') && !keys.include?('defunct')
|
212
|
+
|
213
|
+
latest = target
|
214
|
+
.reject { |a| a['defunct'] && Date.parse(a['defunct'].to_s) < @date }
|
215
|
+
.filter { |a| a['effective'] && Date.parse(a['effective'].to_s) < @date }
|
216
|
+
.max { |a, b| Date.parse(a['effective'].to_s) <=> Date.parse(b['effective'].to_s) }
|
217
|
+
|
218
|
+
latest&.dig('val') || latest
|
164
219
|
end
|
165
220
|
|
166
221
|
def has_status?(dat, status)
|
data/lib/luca_support/config.rb
CHANGED
@@ -3,18 +3,20 @@
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'yaml'
|
5
5
|
|
6
|
-
#
|
7
6
|
# startup config
|
8
7
|
#
|
9
8
|
module LucaSupport
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
PJDIR = ENV['LUCA_TEST_DIR'] || Dir.pwd.freeze
|
10
|
+
CONFIG = begin
|
11
|
+
{
|
12
|
+
'decimal_separator' => '.',
|
13
|
+
'thousands_separator' => ','
|
14
|
+
}.merge(YAML.load_file(Pathname(PJDIR) / 'config.yml'))
|
15
|
+
rescue Errno::ENOENT
|
16
|
+
{
|
17
|
+
'decimal_separator' => '.',
|
18
|
+
'thousands_separator' => ','
|
19
|
+
}
|
20
|
+
end
|
21
|
+
CONFIG['decimal_num'] ||= CONFIG['country'] == 'jp' ? 0 : 2
|
20
22
|
end
|
data/lib/luca_support/view.rb
CHANGED
@@ -34,13 +34,22 @@ module LucaSupport
|
|
34
34
|
out
|
35
35
|
end
|
36
36
|
|
37
|
+
# Search existing file and return path under:
|
38
|
+
# 1. 'templates/' in Project directory that data resides
|
39
|
+
# 2. 'templates/' in Library directory that calls LucaSupport::View#search_template
|
40
|
+
#
|
37
41
|
def search_template(file, dir = 'templates')
|
38
42
|
# TODO: load config
|
39
|
-
[
|
43
|
+
[LucaSupport::PJDIR, lib_path].each do |base|
|
40
44
|
path = (Pathname(base) / dir / file)
|
41
45
|
return path.to_path if path.file?
|
42
46
|
end
|
43
47
|
nil
|
44
48
|
end
|
49
|
+
|
50
|
+
def nushell(yml)
|
51
|
+
require 'open3'
|
52
|
+
Open3.pipeline_w(%(nu -c 'cat - | from yaml')) { |stdin| stdin.puts yml }
|
53
|
+
end
|
45
54
|
end
|
46
55
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lucarecord
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.25
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chuma Takahiro
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mail
|
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
requirements: []
|
112
|
-
rubygems_version: 3.
|
112
|
+
rubygems_version: 3.2.3
|
113
113
|
signing_key:
|
114
114
|
specification_version: 4
|
115
115
|
summary: ERP File operation framework
|