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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -4
  3. data/lib/kv/version.rb +3 -3
  4. data/lib/kv.rb +130 -39
  5. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07f9b8b9be509c1a29acef17374146c9381713514e19c91a8a2942f9c2ba09e9
4
- data.tar.gz: fb877ac8dbf8b5fac3f9b2c4e6690aa01fc25bca3b11fcf7abf88c52ef41fa23
3
+ metadata.gz: 5c689e9c776cfb742a4258bcbb83ed972be567eff1478e734f30abca73aaed0d
4
+ data.tar.gz: 9aa2015affec2a824ce1f528f29ad180d48be400ecf5505932ad6c8d229c9334
5
5
  SHA512:
6
- metadata.gz: c8c646ba69a9a977ea232dedaaff65db9f781d054e76d846913763821678c93c5362e7f4e153574d252505181da0a73a01b7865c38ac2c380c4bc9abaadcd6bc
7
- data.tar.gz: eb9f37454be0ce707fbc02f6caae238541fe1865460e67de6c730a2eb81e2ba90d2fbaefa97f418426f19a8343e651b843f9943c61cd5e49cb2d22d15e5cde32
6
+ metadata.gz: 4d261340c36970a0b5a828b4ff69868f0bc51858857a42e5307f2194bbc4116bdf6157064067fbbdfd4f175c1e8888e499163aca789fdcc02d93c1302208c959
7
+ data.tar.gz: 39e63f31e30988bc152a08d6afd77c605662291722912d2e3f9981f6731f421d8bb88da0c298b22f9bdaa116afabc8538ee152c8ec0e33afb5538454af604797
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # kv: A page viewer written by Ruby
1
+ # kv: A page viewer written in Ruby
2
2
 
3
- kv is a page viewer designed for streaming data written by Ruby.
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
- # Usage
11
+ kv requires recent Ruby and curses gem.
12
12
 
13
- kv requires Ruby and curses gem.
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.2.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 KV_PushScreen < Exception
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 KV_PopScreen < Exception
17
+ class PopScreen < Exception
17
18
  end
18
19
 
19
- class KV_Screen
20
+ class Screen
20
21
  RenderStatus = Struct.new(
21
- :c_cols, :c_lines, :x, :y,
22
- :search, :goto, :line_mode, :render_full, :last_lineno
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
- @load_unlimited = false
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
- data = input.read_nonblock(4096)
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
- lines.each{|line|
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
- if @following_mode
300
- if @rs.search && search_next_move(self.y + 1)
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
- if @following_mode
308
- @following_mode = false
309
- @load_unlimited = false
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 start
317
- (start...@lines.size).each{|i|
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
- if search_next_move start
328
- # OK
363
+ @searching = start
364
+ if search_next_move
365
+ # OK. self.y is updated.
329
366
  else
330
- screen_status "not found: [#{self.search_str}]"
331
- pause
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 KV_PopScreen
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
- @load_unlimited = true
417
- @yq << true
458
+ set_load_unlimited true
418
459
 
419
460
  when 'L'
420
- @load_unlimited = !@load_unlimited
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 = KV_Screen.new nil, lines: lines, search: @rs.search, name: filter_mode_title
474
- raise KV_PushScreen.new(fscr)
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 KV_PushScreen.new(KV_Screen.new help_io)
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
- if /(.+):(\d+)/ =~ name
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 = [KV_Screen.new(input, name: name, **@opts)]
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 KV_PopScreen
705
+ rescue PopScreen
616
706
  @screens.pop
617
- rescue KV_PushScreen => e
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.2.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-08 00:00:00.000000000 Z
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
- rubygems_version: 3.2.0.pre1
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'