rcommand 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,8 @@
1
+ == RCommand 0.2.0
2
+ * RCommand now uses several different methods of dealing with i/o.
3
+ * Added readline support.
4
+ * Redesigned the API.
5
+ * Fixed readchar method so that it no longer constantly makes system calls.
1
6
  == RCommand 0.1.1
2
7
  * Fixed gets method so that it correctly returns "\n" on the end of strings.
3
8
  == RCommand 0.1.0
data/README CHANGED
@@ -1,42 +1,15 @@
1
1
  RCommand is a generic way for ruby scripts to present a full-featured
2
2
  command interface to users, complete with command history and tab completion.
3
3
 
4
- == Example
5
- require 'rcommand'
4
+ RCommand can use several different methods of input and output. If standard
5
+ input and output are used, RCommand will attempt to use the readline library.
6
+ The i/o method to be used can be manually overridden if necessary.
6
7
 
7
- command_line = RCommand.new(STDIN, STDOUT)
8
- command_line.for_prompt do
9
- command_line.io_write.print("#{command_line.history.size}:> ")
10
- end
11
-
12
- # Default events shown here in full, for demonstration purposes. Omitting
13
- # them will give the same behavior.
14
- command_line.on_single_tab do |partial|
15
- matches = RCommand.matches(partial, command_line.history)
16
- common_prefix = RCommand.common_prefix(matches)
17
- common_prefix.nil? ? partial : common_prefix
18
- end
19
- command_line.on_double_tab do |partial|
20
- matches = RCommand.matches(partial, command_line.history)
21
- if matches.size > 1
22
- command_line.io_write.puts()
23
- for match in matches.uniq
24
- command_line.io_write.puts(match)
25
- end
26
- command_line.prompt()
27
- command_line.io_write.print(partial)
28
- end
29
- partial
30
- end
31
- command_line.on_key_up do |partial|
32
- command_line.prev()
33
- end
34
- command_line.on_key_down do |partial|
35
- command_line.next()
36
- end
8
+ == Example
9
+ command_line = RCommand.new($stdin, $stdout)
37
10
 
38
11
  loop do
39
- command_line.prompt()
40
- result = command_line.gets()
12
+ command_line.prompt = "#{command_line.history.size}:> "
13
+ result = command_line.readline()
41
14
  command_line.io_write.print("Example command: #{result}")
42
15
  end
@@ -21,10 +21,11 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
 
24
- RCOMMAND_VERSION = "0.1.1"
25
-
26
24
  $:.unshift(File.dirname(__FILE__))
27
25
 
26
+ require 'rcommand/version'
27
+ require 'rcommand/io_methods'
28
+
28
29
  # RCommand is a generic way for ruby scripts to present a full-featured
29
30
  # command interface to users with command history and tab completion.
30
31
  class RCommand
@@ -72,19 +73,27 @@ class RCommand
72
73
  unless io_write.kind_of? IO
73
74
  raise "Expecting object of type IO, got #{io_write.class.name}."
74
75
  end
76
+
75
77
  @io_read = io_read
76
78
  @io_write = io_write
79
+ @io_method = nil
80
+
77
81
  @history = []
78
82
  @history_index = nil
79
- @tab_count = 0
80
- @prompt_proc = nil
81
- @single_tab_proc = lambda do |partial|
82
- matches = RCommand.matches(partial, self.history)
83
+
84
+ @procs = {}
85
+ @procs[:completion] = lambda do |partial|
86
+ RCommand.matches(partial, (self.history.collect do |line|
87
+ line.split(' ')
88
+ end).flatten.uniq)
89
+ end
90
+ @procs[:single_tab] = lambda do |partial|
91
+ matches = self.completions_for(partial)
83
92
  common_prefix = RCommand.common_prefix(matches)
84
93
  common_prefix.nil? ? partial : common_prefix
85
94
  end
86
- @double_tab_proc = lambda do |partial|
87
- matches = RCommand.matches(partial, self.history)
95
+ @procs[:double_tab] = lambda do |partial|
96
+ matches = self.completions_for(partial)
88
97
  if matches.size > 1
89
98
  self.io_write.puts()
90
99
  for match in matches.uniq
@@ -95,38 +104,51 @@ class RCommand
95
104
  end
96
105
  partial
97
106
  end
98
- @key_up_proc = lambda do |partial|
107
+ @procs[:key_up] = lambda do |partial|
99
108
  self.prev()
100
109
  end
101
- @key_down_proc = lambda do |partial|
110
+ @procs[:key_down] = lambda do |partial|
102
111
  self.next()
103
112
  end
104
- @key_left_proc = nil
105
- @key_right_proc = nil
106
- @interupt_proc = lambda do
113
+ @procs[:key_left] = nil
114
+ @procs[:key_left] = nil
115
+ @procs[:interupt] = lambda do
107
116
  self.io_write.print("\n")
108
117
  system("stty sane")
109
118
  exit
110
119
  end
120
+
121
+ initialize_io_method()
111
122
  end
112
123
 
113
124
  attr_reader :io_read
114
125
  attr_reader :io_write
126
+ attr_accessor :io_method
127
+
128
+ attr_reader :procs
129
+
115
130
  attr_reader :history
116
131
  attr_accessor :history_index
117
-
118
- def for_prompt(&block); @prompt_proc = block; end
119
- def on_single_tab(&block); @single_tab_proc = block; end
120
- def on_double_tab(&block); @double_tab_proc = block; end
121
- def on_key_up(&block); @key_up_proc = block; end
122
- def on_key_down(&block); @key_down_proc = block; end
123
- def on_key_left(&block); @key_left_proc = block; end
124
- def on_key_right(&block); @key_right_proc = block; end
125
- def on_interupt(&block); @interupt_proc = block; end
126
132
 
127
- # Displays the prompt, if any.
128
- def prompt()
129
- @prompt_proc.call() unless @prompt_proc.nil?
133
+ attr_accessor :prompt
134
+
135
+ def on_completion(&block); @procs[:completion] = block; end
136
+ def on_single_tab(&block); @procs[:single_tab] = block; end
137
+ def on_double_tab(&block); @procs[:double_tab] = block; end
138
+ def on_key_up(&block); @procs[:key_up] = block; end
139
+ def on_key_down(&block); @procs[:key_down] = block; end
140
+ def on_key_left(&block); @procs[:key_left] = block; end
141
+ def on_key_right(&block); @procs[:key_right] = block; end
142
+ def on_interupt(&block); @procs[:interupt] = block; end
143
+
144
+ # Returns an array of completions for the given partial command. This
145
+ # method tries to use the <tt>on_completion</tt> block if available.
146
+ def completions_for(partial)
147
+ if @procs[:completion] != nil
148
+ return @procs[:completion].call(partial)
149
+ else
150
+ return RCommand.matches(partial, self.history)
151
+ end
130
152
  end
131
153
 
132
154
  # Sets the command line to the previous command.
@@ -159,129 +181,39 @@ class RCommand
159
181
  def <<(command)
160
182
  command = command[0..-1] if command[-1] == "\n"
161
183
  command = command[0..-1] if command[-1] == "\r"
162
- if @history[-1] == ""
163
- @history.pop()
184
+ unless command.empty?
185
+ self.history.pop() if self.history[-1] == ""
186
+ self.history << command
164
187
  end
165
- @history << command if command != ""
166
- @history_index = nil
188
+ self.history_index = nil
167
189
  end
168
190
 
169
191
  # Returns the command string entered by the user.
170
- def gets()
171
- command = ""
172
- char = nil
173
- loop do
174
- char = getc()
175
- next if char.nil?
176
- next if char == 0
177
- if char != 9
178
- @tab_count = 0
179
- end
180
- if char == 3
181
- @interupt_proc.call() unless @interupt_proc.nil?
182
- elsif char == 10
183
- @io_write.print("\n")
184
- self << command
185
- break
186
- elsif char == 9
187
- @tab_count += 1
188
- replacement = nil
189
- if @tab_count == 1
190
- unless @single_tab_proc.nil?
191
- replacement =
192
- @single_tab_proc.call(command)
193
- end
194
- elsif @tab_count > 1
195
- unless @double_tab_proc.nil?
196
- replacement =
197
- @double_tab_proc.call(command)
198
- end
199
- end
200
- if replacement != nil && replacement.size > command.size
201
- @io_write.print(
202
- 8.chr * command.size +
203
- " " * command.size +
204
- 8.chr * command.size)
205
- @io_write.print(replacement)
206
- command = replacement
207
- end
208
- elsif char == 8 || char == 127
209
- if command.size > 0
210
- command = command[0..-2]
211
- @io_write.print(8.chr + " " + 8.chr)
212
- end
213
- else
214
- command << char.chr
215
- replacement = nil
216
- if command.index("\e[A") != nil
217
- command.gsub!("\e[A", "")
218
- unless @key_up_proc.nil?
219
- replacement =
220
- @key_up_proc.call(command)
221
- end
222
- elsif command.index("\e[B") != nil
223
- command.gsub!("\e[B", "")
224
- unless @key_down_proc.nil?
225
- replacement =
226
- @key_down_proc.call(command)
227
- end
228
- elsif command.index("\e[C") != nil
229
- command.gsub!("\e[C", "")
230
- unless @key_right_proc.nil?
231
- replacement =
232
- @key_right_proc.call(command)
233
- end
234
- elsif command.index("\e[D") != nil
235
- command.gsub!("\e[D", "")
236
- unless @key_left_proc.nil?
237
- replacement =
238
- @key_left_proc.call(command)
239
- end
240
- else
241
- if (char == "["[0] && command[-2] != "\e"[0]) ||
242
- (char != "\e"[0] && char != "["[0])
243
- @io_write.print(char.chr)
244
- end
245
- end
246
- if replacement != nil
247
- @io_write.print(
248
- 8.chr * command.size +
249
- " " * command.size +
250
- 8.chr * command.size)
251
- @io_write.print(replacement)
252
- command = replacement
253
- end
254
- end
255
- end
256
- return command + "\n"
192
+ def readline()
193
+ line = self.io_method.readline()
194
+ line = line[0..-1] if line[-1] == "\n"
195
+ line = line[0..-1] if line[-1] == "\r"
196
+ self << line
197
+ return line + "\n"
257
198
  end
258
-
199
+
259
200
  private
260
- # Returns a single character without waiting for the enter key to be
261
- # pressed.
262
- def getc()
263
- char = nil
264
- begin
265
- if @io_read == STDIN
266
- if RUBY_PLATFORM =~ /mswin/
267
- require "Win32API"
268
- char = Win32API.new("crtdll", "_getch", [], "L").Call
269
- else
270
- begin
271
- system("stty cbreak -echo")
272
- char = @io_read.readchar()
273
- ensure
274
- system("stty -cbreak -echo")
275
- end
276
- end
201
+ # Selects the appropriate input/output method.
202
+ def initialize_io_method()
203
+ self.io_method = nil
204
+ if self.io_read.object_id == $stdin.object_id &&
205
+ self.io_write.object_id == $stdout.object_id
206
+ if defined?(RCommand::ReadlineIOMethod)
207
+ self.io_method = RCommand::ReadlineIOMethod.new(self)
277
208
  else
278
- char = @io_read.readchar()
209
+ self.io_method = RCommand::StandardIOMethod.new(self)
279
210
  end
280
- rescue Interrupt
281
- return 3
282
- rescue Exception
283
- return nil
211
+ elsif defined?(TCPSocket) && self.io_read.kind_of?(TCPSocket)
212
+ self.io_method = RCommand::RawIOMethod.new(self)
213
+ elsif defined?(File) && self.io_read.kind_of?(File)
214
+ self.io_method = RCommand::RawIOMethod.new(self)
215
+ else
216
+ self.io_method = RCommand::StandardIOMethod.new(self)
284
217
  end
285
- return char
286
218
  end
287
219
  end
@@ -0,0 +1,252 @@
1
+ #--
2
+ # Copyright (c) 2005 Robert Aman
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ class RCommand
25
+ class IOMethod
26
+ def initialize(command_line)
27
+ @command_line = command_line
28
+ end
29
+
30
+ def readline
31
+ raise NotImplementedError,
32
+ "readline not implemented in #{self.class.name}."
33
+ end
34
+
35
+ def eof?
36
+ raise NotImplementedError,
37
+ "eof? not implemented in #{self.class.name}."
38
+ end
39
+ end
40
+
41
+ class RawIOMethod < IOMethod
42
+ def initialize(command_line)
43
+ super(command_line)
44
+ end
45
+
46
+ def readline
47
+ if @command_line.prompt != nil && @command_line.io_write != nil
48
+ @command_line.io_write.print(@command_line.prompt)
49
+ end
50
+
51
+ line = @command_line.io_read.gets()
52
+ line = line[0..-1] if line[-1] == "\n"
53
+ line = line[0..-1] if line[-1] == "\r"
54
+ return line + "\n"
55
+ end
56
+
57
+ def eof?
58
+ begin
59
+ require 'timeout'
60
+ Timeout.timeout(0.01) do
61
+ return $stdin.eof?
62
+ end
63
+ rescue LoadError
64
+ return $stdin.eof?
65
+ rescue Timeout::Error
66
+ return true
67
+ end
68
+ end
69
+ end
70
+
71
+ class StandardIOMethod < IOMethod
72
+ def initialize(command_line)
73
+ super(command_line)
74
+ @stty_set = false
75
+ @tab_count = 0
76
+ end
77
+
78
+ def readline
79
+ if @command_line.prompt != nil && @command_line.io_write != nil
80
+ @command_line.io_write.print(@command_line.prompt)
81
+ end
82
+
83
+ # RESTORE THIS CODE!
84
+ command = ""
85
+ char = nil
86
+ loop do
87
+ char = self.readchar()
88
+ next if char.nil?
89
+ next if char == 0
90
+ if char != 9
91
+ @tab_count = 0
92
+ end
93
+ if char == 3
94
+ raise Interrupt
95
+ elsif char == 10
96
+ @command_line.io_write.print("\n")
97
+ self << command
98
+ break
99
+ elsif char == 9
100
+ @tab_count += 1
101
+ replacement = nil
102
+ if @tab_count == 1
103
+ unless @command_line.procs[:single_tab].nil?
104
+ replacement = @command_line.procs[:single_tab].call(command)
105
+ end
106
+ elsif @tab_count > 1
107
+ unless @command_line.procs[:double_tab].nil?
108
+ replacement = @command_line.procs[:double_tab].call(command)
109
+ end
110
+ end
111
+ if replacement != nil && replacement.size > command.size
112
+ @command_line.io_write.print(
113
+ 8.chr * command.size +
114
+ " " * command.size +
115
+ 8.chr * command.size)
116
+ @command_line.io_write.print(replacement)
117
+ command = replacement
118
+ end
119
+ elsif char == 8 || char == 127
120
+ if command.size > 0
121
+ command = command[0..-2]
122
+ @command_line.io_write.print(8.chr + " " + 8.chr)
123
+ end
124
+ else
125
+ command << char.chr
126
+ replacement = nil
127
+ if command.index("\e[A") != nil
128
+ command.gsub!("\e[A", "")
129
+ unless @command_line.procs[:key_up].nil?
130
+ replacement = @command_line.procs[:key_up].call(command)
131
+ end
132
+ elsif command.index("\e[B") != nil
133
+ command.gsub!("\e[B", "")
134
+ unless @command_line.procs[:key_down].nil?
135
+ replacement = @command_line.procs[:key_down].call(command)
136
+ end
137
+ elsif command.index("\e[C") != nil
138
+ command.gsub!("\e[C", "")
139
+ unless @command_line.procs[:key_right].nil?
140
+ replacement = @command_line.procs[:key_right].call(command)
141
+ end
142
+ elsif command.index("\e[D") != nil
143
+ command.gsub!("\e[D", "")
144
+ unless @command_line.procs[:key_left].nil?
145
+ replacement = @command_line.procs[:key_left].call(command)
146
+ end
147
+ else
148
+ if (char == "["[0] && command[-2] != "\e"[0]) ||
149
+ (char != "\e"[0] && char != "["[0])
150
+ @command_line.io_write.print(char.chr)
151
+ end
152
+ end
153
+ if replacement != nil
154
+ @command_line.io_write.print(
155
+ 8.chr * command.size +
156
+ " " * command.size +
157
+ 8.chr * command.size)
158
+ @command_line.io_write.print(replacement)
159
+ command = replacement
160
+ end
161
+ end
162
+ end
163
+ return command + "\n"
164
+ end
165
+
166
+ def readchar
167
+ char = nil
168
+ begin
169
+ if @command_line.io_read.object_id == $stdin.object_id
170
+ if RUBY_PLATFORM =~ /mswin/
171
+ require "Win32API"
172
+ char = Win32API.new("crtdll", "_getch", [], "L").Call
173
+ else
174
+ begin
175
+ if @stty_set != true
176
+ system("stty cbreak -echo")
177
+ @stty_set = true
178
+ end
179
+ char = @command_line.io_read.readchar()
180
+ ensure
181
+ if @stty_set != true
182
+ system("stty -cbreak -echo")
183
+ @stty_set = true
184
+ end
185
+ end
186
+ end
187
+ else
188
+ char = @command_line.io_read.readchar()
189
+ end
190
+ rescue Interrupt
191
+ return 3
192
+ rescue Exception
193
+ return nil
194
+ end
195
+ return char
196
+ end
197
+ private :getc
198
+
199
+ def eof?
200
+ begin
201
+ require 'timeout'
202
+ Timeout.timeout(0.01) do
203
+ return $stdin.eof?
204
+ end
205
+ rescue LoadError
206
+ return $stdin.eof?
207
+ rescue Timeout::Error
208
+ return true
209
+ end
210
+ end
211
+ end
212
+
213
+ begin
214
+ require "readline"
215
+
216
+ class ReadlineIOMethod < IOMethod
217
+ def initialize(command_line)
218
+ super(command_line)
219
+
220
+ # Initialize Readline here somehow.
221
+ if Readline.respond_to?("basic_word_break_characters=")
222
+ Readline.basic_word_break_characters = " \t\n\"\\'`><=;|&{("
223
+ end
224
+ Readline.completion_append_character = nil
225
+ Readline.completion_proc = lambda do |partial|
226
+ command_line.completions_for(partial)
227
+ end
228
+ Readline::HISTORY.push("") if self.history.empty?
229
+
230
+ @eof = false
231
+ end
232
+
233
+ def history
234
+ return Readline::HISTORY.to_a
235
+ end
236
+
237
+ def readline
238
+ if line = Readline.readline(@command_line.prompt)
239
+ Readline::HISTORY.push(line) unless line.empty?
240
+ else
241
+ @eof = true
242
+ end
243
+ return line
244
+ end
245
+
246
+ def eof?
247
+ return @eof
248
+ end
249
+ end
250
+ rescue LoadError
251
+ end
252
+ end
@@ -0,0 +1,9 @@
1
+ class RCommand
2
+ module RCOMMAND_VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 2
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/rakefile CHANGED
@@ -6,8 +6,10 @@ require 'rake/packagetask'
6
6
  require 'rake/gempackagetask'
7
7
  require 'rake/contrib/rubyforgepublisher'
8
8
 
9
+ require File.join(File.dirname(__FILE__), 'lib/rcommand', 'version')
10
+
9
11
  PKG_NAME = 'rcommand'
10
- PKG_VERSION = '0.1.1'
12
+ PKG_VERSION = RCommand::RCOMMAND_VERSION::STRING
11
13
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
12
14
 
13
15
  RELEASE_NAME = "REL #{PKG_VERSION}"
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: rcommand
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.1
7
- date: 2006-04-02 00:00:00 -05:00
6
+ version: 0.2.0
7
+ date: 2006-04-24 00:00:00 -04:00
8
8
  summary: Command handler.
9
9
  require_paths:
10
10
  - lib
@@ -33,7 +33,10 @@ files:
33
33
  - install.rb
34
34
  - README
35
35
  - CHANGELOG
36
+ - lib/rcommand
36
37
  - lib/rcommand.rb
38
+ - lib/rcommand/io_methods.rb
39
+ - lib/rcommand/version.rb
37
40
  - test/command_test.rb
38
41
  test_files: []
39
42
  rdoc_options: