lucarecord 0.2.19 → 0.2.24

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: e5d79527ced912cddc0f5d33e7d557dd23fb2e2ae0ed0437f7c90eb44095469e
4
- data.tar.gz: ce8fc1c4ec1264913024981a6769a36d6439d6d5b89a820bcfa7fcb6991151bb
3
+ metadata.gz: 88e839f7ee18513f3798a568e65bd067eb16b87209f895f1b13a0deb43433837
4
+ data.tar.gz: 9292a2d9a8e53cab0b5d4aabcea77f5a9a45f8fb504e048ac081a22e86b2ae28
5
5
  SHA512:
6
- metadata.gz: f4f62de1a08971733954c469d2572f6aab8018379de928ab2413aca14d9123b08da67e5f85dcd8bf632c646732cf5c534f259bd83fb5eeff0d851e4d444b7e1c
7
- data.tar.gz: 2ef832f91abef366760fbff0157c97c9e43a814b1df6f1c5244e0e0f7ad32db8459f2abee51fd1fd40812283b87b6b2a1140d6668b98ab98d86088d17d3bb0e4
6
+ metadata.gz: b6d39c80ffea32406bd009cbd1c3393a8c02c52128ac6365f395ff68af2f1b8964aa1bdd2b7f951dab00aad00a3659bff2883dd839e3179bd013b8e1026185d5
7
+ data.tar.gz: 8577ad3aee104dc8341b20ec04aae3f18a0145ca826165696f726cce4a2f25661c1205fb79ce2e43d5d361f7c89a449607f8854349c7ea0c6a6a987da1c3aaee
@@ -1,3 +1,25 @@
1
+ ## LucaRecord 0.2.24
2
+
3
+ * Digit delimiter for `delimit_num` can be customized through `thousands_separator` and `decimal_separator` in config.yml.
4
+ * Const `CONFIG` and `PJDIR` is defined at `LucaRecord::Base`.
5
+ * add `LucaSupport::Code.keys_stringify()`
6
+
7
+ ## LucaRecord 0.2.23
8
+
9
+ * Enhance Dictionary, supporting extensible options.
10
+
11
+ ## LucaRecord 0.2.22
12
+
13
+ * add `LucaSupport::View.nushell()`, render nushell table directly.
14
+
15
+ ## LucaRecord 0.2.21
16
+
17
+ * Enhance `LucaSupport::Code.delimit_num()`. Handle with BigDecimal, decimal length & delmiter customization.
18
+
19
+ ## LucaRecord 0.2.20
20
+
21
+ * UUID completion framework on prefix match
22
+
1
23
  ## LucaRecord 0.2.19
2
24
 
3
25
  * `LucaSupport::Code.decode_id()`
@@ -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,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
- 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 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
- default_word
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
- # 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
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'] 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
@@ -129,7 +115,7 @@ module LucaRecord
129
115
  end
130
116
 
131
117
  def self.dict_path(filename)
132
- Pathname(LucaSupport::Config::Pjdir) / 'dict' / filename
118
+ Pathname(LucaSupport::PJDIR) / 'dict' / filename
133
119
  end
134
120
 
135
121
  def self.reverse(dict)
@@ -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, LucaSupport.match_score(str, k, 3)]
127
+ [v, match_score(str, k, 3)]
142
128
  end
143
129
  res.max { |x, y| x[1] <=> y[1] }
144
130
  end
@@ -62,7 +62,7 @@ module LucaRecord # :nodoc:
62
62
  def term(start_year, start_month, end_year, end_month, code = nil, basedir = @dirname)
63
63
  return enum_for(:term, start_year, start_month, end_year, end_month, code, basedir) unless block_given?
64
64
 
65
- LucaSupport::Code.encode_term(start_year, start_month, end_year, end_month).each do |subdir|
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
67
  if @record_type == 'raw'
68
68
  yield f, path
@@ -119,6 +119,31 @@ module LucaRecord # :nodoc:
119
119
  end
120
120
  end
121
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
+
122
147
  def prepare_dir!(basedir, date_obj)
123
148
  dir_name = (Pathname(basedir) + LucaSupport::Code.encode_dirname(date_obj)).to_s
124
149
  FileUtils.mkdir_p(dir_name) unless Dir.exist?(dir_name)
@@ -218,7 +243,7 @@ module LucaRecord # :nodoc:
218
243
  end
219
244
 
220
245
  # test if having required dirs/files under exec path
221
- def valid_project?(path = LucaSupport::Config::Pjdir)
246
+ def valid_project?(path = LucaSupport::PJDIR)
222
247
  project_dir = Pathname(path)
223
248
  FileTest.file?((project_dir + 'config.yml').to_s) and FileTest.directory?( (project_dir + 'data').to_s)
224
249
  end
@@ -337,7 +362,7 @@ module LucaRecord # :nodoc:
337
362
 
338
363
  # TODO: replace with data_dir method
339
364
  def abs_path(base_dir)
340
- Pathname(LucaSupport::Config::Pjdir) / 'data' / base_dir
365
+ Pathname(LucaSupport::PJDIR) / 'data' / base_dir
341
366
  end
342
367
 
343
368
  # 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.19'
4
+ VERSION = '0.2.24'
5
5
  end
@@ -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
@@ -48,8 +48,30 @@ module LucaSupport
48
48
  '0123456789ABCDEFGHIJKLMNOPQRSTUV'.index(char)
49
49
  end
50
50
 
51
- def delimit_num(num)
52
- 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
+ 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 ').reverse!
67
+ .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
53
75
  end
54
76
 
55
77
  # encode directory name from year and month.
@@ -82,7 +104,7 @@ module LucaSupport
82
104
  (start_year..end_year).to_a.map do |y|
83
105
  g1 = y == start_year ? encode_month(start_month) : encode_month(1)
84
106
  g2 = y == end_year ? encode_month(end_month) : encode_month(12)
85
- "#{y}[#{g1}-#{g2}]"
107
+ g1 == g2 ? "#{y}#{g1}" : "#{y}[#{g1}-#{g2}]"
86
108
  end
87
109
  end
88
110
 
@@ -95,6 +117,31 @@ module LucaSupport
95
117
  Digest::SHA1.hexdigest(SecureRandom.uuid)
96
118
  end
97
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
+ v_a = to_ngram(a, n)
136
+ v_b = to_ngram(b, n)
137
+
138
+ v_a.map { |item| v_b.include?(item) ? 1 : 0 }.sum / v_a.length.to_f
139
+ end
140
+
141
+ def to_ngram(str, n = 2)
142
+ str.each_char.each_cons(n).map(&:join)
143
+ end
144
+
98
145
  def decimalize(obj)
99
146
  case obj.class.name
100
147
  when 'Array'
@@ -112,15 +159,19 @@ module LucaSupport
112
159
  end
113
160
  end
114
161
 
115
- def readable(obj, len = LucaSupport::Config::DECIMAL_NUM)
116
- case obj.class.name
117
- when 'Array'
162
+ def readable(obj, len = LucaSupport::CONFIG['decimal_num'])
163
+ case obj
164
+ when Array
118
165
  obj.map { |i| readable(i) }
119
- when 'Hash'
166
+ when Hash
120
167
  obj.inject({}) { |h, (k, v)| h[k] = readable(v); h }
121
- when 'BigDecimal'
122
- parts = obj.round(len).to_s('F').split('.')
123
- len < 1 ? parts.first : "#{parts[0]}.#{parts[1][0, len]}"
168
+ when BigDecimal
169
+ if len == 0
170
+ obj.round # Integer is precise
171
+ else
172
+ parts = obj.round(len).to_s('F').split('.')
173
+ "#{parts[0]}.#{parts[1][0, len]}"
174
+ end
124
175
  else
125
176
  obj
126
177
  end
@@ -3,18 +3,20 @@
3
3
  require 'pathname'
4
4
  require 'yaml'
5
5
 
6
- #
7
6
  # startup config
8
7
  #
9
8
  module LucaSupport
10
- module Config
11
- # Project top directory.
12
- Pjdir = ENV['LUCA_TEST_DIR'] || Dir.pwd.freeze
13
- if File.exist?(Pathname(Pjdir) / 'config.yml')
14
- # DECIMAL_NUM = YAML.load_file(Pathname(Pjdir) / 'config.yml', **{})['decimal_number']
15
- COUNTRY = YAML.load_file(Pathname(Pjdir) / 'config.yml', **{})['country']
16
- DECIMAL_NUM ||= 0 if COUNTRY == 'jp'
17
- end
18
- DECIMAL_NUM ||= 2
19
- end
9
+ PJDIR = ENV['LUCA_TEST_DIR'] || Dir.pwd.freeze
10
+ CONFIG = begin
11
+ {
12
+ 'decimal_separator' => '.',
13
+ 'thousands_separator' => ','
14
+ }.merge(YAML.load_file(Pathname(PJDIR) / 'config.yml'))
15
+ rescue Errno::ENOENT
16
+ {
17
+ 'decimal_separator' => '.',
18
+ 'thousands_separator' => ','
19
+ }
20
+ end
21
+ CONFIG['decimal_num'] ||= CONFIG['country'] == 'jp' ? 0 : 2
20
22
  end
@@ -36,11 +36,16 @@ module LucaSupport
36
36
 
37
37
  def search_template(file, dir = 'templates')
38
38
  # TODO: load config
39
- [@pjdir, lib_path].each do |base|
39
+ [LucaSupport::PJDIR, lib_path].each do |base|
40
40
  path = (Pathname(base) / dir / file)
41
41
  return path.to_path if path.file?
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.19
4
+ version: 0.2.24
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-10 00:00:00.000000000 Z
11
+ date: 2020-11-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mail