lucarecord 0.2.22 → 0.2.27
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 +20 -0
- data/lib/luca_record/base.rb +2 -0
- data/lib/luca_record/dict.rb +51 -57
- data/lib/luca_record/io.rb +29 -8
- data/lib/luca_record/version.rb +1 -1
- data/lib/luca_support.rb +0 -11
- data/lib/luca_support/code.rb +62 -29
- data/lib/luca_support/config.rb +9 -13
- data/lib/luca_support/view.rb +5 -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: 3e81c1ccc95c688bdfa2da3af37635324f1f744f1976b0dd3654514e2a1254b4
|
4
|
+
data.tar.gz: 2ed07a97b4bca099f1eb7004476960c3c2a8da03d06ee7f4db6e48226294f431
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fcdd0ddae31a118c6532c872361535d89336fa712ba80395a10d5dda7a597c6300214ac2c1cdbab6782bd861d46e9da1e32c48e3fae182c33d63e78b0fcf7424
|
7
|
+
data.tar.gz: 4ec6114ea0f761e7ca4f743fce6c2594cac704c0bb6a32082ba35cbc5b9c6e639ce421d13ccf33a46a5359a4030974ff85447133391641beb83d2efaab3e1a91
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
## LucaRecord 0.2.26
|
2
|
+
|
3
|
+
* Support #dig / #search for TSV dictionary
|
4
|
+
* Fix: shorten n-gram split factor on search word length < specified factor
|
5
|
+
|
6
|
+
## LucaRecord 0.2.25
|
7
|
+
|
8
|
+
* Implement `dir_digest()` for data validation.
|
9
|
+
* support defunct without effective history record
|
10
|
+
|
11
|
+
## LucaRecord 0.2.24
|
12
|
+
|
13
|
+
* Digit delimiter for `delimit_num` can be customized through `thousands_separator` and `decimal_separator` in config.yml.
|
14
|
+
* Const `CONFIG` and `PJDIR` is defined at `LucaRecord::Base`.
|
15
|
+
* add `LucaSupport::Code.keys_stringify()`
|
16
|
+
|
17
|
+
## LucaRecord 0.2.23
|
18
|
+
|
19
|
+
* Enhance Dictionary, supporting extensible options.
|
20
|
+
|
1
21
|
## LucaRecord 0.2.22
|
2
22
|
|
3
23
|
* add `LucaSupport::View.nushell()`, render nushell table directly.
|
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,69 +13,49 @@ 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 code with n-gram word.
|
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
|
+
definitions_lazyload
|
25
|
+
res, score = max_score_code(word.gsub(/[[:space:]]/, ''))
|
26
|
+
return default_word if score < 0.4
|
27
|
+
|
28
|
+
case res
|
29
|
+
when Hash
|
30
|
+
hash2multiassign(res, main_key, options: options)
|
31
|
+
when Array
|
32
|
+
res.map { |item| hash2multiassign(item, main_key, options: options) }
|
26
33
|
else
|
27
|
-
|
34
|
+
res
|
28
35
|
end
|
29
36
|
end
|
30
37
|
|
38
|
+
# Search with unique code.
|
31
39
|
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
#
|
37
|
-
#
|
38
|
-
# :debit_label
|
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
|
40
|
+
def dig(*args)
|
41
|
+
@data.dig(*args)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Separate main item from other options.
|
45
|
+
# If options specified as Array of string, it works as safe list filter.
|
49
46
|
#
|
50
|
-
def
|
51
|
-
{}.tap do |
|
52
|
-
|
53
|
-
|
54
|
-
if
|
55
|
-
|
56
|
-
|
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
|
47
|
+
def hash2multiassign(obj, main_key = 'label', options: nil)
|
48
|
+
options = {}.tap do |opt|
|
49
|
+
obj.map do |k, v|
|
50
|
+
next if k == main_key
|
51
|
+
next if !options.nil? && !options.include?(k)
|
52
|
+
|
53
|
+
opt[k.to_sym] = v
|
64
54
|
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
55
|
end
|
56
|
+
[obj[main_key], options.compact]
|
77
57
|
end
|
78
58
|
|
79
|
-
#
|
80
59
|
# Load CSV with config options
|
81
60
|
#
|
82
61
|
def load_csv(path)
|
@@ -85,7 +64,6 @@ module LucaRecord
|
|
85
64
|
end
|
86
65
|
end
|
87
66
|
|
88
|
-
#
|
89
67
|
# load dictionary data
|
90
68
|
#
|
91
69
|
def self.load(file = @filename)
|
@@ -99,7 +77,6 @@ module LucaRecord
|
|
99
77
|
end
|
100
78
|
end
|
101
79
|
|
102
|
-
#
|
103
80
|
# generate dictionary from TSV file. Minimum assumption is as bellows:
|
104
81
|
# 1st row is converted symbol.
|
105
82
|
#
|
@@ -122,16 +99,33 @@ module LucaRecord
|
|
122
99
|
end
|
123
100
|
end
|
124
101
|
|
102
|
+
def self.validate(filename, target_key = :label)
|
103
|
+
errors = load(filename).map { |k, v| v[target_key].nil? ? k : nil }.compact
|
104
|
+
if errors.empty?
|
105
|
+
puts 'No error detected.'
|
106
|
+
nil
|
107
|
+
else
|
108
|
+
puts "Key #{errors.join(', ')} has nil #{target_key}."
|
109
|
+
errors.count
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
125
113
|
private
|
126
114
|
|
127
115
|
def set_driver
|
128
|
-
|
129
|
-
@config =
|
130
|
-
@definitions =
|
116
|
+
@data = self.class.load(@path)
|
117
|
+
@config = @data['config']
|
118
|
+
@definitions = @data['definitions']
|
119
|
+
end
|
120
|
+
|
121
|
+
# Build Reverse dictionary for TSV data
|
122
|
+
#
|
123
|
+
def definitions_lazyload
|
124
|
+
@definitions ||= @data.each_with_object({}) { |(k, entry), h| h[entry[:label]] = k if entry[:label] }
|
131
125
|
end
|
132
126
|
|
133
127
|
def self.dict_path(filename)
|
134
|
-
Pathname(LucaSupport::
|
128
|
+
Pathname(LucaSupport::PJDIR) / 'dict' / filename
|
135
129
|
end
|
136
130
|
|
137
131
|
def self.reverse(dict)
|
@@ -140,7 +134,7 @@ module LucaRecord
|
|
140
134
|
|
141
135
|
def max_score_code(str)
|
142
136
|
res = @definitions.map do |k, v|
|
143
|
-
[v,
|
137
|
+
[v, match_score(str, k, 2)]
|
144
138
|
end
|
145
139
|
res.max { |x, y| x[1] <=> y[1] }
|
146
140
|
end
|
data/lib/luca_record/io.rb
CHANGED
@@ -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
|
@@ -302,6 +313,14 @@ module LucaRecord # :nodoc:
|
|
302
313
|
end
|
303
314
|
end
|
304
315
|
|
316
|
+
# Calculate md5sum with original digest, file content and filename(optional).
|
317
|
+
#
|
318
|
+
def update_digest(digest, str, filename = nil)
|
319
|
+
str = filename.nil? ? str : filename + str
|
320
|
+
content = Digest::MD5.new.update(str).hexdigest
|
321
|
+
Digest::MD5.new.update(digest + content).hexdigest
|
322
|
+
end
|
323
|
+
|
305
324
|
# git object like structure
|
306
325
|
#
|
307
326
|
def open_hashed(basedir, id, mode = 'r')
|
@@ -339,12 +358,14 @@ module LucaRecord # :nodoc:
|
|
339
358
|
# If specific decode is needed, override this method in each class.
|
340
359
|
#
|
341
360
|
def load_data(io, path = nil)
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
361
|
+
if @record_type
|
362
|
+
case @record_type
|
363
|
+
when 'raw'
|
364
|
+
# TODO: raw may be unneeded in favor of override
|
365
|
+
io
|
366
|
+
when 'json'
|
367
|
+
# TODO: implement JSON parse
|
368
|
+
end
|
348
369
|
else
|
349
370
|
LucaSupport::Code.decimalize(YAML.load(io.read)).tap { |obj| validate_keys(obj) }
|
350
371
|
end
|
@@ -362,7 +383,7 @@ module LucaRecord # :nodoc:
|
|
362
383
|
|
363
384
|
# TODO: replace with data_dir method
|
364
385
|
def abs_path(base_dir)
|
365
|
-
Pathname(LucaSupport::
|
386
|
+
Pathname(LucaSupport::PJDIR) / 'data' / base_dir
|
366
387
|
end
|
367
388
|
|
368
389
|
# true when file doesn't have record on code
|
data/lib/luca_record/version.rb
CHANGED
data/lib/luca_support.rb
CHANGED
@@ -6,15 +6,4 @@ module LucaSupport
|
|
6
6
|
autoload :Config, 'luca_support/config'
|
7
7
|
autoload :Mail, 'luca_support/mail'
|
8
8
|
autoload :View, 'luca_support/view'
|
9
|
-
|
10
|
-
def self.match_score(a, b, n = 2)
|
11
|
-
v_a = to_ngram(a, n)
|
12
|
-
v_b = to_ngram(b, n)
|
13
|
-
|
14
|
-
v_a.map { |item| v_b.include?(item) ? 1 : 0 }.sum / v_a.length.to_f
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.to_ngram(str, n = 2)
|
18
|
-
str.each_char.each_cons(n).map(&:join)
|
19
|
-
end
|
20
9
|
end
|
data/lib/luca_support/code.rb
CHANGED
@@ -54,21 +54,24 @@ module LucaSupport
|
|
54
54
|
def delimit_num(num, decimal: nil, delimiter: nil)
|
55
55
|
return nil if num.nil?
|
56
56
|
|
57
|
-
decimal ||= LucaSupport::
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
72
75
|
end
|
73
76
|
|
74
77
|
# encode directory name from year and month.
|
@@ -101,7 +104,7 @@ module LucaSupport
|
|
101
104
|
(start_year..end_year).to_a.map do |y|
|
102
105
|
g1 = y == start_year ? encode_month(start_month) : encode_month(1)
|
103
106
|
g2 = y == end_year ? encode_month(end_month) : encode_month(12)
|
104
|
-
"#{y}[#{g1}-#{g2}]"
|
107
|
+
g1 == g2 ? "#{y}#{g1}" : "#{y}[#{g1}-#{g2}]"
|
105
108
|
end
|
106
109
|
end
|
107
110
|
|
@@ -114,6 +117,32 @@ module LucaSupport
|
|
114
117
|
Digest::SHA1.hexdigest(SecureRandom.uuid)
|
115
118
|
end
|
116
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
|
+
split_factor = [a.length, b.length, n].min
|
136
|
+
v_a = to_ngram(a, split_factor)
|
137
|
+
v_b = to_ngram(b, split_factor)
|
138
|
+
|
139
|
+
v_a.map { |item| v_b.include?(item) ? 1 : 0 }.sum / v_a.length.to_f
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_ngram(str, n = 2)
|
143
|
+
str.each_char.each_cons(n).map(&:join)
|
144
|
+
end
|
145
|
+
|
117
146
|
def decimalize(obj)
|
118
147
|
case obj.class.name
|
119
148
|
when 'Array'
|
@@ -131,7 +160,7 @@ module LucaSupport
|
|
131
160
|
end
|
132
161
|
end
|
133
162
|
|
134
|
-
def readable(obj, len = LucaSupport::
|
163
|
+
def readable(obj, len = LucaSupport::CONFIG['decimal_num'])
|
135
164
|
case obj
|
136
165
|
when Array
|
137
166
|
obj.map { |i| readable(i) }
|
@@ -159,7 +188,6 @@ module LucaSupport
|
|
159
188
|
end
|
160
189
|
end
|
161
190
|
|
162
|
-
#
|
163
191
|
# return current value with effective/defunct on target @date
|
164
192
|
# For multiple attribues, return hash on other than 'val'. Examples are as bellows:
|
165
193
|
#
|
@@ -172,18 +200,23 @@ module LucaSupport
|
|
172
200
|
# point: 1000
|
173
201
|
# => { 'effective' => 2020-1-1, 'rank' => 5, 'point' => 1000 }
|
174
202
|
#
|
203
|
+
# - defunct: 2020-1-1
|
204
|
+
# val: 3000
|
205
|
+
# => nil
|
206
|
+
#
|
175
207
|
def take_current(dat, item)
|
176
|
-
target = dat
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
208
|
+
target = dat&.dig(item)
|
209
|
+
return target unless target.is_a?(Array)
|
210
|
+
|
211
|
+
keys = target.map(&:keys).flatten
|
212
|
+
return target if !keys.include?('effective') && !keys.include?('defunct')
|
213
|
+
|
214
|
+
latest = target
|
215
|
+
.reject { |a| a['defunct'] && Date.parse(a['defunct'].to_s) < @date }
|
216
|
+
.filter { |a| a['effective'] && Date.parse(a['effective'].to_s) < @date }
|
217
|
+
.max { |a, b| Date.parse(a['effective'].to_s) <=> Date.parse(b['effective'].to_s) }
|
218
|
+
|
219
|
+
latest&.dig('val') || latest
|
187
220
|
end
|
188
221
|
|
189
222
|
def has_status?(dat, status)
|
data/lib/luca_support/config.rb
CHANGED
@@ -8,19 +8,15 @@ require 'yaml'
|
|
8
8
|
module LucaSupport
|
9
9
|
PJDIR = ENV['LUCA_TEST_DIR'] || Dir.pwd.freeze
|
10
10
|
CONFIG = begin
|
11
|
-
|
11
|
+
{
|
12
|
+
'decimal_separator' => '.',
|
13
|
+
'thousands_separator' => ','
|
14
|
+
}.merge(YAML.load_file(Pathname(PJDIR) / 'config.yml'))
|
12
15
|
rescue Errno::ENOENT
|
13
|
-
{
|
16
|
+
{
|
17
|
+
'decimal_separator' => '.',
|
18
|
+
'thousands_separator' => ','
|
19
|
+
}
|
14
20
|
end
|
15
|
-
|
16
|
-
module Config
|
17
|
-
# Project top directory.
|
18
|
-
Pjdir = ENV['LUCA_TEST_DIR'] || Dir.pwd.freeze
|
19
|
-
if File.exist?(Pathname(Pjdir) / 'config.yml')
|
20
|
-
# DECIMAL_NUM = YAML.load_file(Pathname(Pjdir) / 'config.yml', **{})['decimal_number']
|
21
|
-
COUNTRY = YAML.load_file(Pathname(Pjdir) / 'config.yml', **{})['country']
|
22
|
-
DECIMAL_NUM ||= 0 if COUNTRY == 'jp'
|
23
|
-
end
|
24
|
-
DECIMAL_NUM ||= 2
|
25
|
-
end
|
21
|
+
CONFIG['decimal_num'] ||= CONFIG['country'] == 'jp' ? 0 : 2
|
26
22
|
end
|
data/lib/luca_support/view.rb
CHANGED
@@ -34,9 +34,13 @@ 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
|
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.27
|
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-15 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
|