lucabook 0.2.7

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.
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require "optparse"
4
+ require "luca_book/console"
5
+
6
+ def list(args, params)
7
+ if params["c"] or params["code"]
8
+ code = params["c"] || params["code"]
9
+ LucaBookConsole.new.by_code(code, args.dig(0), args.dig(1))
10
+ elsif args.length > 0
11
+ LucaBookConsole.new.by_month(args[0], args.dig(1))
12
+ else
13
+ LucaBookConsole.new.all
14
+ end
15
+ end
16
+
17
+ def report(args, params)
18
+ if params['bs']
19
+ LucaBook::State.term(*args).bs.to_yaml
20
+ elsif params['pl']
21
+ LucaBook::State.term(*args).pl.to_yaml
22
+ else
23
+ LucaBook::State.term(*args).to_yaml
24
+ end
25
+ end
26
+
27
+ cmd = ARGV.shift
28
+
29
+ case cmd
30
+ when "list"
31
+ params = {}
32
+ OptionParser.new do |opt|
33
+ opt.banner = 'Usage: luca list [year month]'
34
+ opt.on('-c', '--code VAL', 'search with code'){|v| params["code"] = v }
35
+ opt.on_tail('List records. If you specify code and/or month, search on each criteria.')
36
+ args = opt.parse!(ARGV)
37
+ list(args, params)
38
+ end
39
+ when "report"
40
+ params = {}
41
+ OptionParser.new do |opt|
42
+ opt.banner = 'Usage: luca report'
43
+ opt.on('--bs', 'show Balance sheet'){|v| params["bs"] = v }
44
+ opt.on('--pl', 'show Income statement'){|v| params["pl"] = v }
45
+ args = opt.parse!(ARGV)
46
+ report(args, params)
47
+ end
48
+ when "--help"
49
+ puts 'Usage: luca subcommand'
50
+ puts ' list: list records'
51
+ puts ' report: show reports'
52
+ else
53
+ puts 'Invalid subcommand'
54
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'luca_book/version'
4
+
5
+ module LucaBook
6
+ autoload :Journal, 'luca_book/journal'
7
+ autoload :State, 'luca_book/state'
8
+ end
@@ -0,0 +1,134 @@
1
+ require 'luca_book'
2
+
3
+ class LucaBookConsole
4
+
5
+ def initialize(dir_path=nil)
6
+ @report = LucaBookReport.new(dir_path)
7
+ end
8
+
9
+ def all
10
+ array = @report.scan_terms(@report.book.pjdir).map{|y,m| y}.uniq.map{|year|
11
+ @report.book.search(year)
12
+ }.flatten
13
+ show_records(array)
14
+ end
15
+
16
+ def by_code(code, year=nil, month=nil)
17
+ array = @report.by_code(code, year, month)
18
+ show_records(array)
19
+ end
20
+
21
+ def by_month(year, month)
22
+ array = @report.book.search(year, month)
23
+ show_records(array)
24
+ end
25
+
26
+ def show_records(records)
27
+ print "#{cnsl_fmt("ID")} #{cnsl_fmt("debit")} #{cnsl_fmt("credit")} #{cnsl_fmt("")*2}"
28
+ print "#{cnsl_fmt("balance")}" unless records.first.dig(:balance).nil?
29
+ puts
30
+ records.each do |h|
31
+ puts "#{cnsl_fmt(h.dig(:id))} #{"-"*85}"
32
+ lines = [h.dig(:debit)&.length, h.dig(:credit)&.length]&.max || 0
33
+ lines.times do |i|
34
+ puts "#{cnsl_fmt("")} #{cnsl_fmt(h.dig(:debit, i, :amount))} #{cnsl_code(h.dig(:debit, i))}" if h.dig(:debit, i, :amount)
35
+ puts "#{cnsl_fmt("")*2} #{cnsl_fmt(h.dig(:credit, i, :amount))} #{cnsl_code(h.dig(:credit, i))}" if h.dig(:credit, i, :amount)
36
+ end
37
+ puts "#{cnsl_fmt(""*15)*5} #{cnsl_fmt(h.dig(:balance))}" unless h.dig(:balance).nil?
38
+ puts "#{cnsl_fmt(""*15)} #{h.dig(:note)}"
39
+ end
40
+ end
41
+
42
+ def bs
43
+ target = []
44
+ report = []
45
+ output = @report.accumulate_all do |f|
46
+ target << f[:target]
47
+ report << f[:current]
48
+ #diff << f[:diff]
49
+ end
50
+ puts "---- BS ----"
51
+ target.each_slice(6) do |v|
52
+ puts "#{cnsl_fmt("", 14)} #{v.map{|v| cnsl_fmt(v, 14)}.join}"
53
+ end
54
+ convert_collection(report).each do |h|
55
+ if /^[0-9]/.match(h[:code])
56
+ if /[^0]$/.match(h[:code])
57
+ print " "
58
+ print " " if h[:code].length > 3
59
+ end
60
+ puts cnsl_label(h[:label], h[:code])
61
+ h[:value].each_slice(6) do |v|
62
+ puts "#{cnsl_fmt("", 14)} #{v.map{|v| cnsl_fmt(v, 14)}.join}"
63
+ end
64
+ end
65
+ end
66
+ puts "---- ----"
67
+ end
68
+
69
+ def pl
70
+ target = []
71
+ report = []
72
+ output = @report.accumulate_all do |f|
73
+ target << f[:target]
74
+ report << f[:diff]
75
+ #current << f[:current]
76
+ end
77
+ puts "---- PL ----"
78
+ target.each_slice(6) do |v|
79
+ puts "#{cnsl_fmt("", 14)} #{v.map{|v| cnsl_fmt(v, 14)}.join}"
80
+ end
81
+ convert_collection(report).each do |h|
82
+ if /^[A-Z]/.match(h[:code])
83
+ total = [h[:value].inject(:+)] + Array.new(h[:value].length)
84
+ if /[^0]$/.match(h[:code])
85
+ print " "
86
+ print " " if h[:code].length > 3
87
+ end
88
+ puts cnsl_label(h[:label], h[:code])
89
+ h[:value].each_slice(6).with_index(0) do |v, i|
90
+ puts "#{cnsl_fmt(total[i], 14)} #{v.map{|v| cnsl_fmt(v, 14)}.join}"
91
+ end
92
+ end
93
+ end
94
+ puts "---- ----"
95
+ end
96
+
97
+ def convert_collection(obj)
98
+ {}.tap {|res|
99
+ obj.each do |month|
100
+ month.each do |k,v|
101
+ if res.has_key?(k)
102
+ res[k] << v
103
+ else
104
+ res[k] = [v]
105
+ end
106
+ end
107
+ end
108
+ }.sort.map do |k,v|
109
+ {code: k, label: @report.dict.dig(k, :label), value: v}
110
+ end
111
+ end
112
+
113
+ def cnsl_label(label, code)
114
+ if /[0]$/.match(code)
115
+ cnsl_bold(label) + " " + "-"*80
116
+ else
117
+ label
118
+ end
119
+ end
120
+
121
+ def cnsl_bold(str)
122
+ "\e[1m#{str}\e[0m"
123
+ end
124
+
125
+ def cnsl_code(obj)
126
+ code = @report.dict.dig(obj&.dig(:code))&.dig(:label) || ""
127
+ end
128
+
129
+ def cnsl_fmt(str, width=15, length=nil)
130
+ length ||= width
131
+ sprintf("%#{width}.#{length}s", str)
132
+ end
133
+
134
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'luca_support/config'
4
+ require 'luca_record/dict'
5
+
6
+ module LucaBook
7
+ class Dict < LucaRecord::Dict
8
+
9
+ @filename = 'dict.tsv'
10
+
11
+ Data = load
12
+ end
13
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require 'json'
5
+ require 'luca_book'
6
+ require 'luca_support'
7
+ #require 'luca_book/dict'
8
+ require 'luca_record'
9
+
10
+ module LucaBook
11
+ class Import
12
+ DEBIT_DEFAULT = '仮払金'
13
+ CREDIT_DEFAULT = '仮受金'
14
+
15
+ def initialize(path)
16
+ raise 'no such file' unless FileTest.file?(path)
17
+
18
+ @target_file = path
19
+ # TODO: yaml need to be configurable
20
+ @dict = LucaRecord::Dict.new('import.yaml')
21
+ @code_map = LucaRecord::Dict.reverse(LucaRecord::Dict.load('base.tsv'))
22
+ @config = @dict.csv_config
23
+ end
24
+
25
+ # === JSON Format:
26
+ # {
27
+ # "date": "2020-05-04",
28
+ # "debit" : [
29
+ # {
30
+ # "label": "savings accounts",
31
+ # "value": 20000
32
+ # }
33
+ # ],
34
+ # "credit" : [
35
+ # {
36
+ # "label": "trade notes receivable",
37
+ # "value": 20000
38
+ # }
39
+ # ],
40
+ # "note": "settlement for the last month trade"
41
+ # }
42
+ #
43
+ def import_json(io)
44
+ d = JSON.parse(io)
45
+ validate(d)
46
+
47
+ # dict = LucaBook::Dict.reverse_dict(LucaBook::Dict::Data)
48
+ d['debit'].each { |h| h['code'] = @dict.search(h['label'], DEBIT_DEFAULT) }
49
+ d['credit'].each { |h| h['code'] = @dict.search(h['label'], CREDIT_DEFAULT) }
50
+
51
+ LucaBook.new.create!(d)
52
+ end
53
+
54
+ def import_csv
55
+ @dict.load_csv(@target_file) do |row|
56
+ if @config[:type] == 'single'
57
+ LucaBook::Journal.create!(parse_single(row))
58
+ elsif @config[:type] == 'double'
59
+ p parse_double(row)
60
+ else
61
+ p row
62
+ end
63
+ end
64
+ end
65
+
66
+ #
67
+ # convert single entry data
68
+ #
69
+ def parse_single(row)
70
+ value = row.dig(@config[:credit_value])&.empty? ? row[@config[:debit_value]] : row[@config[:credit_value]]
71
+ {}.tap do |d|
72
+ d['date'] = parse_date(row)
73
+ if row.dig(@config[:credit_value])&.empty?
74
+ d['debit'] = [
75
+ { 'code' => search_code(row[@config[:label]], DEBIT_DEFAULT) }
76
+ ]
77
+ d['credit'] = [
78
+ { 'code' => @code_map.dig(@config[:counter_label]) }
79
+ ]
80
+ else
81
+ d['debit'] = [
82
+ { 'code' => @code_map.dig(@config[:counter_label]) }
83
+ ]
84
+ d['credit'] = [
85
+ { 'code' => search_code(row[@config[:label]], CREDIT_DEFAULT) }
86
+ ]
87
+ end
88
+ d['debit'][0]['value'] = value
89
+ d['credit'][0]['value'] = value
90
+ d['note'] = row[@config[:note]]
91
+ end
92
+ end
93
+
94
+ #
95
+ # convert double entry data
96
+ #
97
+ def parse_double(row)
98
+ {}.tap do |d|
99
+ d['date'] = parse_date(row)
100
+ d['debit'] = {
101
+ 'code' => search_code(row[@config[:debit_label]], DEBIT_DEFAULT),
102
+ 'value' => row.dig(@config[:debit_value])
103
+ }
104
+ d['credit'] = {
105
+ 'code' => search_code(row[@config[:credit_label]], CREDIT_DEFAULT),
106
+ 'value' => row.dig(@config[:credit_value])
107
+ }
108
+ d['note'] = row[@config[:note]]
109
+ end
110
+ end
111
+
112
+ def search_code(label, default_label)
113
+ @code_map.dig(@dict.search(label, default_label))
114
+ end
115
+
116
+ def parse_date(row)
117
+ return nil if row.dig(@config[:year]).empty?
118
+
119
+ "#{row.dig(@config[:year])}-#{row.dig(@config[:month])}-#{row.dig(@config[:day])}"
120
+ end
121
+
122
+ def validate(obj)
123
+ raise 'NoDateKey' if ! obj.has_key?('date')
124
+ raise 'NoDebitKey' if ! obj.has_key?('debit')
125
+ raise 'NoDebitValue' if obj['debit'].length < 1
126
+ raise 'NoCreditKey' if ! obj.has_key?('credit')
127
+ raise 'NoCreditValue' if obj['credit'].length < 1
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,80 @@
1
+ #
2
+ # manipulate files based on transaction date
3
+ #
4
+
5
+ require 'csv'
6
+ require 'date'
7
+ require 'luca_record'
8
+
9
+ module LucaBook
10
+ class Journal < LucaRecord::Base
11
+ @dirname = 'journals'
12
+
13
+ #
14
+ # create journal from hash
15
+ #
16
+ def self.create!(d)
17
+ date = Date.parse(d['date'])
18
+
19
+ debit_amount = serialize_on_key(d['debit'], 'value')
20
+ credit_amount = serialize_on_key(d['credit'], 'value')
21
+ raise 'BalanceUnmatch' if debit_amount.inject(:+) != credit_amount.inject(:+)
22
+
23
+ debit_code = serialize_on_key(d['debit'], 'code')
24
+ credit_code = serialize_on_key(d['credit'], 'code')
25
+
26
+ # TODO: limit code length for filename
27
+ codes = (debit_code + credit_code).uniq
28
+ create_record!(date, codes) do |f|
29
+ f << debit_code
30
+ f << debit_amount
31
+ f << credit_code
32
+ f << credit_amount
33
+ f << []
34
+ f << [d.dig('note')]
35
+ end
36
+ end
37
+
38
+ #
39
+ # collect values on specified key
40
+ #
41
+ def self.serialize_on_key(array_of_hash, key)
42
+ array_of_hash.map { |h| h[key] }
43
+ end
44
+
45
+ #
46
+ # override de-serializing journal format
47
+ #
48
+ def self.load_data(io, path)
49
+ {}.tap do |record|
50
+ body = false
51
+ record[:id] = path[0] + path[1]
52
+ CSV.new(io, headers: false, col_sep: "\t", encoding: 'UTF-8')
53
+ .each.with_index(0) do |line, i|
54
+ case i
55
+ when 0
56
+ record[:debit] = line.map { |row| { code: row } }
57
+ when 1
58
+ line.each_with_index do |amount, i|
59
+ record[:debit][i][:amount] = amount.to_i # TODO: bigdecimal support
60
+ end
61
+ when 2
62
+ record[:credit] = line.map { |row| { code: row } }
63
+ when 3
64
+ line.each_with_index do |amount, i|
65
+ record[:credit][i][:amount] = amount.to_i # TODO: bigdecimal support
66
+ end
67
+ else
68
+ if line.empty?
69
+ record[:note] ||= []
70
+ body = true
71
+ next
72
+ end
73
+ record[:note] << line.join(' ') if body
74
+ end
75
+ record[:note]&.join('\n')
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,3 @@
1
+ # move to LucaBook::State
2
+ class LucaBookReport
3
+ end
@@ -0,0 +1,250 @@
1
+
2
+ require 'csv'
3
+ require 'pathname'
4
+ require 'date'
5
+ require 'luca_support'
6
+ require 'luca_record'
7
+ require 'luca_record/dict'
8
+ require 'luca_book'
9
+
10
+ #
11
+ # Statement on specified term
12
+ #
13
+ module LucaBook
14
+ class State < LucaRecord::Base
15
+ @dirname = 'journals'
16
+ @record_type = 'raw'
17
+
18
+ attr_reader :statement
19
+
20
+ def initialize(data)
21
+ @data = data
22
+ @dict = LucaRecord::Dict.load('base.tsv')
23
+ end
24
+
25
+ # TODO: not compatible with LucaRecord::Base.open_records
26
+ def search_tag(code)
27
+ count = 0
28
+ Dir.children(LucaSupport::Config::Pjdir).sort.each do |dir|
29
+ next if ! FileTest.directory?(LucaSupport::Config::Pjdir+dir)
30
+
31
+ open_records(datadir, dir, 3) do |row, i|
32
+ next if i == 2
33
+ count += 1 if row.include?(code)
34
+ end
35
+ end
36
+ puts "#{code}: #{count}"
37
+ end
38
+
39
+ def self.term(from_year, from_month, to_year = from_year, to_month = from_month)
40
+ date = Date.new(from_year.to_i, from_month.to_i, -1)
41
+ last_date = Date.new(to_year.to_i, to_month.to_i, -1)
42
+ raise 'invalid term specified' if date > last_date
43
+
44
+ reports = [].tap do |r|
45
+ while date <= last_date do
46
+ r << accumulate_month(date.year, date.month)
47
+ date = date.next_month
48
+ end
49
+ end
50
+ new reports
51
+ end
52
+
53
+ def by_code(code, year=nil, month=nil)
54
+ raise "not supported year range yet" if ! year.nil? && month.nil?
55
+
56
+ bl = @book.load_start.dig(code) || 0
57
+ full_term = scan_terms(LucaSupport::Config::Pjdir)
58
+ if ! month.nil?
59
+ pre_term = full_term.select{|y,m| y <= year.to_i && m < month.to_i }
60
+ bl += pre_term.map{|y,m| self.class.net(y, m)}.inject(0){|sum, h| sum + h[code]}
61
+ [{ code: code, balance: bl, note: "#{code} #{dict.dig(code, :label)}" }] + records_with_balance(year, month, code, bl)
62
+ else
63
+ start = { code: code, balance: bl, note: "#{code} #{dict.dig(code, :label)}" }
64
+ full_term.map {|y, m| y }.uniq.map {|y|
65
+ records_with_balance(y, nil, code, bl)
66
+ }.flatten.prepend(start)
67
+ end
68
+ end
69
+
70
+ def records_with_balance(year, month, code, balance)
71
+ @book.search(year, month, nil, code).each do |h|
72
+ balance += self.class.calc_diff(amount_by_code(h[:debit], code), code) - @book.calc_diff(amount_by_code(h[:credit], code), code)
73
+ h[:balance] = balance
74
+ end
75
+ end
76
+
77
+ #
78
+ # TODO: useless method. consider to remove
79
+ #
80
+ def accumulate_all
81
+ current = @book.load_start
82
+ target = []
83
+ Dir.chdir(@book.pjdir) do
84
+ net_records = scan_terms(@book.pjdir).map do |year, month|
85
+ target << [year, month]
86
+ accumulate_month(year, month)
87
+ end
88
+ all_keys = net_records.map{|h| h.keys}.flatten.uniq
89
+ net_records.each.with_index(0) do |diff, i|
90
+ all_keys.each {|key| diff[key] = 0 unless diff.has_key?(key)}
91
+ diff.each do |k,v|
92
+ if current[k]
93
+ current[k] += v
94
+ else
95
+ current[k] = v
96
+ end
97
+ end
98
+ f = { target: "#{target[i][0]}-#{target[i][1]}", diff: diff.sort, current: current.sort }
99
+ yield f
100
+ end
101
+ end
102
+ end
103
+
104
+ def to_yaml
105
+ YAML.dump(code2label).tap { |data| puts data }
106
+ end
107
+
108
+ def code2label
109
+ @statement ||= @data
110
+ @statement.map do |report|
111
+ {}.tap do |h|
112
+ report.each { |k, v| h[@dict.dig(k, :label)] = v }
113
+ end
114
+ end
115
+ end
116
+
117
+ def bs
118
+ @statement = @data.map do |data|
119
+ data.select { |k, v| /^[0-9].+/.match(k) }
120
+ end
121
+ self
122
+ end
123
+
124
+ def pl
125
+ @statement = @data.map do |data|
126
+ data.select { |k, v| /^[A-F].+/.match(k) }
127
+ end
128
+ self
129
+ end
130
+
131
+ def self.accumulate_month(year, month)
132
+ monthly_record = net(year, month)
133
+ total_subaccount(monthly_record)
134
+ end
135
+
136
+ def amount_by_code(items, code)
137
+ items
138
+ .select{|item| item.dig(:code) == code }
139
+ .inject(0){|sum, item| sum + item[:amount] }
140
+ end
141
+
142
+ def self.total_subaccount(report)
143
+ report.dup.tap do |res|
144
+ report.each do |k, v|
145
+ if k.length >= 4
146
+ if res[k[0, 3]]
147
+ res[k[0, 3]] += v
148
+ else
149
+ res[k[0, 3]] = v
150
+ end
151
+ end
152
+ end
153
+ res['10'] = sum_matched(report, /^[123].[^0]/)
154
+ res['40'] = sum_matched(report, /^[4].[^0]}/)
155
+ res['50'] = sum_matched(report, /^[56].[^0]/)
156
+ res['70'] = sum_matched(report, /^[78].[^0]/)
157
+ res['90'] = sum_matched(report, /^[9].[^0]/)
158
+ res['A0'] = sum_matched(report, /^[A].[^0]/)
159
+ res['B0'] = sum_matched(report, /^[B].[^0]/)
160
+ res['BA'] = res['A0'] - res['B0']
161
+ res['C0'] = sum_matched(report, /^[C].[^0]/)
162
+ res['CA'] = res['BA'] - res['C0']
163
+ res['D0'] = sum_matched(report, /^[D].[^0]/)
164
+ res['E0'] = sum_matched(report, /^[E].[^0]/)
165
+ res['EA'] = res['CA'] + res['D0'] - res['E0']
166
+ res['F0'] = sum_matched(report, /^[F].[^0]/)
167
+ res['G0'] = sum_matched(report, /^[G].[^0]/)
168
+ res['GA'] = res['EA'] + res['F0'] - res['G0']
169
+ res['HA'] = res['GA'] - sum_matched(report, /^[H].[^0]/)
170
+ end
171
+ end
172
+
173
+ def self.sum_matched(report, reg)
174
+ report.select { |k, v| reg.match(k)}.values.sum
175
+ end
176
+
177
+ # for assert purpose
178
+ def self.gross(year, month = nil, code = nil, date_range = nil, rows = 4)
179
+ if ! date_range.nil?
180
+ raise if date_range.class != Range
181
+ # TODO: date based range search
182
+ end
183
+
184
+ sum = { debit: {}, credit: {} }
185
+ idx_memo = []
186
+ asof(year, month) do |f, _path|
187
+ CSV.new(f, headers: false, col_sep: "\t", encoding: 'UTF-8')
188
+ .each_with_index do |row, i|
189
+ break if i >= rows
190
+ case i
191
+ when 0
192
+ idx_memo = row.map(&:to_s)
193
+ idx_memo.each { |r| sum[:debit][r] ||= 0 }
194
+ when 1
195
+ row.each_with_index { |r, i| sum[:debit][idx_memo[i]] += r.to_i } # TODO: bigdecimal support
196
+ when 2
197
+ idx_memo = row.map(&:to_s)
198
+ idx_memo.each { |r| sum[:credit][r] ||= 0 }
199
+ when 3
200
+ row.each_with_index { |r, i| sum[:credit][idx_memo[i]] += r.to_i } # TODO: bigdecimal support
201
+ else
202
+ puts row # for debug
203
+ end
204
+ end
205
+ end
206
+ sum
207
+ end
208
+
209
+ # netting vouchers in specified term
210
+ def self.net(year, month = nil, code = nil, date_range = nil)
211
+ g = gross(year, month, code, date_range)
212
+ idx = (g[:debit].keys + g[:credit].keys).uniq.sort
213
+ {}.tap do |sum|
214
+ idx.each do |code|
215
+ sum[code] = g.dig(:debit, code).nil? ? 0 : calc_diff(g[:debit][code], code)
216
+ sum[code] -= g.dig(:credit, code).nil? ? 0 : calc_diff(g[:credit][code], code)
217
+ end
218
+ end
219
+ end
220
+
221
+ def load_start
222
+ file = LucaSupport::Config::Pjdir + 'start.tsv'
223
+ {}.tap do |dic|
224
+ load_tsv(file) do |row|
225
+ dic[row[0]] = row[2].to_i if ! row[2].nil?
226
+ end
227
+ end
228
+ end
229
+
230
+ def self.calc_diff(num, code)
231
+ amount = /\./.match(num.to_s) ? BigDecimal(num) : num.to_i
232
+ amount * pn_debit(code.to_s)
233
+ end
234
+
235
+ def self.pn_debit(code)
236
+ case code
237
+ when /^[0-4BCEGH]/
238
+ 1
239
+ when /^[5-9ADF]/
240
+ -1
241
+ else
242
+ nil
243
+ end
244
+ end
245
+
246
+ def dict
247
+ LucaBook::Dict::Data
248
+ end
249
+ end
250
+ end