lucarecord 0.2.22 → 0.2.27

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 217630d4010c2341954f05600982cf641470b244440849c7816873ed8d10e8d2
4
- data.tar.gz: f535546634069491d64b3e0359fc9a40f10059cd08d216f8a947c28d717d1c6c
3
+ metadata.gz: 3e81c1ccc95c688bdfa2da3af37635324f1f744f1976b0dd3654514e2a1254b4
4
+ data.tar.gz: 2ed07a97b4bca099f1eb7004476960c3c2a8da03d06ee7f4db6e48226294f431
5
5
  SHA512:
6
- metadata.gz: 9e69a01066562a854ebe78128ead7c7d87be1227cfbce7453edf5510fa03f7b2acfbea5b334837b1c80e49e71a8a858d2d273277769e096baf29e4c90977816b
7
- data.tar.gz: c7caf90c504940b0533441f19cde460fbb680f69937412659ac04670f009b74ed84c5a2aa120f2554a7e4cfab8d4eba62309cd5c0ace1e057fc09400fdc05cee
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.
@@ -6,6 +6,8 @@ require 'luca_support'
6
6
 
7
7
  module LucaRecord
8
8
  class Base
9
+ CONFIG = LucaSupport::CONFIG
10
+ PJDIR = LucaSupport::PJDIR
9
11
  include LucaRecord::IO
10
12
  include LucaSupport::View
11
13
  end
@@ -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
- def search(word, default_word = nil)
23
- res = max_score_code(word.gsub(/[[:space:]]/, ''))
24
- if res[1] > 0.4
25
- res[0]
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
- default_word
34
+ res
28
35
  end
29
36
  end
30
37
 
38
+ # Search with unique code.
31
39
  #
32
- # Column number settings for CSV/TSV convert
33
- #
34
- # :label
35
- # for double entry data
36
- # :counter_label
37
- # must be specified with label
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 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
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
- input = self.class.load(@path)
129
- @config = input['config']
130
- @definitions = input['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::Config::Pjdir) / 'dict' / filename
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, LucaSupport.match_score(str, k, 3)]
137
+ [v, match_score(str, k, 2)]
144
138
  end
145
139
  res.max { |x, y| x[1] <=> y[1] }
146
140
  end
@@ -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::Config::Pjdir)
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
- case @record_type
343
- when 'raw'
344
- # TODO: raw may be unneeded in favor of override
345
- io
346
- when 'json'
347
- # TODO: implement JSON parse
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::Config::Pjdir) / 'data' / base_dir
386
+ Pathname(LucaSupport::PJDIR) / 'data' / base_dir
366
387
  end
367
388
 
368
389
  # true when file doesn't have record on code
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LucaRecord
4
- VERSION = '0.2.22'
4
+ VERSION = '0.2.27'
5
5
  end
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
@@ -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::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)
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::Config::DECIMAL_NUM)
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.dig(item)
177
- if target.is_a?(Array) && target.map(&:keys).flatten.include?('effective')
178
- latest = target
179
- .filter { |a| Date.parse(a.dig('effective').to_s) < @date }
180
- .max { |a, b| Date.parse(a.dig('effective').to_s) <=> Date.parse(b.dig('effective').to_s) }
181
- return nil if !latest.dig('defunct').nil? && Date.parse(latest.dig('defunct').to_s) < @date
182
-
183
- latest.dig('val') || latest
184
- else
185
- target
186
- end
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)
@@ -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
- YAML.load_file(Pathname(PJDIR) / 'config.yml', **{})
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
@@ -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
- [@pjdir, lib_path].each do |base|
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.22
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: 2020-11-22 00:00:00.000000000 Z
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.1.2
112
+ rubygems_version: 3.2.3
113
113
  signing_key:
114
114
  specification_version: 4
115
115
  summary: ERP File operation framework