lucarecord 0.2.19 → 0.2.24

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