kv 0.2.0 → 0.7.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 +23 -6
- data/lib/kv.rb +307 -78
- data/lib/kv/version.rb +3 -3
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 573415b451a9344ac21da113782892338606925a3dd04c57d3e82805a5e55de2
|
4
|
+
data.tar.gz: 7d29a38d796aacb6d769a31c52313fc6e510ff68de5e51615e9ce7357e2f3b1a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bf24a502a46eead71095315a4d1e7c76872e849c04360e2d740e6521704ce56b75126fa45f7bfceb1b983845f4e0d9bf189fbde870e001a3a580b6b076116139
|
7
|
+
data.tar.gz: 720ac127b0e1960712493473d12254ec39b28c84f80cc0b7a99631728038da9e31a85f1970274badc89b36943632b938b3b557ac0633dd815e477259d248cdcf
|
data/.github/FUNDING.yml
ADDED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
# kv: A page viewer written
|
1
|
+
# kv: A page viewer written in Ruby
|
2
2
|
|
3
|
-
kv is a page viewer designed for streaming data written
|
3
|
+
kv is a page viewer designed for streaming data written in Ruby.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -8,9 +8,7 @@ Install it yourself as:
|
|
8
8
|
|
9
9
|
$ gem install kv
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
kv requires Ruby and curses gem.
|
11
|
+
kv requires recent Ruby and curses gem.
|
14
12
|
|
15
13
|
## Use kv
|
16
14
|
|
@@ -18,17 +16,27 @@ kv requires Ruby and curses gem.
|
|
18
16
|
# View [FILE]
|
19
17
|
$ kv [OPTIONS] [FILE]
|
20
18
|
|
19
|
+
# View [URI] source code
|
20
|
+
$ kv [URI]
|
21
|
+
|
21
22
|
# View results of [CMD]
|
22
23
|
$ [CMD] | kv [OPTIONS]
|
23
24
|
|
24
25
|
# View command help
|
25
26
|
$ kv
|
26
27
|
|
27
|
-
|
28
|
+
Usage: kv [options]
|
28
29
|
-f following mode like "tail -f"
|
29
30
|
-n, --line-number LINE goto LINE
|
31
|
+
-N Show lines
|
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)
|
30
36
|
```
|
31
37
|
|
38
|
+
Note that `--pipe` option creates a named pipe (`~/.kv_pipe` or a specified file) if there is not a fifo file.
|
39
|
+
|
32
40
|
## Command on a pager
|
33
41
|
|
34
42
|
```
|
@@ -72,12 +80,21 @@ kv: A pager by Ruby Command list
|
|
72
80
|
|
73
81
|
# Output
|
74
82
|
s: Save screen buffer to file
|
83
|
+
P: gist -p
|
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
|
75
90
|
|
76
91
|
# Modes
|
77
92
|
N: toggle line mode
|
93
|
+
T: toggle time stamp mode
|
78
94
|
m: toggle mouse mode
|
79
95
|
t: terminal (REPL) mode
|
80
96
|
v: vi ("vi filename +[LINE]")
|
97
|
+
H: show HTTP response header
|
81
98
|
```
|
82
99
|
|
83
100
|
`G` is notable feature, `less` doesn't have. This feature jumps to "current" last line even if the pipe source command does not close output (== input for kv). You can refresh the last line by putting any command.
|
data/lib/kv.rb
CHANGED
@@ -4,22 +4,24 @@ require "kv/version"
|
|
4
4
|
require "curses"
|
5
5
|
require 'stringio'
|
6
6
|
require 'optparse'
|
7
|
+
require 'open-uri'
|
7
8
|
|
8
9
|
module KV
|
9
|
-
class
|
10
|
+
class PushScreen < Exception
|
10
11
|
attr_reader :screen
|
11
12
|
def initialize screen
|
12
13
|
@screen = screen
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
|
-
class
|
17
|
+
class PopScreen < Exception
|
17
18
|
end
|
18
19
|
|
19
|
-
class
|
20
|
+
class Screen
|
20
21
|
RenderStatus = Struct.new(
|
21
|
-
:c_cols, :c_lines, :x, :y,
|
22
|
-
:search, :goto, :
|
22
|
+
:c_cols, :c_lines, :x, :y, :last_lineno,
|
23
|
+
:search, :goto, :wrapping,
|
24
|
+
:line_mode, :ts_mode, :separation_mode,
|
23
25
|
)
|
24
26
|
class RenderStatus
|
25
27
|
def to_s
|
@@ -27,7 +29,10 @@ class KV_Screen
|
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
30
|
-
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
|
31
36
|
@rs = RenderStatus.new
|
32
37
|
@last_rs = nil
|
33
38
|
@rs.y = first_line
|
@@ -36,14 +41,22 @@ class KV_Screen
|
|
36
41
|
@rs.last_lineno = 0
|
37
42
|
@rs.line_mode = line_mode
|
38
43
|
@rs.search = search
|
44
|
+
@rs.wrapping = false
|
45
|
+
@rs.ts_mode = false
|
46
|
+
@rs.separation_mode = separation_mode
|
39
47
|
|
40
48
|
@name = name
|
41
49
|
@filename = @name if @name && File.exist?(@name)
|
42
50
|
|
51
|
+
@time_stamp = time_stamp
|
52
|
+
@ext_input = ext_input
|
53
|
+
@fifo_file = fifo_file
|
54
|
+
|
43
55
|
@lines = lines
|
44
56
|
@mode = :screen
|
45
57
|
|
46
|
-
@
|
58
|
+
@following = following_mode
|
59
|
+
@apos = 0
|
47
60
|
|
48
61
|
@mouse = false
|
49
62
|
@search_ignore_case = false
|
@@ -51,25 +64,35 @@ class KV_Screen
|
|
51
64
|
@loading = false
|
52
65
|
@buffer_lines = 10_000
|
53
66
|
@yq = Queue.new
|
54
|
-
@
|
67
|
+
if @filename
|
68
|
+
@load_unlimited = true
|
69
|
+
else
|
70
|
+
@load_unlimited = false
|
71
|
+
end
|
72
|
+
|
55
73
|
@prev_render = {}
|
74
|
+
@meta = input.respond_to?(:meta) ? input.meta : nil
|
56
75
|
|
57
76
|
read_async input if input
|
58
77
|
end
|
59
78
|
|
60
79
|
def setup_line line
|
61
80
|
line = line.chomp
|
81
|
+
line.instance_variable_set(:@time_stamp, Time.now.strftime('%H:%M:%S')) if @time_stamp
|
62
82
|
line.instance_variable_set(:@lineno, @rs.last_lineno += 1)
|
63
83
|
line
|
64
84
|
end
|
65
85
|
|
66
86
|
def read_async input
|
67
87
|
@loading = true
|
68
|
-
|
88
|
+
begin
|
89
|
+
data = input.read_nonblock(800_000)
|
90
|
+
rescue IO::EAGAINWaitReadable, EOFError
|
91
|
+
data = ''
|
92
|
+
end
|
69
93
|
|
70
|
-
lines = data.each_line.to_a
|
71
94
|
last_line = nil
|
72
|
-
|
95
|
+
data.each_line{|line|
|
73
96
|
if line[-1] != "\n"
|
74
97
|
last_line = line
|
75
98
|
break
|
@@ -86,6 +109,7 @@ class KV_Screen
|
|
86
109
|
end
|
87
110
|
|
88
111
|
@lines << setup_line(line)
|
112
|
+
|
89
113
|
while !@load_unlimited && @lines.size > self.y + @buffer_lines
|
90
114
|
@yq.pop; @yq.clear
|
91
115
|
end
|
@@ -95,6 +119,10 @@ class KV_Screen
|
|
95
119
|
if @filename
|
96
120
|
@file_mtime = File.mtime(@filename)
|
97
121
|
@file_lastpos = input.tell
|
122
|
+
elsif @fifo_file
|
123
|
+
input = open(@fifo_file)
|
124
|
+
log(input)
|
125
|
+
redo
|
98
126
|
end
|
99
127
|
input.close
|
100
128
|
@loading = false
|
@@ -102,7 +130,8 @@ class KV_Screen
|
|
102
130
|
end
|
103
131
|
|
104
132
|
def y_max
|
105
|
-
@lines.size - Curses.lines + 2
|
133
|
+
max = @lines.size - Curses.lines + 2
|
134
|
+
max < 0 ? 0 : max
|
106
135
|
end
|
107
136
|
|
108
137
|
def y
|
@@ -131,6 +160,11 @@ class KV_Screen
|
|
131
160
|
@rs.x
|
132
161
|
end
|
133
162
|
|
163
|
+
def set_load_unlimited b
|
164
|
+
@load_unlimited = b
|
165
|
+
@yq << true
|
166
|
+
end
|
167
|
+
|
134
168
|
def init_screen
|
135
169
|
Curses.init_screen
|
136
170
|
Curses.stdscr.keypad(true)
|
@@ -141,6 +175,11 @@ class KV_Screen
|
|
141
175
|
else
|
142
176
|
Curses.mousemask(0)
|
143
177
|
end
|
178
|
+
|
179
|
+
if @loading && self.y_max < @rs.y
|
180
|
+
log [:going, self.y_max, @rs.y]
|
181
|
+
@following = :going
|
182
|
+
end
|
144
183
|
self.y = @rs.y
|
145
184
|
end
|
146
185
|
|
@@ -169,24 +208,12 @@ class KV_Screen
|
|
169
208
|
end
|
170
209
|
end
|
171
210
|
|
172
|
-
def screen_status status, post = nil
|
173
|
-
Curses.setpos Curses.lines-1, 0
|
174
|
-
Curses.addstr ' '.ljust(Curses.cols)
|
175
|
-
|
176
|
-
standout{
|
177
|
-
Curses.setpos Curses.lines-1, 0
|
178
|
-
Curses.addstr status
|
179
|
-
}
|
180
|
-
Curses.addstr post if post
|
181
|
-
Curses.standend
|
182
|
-
end
|
183
|
-
|
184
211
|
LINE_ATTR = Curses::A_DIM
|
185
212
|
|
186
213
|
def render_data
|
187
214
|
# check update
|
188
|
-
c_lines = Curses.lines
|
189
|
-
c_cols = Curses.cols
|
215
|
+
c_lines = @rs.c_lines = Curses.lines
|
216
|
+
c_cols = @rs.c_cols = Curses.cols
|
190
217
|
|
191
218
|
if @rs != @last_rs
|
192
219
|
@last_rs = @rs.dup
|
@@ -196,6 +223,15 @@ class KV_Screen
|
|
196
223
|
|
197
224
|
Curses.clear
|
198
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
|
+
|
199
235
|
(c_lines-1).times{|i|
|
200
236
|
lno = i + self.y
|
201
237
|
line = @lines[lno]
|
@@ -229,8 +265,26 @@ class KV_Screen
|
|
229
265
|
end
|
230
266
|
end
|
231
267
|
|
268
|
+
if @rs.ts_mode && ts = line.instance_variable_get(:@time_stamp)
|
269
|
+
cattr LINE_ATTR do
|
270
|
+
ts = line.instance_variable_get(:@time_stamp)
|
271
|
+
Curses.addstr("#{ts} |")
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
232
275
|
line = line[self.x, cols] || ''
|
233
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
|
+
|
234
288
|
if !@rs.search || !(Regexp === @rs.search)
|
235
289
|
Curses.addstr line
|
236
290
|
else
|
@@ -263,11 +317,49 @@ class KV_Screen
|
|
263
317
|
name = @name ? "<#{@name}>" : ''
|
264
318
|
mouse = @mouse ? ' [MOUSE]' : ''
|
265
319
|
search = @rs.search ? " search[#{search_str}]" : ''
|
266
|
-
loading = @loading ? " (loading...#{@load_unlimited ? '!' : nil}#{@
|
320
|
+
loading = @loading ? " (loading...#{@load_unlimited ? '!' : nil}#{@following ? ' following' : ''}) " : ''
|
267
321
|
x = self.x > 0 ? " x:#{self.x}" : ''
|
268
322
|
screen_status "#{name} lines:#{self.y+1}/#{@lines.size}#{x}#{loading}#{search}#{mouse}"
|
269
323
|
end
|
270
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
|
+
|
271
363
|
def check_update
|
272
364
|
if @loading == false
|
273
365
|
if @filename && File.exist?(@filename) && File.mtime(@filename) > @file_mtime
|
@@ -288,7 +380,7 @@ class KV_Screen
|
|
288
380
|
def render_screen
|
289
381
|
ev = nil
|
290
382
|
|
291
|
-
ms = @
|
383
|
+
ms = @following ? 100 : 500
|
292
384
|
|
293
385
|
ctimeout ms do
|
294
386
|
while ev == nil
|
@@ -296,39 +388,62 @@ class KV_Screen
|
|
296
388
|
render_status
|
297
389
|
ev = Curses.getch
|
298
390
|
check_update
|
299
|
-
|
300
|
-
|
301
|
-
|
391
|
+
y_max = self.y_max
|
392
|
+
|
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}"
|
302
406
|
end
|
303
|
-
|
407
|
+
|
408
|
+
self.y = y_max
|
304
409
|
end
|
305
410
|
end
|
306
411
|
|
307
|
-
|
308
|
-
|
309
|
-
@load_unlimited = false
|
310
|
-
end
|
412
|
+
@following = false
|
413
|
+
set_load_unlimited false
|
311
414
|
|
312
415
|
return ev
|
313
416
|
end
|
314
417
|
end
|
315
418
|
|
316
|
-
def search_next_move
|
317
|
-
|
419
|
+
def search_next_move
|
420
|
+
last_line = @lines.size
|
421
|
+
# log (@searching..last_line)
|
422
|
+
|
423
|
+
(@searching...last_line).each{|i|
|
318
424
|
if @rs.search === @lines[i]
|
319
425
|
self.y = i
|
426
|
+
@searching = false
|
320
427
|
return true
|
321
428
|
end
|
322
429
|
}
|
430
|
+
@searching = last_line
|
323
431
|
return false
|
324
432
|
end
|
325
433
|
|
326
434
|
def search_next start
|
327
|
-
|
328
|
-
|
435
|
+
@searching = start
|
436
|
+
if search_next_move
|
437
|
+
# OK. self.y is updated.
|
329
438
|
else
|
330
|
-
|
331
|
-
|
439
|
+
if @loading
|
440
|
+
set_load_unlimited true
|
441
|
+
@following = :searching
|
442
|
+
else
|
443
|
+
screen_status "not found: [#{self.search_str}]"
|
444
|
+
pause
|
445
|
+
@searching = false
|
446
|
+
end
|
332
447
|
end
|
333
448
|
end
|
334
449
|
|
@@ -350,26 +465,32 @@ class KV_Screen
|
|
350
465
|
ev
|
351
466
|
end
|
352
467
|
|
353
|
-
def input_str pattern, str = ''.dup, other_actions:
|
354
|
-
|
355
|
-
ev = Curses.getch
|
468
|
+
def input_str pattern, str = ''.dup, other_actions: {}
|
469
|
+
update_action = other_actions[:update]
|
356
470
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
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]
|
367
484
|
else
|
368
|
-
|
369
|
-
|
485
|
+
if action = other_actions[ev]
|
486
|
+
action.call(ev)
|
487
|
+
else
|
488
|
+
log "failure: #{key_name ev}"
|
489
|
+
return nil
|
490
|
+
end
|
370
491
|
end
|
371
492
|
end
|
372
|
-
|
493
|
+
end
|
373
494
|
end
|
374
495
|
|
375
496
|
def pause
|
@@ -382,7 +503,7 @@ class KV_Screen
|
|
382
503
|
|
383
504
|
case ev
|
384
505
|
when 'q'
|
385
|
-
raise
|
506
|
+
raise PopScreen
|
386
507
|
|
387
508
|
when Curses::KEY_UP, 'k'
|
388
509
|
self.y -= 1
|
@@ -412,13 +533,11 @@ class KV_Screen
|
|
412
533
|
end
|
413
534
|
|
414
535
|
when 'F'
|
415
|
-
@
|
416
|
-
|
417
|
-
@yq << true
|
536
|
+
@following = true
|
537
|
+
set_load_unlimited true
|
418
538
|
|
419
539
|
when 'L'
|
420
|
-
|
421
|
-
@yq << true
|
540
|
+
set_load_unlimited !@load_unlimited
|
422
541
|
|
423
542
|
when '/'
|
424
543
|
search_str = ''.dup
|
@@ -470,8 +589,8 @@ class KV_Screen
|
|
470
589
|
filter_mode_title = "*filter mode [#{self.search_str}]*"
|
471
590
|
if @name != filter_mode_title
|
472
591
|
lines = @lines.grep(@rs.search)
|
473
|
-
fscr =
|
474
|
-
raise
|
592
|
+
fscr = Screen.new nil, lines: lines, search: @rs.search, name: filter_mode_title
|
593
|
+
raise PushScreen.new(fscr)
|
475
594
|
end
|
476
595
|
end
|
477
596
|
|
@@ -498,10 +617,34 @@ class KV_Screen
|
|
498
617
|
|
499
618
|
when 'v'
|
500
619
|
if @filename
|
501
|
-
|
620
|
+
syste m("vi #{@filename} +#{self.y + 1}")
|
502
621
|
@last_rs = nil
|
503
622
|
end
|
504
623
|
|
624
|
+
when 'P'
|
625
|
+
begin
|
626
|
+
if v = `gist -v` and /^gist v\d/ =~ v
|
627
|
+
screen_status "gist-ing..."
|
628
|
+
|
629
|
+
url = IO.popen('gist -p', 'a+'){|rw|
|
630
|
+
@lines.each{|line| rw.puts line}
|
631
|
+
rw.close_write
|
632
|
+
rw.read
|
633
|
+
}
|
634
|
+
msg = "gist URL: #{url}"
|
635
|
+
at_exit{
|
636
|
+
puts msg
|
637
|
+
}
|
638
|
+
screen_status msg
|
639
|
+
pause
|
640
|
+
else
|
641
|
+
raise v.inspect
|
642
|
+
end
|
643
|
+
rescue Errno::ENOENT
|
644
|
+
screen_status 'gist command is not found'
|
645
|
+
pause
|
646
|
+
end
|
647
|
+
|
505
648
|
when 'm'
|
506
649
|
@mouse = !@mouse
|
507
650
|
Curses.close_screen
|
@@ -514,16 +657,52 @@ class KV_Screen
|
|
514
657
|
|
515
658
|
when 'N'
|
516
659
|
@rs.line_mode = !@rs.line_mode
|
660
|
+
when 'T'
|
661
|
+
@rs.ts_mode = !@rs.ts_mode if @time_stamp
|
662
|
+
when 'S'
|
663
|
+
@rs.separation_mode = !@rs.separation_mode
|
517
664
|
when 't'
|
518
665
|
Curses.close_screen
|
519
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
|
687
|
+
|
688
|
+
when 'H'
|
689
|
+
if @meta
|
690
|
+
lines = @meta.map{|k, v| "#{k}: #{v}"}
|
691
|
+
raise PushScreen.new(Screen.new nil, lines: lines, name: "Response header [#{@name}]")
|
692
|
+
end
|
693
|
+
|
694
|
+
when Curses::KEY_CTRL_G
|
695
|
+
# do nothing
|
520
696
|
|
521
697
|
when '?'
|
522
|
-
raise
|
698
|
+
raise PushScreen.new(Screen.new help_io)
|
523
699
|
|
524
700
|
when nil
|
525
701
|
# ignore
|
526
702
|
|
703
|
+
when Curses::KEY_RESIZE
|
704
|
+
# ignore
|
705
|
+
|
527
706
|
else
|
528
707
|
screen_status "unknown: #{key_name(ev)}"
|
529
708
|
pause
|
@@ -548,6 +727,10 @@ class KV_Screen
|
|
548
727
|
raise
|
549
728
|
end
|
550
729
|
end
|
730
|
+
|
731
|
+
def redraw!
|
732
|
+
@last_rs = nil
|
733
|
+
end
|
551
734
|
end
|
552
735
|
|
553
736
|
class KV
|
@@ -559,11 +742,39 @@ class KV
|
|
559
742
|
}
|
560
743
|
|
561
744
|
files = parse_option(argv)
|
745
|
+
name = files.shift
|
562
746
|
|
563
747
|
@pipe_in = nil
|
564
748
|
|
565
|
-
if
|
566
|
-
|
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
|
567
778
|
input = help_io
|
568
779
|
name = 'HELP'
|
569
780
|
else
|
@@ -573,16 +784,20 @@ class KV
|
|
573
784
|
@pipe_in = input
|
574
785
|
end
|
575
786
|
else
|
576
|
-
name = files.shift
|
577
787
|
begin
|
578
788
|
input = open(name)
|
579
|
-
rescue
|
580
|
-
|
789
|
+
rescue Errno::ENOENT
|
790
|
+
case name
|
791
|
+
when /(.+):(\d+)/
|
581
792
|
name = $1
|
582
|
-
@first_line = $2.to_i - 1
|
793
|
+
@opts[:first_line] = $2.to_i - 1
|
583
794
|
retry
|
795
|
+
when URI.regexp
|
796
|
+
input = URI.open(name)
|
797
|
+
else
|
798
|
+
STDERR.puts "#{name}: No such file or directory"
|
799
|
+
exit 1
|
584
800
|
end
|
585
|
-
raise
|
586
801
|
end
|
587
802
|
end
|
588
803
|
|
@@ -590,7 +805,7 @@ class KV
|
|
590
805
|
log "SIGINT"
|
591
806
|
}
|
592
807
|
|
593
|
-
@screens = [
|
808
|
+
@screens = [Screen.new(input, name: name, **@opts)]
|
594
809
|
end
|
595
810
|
|
596
811
|
def parse_option argv
|
@@ -604,6 +819,18 @@ class KV
|
|
604
819
|
opts.on('-N', 'Show lines'){
|
605
820
|
@opts[:line_mode] = true
|
606
821
|
}
|
822
|
+
opts.on('-T', '--time-stamp', 'Enable time stamp'){
|
823
|
+
@opts[:time_stamp] = true
|
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
|
+
}
|
607
834
|
opts.parse!(argv)
|
608
835
|
end
|
609
836
|
|
@@ -612,10 +839,12 @@ class KV
|
|
612
839
|
until @screens.empty?
|
613
840
|
begin
|
614
841
|
@screens.last.control
|
615
|
-
rescue
|
842
|
+
rescue PopScreen
|
616
843
|
@screens.pop
|
617
|
-
|
844
|
+
@screens.last.redraw!
|
845
|
+
rescue PushScreen => e
|
618
846
|
@screens.push e.screen
|
847
|
+
@screens.last.redraw!
|
619
848
|
end
|
620
849
|
end
|
621
850
|
ensure
|
data/lib/kv/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module KV
|
2
|
-
VERSION = "0.
|
3
|
-
end
|
1
|
+
module KV
|
2
|
+
VERSION = "0.7.0"
|
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.
|
4
|
+
version: 0.7.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,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
62
|
- !ruby/object:Gem::Version
|
62
63
|
version: '0'
|
63
64
|
requirements: []
|
64
|
-
rubygems_version: 3.2
|
65
|
+
rubygems_version: 3.1.2
|
65
66
|
signing_key:
|
66
67
|
specification_version: 4
|
67
68
|
summary: 'kv: A page viewer written by Ruby'
|