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 +4 -4
- data/.github/FUNDING.yml +3 -0
- data/README.md +12 -3
- data/lib/kv.rb +188 -50
- data/lib/kv/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88a03848ba99241da0e03e0947b101f10f3377b2b86324a5d2221ebe5173da45
|
4
|
+
data.tar.gz: 381798ca027176bca1c4dcc6f62eba469b59a64bfd9fe8d1c3431791f7671d28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: abe415462dbed0bb96d4cc816c6a2c6b909662c7daf08bc575b748af6a15a803be27ac19dc8bb071d555cc273c7552517d18fc18a70f64623fcb3fc7e5274d1b
|
7
|
+
data.tar.gz: cc247a3f2488489a6023987e94aa346fdbb2f84dd581c552d4ab4e8554396bb1c510fa4a7a2528afdc221e618be9868bca9b6331dc647644d81d5e20664cead3
|
data/.github/FUNDING.yml
ADDED
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
|
-
|
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, :
|
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: [],
|
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
|
-
@
|
51
|
-
@
|
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(
|
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}#{@
|
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 = @
|
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 @
|
329
|
-
|
330
|
-
|
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
|
-
@
|
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:
|
396
|
-
|
397
|
-
ev = Curses.getch
|
468
|
+
def input_str pattern, str = ''.dup, other_actions: {}
|
469
|
+
update_action = other_actions[:update]
|
398
470
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
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
|
-
|
411
|
-
|
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
|
-
@
|
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
|
-
|
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
|
648
|
-
|
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!
|
data/lib/kv/version.rb
CHANGED
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.
|
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-
|
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
|
-
|
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'
|