madCLIbs 0.0.2
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 +7 -0
- data/.gitignore +22 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +26 -0
- data/README.md +28 -0
- data/Rakefile +2 -0
- data/examples/basic.rb +6 -0
- data/examples/colors.rb +17 -0
- data/examples/scoped.rb +13 -0
- data/lib/madCLIbs/import.rb +10 -0
- data/lib/madCLIbs.rb +3 -0
- data/lib/mad_clibs/blanks/base.rb +43 -0
- data/lib/mad_clibs/blanks/mixins/color_handling.rb +50 -0
- data/lib/mad_clibs/blanks/mixins/default_key_handling.rb +35 -0
- data/lib/mad_clibs/blanks/mixins/value_buffer_delegate.rb +34 -0
- data/lib/mad_clibs/blanks/string.rb +23 -0
- data/lib/mad_clibs/blanks.rb +3 -0
- data/lib/mad_clibs/line.rb +66 -0
- data/lib/mad_clibs/prompter.rb +105 -0
- data/lib/mad_clibs/util/iohelper.rb +134 -0
- data/lib/mad_clibs/util/value_buffer.rb +37 -0
- data/lib/mad_clibs/version.rb +3 -0
- data/lib/mad_clibs.rb +19 -0
- data/madCLIbs.gemspec +31 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0c3ce55fec8d9b38d33c8eb9d4d716281c1b6bc2
|
4
|
+
data.tar.gz: 75dfe916d9f51d70a2c521aff89c1360772422ac
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ae10344a49778297444e83391b9e681e0d95e1dd2eb37efcb0b2c016c01bceaa1bb1313716b8fae0649736051e8c0a49b8f975e67eff65fdd19106253e10f4cf
|
7
|
+
data.tar.gz: 476a18157930971186915a5c57a548adb4d21740c89bfaeaa2509375e6148d0f3425864a8fc2d88b3786deb93e081b308cc3e5d9478de283d66b273f5a0909e5
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Except where indicated in header comments, this work is
|
2
|
+
|
3
|
+
Copyright (c) 2015 Donald Guy
|
4
|
+
|
5
|
+
and licensed under the terms of the
|
6
|
+
|
7
|
+
MIT License
|
8
|
+
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
a copy of this software and associated documentation files (the
|
11
|
+
"Software"), to deal in the Software without restriction, including
|
12
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
permit persons to whom the Software is furnished to do so, subject to
|
15
|
+
the following conditions:
|
16
|
+
|
17
|
+
The above copyright notice and this permission notice shall be
|
18
|
+
included in all copies or substantial portions of the Software.
|
19
|
+
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
23
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
24
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
25
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# MadCLI</tt>bs:<br> User-Friendly Fill-in-the-Blank(s) UX for your Terminal Application
|
2
|
+
|
3
|
+
Seeks to mimic the familiar UX of HTML5 form elements, with
|
4
|
+
[placeholders](http://diveintohtml5.info/forms.html#placeholder),
|
5
|
+
in the terminal.
|
6
|
+
|
7
|
+
Allows a program to present a line with one or more "blanks" which can be edited and moved between
|
8
|
+
at will (with tab or up & down arrow keys). On new line, returns the values of the blanks (treating
|
9
|
+
placeholders as a default values)
|
10
|
+
|
11
|
+
The name is supposed to be Mad Libs with a CLI in it, but you might also
|
12
|
+
reasonably call it "Mad CLI BS" :smile:
|
13
|
+
|
14
|
+
You could, in fact, use it to make a sweet Mad Libs app, but its also
|
15
|
+
pretty useful for things like [Dramaturg](https://github.com/donaldguy/dramaturg)
|
16
|
+
(for which it was created)
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
See [examples](https://github.com/donaldguy/madCLIbs/tree/master/examples)
|
21
|
+
|
22
|
+
## Contributing
|
23
|
+
|
24
|
+
1. Fork it ( https://github.com/donaldguy/madCLIbs/fork )
|
25
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
26
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
27
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
28
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/examples/basic.rb
ADDED
data/examples/colors.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'madCLIbs/import'
|
2
|
+
require 'term/ansicolor'
|
3
|
+
|
4
|
+
include Term::ANSIColor
|
5
|
+
|
6
|
+
prompt(red("Tokens"), string("are"), red("styled"), string(green(bold("individually"))))
|
7
|
+
|
8
|
+
prompt("Placeholders can have", cyan(string("the same style as a typed value")))
|
9
|
+
|
10
|
+
prompt("Or a", red(string(cyan("different one"))))
|
11
|
+
prompt(green("Including"), red(string(reset("none"))))
|
12
|
+
|
13
|
+
prompt(bold, "You can also used unscoped colors", red,
|
14
|
+
string(cyan("but")), "they leak til next reset", reset )
|
15
|
+
|
16
|
+
#you cannot/should not try to wrap multiple tokens in a scope tho
|
17
|
+
# none of this: prompt( bold("Question":, string("answer") ) )
|
data/examples/scoped.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'madCLIbs'
|
2
|
+
require 'term/ansicolor'
|
3
|
+
|
4
|
+
$cli = MadCLIbs.new
|
5
|
+
$color = Term::ANSIColor
|
6
|
+
|
7
|
+
s1, s2 = $cli.prompt($color.bold,
|
8
|
+
"Should also work without",
|
9
|
+
$color.cyan($cli.string("polluting too many")),
|
10
|
+
$color.red($cli.string("namespaces")),
|
11
|
+
$color.reset)
|
12
|
+
|
13
|
+
puts "Field values s1=#{s1}, s2=#{s2}"
|
data/lib/madCLIbs.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative 'mixins/color_handling'
|
2
|
+
require_relative 'mixins/default_key_handling'
|
3
|
+
|
4
|
+
class MadClibs
|
5
|
+
module Blanks
|
6
|
+
class Base
|
7
|
+
|
8
|
+
attr_reader :start_value, :completions
|
9
|
+
attr_accessor :value, :position
|
10
|
+
def length; self.value.length; end
|
11
|
+
def empty?; self.value.length <= 0; end
|
12
|
+
|
13
|
+
def render
|
14
|
+
raise NotImplementedEror, "render called on abstract superclass D:"
|
15
|
+
end
|
16
|
+
|
17
|
+
def post_render
|
18
|
+
true # no-op
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?
|
22
|
+
raise NotImplementedEror, "valid? called on abstract superclass D:"
|
23
|
+
end
|
24
|
+
|
25
|
+
def placeholder_showing?
|
26
|
+
@placeholder_showing = true unless defined? @placeholder_showing
|
27
|
+
@placeholder_showing
|
28
|
+
end
|
29
|
+
|
30
|
+
def key(key)
|
31
|
+
if placeholder_showing?
|
32
|
+
default_key_placeholder_showing(key)
|
33
|
+
else #no placeholder showing
|
34
|
+
default_key_no_placeholder(key)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
include Mixins::ColorHandling
|
40
|
+
include Mixins::DefaultKeyHandling
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'term/ansicolor'
|
2
|
+
|
3
|
+
module Term::ANSIColor
|
4
|
+
alias old_color color
|
5
|
+
def color(name, string = nil, &block)
|
6
|
+
if string.respond_to? :save_and_remove_color
|
7
|
+
string.send :save_and_remove_color, old_color(name, string.value)
|
8
|
+
string
|
9
|
+
else
|
10
|
+
old_color(name, string, &block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class MadClibs
|
16
|
+
module Blanks::Mixins
|
17
|
+
module ColorHandling
|
18
|
+
include Term::ANSIColor
|
19
|
+
|
20
|
+
def save_and_remove_start_color(s)
|
21
|
+
@colored_placeholder = s
|
22
|
+
stripped = uncolor(s)
|
23
|
+
|
24
|
+
@placeholder_inherits_style = (@colored_placeholder == stripped)
|
25
|
+
stripped
|
26
|
+
end
|
27
|
+
|
28
|
+
def save_and_remove_color(s)
|
29
|
+
colored = s
|
30
|
+
uncolored = uncolor(s)
|
31
|
+
@color_seed = colored.sub(%r'#{uncolored}', '%{value}')
|
32
|
+
uncolored
|
33
|
+
end
|
34
|
+
|
35
|
+
def restore_color(v)
|
36
|
+
if placeholder_showing? and !@placeholder_inherits_style
|
37
|
+
@colored_placeholder
|
38
|
+
elsif @color_seed
|
39
|
+
@color_seed % {value: v}
|
40
|
+
else
|
41
|
+
value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_str
|
46
|
+
value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class MadClibs
|
2
|
+
module Blanks::Mixins::DefaultKeyHandling
|
3
|
+
def default_key_placeholder_showing(key)
|
4
|
+
case key
|
5
|
+
when"backspace"
|
6
|
+
#toggle visible display of value; leave behavior the same
|
7
|
+
#whether placeholder visible or not
|
8
|
+
if self.value == start_value
|
9
|
+
self.value = ""
|
10
|
+
else
|
11
|
+
self.value = start_value
|
12
|
+
end
|
13
|
+
when "left", "right"
|
14
|
+
#ignore it
|
15
|
+
else # normal character -> start writing in
|
16
|
+
self.value = ""
|
17
|
+
value_key(key)
|
18
|
+
@placeholder_showing = false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def default_key_no_placeholder(key)
|
23
|
+
case key
|
24
|
+
when"backspace"
|
25
|
+
value_key(key)
|
26
|
+
if self.value.empty?
|
27
|
+
self.value = start_value
|
28
|
+
@placeholder_showing = true
|
29
|
+
end
|
30
|
+
else
|
31
|
+
value_key(key)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative '../../util/value_buffer'
|
2
|
+
|
3
|
+
class MadClibs
|
4
|
+
module Blanks::Mixins::ValueBufferDelegate
|
5
|
+
def value
|
6
|
+
@valuebuff ||= ValueBuffer.new(save_and_remove_start_color(start_value))
|
7
|
+
@valuebuff.value
|
8
|
+
end
|
9
|
+
|
10
|
+
def value=(v)
|
11
|
+
@valuebuff ||= ValueBuffer.new(save_and_remove_color(v))
|
12
|
+
@valuebuff.value = v
|
13
|
+
end
|
14
|
+
|
15
|
+
def value_key(key)
|
16
|
+
case key
|
17
|
+
when "backspace"
|
18
|
+
@valuebuff.backspace
|
19
|
+
when "left"
|
20
|
+
@valuebuff.left
|
21
|
+
when "right"
|
22
|
+
@valuebuff.right
|
23
|
+
when /[\w\s]/
|
24
|
+
@valuebuff << key
|
25
|
+
else
|
26
|
+
raise ArgumentError, "No handler for key '#{key}' (while focused on this blank)"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def position
|
31
|
+
@valuebuff.position > 0 ? @valuebuff.position : 0
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require_relative 'mixins/value_buffer_delegate'
|
3
|
+
|
4
|
+
class MadClibs
|
5
|
+
module Blanks
|
6
|
+
class String < Base
|
7
|
+
include Mixins::ValueBufferDelegate
|
8
|
+
|
9
|
+
def initialize(s)
|
10
|
+
@start_value = s.to_s
|
11
|
+
@completions = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def render
|
15
|
+
restore_color(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
!empty?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'term/ansicolor'
|
2
|
+
|
3
|
+
class MadCLIbs
|
4
|
+
class Line
|
5
|
+
def initialize(*tokens)
|
6
|
+
@tokens = []
|
7
|
+
@blanks = []
|
8
|
+
|
9
|
+
tokens.each { |t| self << t }
|
10
|
+
squash_colors
|
11
|
+
end
|
12
|
+
|
13
|
+
def <<(token)
|
14
|
+
@tokens << token
|
15
|
+
if token_is_blank? token
|
16
|
+
@blanks << token
|
17
|
+
end
|
18
|
+
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def values
|
23
|
+
values = @blanks.map &:value
|
24
|
+
values.length <= 1? values.first : values
|
25
|
+
end
|
26
|
+
|
27
|
+
def token_is_blank?(token)
|
28
|
+
token.respond_to?(:value) && token.respond_to?(:value=)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Add bare color tokens to the end (or failing that beginning)
|
32
|
+
# of adjacent strings; fixes spacing when using bare colors
|
33
|
+
def squash_colors
|
34
|
+
types = @tokens.map do |t|
|
35
|
+
if token_is_blank? t
|
36
|
+
:blank
|
37
|
+
elsif Term::ANSIColor.uncolored(t).empty?
|
38
|
+
:color
|
39
|
+
else
|
40
|
+
:string
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@tokens.size.times do |i|
|
45
|
+
case types[i]
|
46
|
+
when :color
|
47
|
+
if i != 0 and types[i-1] == :string
|
48
|
+
@tokens[i-1] = @tokens[i-1] + @tokens[i]
|
49
|
+
@tokens[i] = nil
|
50
|
+
i += 1
|
51
|
+
elsif i == @tokens.size - 1
|
52
|
+
# its fine by itself.
|
53
|
+
elsif types[i+1] == :string
|
54
|
+
@tokens[i+1] = @tokens[i] + @tokens[i+1]
|
55
|
+
@tokens[i] = nil
|
56
|
+
i += 1
|
57
|
+
end
|
58
|
+
else
|
59
|
+
#nothing
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
@tokens = @tokens.reject { |t| t.nil? }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require_relative 'util/iohelper'
|
2
|
+
require 'stringio'
|
3
|
+
require 'term/ansicolor'
|
4
|
+
|
5
|
+
class MadClibs
|
6
|
+
class Prompter
|
7
|
+
def initialize
|
8
|
+
@io = IOHelper
|
9
|
+
reinit
|
10
|
+
end
|
11
|
+
|
12
|
+
def prompt(line)
|
13
|
+
reinit
|
14
|
+
@tokens = line.instance_variable_get(:@tokens)
|
15
|
+
@blanks = line.instance_variable_get(:@blanks)
|
16
|
+
@current_blank_index = 0
|
17
|
+
|
18
|
+
until done?
|
19
|
+
render
|
20
|
+
place_cursor
|
21
|
+
getc
|
22
|
+
end
|
23
|
+
|
24
|
+
post_render
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def reinit
|
29
|
+
@last_char = nil
|
30
|
+
@tokens = nil
|
31
|
+
@blanks = nil
|
32
|
+
@active_blank = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def done?
|
36
|
+
@last_char == 'return'
|
37
|
+
end
|
38
|
+
|
39
|
+
def render
|
40
|
+
buffer = StringIO.new
|
41
|
+
|
42
|
+
@tokens.each do |token|
|
43
|
+
if token.respond_to? :render
|
44
|
+
buffer.print token.render
|
45
|
+
elsif token.respond_to? :to_s
|
46
|
+
buffer.print token
|
47
|
+
end
|
48
|
+
|
49
|
+
buffer.print " "
|
50
|
+
end
|
51
|
+
|
52
|
+
@io.rerender buffer.string
|
53
|
+
end
|
54
|
+
|
55
|
+
def getc
|
56
|
+
@last_char = @io.read_key
|
57
|
+
case @last_char
|
58
|
+
when "up"
|
59
|
+
previous_blank!
|
60
|
+
when "tab", "down"
|
61
|
+
#if @active_blank.has_completions?
|
62
|
+
#end
|
63
|
+
next_blank!
|
64
|
+
when "return", "linefeed"
|
65
|
+
@last_char = "return"
|
66
|
+
else
|
67
|
+
active_blank.key(@last_char)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def place_cursor
|
72
|
+
print @io.carriage_return
|
73
|
+
|
74
|
+
@tokens.each do |t|
|
75
|
+
if t == active_blank
|
76
|
+
print @io.char_right*t.position
|
77
|
+
break
|
78
|
+
end
|
79
|
+
print @io.char_right*(Term::ANSIColor.uncolored(t).length+1) # + 1 for space
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def post_render
|
84
|
+
@tokens.each do |token|
|
85
|
+
token.post_render if token.respond_to? :post_render
|
86
|
+
end
|
87
|
+
render
|
88
|
+
puts ""
|
89
|
+
end
|
90
|
+
|
91
|
+
def next_blank!
|
92
|
+
@current_blank_index += 1
|
93
|
+
@current_blank_index = 0 if @current_blank_index >= @blanks.length
|
94
|
+
end
|
95
|
+
|
96
|
+
def previous_blank!
|
97
|
+
@current_blank_index -= 1
|
98
|
+
@current_blank_index = @blanks.length - 1 if @current_blank_index < 0
|
99
|
+
end
|
100
|
+
|
101
|
+
def active_blank
|
102
|
+
@blanks[@current_blank_index]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# Copied initially from github.com/arlimus/inquirer.rb (http://git.io/N6xT)
|
2
|
+
# Copyright Dominik Richter, licensed under Apache v2 (http://git.io/N6xC)
|
3
|
+
#
|
4
|
+
# Additional changes by Donald Guy - see:
|
5
|
+
# https://github.com/donaldguy/madCLIbs/blame/master/lib/mad_clibs/util/iohelper.rb
|
6
|
+
|
7
|
+
require 'io/console'
|
8
|
+
|
9
|
+
module IOHelper
|
10
|
+
extend self
|
11
|
+
|
12
|
+
@rendered = ""
|
13
|
+
|
14
|
+
KEYS = {
|
15
|
+
"\t" => "tab",
|
16
|
+
"\r" => "return",
|
17
|
+
"\n" => "linefeed",
|
18
|
+
# "\e" => "escape",
|
19
|
+
"\e[A" => "up",
|
20
|
+
"\e[B" => "down",
|
21
|
+
"\e[C" => "right",
|
22
|
+
"\e[D" => "left",
|
23
|
+
"\177" => "backspace",
|
24
|
+
# ctrl + c
|
25
|
+
"\003" => "ctrl-c",
|
26
|
+
# ctrl + d
|
27
|
+
"\004" => "ctrl-d"
|
28
|
+
}
|
29
|
+
|
30
|
+
# Read a character the user enters on console. This call is synchronous blocking.
|
31
|
+
# This is taken from: http://www.alecjacobson.com/weblog/?p=75
|
32
|
+
def read_char
|
33
|
+
begin
|
34
|
+
# save previous state of stty
|
35
|
+
old_state = `stty -g`
|
36
|
+
# disable echoing and enable raw (not having to press enter)
|
37
|
+
system "stty raw -echo"
|
38
|
+
c = STDIN.getc.chr
|
39
|
+
# gather next two characters of special keys
|
40
|
+
if(c=="\e")
|
41
|
+
extra_thread = Thread.new{
|
42
|
+
c = c + STDIN.getc.chr
|
43
|
+
c = c + STDIN.getc.chr
|
44
|
+
}
|
45
|
+
# wait just long enough for special keys to get swallowed
|
46
|
+
extra_thread.join(0.00001)
|
47
|
+
# kill thread so not-so-long special keys don't wait on getc
|
48
|
+
extra_thread.kill
|
49
|
+
end
|
50
|
+
rescue => ex
|
51
|
+
puts "#{ex.class}: #{ex.message}"
|
52
|
+
puts ex.backtrace
|
53
|
+
ensure
|
54
|
+
# restore previous state of stty
|
55
|
+
system "stty #{old_state}"
|
56
|
+
end
|
57
|
+
return c
|
58
|
+
end
|
59
|
+
|
60
|
+
# Read a keypress on console. Return the key name (e.g. "space", "a", "B")
|
61
|
+
# Params:
|
62
|
+
# +with_exit_codes+:: +Bool+ whether to throw Interrupts when the user presses
|
63
|
+
# ctrl-c and ctrl-d. (true by default)
|
64
|
+
def read_key with_exit_codes = true, return_char = false
|
65
|
+
char = read_char
|
66
|
+
raise Interrupt if with_exit_codes and ( char == "\003" or char == "\004" )
|
67
|
+
if return_char then char else char_to_raw char end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Get each key the user presses and hand it one by one to the block. Do this
|
71
|
+
# as long as the block returns truthy
|
72
|
+
# Params:
|
73
|
+
# +&block+:: +Proc+ a block that receives a user key and returns truthy or falsy
|
74
|
+
def read_key_while return_char = false, &block
|
75
|
+
STDIN.noecho do
|
76
|
+
# as long as the block doen't return falsy,
|
77
|
+
# read the user input key and sned it to the block
|
78
|
+
while block.( IOHelper.read_key true, return_char )
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Get the console window size
|
84
|
+
# Returns: [width, height]
|
85
|
+
def winsize
|
86
|
+
STDIN.winsize
|
87
|
+
end
|
88
|
+
|
89
|
+
# Render a text to the prompt
|
90
|
+
def render(s)
|
91
|
+
@rendered = s
|
92
|
+
print s
|
93
|
+
end
|
94
|
+
|
95
|
+
# Clear the prompt and render the update
|
96
|
+
def rerender(s)
|
97
|
+
clear
|
98
|
+
render s
|
99
|
+
end
|
100
|
+
|
101
|
+
# clear the console based on the last text rendered
|
102
|
+
def clear
|
103
|
+
# get console window height and width
|
104
|
+
h,w = IOHelper.winsize
|
105
|
+
# determine how many lines to move up
|
106
|
+
n = @rendered.scan(/\n/).length
|
107
|
+
# jump back to the first position and clear the line
|
108
|
+
print carriage_return + ( line_up + clear_line ) * n + clear_line
|
109
|
+
end
|
110
|
+
|
111
|
+
# hides the cursor and ensure the curso be visible at the end
|
112
|
+
def without_cursor
|
113
|
+
# tell the terminal to hide the cursor
|
114
|
+
print `tput civis`
|
115
|
+
begin
|
116
|
+
# run the block
|
117
|
+
yield
|
118
|
+
ensure
|
119
|
+
# tell the terminal to show the cursor
|
120
|
+
print `tput cnorm`
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def char_to_raw char
|
125
|
+
KEYS.fetch char, char
|
126
|
+
end
|
127
|
+
|
128
|
+
def carriage_return; "\r" end
|
129
|
+
def line_up; "\e[A" end
|
130
|
+
def clear_line; "\e[0K" end
|
131
|
+
def char_left; "\e[D" end
|
132
|
+
def char_right; "\e[C" end
|
133
|
+
|
134
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class ValueBuffer
|
2
|
+
attr_reader :position
|
3
|
+
|
4
|
+
def initialize(s="")
|
5
|
+
self.value = s
|
6
|
+
end
|
7
|
+
|
8
|
+
def left
|
9
|
+
@position -= 1 if @position >= 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def right
|
13
|
+
@position += 1 if @position < value.length
|
14
|
+
end
|
15
|
+
|
16
|
+
def backspace
|
17
|
+
@buffer = @buffer[0..-2]
|
18
|
+
left
|
19
|
+
end
|
20
|
+
|
21
|
+
def << c
|
22
|
+
if @position < 0
|
23
|
+
@position = 0
|
24
|
+
end
|
25
|
+
@buffer.insert(@position,c)
|
26
|
+
@position += 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def value=(s)
|
30
|
+
@buffer = s.dup
|
31
|
+
@position = -1
|
32
|
+
end
|
33
|
+
|
34
|
+
def value
|
35
|
+
@buffer
|
36
|
+
end
|
37
|
+
end
|
data/lib/mad_clibs.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
class MadClibs
|
2
|
+
autoload :Line, 'mad_clibs/line'
|
3
|
+
autoload :Blanks, 'mad_clibs/blanks'
|
4
|
+
autoload :Prompter, 'mad_clibs/prompter'
|
5
|
+
|
6
|
+
def initialize()
|
7
|
+
@prompter = Prompter.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def prompt(*args)
|
11
|
+
l = Line.new(*args)
|
12
|
+
@prompter.prompt(l)
|
13
|
+
l.values()
|
14
|
+
end
|
15
|
+
|
16
|
+
def string(val)
|
17
|
+
Blanks::String.new(val)
|
18
|
+
end
|
19
|
+
end
|
data/madCLIbs.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mad_clibs/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "madCLIbs"
|
8
|
+
spec.version = MadClibs::VERSION
|
9
|
+
spec.authors = ["Donald Guy"]
|
10
|
+
spec.email = ["fawkes@mit.edu"]
|
11
|
+
spec.summary = %q{User-friendly fill-in-the-blank CLI forms (think HTML5 with placeholders) }
|
12
|
+
spec.description = <<-EOS
|
13
|
+
Seeks to mimic the familiar UX of HTML5 form elements, with placeholder attributes,
|
14
|
+
in an ANSI-compatible terminal.
|
15
|
+
|
16
|
+
Allows a program to present a line with one or more "blanks" which can be edited and moved between
|
17
|
+
at will (with tab or up & down arrow keys). On new line, returns the values of the blanks (treating
|
18
|
+
placeholders as a default values if no editing occured)
|
19
|
+
EOS
|
20
|
+
spec.homepage = "https://github.com/donaldguy/madCLIbs"
|
21
|
+
spec.license = "MIT"
|
22
|
+
|
23
|
+
spec.files = `git ls-files -z`.split("\x0")
|
24
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
25
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_dependency "term-ansicolor", "~> 1.1"
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: madCLIbs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Donald Guy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: term-ansicolor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
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.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
description: |2
|
42
|
+
Seeks to mimic the familiar UX of HTML5 form elements, with placeholder attributes,
|
43
|
+
in an ANSI-compatible terminal.
|
44
|
+
|
45
|
+
Allows a program to present a line with one or more "blanks" which can be edited and moved between
|
46
|
+
at will (with tab or up & down arrow keys). On new line, returns the values of the blanks (treating
|
47
|
+
placeholders as a default values if no editing occured)
|
48
|
+
email:
|
49
|
+
- fawkes@mit.edu
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- ".gitignore"
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE.txt
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- examples/basic.rb
|
60
|
+
- examples/colors.rb
|
61
|
+
- examples/scoped.rb
|
62
|
+
- lib/madCLIbs.rb
|
63
|
+
- lib/madCLIbs/import.rb
|
64
|
+
- lib/mad_clibs.rb
|
65
|
+
- lib/mad_clibs/blanks.rb
|
66
|
+
- lib/mad_clibs/blanks/base.rb
|
67
|
+
- lib/mad_clibs/blanks/mixins/color_handling.rb
|
68
|
+
- lib/mad_clibs/blanks/mixins/default_key_handling.rb
|
69
|
+
- lib/mad_clibs/blanks/mixins/value_buffer_delegate.rb
|
70
|
+
- lib/mad_clibs/blanks/string.rb
|
71
|
+
- lib/mad_clibs/line.rb
|
72
|
+
- lib/mad_clibs/prompter.rb
|
73
|
+
- lib/mad_clibs/util/iohelper.rb
|
74
|
+
- lib/mad_clibs/util/value_buffer.rb
|
75
|
+
- lib/mad_clibs/version.rb
|
76
|
+
- madCLIbs.gemspec
|
77
|
+
homepage: https://github.com/donaldguy/madCLIbs
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.2.2
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: User-friendly fill-in-the-blank CLI forms (think HTML5 with placeholders)
|
101
|
+
test_files: []
|