lucaterm 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bd6fad88274a18a524e2ce05ca8b6e25fccbe359ba7463c307a6528079861667
4
+ data.tar.gz: d045d3218d25d78b6dc69e93140d7eb5ea0b88d72e248028fc6e15fbf9bce077
5
+ SHA512:
6
+ metadata.gz: 83ad42fcd1547ad456fb4708818db8b2e5e0dad52ec083a6a575ab83a675c16068a384cd224ba9a36c8d31f33eb81d3bd99f66e10167b8dee4042b219c4a4984
7
+ data.tar.gz: 900fcdd82a1afd724e8c7c9a36aa76d767129798719427278dbe5f94c51966d110476cf12828c43038147e7b141fc34267e1558125ad9054c0a6b61e6b6e4d9f
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## LucaTerm master
2
+
3
+ * `luca book`: handle cursor on journal detail
4
+ * `luca book`: edit journal
5
+ * `luca book`: move to another month
data/exe/luca ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'optparse'
5
+ require 'curses'
6
+ require 'luca_term'
7
+
8
+ include Curses
9
+
10
+ init_screen
11
+ curs_set(0)
12
+ noecho
13
+
14
+ class LucaCmd
15
+ def self.book(args, params = {})
16
+ begin
17
+ window = Curses::Window.new(0, 0, 0, 0)
18
+ LucaTerm::Book.journals(window, *ARGV)
19
+ ensure
20
+ close_screen
21
+ end
22
+ end
23
+ end
24
+
25
+ cmd = ARGV.shift
26
+ params = {}
27
+
28
+ case cmd
29
+ when /book/, 'b'
30
+ LucaCmd.book(cmd, params)
31
+ end
data/lib/luca_term.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'curses'
4
+ require 'luca_term/version'
5
+
6
+ module LucaTerm
7
+ autoload :Book, 'luca_term/book'
8
+ end
@@ -0,0 +1,367 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'curses'
4
+ require "unicode/display_width/string_ext"
5
+ require 'mb_string'
6
+ require 'luca_book'
7
+
8
+ module LucaTerm
9
+ class Book
10
+ include Curses
11
+ attr_accessor :window
12
+
13
+ def initialize(window, data=nil)
14
+ @window = window
15
+ @data = data
16
+ @index = 0
17
+ @active = 0 # active line in window
18
+ @visible = set_visible(@data)
19
+ @dict = LucaRecord::Dict.load('base.tsv')
20
+ main_loop
21
+ end
22
+
23
+ def self.journals(window, *args)
24
+ new(window, LucaSupport::Code.readable(LucaBook::List.term(*args).data))
25
+ end
26
+
27
+ def main_loop
28
+ loop do
29
+ window.setpos(0,0)
30
+ @visible.each.with_index(0) do |dat, i|
31
+ cursor = i == @active ? :full : nil
32
+ draw_line(dat, cursor, true)
33
+ clrtoeol
34
+ window << "\n"
35
+ end
36
+ (window.maxy - window.cury).times { window.deleteln() }
37
+ window.refresh
38
+
39
+ window.keypad(true)
40
+ cmd = window.getch
41
+ case cmd
42
+ when KEY_DOWN, 'j', KEY_CTRL_N
43
+ next if @index >= @data.length - 1
44
+
45
+ cursor_down @data
46
+ when KEY_UP, 'k', KEY_CTRL_P
47
+ next if @index <= 0
48
+
49
+ cursor_up @data
50
+ when 'G'
51
+ cursor_last @data
52
+ when 'm'
53
+ ym = edit_dialog('Change month: yyyy m')&.split(/[\/\s]/)
54
+ @data = LucaSupport::Code.readable(LucaBook::List.term(*ym).data)
55
+ @index = 0
56
+ @active = 0
57
+ @visible = set_visible(@data)
58
+ when KEY_ENTER, KEY_CTRL_J
59
+ show_detail(@data[@index])
60
+ when 'q'
61
+ exit 0
62
+ end
63
+ end
64
+ end
65
+
66
+ def show_detail(record)
67
+ @d_v = 0
68
+ @d_h = 0
69
+ debit_length = Array(record[:debit]).length
70
+ credit_length = Array(record[:credit]).length
71
+ date, txid = LucaSupport::Code.decode_id(record[:id]) if record[:id]
72
+ loop do
73
+ window.setpos(0, 0)
74
+ window << "#{date} "
75
+ window << record[:note]
76
+ clrtoeol; window << "\n"
77
+ [debit_length, credit_length].max.times do |i|
78
+ { id: nil, debit: [], credit: [] }.tap do |dat|
79
+ if i < debit_length
80
+ dat[:debit] << Array(record[:debit])[i]
81
+ end
82
+ if i < credit_length
83
+ dat[:credit] << Array(record[:credit])[i]
84
+ end
85
+ cursor = @d_v == i ? @d_h : nil
86
+ draw_line(dat, cursor)
87
+ end
88
+ clrtoeol
89
+ window << "\n"
90
+ end
91
+ (window.maxy - window.cury).times { window.deleteln() }
92
+ window.refresh
93
+
94
+ window.keypad(true)
95
+ cmd = window.getch
96
+ case cmd
97
+ when KEY_DOWN, 'j', KEY_CTRL_N
98
+ case @d_h
99
+ when 0, 1
100
+ @d_v = @d_v >= debit_length - 1 ? 0 : @d_v + 1
101
+ else #2,3
102
+ @d_v = @d_v >= credit_length - 1 ? 0 : @d_v + 1
103
+ end
104
+ when KEY_UP, 'k', KEY_CTRL_P
105
+ case @d_h
106
+ when 0, 1
107
+ @d_v = @d_v == 0 ? debit_length - 1 : @d_v - 1
108
+ else #2,3
109
+ @d_v = @d_v == 0 ? credit_length - 1 : @d_v - 1
110
+ end
111
+ when KEY_LEFT, 'h', KEY_CTRL_B
112
+ case @d_h
113
+ when 1, 3
114
+ @d_h -= 1
115
+ when 2
116
+ @d_v = debit_length - 1 if @d_v > debit_length - 1
117
+ @d_h -= 1
118
+ else # 0
119
+ @d_v = credit_length - 1 if @d_v > credit_length - 1
120
+ @d_h = 3
121
+ end
122
+ when KEY_RIGHT, 'l', KEY_CTRL_F
123
+ case @d_h
124
+ when 0, 2
125
+ @d_h += 1
126
+ when 1
127
+ @d_v = credit_length - 1 if @d_v > credit_length - 1
128
+ @d_h += 1
129
+ else # 3
130
+ @d_v = debit_length - 1 if @d_v > debit_length - 1
131
+ @d_h = 0
132
+ end
133
+ when 'n'
134
+ position = [0,1].include?(@d_h) ? :debit : :credit
135
+ new_code = select_code
136
+ next if new_code.nil?
137
+
138
+ new_amount = edit_amount
139
+ next if new_amount.nil?
140
+
141
+ record[position] << { code: new_code, amount: new_amount }
142
+ debit_length = Array(record[:debit]).length
143
+ credit_length = Array(record[:credit]).length
144
+ when KEY_CTRL_J
145
+ position = [0,1].include?(@d_h) ? :debit : :credit
146
+ if [0, 2].include? @d_h
147
+ new_code = select_code
148
+ next if new_code.nil?
149
+
150
+ record[position][@d_v][:code] = new_code
151
+ else
152
+ new_amount = edit_amount(record[position][@d_v][:amount])
153
+ next if new_amount.nil?
154
+
155
+ record[position][@d_v][:amount] = new_amount
156
+ end
157
+ when 's', KEY_CTRL_S
158
+ LucaBook::Journal.save record
159
+ break
160
+ when 'q'
161
+ break
162
+ end
163
+ end
164
+ end
165
+
166
+ def edit_amount(current = nil)
167
+ begin
168
+ scmd = edit_dialog "Current: #{current&.to_s}"
169
+ return nil if scmd.length == 0
170
+ # TODO: guard from not number
171
+ return scmd.to_i
172
+ rescue
173
+ return nil
174
+ end
175
+ end
176
+
177
+ def edit_dialog(message = '')
178
+ sub = window.subwin(4, 30, (window.maxy-4)/2, (window.maxx - 30)/2)
179
+ sub.box(?|, ?-)
180
+ sub.setpos(1, 3)
181
+ sub << message
182
+ clrtoeol
183
+ sub.setpos(2, 3)
184
+ sub << "> "
185
+ clrtoeol
186
+ sub.refresh
187
+ loop do
188
+ echo
189
+ scmd = sub.getstr
190
+ noecho
191
+ sub.close
192
+ return scmd
193
+ end
194
+ end
195
+
196
+ def select_code
197
+ list = @dict.map{ |code, entry| { code: code, label: entry[:label] } }
198
+ visible_dup = @visible
199
+ index_dup = @index
200
+ active_dup = @active
201
+ @index = 0
202
+ @active = 0
203
+ @visible = nil
204
+ @visible = set_visible(list)
205
+ loop do
206
+ window.setpos(0,0)
207
+ @visible.each.with_index(0) do |entry, i|
208
+ line = format("%s %s", entry[:code], entry[:label])
209
+ if i == @active
210
+ window.attron(A_REVERSE) { window << line }
211
+ elsif @visible[i][:code].length <= 2
212
+ window.attron(A_UNDERLINE) { window << line }
213
+ else
214
+ window << line
215
+ end
216
+ clrtoeol
217
+ window << "\n"
218
+ end
219
+ (window.maxy - window.cury).times { window.deleteln() }
220
+ window.refresh
221
+
222
+ cmd = window.getch
223
+ case cmd
224
+ when KEY_DOWN, 'j', KEY_CTRL_N
225
+ next if @index >= list.length - 1
226
+
227
+ cursor_down list
228
+ when KEY_NPAGE
229
+ cursor_pagedown list
230
+ when KEY_UP, 'k', KEY_CTRL_P
231
+ next if @index <= 0
232
+
233
+ cursor_up list
234
+ when KEY_PPAGE
235
+ cursor_pageup list
236
+ when 'G'
237
+ cursor_last list
238
+ when KEY_CTRL_J
239
+ code = list[@index][:code]
240
+ @visible = visible_dup
241
+ @index = index_dup
242
+ @active = active_dup
243
+ return code
244
+ when 'q'
245
+ @visible = visible_dup
246
+ @index = index_dup
247
+ @active = active_dup
248
+ return nil
249
+ end
250
+ end
251
+ end
252
+
253
+ private
254
+
255
+ def draw_line(dat, cursor = nil, note = false)
256
+ date, txid = LucaSupport::Code.decode_id(dat[:id]) if dat[:id]
257
+ debit_cd = fmt_code(dat[:debit])
258
+ debit_amount = fmt_amount(dat[:debit])
259
+ credit_cd = fmt_code(dat[:credit])
260
+ credit_amount = fmt_amount(dat[:credit])
261
+ lines = [Array(dat[:debit]).length, Array(dat[:credit]).length].max
262
+ window << sprintf("%s %s |%s| ",
263
+ date&.mb_rjust(10, ' ') || '',
264
+ txid || '',
265
+ lines > 1 ? lines.to_s : ' ',
266
+ )
267
+ case cursor
268
+ when 0
269
+ window.attron(A_REVERSE) { window << debit_cd }
270
+ window << sprintf(" %s | %s %s", debit_amount, credit_cd, credit_amount)
271
+ when 1
272
+ window << sprintf("%s ", debit_cd)
273
+ window.attron(A_REVERSE) { window << debit_amount }
274
+ window << sprintf(" | %s %s", credit_cd, credit_amount)
275
+ when 2
276
+ window << sprintf("%s %s | ", debit_cd, debit_amount)
277
+ window.attron(A_REVERSE) { window << credit_cd }
278
+ window << sprintf(" %s", credit_amount)
279
+ when 3
280
+ window << sprintf("%s %s | %s ", debit_cd, debit_amount, credit_cd)
281
+ window.attron(A_REVERSE) { window << credit_amount }
282
+ else
283
+ rest = format("%s %s | %s %s", debit_cd, debit_amount, credit_cd, credit_amount)
284
+ if note && window.maxx > 80
285
+ rest += " | #{dat[:note].mb_truncate(window.maxx - 80)}"
286
+ end
287
+ if cursor == :full
288
+ window.attron(A_REVERSE) { window << rest }
289
+ else
290
+ window << rest
291
+ end
292
+ end
293
+ end
294
+
295
+ def fmt_code(record)
296
+ cd = Array(record).dig(0, :code)
297
+ width = (window.maxx / 3) < 30 ? 12 : 17
298
+ return ''.mb_ljust(width, ' ') if cd.nil?
299
+
300
+ label = @dict.dig(cd, :label)&.mb_truncate(12, omission: '')
301
+ if width == 12
302
+ label&.mb_ljust(width, ' ') || ''
303
+ else
304
+ sprintf("%s %s",
305
+ cd&.mb_ljust(4, ' ') || '',
306
+ label&.mb_ljust(12, ' ') || '')
307
+ end
308
+ end
309
+
310
+ def fmt_amount(record)
311
+ amount = Array(record).dig(0, :amount) || ''
312
+ amount.to_s.mb_rjust(10, ' ')
313
+ end
314
+
315
+ def cursor_up(data)
316
+ @index -= 1
317
+ @active = @active <= 0 ? 0 : @active - 1
318
+ @visible = set_visible(data)
319
+ end
320
+
321
+ def cursor_pageup(data)
322
+ n_idx = @index - window.maxy
323
+ return if n_idx <= 0
324
+
325
+ @index = n_idx
326
+ @active = 0
327
+ @visible = set_visible(data)
328
+ end
329
+
330
+ def cursor_down(data)
331
+ @index += 1
332
+ @active = @active >= window.maxy - 1 ? window.maxy - 1 : @active + 1
333
+ @visible = set_visible(data)
334
+ end
335
+
336
+ def cursor_pagedown(data)
337
+ n_idx = @index + window.maxy
338
+ return if n_idx >= data.length - 1
339
+
340
+ @index = n_idx
341
+ @active = 0
342
+ @visible = set_visible(data)
343
+ end
344
+
345
+ def cursor_last(data)
346
+ @index = data.length - 1
347
+ @active = window.maxy - 1
348
+ @visible = set_visible(data)
349
+ end
350
+
351
+ def set_visible(data)
352
+ return data if data.nil? || data.length <= window.maxy
353
+
354
+ if @visible.nil?
355
+ data.slice(0, window.maxy)
356
+ else
357
+ if @active == (window.maxy - 1)
358
+ data.slice(@index - window.maxy + 1, window.maxy)
359
+ elsif @active == 0
360
+ data.slice(@index, window.maxy)
361
+ else
362
+ @visible
363
+ end
364
+ end
365
+ end
366
+ end
367
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LucaTerm
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lucaterm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chuma Takahiro
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-03-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: curses
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mb_string
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: lucabook
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '1.17'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '1.17'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '5.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '5.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 12.3.3
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 12.3.3
97
+ description: 'Terminal frontend for Luca Suite
98
+
99
+ '
100
+ email:
101
+ - co.chuma@gmail.com
102
+ executables:
103
+ - luca
104
+ extensions: []
105
+ extra_rdoc_files: []
106
+ files:
107
+ - CHANGELOG.md
108
+ - exe/luca
109
+ - lib/luca_term.rb
110
+ - lib/luca_term/book.rb
111
+ - lib/luca_term/version.rb
112
+ homepage: https://github.com/chumaltd/luca/tree/master/lucaterm
113
+ licenses:
114
+ - GPL
115
+ metadata:
116
+ homepage_uri: https://github.com/chumaltd/luca/tree/master/lucaterm
117
+ source_code_uri: https://github.com/chumaltd/luca/tree/master/lucaterm
118
+ changelog_uri: https://github.com/chumaltd/luca/tree/master/lucaterm/CHANGELOG.md
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: 2.6.0
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubygems_version: 3.2.3
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Terminal frontend for Luca Suite
138
+ test_files: []