kv 0.3.0 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c689e9c776cfb742a4258bcbb83ed972be567eff1478e734f30abca73aaed0d
4
- data.tar.gz: 9aa2015affec2a824ce1f528f29ad180d48be400ecf5505932ad6c8d229c9334
3
+ metadata.gz: 88a03848ba99241da0e03e0947b101f10f3377b2b86324a5d2221ebe5173da45
4
+ data.tar.gz: 381798ca027176bca1c4dcc6f62eba469b59a64bfd9fe8d1c3431791f7671d28
5
5
  SHA512:
6
- metadata.gz: 4d261340c36970a0b5a828b4ff69868f0bc51858857a42e5307f2194bbc4116bdf6157064067fbbdfd4f175c1e8888e499163aca789fdcc02d93c1302208c959
7
- data.tar.gz: 39e63f31e30988bc152a08d6afd77c605662291722912d2e3f9981f6731f421d8bb88da0c298b22f9bdaa116afabc8538ee152c8ec0e33afb5538454af604797
6
+ metadata.gz: abe415462dbed0bb96d4cc816c6a2c6b909662c7daf08bc575b748af6a15a803be27ac19dc8bb071d555cc273c7552517d18fc18a70f64623fcb3fc7e5274d1b
7
+ data.tar.gz: cc247a3f2488489a6023987e94aa346fdbb2f84dd581c552d4ab4e8554396bb1c510fa4a7a2528afdc221e618be9868bca9b6331dc647644d81d5e20664cead3
@@ -0,0 +1,3 @@
1
+ # These are supported funding model platforms
2
+
3
+ github: [ko1]
data/README.md CHANGED
@@ -10,8 +10,6 @@ Install it yourself as:
10
10
 
11
11
  kv requires recent Ruby and curses gem.
12
12
 
13
- # Usage
14
-
15
13
  ## Use kv
16
14
 
17
15
  ```
@@ -27,13 +25,18 @@ $ [CMD] | kv [OPTIONS]
27
25
  # View command help
28
26
  $ kv
29
27
 
30
- Options:
28
+ Usage: kv [options]
31
29
  -f following mode like "tail -f"
32
30
  -n, --line-number LINE goto LINE
33
31
  -N Show lines
34
32
  -T, --time-stamp Enable time stamp
33
+ -e CMD Run CMD as a child process
34
+ -p, --pipe Open named pipe
35
+ -s Separation mode (tsv)
35
36
  ```
36
37
 
38
+ Note that `--pipe` option creates a named pipe (`~/.kv_pipe` or a specified file) if there is not a fifo file.
39
+
37
40
  ## Command on a pager
38
41
 
39
42
  ```
@@ -79,6 +82,12 @@ kv: A pager by Ruby Command list
79
82
  s: Save screen buffer to file
80
83
  P: gist -p
81
84
 
85
+ # Child process
86
+ You can run child process using -e command line option like -e CMD
87
+ and you can send a meesage to the child process with x command.
88
+
89
+ x: send a message to the child process
90
+
82
91
  # Modes
83
92
  N: toggle line mode
84
93
  T: toggle time stamp mode
data/lib/kv.rb CHANGED
@@ -20,7 +20,8 @@ end
20
20
  class Screen
21
21
  RenderStatus = Struct.new(
22
22
  :c_cols, :c_lines, :x, :y, :last_lineno,
23
- :search, :goto, :line_mode, :ts_mode, :wrapping,
23
+ :search, :goto, :wrapping,
24
+ :line_mode, :ts_mode, :separation_mode,
24
25
  )
25
26
  class RenderStatus
26
27
  def to_s
@@ -28,7 +29,10 @@ class Screen
28
29
  end
29
30
  end
30
31
 
31
- def initialize input, lines: [], search: nil, name: nil, following_mode: false, first_line: 0, line_mode: false, time_stamp: nil
32
+ def initialize input, lines: [],
33
+ name: nil, search: nil, first_line: 0,
34
+ following_mode: false, line_mode: false, separation_mode: false,
35
+ time_stamp: nil, ext_input: nil, fifo_file: nil
32
36
  @rs = RenderStatus.new
33
37
  @last_rs = nil
34
38
  @rs.y = first_line
@@ -39,16 +43,20 @@ class Screen
39
43
  @rs.search = search
40
44
  @rs.wrapping = false
41
45
  @rs.ts_mode = false
46
+ @rs.separation_mode = separation_mode
42
47
 
43
48
  @name = name
44
49
  @filename = @name if @name && File.exist?(@name)
50
+
45
51
  @time_stamp = time_stamp
52
+ @ext_input = ext_input
53
+ @fifo_file = fifo_file
46
54
 
47
55
  @lines = lines
48
56
  @mode = :screen
49
57
 
50
- @following_mode = following_mode
51
- @searching = false
58
+ @following = following_mode
59
+ @apos = 0
52
60
 
53
61
  @mouse = false
54
62
  @search_ignore_case = false
@@ -78,7 +86,7 @@ class Screen
78
86
  def read_async input
79
87
  @loading = true
80
88
  begin
81
- data = input.read_nonblock(4096)
89
+ data = input.read_nonblock(800_000)
82
90
  rescue IO::EAGAINWaitReadable, EOFError
83
91
  data = ''
84
92
  end
@@ -101,6 +109,7 @@ class Screen
101
109
  end
102
110
 
103
111
  @lines << setup_line(line)
112
+
104
113
  while !@load_unlimited && @lines.size > self.y + @buffer_lines
105
114
  @yq.pop; @yq.clear
106
115
  end
@@ -110,6 +119,10 @@ class Screen
110
119
  if @filename
111
120
  @file_mtime = File.mtime(@filename)
112
121
  @file_lastpos = input.tell
122
+ elsif @fifo_file
123
+ input = open(@fifo_file)
124
+ log(input)
125
+ redo
113
126
  end
114
127
  input.close
115
128
  @loading = false
@@ -117,7 +130,8 @@ class Screen
117
130
  end
118
131
 
119
132
  def y_max
120
- @lines.size - Curses.lines + 2
133
+ max = @lines.size - Curses.lines + 2
134
+ max < 0 ? 0 : max
121
135
  end
122
136
 
123
137
  def y
@@ -161,6 +175,11 @@ class Screen
161
175
  else
162
176
  Curses.mousemask(0)
163
177
  end
178
+
179
+ if @loading && self.y_max < @rs.y
180
+ log [:going, self.y_max, @rs.y]
181
+ @following = :going
182
+ end
164
183
  self.y = @rs.y
165
184
  end
166
185
 
@@ -189,18 +208,6 @@ class Screen
189
208
  end
190
209
  end
191
210
 
192
- def screen_status status, post = nil
193
- Curses.setpos Curses.lines-1, 0
194
- Curses.addstr ' '.ljust(Curses.cols)
195
-
196
- standout{
197
- Curses.setpos Curses.lines-1, 0
198
- Curses.addstr status
199
- }
200
- Curses.addstr post if post
201
- Curses.standend
202
- end
203
-
204
211
  LINE_ATTR = Curses::A_DIM
205
212
 
206
213
  def render_data
@@ -216,6 +223,15 @@ class Screen
216
223
 
217
224
  Curses.clear
218
225
 
226
+ if @rs.separation_mode && (lines = @lines[self.y ... (self.y + c_lines - 1)])
227
+ max_cols = []
228
+ lines.each.with_index{|line, ln|
229
+ line.split("\t").each_with_index{|w, i|
230
+ max_cols[i] = max_cols[i] ? [max_cols[i], w.size].max : w.size
231
+ }
232
+ }
233
+ end
234
+
219
235
  (c_lines-1).times{|i|
220
236
  lno = i + self.y
221
237
  line = @lines[lno]
@@ -258,6 +274,17 @@ class Screen
258
274
 
259
275
  line = line[self.x, cols] || ''
260
276
 
277
+ if @rs.separation_mode
278
+ line = line.split(/\t/).tap{|e|
279
+ if (max = max_cols.size) > 0
280
+ # fill empty columns
281
+ e[max - 1] ||= nil
282
+ end
283
+ }.map.with_index{|w, i|
284
+ "%-#{max_cols[i]}s" % w
285
+ }.join(' | ')
286
+ end
287
+
261
288
  if !@rs.search || !(Regexp === @rs.search)
262
289
  Curses.addstr line
263
290
  else
@@ -290,11 +317,49 @@ class Screen
290
317
  name = @name ? "<#{@name}>" : ''
291
318
  mouse = @mouse ? ' [MOUSE]' : ''
292
319
  search = @rs.search ? " search[#{search_str}]" : ''
293
- loading = @loading ? " (loading...#{@load_unlimited ? '!' : nil}#{@following_mode ? ' following' : ''}) " : ''
320
+ loading = @loading ? " (loading...#{@load_unlimited ? '!' : nil}#{@following ? ' following' : ''}) " : ''
294
321
  x = self.x > 0 ? " x:#{self.x}" : ''
295
322
  screen_status "#{name} lines:#{self.y+1}/#{@lines.size}#{x}#{loading}#{search}#{mouse}"
296
323
  end
297
324
 
325
+ ANIMATION = ['[O ]',
326
+ '[o. ]',
327
+ '[... ]',
328
+ '[ ... ]',
329
+ '[ ... ]',
330
+ '[ ...]',
331
+ '[ .o]',
332
+ '[ O]',
333
+ '[ .o]',
334
+ '[ ...]',
335
+ '[ ... ]',
336
+ '[ ... ]',
337
+ '[ ... ]',
338
+ '[... ]',
339
+ '[o. ]',
340
+ ]
341
+
342
+ def screen_status status, post = nil
343
+ cols = Curses.cols
344
+ line = Curses.lines-1
345
+ Curses.setpos line, 0
346
+ Curses.addstr ' '.ljust(cols)
347
+ len = status.size
348
+ len += post.size if post
349
+
350
+ standout{
351
+ Curses.setpos Curses.lines-1, 0
352
+ Curses.addstr status
353
+ }
354
+ Curses.addstr post if post
355
+
356
+ if !post && len < cols - ANIMATION.first.size
357
+ Curses.setpos line, cols - ANIMATION.first.size - 1
358
+ @apos = (@apos + 1) % ANIMATION.size
359
+ Curses.addstr ANIMATION[@apos]
360
+ end
361
+ end
362
+
298
363
  def check_update
299
364
  if @loading == false
300
365
  if @filename && File.exist?(@filename) && File.mtime(@filename) > @file_mtime
@@ -315,7 +380,7 @@ class Screen
315
380
  def render_screen
316
381
  ev = nil
317
382
 
318
- ms = @following_mode ? 100 : 500
383
+ ms = @following ? 100 : 500
319
384
 
320
385
  ctimeout ms do
321
386
  while ev == nil
@@ -325,20 +390,27 @@ class Screen
325
390
  check_update
326
391
  y_max = self.y_max
327
392
 
328
- if @rs.search && @searching
329
- if search_next_move
330
- break
393
+ if @following
394
+ case @following
395
+ when :searching
396
+ break if search_next_move
397
+ when :going
398
+ if @rs.goto <= y_max
399
+ self.y = @rs.goto
400
+ break
401
+ end
402
+ when true
403
+ # ok
404
+ else
405
+ raise "unknown following mode: #{@following}"
331
406
  end
332
- end
333
407
 
334
- if @following_mode
335
408
  self.y = y_max
336
409
  end
337
410
  end
338
411
 
339
- @following_mode = false
412
+ @following = false
340
413
  set_load_unlimited false
341
- @searching = false
342
414
 
343
415
  return ev
344
416
  end
@@ -346,7 +418,7 @@ class Screen
346
418
 
347
419
  def search_next_move
348
420
  last_line = @lines.size
349
- log (@searching..last_line)
421
+ # log (@searching..last_line)
350
422
 
351
423
  (@searching...last_line).each{|i|
352
424
  if @rs.search === @lines[i]
@@ -366,6 +438,7 @@ class Screen
366
438
  else
367
439
  if @loading
368
440
  set_load_unlimited true
441
+ @following = :searching
369
442
  else
370
443
  screen_status "not found: [#{self.search_str}]"
371
444
  pause
@@ -392,26 +465,32 @@ class Screen
392
465
  ev
393
466
  end
394
467
 
395
- def input_str pattern, str = ''.dup, other_actions: nil
396
- loop{
397
- ev = Curses.getch
468
+ def input_str pattern, str = ''.dup, other_actions: {}
469
+ update_action = other_actions[:update]
398
470
 
399
- case ev
400
- when 10
401
- return str
402
- when Curses::KEY_BACKSPACE
403
- str.chop!
404
- when pattern
405
- str << ev
406
- else
407
- if other_actions && (action = other_actions[ev])
408
- action.call(ev)
471
+ ctimeout update_action ? 200 : -1 do
472
+ loop do
473
+ ev = Curses.getch
474
+
475
+ case ev
476
+ when 10
477
+ return str
478
+ when Curses::KEY_BACKSPACE
479
+ str.chop!
480
+ when pattern
481
+ str << ev
482
+ when nil # timeout
483
+ update_action[str]
409
484
  else
410
- log "failure: #{key_name ev}"
411
- return nil
485
+ if action = other_actions[ev]
486
+ action.call(ev)
487
+ else
488
+ log "failure: #{key_name ev}"
489
+ return nil
490
+ end
412
491
  end
413
492
  end
414
- }
493
+ end
415
494
  end
416
495
 
417
496
  def pause
@@ -454,7 +533,7 @@ class Screen
454
533
  end
455
534
 
456
535
  when 'F'
457
- @following_mode = true
536
+ @following = true
458
537
  set_load_unlimited true
459
538
 
460
539
  when 'L'
@@ -538,7 +617,7 @@ class Screen
538
617
 
539
618
  when 'v'
540
619
  if @filename
541
- system("vi #{@filename} +#{self.y + 1}")
620
+ syste m("vi #{@filename} +#{self.y + 1}")
542
621
  @last_rs = nil
543
622
  end
544
623
 
@@ -580,9 +659,31 @@ class Screen
580
659
  @rs.line_mode = !@rs.line_mode
581
660
  when 'T'
582
661
  @rs.ts_mode = !@rs.ts_mode if @time_stamp
662
+ when 'S'
663
+ @rs.separation_mode = !@rs.separation_mode
583
664
  when 't'
584
665
  Curses.close_screen
585
666
  @mode = :terminal
667
+ when 'x'
668
+ while @ext_input && !@ext_input.closed?
669
+ update_ext_status = -> str do
670
+ screen_status "input for ext:", str
671
+ end
672
+ update_ext_status['']
673
+ actions = {
674
+ update: -> str do
675
+ self.y = self.y_max
676
+ render_data
677
+ update_ext_status[str]
678
+ end
679
+ }
680
+ str = input_str(/./, other_actions: actions)
681
+ if str && !str.empty?
682
+ @ext_input.puts str unless @ext_input.closed?
683
+ else
684
+ break
685
+ end
686
+ end
586
687
 
587
688
  when 'H'
588
689
  if @meta
@@ -641,11 +742,39 @@ class KV
641
742
  }
642
743
 
643
744
  files = parse_option(argv)
745
+ name = files.shift
644
746
 
645
747
  @pipe_in = nil
646
748
 
647
- if files.empty?
648
- if STDIN.isatty
749
+ if @opts[:pipe] || (name && File.pipe?(name))
750
+ @opts.delete(:pipe)
751
+ @opts[:fifo_file] = name || '/tmp/kv_pipe'
752
+
753
+ if name && File.pipe?(name)
754
+ # ok
755
+ else
756
+ begin
757
+ name ||= File.expand_path('~/.kv_pipe')
758
+ unlink_name = name
759
+ File.mkfifo(name)
760
+ at_exit{ puts "$ rm #{unlink_name}"; File.unlink(unlink_name) }
761
+ rescue Errno::EEXIST
762
+ raise "#{name} already exists."
763
+ end
764
+ end
765
+
766
+ puts "waiting for #{name}"
767
+ input = @pipe_in = open(name)
768
+ name = nil
769
+ elsif !name
770
+ case
771
+ when @opts[:e]
772
+ cmd = @opts.delete(:e)
773
+ input = IO.popen(cmd, 'a+')
774
+ name = nil
775
+ @pipe_in = input
776
+ @opts[:ext_input] = input
777
+ when STDIN.isatty
649
778
  input = help_io
650
779
  name = 'HELP'
651
780
  else
@@ -655,14 +784,13 @@ class KV
655
784
  @pipe_in = input
656
785
  end
657
786
  else
658
- name = files.shift
659
787
  begin
660
788
  input = open(name)
661
789
  rescue Errno::ENOENT
662
790
  case name
663
791
  when /(.+):(\d+)/
664
792
  name = $1
665
- @first_line = $2.to_i - 1
793
+ @opts[:first_line] = $2.to_i - 1
666
794
  retry
667
795
  when URI.regexp
668
796
  input = URI.open(name)
@@ -694,6 +822,15 @@ class KV
694
822
  opts.on('-T', '--time-stamp', 'Enable time stamp'){
695
823
  @opts[:time_stamp] = true
696
824
  }
825
+ opts.on('-e CMD', 'Run CMD as a child process'){|cmd|
826
+ @opts[:e] = cmd
827
+ }
828
+ opts.on('-p', '--pipe', 'Open named pipe'){
829
+ @opts[:pipe] = true
830
+ }
831
+ opts.on('-s', 'Separation mode (tsv)'){
832
+ @opts[:separation_mode] = true
833
+ }
697
834
  opts.parse!(argv)
698
835
  end
699
836
 
@@ -704,6 +841,7 @@ class KV
704
841
  @screens.last.control
705
842
  rescue PopScreen
706
843
  @screens.pop
844
+ @screens.last.redraw! unless @screens.empty?
707
845
  rescue PushScreen => e
708
846
  @screens.push e.screen
709
847
  @screens.last.redraw!
@@ -1,3 +1,3 @@
1
1
  module KV
2
- VERSION = "0.3.0"
2
+ VERSION = "0.8.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koichi Sasada
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-02-17 00:00:00.000000000 Z
11
+ date: 2020-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: curses
@@ -32,6 +32,7 @@ executables:
32
32
  extensions: []
33
33
  extra_rdoc_files: []
34
34
  files:
35
+ - ".github/FUNDING.yml"
35
36
  - ".gitignore"
36
37
  - LICENSE
37
38
  - README.md
@@ -61,8 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
61
62
  - !ruby/object:Gem::Version
62
63
  version: '0'
63
64
  requirements: []
64
- rubyforge_project:
65
- rubygems_version: 2.7.6
65
+ rubygems_version: 3.1.2
66
66
  signing_key:
67
67
  specification_version: 4
68
68
  summary: 'kv: A page viewer written by Ruby'