lucarecord 0.2.23 → 0.2.28

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a408c56d2c0f47238bc8c17e4135867e1be31b9897f940370a9bc8e87f8d014e
4
- data.tar.gz: 66fead2c3441f8b019f8d805fe564dbf9a9121abec424ea5d5482277d7bcc3ad
3
+ metadata.gz: c52d38b86c9670e3358ef9e94553902a3a511f626689566a4dc0b1d5e480c041
4
+ data.tar.gz: 064b974bbeeaa1dbb8e644532c5e2bee67cdd5d697e1cdde99087204698aa665
5
5
  SHA512:
6
- metadata.gz: a6f3d2e4773bc6c29c657a7fbe5864893efa39ec739ae1df4eece68fa82907bf99feb72f5a43888b59e39bc9f9a93f023fa4c0243e2429d3c04ba2c2f09d2e20
7
- data.tar.gz: 7fd98c339f4b81430dc961b009233e6de65228f6df83d361b81f4040857045740f0d2d18363e29bd2579be6b3d19a5a09cca37f9e39cfc1a8703212c32ba4e78
6
+ metadata.gz: 20ece81a5471e985bdc6b46aa341b3d859ad050b28a5976b0b20f15a44971ac0111b16a58db1053c91da73d91f8c37dc26e6bd825b4604a3c17ced65239a19b2
7
+ data.tar.gz: fc4af325295583e739fb972f3d96470f0a84d74ea4a4b330a16178d88de0c4700f64b28f36166539904c3b589c7540ec4486e36e74d6fd0d1e908da22e4cc685
data/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ ## LucaRecord 0.2.28
2
+
3
+ * implement LucaSupport::Range, handle #by_month enumeration between several months.
4
+ * @record_type = 'raw' is deprecated in favor of overriding LucaRecord::IO.load_data
5
+ * change code search from exact match to prefix match
6
+
7
+ ## LucaRecord 0.2.27
8
+
9
+ * Fix: update_digest
10
+
11
+ ## LucaRecord 0.2.26
12
+
13
+ * Support #dig / #search for TSV dictionary
14
+ * Fix: shorten n-gram split factor on search word length < specified factor
15
+
16
+ ## LucaRecord 0.2.25
17
+
18
+ * Implement `dir_digest()` for data validation.
19
+ * support defunct without effective history record
20
+
21
+ ## LucaRecord 0.2.24
22
+
23
+ * Digit delimiter for `delimit_num` can be customized through `thousands_separator` and `decimal_separator` in config.yml.
24
+ * Const `CONFIG` and `PJDIR` is defined at `LucaRecord::Base`.
25
+ * add `LucaSupport::Code.keys_stringify()`
26
+
1
27
  ## LucaRecord 0.2.23
2
28
 
3
29
  * Enhance Dictionary, supporting extensible options.
@@ -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
@@ -17,10 +17,11 @@ module LucaRecord
17
17
  set_driver
18
18
  end
19
19
 
20
- # Search word with n-gram.
20
+ # Search code with n-gram word.
21
21
  # If dictionary has Hash or Array, it returns [label, options].
22
22
  #
23
23
  def search(word, default_word = nil, main_key: 'label', options: nil)
24
+ definitions_lazyload
24
25
  res, score = max_score_code(word.gsub(/[[:space:]]/, ''))
25
26
  return default_word if score < 0.4
26
27
 
@@ -34,6 +35,12 @@ module LucaRecord
34
35
  end
35
36
  end
36
37
 
38
+ # Search with unique code.
39
+ #
40
+ def dig(*args)
41
+ @data.dig(*args)
42
+ end
43
+
37
44
  # Separate main item from other options.
38
45
  # If options specified as Array of string, it works as safe list filter.
39
46
  #
@@ -49,7 +56,6 @@ module LucaRecord
49
56
  [obj[main_key], options.compact]
50
57
  end
51
58
 
52
- #
53
59
  # Load CSV with config options
54
60
  #
55
61
  def load_csv(path)
@@ -58,7 +64,6 @@ module LucaRecord
58
64
  end
59
65
  end
60
66
 
61
- #
62
67
  # load dictionary data
63
68
  #
64
69
  def self.load(file = @filename)
@@ -72,7 +77,6 @@ module LucaRecord
72
77
  end
73
78
  end
74
79
 
75
- #
76
80
  # generate dictionary from TSV file. Minimum assumption is as bellows:
77
81
  # 1st row is converted symbol.
78
82
  #
@@ -101,7 +105,7 @@ module LucaRecord
101
105
  puts 'No error detected.'
102
106
  nil
103
107
  else
104
- "Key #{errors.join(', ')} has nil #{target_key}."
108
+ puts "Key #{errors.join(', ')} has nil #{target_key}."
105
109
  errors.count
106
110
  end
107
111
  end
@@ -109,13 +113,19 @@ module LucaRecord
109
113
  private
110
114
 
111
115
  def set_driver
112
- input = self.class.load(@path)
113
- @config = input['config']
114
- @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] }
115
125
  end
116
126
 
117
127
  def self.dict_path(filename)
118
- Pathname(LucaSupport::Config::Pjdir) / 'dict' / filename
128
+ Pathname(LucaSupport::PJDIR) / 'dict' / filename
119
129
  end
120
130
 
121
131
  def self.reverse(dict)
@@ -124,7 +134,7 @@ module LucaRecord
124
134
 
125
135
  def max_score_code(str)
126
136
  res = @definitions.map do |k, v|
127
- [v, match_score(str, k, 3)]
137
+ [v, match_score(str, k, 2)]
128
138
  end
129
139
  res.max { |x, y| x[1] <=> y[1] }
130
140
  end
@@ -64,11 +64,7 @@ module LucaRecord # :nodoc:
64
64
 
65
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
- if @record_type == 'raw'
68
- yield f, path
69
- else
70
- yield load_data(f, path)
71
- end
67
+ yield load_data(f, path)
72
68
  end
73
69
  end
74
70
  end
@@ -80,11 +76,7 @@ module LucaRecord # :nodoc:
80
76
 
81
77
  subdir = year.to_s + LucaSupport::Code.encode_month(month)
82
78
  open_records(basedir, subdir, LucaSupport::Code.encode_date(day), code) do |f, path|
83
- if @record_type == 'raw'
84
- yield f, path
85
- else
86
- yield load_data(f, path), path
87
- end
79
+ yield load_data(f, path), path
88
80
  end
89
81
  end
90
82
 
@@ -243,7 +235,7 @@ module LucaRecord # :nodoc:
243
235
  end
244
236
 
245
237
  # test if having required dirs/files under exec path
246
- def valid_project?(path = LucaSupport::Config::Pjdir)
238
+ def valid_project?(path = LucaSupport::PJDIR)
247
239
  project_dir = Pathname(path)
248
240
  FileTest.file?((project_dir + 'config.yml').to_s) and FileTest.directory?( (project_dir + 'data').to_s)
249
241
  end
@@ -252,6 +244,17 @@ module LucaRecord # :nodoc:
252
244
  LucaSupport::Code.encode_txid(new_record_no(basedir, date_obj))
253
245
  end
254
246
 
247
+ # Calculate md5sum under specific month directory.
248
+ #
249
+ def dir_digest(year, month, basedir = @dirname)
250
+ subdir = year.to_s + LucaSupport::Code.encode_month(month)
251
+ digest = String.new
252
+ open_records(basedir, subdir).each do |f, path|
253
+ digest = update_digest(digest, f.read, path[1])
254
+ end
255
+ digest
256
+ end
257
+
255
258
  private
256
259
 
257
260
  # define new transaction ID & write data at once
@@ -302,6 +305,14 @@ module LucaRecord # :nodoc:
302
305
  end
303
306
  end
304
307
 
308
+ # Calculate md5sum with original digest, file content and filename(optional).
309
+ #
310
+ def update_digest(digest, str, filename = nil)
311
+ str = filename.nil? ? str : filename + str
312
+ content = Digest::MD5.new.update(str).hexdigest
313
+ Digest::MD5.new.update(digest + content).hexdigest
314
+ end
315
+
305
316
  # git object like structure
306
317
  #
307
318
  def open_hashed(basedir, id, mode = 'r')
@@ -339,12 +350,11 @@ 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
- 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
353
+ if @record_type
354
+ case @record_type
355
+ when 'json'
356
+ # TODO: implement JSON parse
357
+ end
348
358
  else
349
359
  LucaSupport::Code.decimalize(YAML.load(io.read)).tap { |obj| validate_keys(obj) }
350
360
  end
@@ -362,17 +372,20 @@ module LucaRecord # :nodoc:
362
372
 
363
373
  # TODO: replace with data_dir method
364
374
  def abs_path(base_dir)
365
- Pathname(LucaSupport::Config::Pjdir) / 'data' / base_dir
375
+ Pathname(LucaSupport::PJDIR) / 'data' / base_dir
366
376
  end
367
377
 
368
- # true when file doesn't have record on code
369
- # false when file may have one
378
+ # True when file doesn't have record on code.
379
+ # False when file may have one.
380
+ # If filename doesn't record codes, always return false,
381
+ # so later check is required. This is partial optimization.
382
+ #
370
383
  def skip_on_unmatch_code(subpath, code = nil)
371
384
  # p filename.split('-')[1..-1]
372
385
  filename = subpath.split('/').last
373
386
  return false if code.nil? || filename.length <= 4
374
387
 
375
- !filename.split('-')[1..-1].include?(code)
388
+ filename.split('-')[1..-1].select { |fragment| /^#{code}/.match(fragment) }.empty?
376
389
  end
377
390
 
378
391
  # AUTO INCREMENT
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LucaRecord
4
- VERSION = '0.2.23'
4
+ VERSION = '0.2.28'
5
5
  end
@@ -5,8 +5,9 @@ require 'securerandom'
5
5
  require 'digest/sha1'
6
6
  require 'luca_support/config'
7
7
 
8
- # implement Luca IDs convention
9
- module LucaSupport
8
+ module LucaSupport # :nodoc:
9
+ # implement Luca IDs convention
10
+ #
10
11
  module Code
11
12
  module_function
12
13
 
@@ -54,21 +55,24 @@ module LucaSupport
54
55
  def delimit_num(num, decimal: nil, delimiter: nil)
55
56
  return nil if num.nil?
56
57
 
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)
58
+ decimal ||= LucaSupport::CONFIG['decimal_num']
59
+ delimiter ||= LucaSupport::CONFIG['thousands_separator']
60
+ case num
61
+ when BigDecimal
62
+ if decimal == 0
63
+ num.floor.to_s.reverse!.gsub(/(\d{3})(?=\d)/, '\1 ').reverse!
64
+ .gsub(/\s/, delimiter)
65
+ else
66
+ fragments = num.floor(decimal).to_s('F').split('.')
67
+ fragments[0].reverse!.gsub!(/(\d{3})(?=\d)/, '\1 ')
68
+ fragments[0].reverse!.gsub!(/\s/, delimiter)
69
+ fragments[1].gsub!(/(\d{3})(?=\d)/, '\1 ')
70
+ fragments.join(LucaSupport::CONFIG['decimal_separator'])
71
+ end
72
+ else
73
+ num.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\1 ').reverse!
74
+ .gsub(/\s/, delimiter)
75
+ end
72
76
  end
73
77
 
74
78
  # encode directory name from year and month.
@@ -114,9 +118,24 @@ module LucaSupport
114
118
  Digest::SHA1.hexdigest(SecureRandom.uuid)
115
119
  end
116
120
 
121
+ # Convert Hash keys to string recursively.
122
+ # Required for YAML compatibility.
123
+ #
124
+ def keys_stringify(dat)
125
+ case dat
126
+ when Array
127
+ dat.map { |d| keys_stringify(d) }
128
+ when Hash
129
+ dat.map { |k, v| [k.to_s, keys_stringify(v)] }.to_h
130
+ else
131
+ dat
132
+ end
133
+ end
134
+
117
135
  def match_score(a, b, n = 2)
118
- v_a = to_ngram(a, n)
119
- v_b = to_ngram(b, n)
136
+ split_factor = [a.length, b.length, n].min
137
+ v_a = to_ngram(a, split_factor)
138
+ v_b = to_ngram(b, split_factor)
120
139
 
121
140
  v_a.map { |item| v_b.include?(item) ? 1 : 0 }.sum / v_a.length.to_f
122
141
  end
@@ -142,7 +161,7 @@ module LucaSupport
142
161
  end
143
162
  end
144
163
 
145
- def readable(obj, len = LucaSupport::Config::DECIMAL_NUM)
164
+ def readable(obj, len = LucaSupport::CONFIG['decimal_num'])
146
165
  case obj
147
166
  when Array
148
167
  obj.map { |i| readable(i) }
@@ -170,7 +189,6 @@ module LucaSupport
170
189
  end
171
190
  end
172
191
 
173
- #
174
192
  # return current value with effective/defunct on target @date
175
193
  # For multiple attribues, return hash on other than 'val'. Examples are as bellows:
176
194
  #
@@ -183,18 +201,23 @@ module LucaSupport
183
201
  # point: 1000
184
202
  # => { 'effective' => 2020-1-1, 'rank' => 5, 'point' => 1000 }
185
203
  #
204
+ # - defunct: 2020-1-1
205
+ # val: 3000
206
+ # => nil
207
+ #
186
208
  def take_current(dat, item)
187
- target = dat.dig(item)
188
- if target.is_a?(Array) && target.map(&:keys).flatten.include?('effective')
189
- latest = target
190
- .filter { |a| Date.parse(a.dig('effective').to_s) < @date }
191
- .max { |a, b| Date.parse(a.dig('effective').to_s) <=> Date.parse(b.dig('effective').to_s) }
192
- return nil if !latest.dig('defunct').nil? && Date.parse(latest.dig('defunct').to_s) < @date
193
-
194
- latest.dig('val') || latest
195
- else
196
- target
197
- end
209
+ target = dat&.dig(item)
210
+ return target unless target.is_a?(Array)
211
+
212
+ keys = target.map(&:keys).flatten
213
+ return target if !keys.include?('effective') && !keys.include?('defunct')
214
+
215
+ latest = target
216
+ .reject { |a| a['defunct'] && Date.parse(a['defunct'].to_s) < @date }
217
+ .filter { |a| a['effective'] && Date.parse(a['effective'].to_s) < @date }
218
+ .max { |a, b| Date.parse(a['effective'].to_s) <=> Date.parse(b['effective'].to_s) }
219
+
220
+ latest&.dig('val') || latest
198
221
  end
199
222
 
200
223
  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
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LucaSupport # :nodoc:
4
+ # Partial range operation
5
+ #
6
+ module Range
7
+ def self.included(klass) # :nodoc:
8
+ klass.extend ClassMethods
9
+ end
10
+
11
+ def by_month(step = nil, from: nil, to: nil)
12
+ return enum_for(:by_month, step, from: from, to: to) unless block_given?
13
+
14
+ from ||= @start_date
15
+ to ||= @end_date
16
+ self.class.term_by_month(from, to, step || 1).each do |date|
17
+ @cursor_start = date
18
+ @cursor_end = step.nil? ? date : [date.next_month(step - 1), to].min
19
+ yield @cursor_start, @cursor_end
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ def term_by_month(start_date, end_date, step = 1)
25
+ Enumerator.new do |yielder|
26
+ each_month = start_date
27
+ while each_month <= end_date
28
+ yielder << each_month
29
+ each_month = each_month.next_month(step)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ 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.23
4
+ version: 0.2.28
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-25 00:00:00.000000000 Z
11
+ date: 2021-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mail
@@ -86,6 +86,7 @@ files:
86
86
  - lib/luca_support/code.rb
87
87
  - lib/luca_support/config.rb
88
88
  - lib/luca_support/mail.rb
89
+ - lib/luca_support/range.rb
89
90
  - lib/luca_support/view.rb
90
91
  homepage: https://github.com/chumaltd/luca/tree/master/lucarecord
91
92
  licenses:
@@ -109,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
110
  - !ruby/object:Gem::Version
110
111
  version: '0'
111
112
  requirements: []
112
- rubygems_version: 3.1.2
113
+ rubygems_version: 3.2.3
113
114
  signing_key:
114
115
  specification_version: 4
115
116
  summary: ERP File operation framework