kv 0.3.0 → 0.8.0

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