lucarecord 0.2.23 → 0.2.28

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: 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