remedy 0.0.3.pre
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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.markdown +33 -0
- data/Rakefile +1 -0
- data/lib/remedy.rb +9 -0
- data/lib/remedy/ansi.rb +207 -0
- data/lib/remedy/characters.rb +155 -0
- data/lib/remedy/console.rb +61 -0
- data/lib/remedy/console_resized.rb +46 -0
- data/lib/remedy/content.rb +4 -0
- data/lib/remedy/footer.rb +7 -0
- data/lib/remedy/header.rb +7 -0
- data/lib/remedy/interaction.rb +65 -0
- data/lib/remedy/key.rb +117 -0
- data/lib/remedy/keyboard.rb +66 -0
- data/lib/remedy/partial.rb +70 -0
- data/lib/remedy/version.rb +3 -0
- data/lib/remedy/view.rb +43 -0
- data/lib/remedy/viewport.rb +58 -0
- data/remedy.gemspec +19 -0
- metadata +68 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Anthony Cook
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
Remedy
|
2
|
+
======
|
3
|
+
|
4
|
+
Remedy is a console interaction framework along the lines of Curses written in pure Ruby with an Object-Oriented approach and baked-in support for bulding MVC applications.
|
5
|
+
|
6
|
+
Installation
|
7
|
+
------------
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'remedy'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install remedy
|
20
|
+
|
21
|
+
Usage
|
22
|
+
-----
|
23
|
+
|
24
|
+
TODO: Write usage instructions here
|
25
|
+
|
26
|
+
Contributing
|
27
|
+
------------
|
28
|
+
|
29
|
+
1. Fork it
|
30
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
31
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
32
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
33
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/remedy.rb
ADDED
data/lib/remedy/ansi.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'remedy/console'
|
2
|
+
|
3
|
+
module Remedy
|
4
|
+
module ANSI
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def push *sequences
|
8
|
+
Console.output << sequences.join('')
|
9
|
+
end
|
10
|
+
|
11
|
+
def pushesc *sequences
|
12
|
+
push sequences.map{|sequence| esc sequence }
|
13
|
+
end
|
14
|
+
|
15
|
+
def pushe *sequences
|
16
|
+
push sequences.map{|sequence| e sequence }
|
17
|
+
end
|
18
|
+
|
19
|
+
def esc sequence = nil
|
20
|
+
"\e[#{sequence}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def e sequence = nil
|
24
|
+
"\e#{sequence}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def cursor
|
28
|
+
Cursor
|
29
|
+
end
|
30
|
+
|
31
|
+
def screen
|
32
|
+
Screen
|
33
|
+
end
|
34
|
+
|
35
|
+
def command
|
36
|
+
Command
|
37
|
+
end
|
38
|
+
|
39
|
+
def color
|
40
|
+
Color
|
41
|
+
end
|
42
|
+
|
43
|
+
module Command
|
44
|
+
extend ANSI
|
45
|
+
|
46
|
+
module_function
|
47
|
+
|
48
|
+
def code
|
49
|
+
{
|
50
|
+
clear_line: 'K',
|
51
|
+
|
52
|
+
clear_down: 'J',
|
53
|
+
clear_up: '1J',
|
54
|
+
clear_screen: '2J'
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def clear_line!
|
59
|
+
pushesc code[:clear_line]
|
60
|
+
end
|
61
|
+
|
62
|
+
def clear_up!
|
63
|
+
pushesc code[:clear_up]
|
64
|
+
end
|
65
|
+
|
66
|
+
def clear_down!
|
67
|
+
pushesc code[:clear_down]
|
68
|
+
end
|
69
|
+
|
70
|
+
def clear_screen!
|
71
|
+
pushesc code[:clear_screen]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module Cursor
|
76
|
+
extend ANSI
|
77
|
+
|
78
|
+
module_function
|
79
|
+
|
80
|
+
def code
|
81
|
+
{
|
82
|
+
# Movement
|
83
|
+
home: 'H',
|
84
|
+
up: '%{lines}A',
|
85
|
+
down: '%{lines}B',
|
86
|
+
to_column: '%{column}G',
|
87
|
+
|
88
|
+
# May not work on all terminals.
|
89
|
+
next_line: '%{lines}E',
|
90
|
+
prev_line: '%{lines}F',
|
91
|
+
|
92
|
+
# Visiblity
|
93
|
+
show: '?25h',
|
94
|
+
hide: '?25l'
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_column column
|
99
|
+
esc code[:to_column] % {column: column}
|
100
|
+
end
|
101
|
+
|
102
|
+
def down lines = 1
|
103
|
+
esc code[:down] % {lines: lines}
|
104
|
+
end
|
105
|
+
|
106
|
+
def up lines = 1
|
107
|
+
esc code[:up] % {lines: lines}
|
108
|
+
end
|
109
|
+
|
110
|
+
def next_line lines = 1
|
111
|
+
#esc code[:next_line] % {lines: lines}
|
112
|
+
down(lines) + to_column(0)
|
113
|
+
end
|
114
|
+
|
115
|
+
def prev_line lines = 1
|
116
|
+
#esc code[:prev_line] % {lines: lines}
|
117
|
+
up(lines) + to_column(0)
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
def home!
|
122
|
+
pushesc code[:home]
|
123
|
+
end
|
124
|
+
|
125
|
+
def hide!
|
126
|
+
pushesc code[:hide]
|
127
|
+
end
|
128
|
+
|
129
|
+
def show!
|
130
|
+
pushesc code[:show]
|
131
|
+
end
|
132
|
+
|
133
|
+
def next_line! lines = 1
|
134
|
+
push next_line(lines)
|
135
|
+
end
|
136
|
+
|
137
|
+
def prev_line! lines = 1
|
138
|
+
push prev_line(lines)
|
139
|
+
end
|
140
|
+
|
141
|
+
def beginning_of_line!
|
142
|
+
to_column! 0
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
module Screen
|
148
|
+
extend ANSI
|
149
|
+
|
150
|
+
module_function
|
151
|
+
|
152
|
+
def code
|
153
|
+
{
|
154
|
+
up: 'M',
|
155
|
+
down: 'D'
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
def reset!
|
160
|
+
ANSI.color.reset!
|
161
|
+
ANSI.cursor.home!
|
162
|
+
clear!
|
163
|
+
end
|
164
|
+
|
165
|
+
def safe_reset!
|
166
|
+
ANSI.cursor.home!
|
167
|
+
ANSI.command.clear_down!
|
168
|
+
end
|
169
|
+
|
170
|
+
def clear!
|
171
|
+
ANSI.command.clear_screen!
|
172
|
+
end
|
173
|
+
|
174
|
+
def up! count = 1
|
175
|
+
count.times { pushe code[:up] }
|
176
|
+
end
|
177
|
+
|
178
|
+
def down! count = 1
|
179
|
+
count.times { pushe code[:down] }
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
module Color
|
184
|
+
extend ANSI
|
185
|
+
|
186
|
+
module_function
|
187
|
+
|
188
|
+
def code
|
189
|
+
{
|
190
|
+
reset: '0'
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
def pushc *sequences
|
195
|
+
push sequences.map{|sequence| c sequence }
|
196
|
+
end
|
197
|
+
|
198
|
+
def c sequence
|
199
|
+
"#{esc sequence}m"
|
200
|
+
end
|
201
|
+
|
202
|
+
def reset!
|
203
|
+
pushc code[:reset]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module Remedy
|
2
|
+
module Characters
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def [] sequence_to_match
|
6
|
+
all[sequence_to_match]
|
7
|
+
end
|
8
|
+
|
9
|
+
def all
|
10
|
+
@all ||= printable.merge(nonprintable)
|
11
|
+
end
|
12
|
+
|
13
|
+
def printable
|
14
|
+
@printable ||= whitespace.merge(
|
15
|
+
alphabetical.merge(
|
16
|
+
numeric.merge(
|
17
|
+
punctuation)))
|
18
|
+
end
|
19
|
+
|
20
|
+
def whitespace
|
21
|
+
{
|
22
|
+
' ' => :space,
|
23
|
+
"\t" => :tab,
|
24
|
+
"\r" => :carriage_return,
|
25
|
+
"\n" => :line_feed
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def alphabetical
|
30
|
+
@alphabetics ||= get_alphabetics
|
31
|
+
end
|
32
|
+
|
33
|
+
def numeric
|
34
|
+
{
|
35
|
+
'0' => :zero,
|
36
|
+
'1' => :one,
|
37
|
+
'2' => :two,
|
38
|
+
'3' => :three,
|
39
|
+
'4' => :four,
|
40
|
+
'5' => :five,
|
41
|
+
'6' => :six,
|
42
|
+
'7' => :seven,
|
43
|
+
'8' => :eight,
|
44
|
+
'9' => :nine
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def punctuation
|
49
|
+
{
|
50
|
+
'.' => :period,
|
51
|
+
',' => :comma,
|
52
|
+
':' => :colon,
|
53
|
+
';' => :semicolon,
|
54
|
+
|
55
|
+
'"' => :double_quote,
|
56
|
+
"'" => :single_quote,
|
57
|
+
'`' => :back_quote,
|
58
|
+
|
59
|
+
'[' => :left_bracket,
|
60
|
+
']' => :right_bracked,
|
61
|
+
'(' => :left_paren,
|
62
|
+
')' => :right_paren,
|
63
|
+
|
64
|
+
'^' => :caret,
|
65
|
+
'_' => :underscore,
|
66
|
+
'-' => :dash,
|
67
|
+
'~' => :tilde,
|
68
|
+
|
69
|
+
'!' => :bang,
|
70
|
+
'?' => :query,
|
71
|
+
|
72
|
+
'|' => :solid_pipe,
|
73
|
+
"\u00A6" => :broken_pipe,
|
74
|
+
|
75
|
+
'/' => :forward_slash,
|
76
|
+
"\\" => :back_slash
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
def nonprintable
|
81
|
+
@nonprintable ||= special.merge(directional).merge(escape).merge(control)
|
82
|
+
end
|
83
|
+
|
84
|
+
def special
|
85
|
+
{
|
86
|
+
"\177" => :backspace
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def directional
|
91
|
+
{
|
92
|
+
"\e[A" => :up,
|
93
|
+
"\e[B" => :down,
|
94
|
+
"\e[D" => :left,
|
95
|
+
"\e[C" => :right
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
def escape
|
100
|
+
{
|
101
|
+
"\e" => :escape,
|
102
|
+
|
103
|
+
"\e[3~" => :delete
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
def control
|
108
|
+
{
|
109
|
+
"\u0003" => :control_c,
|
110
|
+
"\u0004" => :control_d,
|
111
|
+
"\u0012" => :control_r
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def gremlins
|
116
|
+
{
|
117
|
+
space: "\u2420",
|
118
|
+
tab: "\u21B9",
|
119
|
+
carriage_return: "\u23CE",
|
120
|
+
line_feed: "\u240A",
|
121
|
+
|
122
|
+
control_c: "\u2404",
|
123
|
+
control_d: "\u2403",
|
124
|
+
control_r: "\u2412",
|
125
|
+
|
126
|
+
escape: "\u238B",
|
127
|
+
|
128
|
+
backspace: "\u2408",
|
129
|
+
delete: "\u232B",
|
130
|
+
|
131
|
+
up: "\u2191",
|
132
|
+
down: "\u2193",
|
133
|
+
left: "\u2190",
|
134
|
+
right: "\u2192"
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
def alternate_names
|
139
|
+
{
|
140
|
+
control_c: :end_of_transmission,
|
141
|
+
control_d: :end_of_text,
|
142
|
+
|
143
|
+
control_r: :device_control_two
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
def get_alphabetics
|
148
|
+
letters = ('a'..'z').to_a + ('A'..'Z').to_a
|
149
|
+
letters.inject(Hash.new) do |alphas, letter|
|
150
|
+
alphas[letter] = letter.to_sym
|
151
|
+
alphas
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'remedy/console_resized'
|
2
|
+
|
3
|
+
module Remedy
|
4
|
+
module Console
|
5
|
+
require 'io/console'
|
6
|
+
|
7
|
+
TIOCGWINSZ = 0x40087468
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def input
|
12
|
+
STDIN
|
13
|
+
end
|
14
|
+
|
15
|
+
def output
|
16
|
+
STDOUT
|
17
|
+
end
|
18
|
+
|
19
|
+
def raw
|
20
|
+
raw!
|
21
|
+
result = yield
|
22
|
+
ensure
|
23
|
+
cooked!
|
24
|
+
|
25
|
+
return result
|
26
|
+
end
|
27
|
+
|
28
|
+
def raw!
|
29
|
+
input.echo = false
|
30
|
+
input.raw!
|
31
|
+
end
|
32
|
+
|
33
|
+
def cooked!
|
34
|
+
input.echo = true
|
35
|
+
input.cooked!
|
36
|
+
rescue NoMethodError
|
37
|
+
%x{stty -raw echo 2> /dev/null}
|
38
|
+
end
|
39
|
+
|
40
|
+
def size
|
41
|
+
str = [0, 0, 0, 0].pack('SSSS')
|
42
|
+
if input.ioctl(TIOCGWINSZ, str) >= 0 then
|
43
|
+
str.unpack('SSSS').first 2
|
44
|
+
else
|
45
|
+
raise UnknownConsoleSize, "Unable to get console size"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def interactive?
|
50
|
+
input.isatty
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_console_resized_hook!
|
54
|
+
ConsoleResized.set_console_resized_hook! do
|
55
|
+
yield
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class UnknownConsoleSize < IOError; end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Remedy
|
2
|
+
module ConsoleResized
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def resizing?
|
6
|
+
@resize_count > 0
|
7
|
+
end
|
8
|
+
|
9
|
+
def resizing!
|
10
|
+
@resize_count = @resize_count < 1 ? 1 : @resize_count + 1
|
11
|
+
end
|
12
|
+
|
13
|
+
def resized?
|
14
|
+
@resize_count <= 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def resized!
|
18
|
+
@resize_count = @resize_count < 0 ? 0 : @resize_count - 1
|
19
|
+
end
|
20
|
+
|
21
|
+
def resizer?
|
22
|
+
@resize_count == 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_console_resized_hook!
|
26
|
+
@resize_count = 0
|
27
|
+
|
28
|
+
command = lambda { |x|
|
29
|
+
resizing!
|
30
|
+
sleep 0.25
|
31
|
+
|
32
|
+
if resized? then
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
|
36
|
+
resized!
|
37
|
+
}
|
38
|
+
|
39
|
+
Signal.trap 'SIGWINCH', command
|
40
|
+
end
|
41
|
+
|
42
|
+
def default_console_resized_hook!
|
43
|
+
Signal.trap 'SIGWINCH', 'DEFAULT'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'remedy/keyboard'
|
2
|
+
require 'remedy/ansi'
|
3
|
+
|
4
|
+
class Interaction
|
5
|
+
def initialize message = nil
|
6
|
+
@message = message
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :message
|
10
|
+
|
11
|
+
def confirm message = 'Confirm?'
|
12
|
+
ANSI.cursor.home!
|
13
|
+
ANSI.command.clear_line!
|
14
|
+
|
15
|
+
print message, ' y/n '
|
16
|
+
if Keyboard.get === :y then
|
17
|
+
yield if block_given?
|
18
|
+
true
|
19
|
+
else
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def quit!
|
25
|
+
confirm 'Are you sure you want to quit? You will lose everything. :(' do
|
26
|
+
ANSI.cursor.home!
|
27
|
+
ANSI.command.clear_down!
|
28
|
+
ANSI.cursor.show!
|
29
|
+
|
30
|
+
puts " -- Bye!"
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def debug!
|
36
|
+
require 'pry'
|
37
|
+
binding.pry
|
38
|
+
end
|
39
|
+
|
40
|
+
def display key
|
41
|
+
ANSI.command.clear_line!
|
42
|
+
print " -- You pressed: #{key.inspect}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def loop
|
46
|
+
Keyboard.raise_on_control_c!
|
47
|
+
|
48
|
+
super do
|
49
|
+
print " -- #{message}" if message
|
50
|
+
|
51
|
+
ANSI.cursor.hide!
|
52
|
+
key = Keyboard.get
|
53
|
+
|
54
|
+
if key == ?\C-q then
|
55
|
+
display key
|
56
|
+
quit!
|
57
|
+
elsif key == ?\C-d and defined? Pry then
|
58
|
+
display key
|
59
|
+
debug!
|
60
|
+
end
|
61
|
+
|
62
|
+
yield key
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/remedy/key.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'remedy/characters'
|
2
|
+
|
3
|
+
module Remedy
|
4
|
+
class Key
|
5
|
+
|
6
|
+
def initialize character_sequence
|
7
|
+
@character_sequence = character_sequence
|
8
|
+
end
|
9
|
+
|
10
|
+
def seq
|
11
|
+
@character_sequence
|
12
|
+
end
|
13
|
+
|
14
|
+
def raw
|
15
|
+
seq
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
@name ||= Characters[seq] || :unknown
|
20
|
+
end
|
21
|
+
|
22
|
+
def glyph
|
23
|
+
@glyph ||= get_glyph
|
24
|
+
end
|
25
|
+
|
26
|
+
def printable?
|
27
|
+
@printable ||= !!Characters.printable[seq]
|
28
|
+
end
|
29
|
+
|
30
|
+
def nonprintable?
|
31
|
+
@nonprintable ||= !!Characters.nonprintable[seq]
|
32
|
+
end
|
33
|
+
|
34
|
+
def control?
|
35
|
+
@control ||= !!Characters.control[seq]
|
36
|
+
end
|
37
|
+
|
38
|
+
def punctuation?
|
39
|
+
@control ||= !!Characters.punctuation[seq]
|
40
|
+
end
|
41
|
+
|
42
|
+
def gremlin?
|
43
|
+
@gremlin ||= !!Characters.gremlins[name]
|
44
|
+
end
|
45
|
+
|
46
|
+
def control_c?
|
47
|
+
@control_c ||= seq == Characters.control.key(:control_c)
|
48
|
+
end
|
49
|
+
|
50
|
+
def recognized?
|
51
|
+
@recognized ||= name != :unknown
|
52
|
+
end
|
53
|
+
|
54
|
+
def single?
|
55
|
+
@single ||= raw.length == 1
|
56
|
+
end
|
57
|
+
|
58
|
+
def sequence?
|
59
|
+
@sequence ||= raw.legnth > 1
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
@to_s ||= name.to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
def value
|
67
|
+
raw_value = raw.bytes.to_a.join(' ')
|
68
|
+
single? ? raw_value : "(#{raw_value})"
|
69
|
+
end
|
70
|
+
|
71
|
+
def inspect
|
72
|
+
"<#{self.class} #{name.inspect} value:#{value} glyph:#{glyph}>"
|
73
|
+
end
|
74
|
+
|
75
|
+
def == key
|
76
|
+
if key.respond_to? :raw then
|
77
|
+
key.raw == raw
|
78
|
+
else
|
79
|
+
"#{raw}" == "#{key}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def eql? key
|
84
|
+
if key.is_a? self.class then
|
85
|
+
self == key
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def === object
|
90
|
+
"#{object}" =~ /#{to_s}/i
|
91
|
+
end
|
92
|
+
|
93
|
+
def hash
|
94
|
+
raw.hash
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
def get_glyph
|
100
|
+
if punctuation? then
|
101
|
+
seq
|
102
|
+
elsif gremlin? then
|
103
|
+
Characters.gremlins[name]
|
104
|
+
else
|
105
|
+
recognized? ? name : ''
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def Key object
|
111
|
+
if object.is_a? Key then
|
112
|
+
object
|
113
|
+
else
|
114
|
+
Key.new object
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'remedy/console'
|
2
|
+
require 'remedy/key'
|
3
|
+
|
4
|
+
module Keyboard
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def get
|
8
|
+
parse raw_get
|
9
|
+
end
|
10
|
+
|
11
|
+
def raw_get
|
12
|
+
Console.raw do
|
13
|
+
input = STDIN.getc.chr
|
14
|
+
|
15
|
+
if input == "\e" then
|
16
|
+
input << STDIN.read_nonblock(3) rescue nil
|
17
|
+
input << STDIN.read_nonblock(2) rescue nil
|
18
|
+
end
|
19
|
+
|
20
|
+
input
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse sequence
|
25
|
+
key = Key.new sequence
|
26
|
+
$log.debug "Key read", key
|
27
|
+
|
28
|
+
if raise_on_control_c? && key.control_c? then
|
29
|
+
raise ControlC, "User pressed Control-C"
|
30
|
+
elsif key.recognized? then
|
31
|
+
key
|
32
|
+
elsif raise_on_unrecognized_key? then
|
33
|
+
raise UnrecognizedInput, %Q{Unknown key or byte sequence: "#{sequence}" : #{key.inspect}}
|
34
|
+
else
|
35
|
+
key
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def raise_on_unrecognized_key?
|
40
|
+
@raise_on_unrecognized_key
|
41
|
+
end
|
42
|
+
|
43
|
+
def raise_on_unrecognized_key!
|
44
|
+
@raise_on_unrecognized_key = true
|
45
|
+
end
|
46
|
+
|
47
|
+
def dont_raise_on_unrecognized_key!
|
48
|
+
@raise_on_unrecognized_key = false
|
49
|
+
end
|
50
|
+
|
51
|
+
def raise_on_control_c?
|
52
|
+
@raise_on_control_c
|
53
|
+
end
|
54
|
+
|
55
|
+
def raise_on_control_c!
|
56
|
+
@raise_on_control_c = true
|
57
|
+
end
|
58
|
+
|
59
|
+
def dont_raise_on_control_c!
|
60
|
+
@raise_on_control_c = false
|
61
|
+
end
|
62
|
+
|
63
|
+
class ControlC < Interrupt; end
|
64
|
+
class UnrecognizedInput < IOError; end
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Remedy
|
2
|
+
class Partial
|
3
|
+
def initialize collection = Array.new
|
4
|
+
@lines = Array.new
|
5
|
+
self + collection
|
6
|
+
end
|
7
|
+
attr_accessor :lines
|
8
|
+
|
9
|
+
def + new_lines
|
10
|
+
new_lines.each do |line|
|
11
|
+
self << line
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def << line
|
16
|
+
reset_width!
|
17
|
+
@lines += clean line unless line.nil? || line.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
def first
|
21
|
+
lines.first
|
22
|
+
end
|
23
|
+
|
24
|
+
def last
|
25
|
+
lines.last
|
26
|
+
end
|
27
|
+
|
28
|
+
def length
|
29
|
+
lines.length
|
30
|
+
end
|
31
|
+
|
32
|
+
def width
|
33
|
+
@width ||= lines.max{|line| line.length }
|
34
|
+
end
|
35
|
+
|
36
|
+
def size
|
37
|
+
Size.new length, width
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_a
|
41
|
+
lines
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
lines.join '\n'
|
46
|
+
end
|
47
|
+
|
48
|
+
def join seperator
|
49
|
+
lines.join seperator
|
50
|
+
end
|
51
|
+
|
52
|
+
def excerpt lines_range, width_range
|
53
|
+
self.class.new lines[lines_range].map { |line| line[width_range] }
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def reset_width!
|
59
|
+
@width = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def clean line
|
63
|
+
Array split(line)
|
64
|
+
end
|
65
|
+
|
66
|
+
def split line
|
67
|
+
line.split /\r\n|\n\r|\n|\r/
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/remedy/view.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Remedy
|
2
|
+
class View
|
3
|
+
def initialize body, header = [], footer = []
|
4
|
+
@header, @body, @footer = header, body, footer
|
5
|
+
end
|
6
|
+
attr_accessor :body, :header, :footer, :length
|
7
|
+
|
8
|
+
def to_s force_recompile = false
|
9
|
+
unless @view.nil? || force_recompile then
|
10
|
+
@view
|
11
|
+
else
|
12
|
+
reset_length!
|
13
|
+
@view = compile!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def compile!
|
20
|
+
compiled_view = String.new
|
21
|
+
reset_length!
|
22
|
+
|
23
|
+
merged.each do |line|
|
24
|
+
compiled_view << row(line)
|
25
|
+
end
|
26
|
+
|
27
|
+
compiled_view
|
28
|
+
end
|
29
|
+
|
30
|
+
def merged
|
31
|
+
@header.to_a + @body.to_a + @footer.to_a
|
32
|
+
end
|
33
|
+
|
34
|
+
def row line
|
35
|
+
@length += 1
|
36
|
+
"#{line}#{ANSI.cursor.next_line}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def reset_length!
|
40
|
+
@length = 0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'remedy/view'
|
2
|
+
require 'remedy/size'
|
3
|
+
require 'remedy/content'
|
4
|
+
|
5
|
+
module Remedy
|
6
|
+
class Viewport
|
7
|
+
def draw content, center = Size.new(0,0), header = [], footer = []
|
8
|
+
range = range_find content, center, content_size(header,footer)
|
9
|
+
|
10
|
+
if content.size.fits_into? range then
|
11
|
+
viewable_content = content
|
12
|
+
else
|
13
|
+
viewable_content = content.excerpt *range
|
14
|
+
end
|
15
|
+
|
16
|
+
view = View.new viewable_content, header, footer
|
17
|
+
|
18
|
+
ANSI.screen.safe_reset!
|
19
|
+
Console.output << view
|
20
|
+
end
|
21
|
+
|
22
|
+
def range_find map, center, heightwidth
|
23
|
+
row_size, col_size = heightwidth
|
24
|
+
row_limit, col_limit = map.size
|
25
|
+
|
26
|
+
center_row, center_col = center.coords
|
27
|
+
|
28
|
+
row_range = center_range center_row, row_size, row_limit
|
29
|
+
col_range = center_range center_col, col_size, col_limit
|
30
|
+
[row_range, col_range]
|
31
|
+
end
|
32
|
+
|
33
|
+
def content_size header, footer
|
34
|
+
trim = Size [header.length + footer.length, 0]
|
35
|
+
size - trim
|
36
|
+
end
|
37
|
+
|
38
|
+
def size
|
39
|
+
Size Console.size
|
40
|
+
end
|
41
|
+
|
42
|
+
def center_range center, width, limit
|
43
|
+
range_start = center - (width / 2)
|
44
|
+
|
45
|
+
if range_start + width > limit then
|
46
|
+
range_start = limit - width
|
47
|
+
end
|
48
|
+
|
49
|
+
if range_start < 0 then
|
50
|
+
range_start = 0
|
51
|
+
end
|
52
|
+
|
53
|
+
range_end = range_start + width
|
54
|
+
|
55
|
+
(range_start...range_end)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/remedy.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'remedy/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "remedy"
|
8
|
+
gem.version = Remedy::VERSION
|
9
|
+
gem.authors = ["Anthony M. Cook"]
|
10
|
+
gem.email = ["anthonymichaelcook@gmail.com"]
|
11
|
+
gem.description = %q{Pure Ruby console interaction library in the vein of Curses with baked-in MVC support.}
|
12
|
+
gem.summary = %q{MVC Console Interaction Library}
|
13
|
+
gem.homepage = "http://github.com/acook/remedy"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: remedy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3.pre
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Anthony M. Cook
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-31 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Pure Ruby console interaction library in the vein of Curses with baked-in
|
15
|
+
MVC support.
|
16
|
+
email:
|
17
|
+
- anthonymichaelcook@gmail.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- .gitignore
|
23
|
+
- Gemfile
|
24
|
+
- LICENSE.txt
|
25
|
+
- README.markdown
|
26
|
+
- Rakefile
|
27
|
+
- lib/remedy.rb
|
28
|
+
- lib/remedy/ansi.rb
|
29
|
+
- lib/remedy/characters.rb
|
30
|
+
- lib/remedy/console.rb
|
31
|
+
- lib/remedy/console_resized.rb
|
32
|
+
- lib/remedy/content.rb
|
33
|
+
- lib/remedy/footer.rb
|
34
|
+
- lib/remedy/header.rb
|
35
|
+
- lib/remedy/interaction.rb
|
36
|
+
- lib/remedy/key.rb
|
37
|
+
- lib/remedy/keyboard.rb
|
38
|
+
- lib/remedy/partial.rb
|
39
|
+
- lib/remedy/version.rb
|
40
|
+
- lib/remedy/view.rb
|
41
|
+
- lib/remedy/viewport.rb
|
42
|
+
- remedy.gemspec
|
43
|
+
homepage: http://github.com/acook/remedy
|
44
|
+
licenses: []
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
none: false
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ! '>'
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 1.3.1
|
60
|
+
none: false
|
61
|
+
requirements: []
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.8.24
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: MVC Console Interaction Library
|
67
|
+
test_files: []
|
68
|
+
has_rdoc:
|