lucaterm 0.1.1 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb1f374a09f0de19e379ea58a0d99d4dc7a0e5eb417bb98121c45a9b60b16043
4
- data.tar.gz: 7fe2cf713fafcc830e1e43132385988073e9ea60b856916f19980f8e9bc8e7d1
3
+ metadata.gz: 2b89d7a16d5e1b11ec8bc4caccc299fff6dcaa7edceda8d3ae3e0ad8d24686bb
4
+ data.tar.gz: 448a0ed6d343ffd78a09f8f83e80cf729699f49db1f0c3cced73d7cb116cc595
5
5
  SHA512:
6
- metadata.gz: db9a08c3a2014a9c35e51ec242ea1b1d1ab74fb211dddf6cf2baedc6a6ef7d1a615db33dc12ae6d1c06f1b275049e3da7f767c34e5f13aa89f2c770e9d76309a
7
- data.tar.gz: 24da76ce1f12b9a55d974375274ce7c8a406502bd1e083760b1e22a565eb78bb6ccf0a9559105d84aaae0703a0d6cfc3a228f6bf732f221a16fe55ac9280e56d
6
+ metadata.gz: 9e50a20eb61aa70ac29caefedbb0316aaa578179eed5d5c5df340161e821ab5b02b65657a4b39e473bba547e356537153e405a0c508ecf48ec3b036238dffbce
7
+ data.tar.gz: 53fb5a7d452aaaf0a528e2785601a06d36d16f8790fa48981ab21d43d4f08dbdd43925db67a522f6911be7ff348cd911baa1a49447b199f8271a621f6b23cf53
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## LucaTerm 0.2.1
2
+
3
+ * `luca book` Fix: proper account code selection
4
+
5
+ ## LucaTerm 0.2.0
6
+
7
+ * `luca book`: Works without args. Show journals of the latest month.
8
+ * `luca book`: Show categories on Account code selection
9
+ * `luca book`: Show x-customer header on detail screen
10
+
11
+ ## LucaTerm 0.1.2
12
+
13
+ * `luca book`: '<' for prev month, '>' for next month.
14
+ * `luca book`: Refine 'm' command. Year is now optional. Cleanup dialog background.
15
+
1
16
  ## LucaTerm 0.1.1
2
17
 
3
18
  * `luca book`: handle cursor on journal detail
data/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # LucaTerm
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/lucaterm.svg)](https://badge.fury.io/rb/lucaterm)
4
+ [![doc](https://img.shields.io/badge/doc-rubydoc-green.svg)](https://www.rubydoc.info/gems/lucaterm/index)
5
+ ![license](https://img.shields.io/github/license/chumaltd/luca)
6
+
7
+ Terminal frontend app for LucaSuite.
8
+
9
+ ## Usage
10
+
11
+ ```bash
12
+ $ luca book [yyyy m]
13
+ ```
data/exe/luca CHANGED
@@ -3,19 +3,22 @@
3
3
 
4
4
  require 'optparse'
5
5
  require 'curses'
6
+ require 'luca_book/journal'
6
7
  require 'luca_term'
7
8
 
8
9
  include Curses
9
10
 
10
- init_screen
11
- curs_set(0)
12
- noecho
13
11
 
14
12
  class LucaCmd
15
13
  def self.book(args, params = {})
14
+ init_screen
15
+ curs_set(0)
16
+ noecho
17
+
16
18
  begin
17
- window = Curses::Window.new(0, 0, 0, 0)
18
- LucaTerm::Book.journals(window, *ARGV)
19
+ window = Curses::Window.new(0, 0, 1, 0)
20
+ args = ARGV.empty? ? LucaBook::Journal.latest_month : ARGV
21
+ LucaTerm::Book.journals(window, *args)
19
22
  ensure
20
23
  close_screen
21
24
  end
@@ -4,14 +4,17 @@ require 'curses'
4
4
  require "unicode/display_width/string_ext"
5
5
  require 'mb_string'
6
6
  require 'luca_book'
7
-
7
+ require 'json'
8
8
  module LucaTerm
9
9
  class Book
10
10
  include Curses
11
- attr_accessor :window
11
+ attr_accessor :window, :modeline
12
12
 
13
- def initialize(window, data=nil)
13
+ def initialize(window, year, month, data=nil)
14
+ @modeline = Window.new(1, 0, 0, 0)
14
15
  @window = window
16
+ @year = year
17
+ @month = month
15
18
  @data = data
16
19
  @index = 0
17
20
  @active = 0 # active line in window
@@ -21,19 +24,25 @@ module LucaTerm
21
24
  end
22
25
 
23
26
  def self.journals(window, *args)
24
- new(window, LucaSupport::Code.readable(LucaBook::List.term(*args).data))
27
+ new(window, args[0], args[1], LucaSupport::Code.readable(LucaBook::List.term(*args).data))
25
28
  end
26
29
 
30
+ # render monthly journal list
31
+ #
27
32
  def main_loop
28
33
  loop do
34
+ modeline.setpos(0, 0)
35
+ modeline << "#{Date::ABBR_MONTHNAMES[@month.to_i]} #{@year}"
36
+ modeline.clrtoeol
37
+ modeline.refresh
38
+
29
39
  window.setpos(0,0)
30
40
  @visible.each.with_index(0) do |dat, i|
31
41
  cursor = i == @active ? :full : nil
32
42
  draw_line(dat, cursor, true)
33
- clrtoeol
34
43
  window << "\n"
35
44
  end
36
- (window.maxy - window.cury).times { window.deleteln() }
45
+ (window.maxy - window.cury).times { window << "\n" }
37
46
  window.refresh
38
47
 
39
48
  window.keypad(true)
@@ -50,15 +59,32 @@ module LucaTerm
50
59
  when 'G'
51
60
  cursor_last @data
52
61
  when 'm'
53
- ym = edit_dialog('Change month: yyyy m')&.split(/[\/\s]/)
62
+ ym = edit_dialog('Enter: [yyyy] m', title: 'Change Month')&.split(/[\/\s]/)
63
+ ym = [@year, ym[0]] if ym.length == 1
54
64
  @data = LucaSupport::Code.readable(LucaBook::List.term(*ym).data)
65
+ @year, @month = ym
66
+ @index = 0
67
+ @active = 0
68
+ @visible = set_visible(@data)
69
+ when '<'
70
+ target = Date.parse("#{@year}-#{@month}-1").prev_month
71
+ @data = LucaSupport::Code.readable(LucaBook::List.term(target.year, target.month).data)
72
+ @year, @month = target.year, target.month
73
+ @index = 0
74
+ @active = 0
75
+ @visible = set_visible(@data)
76
+ when '>'
77
+ target = Date.parse("#{@year}-#{@month}-1").next_month
78
+ @data = LucaSupport::Code.readable(LucaBook::List.term(target.year, target.month).data)
79
+ @year, @month = target.year, target.month
55
80
  @index = 0
56
81
  @active = 0
57
82
  @visible = set_visible(@data)
58
83
  when KEY_ENTER, KEY_CTRL_J
59
84
  show_detail(@data[@index])
85
+ @visible = set_visible(@data)
60
86
  when 'N'
61
- newdate = edit_dialog "Enter date of new record: YYYY-m-d"
87
+ newdate = edit_dialog "Enter date of new record: YYYY-m-d", title: 'Create Journal'
62
88
  tmpl = {
63
89
  date: newdate,
64
90
  debit: [
@@ -75,16 +101,26 @@ module LucaTerm
75
101
  end
76
102
  end
77
103
 
104
+ # render each journal
105
+ #
78
106
  def show_detail(record)
79
107
  @d_v = 0
80
108
  @d_h = 0
81
109
  debit_length = Array(record[:debit]).length
82
110
  credit_length = Array(record[:credit]).length
83
111
  date, txid = LucaSupport::Code.decode_id(record[:id]) if record[:id]
112
+ fileid = record[:id].split('/').last if record[:id]
84
113
  date ||= record[:date]
114
+ modeline.setpos(0, 0)
115
+ modeline << "#{date} #{fileid} "
116
+ modeline.clrtoeol
117
+ modeline.refresh
118
+
85
119
  loop do
86
120
  window.setpos(0, 0)
87
- window << "#{date} "
121
+ if record.dig(:headers, 'x-customer')
122
+ window << format(" [%s] ", record.dig(:headers, 'x-customer'))
123
+ end
88
124
  window << record[:note]
89
125
  clrtoeol; window << "\n"
90
126
  [debit_length, credit_length].max.times do |i|
@@ -101,7 +137,7 @@ module LucaTerm
101
137
  clrtoeol
102
138
  window << "\n"
103
139
  end
104
- (window.maxy - window.cury).times { window.deleteln() }
140
+ (window.maxy - window.cury).times { window << "\n" }
105
141
  window.refresh
106
142
 
107
143
  window.keypad(true)
@@ -155,14 +191,15 @@ module LucaTerm
155
191
  debit_length = Array(record[:debit]).length
156
192
  credit_length = Array(record[:credit]).length
157
193
  when KEY_CTRL_J
158
- position = [0,1].include?(@d_h) ? :debit : :credit
194
+ position, counter = [0,1].include?(@d_h) ? [:debit, :credit] : [:credit, :debit]
159
195
  if [0, 2].include? @d_h
160
196
  new_code = select_code
161
197
  next if new_code.nil?
162
198
 
163
199
  record[position][@d_v][:code] = new_code
164
200
  else
165
- new_amount = edit_amount(record[position][@d_v][:amount])
201
+ diff = record[counter].map { |c| c[:amount] }.sum - record[position].map { |p| p[:amount] }.sum + record[position][@d_v][:amount]
202
+ new_amount = edit_amount(record[position][@d_v][:amount], diff)
166
203
  next if new_amount.nil?
167
204
 
168
205
  record[position][@d_v][:amount] = new_amount
@@ -180,9 +217,12 @@ module LucaTerm
180
217
  end
181
218
  end
182
219
 
183
- def edit_amount(current = nil)
220
+ # returns amount after edit
221
+ #
222
+ def edit_amount(current = nil, diff = nil)
223
+ diff_msg = diff.nil? ? '' : "#{diff} meets balance."
184
224
  begin
185
- scmd = edit_dialog "Current: #{current&.to_s}"
225
+ scmd = edit_dialog "Current: #{current&.to_s}", diff_msg, title: 'Edit Amount'
186
226
  return nil if scmd.length == 0
187
227
  # TODO: guard from not number
188
228
  return scmd.to_i
@@ -191,15 +231,22 @@ module LucaTerm
191
231
  end
192
232
  end
193
233
 
194
- def edit_dialog(message = '')
195
- sub = window.subwin(4, 30, (window.maxy-4)/2, (window.maxx - 30)/2)
196
- sub.box(?|, ?-)
197
- sub.setpos(1, 3)
198
- sub << message
199
- clrtoeol
234
+ def edit_dialog(message = '', submessage = '', title: '')
235
+ sub = window.subwin(5, 30, (window.maxy-5)/2, (window.maxx - 30)/2)
236
+ sub.setpos(1, 1)
237
+ sub << " #{message}"
238
+ sub.clrtoeol
239
+ sub.setpos(2, 1)
240
+ sub.clrtoeol
200
241
  sub.setpos(2, 3)
201
- sub << "> "
202
- clrtoeol
242
+ sub.attron(A_REVERSE) { sub << " > #{' ' * (30 - 9)}" }
243
+ sub.setpos(3, 1)
244
+ sub << " #{submessage}"
245
+ sub.clrtoeol
246
+ sub.box(?|, ?-)
247
+ sub.setpos(0, 2)
248
+ sub << "[ #{title} ]"
249
+ sub.setpos(2, 7)
203
250
  sub.refresh
204
251
  loop do
205
252
  echo
@@ -210,57 +257,76 @@ module LucaTerm
210
257
  end
211
258
  end
212
259
 
260
+ # returns Account code after selection from list dialog
261
+ #
213
262
  def select_code
214
- list = @dict.map{ |code, entry| { code: code, label: entry[:label] } }
215
- .select{ |d| d[:code].length >= 3 }
263
+ top = window.maxy >= 25 ? 5 : 2
264
+ sub = window.subwin(window.maxy - top, window.maxx - 4, top, 2)
265
+ padding = ' ' * account_index(sub.maxx)[0].length
266
+ list = @dict.reject{ |code, _e| code.length < 3 || /^[15]0XX/.match(code) || /^[89]ZZ/.match(code) }
267
+ .map{ |code, entry| { code: code, label: entry[:label], category: padding } }
268
+ tabstop = ['1', '5', '9', 'A', 'C'].map { |cap| list.index { |ac| /^#{cap}/.match(ac[:code]) } }.compact
269
+ account_index(sub.maxx).each.with_index do |cat, i|
270
+ list[tabstop[i]][:category] = cat
271
+ end
216
272
  visible_dup = @visible
217
273
  index_dup = @index
218
274
  active_dup = @active
219
275
  @index = 0
220
276
  @active = 0
221
277
  @visible = nil
222
- @visible = set_visible(list)
278
+ @visible = set_visible(list, sub.maxy - 2)
223
279
  loop do
224
- window.setpos(0,0)
225
280
  @visible.each.with_index(0) do |entry, i|
226
- line = format("%s %s", entry[:code], entry[:label])
281
+ sub.setpos(i+1, 1)
282
+ head = entry[:code].length == 3 ? '' : ' '
283
+ line = format("%s %s %s %s", head, entry[:category], entry[:code], entry[:label])
227
284
  if i == @active
228
- window.attron(A_REVERSE) { window << line }
285
+ sub.attron(A_REVERSE) { sub << line }
229
286
  else
230
- window << line
287
+ sub << line
231
288
  end
232
- clrtoeol
233
- window << "\n"
289
+ sub.clrtoeol
290
+ #sub << "\n"
234
291
  end
235
- (window.maxy - window.cury).times { window.deleteln() }
236
- window.refresh
292
+ (window.maxy - window.cury).times { window << "\n" }
293
+ sub.box(?|, ?-)
294
+ sub.setpos(0, 2)
295
+ sub << "[ Select Account ]"
296
+ sub.refresh
237
297
 
238
298
  cmd = window.getch
239
299
  case cmd
240
300
  when KEY_DOWN, 'j', KEY_CTRL_N
241
301
  next if @index >= list.length - 1
242
302
 
243
- cursor_down list
303
+ cursor_down list, sub.maxy - 2
244
304
  when KEY_NPAGE
245
- cursor_pagedown list
305
+ cursor_pagedown list, sub.maxy - 2
246
306
  when KEY_UP, 'k', KEY_CTRL_P
247
307
  next if @index <= 0
248
308
 
249
309
  cursor_up list
250
310
  when KEY_PPAGE
251
- cursor_pageup list
311
+ cursor_pageup list, sub.maxy - 2
312
+ when KEY_LEFT
313
+ cursor_jump tabstop, list, rev: true
314
+ when KEY_RIGHT
315
+ cursor_jump tabstop, list
252
316
  when 'G'
253
- cursor_last list
317
+ cursor_last list, sub.maxy - 2
254
318
  when KEY_CTRL_J
255
- code = list[@index][:code]
319
+ selected = list[@index][:code]
256
320
  @visible = visible_dup
257
321
  @index = index_dup
258
322
  @active = active_dup
259
- return code
323
+ sub.close
324
+ return selected
260
325
  when 'q'
261
326
  @visible = visible_dup
262
327
  @index = index_dup
263
328
  @active = active_dup
329
+ sub.close
264
330
  return nil
265
331
  end
266
332
  end
@@ -268,17 +334,36 @@ module LucaTerm
268
334
 
269
335
  private
270
336
 
337
+ def account_index(maxx = 50)
338
+ term = if maxx >= 45
339
+ ['Assets', 'Liabilities', 'Net Assets', 'Sales', 'Expenses']
340
+ elsif maxx >= 40
341
+ ['Assets', 'LIAB', 'NetAsset', 'Sales', 'EXP']
342
+ else
343
+ ['', '', '', '', '']
344
+ end
345
+ len = term.map { |str| str.length }.max
346
+ term.map { |str| str += ' ' * (len - str.length) }
347
+ end
348
+
271
349
  def draw_line(dat, cursor = nil, note = false)
272
350
  date, txid = LucaSupport::Code.decode_id(dat[:id]) if dat[:id]
351
+ date_str = date.nil? ? '' : date.split('-')[1, 2].join('/')&.mb_rjust(5, ' ')
273
352
  debit_cd = fmt_code(dat[:debit])
274
353
  debit_amount = fmt_amount(dat[:debit])
275
354
  credit_cd = fmt_code(dat[:credit])
276
355
  credit_amount = fmt_amount(dat[:credit])
277
356
  lines = [Array(dat[:debit]).length, Array(dat[:credit]).length].max
278
- window << sprintf("%s %s |%s| ",
279
- date&.mb_rjust(10, ' ') || '',
280
- txid || '',
281
- lines > 1 ? lines.to_s : ' ',
357
+ lines = if lines == 1
358
+ ' '
359
+ elsif lines > 9
360
+ '+'
361
+ else
362
+ lines
363
+ end
364
+ window << sprintf("%s |%s| ",
365
+ date_str,
366
+ lines,
282
367
  )
283
368
  case cursor
284
369
  when 0
@@ -297,8 +382,8 @@ module LucaTerm
297
382
  window.attron(A_REVERSE) { window << credit_amount }
298
383
  else
299
384
  rest = format("%s %s | %s %s", debit_cd, debit_amount, credit_cd, credit_amount)
300
- if note && window.maxx > 80
301
- rest += " | #{dat[:note].mb_truncate(window.maxx - 80)}"
385
+ if note && window.maxx > 70
386
+ rest += " | #{dat[:note]&.mb_truncate(window.maxx - 70)}"
302
387
  end
303
388
  if cursor == :full
304
389
  window.attron(A_REVERSE) { window << rest }
@@ -334,46 +419,62 @@ module LucaTerm
334
419
  @visible = set_visible(data)
335
420
  end
336
421
 
337
- def cursor_pageup(data)
338
- n_idx = @index - window.maxy
422
+ def cursor_pageup(data, maxy = nil)
423
+ maxy ||= window.maxy
424
+ n_idx = @index - maxy
339
425
  return if n_idx <= 0
340
426
 
341
427
  @index = n_idx
342
428
  @active = 0
343
- @visible = set_visible(data)
429
+ @visible = set_visible(data, maxy)
344
430
  end
345
431
 
346
- def cursor_down(data)
432
+ def cursor_down(data, maxy = nil)
433
+ maxy ||= window.maxy
347
434
  @index += 1
348
- @active = @active >= window.maxy - 1 ? window.maxy - 1 : @active + 1
349
- @visible = set_visible(data)
435
+ @active = @active >= maxy - 1 ? @active : @active + 1
436
+ @visible = set_visible(data, maxy)
350
437
  end
351
438
 
352
- def cursor_pagedown(data)
353
- n_idx = @index + window.maxy
439
+ def cursor_pagedown(data, maxy = nil)
440
+ maxy ||= window.maxy
441
+ n_idx = @index + maxy
354
442
  return if n_idx >= data.length - 1
355
443
 
356
444
  @index = n_idx
445
+ @active = 0
446
+ @visible = set_visible(data, maxy)
447
+ end
448
+
449
+ def cursor_jump(tabstop, data, rev: false)
450
+ @index = if rev
451
+ tabstop.filter{ |t| t < @index ? t : nil }.max || @index
452
+ else
453
+ tabstop.filter{ |t| t > @index ? t : nil }.min || @index
454
+ end
455
+
357
456
  @active = 0
358
457
  @visible = set_visible(data)
359
458
  end
360
459
 
361
- def cursor_last(data)
460
+ def cursor_last(data, maxy = nil)
461
+ maxy ||= window.maxy
362
462
  @index = data.length - 1
363
- @active = window.maxy - 1
364
- @visible = set_visible(data)
463
+ @active = maxy - 1
464
+ @visible = set_visible(data, maxy)
365
465
  end
366
466
 
367
- def set_visible(data)
368
- return data if data.nil? || data.length <= window.maxy
467
+ def set_visible(data, maxy = nil)
468
+ maxy ||= window.maxy
469
+ return data if data.nil? || data.length <= maxy
369
470
 
370
471
  if @visible.nil?
371
- data.slice(0, window.maxy)
472
+ data.slice(0, maxy)
372
473
  else
373
- if @active == (window.maxy - 1)
374
- data.slice(@index - window.maxy + 1, window.maxy)
474
+ if @active == (maxy - 1)
475
+ data.slice(@index - maxy + 1, maxy)
375
476
  elsif @active == 0
376
- data.slice(@index, window.maxy)
477
+ data.slice(@index, maxy)
377
478
  else
378
479
  @visible
379
480
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LucaTerm
4
- VERSION = '0.1.1'
4
+ VERSION = '0.2.1'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lucaterm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuma Takahiro
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-13 00:00:00.000000000 Z
11
+ date: 2022-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: curses
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: lucarecord
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0.3'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0.3'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: bundler
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -96,7 +110,7 @@ dependencies:
96
110
  version: 12.3.3
97
111
  description: 'Terminal frontend for Luca Suite
98
112
 
99
- '
113
+ '
100
114
  email:
101
115
  - co.chuma@gmail.com
102
116
  executables:
@@ -105,6 +119,7 @@ extensions: []
105
119
  extra_rdoc_files: []
106
120
  files:
107
121
  - CHANGELOG.md
122
+ - README.md
108
123
  - exe/luca
109
124
  - lib/luca_term.rb
110
125
  - lib/luca_term/book.rb
@@ -116,7 +131,7 @@ metadata:
116
131
  homepage_uri: https://github.com/chumaltd/luca/tree/master/lucaterm
117
132
  source_code_uri: https://github.com/chumaltd/luca/tree/master/lucaterm
118
133
  changelog_uri: https://github.com/chumaltd/luca/tree/master/lucaterm/CHANGELOG.md
119
- post_install_message:
134
+ post_install_message:
120
135
  rdoc_options: []
121
136
  require_paths:
122
137
  - lib
@@ -131,8 +146,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
131
146
  - !ruby/object:Gem::Version
132
147
  version: '0'
133
148
  requirements: []
134
- rubygems_version: 3.2.3
135
- signing_key:
149
+ rubygems_version: 3.2.5
150
+ signing_key:
136
151
  specification_version: 4
137
152
  summary: Terminal frontend for Luca Suite
138
153
  test_files: []