listpager 1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9d70bf27f951f700d53cc6fb25c3c62d652ae150
4
+ data.tar.gz: 8483fcf02b0b1373a10c4d1f5ef0585ef202cc60
5
+ SHA512:
6
+ metadata.gz: 25c7f7684495706aad4efe72393af0228c02c2a00a258b5c00fdcc1a4b1740a40fe8f7a5e841c9fa2ddc6b7fc6521cfd001e2e5f9ffafe3ca4e9d8fbeabf2027
7
+ data.tar.gz: 22e83c69890005484f1e75cac5332965f24992c646bdddab4b81aa5d2ccbcce2392515266fa10b924763ec5cd14578b2c20f45a06c6c0ec8f846155369491bdd
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in listpager.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Mike Owens <mike@meter.md>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # listpager
2
+
3
+ ## Introduction
4
+ listpager is a terminal listbox. It reads `stdin` for a list of items, goes all
5
+ interactive, and writes events to `stdout` as the user interacts with it.
6
+
7
+ ![listpager in action](./doc/screenshot.png)
8
+
9
+ *listpager is in the left-hand panel*
10
+
11
+ listpager has proportional scroll bars, and can handle lists of any length. It
12
+ handles terminal resizing just fine.
13
+
14
+ listpager was written as a component of [Cult][1], a fleet management tool. Cult's
15
+ interactive mode is a specially-crafted tmux session consisting of tools that
16
+ talk to each other. listpager is the node selection widget.
17
+
18
+ So basically, you may want to `popen` listpager, print a list of somethings to
19
+ its `stdin`, and listen on its `stdout`.
20
+
21
+ ```ruby
22
+ listpager = IO.popen('listpager', 'r+')
23
+
24
+ # You don't want buffered IO
25
+ listpager.sync = true
26
+
27
+ 50.times do |i|
28
+ listpager.puts "Item #{i}"
29
+ end
30
+
31
+ # Enter command mode.
32
+ listpager.puts "%%"
33
+ listpager.puts "select 35"
34
+ ```
35
+
36
+ If you want to play with the protocol, it's easiest to use two terminals and
37
+ `socat` (some implementations of `nc`/`netcat` do weird FD juggling which
38
+ ends up sending raw keyboard character input back to the client.).
39
+
40
+ Set up one like:
41
+
42
+ ```bash
43
+ socat TCP-LISTEN:4500,reuseaddr EXEC:'listpager'
44
+ ```
45
+
46
+ And a "client", like:
47
+ ```bash
48
+ socat TCP:localhost:4500 -
49
+ ```
50
+
51
+ Add some items on your keyboard, then enter `%%` to enter command mode. Another
52
+ `%%` will put you back in command mode. If you need a literal `%%` list item,
53
+ escape it with `\%%`. If you need a literal `\\%%`, you're out of luck, because
54
+ complete escaping isn't available yet.
55
+
56
+ There are a lot of obvious things the protocol could do, that it doesn't
57
+ currently. It's way low-hanging fruit for any contributors (`clear`, `rename`,
58
+ `move`, etc.)
59
+
60
+ ## Protocol
61
+ listpager reads each item from stdin, and it becomes a list item. As the user
62
+ arrows through the list, it outputs messages like:
63
+
64
+ `select 21 apples` where `21` is the index into the list, and `abacate` is the
65
+ caption. Any other keys pressed on an item are written out like
66
+ `keypress enter apples`.
67
+
68
+ listpager stops considering input bulk list items once it sees: `%%`, where it
69
+ enters command mode. Currently, command mode does nothing, but in the future,
70
+ it will allow the calling program to instruct listpager to select certain items,
71
+ ask for statuses, manipulate the list, add badges, change captions, etc.
72
+
73
+
74
+ ## Dependencies and Installation
75
+ Install listpager with `gem install listpager`. It has few dependencies:
76
+ currently only `ncurses-ruby`.
77
+
78
+
79
+ ## Implementation Notes
80
+ curses is terrible but portable. 'curses' doesn't expose enough to be useful,
81
+ 'ncurses-ruby' is about as good as you'll do in Ruby.
82
+
83
+
84
+ ## Upcoming Features
85
+ Right now, listpager does exactly what Cult needs, and nothing more. For it to
86
+ be more functional, I'd like to add a few features:
87
+
88
+ * `listpager -1`, for displaying a list, and just outputting the first item
89
+ the user selected with enter, ala Zenity/dialog.
90
+ * A search/filter activated with the `/` key
91
+ * Mouse support, with scroll wheels.
92
+ * Checkboxes
93
+ * Extend command mode
94
+
95
+
96
+ ## Contributing
97
+ ~~I'm trying to keep listpager a single-file, small project, preferably under
98
+ 500 lines of code.~~ I've given up on the idea that this can be functional,
99
+ correct, and maintainable in a single file that can be copied to a bin
100
+ directory. If you use listpager and know Ruby, please dig in and PR at its
101
+ github: https://github.com/mieko/listpager
102
+
103
+
104
+ ## License
105
+ listpager is released under the MIT license. Check out LICENSE.txt
106
+
107
+
108
+ ## Authors
109
+ listpager was written by Mike A. Owens at meter.md. mike@meter.md
110
+
111
+ [1]: https://github.com/metermd/cult "Cult"
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "listpager"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
Binary file
data/exe/listpager ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ require 'listpager/client_terminal'
3
+
4
+ [$stdin, $stdout].each do |io|
5
+ io.sync = true
6
+ io.reopen('/dev/null') if io.tty?
7
+ end
8
+
9
+ begin
10
+ Listpager::ClientTerminal.new.run
11
+ rescue Interrupt
12
+ $stdout.puts "interrupt"
13
+ $stdout.flush
14
+ rescue SystemCallError => e
15
+ $stderr.puts "#{$0}: #{e.message}"
16
+ exit 1
17
+ end
@@ -0,0 +1,182 @@
1
+ require 'ncurses'
2
+ require 'shellwords'
3
+ require 'optparse'
4
+ require 'io/console'
5
+
6
+ require 'listpager/color'
7
+ require 'listpager/list'
8
+
9
+ module Listpager
10
+ class ClientTerminal
11
+ attr_reader :tty
12
+ attr_reader :self_pipe
13
+ attr_reader :list
14
+ attr_reader :mode
15
+
16
+ def initialize
17
+ @tty = File.open('/dev/tty', 'r+')
18
+ @self_pipe = IO.pipe
19
+
20
+ [@tty, *self_pipe].each do |io|
21
+ io.sync = true
22
+ end
23
+
24
+ initialize_curses
25
+
26
+ @list = List.new(Ncurses.stdscr)
27
+ @mode = :append
28
+ @buffer = ''
29
+ end
30
+
31
+ def initialize_curses
32
+ screen = Ncurses.newterm(nil, @tty, @tty)
33
+ Ncurses.set_term(screen)
34
+ Ncurses.start_color
35
+ Color.init
36
+ Ncurses.use_default_colors
37
+ Ncurses.cbreak
38
+ Ncurses.stdscr.scrollok(false)
39
+ Ncurses.stdscr.keypad(true)
40
+ Ncurses.curs_set(0)
41
+ Ncurses.noecho
42
+ Ncurses.timeout(0)
43
+ end
44
+
45
+ def deinitialize
46
+ Ncurses.echo
47
+ Ncurses.nocbreak
48
+ Ncurses.nl
49
+ Ncurses.endwin
50
+ @tty.close
51
+ end
52
+
53
+ def append_mode?
54
+ @mode == :append
55
+ end
56
+
57
+ def process_command(line)
58
+ if append_mode?
59
+ case line
60
+ when '\%%'
61
+ list.values.push('%%')
62
+ list.dirty!
63
+ when '%%'
64
+ @mode = :command
65
+ else
66
+ list.values.push(line)
67
+ list.dirty!
68
+ end
69
+ else
70
+ cmd, *args = Shellwords.split(line)
71
+ begin
72
+ case cmd
73
+ when '%%', 'append-mode'
74
+ @mode = :append
75
+ when 'get-selected'
76
+ list.selection_changed
77
+ when 'select'
78
+ list.selected = args.fetch(0).to_i
79
+ when 'get-item'
80
+ puts ["item", args.fetch(0), list.values[args.fetch(0).to_i]].join ' '
81
+ when 'quit'
82
+ raise Interrupt
83
+ end
84
+ rescue IndexError => e
85
+ puts "error bad-command #{line}"
86
+ end
87
+ end
88
+ end
89
+
90
+ def consume_stdin(handles, fd)
91
+ loop do
92
+ begin
93
+ @buffer << fd.read_nonblock(512)
94
+ rescue EOFError
95
+ handles.delete(fd)
96
+ break
97
+ rescue IO::WaitReadable
98
+ break
99
+ end
100
+ end
101
+
102
+ unless @buffer.empty?
103
+ used = 0
104
+ StringIO.new(@buffer).each_line do |line|
105
+ if line[-1] == "\n"
106
+ process_command(line.chomp)
107
+ used += line.size
108
+ else
109
+ break
110
+ end
111
+ end
112
+ @buffer = @buffer[used...-1]
113
+ end
114
+ end
115
+
116
+ def consume_tty(handles, fd)
117
+ while (ch = Ncurses.getch) != -1
118
+ list.key_input(ch)
119
+ end
120
+ end
121
+
122
+ # We get a character from self_pipe here telling us the window
123
+ # has resized.
124
+ def consume_self_pipe(handles, fd)
125
+ code = fd.read(1)
126
+ case code
127
+ when 'R'
128
+ new_size = IO.console.winsize
129
+ Ncurses.resizeterm(*new_size)
130
+ Ncurses.stdscr.clear
131
+ list.dirty!
132
+ list.render
133
+ Ncurses.refresh
134
+ end
135
+ end
136
+
137
+ def dispatch_fd(handles, fd)
138
+ case fd
139
+ when $stdin
140
+ consume_stdin(@handles, fd)
141
+ when tty
142
+ consume_tty(@handles, fd)
143
+ when self_pipe[0]
144
+ consume_self_pipe(@handles, fd)
145
+ end
146
+ end
147
+
148
+ def process_events
149
+ @handles ||= [$stdin, tty, self_pipe[0]]
150
+
151
+ return if @handles.empty?
152
+
153
+ res = IO.select(@handles)
154
+ if res && (readers = res[0])
155
+ readers.each do |fd|
156
+ dispatch_fd(@handles, fd)
157
+ end
158
+ end
159
+ end
160
+
161
+ def run
162
+ trap 'WINCH' do
163
+ self_pipe[1].tap do |fd|
164
+ fd.write 'R'
165
+ fd.flush
166
+ end
167
+ end
168
+
169
+ begin
170
+ loop do
171
+ process_events
172
+ if list.render
173
+ Ncurses.redrawwin(list.window)
174
+ Ncurses.refresh
175
+ end
176
+ end
177
+ ensure
178
+ deinitialize
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,33 @@
1
+ require 'ncurses'
2
+
3
+ module Listpager
4
+ class Color
5
+ VALUES = {
6
+ list_default: 0,
7
+ list_selected: 1,
8
+ scroll_track: 2,
9
+ scroll_thumb: 3,
10
+ scroll_arrow: 4,
11
+ }.freeze
12
+
13
+ def self.curses_lookup(c)
14
+ Ncurses.const_get(c)
15
+ end
16
+
17
+ def self.init_color(c, fg, bg)
18
+ fail "Invalid color: #{c.inspect}" if VALUES[c].nil?
19
+ Ncurses.init_pair(VALUES[c], curses_lookup(fg), curses_lookup(bg))
20
+ end
21
+
22
+ def self.init
23
+ init_color(:list_selected, :COLOR_BLACK, :COLOR_WHITE)
24
+ init_color(:scroll_track, :COLOR_BLACK, :COLOR_BLACK)
25
+ init_color(:scroll_thumb, :COLOR_WHITE, :COLOR_WHITE)
26
+ init_color(:scroll_arrow, :COLOR_WHITE, :COLOR_BLACK)
27
+ end
28
+
29
+ def self.[](name)
30
+ VALUES[name] or fail "invalid color name: #{name.inspect}"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,150 @@
1
+ require 'ncurses'
2
+
3
+ require 'listpager/color'
4
+ require 'listpager/scrollbar'
5
+
6
+ module Listpager
7
+ class List
8
+ INDICATOR = ' ➤ '
9
+ NO_INDICATOR = ' '
10
+
11
+ # U+2003 "EM SPACE". Ncurses' range-combining "optimizations" fuck up normal
12
+ # and non-breaking spaces. For display, this is fine. For copying, you'd
13
+ # have a scrollbar in the way anyway. I fear newer releases of ncurses will
14
+ # get smarter and also consider this "blank" for optimizations.
15
+ BLANK_SPACE = ' '
16
+
17
+ def on_select_change
18
+ puts "select #{selected} #{values[selected]}"
19
+ end
20
+
21
+ def on_key_press(k)
22
+ puts "keypress #{key_name(k)} #{selected} #{values[selected]}"
23
+ end
24
+
25
+ attr_reader :window
26
+ attr_accessor :values
27
+ attr_reader :selected
28
+ attr_reader :offset
29
+ attr_reader :scrollbar
30
+
31
+ def initialize(window)
32
+ @window = window
33
+ @values = []
34
+ @selected = 0
35
+ @offset = 0
36
+ @dirty = true
37
+ @scrollbar = Scrollbar.new(self)
38
+ end
39
+
40
+ def dirty!(value = true)
41
+ @dirty = value
42
+ end
43
+
44
+ def dirty?
45
+ @dirty
46
+ end
47
+
48
+ def offset=(v)
49
+ dirty! if v != @offset
50
+ @offset = v
51
+ end
52
+
53
+ attr_reader :selected
54
+ def selected=(v)
55
+ maxx, maxy = getmaxxy
56
+
57
+ v = [0, v, values.size - 1].sort[1]
58
+ self.offset = [v - maxy + 1, offset, v].sort[1]
59
+
60
+ if v != @selected
61
+ dirty!
62
+ on_select_change
63
+ end
64
+
65
+ return (@selected = v)
66
+ end
67
+
68
+ def key_name(v)
69
+ @m ||= {
70
+ 27 => 'esc',
71
+ 10 => 'enter',
72
+ 260 => 'left',
73
+ 261 => 'right',
74
+ 127 => 'backspace',
75
+ 330 => 'delete',
76
+ ' ' => 'space',
77
+ }
78
+ @m[v] || (v < 255 && v.chr.match(/[[:print:]]/) ? v.chr : "\##{v}")
79
+ end
80
+
81
+ def key_input(value)
82
+ maxx, maxy = getmaxxy
83
+
84
+ case value
85
+ when Ncurses::KEY_UP
86
+ self.selected -= 1
87
+ when Ncurses::KEY_DOWN
88
+ self.selected += 1
89
+ when Ncurses::KEY_PPAGE
90
+ self.selected -= maxy - 1
91
+ when Ncurses::KEY_NPAGE
92
+ self.selected += maxy - 1
93
+ else
94
+ on_key_press(value)
95
+ end
96
+ end
97
+
98
+ def dirty!(v = true)
99
+ @dirty = v
100
+ end
101
+
102
+ def normalize(s)
103
+ s.gsub(/[^[:print:]]/, '')
104
+ end
105
+
106
+ def getmaxxy
107
+ maxx, maxy = [], []
108
+ window.getmaxyx(maxy, maxx)
109
+ [maxx[0], maxy[0]]
110
+ end
111
+
112
+ def render
113
+ return false if ! dirty?
114
+
115
+ maxx, maxy = getmaxxy
116
+
117
+ (0...maxy).each do |i|
118
+ window.color_set(Color[:list_default], nil)
119
+
120
+ list_index = offset + i
121
+ window.move(i, 0)
122
+ indicator = nil
123
+
124
+ fixed_len = maxx - scrollbar.width
125
+
126
+ if list_index == selected
127
+ window.color_set(Color[:list_selected], nil)
128
+ indicator = INDICATOR
129
+ else
130
+ indicator = NO_INDICATOR
131
+ end
132
+
133
+ string = values[list_index] || ''
134
+ string = normalize(string)
135
+ string = indicator + string
136
+
137
+ if string.size < fixed_len
138
+ string += (BLANK_SPACE * (fixed_len - string.size))
139
+ elsif string.size > fixed_len
140
+ string = string[0...fixed_len]
141
+ end
142
+ window.addstr(string)
143
+ end
144
+
145
+ scrollbar.render
146
+ dirty!(false)
147
+ return true
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,107 @@
1
+ require 'listpager/color'
2
+
3
+ module Listpager
4
+ class Scrollbar
5
+ UP_ARROW = '▴'
6
+ DOWN_ARROW = '▾'
7
+
8
+ attr_reader :list
9
+
10
+ def initialize(list)
11
+ @list = list
12
+ @memo_keys = nil
13
+ @memo_val = nil
14
+ end
15
+
16
+ def width
17
+ 1
18
+ end
19
+
20
+ def scroll_thumb_range
21
+ maxx, maxy = getmaxxy
22
+
23
+ # We memoize these based on the inputs that effect the output
24
+ memo_keys = [list.values.size, list.offset, maxy]
25
+ return @memo_val if @memo_keys == memo_keys
26
+
27
+ # Ref: http://csdgn.org/inform/scrollbar-mechanics
28
+ # using original camelCasedVariableNames for clarity with the source.
29
+ contentSize = list.values.size.to_f
30
+ windowSize = maxy.to_f
31
+ trackSize = windowSize - 2
32
+ windowContentRatio = windowSize / contentSize
33
+ gripSize = trackSize * windowContentRatio
34
+
35
+ minimalGripSize = 1.0
36
+ if gripSize < minimalGripSize
37
+ gripSize = minimalGripSize
38
+ end
39
+
40
+ windowScrollAreaSize = contentSize - windowSize
41
+ windowPosition = list.offset.to_f
42
+ windowPositionRatio = windowPosition / windowScrollAreaSize
43
+
44
+ trackScrollAreaSize = trackSize - gripSize
45
+ gripPositionOnTrack = trackScrollAreaSize * windowPositionRatio
46
+
47
+ st = 1 + gripPositionOnTrack.floor.to_i
48
+ e = (st + gripSize.ceil).to_i
49
+
50
+ st = 1 if st < 1
51
+ e = maxy - 1 if e > maxy - 1
52
+
53
+ @memo_keys = memo_keys
54
+ return (@memo_val = st ... e)
55
+ end
56
+
57
+ def window
58
+ list.window
59
+ end
60
+
61
+ def getmaxxy
62
+ list.getmaxxy
63
+ end
64
+
65
+ def render
66
+ maxx, maxy = getmaxxy
67
+ x = maxx - self.width
68
+
69
+ # If we don't need a scroll bar...
70
+ return if list.values.size <= maxy
71
+
72
+ # Both arrows
73
+ window.color_set(Color[:scroll_arrow], nil)
74
+ window.move(0, x)
75
+ window.addstr(UP_ARROW)
76
+ window.move(maxy - 1, x)
77
+ window.addstr(DOWN_ARROW)
78
+
79
+ # The full track
80
+ window.color_set(Color[:scroll_track], nil)
81
+ (1 ... maxy - 1).each do |y|
82
+ window.move(y, x)
83
+ window.addstr(' ')
84
+ end
85
+
86
+ # Scroll thumb on top of it
87
+ window.color_set(Color[:scroll_thumb], nil)
88
+ scroll_thumb_range.each do |y|
89
+ window.move(y, x)
90
+ window.addstr(' ')
91
+ end
92
+
93
+ # Set our drawing cursor at the origin
94
+ window.color_set(Color[:list_default], nil)
95
+ window.move(0, 0)
96
+ end
97
+
98
+ def can_scroll_up?
99
+ list.offset > 0
100
+ end
101
+
102
+ def can_scroll_down?
103
+ _, maxy = getmaxxy
104
+ list.values.size > list.offset + maxy
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,3 @@
1
+ module Listpager
2
+ VERSION = "1.0"
3
+ end
data/lib/listpager.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "listpager/version"
2
+
3
+ module Listpager
4
+ # Your code goes here...
5
+ end
data/listpager.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'listpager/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "listpager"
8
+ spec.version = Listpager::VERSION
9
+ spec.authors = ["Mike Owens"]
10
+ spec.email = ["mike@meter.md"]
11
+
12
+ spec.summary = "Interactive terminal pager for lists"
13
+ spec.description = "Ncurses list pager, controllable via stdin and stdout"
14
+ spec.homepage = "https://github.com/mieko/listpager"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = "https://rubygems.org/"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_runtime_dependency "ncurses-ruby", "~> 1.2.4"
31
+ spec.add_development_dependency "bundler", "~> 1.12"
32
+ spec.add_development_dependency "rake", "~> 10.0"
33
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: listpager
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - Mike Owens
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-08-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ncurses-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.2.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.2.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: Ncurses list pager, controllable via stdin and stdout
56
+ email:
57
+ - mike@meter.md
58
+ executables:
59
+ - listpager
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - bin/console
69
+ - bin/setup
70
+ - doc/screenshot.png
71
+ - exe/listpager
72
+ - lib/listpager.rb
73
+ - lib/listpager/client_terminal.rb
74
+ - lib/listpager/color.rb
75
+ - lib/listpager/list.rb
76
+ - lib/listpager/scrollbar.rb
77
+ - lib/listpager/version.rb
78
+ - listpager.gemspec
79
+ homepage: https://github.com/mieko/listpager
80
+ licenses:
81
+ - MIT
82
+ metadata:
83
+ allowed_push_host: https://rubygems.org/
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.5.1
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Interactive terminal pager for lists
104
+ test_files: []