lucaterm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []