pecorb 1.1.0 → 2.0.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/.rspec +2 -0
- data/Gemfile.lock +57 -5
- data/Guardfile +70 -0
- data/README.md +34 -14
- data/bin/pecorb +6 -2
- data/lib/core_extensions/comparable.rb +10 -0
- data/lib/pecorb/console.rb +64 -49
- data/lib/pecorb/list.rb +113 -0
- data/lib/pecorb/pager.rb +81 -0
- data/lib/pecorb/version.rb +1 -1
- data/lib/pecorb.rb +13 -117
- data/pecorb.gemspec +5 -3
- metadata +48 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ac862459ac7398a7e517d4d1b169e1b76c78c676
|
4
|
+
data.tar.gz: c234e84d93a3089facd5d786cc5d25f10b93fe70
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab9498a864ed1a74adeddb706a08b069ab11438015213fb19e8788462ea8ed4e029bf5611c6a1ac2e9d6e17fca9742cfd52664c9da41a6f780e34dc59c7d5697
|
7
|
+
data.tar.gz: 723c86474c4a89ec9d1bc1efcc4f02e84539c0fa2a2825eac0a1b7e706ab28d6fe4b09742f8f1c20a48035e74e229a0fc3a818771a4fff51bf1423517b30e768
|
data/.rspec
ADDED
data/Gemfile.lock
CHANGED
@@ -1,22 +1,74 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pecorb (
|
4
|
+
pecorb (2.0.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
-
|
9
|
+
byebug (9.0.6)
|
10
|
+
coderay (1.1.1)
|
11
|
+
diff-lcs (1.2.5)
|
12
|
+
ffi (1.9.14)
|
13
|
+
formatador (0.2.5)
|
14
|
+
guard (2.14.0)
|
15
|
+
formatador (>= 0.2.4)
|
16
|
+
listen (>= 2.7, < 4.0)
|
17
|
+
lumberjack (~> 1.0)
|
18
|
+
nenv (~> 0.1)
|
19
|
+
notiffany (~> 0.0)
|
20
|
+
pry (>= 0.9.12)
|
21
|
+
shellany (~> 0.0)
|
22
|
+
thor (>= 0.18.1)
|
23
|
+
listen (3.1.5)
|
24
|
+
rb-fsevent (~> 0.9, >= 0.9.4)
|
25
|
+
rb-inotify (~> 0.9, >= 0.9.7)
|
26
|
+
ruby_dep (~> 1.2)
|
27
|
+
lumberjack (1.0.10)
|
28
|
+
method_source (0.8.2)
|
29
|
+
nenv (0.3.0)
|
30
|
+
notiffany (0.1.1)
|
31
|
+
nenv (~> 0.1)
|
32
|
+
shellany (~> 0.0)
|
33
|
+
pry (0.10.4)
|
34
|
+
coderay (~> 1.1.0)
|
35
|
+
method_source (~> 0.8.1)
|
36
|
+
slop (~> 3.4)
|
37
|
+
pry-byebug (3.4.0)
|
38
|
+
byebug (~> 9.0)
|
39
|
+
pry (~> 0.10)
|
10
40
|
rake (10.4.2)
|
41
|
+
rb-fsevent (0.9.8)
|
42
|
+
rb-inotify (0.9.7)
|
43
|
+
ffi (>= 0.5.0)
|
44
|
+
rspec (3.5.0)
|
45
|
+
rspec-core (~> 3.5.0)
|
46
|
+
rspec-expectations (~> 3.5.0)
|
47
|
+
rspec-mocks (~> 3.5.0)
|
48
|
+
rspec-core (3.5.4)
|
49
|
+
rspec-support (~> 3.5.0)
|
50
|
+
rspec-expectations (3.5.0)
|
51
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
52
|
+
rspec-support (~> 3.5.0)
|
53
|
+
rspec-mocks (3.5.0)
|
54
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
55
|
+
rspec-support (~> 3.5.0)
|
56
|
+
rspec-support (3.5.0)
|
57
|
+
ruby_dep (1.5.0)
|
58
|
+
shellany (0.0.1)
|
59
|
+
slop (3.6.0)
|
60
|
+
thor (0.19.4)
|
11
61
|
|
12
62
|
PLATFORMS
|
13
63
|
ruby
|
14
64
|
|
15
65
|
DEPENDENCIES
|
16
|
-
bundler
|
17
|
-
|
66
|
+
bundler
|
67
|
+
guard
|
18
68
|
pecorb!
|
19
|
-
|
69
|
+
pry-byebug
|
70
|
+
rake
|
71
|
+
rspec
|
20
72
|
|
21
73
|
BUNDLED WITH
|
22
74
|
1.13.3
|
data/Guardfile
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
19
|
+
# rspec may be run, below are examples of the most common uses.
|
20
|
+
# * bundler: 'bundle exec rspec'
|
21
|
+
# * bundler binstubs: 'bin/rspec'
|
22
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
23
|
+
# installed the spring binstubs per the docs)
|
24
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
25
|
+
# * 'just' rspec: 'rspec'
|
26
|
+
|
27
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
28
|
+
require "guard/rspec/dsl"
|
29
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
30
|
+
|
31
|
+
# Feel free to open issues for suggestions and improvements
|
32
|
+
|
33
|
+
# RSpec files
|
34
|
+
rspec = dsl.rspec
|
35
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
36
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
37
|
+
watch(rspec.spec_files)
|
38
|
+
|
39
|
+
# Ruby files
|
40
|
+
ruby = dsl.ruby
|
41
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
42
|
+
|
43
|
+
# Rails files
|
44
|
+
rails = dsl.rails(view_extensions: %w(erb haml slim))
|
45
|
+
dsl.watch_spec_files_for(rails.app_files)
|
46
|
+
dsl.watch_spec_files_for(rails.views)
|
47
|
+
|
48
|
+
watch(rails.controllers) do |m|
|
49
|
+
[
|
50
|
+
rspec.spec.call("routing/#{m[1]}_routing"),
|
51
|
+
rspec.spec.call("controllers/#{m[1]}_controller"),
|
52
|
+
rspec.spec.call("acceptance/#{m[1]}")
|
53
|
+
]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Rails config changes
|
57
|
+
watch(rails.spec_helper) { rspec.spec_dir }
|
58
|
+
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
59
|
+
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
60
|
+
|
61
|
+
# Capybara features specs
|
62
|
+
watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
|
63
|
+
watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
|
64
|
+
|
65
|
+
# Turnip features and steps
|
66
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
67
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
|
68
|
+
Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
|
69
|
+
end
|
70
|
+
end
|
data/README.md
CHANGED
@@ -6,17 +6,11 @@ It is based on `inquirer.js` and `peco` (which itself is based on `percol`).
|
|
6
6
|
The above mentioned tools are all more feature rich than this one (and I'm sure
|
7
7
|
the code is better too), but I wanted a few different features:
|
8
8
|
|
9
|
-
- A list that can be navigated using the
|
9
|
+
- A list that can be navigated using the keyboard (like `enquirer`)
|
10
10
|
- Fuzzy filterable (like `ctrl-p` in vim, etc.)
|
11
11
|
- Not take over the entire screen (like curses interfaces normally do)
|
12
|
-
- (
|
12
|
+
- Dependency free (except for development dependencies)
|
13
13
|
|
14
|
-
## Status
|
15
|
-
|
16
|
-
Currently this works well on the command line but may still have some bugs.
|
17
|
-
Using it as part of a ruby program still needs to be tested.
|
18
|
-
|
19
|
-
This was written during a hackathon and has no tests :'(
|
20
14
|
|
21
15
|
## Installation
|
22
16
|
|
@@ -27,12 +21,15 @@ gem 'pecorb'
|
|
27
21
|
```
|
28
22
|
|
29
23
|
And then execute:
|
24
|
+
```
|
25
|
+
$ bundle
|
26
|
+
```
|
30
27
|
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
Or install it system wide for use on the command line:
|
29
|
+
```
|
30
|
+
$ gem install pecorb
|
31
|
+
```
|
34
32
|
|
35
|
-
$ gem install pecorb
|
36
33
|
|
37
34
|
## Usage
|
38
35
|
|
@@ -46,10 +43,30 @@ pecorb myFile.txt
|
|
46
43
|
For use in a ruby program:
|
47
44
|
|
48
45
|
```ruby
|
49
|
-
Pecorb.
|
50
|
-
Pecorb.
|
46
|
+
result = Pecorb.list %w[Apples Bananas Cake Donuts]
|
47
|
+
result = Pecorb.list %w[Apples Bananas Cake Donuts], prompt: "Favourite food: "
|
51
48
|
```
|
52
49
|
|
50
|
+
|
51
|
+
## Key Bindings
|
52
|
+
|
53
|
+
- `up` and `ctrl-k` will move up through the list
|
54
|
+
- `down` and `ctrl-j` will move down through the list
|
55
|
+
- `ctrl-l` will clear the screen and re-print the menu
|
56
|
+
- `crtl-c` and `escape` will exit (with exit code `0`)
|
57
|
+
- `ctrl-d` (`eof`) and `enter` (`\r`) will select the item
|
58
|
+
- `baskspace`, `left` and `right` do what you would expect
|
59
|
+
|
60
|
+
|
61
|
+
## Status
|
62
|
+
|
63
|
+
Currently this seems to work well on the command line and as a ruby library.
|
64
|
+
|
65
|
+
There are some tests but not great coverage. The complex parts are mainly the
|
66
|
+
paging logic (which does have tests) and the random access terminal printing
|
67
|
+
that makes up the user interface (which doesn't have tests).
|
68
|
+
|
69
|
+
|
53
70
|
## Development
|
54
71
|
|
55
72
|
After checking out the repo, run `bundle install` to install dependencies.
|
@@ -60,16 +77,19 @@ run `bundle install` and `bundle exec rake release`, which will create a git
|
|
60
77
|
tag for the version, push git commits and tags, and push the `.gem` file to
|
61
78
|
[rubygems.org](https://rubygems.org).
|
62
79
|
|
80
|
+
|
63
81
|
## Contributing
|
64
82
|
|
65
83
|
Bug reports and pull requests are welcome on GitHub at
|
66
84
|
https://github.com/stevenocchipinti/pecorb.
|
67
85
|
|
86
|
+
|
68
87
|
## License
|
69
88
|
|
70
89
|
The gem is available as open source under the terms of the
|
71
90
|
[MIT License](http://opensource.org/licenses/MIT).
|
72
91
|
|
92
|
+
|
73
93
|
## Kudos
|
74
94
|
|
75
95
|
Inspiration and education came from these resources:
|
data/bin/pecorb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require_relative "../lib/pecorb"
|
3
3
|
|
4
|
+
Pecorb.configure do |config|
|
5
|
+
config.output_stream = $stdout.isatty ? $stdout : $stderr
|
6
|
+
end
|
7
|
+
|
4
8
|
input = ARGF.readlines.map(&:strip).reject(&:empty?)
|
5
9
|
|
6
10
|
# After reading from $stdin (file, pipe, etc.), switch $stdin to be the users
|
7
|
-
# terminal
|
11
|
+
# terminal so they can interact with the prompt
|
8
12
|
$stdin.reopen(File.open("/dev/tty", "r"))
|
9
13
|
|
10
|
-
Pecorb.
|
14
|
+
$stdout.puts Pecorb.list input unless input.empty?
|
data/lib/pecorb/console.rb
CHANGED
@@ -1,56 +1,71 @@
|
|
1
1
|
require "io/console"
|
2
|
+
require_relative "../pecorb"
|
2
3
|
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
input
|
16
|
-
|
4
|
+
module Pecorb
|
5
|
+
module Console
|
6
|
+
CSI = "\e["
|
7
|
+
UP = "#{CSI}A"
|
8
|
+
DOWN = "#{CSI}B"
|
9
|
+
RIGHT = "#{CSI}C"
|
10
|
+
LEFT = "#{CSI}D"
|
11
|
+
|
12
|
+
def read_char
|
13
|
+
input_stream.echo = false
|
14
|
+
input_stream.raw!
|
15
|
+
input = input_stream.getc.chr
|
16
|
+
if input == "\e" then
|
17
|
+
input << input_stream.read_nonblock(3) rescue nil
|
18
|
+
input << input_stream.read_nonblock(2) rescue nil
|
19
|
+
end
|
20
|
+
ensure
|
21
|
+
input_stream.echo = true
|
22
|
+
input_stream.cooked!
|
23
|
+
return input
|
24
|
+
end
|
25
|
+
|
26
|
+
def puts(val="") output_stream.puts val end
|
27
|
+
def print(val="") output_stream.print val end
|
28
|
+
|
29
|
+
def up(n=1) output_stream.print "#{CSI}#{n}A" end
|
30
|
+
def down(n=1) output_stream.print "#{CSI}#{n}B" end
|
31
|
+
def right(n=1) output_stream.print "#{CSI}#{n}C" end
|
32
|
+
def left(n=1) output_stream.print "#{CSI}#{n}D" end
|
33
|
+
|
34
|
+
def backspace(n=1) output_stream.print "\b"*n end
|
35
|
+
def carriage_return() output_stream.print "\r" end
|
36
|
+
def clear_to_eol() output_stream.print "#{CSI}K" end
|
37
|
+
def clear_to_eos() output_stream.print "#{CSI}J" end
|
38
|
+
def clear_screen() output_stream.print "#{CSI}H#{CSI}J" end
|
39
|
+
|
40
|
+
def black() output_stream.print "#{CSI}#{CSI}30m" end
|
41
|
+
def red() output_stream.print "#{CSI}#{CSI}31m" end
|
42
|
+
def green() output_stream.print "#{CSI}#{CSI}32m" end
|
43
|
+
def yellow() output_stream.print "#{CSI}#{CSI}33m" end
|
44
|
+
def blue() output_stream.print "#{CSI}#{CSI}34m" end
|
45
|
+
def magenta() output_stream.print "#{CSI}#{CSI}35m" end
|
46
|
+
def cyan() output_stream.print "#{CSI}#{CSI}36m" end
|
47
|
+
def white() output_stream.print "#{CSI}#{CSI}37m" end
|
48
|
+
def reset_color() output_stream.print "#{CSI}#{CSI}0m" end
|
49
|
+
|
50
|
+
# WARNING: Only use this for updates, when we can be sure no new lines will
|
51
|
+
# be added as this causes lines to be overwritten, see issue #1
|
52
|
+
def save_pos
|
53
|
+
output_stream.print "#{CSI}s"
|
54
|
+
if block_given?
|
55
|
+
yield
|
56
|
+
load_pos
|
57
|
+
end
|
58
|
+
end
|
59
|
+
def load_pos() output_stream.print "#{CSI}u" end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def input_stream
|
64
|
+
Pecorb.config.input_stream
|
17
65
|
end
|
18
|
-
ensure
|
19
|
-
$stdin.echo = true
|
20
|
-
$stdin.cooked!
|
21
|
-
return input
|
22
|
-
end
|
23
66
|
|
24
|
-
|
25
|
-
|
26
|
-
def output(val) $stdout.puts val end
|
27
|
-
|
28
|
-
def up(n=1) $stderr.print "#{CSI}#{n}A" end
|
29
|
-
def down(n=1) $stderr.print "#{CSI}#{n}B" end
|
30
|
-
def right(n=1) $stderr.print "#{CSI}#{n}C" end
|
31
|
-
def left(n=1) $stderr.print "#{CSI}#{n}D" end
|
32
|
-
|
33
|
-
def backspace(n=1) $stderr.print "\b"*n end
|
34
|
-
def carriage_return() $stderr.print "\r" end
|
35
|
-
def clear_to_eol() $stderr.print "#{CSI}K" end
|
36
|
-
def clear_screen() $stderr.print "#{CSI}H#{CSI}J" end
|
37
|
-
|
38
|
-
def black() $stderr.print "#{CSI}#{CSI}30m" end
|
39
|
-
def red() $stderr.print "#{CSI}#{CSI}31m" end
|
40
|
-
def green() $stderr.print "#{CSI}#{CSI}32m" end
|
41
|
-
def yellow() $stderr.print "#{CSI}#{CSI}33m" end
|
42
|
-
def blue() $stderr.print "#{CSI}#{CSI}34m" end
|
43
|
-
def magenta() $stderr.print "#{CSI}#{CSI}35m" end
|
44
|
-
def cyan() $stderr.print "#{CSI}#{CSI}36m" end
|
45
|
-
def white() $stderr.print "#{CSI}#{CSI}37m" end
|
46
|
-
def reset_color() $stderr.print "#{CSI}#{CSI}0m" end
|
47
|
-
|
48
|
-
def save_pos
|
49
|
-
$stderr.print "#{CSI}s"
|
50
|
-
if block_given?
|
51
|
-
yield
|
52
|
-
load_pos
|
67
|
+
def output_stream
|
68
|
+
Pecorb.config.output_stream
|
53
69
|
end
|
54
70
|
end
|
55
|
-
def load_pos() $stderr.print "#{CSI}u" end
|
56
71
|
end
|
data/lib/pecorb/list.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require_relative "console"
|
2
|
+
require_relative "../core_extensions/comparable"
|
3
|
+
require_relative "pager"
|
4
|
+
|
5
|
+
module Pecorb
|
6
|
+
class List
|
7
|
+
include Console
|
8
|
+
|
9
|
+
def initialize(items, opts={})
|
10
|
+
raise "Items must be enumerable!" unless items.is_a? Enumerable
|
11
|
+
@prompt = opts.fetch(:prompt, "Select an item: ")
|
12
|
+
@pager = Pager.new items, IO.console.winsize.first - 2
|
13
|
+
@cursor = 0
|
14
|
+
@filter_text = ""
|
15
|
+
end
|
16
|
+
|
17
|
+
def prompt
|
18
|
+
init_ui
|
19
|
+
while c = read_char
|
20
|
+
case c
|
21
|
+
when "", "\r"
|
22
|
+
break
|
23
|
+
when "", ""
|
24
|
+
carriage_return
|
25
|
+
clear_to_eos
|
26
|
+
exit 0
|
27
|
+
when ""
|
28
|
+
clear_screen
|
29
|
+
print @prompt
|
30
|
+
@cursor.times { right }
|
31
|
+
update_ui
|
32
|
+
when "" # Backspace key
|
33
|
+
next if @filter_text.empty? || @cursor <= 0
|
34
|
+
@filter_text.slice!(@cursor - 1)
|
35
|
+
update_ui
|
36
|
+
backspace
|
37
|
+
@cursor -= 1
|
38
|
+
when Console::LEFT
|
39
|
+
next unless @cursor > 0
|
40
|
+
print c
|
41
|
+
@cursor -= 1
|
42
|
+
when Console::RIGHT
|
43
|
+
next unless @cursor < @filter_text.length
|
44
|
+
print c
|
45
|
+
@cursor += 1
|
46
|
+
when Console::UP, ""
|
47
|
+
@pager.up
|
48
|
+
update_ui
|
49
|
+
when Console::DOWN, "\n" # CTRL-J enters a linefeed char in bash
|
50
|
+
@pager.down
|
51
|
+
update_ui
|
52
|
+
else
|
53
|
+
@filter_text.insert(@cursor, c)
|
54
|
+
update_ui
|
55
|
+
print c
|
56
|
+
@cursor += 1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
backspace(@cursor)
|
61
|
+
clear_to_eos
|
62
|
+
cyan
|
63
|
+
puts @pager.selected_item
|
64
|
+
reset_color
|
65
|
+
@pager.selected_item
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def init_ui
|
71
|
+
# WARNING: Can't use save_pos here because it causes problems when
|
72
|
+
# introducing newlines, see issue #1
|
73
|
+
puts
|
74
|
+
print_items
|
75
|
+
carriage_return; (@pager.items_in_viewport.size + 1).times { up }
|
76
|
+
print @prompt
|
77
|
+
end
|
78
|
+
|
79
|
+
def update_ui
|
80
|
+
save_pos do
|
81
|
+
update_filter_text
|
82
|
+
clear_items
|
83
|
+
@pager.filter! @filter_text
|
84
|
+
print_items
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def update_filter_text
|
89
|
+
backspace(@cursor)
|
90
|
+
print @filter_text
|
91
|
+
clear_to_eol
|
92
|
+
end
|
93
|
+
|
94
|
+
def clear_items
|
95
|
+
carriage_return; down; clear_to_eol
|
96
|
+
@pager.items_in_viewport.size.times { down; clear_to_eol }
|
97
|
+
@pager.items_in_viewport.size.times { up }
|
98
|
+
end
|
99
|
+
|
100
|
+
def print_items
|
101
|
+
@pager.items_in_viewport.each_with_index do |item, i|
|
102
|
+
if item == @pager.selected_item
|
103
|
+
cyan
|
104
|
+
print "‣ "
|
105
|
+
else
|
106
|
+
print " "
|
107
|
+
end
|
108
|
+
puts item
|
109
|
+
reset_color
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/pecorb/pager.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require_relative "../core_extensions/comparable"
|
2
|
+
|
3
|
+
module Pecorb
|
4
|
+
class Pager
|
5
|
+
def initialize(items, viewport_size)
|
6
|
+
@configured_items = @items = items
|
7
|
+
@viewport_size = viewport_size
|
8
|
+
@cursor = 0
|
9
|
+
set_viewport_to_top
|
10
|
+
end
|
11
|
+
|
12
|
+
def selected_item
|
13
|
+
@items.fetch(@cursor)
|
14
|
+
end
|
15
|
+
|
16
|
+
def items_in_viewport
|
17
|
+
@items.slice(@viewport.min, @viewport_size)
|
18
|
+
end
|
19
|
+
|
20
|
+
def filter!(filter_text)
|
21
|
+
self.items = fuzzy_filter(filter_text)
|
22
|
+
end
|
23
|
+
|
24
|
+
def items=(new_items)
|
25
|
+
@items = new_items
|
26
|
+
@cursor = @cursor.clamp(0, @items.size-1)
|
27
|
+
reset_viewport_to_cover_cursor
|
28
|
+
end
|
29
|
+
|
30
|
+
def down
|
31
|
+
move_cursor_by(1)
|
32
|
+
end
|
33
|
+
|
34
|
+
def up
|
35
|
+
move_cursor_by(-1)
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def move_cursor_by(number)
|
42
|
+
new_cursor = @cursor + number
|
43
|
+
if new_cursor >= @items.size
|
44
|
+
@cursor = 0
|
45
|
+
reset_viewport_to_cover_cursor
|
46
|
+
elsif new_cursor > @viewport.max
|
47
|
+
shift_viewport_by(1)
|
48
|
+
end
|
49
|
+
if new_cursor < 0
|
50
|
+
@cursor = @items.size - 1
|
51
|
+
reset_viewport_to_cover_cursor
|
52
|
+
elsif new_cursor < @viewport.min
|
53
|
+
shift_viewport_by(-1)
|
54
|
+
end
|
55
|
+
@cursor = new_cursor % @items.size
|
56
|
+
end
|
57
|
+
|
58
|
+
def shift_viewport_by(number)
|
59
|
+
@viewport = (@viewport.min + number)..(@viewport.max + number)
|
60
|
+
end
|
61
|
+
|
62
|
+
def reset_viewport_to_cover_cursor
|
63
|
+
set_viewport_to_top unless @viewport.all? {|i| @items[i]}
|
64
|
+
if @cursor < @viewport.min
|
65
|
+
@viewport = @cursor..(@cursor + @viewport_size - 1)
|
66
|
+
elsif @cursor > @viewport.max
|
67
|
+
@viewport = (@cursor - @viewport_size + 1)..@cursor
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def set_viewport_to_top
|
72
|
+
@viewport = 0..(@viewport_size - 1)
|
73
|
+
end
|
74
|
+
|
75
|
+
def fuzzy_filter(filter_text)
|
76
|
+
return @configured_items unless filter_text
|
77
|
+
regex = Regexp.new(filter_text.chars.join(".*"), "i")
|
78
|
+
@configured_items.select {|i| regex.match i }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/pecorb/version.rb
CHANGED
data/lib/pecorb.rb
CHANGED
@@ -1,131 +1,27 @@
|
|
1
|
+
require_relative "pecorb/list"
|
1
2
|
require_relative "pecorb/version"
|
2
|
-
require_relative "pecorb/console"
|
3
3
|
|
4
4
|
module Pecorb
|
5
5
|
extend self
|
6
|
-
extend Console
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
@selected = 0
|
11
|
-
@items = []
|
12
|
-
@displayed_items = []
|
13
|
-
|
14
|
-
|
15
|
-
def prompt(items, prompt="Select an item: ")
|
16
|
-
@displayed_items = @items = items
|
17
|
-
@prompt = prompt
|
18
|
-
|
19
|
-
print_menu
|
20
|
-
|
21
|
-
while c = read_char
|
22
|
-
case c
|
23
|
-
when /[\r]/
|
24
|
-
break
|
25
|
-
when /[]/
|
26
|
-
@input = ""
|
27
|
-
break
|
28
|
-
when /[]/
|
29
|
-
clear_screen
|
30
|
-
print_menu
|
31
|
-
when "" # Backspace key
|
32
|
-
next if @input.empty? || @cursor <= 0
|
33
|
-
@input.slice!(@cursor-1)
|
34
|
-
replace_input(@input)
|
35
|
-
replace_items { filter_items(@items, @input) }
|
36
|
-
backspace
|
37
|
-
@cursor -= 1
|
38
|
-
when Console::LEFT
|
39
|
-
next unless @cursor > 0
|
40
|
-
print c
|
41
|
-
@cursor -= 1
|
42
|
-
when Console::RIGHT
|
43
|
-
next unless @cursor < @input.length
|
44
|
-
print c
|
45
|
-
@cursor += 1
|
46
|
-
when Console::UP
|
47
|
-
@selected = (@selected - 1) % @displayed_items.size
|
48
|
-
replace_items { filter_items(@items, @input) }
|
49
|
-
when Console::DOWN
|
50
|
-
@selected = (@selected + 1) % @displayed_items.size
|
51
|
-
replace_items { filter_items(@items, @input) }
|
52
|
-
else
|
53
|
-
@input.insert(@cursor, c)
|
54
|
-
replace_input(@input)
|
55
|
-
replace_items { filter_items(@items, @input) }
|
56
|
-
print c
|
57
|
-
@cursor += 1
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
move_cursor_from_start_to_end
|
62
|
-
output @displayed_items[@selected]
|
63
|
-
@displayed_items[@selected]
|
7
|
+
def list(*args)
|
8
|
+
List.new(*args).prompt
|
64
9
|
end
|
65
10
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
print_items(@displayed_items)
|
71
|
-
move_cursor_from_end_to_start
|
72
|
-
print @input
|
73
|
-
end
|
74
|
-
|
75
|
-
def move_cursor_from_end_to_start
|
76
|
-
up(@displayed_items.size + 1)
|
77
|
-
right(@prompt.size)
|
78
|
-
end
|
79
|
-
|
80
|
-
def move_cursor_from_start_to_end
|
81
|
-
down(@displayed_items.size + 1)
|
82
|
-
carriage_return
|
83
|
-
end
|
84
|
-
|
85
|
-
def replace_input(str)
|
86
|
-
save_pos do
|
87
|
-
backspace(@cursor)
|
88
|
-
print @input
|
89
|
-
clear_to_eol
|
11
|
+
DefaultConfig = Struct.new(:input_stream, :output_stream) do
|
12
|
+
def initialize
|
13
|
+
self.input_stream = $stdin
|
14
|
+
self.output_stream = $stdout
|
90
15
|
end
|
91
16
|
end
|
92
17
|
|
93
|
-
def
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
down
|
98
|
-
carriage_return
|
99
|
-
clear_to_eol
|
100
|
-
if list_size > 0
|
101
|
-
(list_size - 1).times { down; clear_to_eol}
|
102
|
-
(list_size - 1).times { up }
|
103
|
-
end
|
104
|
-
@displayed_items = yield
|
105
|
-
@selected = limit_max @selected, list_size - 1
|
106
|
-
print_items @displayed_items
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def limit_max(n, max)
|
111
|
-
[[max, n].min, 0].max
|
112
|
-
end
|
113
|
-
|
114
|
-
def print_items(items)
|
115
|
-
items.each_with_index do |item, i|
|
116
|
-
if @selected == i
|
117
|
-
cyan
|
118
|
-
print "‣ "
|
119
|
-
else
|
120
|
-
print " "
|
121
|
-
end
|
122
|
-
puts item
|
123
|
-
reset_color
|
124
|
-
end
|
18
|
+
def self.configure
|
19
|
+
@config = DefaultConfig.new
|
20
|
+
yield(@config) if block_given?
|
21
|
+
@config
|
125
22
|
end
|
126
23
|
|
127
|
-
def
|
128
|
-
|
129
|
-
items.select {|i| regex.match i }
|
24
|
+
def self.config
|
25
|
+
@config || configure
|
130
26
|
end
|
131
27
|
end
|
data/pecorb.gemspec
CHANGED
@@ -21,7 +21,9 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
22
|
spec.require_paths = ["lib"]
|
23
23
|
|
24
|
-
spec.add_development_dependency "bundler"
|
25
|
-
spec.add_development_dependency "rake"
|
26
|
-
spec.add_development_dependency "
|
24
|
+
spec.add_development_dependency "bundler"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
spec.add_development_dependency "guard"
|
28
|
+
spec.add_development_dependency "pry-byebug"
|
27
29
|
end
|
metadata
CHANGED
@@ -1,57 +1,85 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pecorb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Steven Occhipinti
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry-byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
55
83
|
description: A lightweight, ruby version of peco with filtering
|
56
84
|
email:
|
57
85
|
- dev@stevenocchipinti.com
|
@@ -61,14 +89,19 @@ extensions: []
|
|
61
89
|
extra_rdoc_files: []
|
62
90
|
files:
|
63
91
|
- ".gitignore"
|
92
|
+
- ".rspec"
|
64
93
|
- Gemfile
|
65
94
|
- Gemfile.lock
|
95
|
+
- Guardfile
|
66
96
|
- LICENSE.txt
|
67
97
|
- README.md
|
68
98
|
- Rakefile
|
69
99
|
- bin/pecorb
|
100
|
+
- lib/core_extensions/comparable.rb
|
70
101
|
- lib/pecorb.rb
|
71
102
|
- lib/pecorb/console.rb
|
103
|
+
- lib/pecorb/list.rb
|
104
|
+
- lib/pecorb/pager.rb
|
72
105
|
- lib/pecorb/version.rb
|
73
106
|
- pecorb.gemspec
|
74
107
|
- pecorb/.gitignore
|