kv 0.2.0 → 0.3.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/README.md +12 -4
- data/lib/kv/version.rb +3 -3
- data/lib/kv.rb +130 -39
- 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: 5c689e9c776cfb742a4258bcbb83ed972be567eff1478e734f30abca73aaed0d
|
|
4
|
+
data.tar.gz: 9aa2015affec2a824ce1f528f29ad180d48be400ecf5505932ad6c8d229c9334
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d261340c36970a0b5a828b4ff69868f0bc51858857a42e5307f2194bbc4116bdf6157064067fbbdfd4f175c1e8888e499163aca789fdcc02d93c1302208c959
|
|
7
|
+
data.tar.gz: 39e63f31e30988bc152a08d6afd77c605662291722912d2e3f9981f6731f421d8bb88da0c298b22f9bdaa116afabc8538ee152c8ec0e33afb5538454af604797
|
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,9 @@ Install it yourself as:
|
|
|
8
8
|
|
|
9
9
|
$ gem install kv
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
kv requires recent Ruby and curses gem.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# Usage
|
|
14
14
|
|
|
15
15
|
## Use kv
|
|
16
16
|
|
|
@@ -18,6 +18,9 @@ kv requires Ruby and curses gem.
|
|
|
18
18
|
# View [FILE]
|
|
19
19
|
$ kv [OPTIONS] [FILE]
|
|
20
20
|
|
|
21
|
+
# View [URI] source code
|
|
22
|
+
$ kv [URI]
|
|
23
|
+
|
|
21
24
|
# View results of [CMD]
|
|
22
25
|
$ [CMD] | kv [OPTIONS]
|
|
23
26
|
|
|
@@ -27,6 +30,8 @@ $ kv
|
|
|
27
30
|
Options:
|
|
28
31
|
-f following mode like "tail -f"
|
|
29
32
|
-n, --line-number LINE goto LINE
|
|
33
|
+
-N Show lines
|
|
34
|
+
-T, --time-stamp Enable time stamp
|
|
30
35
|
```
|
|
31
36
|
|
|
32
37
|
## Command on a pager
|
|
@@ -72,12 +77,15 @@ kv: A pager by Ruby Command list
|
|
|
72
77
|
|
|
73
78
|
# Output
|
|
74
79
|
s: Save screen buffer to file
|
|
80
|
+
P: gist -p
|
|
75
81
|
|
|
76
82
|
# Modes
|
|
77
83
|
N: toggle line mode
|
|
84
|
+
T: toggle time stamp mode
|
|
78
85
|
m: toggle mouse mode
|
|
79
86
|
t: terminal (REPL) mode
|
|
80
87
|
v: vi ("vi filename +[LINE]")
|
|
88
|
+
H: show HTTP response header
|
|
81
89
|
```
|
|
82
90
|
|
|
83
91
|
`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/version.rb
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
module KV
|
|
2
|
-
VERSION = "0.
|
|
3
|
-
end
|
|
1
|
+
module KV
|
|
2
|
+
VERSION = "0.3.0"
|
|
3
|
+
end
|
data/lib/kv.rb
CHANGED
|
@@ -4,22 +4,23 @@ 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, :line_mode, :
|
|
22
|
+
:c_cols, :c_lines, :x, :y, :last_lineno,
|
|
23
|
+
:search, :goto, :line_mode, :ts_mode, :wrapping,
|
|
23
24
|
)
|
|
24
25
|
class RenderStatus
|
|
25
26
|
def to_s
|
|
@@ -27,7 +28,7 @@ class KV_Screen
|
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
30
|
|
|
30
|
-
def initialize input, lines: [], search: nil, name: nil, following_mode: false, first_line: 0, line_mode: false
|
|
31
|
+
def initialize input, lines: [], search: nil, name: nil, following_mode: false, first_line: 0, line_mode: false, time_stamp: nil
|
|
31
32
|
@rs = RenderStatus.new
|
|
32
33
|
@last_rs = nil
|
|
33
34
|
@rs.y = first_line
|
|
@@ -36,14 +37,18 @@ class KV_Screen
|
|
|
36
37
|
@rs.last_lineno = 0
|
|
37
38
|
@rs.line_mode = line_mode
|
|
38
39
|
@rs.search = search
|
|
40
|
+
@rs.wrapping = false
|
|
41
|
+
@rs.ts_mode = false
|
|
39
42
|
|
|
40
43
|
@name = name
|
|
41
44
|
@filename = @name if @name && File.exist?(@name)
|
|
45
|
+
@time_stamp = time_stamp
|
|
42
46
|
|
|
43
47
|
@lines = lines
|
|
44
48
|
@mode = :screen
|
|
45
49
|
|
|
46
50
|
@following_mode = following_mode
|
|
51
|
+
@searching = false
|
|
47
52
|
|
|
48
53
|
@mouse = false
|
|
49
54
|
@search_ignore_case = false
|
|
@@ -51,25 +56,35 @@ class KV_Screen
|
|
|
51
56
|
@loading = false
|
|
52
57
|
@buffer_lines = 10_000
|
|
53
58
|
@yq = Queue.new
|
|
54
|
-
@
|
|
59
|
+
if @filename
|
|
60
|
+
@load_unlimited = true
|
|
61
|
+
else
|
|
62
|
+
@load_unlimited = false
|
|
63
|
+
end
|
|
64
|
+
|
|
55
65
|
@prev_render = {}
|
|
66
|
+
@meta = input.respond_to?(:meta) ? input.meta : nil
|
|
56
67
|
|
|
57
68
|
read_async input if input
|
|
58
69
|
end
|
|
59
70
|
|
|
60
71
|
def setup_line line
|
|
61
72
|
line = line.chomp
|
|
73
|
+
line.instance_variable_set(:@time_stamp, Time.now.strftime('%H:%M:%S')) if @time_stamp
|
|
62
74
|
line.instance_variable_set(:@lineno, @rs.last_lineno += 1)
|
|
63
75
|
line
|
|
64
76
|
end
|
|
65
77
|
|
|
66
78
|
def read_async input
|
|
67
79
|
@loading = true
|
|
68
|
-
|
|
80
|
+
begin
|
|
81
|
+
data = input.read_nonblock(4096)
|
|
82
|
+
rescue IO::EAGAINWaitReadable, EOFError
|
|
83
|
+
data = ''
|
|
84
|
+
end
|
|
69
85
|
|
|
70
|
-
lines = data.each_line.to_a
|
|
71
86
|
last_line = nil
|
|
72
|
-
|
|
87
|
+
data.each_line{|line|
|
|
73
88
|
if line[-1] != "\n"
|
|
74
89
|
last_line = line
|
|
75
90
|
break
|
|
@@ -131,6 +146,11 @@ class KV_Screen
|
|
|
131
146
|
@rs.x
|
|
132
147
|
end
|
|
133
148
|
|
|
149
|
+
def set_load_unlimited b
|
|
150
|
+
@load_unlimited = b
|
|
151
|
+
@yq << true
|
|
152
|
+
end
|
|
153
|
+
|
|
134
154
|
def init_screen
|
|
135
155
|
Curses.init_screen
|
|
136
156
|
Curses.stdscr.keypad(true)
|
|
@@ -185,8 +205,8 @@ class KV_Screen
|
|
|
185
205
|
|
|
186
206
|
def render_data
|
|
187
207
|
# check update
|
|
188
|
-
c_lines = Curses.lines
|
|
189
|
-
c_cols = Curses.cols
|
|
208
|
+
c_lines = @rs.c_lines = Curses.lines
|
|
209
|
+
c_cols = @rs.c_cols = Curses.cols
|
|
190
210
|
|
|
191
211
|
if @rs != @last_rs
|
|
192
212
|
@last_rs = @rs.dup
|
|
@@ -229,6 +249,13 @@ class KV_Screen
|
|
|
229
249
|
end
|
|
230
250
|
end
|
|
231
251
|
|
|
252
|
+
if @rs.ts_mode && ts = line.instance_variable_get(:@time_stamp)
|
|
253
|
+
cattr LINE_ATTR do
|
|
254
|
+
ts = line.instance_variable_get(:@time_stamp)
|
|
255
|
+
Curses.addstr("#{ts} |")
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
232
259
|
line = line[self.x, cols] || ''
|
|
233
260
|
|
|
234
261
|
if !@rs.search || !(Regexp === @rs.search)
|
|
@@ -296,39 +323,54 @@ class KV_Screen
|
|
|
296
323
|
render_status
|
|
297
324
|
ev = Curses.getch
|
|
298
325
|
check_update
|
|
299
|
-
|
|
300
|
-
|
|
326
|
+
y_max = self.y_max
|
|
327
|
+
|
|
328
|
+
if @rs.search && @searching
|
|
329
|
+
if search_next_move
|
|
301
330
|
break
|
|
302
331
|
end
|
|
303
|
-
self.y = self.y_max
|
|
304
332
|
end
|
|
305
|
-
end
|
|
306
333
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
334
|
+
if @following_mode
|
|
335
|
+
self.y = y_max
|
|
336
|
+
end
|
|
310
337
|
end
|
|
311
338
|
|
|
339
|
+
@following_mode = false
|
|
340
|
+
set_load_unlimited false
|
|
341
|
+
@searching = false
|
|
342
|
+
|
|
312
343
|
return ev
|
|
313
344
|
end
|
|
314
345
|
end
|
|
315
346
|
|
|
316
|
-
def search_next_move
|
|
317
|
-
|
|
347
|
+
def search_next_move
|
|
348
|
+
last_line = @lines.size
|
|
349
|
+
log (@searching..last_line)
|
|
350
|
+
|
|
351
|
+
(@searching...last_line).each{|i|
|
|
318
352
|
if @rs.search === @lines[i]
|
|
319
353
|
self.y = i
|
|
354
|
+
@searching = false
|
|
320
355
|
return true
|
|
321
356
|
end
|
|
322
357
|
}
|
|
358
|
+
@searching = last_line
|
|
323
359
|
return false
|
|
324
360
|
end
|
|
325
361
|
|
|
326
362
|
def search_next start
|
|
327
|
-
|
|
328
|
-
|
|
363
|
+
@searching = start
|
|
364
|
+
if search_next_move
|
|
365
|
+
# OK. self.y is updated.
|
|
329
366
|
else
|
|
330
|
-
|
|
331
|
-
|
|
367
|
+
if @loading
|
|
368
|
+
set_load_unlimited true
|
|
369
|
+
else
|
|
370
|
+
screen_status "not found: [#{self.search_str}]"
|
|
371
|
+
pause
|
|
372
|
+
@searching = false
|
|
373
|
+
end
|
|
332
374
|
end
|
|
333
375
|
end
|
|
334
376
|
|
|
@@ -382,7 +424,7 @@ class KV_Screen
|
|
|
382
424
|
|
|
383
425
|
case ev
|
|
384
426
|
when 'q'
|
|
385
|
-
raise
|
|
427
|
+
raise PopScreen
|
|
386
428
|
|
|
387
429
|
when Curses::KEY_UP, 'k'
|
|
388
430
|
self.y -= 1
|
|
@@ -413,12 +455,10 @@ class KV_Screen
|
|
|
413
455
|
|
|
414
456
|
when 'F'
|
|
415
457
|
@following_mode = true
|
|
416
|
-
|
|
417
|
-
@yq << true
|
|
458
|
+
set_load_unlimited true
|
|
418
459
|
|
|
419
460
|
when 'L'
|
|
420
|
-
|
|
421
|
-
@yq << true
|
|
461
|
+
set_load_unlimited !@load_unlimited
|
|
422
462
|
|
|
423
463
|
when '/'
|
|
424
464
|
search_str = ''.dup
|
|
@@ -470,8 +510,8 @@ class KV_Screen
|
|
|
470
510
|
filter_mode_title = "*filter mode [#{self.search_str}]*"
|
|
471
511
|
if @name != filter_mode_title
|
|
472
512
|
lines = @lines.grep(@rs.search)
|
|
473
|
-
fscr =
|
|
474
|
-
raise
|
|
513
|
+
fscr = Screen.new nil, lines: lines, search: @rs.search, name: filter_mode_title
|
|
514
|
+
raise PushScreen.new(fscr)
|
|
475
515
|
end
|
|
476
516
|
end
|
|
477
517
|
|
|
@@ -502,6 +542,30 @@ class KV_Screen
|
|
|
502
542
|
@last_rs = nil
|
|
503
543
|
end
|
|
504
544
|
|
|
545
|
+
when 'P'
|
|
546
|
+
begin
|
|
547
|
+
if v = `gist -v` and /^gist v\d/ =~ v
|
|
548
|
+
screen_status "gist-ing..."
|
|
549
|
+
|
|
550
|
+
url = IO.popen('gist -p', 'a+'){|rw|
|
|
551
|
+
@lines.each{|line| rw.puts line}
|
|
552
|
+
rw.close_write
|
|
553
|
+
rw.read
|
|
554
|
+
}
|
|
555
|
+
msg = "gist URL: #{url}"
|
|
556
|
+
at_exit{
|
|
557
|
+
puts msg
|
|
558
|
+
}
|
|
559
|
+
screen_status msg
|
|
560
|
+
pause
|
|
561
|
+
else
|
|
562
|
+
raise v.inspect
|
|
563
|
+
end
|
|
564
|
+
rescue Errno::ENOENT
|
|
565
|
+
screen_status 'gist command is not found'
|
|
566
|
+
pause
|
|
567
|
+
end
|
|
568
|
+
|
|
505
569
|
when 'm'
|
|
506
570
|
@mouse = !@mouse
|
|
507
571
|
Curses.close_screen
|
|
@@ -514,16 +578,30 @@ class KV_Screen
|
|
|
514
578
|
|
|
515
579
|
when 'N'
|
|
516
580
|
@rs.line_mode = !@rs.line_mode
|
|
581
|
+
when 'T'
|
|
582
|
+
@rs.ts_mode = !@rs.ts_mode if @time_stamp
|
|
517
583
|
when 't'
|
|
518
584
|
Curses.close_screen
|
|
519
585
|
@mode = :terminal
|
|
520
586
|
|
|
587
|
+
when 'H'
|
|
588
|
+
if @meta
|
|
589
|
+
lines = @meta.map{|k, v| "#{k}: #{v}"}
|
|
590
|
+
raise PushScreen.new(Screen.new nil, lines: lines, name: "Response header [#{@name}]")
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
when Curses::KEY_CTRL_G
|
|
594
|
+
# do nothing
|
|
595
|
+
|
|
521
596
|
when '?'
|
|
522
|
-
raise
|
|
597
|
+
raise PushScreen.new(Screen.new help_io)
|
|
523
598
|
|
|
524
599
|
when nil
|
|
525
600
|
# ignore
|
|
526
601
|
|
|
602
|
+
when Curses::KEY_RESIZE
|
|
603
|
+
# ignore
|
|
604
|
+
|
|
527
605
|
else
|
|
528
606
|
screen_status "unknown: #{key_name(ev)}"
|
|
529
607
|
pause
|
|
@@ -548,6 +626,10 @@ class KV_Screen
|
|
|
548
626
|
raise
|
|
549
627
|
end
|
|
550
628
|
end
|
|
629
|
+
|
|
630
|
+
def redraw!
|
|
631
|
+
@last_rs = nil
|
|
632
|
+
end
|
|
551
633
|
end
|
|
552
634
|
|
|
553
635
|
class KV
|
|
@@ -576,13 +658,18 @@ class KV
|
|
|
576
658
|
name = files.shift
|
|
577
659
|
begin
|
|
578
660
|
input = open(name)
|
|
579
|
-
rescue
|
|
580
|
-
|
|
661
|
+
rescue Errno::ENOENT
|
|
662
|
+
case name
|
|
663
|
+
when /(.+):(\d+)/
|
|
581
664
|
name = $1
|
|
582
665
|
@first_line = $2.to_i - 1
|
|
583
666
|
retry
|
|
667
|
+
when URI.regexp
|
|
668
|
+
input = URI.open(name)
|
|
669
|
+
else
|
|
670
|
+
STDERR.puts "#{name}: No such file or directory"
|
|
671
|
+
exit 1
|
|
584
672
|
end
|
|
585
|
-
raise
|
|
586
673
|
end
|
|
587
674
|
end
|
|
588
675
|
|
|
@@ -590,7 +677,7 @@ class KV
|
|
|
590
677
|
log "SIGINT"
|
|
591
678
|
}
|
|
592
679
|
|
|
593
|
-
@screens = [
|
|
680
|
+
@screens = [Screen.new(input, name: name, **@opts)]
|
|
594
681
|
end
|
|
595
682
|
|
|
596
683
|
def parse_option argv
|
|
@@ -604,6 +691,9 @@ class KV
|
|
|
604
691
|
opts.on('-N', 'Show lines'){
|
|
605
692
|
@opts[:line_mode] = true
|
|
606
693
|
}
|
|
694
|
+
opts.on('-T', '--time-stamp', 'Enable time stamp'){
|
|
695
|
+
@opts[:time_stamp] = true
|
|
696
|
+
}
|
|
607
697
|
opts.parse!(argv)
|
|
608
698
|
end
|
|
609
699
|
|
|
@@ -612,10 +702,11 @@ class KV
|
|
|
612
702
|
until @screens.empty?
|
|
613
703
|
begin
|
|
614
704
|
@screens.last.control
|
|
615
|
-
rescue
|
|
705
|
+
rescue PopScreen
|
|
616
706
|
@screens.pop
|
|
617
|
-
rescue
|
|
707
|
+
rescue PushScreen => e
|
|
618
708
|
@screens.push e.screen
|
|
709
|
+
@screens.last.redraw!
|
|
619
710
|
end
|
|
620
711
|
end
|
|
621
712
|
ensure
|
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.3.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-
|
|
11
|
+
date: 2020-02-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: curses
|
|
@@ -61,7 +61,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
61
61
|
- !ruby/object:Gem::Version
|
|
62
62
|
version: '0'
|
|
63
63
|
requirements: []
|
|
64
|
-
|
|
64
|
+
rubyforge_project:
|
|
65
|
+
rubygems_version: 2.7.6
|
|
65
66
|
signing_key:
|
|
66
67
|
specification_version: 4
|
|
67
68
|
summary: 'kv: A page viewer written by Ruby'
|