ansi-select 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dbdb0d626907ead9edbe0c70ece388639bfd60b2
4
- data.tar.gz: f6c5d41601a0ba70f4c5c14ba6b30992968fd6d6
3
+ metadata.gz: 31facd9c408f0836c2f8ab324fc26995701a433d
4
+ data.tar.gz: f6dc35cd7b1ea27db706eaba87912afe22abb5f9
5
5
  SHA512:
6
- metadata.gz: 93fd756748db4238b3fd331c947ae32973596909b04c349d40469688a1dcda566695c5109c1200514c4511f15d56c3e355d44fc3bd528abdd7e6df21a942a573
7
- data.tar.gz: beebfe8259573a84716765cbd55d0ac64e394bbb0f8a1aedb3c470972a5665b62070dbe4f3b377f5010fe6fc8756b3fe5d7111a601db708421bf5aa17d66de6b
6
+ metadata.gz: c13549de88f3ddc5905ea2813122140b1b0fec2a58a87a35c911e0fea365181867fa8fd2c8a06be1f4bd0b9668956aa267e3465f7d2e4a545d8b972cc4e2f14a
7
+ data.tar.gz: df044fe624e21523b04bccc32bf76e7565b055171d0cb946fbf84f68c30ceeecbe5637d2df1c88f20abf127e903e16d1b68653d9bf888c879c29079fed9b5395
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ### 0.2.0
2
+
3
+ * Add multi select.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![](https://dl.dropboxusercontent.com/spa/dlqheu39w0arg9q/yaano147.png)
1
+ ![](https://dl.dropboxusercontent.com/spa/dlqheu39w0arg9q/gvpg7_fw.png)
2
2
 
3
3
  ## Installation
4
4
 
@@ -13,17 +13,25 @@ There are two options:
13
13
 
14
14
  ```bash
15
15
  echo some words to choose from | tr ' ' '\n' | ansi-select
16
+
16
17
  cd $(ls -d */ | ansi-select) # Go to a visually selected subdirectory.
17
18
  git checkout $(git branch | ansi-select) # The same, but with git branches.
19
+
20
+ rm -r $(ls | ansi-select --multi) # Delete all the selected files.
18
21
  ```
19
22
 
20
23
  * A Ruby library.
21
24
 
22
25
  ```ruby
23
- require "ansi/select"
26
+ require "ansi/selector"
27
+
28
+ beverage = Ansi::Selector.select(["coffee", "tee"])
29
+
30
+ puts "Would you like some additions?"
31
+ additions = Ansi::Selector.multi_select(["sugar", "cream", "milk"])
24
32
 
25
- answer = Ansi::Select.new(["some", "words", "to", "choose", "from"]).select
26
- print "You chose #{answer}."
33
+ print "Here's your #{beverage}. "
34
+ print "We've also added #{additions.join(', ')}." if additions.present?
27
35
  ```
28
36
 
29
37
  The Ruby interface has an additional benefit of accepting any objects that respond
@@ -32,10 +40,7 @@ to `#to_s` and returning one of them instead of a string.
32
40
 
33
41
  ## Keyboard
34
42
 
35
- You can use up and down keys or `j`/`k` for navigation, and space or return key for choosing an option.
36
- If you've changed your mind, you can quit with Ctrl+C or `q`.
37
-
38
-
39
- ## TODO
40
-
41
- * Support multi-select.
43
+ * Up and down arrows or `j`/`k` to move around.
44
+ * Return to choose.
45
+ * Space to toggle in multi select mode.
46
+ * Ctrl-c or `q` to quit.
data/ansi-select.gemspec CHANGED
@@ -1,16 +1,16 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'ansi/select/version'
4
+ require 'ansi/selector/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "ansi-select"
8
- spec.version = Ansi::Select::VERSION
8
+ spec.version = Ansi::Selector::VERSION
9
9
  spec.authors = ["Volodymyr Shatskyi"]
10
10
  spec.email = ["shockone89@gmail.com"]
11
11
 
12
- spec.summary = %q{Simple, not full-screen, ncurses-like TUI select}
13
- spec.description = %q{This gem allows you to select an array element (where an array is arbitrary input) with a pretty text user interface.}
12
+ spec.summary = %q{A simple, not full-screen, ncurses-like TUI selector}
13
+ spec.description = %q{This gem allows you to select array elements (where an array is some arbitrary input) with a pretty text user interface.}
14
14
  spec.homepage = "https://github.com/shockone/ansi-select"
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
data/bin/ansi-select CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
- require 'ansi/select'
2
+ require 'ansi/selector'
3
3
 
4
- puts Ansi::Select.new(STDIN.readlines.map(&:chomp)).select
4
+ method_name = ARGV.include?('--multi') ? :multi_select : :select
5
+
6
+ puts Ansi::Selector.public_send(method_name, STDIN.readlines.map(&:chomp))
@@ -0,0 +1,21 @@
1
+ module Ansi
2
+ class Selector
3
+ # @param [Array<#to_s>] options
4
+ #
5
+ # @return [#to_s] option
6
+ def self.select(options)
7
+ require_relative "selector/single_impl"
8
+
9
+ SingleImpl.new(options).select
10
+ end
11
+
12
+ # @param [Array<#to_s>] options
13
+ #
14
+ # @return [Array<#to_s>] option
15
+ def self.multi_select(options)
16
+ require_relative "selector/multi_impl"
17
+
18
+ MultiImpl.new(options).select
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,133 @@
1
+ require "io/console"
2
+
3
+ module Ansi
4
+ class Selector
5
+ class Impl
6
+ CODES = {
7
+ standout_mode: `tput rev`,
8
+ exit_standout_mode: `tput rmso`,
9
+ cursor_up: `tput cuu1`,
10
+ cursor_down: `tput cud1`,
11
+ carriage_return_key: `tput cr`
12
+ }
13
+
14
+ def initialize(options)
15
+ @options = options
16
+
17
+ @highlighted_line_index = 0
18
+ @cursor_line_index = 0
19
+ end
20
+
21
+ def select
22
+ print_options
23
+ answer = ask_to_choose
24
+ go_to_line(@options.size)
25
+
26
+ answer
27
+ ensure
28
+ tty.close
29
+ end
30
+
31
+ private
32
+
33
+ # @return [File]
34
+ def tty
35
+ @tty ||= File.open('/dev/tty', 'w+')
36
+ end
37
+
38
+ def print_options
39
+ @options.each.with_index do |_, index|
40
+ print_line(index, index == @highlighted_line_index)
41
+
42
+ unless index == @options.size - 1
43
+ tty.print $/ # This strange thing is a cross-platform new line.
44
+ @cursor_line_index += 1
45
+ end
46
+ end
47
+
48
+ go_to_line(0)
49
+ end
50
+
51
+ # @return [String]
52
+ def listen_carefully_to_keyboard
53
+ tty.noecho do
54
+ tty.raw do
55
+ input = tty.getc.chr
56
+ if input == "\e"
57
+ input << tty.read_nonblock(3) rescue nil
58
+ input << tty.read_nonblock(2) rescue nil
59
+ end
60
+
61
+ input
62
+ end
63
+ end
64
+ end
65
+
66
+ # @return [#to_s]
67
+ def ask_to_choose
68
+ loop do
69
+ input = listen_carefully_to_keyboard
70
+
71
+ case input
72
+ when "\u0003", "q"
73
+ exit(0)
74
+ when " "
75
+ space_handler
76
+ when CODES[:carriage_return_key]
77
+ break carriage_return_handler
78
+ when "\e[A", "k", CODES[:cursor_up]
79
+ highlight_line(@highlighted_line_index - 1) unless @highlighted_line_index == 0
80
+ when "\e[B", "j", CODES[:cursor_down]
81
+ highlight_line(@highlighted_line_index + 1) unless @highlighted_line_index == @options.size - 1
82
+ end
83
+ end
84
+ end
85
+
86
+ # @param [Fixnum] index
87
+ # @param [Boolean] highlight
88
+ def print_line(index, highlight)
89
+ go_to_line(index)
90
+
91
+ if highlight
92
+ tty.print(CODES[:standout_mode] + prefix(index) + @options[index] + CODES[:exit_standout_mode])
93
+ else
94
+ tty.print(prefix(index) + @options[index])
95
+ end
96
+ end
97
+
98
+ # @param [Fixnum] index
99
+ def highlight_line(index)
100
+ print_line(@highlighted_line_index, false)
101
+ print_line(index, true)
102
+
103
+ @highlighted_line_index = index
104
+ end
105
+
106
+ # @param [Fixnum] index
107
+ def go_to_line(index)
108
+ if index == @cursor_line_index
109
+ # do nothing
110
+ elsif index > @cursor_line_index
111
+ (index - @cursor_line_index).times { tty.print CODES[:cursor_down] }
112
+ else
113
+ (@cursor_line_index - index).times { tty.print CODES[:cursor_up] }
114
+ end
115
+
116
+ @cursor_line_index = index
117
+ tty.print CODES[:carriage_return_key]
118
+ end
119
+
120
+ def prefix(index)
121
+ raise NotImplementedError
122
+ end
123
+
124
+ def space_handler
125
+ raise NotImplementedError
126
+ end
127
+
128
+ def carriage_return_handler
129
+ raise NotImplementedError
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'impl'
2
+
3
+ module Ansi
4
+ class Selector
5
+ class MultiImpl < Impl
6
+ def initialize(options)
7
+ super
8
+
9
+ # @type [Array<Boolean>]
10
+ @selected_options = []
11
+ end
12
+
13
+ private
14
+
15
+ def prefix(index)
16
+ @selected_options[index] ? '[x] ' : '[ ] '
17
+ end
18
+
19
+ def space_handler
20
+ @selected_options[@cursor_line_index] = !@selected_options[@cursor_line_index]
21
+ print_line(@cursor_line_index, true)
22
+ end
23
+
24
+ def carriage_return_handler
25
+ @selected_options.map.with_index { |value, index| @options[index] if value }.compact
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'impl'
2
+
3
+ module Ansi
4
+ class Selector
5
+ class SingleImpl < Impl
6
+ private
7
+
8
+ def prefix(index)
9
+ ' '
10
+ end
11
+
12
+ def space_handler
13
+ # Do nothing
14
+ end
15
+
16
+ def carriage_return_handler
17
+ @options[@highlighted_line_index]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ module Ansi
2
+ class Selector
3
+ VERSION = "0.2.0"
4
+ end
5
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ansi-select
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Volodymyr Shatskyi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-27 00:00:00.000000000 Z
11
+ date: 2015-10-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,8 +38,8 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
- description: This gem allows you to select an array element (where an array is arbitrary
42
- input) with a pretty text user interface.
41
+ description: This gem allows you to select array elements (where an array is some
42
+ arbitrary input) with a pretty text user interface.
43
43
  email:
44
44
  - shockone89@gmail.com
45
45
  executables:
@@ -52,13 +52,17 @@ files:
52
52
  - ".ruby-gemset"
53
53
  - ".ruby-version"
54
54
  - ".travis.yml"
55
+ - CHANGELOG.md
55
56
  - Gemfile
56
57
  - README.md
57
58
  - Rakefile
58
59
  - ansi-select.gemspec
59
60
  - bin/ansi-select
60
- - lib/ansi/select.rb
61
- - lib/ansi/select/version.rb
61
+ - lib/ansi/selector.rb
62
+ - lib/ansi/selector/impl.rb
63
+ - lib/ansi/selector/multi_impl.rb
64
+ - lib/ansi/selector/single_impl.rb
65
+ - lib/ansi/selector/version.rb
62
66
  homepage: https://github.com/shockone/ansi-select
63
67
  licenses: []
64
68
  metadata: {}
@@ -81,5 +85,5 @@ rubyforge_project:
81
85
  rubygems_version: 2.4.8
82
86
  signing_key:
83
87
  specification_version: 4
84
- summary: Simple, not full-screen, ncurses-like TUI select
88
+ summary: A simple, not full-screen, ncurses-like TUI selector
85
89
  test_files: []
data/lib/ansi/select.rb DELETED
@@ -1,121 +0,0 @@
1
- # coding: utf-8
2
-
3
- require "io/console"
4
-
5
- module Ansi
6
- class Select
7
- CODES = {
8
- standout_mode: `tput rev`,
9
- exit_standout_mode: `tput rmso`,
10
- cursor_up: `tput cuu1`,
11
- cursor_down: `tput cud1`,
12
- carriage_return_key: `tput cr`
13
- }
14
-
15
- # @param [Array<#to_s>] options
16
- def initialize(options)
17
- @options = options
18
-
19
- @highlighted_line_index = 0
20
- @cursor_line_index = 0
21
- end
22
-
23
- # @return [#to_s] option
24
- def select
25
- print_options
26
- answer = ask_to_choose
27
- go_to_line(@options.size)
28
-
29
- answer
30
- ensure
31
- tty.close
32
- end
33
-
34
- private
35
-
36
- # @return [File]
37
- def tty
38
- @tty ||= File.open('/dev/tty', 'w+')
39
- end
40
-
41
- def print_options
42
- @options.each.with_index do |_, index|
43
- print_line(index, index == @highlighted_line_index)
44
-
45
- unless index == @options.size - 1
46
- tty.print $/ # This strange thing is a cross-platform new line.
47
- @cursor_line_index += 1
48
- end
49
- end
50
-
51
- go_to_line(0)
52
- end
53
-
54
- # @return [String]
55
- def listen_carefully_to_keyboard
56
- tty.noecho do
57
- tty.raw do
58
- input = tty.getc.chr
59
- if input == "\e"
60
- input << tty.read_nonblock(3) rescue nil
61
- input << tty.read_nonblock(2) rescue nil
62
- end
63
-
64
- input
65
- end
66
- end
67
- end
68
-
69
- # @return [#to_s]
70
- def ask_to_choose
71
- loop do
72
- input = listen_carefully_to_keyboard
73
-
74
- case input
75
- when "\u0003", "q"
76
- exit(0)
77
- when CODES[:carriage_return_key], " "
78
- break @options[@highlighted_line_index]
79
- when "\e[A", "k", CODES[:cursor_up]
80
- highlight_line(@highlighted_line_index - 1) unless @highlighted_line_index == 0
81
- when "\e[B", "j", CODES[:cursor_down]
82
- highlight_line(@highlighted_line_index + 1) unless @highlighted_line_index == @options.size - 1
83
- end
84
- end
85
- end
86
-
87
- # @param [Fixnum] index
88
- # @param [Boolean] highlight
89
- def print_line(index, highlight)
90
- go_to_line(index)
91
-
92
- if highlight
93
- tty.print "#{CODES[:standout_mode]}#{@options[index]}#{CODES[:exit_standout_mode]}"
94
- else
95
- tty.print @options[index]
96
- end
97
- end
98
-
99
- # @param [Fixnum] index
100
- def highlight_line(index)
101
- print_line(@highlighted_line_index, false)
102
- print_line(index, true)
103
-
104
- @highlighted_line_index = index
105
- end
106
-
107
- # @param [Fixnum] index
108
- def go_to_line(index)
109
- if index == @cursor_line_index
110
- # do nothing
111
- elsif index > @cursor_line_index
112
- (index - @cursor_line_index).times { tty.print CODES[:cursor_down] }
113
- else
114
- (@cursor_line_index - index).times { tty.print CODES[:cursor_up] }
115
- end
116
-
117
- @cursor_line_index = index
118
- tty.print CODES[:carriage_return_key]
119
- end
120
- end
121
- end
@@ -1,5 +0,0 @@
1
- module Ansi
2
- class Select
3
- VERSION = "0.1.0"
4
- end
5
- end