rcommand 0.1.1 → 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.
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: