lucarecord 0.2.18 → 0.2.23
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 +21 -0
- data/lib/luca_record/dict.rb +35 -49
- data/lib/luca_record/io.rb +68 -15
- data/lib/luca_record/version.rb +1 -1
- data/lib/luca_support.rb +1 -11
- data/lib/luca_support/code.rb +59 -8
- data/lib/luca_support/config.rb +8 -2
- data/lib/luca_support/view.rb +5 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a408c56d2c0f47238bc8c17e4135867e1be31b9897f940370a9bc8e87f8d014e
|
4
|
+
data.tar.gz: 66fead2c3441f8b019f8d805fe564dbf9a9121abec424ea5d5482277d7bcc3ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a6f3d2e4773bc6c29c657a7fbe5864893efa39ec739ae1df4eece68fa82907bf99feb72f5a43888b59e39bc9f9a93f023fa4c0243e2429d3c04ba2c2f09d2e20
|
7
|
+
data.tar.gz: 7fd98c339f4b81430dc961b009233e6de65228f6df83d361b81f4040857045740f0d2d18363e29bd2579be6b3d19a5a09cca37f9e39cfc1a8703212c32ba4e78
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
## LucaRecord 0.2.23
|
2
|
+
|
3
|
+
* Enhance Dictionary, supporting extensible options.
|
4
|
+
|
5
|
+
## LucaRecord 0.2.22
|
6
|
+
|
7
|
+
* add `LucaSupport::View.nushell()`, render nushell table directly.
|
8
|
+
|
9
|
+
## LucaRecord 0.2.21
|
10
|
+
|
11
|
+
* Enhance `LucaSupport::Code.delimit_num()`. Handle with BigDecimal, decimal length & delmiter customization.
|
12
|
+
|
13
|
+
## LucaRecord 0.2.20
|
14
|
+
|
15
|
+
* UUID completion framework on prefix match
|
16
|
+
|
17
|
+
## LucaRecord 0.2.19
|
18
|
+
|
19
|
+
* `LucaSupport::Code.decode_id()`
|
20
|
+
* `LucaSupport::Code.encode_term()` for multiple months search. Old `scan_term()` removed.
|
21
|
+
|
1
22
|
## LucaRecord 0.2.18
|
2
23
|
|
3
24
|
* `find()`, `create()`, `save()` now supports both of uuid / historical records. If specified `date:` keyword option to `create()`, then generate historical record. `find()`, `save()` identifies with 'id' attribute.
|
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,64 +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'].to_i 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
48
|
end
|
49
|
+
[obj[main_key], options.compact]
|
75
50
|
end
|
76
51
|
|
77
52
|
#
|
@@ -120,6 +95,17 @@ module LucaRecord
|
|
120
95
|
end
|
121
96
|
end
|
122
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
|
+
|
123
109
|
private
|
124
110
|
|
125
111
|
def set_driver
|
@@ -138,7 +124,7 @@ module LucaRecord
|
|
138
124
|
|
139
125
|
def max_score_code(str)
|
140
126
|
res = @definitions.map do |k, v|
|
141
|
-
[v,
|
127
|
+
[v, match_score(str, k, 3)]
|
142
128
|
end
|
143
129
|
res.max { |x, y| x[1] <=> y[1] }
|
144
130
|
end
|
data/lib/luca_record/io.rb
CHANGED
@@ -54,8 +54,22 @@ module LucaRecord # :nodoc:
|
|
54
54
|
def asof(year, month = nil, day = nil, basedir = @dirname)
|
55
55
|
return enum_for(:search, year, month, day, nil, basedir) unless block_given?
|
56
56
|
|
57
|
-
search(year, month, day, nil, basedir)
|
58
|
-
|
57
|
+
search(year, month, day, nil, basedir) { |data, path| yield data, path }
|
58
|
+
end
|
59
|
+
|
60
|
+
# scan ranging data on multiple months
|
61
|
+
#
|
62
|
+
def term(start_year, start_month, end_year, end_month, code = nil, basedir = @dirname)
|
63
|
+
return enum_for(:term, start_year, start_month, end_year, end_month, code, basedir) unless block_given?
|
64
|
+
|
65
|
+
LucaSupport::Code.encode_term(start_year, start_month, end_year, end_month).each do |subdir|
|
66
|
+
open_records(basedir, subdir, nil, code) do |f, path|
|
67
|
+
if @record_type == 'raw'
|
68
|
+
yield f, path
|
69
|
+
else
|
70
|
+
yield load_data(f, path)
|
71
|
+
end
|
72
|
+
end
|
59
73
|
end
|
60
74
|
end
|
61
75
|
|
@@ -105,6 +119,31 @@ module LucaRecord # :nodoc:
|
|
105
119
|
end
|
106
120
|
end
|
107
121
|
|
122
|
+
# If multiple ID matched, return short ID and human readable label.
|
123
|
+
#
|
124
|
+
def id_completion(phrase, label: 'name', basedir: @dirname)
|
125
|
+
list = prefix_search(phrase, basedir: basedir)
|
126
|
+
case list.length
|
127
|
+
when 1
|
128
|
+
list
|
129
|
+
when 0
|
130
|
+
raise 'No match on specified phrase'
|
131
|
+
else
|
132
|
+
(3..list[0].length).each do |l|
|
133
|
+
if list.map { |id| id[0, l] }.uniq.length == list.length
|
134
|
+
return list.map { |id| { id: id[0, l], label: find(id).dig(label) } }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def prefix_search(phrase, basedir: @dirname)
|
141
|
+
glob_str = phrase.length <= 3 ? "#{phrase}*/*" : "#{id2path(phrase)}*"
|
142
|
+
Dir.chdir(abs_path(basedir)) do
|
143
|
+
Dir.glob(glob_str).to_a.map! { |path| path.gsub!('/', '') }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
108
147
|
def prepare_dir!(basedir, date_obj)
|
109
148
|
dir_name = (Pathname(basedir) + LucaSupport::Code.encode_dirname(date_obj)).to_s
|
110
149
|
FileUtils.mkdir_p(dir_name) unless Dir.exist?(dir_name)
|
@@ -148,6 +187,21 @@ module LucaRecord # :nodoc:
|
|
148
187
|
id
|
149
188
|
end
|
150
189
|
|
190
|
+
# change filename with new code set
|
191
|
+
#
|
192
|
+
def change_codes(id, new_codes, basedir = @dirname)
|
193
|
+
raise 'invalid id' if id.split('/').length != 2
|
194
|
+
|
195
|
+
newfile = new_codes.empty? ? id : id + '-' + new_codes.join('-')
|
196
|
+
Dir.chdir(abs_path(basedir)) do
|
197
|
+
origin = Dir.glob("#{id}*")
|
198
|
+
raise 'duplicated files' if origin.length != 1
|
199
|
+
|
200
|
+
File.rename(origin.first, newfile)
|
201
|
+
end
|
202
|
+
newfile
|
203
|
+
end
|
204
|
+
|
151
205
|
# ----------------------------------------------------------------
|
152
206
|
# :section: Path Utilities
|
153
207
|
# ----------------------------------------------------------------
|
@@ -270,6 +324,17 @@ module LucaRecord # :nodoc:
|
|
270
324
|
end
|
271
325
|
end
|
272
326
|
|
327
|
+
# parse data dir and respond existing months
|
328
|
+
#
|
329
|
+
def scan_terms(query = nil, base_dir = @dirname)
|
330
|
+
pattern = query.nil? ? "*" : "#{query}*"
|
331
|
+
Dir.chdir(abs_path(base_dir)) do
|
332
|
+
Dir.glob(pattern).select { |dir|
|
333
|
+
FileTest.directory?(dir) && /^[0-9]/.match(dir)
|
334
|
+
}.sort.map { |str| decode_term(str) }
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
273
338
|
# Decode basic format.
|
274
339
|
# If specific decode is needed, override this method in each class.
|
275
340
|
#
|
@@ -281,8 +346,7 @@ module LucaRecord # :nodoc:
|
|
281
346
|
when 'json'
|
282
347
|
# TODO: implement JSON parse
|
283
348
|
else
|
284
|
-
YAML.load(io.read).tap { |obj| validate_keys(obj) }
|
285
|
-
.inject({}) { |h, (k, v)| h[k] = LucaSupport::Code.decimalize(v); h }
|
349
|
+
LucaSupport::Code.decimalize(YAML.load(io.read)).tap { |obj| validate_keys(obj) }
|
286
350
|
end
|
287
351
|
end
|
288
352
|
|
@@ -348,17 +412,6 @@ module LucaRecord # :nodoc:
|
|
348
412
|
end
|
349
413
|
end
|
350
414
|
|
351
|
-
# parse data dir and respond existing months
|
352
|
-
#
|
353
|
-
def scan_terms(base_dir, query = nil)
|
354
|
-
pattern = query.nil? ? "*" : "#{query}*"
|
355
|
-
Dir.chdir(base_dir) do
|
356
|
-
Dir.glob(pattern).select { |dir|
|
357
|
-
FileTest.directory?(dir) && /^[0-9]/.match(dir)
|
358
|
-
}.sort.map { |str| decode_term(str) }
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
415
|
def load_config(path = nil)
|
363
416
|
path = path.to_s
|
364
417
|
if File.exist?(path)
|
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
@@ -10,6 +10,13 @@ module LucaSupport
|
|
10
10
|
module Code
|
11
11
|
module_function
|
12
12
|
|
13
|
+
# Parse historical id into Array of date & transaction id.
|
14
|
+
#
|
15
|
+
def decode_id(id_str)
|
16
|
+
m = %r(^(?<year>[0-9]+)(?<month>[A-L])/?(?<day>[0-9A-V])(?<txid>[0-9A-Z]{,3})).match(id_str)
|
17
|
+
["#{m[:year]}-#{decode_month(m[:month])}-#{decode_date(m[:day])}", decode_txid(m[:txid])]
|
18
|
+
end
|
19
|
+
|
13
20
|
def encode_txid(num)
|
14
21
|
txmap = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
15
22
|
l = txmap.length
|
@@ -41,8 +48,27 @@ module LucaSupport
|
|
41
48
|
'0123456789ABCDEFGHIJKLMNOPQRSTUV'.index(char)
|
42
49
|
end
|
43
50
|
|
44
|
-
|
45
|
-
|
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
|
+
str = case num
|
59
|
+
when BigDecimal
|
60
|
+
if decimal == 0
|
61
|
+
num.floor.to_s.reverse.gsub!(/(\d{3})(?=\d)/, '\1 ').reverse!
|
62
|
+
else
|
63
|
+
fragments = num.floor(decimal).to_s('F').split('.')
|
64
|
+
fragments[0].reverse!.gsub!(/(\d{3})(?=\d)/, '\1 ').reverse!
|
65
|
+
fragments[1].gsub!(/(\d{3})(?=\d)/, '\1 ')
|
66
|
+
fragments.join('.')
|
67
|
+
end
|
68
|
+
else
|
69
|
+
num.to_s.reverse.gsub!(/(\d{3})(?=\d)/, '\1 ').reverse!
|
70
|
+
end
|
71
|
+
delimiter.nil? ? str : str.gsub!(/\s/, delimiter)
|
46
72
|
end
|
47
73
|
|
48
74
|
# encode directory name from year and month.
|
@@ -69,6 +95,16 @@ module LucaSupport
|
|
69
95
|
'0ABCDEFGHIJKL'.index(char)
|
70
96
|
end
|
71
97
|
|
98
|
+
# Generate globbing phrase like ["2020[C-H]"] for range search.
|
99
|
+
#
|
100
|
+
def encode_term(start_year, start_month, end_year, end_month)
|
101
|
+
(start_year..end_year).to_a.map do |y|
|
102
|
+
g1 = y == start_year ? encode_month(start_month) : encode_month(1)
|
103
|
+
g2 = y == end_year ? encode_month(end_month) : encode_month(12)
|
104
|
+
g1 == g2 ? "#{y}#{g1}" : "#{y}[#{g1}-#{g2}]"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
72
108
|
def decode_term(char)
|
73
109
|
m = /^([0-9]{4})([A-La-l])/.match(char)
|
74
110
|
[m[1].to_i, decode_month(m[2])]
|
@@ -78,6 +114,17 @@ module LucaSupport
|
|
78
114
|
Digest::SHA1.hexdigest(SecureRandom.uuid)
|
79
115
|
end
|
80
116
|
|
117
|
+
def match_score(a, b, n = 2)
|
118
|
+
v_a = to_ngram(a, n)
|
119
|
+
v_b = to_ngram(b, n)
|
120
|
+
|
121
|
+
v_a.map { |item| v_b.include?(item) ? 1 : 0 }.sum / v_a.length.to_f
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_ngram(str, n = 2)
|
125
|
+
str.each_char.each_cons(n).map(&:join)
|
126
|
+
end
|
127
|
+
|
81
128
|
def decimalize(obj)
|
82
129
|
case obj.class.name
|
83
130
|
when 'Array'
|
@@ -96,14 +143,18 @@ module LucaSupport
|
|
96
143
|
end
|
97
144
|
|
98
145
|
def readable(obj, len = LucaSupport::Config::DECIMAL_NUM)
|
99
|
-
case obj
|
100
|
-
when
|
146
|
+
case obj
|
147
|
+
when Array
|
101
148
|
obj.map { |i| readable(i) }
|
102
|
-
when
|
149
|
+
when Hash
|
103
150
|
obj.inject({}) { |h, (k, v)| h[k] = readable(v); h }
|
104
|
-
when
|
105
|
-
|
106
|
-
|
151
|
+
when BigDecimal
|
152
|
+
if len == 0
|
153
|
+
obj.round # Integer is precise
|
154
|
+
else
|
155
|
+
parts = obj.round(len).to_s('F').split('.')
|
156
|
+
"#{parts[0]}.#{parts[1][0, len]}"
|
157
|
+
end
|
107
158
|
else
|
108
159
|
obj
|
109
160
|
end
|
data/lib/luca_support/config.rb
CHANGED
@@ -3,15 +3,21 @@
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'yaml'
|
5
5
|
|
6
|
-
#
|
7
6
|
# startup config
|
8
7
|
#
|
9
8
|
module LucaSupport
|
9
|
+
PJDIR = ENV['LUCA_TEST_DIR'] || Dir.pwd.freeze
|
10
|
+
CONFIG = begin
|
11
|
+
YAML.load_file(Pathname(PJDIR) / 'config.yml', **{})
|
12
|
+
rescue Errno::ENOENT
|
13
|
+
{}
|
14
|
+
end
|
15
|
+
|
10
16
|
module Config
|
11
17
|
# Project top directory.
|
12
18
|
Pjdir = ENV['LUCA_TEST_DIR'] || Dir.pwd.freeze
|
13
19
|
if File.exist?(Pathname(Pjdir) / 'config.yml')
|
14
|
-
DECIMAL_NUM = YAML.load_file(Pathname(Pjdir) / 'config.yml', **{})['decimal_number']
|
20
|
+
# DECIMAL_NUM = YAML.load_file(Pathname(Pjdir) / 'config.yml', **{})['decimal_number']
|
15
21
|
COUNTRY = YAML.load_file(Pathname(Pjdir) / 'config.yml', **{})['country']
|
16
22
|
DECIMAL_NUM ||= 0 if COUNTRY == 'jp'
|
17
23
|
end
|
data/lib/luca_support/view.rb
CHANGED
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.23
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chuma Takahiro
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-11-
|
11
|
+
date: 2020-11-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mail
|