lucarecord 0.2.15 → 0.2.20
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 +4 -4
- data/CHANGELOG.md +17 -0
- data/lib/luca_record/dict.rb +4 -2
- data/lib/luca_record/io.rb +146 -43
- data/lib/luca_record/version.rb +1 -1
- data/lib/luca_support/code.rb +55 -1
- data/lib/luca_support/config.rb +9 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e2644c50f67e64dfe2502400e04eddb6278a314d00f82eb5a51a6489797f0b48
|
4
|
+
data.tar.gz: c6d286442884c6675f18340ad2eb3322cd76515992530cbf4fc8536b66a6bbad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0122e43160066ce33dad8478fc99cd4bc639abf232788852ebce74328f664f413756ea91c73a6f6372f4dce9648a9c6776dd7f7ee1f0971930b71f0816a2841b
|
7
|
+
data.tar.gz: '09c51366863747bfea0cdf829d0829be6d084b79cbc729c3cb51ecea7f07196ca2c7ff0eea8b52d834d0af476de9cb953ef50d9e1b7e620de0c6f5d51c64f158'
|
data/CHANGELOG.md
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
## LucaRecord 0.2.20
|
2
|
+
|
3
|
+
* UUID completion framework on prefix match
|
4
|
+
|
5
|
+
## LucaRecord 0.2.19
|
6
|
+
|
7
|
+
* `LucaSupport::Code.decode_id()`
|
8
|
+
* `LucaSupport::Code.encode_term()` for multiple months search. Old `scan_term()` removed.
|
9
|
+
|
10
|
+
## LucaRecord 0.2.18
|
11
|
+
|
12
|
+
* `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.
|
13
|
+
|
14
|
+
## LucaRecord 0.2.17
|
15
|
+
|
16
|
+
* Change internal number format to BigDecimal.
|
17
|
+
* Number of Decimal is configurable through `decimal_number` in config.yml(default = 2). `country` setting can also affect.
|
data/lib/luca_record/dict.rb
CHANGED
@@ -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']
|
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
|
|
data/lib/luca_record/io.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'bigdecimal'
|
3
4
|
require 'csv'
|
4
5
|
require 'date'
|
5
6
|
require 'fileutils'
|
@@ -29,15 +30,15 @@ module LucaRecord # :nodoc:
|
|
29
30
|
|
30
31
|
# find ID based record. Support uuid and encoded date.
|
31
32
|
def find(id, basedir = @dirname)
|
32
|
-
return enum_for(:find, id, basedir) unless block_given?
|
33
|
+
return enum_for(:find, id, basedir).first unless block_given?
|
33
34
|
|
34
35
|
if id.length >= 40
|
35
36
|
open_hashed(basedir, id) do |f|
|
36
37
|
yield load_data(f)
|
37
38
|
end
|
38
|
-
elsif id.length >=
|
39
|
-
|
40
|
-
open_records(basedir,
|
39
|
+
elsif id.length >= 7
|
40
|
+
parts = id.split('/')
|
41
|
+
open_records(basedir, parts[0], parts[1]) do |f, path|
|
41
42
|
yield load_data(f, path)
|
42
43
|
end
|
43
44
|
else
|
@@ -53,8 +54,22 @@ module LucaRecord # :nodoc:
|
|
53
54
|
def asof(year, month = nil, day = nil, basedir = @dirname)
|
54
55
|
return enum_for(:search, year, month, day, nil, basedir) unless block_given?
|
55
56
|
|
56
|
-
search(year, month, day, nil, basedir)
|
57
|
-
|
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
|
58
73
|
end
|
59
74
|
end
|
60
75
|
|
@@ -89,25 +104,48 @@ module LucaRecord # :nodoc:
|
|
89
104
|
# of each concrete class.
|
90
105
|
# ----------------------------------------------------------------
|
91
106
|
|
92
|
-
# create
|
93
|
-
|
94
|
-
|
95
|
-
obj
|
96
|
-
|
97
|
-
|
107
|
+
# create record both of uuid/date identified.
|
108
|
+
#
|
109
|
+
def create(obj, date: nil, codes: nil, basedir: @dirname)
|
110
|
+
validate_keys(obj)
|
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']
|
98
119
|
end
|
99
|
-
id
|
100
120
|
end
|
101
121
|
|
102
|
-
#
|
103
|
-
|
104
|
-
|
105
|
-
|
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!('/', '') }
|
106
144
|
end
|
107
145
|
end
|
108
146
|
|
109
147
|
def prepare_dir!(basedir, date_obj)
|
110
|
-
dir_name = (Pathname(basedir) + encode_dirname(date_obj)).to_s
|
148
|
+
dir_name = (Pathname(basedir) + LucaSupport::Code.encode_dirname(date_obj)).to_s
|
111
149
|
FileUtils.mkdir_p(dir_name) unless Dir.exist?(dir_name)
|
112
150
|
dir_name
|
113
151
|
end
|
@@ -121,6 +159,49 @@ module LucaRecord # :nodoc:
|
|
121
159
|
File.write(path, YAML.dump(origin.sort.to_h))
|
122
160
|
end
|
123
161
|
|
162
|
+
# update file with obj['id']
|
163
|
+
def save(obj, basedir = @dirname)
|
164
|
+
if obj['id'].nil?
|
165
|
+
create(obj, basedir)
|
166
|
+
else
|
167
|
+
validate_keys(obj)
|
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
|
179
|
+
end
|
180
|
+
end
|
181
|
+
obj['id']
|
182
|
+
end
|
183
|
+
|
184
|
+
# delete file by id
|
185
|
+
def delete(id, basedir = @dirname)
|
186
|
+
FileUtils.rm(Pathname(abs_path(basedir)) / id2path(id))
|
187
|
+
id
|
188
|
+
end
|
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
|
+
|
124
205
|
# ----------------------------------------------------------------
|
125
206
|
# :section: Path Utilities
|
126
207
|
# ----------------------------------------------------------------
|
@@ -161,10 +242,6 @@ module LucaRecord # :nodoc:
|
|
161
242
|
end
|
162
243
|
end
|
163
244
|
|
164
|
-
def encode_dirname(date_obj)
|
165
|
-
date_obj.year.to_s + LucaSupport::Code.encode_month(date_obj)
|
166
|
-
end
|
167
|
-
|
168
245
|
# test if having required dirs/files under exec path
|
169
246
|
def valid_project?(path = LucaSupport::Config::Pjdir)
|
170
247
|
project_dir = Pathname(path)
|
@@ -177,6 +254,29 @@ module LucaRecord # :nodoc:
|
|
177
254
|
|
178
255
|
private
|
179
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
|
+
|
180
280
|
# open records with 'basedir/month/date-code' path structure.
|
181
281
|
# Glob pattern can be specified like folloing examples.
|
182
282
|
#
|
@@ -192,6 +292,7 @@ module LucaRecord # :nodoc:
|
|
192
292
|
|
193
293
|
file_pattern = filename.nil? ? '*' : "#{filename}*"
|
194
294
|
Dir.chdir(abs_path(basedir)) do
|
295
|
+
FileUtils.mkdir_p(subdir) if mode == 'w' && !Dir.exist?(subdir)
|
195
296
|
Dir.glob("#{subdir}*/#{file_pattern}").sort.each do |subpath|
|
196
297
|
next if skip_on_unmatch_code(subpath, code)
|
197
298
|
|
@@ -223,6 +324,17 @@ module LucaRecord # :nodoc:
|
|
223
324
|
end
|
224
325
|
end
|
225
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
|
+
|
226
338
|
# Decode basic format.
|
227
339
|
# If specific decode is needed, override this method in each class.
|
228
340
|
#
|
@@ -234,18 +346,18 @@ module LucaRecord # :nodoc:
|
|
234
346
|
when 'json'
|
235
347
|
# TODO: implement JSON parse
|
236
348
|
else
|
237
|
-
YAML.load(io.read)
|
349
|
+
LucaSupport::Code.decimalize(YAML.load(io.read)).tap { |obj| validate_keys(obj) }
|
238
350
|
end
|
239
351
|
end
|
240
352
|
|
241
|
-
def
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
353
|
+
def validate_keys(obj)
|
354
|
+
return nil unless @required
|
355
|
+
|
356
|
+
keys = obj.keys
|
357
|
+
[].tap do |errors|
|
358
|
+
@required.each { |r| errors << r unless keys.include?(r) }
|
359
|
+
raise "Missing keys: #{errors.join(' ')}" unless errors.empty?
|
246
360
|
end
|
247
|
-
path = Pathname(d) + filename
|
248
|
-
File.open(path.to_s, 'w') { |f| yield(f) }
|
249
361
|
end
|
250
362
|
|
251
363
|
# TODO: replace with data_dir method
|
@@ -265,8 +377,10 @@ module LucaRecord # :nodoc:
|
|
265
377
|
|
266
378
|
# AUTO INCREMENT
|
267
379
|
def new_record_no(basedir, date_obj)
|
268
|
-
|
269
|
-
|
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)
|
270
384
|
|
271
385
|
Dir.chdir(dir_name) do
|
272
386
|
last_file = Dir.glob("#{LucaSupport::Code.encode_date(date_obj)}*").max
|
@@ -298,17 +412,6 @@ module LucaRecord # :nodoc:
|
|
298
412
|
end
|
299
413
|
end
|
300
414
|
|
301
|
-
# parse data dir and respond existing months
|
302
|
-
#
|
303
|
-
def scan_terms(base_dir, query = nil)
|
304
|
-
pattern = query.nil? ? "*" : "#{query}*"
|
305
|
-
Dir.chdir(base_dir) do
|
306
|
-
Dir.glob(pattern).select { |dir|
|
307
|
-
FileTest.directory?(dir) && /^[0-9]/.match(dir)
|
308
|
-
}.sort.map { |str| decode_term(str) }
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
415
|
def load_config(path = nil)
|
313
416
|
path = path.to_s
|
314
417
|
if File.exist?(path)
|
data/lib/luca_record/version.rb
CHANGED
data/lib/luca_support/code.rb
CHANGED
@@ -3,12 +3,20 @@
|
|
3
3
|
require 'date'
|
4
4
|
require 'securerandom'
|
5
5
|
require 'digest/sha1'
|
6
|
+
require 'luca_support/config'
|
6
7
|
|
7
8
|
# implement Luca IDs convention
|
8
9
|
module LucaSupport
|
9
10
|
module Code
|
10
11
|
module_function
|
11
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
|
+
|
12
20
|
def encode_txid(num)
|
13
21
|
txmap = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
14
22
|
l = txmap.length
|
@@ -44,7 +52,12 @@ module LucaSupport
|
|
44
52
|
num.to_s.reverse.gsub!(/(\d{3})(?=\d)/, '\1,').reverse!
|
45
53
|
end
|
46
54
|
|
55
|
+
# encode directory name from year and month.
|
47
56
|
#
|
57
|
+
def encode_dirname(date_obj)
|
58
|
+
date_obj.year.to_s + encode_month(date_obj)
|
59
|
+
end
|
60
|
+
|
48
61
|
# Month to code conversion.
|
49
62
|
# Date, DateTime, String, Integer is valid input. If nil, returns empty String for consistency.
|
50
63
|
#
|
@@ -63,6 +76,16 @@ module LucaSupport
|
|
63
76
|
'0ABCDEFGHIJKL'.index(char)
|
64
77
|
end
|
65
78
|
|
79
|
+
# Generate globbing phrase like ["2020[C-H]"] for range search.
|
80
|
+
#
|
81
|
+
def encode_term(start_year, start_month, end_year, end_month)
|
82
|
+
(start_year..end_year).to_a.map do |y|
|
83
|
+
g1 = y == start_year ? encode_month(start_month) : encode_month(1)
|
84
|
+
g2 = y == end_year ? encode_month(end_month) : encode_month(12)
|
85
|
+
"#{y}[#{g1}-#{g2}]"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
66
89
|
def decode_term(char)
|
67
90
|
m = /^([0-9]{4})([A-La-l])/.match(char)
|
68
91
|
[m[1].to_i, decode_month(m[2])]
|
@@ -72,6 +95,37 @@ module LucaSupport
|
|
72
95
|
Digest::SHA1.hexdigest(SecureRandom.uuid)
|
73
96
|
end
|
74
97
|
|
98
|
+
def decimalize(obj)
|
99
|
+
case obj.class.name
|
100
|
+
when 'Array'
|
101
|
+
obj.map { |i| decimalize(i) }
|
102
|
+
when 'Hash'
|
103
|
+
obj.inject({}) { |h, (k, v)| h[k] = decimalize(v); h }
|
104
|
+
when 'Integer'
|
105
|
+
BigDecimal(obj.to_s)
|
106
|
+
when 'String'
|
107
|
+
/^[0-9\.]+$/.match(obj) ? BigDecimal(obj) : obj
|
108
|
+
when 'Float'
|
109
|
+
raise 'already float'
|
110
|
+
else
|
111
|
+
obj
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def readable(obj, len = LucaSupport::Config::DECIMAL_NUM)
|
116
|
+
case obj.class.name
|
117
|
+
when 'Array'
|
118
|
+
obj.map { |i| readable(i) }
|
119
|
+
when 'Hash'
|
120
|
+
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]}"
|
124
|
+
else
|
125
|
+
obj
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
75
129
|
#
|
76
130
|
# convert effective/defunct data into current hash on @date.
|
77
131
|
# not parse nested children.
|
@@ -97,7 +151,7 @@ module LucaSupport
|
|
97
151
|
#
|
98
152
|
def take_current(dat, item)
|
99
153
|
target = dat.dig(item)
|
100
|
-
if target.
|
154
|
+
if target.is_a?(Array) && target.map(&:keys).flatten.include?('effective')
|
101
155
|
latest = target
|
102
156
|
.filter { |a| Date.parse(a.dig('effective').to_s) < @date }
|
103
157
|
.max { |a, b| Date.parse(a.dig('effective').to_s) <=> Date.parse(b.dig('effective').to_s) }
|
data/lib/luca_support/config.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'pathname'
|
4
|
+
require 'yaml'
|
5
|
+
|
3
6
|
#
|
4
7
|
# startup config
|
5
8
|
#
|
@@ -7,5 +10,11 @@ module LucaSupport
|
|
7
10
|
module Config
|
8
11
|
# Project top directory.
|
9
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
|
10
19
|
end
|
11
20
|
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.
|
4
|
+
version: 0.2.20
|
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-
|
11
|
+
date: 2020-11-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mail
|