lucarecord 0.2.17 → 0.2.22

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: 25005e140ff2445d9a7c39bb8805f94373d0cf5fdc74996248537c8493996a76
4
- data.tar.gz: 675eb686f9565bf73319117f41f6ea0f4e1a0202250cc8987f67aaa7a7cf677a
3
+ metadata.gz: 217630d4010c2341954f05600982cf641470b244440849c7816873ed8d10e8d2
4
+ data.tar.gz: f535546634069491d64b3e0359fc9a40f10059cd08d216f8a947c28d717d1c6c
5
5
  SHA512:
6
- metadata.gz: fb805203d9ff072caf90e281e5dbf4b062455b1707925cd16a319735dea95c346c6b959c2c4633c59630fcbc99209bfb7477e6764453dcad5bfac6bd68e68302
7
- data.tar.gz: b374ae84672a08400ed074f6a150780da0ae29558941178db941b99e0074c6e51e34245aeb4865fac0252a601d5f677b094f3e827e5bb91030e6cd88468096b1
6
+ metadata.gz: 9e69a01066562a854ebe78128ead7c7d87be1227cfbce7453edf5510fa03f7b2acfbea5b334837b1c80e49e71a8a858d2d273277769e096baf29e4c90977816b
7
+ data.tar.gz: c7caf90c504940b0533441f19cde460fbb680f69937412659ac04670f009b74ed84c5a2aa120f2554a7e4cfab8d4eba62309cd5c0ace1e057fc09400fdc05cee
@@ -0,0 +1,25 @@
1
+ ## LucaRecord 0.2.22
2
+
3
+ * add `LucaSupport::View.nushell()`, render nushell table directly.
4
+
5
+ ## LucaRecord 0.2.21
6
+
7
+ * Enhance `LucaSupport::Code.delimit_num()`. Handle with BigDecimal, decimal length & delmiter customization.
8
+
9
+ ## LucaRecord 0.2.20
10
+
11
+ * UUID completion framework on prefix match
12
+
13
+ ## LucaRecord 0.2.19
14
+
15
+ * `LucaSupport::Code.decode_id()`
16
+ * `LucaSupport::Code.encode_term()` for multiple months search. Old `scan_term()` removed.
17
+
18
+ ## LucaRecord 0.2.18
19
+
20
+ * `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.
21
+
22
+ ## LucaRecord 0.2.17
23
+
24
+ * Change internal number format to BigDecimal.
25
+ * Number of Decimal is configurable through `decimal_number` in config.yml(default = 2). `country` setting can also affect.
@@ -20,7 +20,7 @@ module LucaRecord
20
20
  end
21
21
 
22
22
  def search(word, default_word = nil)
23
- res = max_score_code(word)
23
+ res = max_score_code(word.gsub(/[[:space:]]/, ''))
24
24
  if res[1] > 0.4
25
25
  res[0]
26
26
  else
@@ -65,12 +65,14 @@ module LucaRecord
65
65
  config[:type] ||= 'invalid'
66
66
  config[:debit_value] = @config['debit_value'].to_i if @config.dig('debit_value')
67
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')
68
+ config[:note] = @config['note'] if @config.dig('note')
69
69
  config[:encoding] = @config['encoding'] if @config.dig('encoding')
70
70
 
71
71
  config[:year] = @config['year'] if @config.dig('year')
72
72
  config[:month] = @config['month'] if @config.dig('month')
73
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')
74
76
  end
75
77
  end
76
78
 
@@ -36,9 +36,9 @@ module LucaRecord # :nodoc:
36
36
  open_hashed(basedir, id) do |f|
37
37
  yield load_data(f)
38
38
  end
39
- elsif id.length >= 9
40
- # TODO: need regexp match for more flexible coding(after AD9999)
41
- open_records(basedir, id[0, 5], id[5, 6]) do |f, path|
39
+ elsif id.length >= 7
40
+ parts = id.split('/')
41
+ open_records(basedir, parts[0], parts[1]) do |f, path|
42
42
  yield load_data(f, path)
43
43
  end
44
44
  else
@@ -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) do |data, path|
58
- yield data, path
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
 
@@ -90,26 +104,48 @@ module LucaRecord # :nodoc:
90
104
  # of each concrete class.
91
105
  # ----------------------------------------------------------------
92
106
 
93
- # create hash based record
94
- def create(obj, basedir = @dirname)
107
+ # create record both of uuid/date identified.
108
+ #
109
+ def create(obj, date: nil, codes: nil, basedir: @dirname)
95
110
  validate_keys(obj)
96
- id = LucaSupport::Code.issue_random_id
97
- obj['id'] = id
98
- open_hashed(basedir, id, 'w') do |f|
99
- f.write(YAML.dump(LucaSupport::Code.readable(obj.sort.to_h)))
111
+ if date
112
+ create_record(obj, date, codes, basedir)
113
+ else
114
+ obj['id'] = LucaSupport::Code.issue_random_id
115
+ open_hashed(basedir, obj['id'], 'w') do |f|
116
+ f.write(YAML.dump(LucaSupport::Code.readable(obj.sort.to_h)))
117
+ end
118
+ obj['id']
100
119
  end
101
- id
102
120
  end
103
121
 
104
- # define new transaction ID & write data at once
105
- def create_record!(obj, date_obj, codes = nil, basedir = @dirname)
106
- gen_record_file!(basedir, date_obj, codes) do |f|
107
- f.write(YAML.dump(LucaSupport::Code.readable(obj.sort.to_h)))
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!('/', '') }
108
144
  end
109
145
  end
110
146
 
111
147
  def prepare_dir!(basedir, date_obj)
112
- dir_name = (Pathname(basedir) + encode_dirname(date_obj)).to_s
148
+ dir_name = (Pathname(basedir) + LucaSupport::Code.encode_dirname(date_obj)).to_s
113
149
  FileUtils.mkdir_p(dir_name) unless Dir.exist?(dir_name)
114
150
  dir_name
115
151
  end
@@ -129,8 +165,17 @@ module LucaRecord # :nodoc:
129
165
  create(obj, basedir)
130
166
  else
131
167
  validate_keys(obj)
132
- open_hashed(basedir, obj['id'], 'w') do |f|
133
- f.write(YAML.dump(LucaSupport::Code.readable(obj.sort.to_h)))
168
+ if obj['id'].length < 40
169
+ parts = obj['id'].split('/')
170
+ raise 'invalid ID' if parts.length != 2
171
+
172
+ open_records(basedir, parts[0], parts[1], nil, 'w') do |f, path|
173
+ f.write(YAML.dump(LucaSupport::Code.readable(obj.sort.to_h)))
174
+ end
175
+ else
176
+ open_hashed(basedir, obj['id'], 'w') do |f|
177
+ f.write(YAML.dump(LucaSupport::Code.readable(obj.sort.to_h)))
178
+ end
134
179
  end
135
180
  end
136
181
  obj['id']
@@ -142,6 +187,21 @@ module LucaRecord # :nodoc:
142
187
  id
143
188
  end
144
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
+
145
205
  # ----------------------------------------------------------------
146
206
  # :section: Path Utilities
147
207
  # ----------------------------------------------------------------
@@ -182,10 +242,6 @@ module LucaRecord # :nodoc:
182
242
  end
183
243
  end
184
244
 
185
- def encode_dirname(date_obj)
186
- date_obj.year.to_s + LucaSupport::Code.encode_month(date_obj)
187
- end
188
-
189
245
  # test if having required dirs/files under exec path
190
246
  def valid_project?(path = LucaSupport::Config::Pjdir)
191
247
  project_dir = Pathname(path)
@@ -198,6 +254,29 @@ module LucaRecord # :nodoc:
198
254
 
199
255
  private
200
256
 
257
+ # define new transaction ID & write data at once
258
+ # ID format is like '2020H/A001', which means record no.1 of 2020/10/10.
259
+ # Any data format can be written with block.
260
+ #
261
+ def create_record(obj, date_obj, codes = nil, basedir = @dirname)
262
+ FileUtils.mkdir_p(abs_path(basedir)) unless Dir.exist?(abs_path(basedir))
263
+ subdir = "#{date_obj.year}#{LucaSupport::Code.encode_month(date_obj)}"
264
+ filename = LucaSupport::Code.encode_date(date_obj) + new_record_id(basedir, date_obj)
265
+ obj['id'] = "#{subdir}/#{filename}" if obj.is_a? Hash
266
+ filename += '-' + codes.join('-') if codes
267
+ Dir.chdir(abs_path(basedir)) do
268
+ FileUtils.mkdir_p(subdir) unless Dir.exist?(subdir)
269
+ File.open(Pathname(subdir) / filename, 'w') do |f|
270
+ if block_given?
271
+ yield(f)
272
+ else
273
+ f.write(YAML.dump(LucaSupport::Code.readable(obj.sort.to_h)))
274
+ end
275
+ end
276
+ end
277
+ "#{subdir}/#{filename}"
278
+ end
279
+
201
280
  # open records with 'basedir/month/date-code' path structure.
202
281
  # Glob pattern can be specified like folloing examples.
203
282
  #
@@ -213,6 +292,7 @@ module LucaRecord # :nodoc:
213
292
 
214
293
  file_pattern = filename.nil? ? '*' : "#{filename}*"
215
294
  Dir.chdir(abs_path(basedir)) do
295
+ FileUtils.mkdir_p(subdir) if mode == 'w' && !Dir.exist?(subdir)
216
296
  Dir.glob("#{subdir}*/#{file_pattern}").sort.each do |subpath|
217
297
  next if skip_on_unmatch_code(subpath, code)
218
298
 
@@ -244,6 +324,17 @@ module LucaRecord # :nodoc:
244
324
  end
245
325
  end
246
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
+
247
338
  # Decode basic format.
248
339
  # If specific decode is needed, override this method in each class.
249
340
  #
@@ -255,8 +346,7 @@ module LucaRecord # :nodoc:
255
346
  when 'json'
256
347
  # TODO: implement JSON parse
257
348
  else
258
- YAML.load(io.read).tap { |obj| validate_keys(obj) }
259
- .inject({}) { |h, (k, v)| h[k] = LucaSupport::Code.decimalize(v); h }
349
+ LucaSupport::Code.decimalize(YAML.load(io.read)).tap { |obj| validate_keys(obj) }
260
350
  end
261
351
  end
262
352
 
@@ -270,16 +360,6 @@ module LucaRecord # :nodoc:
270
360
  end
271
361
  end
272
362
 
273
- def gen_record_file!(basedir, date_obj, codes = nil)
274
- d = prepare_dir!(abs_path(basedir), date_obj)
275
- filename = LucaSupport::Code.encode_date(date_obj) + new_record_id(abs_path(basedir), date_obj)
276
- if codes
277
- filename += codes.inject('') { |fragment, code| "#{fragment}-#{code}" }
278
- end
279
- path = Pathname(d) + filename
280
- File.open(path.to_s, 'w') { |f| yield(f) }
281
- end
282
-
283
363
  # TODO: replace with data_dir method
284
364
  def abs_path(base_dir)
285
365
  Pathname(LucaSupport::Config::Pjdir) / 'data' / base_dir
@@ -297,8 +377,10 @@ module LucaRecord # :nodoc:
297
377
 
298
378
  # AUTO INCREMENT
299
379
  def new_record_no(basedir, date_obj)
300
- dir_name = (Pathname(basedir) + encode_dirname(date_obj)).to_s
301
- raise 'No target dir exists.' unless Dir.exist?(dir_name)
380
+ raise 'No target dir exists.' unless Dir.exist?(abs_path(basedir))
381
+
382
+ dir_name = (Pathname(abs_path(basedir)) / LucaSupport::Code.encode_dirname(date_obj)).to_s
383
+ return 1 unless Dir.exist?(dir_name)
302
384
 
303
385
  Dir.chdir(dir_name) do
304
386
  last_file = Dir.glob("#{LucaSupport::Code.encode_date(date_obj)}*").max
@@ -330,17 +412,6 @@ module LucaRecord # :nodoc:
330
412
  end
331
413
  end
332
414
 
333
- # parse data dir and respond existing months
334
- #
335
- def scan_terms(base_dir, query = nil)
336
- pattern = query.nil? ? "*" : "#{query}*"
337
- Dir.chdir(base_dir) do
338
- Dir.glob(pattern).select { |dir|
339
- FileTest.directory?(dir) && /^[0-9]/.match(dir)
340
- }.sort.map { |str| decode_term(str) }
341
- end
342
- end
343
-
344
415
  def load_config(path = nil)
345
416
  path = path.to_s
346
417
  if File.exist?(path)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LucaRecord
4
- VERSION = '0.2.17'
4
+ VERSION = '0.2.22'
5
5
  end
@@ -2,6 +2,7 @@
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'
@@ -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,11 +48,35 @@ module LucaSupport
41
48
  '0123456789ABCDEFGHIJKLMNOPQRSTUV'.index(char)
42
49
  end
43
50
 
44
- def delimit_num(num)
45
- num.to_s.reverse.gsub!(/(\d{3})(?=\d)/, '\1,').reverse!
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
 
74
+ # encode directory name from year and month.
48
75
  #
76
+ def encode_dirname(date_obj)
77
+ date_obj.year.to_s + encode_month(date_obj)
78
+ end
79
+
49
80
  # Month to code conversion.
50
81
  # Date, DateTime, String, Integer is valid input. If nil, returns empty String for consistency.
51
82
  #
@@ -64,6 +95,16 @@ module LucaSupport
64
95
  '0ABCDEFGHIJKL'.index(char)
65
96
  end
66
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
+ "#{y}[#{g1}-#{g2}]"
105
+ end
106
+ end
107
+
67
108
  def decode_term(char)
68
109
  m = /^([0-9]{4})([A-La-l])/.match(char)
69
110
  [m[1].to_i, decode_month(m[2])]
@@ -91,14 +132,18 @@ module LucaSupport
91
132
  end
92
133
 
93
134
  def readable(obj, len = LucaSupport::Config::DECIMAL_NUM)
94
- case obj.class.name
95
- when 'Array'
135
+ case obj
136
+ when Array
96
137
  obj.map { |i| readable(i) }
97
- when 'Hash'
138
+ when Hash
98
139
  obj.inject({}) { |h, (k, v)| h[k] = readable(v); h }
99
- when 'BigDecimal'
100
- parts = obj.round(len).to_s('F').split('.')
101
- len < 1 ? parts.first : "#{parts[0]}.#{parts[1][0, len]}"
140
+ when BigDecimal
141
+ if len == 0
142
+ obj.round # Integer is precise
143
+ else
144
+ parts = obj.round(len).to_s('F').split('.')
145
+ "#{parts[0]}.#{parts[1][0, len]}"
146
+ end
102
147
  else
103
148
  obj
104
149
  end
@@ -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
@@ -42,5 +42,10 @@ module LucaSupport
42
42
  end
43
43
  nil
44
44
  end
45
+
46
+ def nushell(yml)
47
+ require 'open3'
48
+ Open3.pipeline_w(%(nu -c 'cat - | from yaml')) { |stdin| stdin.puts yml }
49
+ end
45
50
  end
46
51
  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.17
4
+ version: 0.2.22
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-07 00:00:00.000000000 Z
11
+ date: 2020-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mail