simplecli 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/LICENSE +22 -0
  2. data/README.rdoc +169 -0
  3. data/Rakefile +57 -0
  4. data/VERSION.yml +4 -0
  5. data/examples/hello-cli +44 -0
  6. data/lib/simplecli.rb +221 -0
  7. metadata +61 -0
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009 ryan "remi" Taylor
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ 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
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,169 @@
1
+ = SimpleCLI
2
+
3
+ == DEPRECATED
4
+
5
+ I used to use this for all of my CLI's. Then I found Thor.
6
+
7
+ I still think it would be fun to complete a new SimpleCLI DSL
8
+ that I was working on ... but Thor is becoming pretty standard
9
+ so I would much rather use something that's widely known and used.
10
+
11
+ I personally recommend using Thor and, when you want to run
12
+ your script, call ClassThatInheritsFromThor.start
13
+
14
+ Thor:: http://github.com/wycats/thor
15
+
16
+ == Old Documentation ...
17
+
18
+ SimpleCLI is Undergoing Renovations
19
+
20
+ After over a year of use, this library needs some love!
21
+
22
+ I'm going to ...
23
+
24
+ - get a spec suite working again (I had specs for SimpleCLI once ... they're gone now)
25
+ - create a DSL (or 2) for making it easier for people to make SimpleCLI apps if
26
+ they don't want to just include the SimpleCLI module in one of their classes
27
+ - make (atleast some of) the DSL work in pre-defined classes (kindof like what we do now)
28
+ - create a screencast showing how easy it is to use SimpleCLI (and how to test-drive
29
+ SimpleCLI applications)
30
+ - document #command_missing and #default_comment usage with good examples
31
+ - create abunchof examples and throw them in the examples directory
32
+ - add #before and #after blocks for handling global options or something to do before
33
+ exiting type stuff
34
+ - make sure errors return the right response code
35
+
36
+ == About
37
+
38
+ Super Simple RubyGems-like CLI
39
+
40
+ SimpleCLI gives you a stupidly simple way to implement command-line
41
+ interfaces like that of RubyGems with a basic interface like:
42
+
43
+ gem command [options]
44
+
45
+ SimpleCLI gives you a way of defining your commands (or actions) so
46
+ they'll automatically show up when you run `yourapp commands`
47
+
48
+ SimpleCLI also makes it really easy to add documentation to each of
49
+ your commands (or actions)
50
+
51
+ == Real Examples
52
+
53
+ I use SimpleCLI in most of my apps for quick and dirty command-line interfaces.
54
+
55
+ Here are a few real examples:
56
+
57
+ * {Syntax On}[http://github.com/remi/syntax-on/tree/master/lib/syntax-on/bin.rb]
58
+ * {Domain Finder}[http://github.com/remi/domain-finder/tree/master/lib/domain-finder/bin.rb]
59
+ * ADF[http://github.com/remi/adf/tree/master/lib/adf/bin.rb]
60
+
61
+ == Example
62
+
63
+ Here's a super simple SimpleCLI example:
64
+
65
+ #! /usr/bin/env ruby
66
+
67
+ require File.dirname(__FILE__) + '/../lib/simplecli'
68
+
69
+ class Hello
70
+ include SimpleCLI
71
+
72
+ def usage
73
+ puts <<doco
74
+
75
+ Hello CLI
76
+
77
+ Usage:
78
+ #{ script_name } command [options]
79
+
80
+ Futher help:
81
+ #{ script_name } commands # list all available commands
82
+ #{ script_name } help <COMMAND> # show help for COMMAND
83
+ #{ script_name } help # show this help message
84
+
85
+ doco
86
+ end
87
+
88
+ def sayhello_help
89
+ <<doco
90
+ Usage: #{ script_name } sayhello [SAY]
91
+
92
+ Arguments:
93
+ SAY: Something to say (default 'Hello World!')
94
+
95
+ Summary:
96
+ Says hello!
97
+ doco
98
+ end
99
+ def sayhello *args
100
+ puts args.empty? ? "Hello World!" : args.join(' ')
101
+ end
102
+
103
+ end
104
+
105
+ # POSTAMBLE
106
+ if __FILE__ == $0
107
+ Hello.new( ARGV, :default => 'sayhello' ).run
108
+ end
109
+
110
+ Example usage:
111
+
112
+ <em><tt>$ ./hello-cli</tt></em>
113
+
114
+ Hello CLI
115
+
116
+ Usage:
117
+ hello-cli command [options]
118
+
119
+ Futher help:
120
+ hello-cli commands # list all available commands
121
+ hello-cli help <COMMAND> # show help for COMMAND
122
+ hello-cli help # show this help message
123
+
124
+ <em><tt>$ ./hello-cli commands</tt></em>
125
+
126
+ hello-cli commands are:
127
+
128
+ DEFAULT COMMAND sayhello
129
+
130
+ commands List all 'hello-cli' commands
131
+ help Provide help documentation for a command
132
+ sayhello Says hello!
133
+
134
+ For help on a particular command, use 'hello-cli help COMMAND'.
135
+
136
+ <em><tt>$ ./hello-cli help</tt></em>
137
+
138
+ Usage: hello-cli help COMMAND
139
+
140
+ Summary:
141
+ Provide help documentation for a command
142
+
143
+ <em><tt>$ ./hello-cli help sayhello</tt></em>
144
+
145
+ Usage: hello-cli sayhello [SAY]
146
+
147
+ Arguments:
148
+ SAY: Something to say (default 'Hello World!')
149
+
150
+ Summary:
151
+ Says hello!
152
+
153
+ <em><tt>$ ./hello-cli sayhello</tt></em>
154
+
155
+ Hello World!
156
+
157
+ <em><tt>$ ./hello-cli sayhello Hi There</tt></em>
158
+
159
+ Hi There
160
+
161
+ <em><tt>$ ./hello-cli Hi There</tt></em> `# this works because sayhello is configured as the default command`
162
+
163
+ Hi There
164
+
165
+ TODO
166
+ ----
167
+
168
+ * implement a `before` block for handling global options
169
+ * there was once a spec suite ... where'd it go? find or recreate it!
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ require 'rake'
2
+ require 'rubygems'
3
+ require 'rake/rdoctask'
4
+ require 'spec/rake/spectask'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |s|
9
+ s.name = "simplecli"
10
+ s.summary = "For making simple RubyGem-like command-line interfaces"
11
+ s.email = "remi@remitaylor.com"
12
+ s.homepage = "http://github.com/remi/simplecli"
13
+ s.description = "SimpleCLI gives you a stupidly simple way to implement command-line interfaces like that of RubyGems"
14
+ s.authors = %w( remi )
15
+ s.files = FileList["[A-Z]*", "{lib,spec,examples}/**/*"]
16
+ # s.add_dependency 'person-project'
17
+ # s.executables = "neato"
18
+ end
19
+ rescue LoadError
20
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
21
+ end
22
+
23
+ Spec::Rake::SpecTask.new do |t|
24
+ t.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ desc "Run all examples with RCov"
28
+ Spec::Rake::SpecTask.new('rcov') do |t|
29
+ t.spec_files = FileList['spec/**/*_spec.rb']
30
+ t.rcov = true
31
+ end
32
+
33
+ Rake::RDocTask.new do |rdoc|
34
+ rdoc.rdoc_dir = 'rdoc'
35
+ rdoc.title = 'simplecli'
36
+ rdoc.options << '--line-numbers' << '--inline-source'
37
+ rdoc.rdoc_files.include('README.rdoc')
38
+ rdoc.rdoc_files.include('lib/**/*.rb')
39
+ end
40
+
41
+ desc 'Confirm that gemspec is $SAFE'
42
+ task :safe do
43
+ require 'yaml'
44
+ require 'rubygems/specification'
45
+ data = File.read('simplecli.gemspec')
46
+ spec = nil
47
+ if data !~ %r{!ruby/object:Gem::Specification}
48
+ Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
49
+ else
50
+ spec = YAML.load(data)
51
+ end
52
+ spec.validate
53
+ puts spec
54
+ puts "OK"
55
+ end
56
+
57
+ task :default => :spec
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 6
@@ -0,0 +1,44 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/simplecli'
4
+
5
+ class Hello
6
+ include SimpleCLI
7
+
8
+ def usage
9
+ puts <<doco
10
+
11
+ Hello CLI
12
+
13
+ Usage:
14
+ #{ script_name } command [options]
15
+
16
+ Futher help:
17
+ #{ script_name } commands # list all available commands
18
+ #{ script_name } help <COMMAND> # show help for COMMAND
19
+ #{ script_name } help # show this help message
20
+
21
+ doco
22
+ end
23
+
24
+ def sayhello_help
25
+ <<doco
26
+ Usage: #{ script_name } sayhello [SAY]
27
+
28
+ Arguments:
29
+ SAY: Something to say (default 'Hello World!')
30
+
31
+ Summary:
32
+ Says hello!
33
+ doco
34
+ end
35
+ def sayhello *args
36
+ puts args.empty? ? "Hello World!" : args.join(' ')
37
+ end
38
+
39
+ end
40
+
41
+ # POSTAMBLE
42
+ if __FILE__ == $0
43
+ Hello.new( ARGV, :default => 'sayhello' ).run
44
+ end
data/lib/simplecli.rb ADDED
@@ -0,0 +1,221 @@
1
+ #
2
+ # Stupidly simple way to get a CLI that handles:
3
+ # myapp somecommand --blah=5 args --more stuff -y
4
+ #
5
+ # All it does is, if the "command" (or "action") passed
6
+ # has a method with the same name, the rest of the args
7
+ # are passed to the method.
8
+ #
9
+ # If you provide a command_help method that returns help
10
+ # info as a String, that'll be used when you call:
11
+ # myapp help somecommand
12
+ #
13
+ # If you provide a 'Summary:\n blah blah blah' bit in
14
+ # your help_somecommand, it'll be used as the command's
15
+ # summary and your command will show up when you:
16
+ # myapp commands
17
+ #
18
+ # To use, include in your class
19
+ #
20
+ # NOTE: if you use the 'default' command functionality,
21
+ # we don'e even bother to check to see if we respond_to?
22
+ # what you provide as a default command, incase it
23
+ # uses method missing or something. So it's *YOUR*
24
+ # responsibility to provide this method
25
+ #
26
+ # Conventionally, your 'default' method should simple pass
27
+ # along the arguments to another defined and documented command!
28
+ #
29
+ module SimpleCLI
30
+ attr_accessor :options
31
+ attr_reader :args, :command, :command_args
32
+
33
+ def initialize args = [], options = {}
34
+ @args = args
35
+ @options = options
36
+ parse!
37
+ end
38
+
39
+ # figure out what command to run, arguments to pass it, etc
40
+ #
41
+ # call #run afterwards, to run. or call parse! to parse and run
42
+ #
43
+ # typically, you shouldn't call this yourself. call parse when you
44
+ # want to RE-parse the arguments passed in, because initialize auto-parses
45
+ #
46
+ # typical:
47
+ # Bin.new( ARGV ).run
48
+ # Bin.new( ARGV, :default => 'some_default_method ).run
49
+ #
50
+ # use this is you want to ...
51
+ # bin = Bin.new ARGV
52
+ # bin.options[:default] = 'some_default_method'
53
+ # bin.instance_eval { 'do some custom stuff that might change the command to run, etc' }
54
+ # bin.parse!
55
+ # bin.run
56
+ #
57
+ def parse!
58
+ args = @args.clone
59
+
60
+ @default_command = @options[:default].to_s if @options.keys.include? :default
61
+ @commands = all_commands
62
+
63
+ # if a command is discovered, eg. $ myscript foo 1 2 3 # where foo is a command
64
+ if not args.empty? and @commands.map {|c| c.downcase }.include? args.first.downcase
65
+ @command = args.shift.downcase
66
+
67
+ # if a command is not discovered, and no arguments were sent at all, eg. $ myscript
68
+ elsif args.empty?
69
+ @command = @default_command || 'usage'
70
+
71
+ # there were args passed, but we don't know what to call ... try command_missing first
72
+ elsif command_from_command_missing = command_missing(args)
73
+ @command = command_from_command_missing # if it returns something that's not nil, set it to command
74
+
75
+ # there were some arguments, pass if to the default command if there is one, else 'command not found'
76
+ elsif @default_command
77
+ @command = @default_command
78
+
79
+ # nothing worked out ... show command not found & usage
80
+ else
81
+ puts "command not found: #{ args.first.downcase }"
82
+ @command = 'usage'
83
+
84
+ end
85
+
86
+ @command_args = args
87
+ end
88
+
89
+ # before dropping to default command ( if defined via :default option ),
90
+ # the arguments get passed along to command_missing (you get the original args array)
91
+ #
92
+ # your command_missing can return a command string (name of the command) or just a proc to call!
93
+ #
94
+ # actually, if what you respond with responds to #call, we use that, else we call #to_s to
95
+ # get the name of the command to run
96
+ #
97
+ # ***NOTE*** if command_missing makes changes to the args passed to it, these are persisted
98
+ # and passed to the command. args.dup if you need to mess with args!
99
+ #
100
+ # if you return something #call-able, the command arguments will be passed to your block
101
+ #
102
+ # you can call super to drop back to the default command_missing in SimpleCLI
103
+ # or just return nil to say "nope, can't find a command for this"
104
+ #
105
+ def command_missing args
106
+ nil
107
+ end
108
+
109
+ # run command determined by parse
110
+ def run
111
+ begin
112
+ if @command.respond_to? :call
113
+ @command.call @command_args
114
+ else
115
+ self.send @command.to_s, *@command_args
116
+ end
117
+ rescue ArgumentError => ex
118
+ puts "'#{@command}' called with wrong number of arguments\n\n"
119
+ puts help_for( @command )
120
+ end
121
+ end
122
+
123
+ # returns names of all defined 'command' methods
124
+ #
125
+ # only returns methods with methodname_help sister methods,
126
+ # inotherwords: only returns DOCUMENTED methods
127
+ # ... this should get you to document that command!
128
+ def all_commands
129
+ self.methods.sort.grep( /_help/ ).collect{ |help_method| help_method.gsub( /(.*)_help/ , '\1' ) }
130
+ end
131
+
132
+ # returns help String for command
133
+ def help_for command
134
+ help_method = "#{ command }_help".to_sym
135
+ self.send( help_method ) if self.respond_to? help_method
136
+ end
137
+
138
+ # returns summary String for command (extracted from help_for command)
139
+ #
140
+ # Looks for
141
+ # Summary:
142
+ # some summary text here, on a new line after 'Summary:'
143
+ #
144
+ def summary_for command
145
+ doco = help_for command
146
+ if doco
147
+ match = /Summary:\n*(.*)/.match doco
148
+ if match and match.length > 1
149
+ match[1].strip
150
+ end
151
+ end
152
+ end
153
+
154
+ # default usage message, called if we can't figure out what command to run
155
+ #
156
+ # override in your app by re-defining - it should puts the message (or do whatever) itself!
157
+ # this method doesn't return a string, like blah_help methods (which are called by #help).
158
+ # usage is called, all by itself ... so it needs to print itself!
159
+ #
160
+ def usage *args
161
+ puts "default usage message. please define a 'usage' method returning a new message."
162
+ end
163
+
164
+ # shortcut to pretty file name of script, which can be used in your help doco
165
+ def script_name
166
+ File.basename $0
167
+ end
168
+
169
+ # HELP
170
+ def help_help
171
+ <<doco
172
+ Usage: #{ script_name } help COMMAND
173
+
174
+ Summary:
175
+ Provide help documentation for a command
176
+ doco
177
+ end
178
+ def help *args
179
+ command = args.shift
180
+ if command.nil?
181
+ puts help_for( :help )
182
+ elsif (doco = help_for command)
183
+ puts doco
184
+ else
185
+ puts "No documentation found for command: #{command}"
186
+ end
187
+ end
188
+
189
+ # COMMANDS
190
+ def commands_help
191
+ <<doco
192
+ Usage: #{ script_name } commands
193
+
194
+ Summary:
195
+ List all '#{ script_name }' commands
196
+ doco
197
+ end
198
+ def commands *no_args
199
+ before_spaces = 4
200
+ after_spaces = 18
201
+ text = all_commands.inject(''){ |all,cmd| all << "\n#{' ' * before_spaces}#{cmd}#{' ' * (after_spaces - cmd.length)}#{summary_for(cmd)}" }
202
+ puts <<doco
203
+ #{ script_name } commands are:
204
+
205
+ DEFAULT COMMAND #{ @default_command || 'not set' }
206
+ #{ text }
207
+
208
+ For help on a particular command, use '#{ script_name } help COMMAND'.
209
+ doco
210
+
211
+ #If you've made a command and it's not showing up here, you
212
+ #need to make help method named 'COMMAND_help' that returns
213
+ #your commands help documentation.
214
+ #
215
+ #[NOT YET IMPLEMENTED:]
216
+ #Commands may be abbreviated, so long as they are unumbiguous.
217
+ #e.g. 'snip h commands' is short for 'snip help commands'.
218
+ #doco
219
+ end
220
+
221
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simplecli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.6
5
+ platform: ruby
6
+ authors:
7
+ - remi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-14 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: SimpleCLI gives you a stupidly simple way to implement command-line interfaces like that of RubyGems
17
+ email: remi@remitaylor.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - Rakefile
26
+ - VERSION.yml
27
+ - README.rdoc
28
+ - LICENSE
29
+ - lib/simplecli.rb
30
+ - examples/hello-cli
31
+ has_rdoc: true
32
+ homepage: http://github.com/remi/simplecli
33
+ licenses: []
34
+
35
+ post_install_message:
36
+ rdoc_options:
37
+ - --inline-source
38
+ - --charset=UTF-8
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project:
56
+ rubygems_version: 1.3.5
57
+ signing_key:
58
+ specification_version: 2
59
+ summary: For making simple RubyGem-like command-line interfaces
60
+ test_files: []
61
+