kv 0.2.0 → 0.3.0

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