optplus 0.0.8
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 +15 -0
- data/Bugs.rdoc +5 -0
- data/Gemfile +3 -0
- data/History.txt +39 -0
- data/Intro.txt +3 -0
- data/LICENCE.rdoc +159 -0
- data/README.md +296 -0
- data/bin/optplus +78 -0
- data/bin/optplus-installer +32 -0
- data/lib/optplus.rb +416 -0
- data/lib/optplus/errors.rb +31 -0
- data/lib/optplus/nested.rb +159 -0
- data/lib/optplus/version.rb +13 -0
- data/spec/optplus_spec.rb +41 -0
- data/spec/spec_helper.rb +12 -0
- data/test/conf.d/optplus.rb +1 -0
- data/test/my_nested_parser.rb +130 -0
- data/test/my_parser.rb +90 -0
- metadata +89 -0
data/bin/optplus
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
#!/usr/bin/env ruby18
|
2
|
+
#
|
3
|
+
# Author:: Robert Sharp
|
4
|
+
# Copyright:: Copyright (c) 2013 Robert Sharp
|
5
|
+
# License:: Open Software Licence v3.0
|
6
|
+
#
|
7
|
+
# This software is licensed for use under the Open Software Licence v. 3.0
|
8
|
+
# The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
|
9
|
+
# and in the file copyright.txt. Under the terms of this licence, all derivative works
|
10
|
+
# must themselves be licensed under the Open Software Licence v. 3.0
|
11
|
+
#
|
12
|
+
#
|
13
|
+
|
14
|
+
|
15
|
+
require 'rubygems'
|
16
|
+
require 'thor'
|
17
|
+
require 'colored'
|
18
|
+
|
19
|
+
require 'optplus'
|
20
|
+
|
21
|
+
# thor-based command line interpreter for the service. Define additional commands etc
|
22
|
+
# as required.
|
23
|
+
|
24
|
+
class OptplusCLI < Thor
|
25
|
+
|
26
|
+
class_option :config, :aliases=>'-c', :desc=>'use the given config file'
|
27
|
+
|
28
|
+
default_task :usage
|
29
|
+
|
30
|
+
desc "usage", "You are looking at it"
|
31
|
+
def usage
|
32
|
+
puts "What does this do?"
|
33
|
+
puts ""
|
34
|
+
help
|
35
|
+
puts ""
|
36
|
+
puts "See Also:"
|
37
|
+
puts " README.md: (see optplus readme above)"
|
38
|
+
puts " GitHub: https://github.com/osburn-sharp/optplus"
|
39
|
+
puts " RubyDoc: http://rubydoc.info/github/osburn-sharp/optplus/frames"
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "readme", "display the readme file for the gem"
|
43
|
+
def readme
|
44
|
+
gem_spec = Gem::Specification.find_by_name('optplus')
|
45
|
+
readme_path = File.join(gem_spec.gem_dir, 'README.md')
|
46
|
+
|
47
|
+
if FileTest.exists?(readme_path) then
|
48
|
+
|
49
|
+
File.open(readme_path) do |rfile|
|
50
|
+
rfile.each_line do |rline|
|
51
|
+
puts rline
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
else
|
56
|
+
puts "There is no readme available".red.bold
|
57
|
+
end
|
58
|
+
|
59
|
+
rescue
|
60
|
+
puts "There is no readme available".red.bold
|
61
|
+
end
|
62
|
+
|
63
|
+
# put your own commands here
|
64
|
+
desc "list", "list things that might be useful"
|
65
|
+
def list
|
66
|
+
|
67
|
+
client_opts = {:local=>false}
|
68
|
+
client_opts[:config_file] = options[:config] #if options.has_key?(:config)
|
69
|
+
JerbilService::Client.connect(Optplus, client_opts) do |service|
|
70
|
+
skey = service.service_key
|
71
|
+
# do what you need to here
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
OptplusCLI.start
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby18
|
2
|
+
#
|
3
|
+
# @markup ruby
|
4
|
+
# @title Installation Script
|
5
|
+
#
|
6
|
+
# = Optplus Installation Script
|
7
|
+
#
|
8
|
+
# == Uses Jeni to install Optplus Files
|
9
|
+
#
|
10
|
+
#
|
11
|
+
# Author:: Robert Sharp
|
12
|
+
# Copyright:: Copyright (c) 2013 Robert Sharp
|
13
|
+
# License:: Open Software Licence v3.0
|
14
|
+
#
|
15
|
+
# This software is licensed for use under the Open Software Licence v. 3.0
|
16
|
+
# The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
|
17
|
+
# and in the file copyright.txt. Under the terms of this licence, all derivative works
|
18
|
+
# must themselves be licensed under the Open Software Licence v. 3.0
|
19
|
+
#
|
20
|
+
#
|
21
|
+
|
22
|
+
|
23
|
+
require 'rubygems' # jeni uses it anyway to find the jerbil gem so why not use it here?
|
24
|
+
require 'jeni'
|
25
|
+
|
26
|
+
|
27
|
+
Jeni::Installer.new_from_gem('optplus') do |jeni|
|
28
|
+
jeni.optparse(ARGV)
|
29
|
+
|
30
|
+
# add custom installation actions
|
31
|
+
|
32
|
+
end.run!
|
data/lib/optplus.rb
ADDED
@@ -0,0 +1,416 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Robert Sharp
|
3
|
+
# Copyright:: Copyright (c) 2013 Robert Sharp
|
4
|
+
# License:: Open Software Licence v3.0
|
5
|
+
#
|
6
|
+
# This software is licensed for use under the Open Software Licence v. 3.0
|
7
|
+
# The terms of this licence can be found at http://www.opensource.org/licenses/osl-3.0.php
|
8
|
+
# and in the file copyright.txt. Under the terms of this licence, all derivative works
|
9
|
+
# must themselves be licensed under the Open Software Licence v. 3.0
|
10
|
+
#
|
11
|
+
#
|
12
|
+
require 'optparse'
|
13
|
+
require 'colored'
|
14
|
+
require 'abbrev'
|
15
|
+
require 'optplus/errors'
|
16
|
+
|
17
|
+
module Optplus
|
18
|
+
|
19
|
+
# == Optplus Parser
|
20
|
+
#
|
21
|
+
# A wrapper class that adds a little value to writing scipts
|
22
|
+
# with optparse. Like Thor but without trying to do too much.
|
23
|
+
#
|
24
|
+
class Parser
|
25
|
+
|
26
|
+
class << self
|
27
|
+
|
28
|
+
# define the usage banner, less "Usage: <prog_name>"!
|
29
|
+
#
|
30
|
+
# For example: usage "[options] [actions] [filename]" becomes:
|
31
|
+
# "Usage: progname [options] [actions] [filename]"
|
32
|
+
#
|
33
|
+
# @param [String] txt that is the banner
|
34
|
+
def usage(txt)
|
35
|
+
@_banner = txt
|
36
|
+
end
|
37
|
+
|
38
|
+
# Adds a description to the help/usage
|
39
|
+
#
|
40
|
+
# This takes any number of string arguments and displays them as separate lines.
|
41
|
+
#
|
42
|
+
# @param [Array] lines of description text as variable arguments
|
43
|
+
def description(*lines)
|
44
|
+
@_description = lines
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Add a brief description for a specific action
|
49
|
+
#
|
50
|
+
# Add a little Thor-like description before each method. Unlike Thor,
|
51
|
+
# you will not get told off if there is no corresponding method but
|
52
|
+
# its probably a good idea if you add one.
|
53
|
+
#
|
54
|
+
# @param [Symbol] action to be described
|
55
|
+
# @param [String] description of the action
|
56
|
+
def describe(action, description)
|
57
|
+
@_actions ||= Array.new
|
58
|
+
@_actions << action.to_s
|
59
|
+
@_descriptions ||= Hash.new
|
60
|
+
@_descriptions[action] = description
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# add a block of helpful text for an action
|
65
|
+
#
|
66
|
+
# Adds all of the arguments as lines to display when you use the help
|
67
|
+
# switch with the given argument, instead of the general help.
|
68
|
+
# Note that optplus does not allow options specific to actions so this is
|
69
|
+
# just text.
|
70
|
+
#
|
71
|
+
# @param [String] action to describe with helpful text
|
72
|
+
# @param [Array] lines of helpful text to display as arguments
|
73
|
+
def help(action, *lines)
|
74
|
+
@_help ||= Hash.new
|
75
|
+
@_help[action] = lines
|
76
|
+
end
|
77
|
+
|
78
|
+
# @!visibility private
|
79
|
+
attr_reader :_banner
|
80
|
+
# @!visibility private
|
81
|
+
attr_reader :_description
|
82
|
+
# @!visibility private
|
83
|
+
attr_reader :_actions
|
84
|
+
# @!visibility private
|
85
|
+
attr_reader :_descriptions
|
86
|
+
# @!visibility private
|
87
|
+
attr_accessor :_help
|
88
|
+
# end of private stuff
|
89
|
+
|
90
|
+
# @!visibility public
|
91
|
+
|
92
|
+
# Do the option parsing and actioning stuff
|
93
|
+
#
|
94
|
+
# If you write an optplus class, run the script and nothing happens it is because
|
95
|
+
# you forgot to add MyClass.run! Simple and easily done.
|
96
|
+
#
|
97
|
+
def run!
|
98
|
+
|
99
|
+
@_parent ||= nil
|
100
|
+
|
101
|
+
begin
|
102
|
+
me = self.new
|
103
|
+
|
104
|
+
if me._needs_help? then
|
105
|
+
me._help_me
|
106
|
+
elsif me._args.length > 0 then
|
107
|
+
action = me.next_argument
|
108
|
+
alup = @_actions.abbrev(action)
|
109
|
+
if alup.has_key?(action) then
|
110
|
+
|
111
|
+
me.before_actions if me.respond_to?(:before_actions)
|
112
|
+
|
113
|
+
begin
|
114
|
+
me.send(alup[action].to_sym)
|
115
|
+
|
116
|
+
# trap a deliberate exit and tidy up
|
117
|
+
# if required
|
118
|
+
rescue Optplus::ExitOnError => err
|
119
|
+
puts err.message.red.bold unless err.message == ''
|
120
|
+
me.after_actions if me.respond_to?(:after_actions)
|
121
|
+
raise Optplus::ExitOnError, '' # with no message
|
122
|
+
end
|
123
|
+
|
124
|
+
me.after_actions if me.respond_to?(:after_actions)
|
125
|
+
|
126
|
+
else
|
127
|
+
puts "Sorry, What?"
|
128
|
+
puts ""
|
129
|
+
me._get_help
|
130
|
+
end
|
131
|
+
else
|
132
|
+
me._get_help
|
133
|
+
end
|
134
|
+
|
135
|
+
return true
|
136
|
+
|
137
|
+
rescue OptionParser::InvalidOption => opterr
|
138
|
+
puts "Error: Invalid Option".red.bold
|
139
|
+
puts "I do not understand the option: #{opterr.args.join}"
|
140
|
+
rescue OptionParser::InvalidArgument => opterr
|
141
|
+
puts "Error: You have entered an invalid argument to an option".red.bold
|
142
|
+
puts "The option in question is: #{opterr.args.join(' ')}"
|
143
|
+
rescue OptionParser::AmbiguousOption => opterr
|
144
|
+
puts "Error: You need to be clearer than that".red.bold
|
145
|
+
puts "I am not be sure what option you mean: #{opterr.args.join}"
|
146
|
+
rescue OptionParser::AmbiguousArgument => opterr
|
147
|
+
puts "Error: You need to be clearer than that".red.bold
|
148
|
+
puts "I am not sure what argument you mean: #{opterr.args.join(' ')}"
|
149
|
+
rescue OptionParser::MissingArgument => opterr
|
150
|
+
puts "Error: You need to provide an argument with that option".red.bold
|
151
|
+
puts "This is the option in question: #{opterr.args.join}"
|
152
|
+
rescue OptionParser::ParseError => opterr
|
153
|
+
puts "Error: the command line is not as expected".red.bold
|
154
|
+
puts opterr.to_s
|
155
|
+
rescue Optplus::ParseError => err
|
156
|
+
puts "Error: #{err.message}".red.bold
|
157
|
+
rescue Optplus::ExitOnError => err
|
158
|
+
puts err.message.red.bold unless err.message == ''
|
159
|
+
raise Optplus::ExitOnError, '' unless @_parent.nil?
|
160
|
+
end
|
161
|
+
|
162
|
+
# only rescued exceptions will reach here
|
163
|
+
exit 1 if @_parent.nil?
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end # class << self
|
168
|
+
|
169
|
+
# @!method self.nest_parser(name, klass, description)
|
170
|
+
# nest a parser for subcommands
|
171
|
+
# This will add the given name to the actions list
|
172
|
+
# and then parse the next argument as a subcommand
|
173
|
+
# The klass must inherit {Optplus::NestedParser}
|
174
|
+
# @param [Symbol] name of action to nest
|
175
|
+
# @param [Class] klass of Nested Parser
|
176
|
+
# @param [String] description of action
|
177
|
+
instance_eval do
|
178
|
+
def nest_parser(name, klass, description)
|
179
|
+
self.describe(name, description)
|
180
|
+
self._help[name] = klass
|
181
|
+
class_eval %Q{
|
182
|
+
def #{name}
|
183
|
+
#{klass}.run!(self)
|
184
|
+
end
|
185
|
+
}
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# create an Optplus instance, define the options and parse the command line
|
190
|
+
#
|
191
|
+
# This method will call the following if they have been defined:
|
192
|
+
#
|
193
|
+
# * before_all - any setting up needed right at the start
|
194
|
+
# * options - to add options
|
195
|
+
# * before_actions - after options have been parsed but before actions are
|
196
|
+
# implemented
|
197
|
+
#
|
198
|
+
# @param [Class] klass for internal use in the instance itself
|
199
|
+
def initialize
|
200
|
+
|
201
|
+
@klass = self.class
|
202
|
+
@klass._help ||= Hash.new
|
203
|
+
@_help = false
|
204
|
+
@options = Hash.new
|
205
|
+
|
206
|
+
self.before_all if self.respond_to?(:before_all)
|
207
|
+
|
208
|
+
begin
|
209
|
+
@_optparse = OptionParser.new do |opts|
|
210
|
+
@program_name = opts.program_name
|
211
|
+
opts.banner = "Usage: #{@program_name} #{@klass._banner}"
|
212
|
+
opts.separator ""
|
213
|
+
|
214
|
+
@klass._description.each do |dline|
|
215
|
+
opts.separator " " + dline
|
216
|
+
end
|
217
|
+
|
218
|
+
opts.separator ""
|
219
|
+
opts.separator "Actions:"
|
220
|
+
opts.separator ""
|
221
|
+
flags = 0
|
222
|
+
@klass._descriptions.each do |key, value|
|
223
|
+
flag = @klass._help.has_key?(key.to_sym) ? '(-h)' : ''
|
224
|
+
flags += 1 unless flag == ''
|
225
|
+
opts.separator " #{key} - #{value} #{flag}"
|
226
|
+
end
|
227
|
+
|
228
|
+
if flags > 0 then
|
229
|
+
opts.separator ""
|
230
|
+
opts.separator " (-h indicates actions with additional help)"
|
231
|
+
opts.separator ""
|
232
|
+
end
|
233
|
+
|
234
|
+
opts.separator ""
|
235
|
+
opts.separator "Options:"
|
236
|
+
opts.separator ""
|
237
|
+
|
238
|
+
if @klass._help.length > 0 then
|
239
|
+
help_string = 'use with an action for further help'
|
240
|
+
else
|
241
|
+
help_string = 'you are looking at it'
|
242
|
+
end
|
243
|
+
options(opts) if self.respond_to?(:options)
|
244
|
+
opts.on_tail('-h', '--help', help_string) do
|
245
|
+
@_help = true
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
@_args = @_optparse.permute(ARGV)
|
251
|
+
|
252
|
+
# trap a deliberate exit and force exit before
|
253
|
+
# executing before_actions
|
254
|
+
rescue ExitOnError => err
|
255
|
+
puts err.message.red.bold unless err.message == ''
|
256
|
+
exit 1
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
end
|
261
|
+
|
262
|
+
# provides convenient access to the name of the program
|
263
|
+
attr_reader :program_name
|
264
|
+
|
265
|
+
# add optparse option for debug mode
|
266
|
+
#
|
267
|
+
# @param [Optparse] opts being the optparse instance
|
268
|
+
# @param [String] switch being the short-form option on the command line
|
269
|
+
def debug_option(opts, switch='-D')
|
270
|
+
opts.on_tail(switch, '--debug', 'show debug information') do |d|
|
271
|
+
@options[:debug] = d
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# add optparse option for verbose mode
|
276
|
+
#
|
277
|
+
# @param [Optparse] opts being the optparse instance
|
278
|
+
# @param [String] switch being the short-form option on the command line
|
279
|
+
def verbose_option(opts, switch='-V')
|
280
|
+
opts.on_tail(switch, '--verbose', 'show verbose information') do |v|
|
281
|
+
@options[:verbose] = v
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
# @!visibility private
|
287
|
+
attr_reader :_args
|
288
|
+
|
289
|
+
|
290
|
+
# return the next argument, if there is one or nil otherwise
|
291
|
+
#
|
292
|
+
# @return [String] being the next argument
|
293
|
+
def next_argument
|
294
|
+
@_args.shift
|
295
|
+
end
|
296
|
+
|
297
|
+
# return the next argument or the given default
|
298
|
+
#
|
299
|
+
# @param [Object] default to return if no argument
|
300
|
+
# @return [String] being the next argument or the default
|
301
|
+
def next_argument_or(default)
|
302
|
+
next_argument || default
|
303
|
+
end
|
304
|
+
|
305
|
+
# return the next argument or raise exception with the given message
|
306
|
+
#
|
307
|
+
# The exception does not need to be handled because {Optplus::Parser.run!}
|
308
|
+
# will rescue it and display an error message.
|
309
|
+
#
|
310
|
+
# @param [String] msg to attach to exception
|
311
|
+
# @return [String] being the next argument
|
312
|
+
# @raise [Optplus::ParseError] if there is no argument
|
313
|
+
def next_argument_or_error(msg)
|
314
|
+
next_argument || raise(Optplus::ParseError, msg)
|
315
|
+
end
|
316
|
+
|
317
|
+
# return all of the remaining args, or an empty array
|
318
|
+
#
|
319
|
+
# This clears all remaining arguments so that subsequent
|
320
|
+
# calls e.g. to {Optplus::Parser#next_argument} return nil
|
321
|
+
#
|
322
|
+
# @return [Array] of arguments
|
323
|
+
def all_arguments
|
324
|
+
args = @_args.dup
|
325
|
+
@_args = Array.new
|
326
|
+
return args
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
# @!visibility private
|
331
|
+
def _get_help
|
332
|
+
puts @_optparse.help
|
333
|
+
puts ""
|
334
|
+
end
|
335
|
+
|
336
|
+
# @!visibility private
|
337
|
+
def _needs_help?
|
338
|
+
@_help
|
339
|
+
end
|
340
|
+
|
341
|
+
# set the value of the given option, which defaults to true
|
342
|
+
#
|
343
|
+
# If a value is omitted then the option is set to be true
|
344
|
+
#
|
345
|
+
# @param [Symbol] key to use in getting the option
|
346
|
+
# @param [Object] value to set the option to
|
347
|
+
def set_option(key, value=true)
|
348
|
+
@options[key] = value
|
349
|
+
end
|
350
|
+
|
351
|
+
# get the value of the option
|
352
|
+
#
|
353
|
+
# Returns nil if there is no option with the given key
|
354
|
+
#
|
355
|
+
# @param [Symbol] key to the option to get
|
356
|
+
# @return [Object] or nil if no option set
|
357
|
+
def get_option(key)
|
358
|
+
@options[key]
|
359
|
+
end
|
360
|
+
|
361
|
+
# check if the option has been set
|
362
|
+
#
|
363
|
+
# @param [Symbol] key for the option to test
|
364
|
+
# @return [Boolean] true if option has been set
|
365
|
+
def option?(key)
|
366
|
+
@options.has_key?(key)
|
367
|
+
end
|
368
|
+
|
369
|
+
# call this to exit the script in case of an error
|
370
|
+
# and ensure any tidying up has been done
|
371
|
+
def exit_on_error(msg='')
|
372
|
+
raise Optplus::ExitOnError, msg
|
373
|
+
end
|
374
|
+
|
375
|
+
# @!visibility private
|
376
|
+
def _help_me
|
377
|
+
# is there an action on the line?
|
378
|
+
if _args.length > 0 then
|
379
|
+
# yes, but is it legit?
|
380
|
+
action = next_argument
|
381
|
+
alup = @klass._actions.abbrev(action)
|
382
|
+
action = alup[action].to_sym if alup.has_key?(action)
|
383
|
+
if @klass._help.has_key?(action) then
|
384
|
+
# valid help so use it
|
385
|
+
if @klass._help[action].kind_of?(Array) then
|
386
|
+
# its an array of strings, so print them
|
387
|
+
puts "Help for #{action}"
|
388
|
+
puts ""
|
389
|
+
@klass._help[action].each do |aline|
|
390
|
+
puts aline
|
391
|
+
end
|
392
|
+
puts ""
|
393
|
+
else
|
394
|
+
# its a nested parser so call its _help_me method
|
395
|
+
nested_klass = @klass._help[action]
|
396
|
+
nested_parser = nested_klass.new(self)
|
397
|
+
nested_parser._help_me
|
398
|
+
end
|
399
|
+
return
|
400
|
+
elsif @klass._actions.include?(action.to_s)
|
401
|
+
# valid action but no help
|
402
|
+
puts "Sorry, there is no specific help for action: #{action}".yellow
|
403
|
+
puts ""
|
404
|
+
else
|
405
|
+
# invalid action
|
406
|
+
puts "Sorry, but I do not understand the action: #{action}".red.bold
|
407
|
+
puts ""
|
408
|
+
end
|
409
|
+
end
|
410
|
+
_get_help
|
411
|
+
|
412
|
+
end
|
413
|
+
|
414
|
+
end
|
415
|
+
|
416
|
+
end
|