coglius 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Cogfile +15 -0
- data/LICENSE +201 -0
- data/lib/coglius.rb +29 -0
- data/lib/coglius/app.rb +250 -0
- data/lib/coglius/app_support.rb +284 -0
- data/lib/coglius/command.rb +149 -0
- data/lib/coglius/command_line_option.rb +34 -0
- data/lib/coglius/command_line_token.rb +62 -0
- data/lib/coglius/command_support.rb +214 -0
- data/lib/coglius/commands/compound_command.rb +42 -0
- data/lib/coglius/commands/doc.rb +215 -0
- data/lib/coglius/commands/help.rb +73 -0
- data/lib/coglius/commands/help_modules/arg_name_formatter.rb +20 -0
- data/lib/coglius/commands/help_modules/command_finder.rb +60 -0
- data/lib/coglius/commands/help_modules/command_help_format.rb +138 -0
- data/lib/coglius/commands/help_modules/global_help_format.rb +70 -0
- data/lib/coglius/commands/help_modules/help_completion_format.rb +31 -0
- data/lib/coglius/commands/help_modules/list_formatter.rb +23 -0
- data/lib/coglius/commands/help_modules/one_line_wrapper.rb +18 -0
- data/lib/coglius/commands/help_modules/options_formatter.rb +49 -0
- data/lib/coglius/commands/help_modules/text_wrapper.rb +53 -0
- data/lib/coglius/commands/help_modules/tty_only_wrapper.rb +23 -0
- data/lib/coglius/commands/help_modules/verbatim_wrapper.rb +16 -0
- data/lib/coglius/commands/initconfig.rb +69 -0
- data/lib/coglius/commands/rdoc_document_listener.rb +116 -0
- data/lib/coglius/commands/scaffold.rb +401 -0
- data/lib/coglius/copy_options_to_aliases.rb +33 -0
- data/lib/coglius/dsl.rb +221 -0
- data/lib/coglius/exceptions.rb +54 -0
- data/lib/coglius/flag.rb +68 -0
- data/lib/coglius/gli_option_parser.rb +124 -0
- data/lib/coglius/option_parser_factory.rb +45 -0
- data/lib/coglius/options.rb +23 -0
- data/lib/coglius/switch.rb +35 -0
- data/lib/coglius/terminal.rb +94 -0
- data/lib/coglius/version.rb +5 -0
- data/templates/coglius/generator.rb.erb +26 -0
- metadata +208 -0
@@ -0,0 +1,284 @@
|
|
1
|
+
module Coglius
|
2
|
+
# Internals for make App work
|
3
|
+
module AppSupport
|
4
|
+
# Override the device of stderr; exposed only for testing
|
5
|
+
def error_device=(e) #:nodoc:
|
6
|
+
@stderr = e
|
7
|
+
end
|
8
|
+
|
9
|
+
def context_description
|
10
|
+
"in global context"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Reset the Coglius module internal data structures; mostly useful for testing
|
14
|
+
def reset # :nodoc:
|
15
|
+
switches.clear
|
16
|
+
flags.clear
|
17
|
+
@commands = nil
|
18
|
+
@commands_declaration_order = []
|
19
|
+
@flags_declaration_order = []
|
20
|
+
@switches_declaration_order = []
|
21
|
+
@version = nil
|
22
|
+
@config_file = nil
|
23
|
+
@use_openstruct = false
|
24
|
+
@prog_desc = nil
|
25
|
+
@error_block = false
|
26
|
+
@pre_block = false
|
27
|
+
@post_block = false
|
28
|
+
@default_command = :help
|
29
|
+
@around_block = nil
|
30
|
+
clear_nexts
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get an array of commands, ordered by when they were declared
|
34
|
+
def commands_declaration_order # :nodoc:
|
35
|
+
@commands_declaration_order
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get the version string
|
39
|
+
def version_string #:nodoc:
|
40
|
+
@version
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get the default command for the entire app
|
44
|
+
def get_default_command
|
45
|
+
@default_command
|
46
|
+
end
|
47
|
+
|
48
|
+
# Runs whatever command is needed based on the arguments.
|
49
|
+
#
|
50
|
+
# +args+:: the command line ARGV array
|
51
|
+
#
|
52
|
+
# Returns a number that would be a reasonable exit code
|
53
|
+
def run(args) #:nodoc:
|
54
|
+
args = args.dup if @preserve_argv
|
55
|
+
command = nil
|
56
|
+
begin
|
57
|
+
override_defaults_based_on_config(parse_config)
|
58
|
+
|
59
|
+
add_help_switch_if_needed(switches)
|
60
|
+
|
61
|
+
global_options,command,options,arguments = CogliusOptionParser.new(commands,flags,switches,accepts,@default_command).parse_options(args)
|
62
|
+
|
63
|
+
copy_options_to_aliased_versions(global_options,command,options)
|
64
|
+
|
65
|
+
global_options = convert_to_openstruct_if_needed(global_options)
|
66
|
+
options = convert_to_openstruct_if_needed(options)
|
67
|
+
|
68
|
+
if proceed?(global_options,command,options,arguments)
|
69
|
+
call_command(command,global_options,options,arguments)
|
70
|
+
end
|
71
|
+
0
|
72
|
+
rescue Exception => ex
|
73
|
+
handle_exception(ex,command)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# Return the name of the config file; mostly useful for generating help docs
|
79
|
+
def config_file_name #:nodoc:
|
80
|
+
@config_file
|
81
|
+
end
|
82
|
+
|
83
|
+
def accepts #:nodoc:
|
84
|
+
@accepts ||= {}
|
85
|
+
end
|
86
|
+
|
87
|
+
# Copies all options in both global_options and options to keys for the aliases of those flags.
|
88
|
+
# For example, if a flag works with either -f or --flag, this will copy the value from [:f] to [:flag]
|
89
|
+
# to allow the user to access the options by any alias
|
90
|
+
def copy_options_to_aliased_versions(global_options,command,options) # :nodoc:
|
91
|
+
copy_options_to_aliases(global_options)
|
92
|
+
command.copy_options_to_aliases(options)
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_config # :nodoc:
|
96
|
+
config = {
|
97
|
+
'commands' => {},
|
98
|
+
}
|
99
|
+
if @config_file && File.exist?(@config_file)
|
100
|
+
require 'yaml'
|
101
|
+
config.merge!(File.open(@config_file) { |file| YAML::load(file) })
|
102
|
+
end
|
103
|
+
config
|
104
|
+
end
|
105
|
+
|
106
|
+
def clear_nexts # :nodoc:
|
107
|
+
super
|
108
|
+
@skips_post = false
|
109
|
+
@skips_pre = false
|
110
|
+
@skips_around = false
|
111
|
+
end
|
112
|
+
|
113
|
+
def stderr
|
114
|
+
@stderr ||= STDERR
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.included(klass)
|
118
|
+
@stderr = $stderr
|
119
|
+
end
|
120
|
+
|
121
|
+
def flags # :nodoc:
|
122
|
+
@flags ||= {}
|
123
|
+
end
|
124
|
+
|
125
|
+
def switches # :nodoc:
|
126
|
+
@switches ||= {}
|
127
|
+
end
|
128
|
+
|
129
|
+
def commands # :nodoc:
|
130
|
+
if !@commands
|
131
|
+
@commands = { :help => Coglius::Commands::Help.new(self), :_doc => Coglius::Commands::Doc.new(self) }
|
132
|
+
@commands_declaration_order ||= []
|
133
|
+
@commands_declaration_order << @commands[:help]
|
134
|
+
@commands_declaration_order << @commands[:_doc]
|
135
|
+
end
|
136
|
+
@commands
|
137
|
+
end
|
138
|
+
|
139
|
+
def pre_block
|
140
|
+
@pre_block ||= Proc.new do
|
141
|
+
true
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def post_block
|
146
|
+
@post_block ||= Proc.new do
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def around_blocks
|
151
|
+
@around_blocks || []
|
152
|
+
end
|
153
|
+
|
154
|
+
def help_sort_type
|
155
|
+
@help_sort_type || :alpha
|
156
|
+
end
|
157
|
+
|
158
|
+
def help_text_wrap_type
|
159
|
+
@help_text_wrap_type || :to_terminal
|
160
|
+
end
|
161
|
+
|
162
|
+
# Sets the default values for flags based on the configuration
|
163
|
+
def override_defaults_based_on_config(config)
|
164
|
+
override_default(flags,config)
|
165
|
+
override_default(switches,config)
|
166
|
+
|
167
|
+
override_command_defaults(commands,config)
|
168
|
+
end
|
169
|
+
|
170
|
+
def override_command_defaults(command_list,config)
|
171
|
+
command_list.each do |command_name,command|
|
172
|
+
next if command_name == :initconfig || command.nil?
|
173
|
+
command_config = (config['commands'] || {})[command_name] || {}
|
174
|
+
|
175
|
+
override_default(command.topmost_ancestor.flags,command_config)
|
176
|
+
override_default(command.topmost_ancestor.switches,command_config)
|
177
|
+
|
178
|
+
override_command_defaults(command.commands,command_config)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def override_default(tokens,config)
|
183
|
+
tokens.each do |name,token|
|
184
|
+
token.default_value=config[name] if config[name]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
def handle_exception(ex,command)
|
191
|
+
if regular_error_handling?(ex)
|
192
|
+
output_error_message(ex)
|
193
|
+
if ex.kind_of?(OptionParser::ParseError) || ex.kind_of?(BadCommandLine)
|
194
|
+
commands[:help] and commands[:help].execute({},{},command.nil? ? [] : [command.name.to_s])
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
raise ex if ENV['Coglius_DEBUG'] == 'true'
|
199
|
+
|
200
|
+
ex.extend(Coglius::StandardException)
|
201
|
+
ex.exit_code
|
202
|
+
end
|
203
|
+
|
204
|
+
def output_error_message(ex)
|
205
|
+
stderr.puts error_message(ex) unless no_message_given?(ex)
|
206
|
+
if ex.kind_of?(OptionParser::ParseError) || ex.kind_of?(BadCommandLine)
|
207
|
+
stderr.puts unless no_message_given?(ex)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def no_message_given?(ex)
|
212
|
+
ex.message == ex.class.name
|
213
|
+
end
|
214
|
+
|
215
|
+
# Possibly returns a copy of the passed-in Hash as an instance of Coglius::Option.
|
216
|
+
# By default, it will *not*. However by putting <tt>use_openstruct true</tt>
|
217
|
+
# in your CLI definition, it will
|
218
|
+
def convert_to_openstruct_if_needed(options) # :nodoc:
|
219
|
+
@use_openstruct ? Options.new(options) : options
|
220
|
+
end
|
221
|
+
|
222
|
+
def add_help_switch_if_needed(switches)
|
223
|
+
help_switch_exists = switches.values.find { |switch|
|
224
|
+
(Array(switch.aliases) + [switch.name]).find { |an_alias|
|
225
|
+
an_alias.to_s == 'help'
|
226
|
+
}
|
227
|
+
}
|
228
|
+
unless help_switch_exists
|
229
|
+
desc 'Show this message'
|
230
|
+
switch :help, :negatable => false
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# True if we should proceed with executing the command; this calls
|
235
|
+
# the pre block if it's defined
|
236
|
+
def proceed?(global_options,command,options,arguments) #:nodoc:
|
237
|
+
if command && command.skips_pre
|
238
|
+
true
|
239
|
+
else
|
240
|
+
pre_block.call(global_options,command,options,arguments)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Returns true if we should proceed with Coglius's basic error handling.
|
245
|
+
# This calls the error block if the user provided one
|
246
|
+
def regular_error_handling?(ex) #:nodoc:
|
247
|
+
if @error_block
|
248
|
+
@error_block.call(ex)
|
249
|
+
else
|
250
|
+
true
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# Returns a String of the error message to show the user
|
255
|
+
# +ex+:: The exception we caught that launched the error handling routines
|
256
|
+
def error_message(ex) #:nodoc:
|
257
|
+
"error: #{ex.message}"
|
258
|
+
end
|
259
|
+
|
260
|
+
def call_command(command,global_options,options,arguments)
|
261
|
+
arguments = arguments.map { |arg| arg.dup } # unfreeze
|
262
|
+
code = lambda { command.execute(global_options,options,arguments) }
|
263
|
+
nested_arounds = unless command.skips_around
|
264
|
+
around_blocks.inject do |outer_around, inner_around|
|
265
|
+
lambda { |go,c,o,a, code1|
|
266
|
+
inner = lambda { inner_around.call(go,c,o,a, code1) }
|
267
|
+
outer_around.call(go,c,o,a, inner)
|
268
|
+
}
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
if nested_arounds
|
273
|
+
nested_arounds.call(global_options,command, options, arguments, code)
|
274
|
+
else
|
275
|
+
code.call
|
276
|
+
end
|
277
|
+
|
278
|
+
unless command.skips_post
|
279
|
+
post_block.call(global_options,command,options,arguments)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'coglius/command_line_token.rb'
|
2
|
+
require 'coglius/copy_options_to_aliases.rb'
|
3
|
+
require 'coglius/dsl.rb'
|
4
|
+
|
5
|
+
module Coglius
|
6
|
+
# A command to be run, in context of global flags and switches. You are given an instance of this class
|
7
|
+
# to the block you use for Coglius::DSL#command. This class mixes in Coglius::DSL so all of those methods are available
|
8
|
+
# to describe the command, in addition to the methods documented here, most importantly
|
9
|
+
# #action.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# command :list do |c| # <- c is an instance of Coglius::Command
|
14
|
+
# c.desc 'use long form'
|
15
|
+
# c.switch :l
|
16
|
+
#
|
17
|
+
# c.action do |global,options,args|
|
18
|
+
# # list things here
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# c.command :tasks do |t| # <- t is an instance of Coglius::Command
|
22
|
+
# # this is a "subcommand" of list
|
23
|
+
#
|
24
|
+
# t.action do |global,options,args|
|
25
|
+
# # do whatever list tasks should do
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
class Command < CommandLineToken
|
31
|
+
include CopyOptionsToAliases
|
32
|
+
include DSL
|
33
|
+
include CommandSupport
|
34
|
+
|
35
|
+
# Create a new command.
|
36
|
+
#
|
37
|
+
# options:: Keys should be:
|
38
|
+
# +names+:: A String, Symbol, or Array of String or Symbol that represents the name(s) of this command (required).
|
39
|
+
# +description+:: short description of this command as a String
|
40
|
+
# +arguments_name+:: description of the arguments as a String, or nil if this command doesn't take arguments
|
41
|
+
# +long_desc+:: a longer description of the command, possibly with multiple lines. A double line-break is treated
|
42
|
+
# as a paragraph break. No other formatting is respected, though inner whitespace is maintained.
|
43
|
+
# +skips_pre+:: if true, this command advertises that it doesn't want the pre block called first
|
44
|
+
# +skips_post+:: if true, this command advertises that it doesn't want the post block called after it
|
45
|
+
# +skips_around+:: if true, this command advertises that it doesn't want the around block called
|
46
|
+
def initialize(options)
|
47
|
+
super(options[:names],options[:description],options[:long_desc])
|
48
|
+
@arguments_description = options[:arguments_name] || ''
|
49
|
+
@arguments_options = Array(options[:arguments_options]).flatten
|
50
|
+
@skips_pre = options[:skips_pre]
|
51
|
+
@skips_post = options[:skips_post]
|
52
|
+
@skips_around = options[:skips_around]
|
53
|
+
@commands_declaration_order = []
|
54
|
+
@flags_declaration_order = []
|
55
|
+
@switches_declaration_order = []
|
56
|
+
clear_nexts
|
57
|
+
end
|
58
|
+
|
59
|
+
# Set the default command if this command has subcommands and the user doesn't
|
60
|
+
# provide a subcommand when invoking THIS command. When nil, this will show an error and the help
|
61
|
+
# for this command; when set, the command with this name will be executed.
|
62
|
+
#
|
63
|
+
# +command_name+:: The primary name of the subcommand of this command that should be run by default as a String or Symbol.
|
64
|
+
def default_command(command_name)
|
65
|
+
@default_command = command_name
|
66
|
+
end
|
67
|
+
|
68
|
+
# Define the action to take when the user executes this command. Every command should either define this
|
69
|
+
# action block, or have subcommands (or both).
|
70
|
+
#
|
71
|
+
# +block+:: A block of code to execute. The block will be given 3 arguments:
|
72
|
+
# +global_options+:: A Hash of the _global_ options specified
|
73
|
+
# by the user, with defaults set and config file values used (if using a config file, see
|
74
|
+
# Coglius::App#config_file)
|
75
|
+
# +options+:: A Hash of the command-specific options specified by the
|
76
|
+
# user, with defaults set and config file values used (if using a config file, see
|
77
|
+
# Coglius::App#config_file).
|
78
|
+
# +arguments+:: An Array of Strings representing the unparsed command line arguments
|
79
|
+
# The block's result value is not used; raise an exception or use Coglius#exit_now! if you need an early exit based
|
80
|
+
# on an error condition
|
81
|
+
#
|
82
|
+
def action(&block)
|
83
|
+
@action = block
|
84
|
+
end
|
85
|
+
|
86
|
+
# Describes this commands action block when it *also* has subcommands.
|
87
|
+
# In this case, the Coglius::DSL#desc value is the general description of the commands
|
88
|
+
# that this command groups, and the value for *this* method documents what
|
89
|
+
# will happen if you omit a subcommand.
|
90
|
+
#
|
91
|
+
# Note that if you omit the action block and specify a subcommand, that subcommand's
|
92
|
+
# description will be used to describe what happens by default.
|
93
|
+
#
|
94
|
+
# desc:: the description of what this command's action block does.
|
95
|
+
#
|
96
|
+
# Example
|
97
|
+
#
|
98
|
+
# desc 'list things'
|
99
|
+
# command :list do |c|
|
100
|
+
#
|
101
|
+
# c.desc 'list tasks'
|
102
|
+
# c.command :tasks do |t|
|
103
|
+
# t.action do |global,options,args|
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# c.desc 'list contexts'
|
108
|
+
# c.command :contexts do |t|
|
109
|
+
# t.action do |global,options,args|
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# c.default_desc 'list both tasks and contexts'
|
114
|
+
# c.action do |global,options,args|
|
115
|
+
# # list everything
|
116
|
+
# end
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
#
|
120
|
+
# > todo help list
|
121
|
+
# NAME
|
122
|
+
# list - List things
|
123
|
+
#
|
124
|
+
# SYNOPSIS
|
125
|
+
# todo [global options] list [command options]
|
126
|
+
# todo [global options] list [command options] tasks
|
127
|
+
# todo [global options] list [command options] contexts
|
128
|
+
#
|
129
|
+
# COMMANDS
|
130
|
+
# <default> - list both tasks and contexts
|
131
|
+
# tasks - list tasks
|
132
|
+
# contexts - list contexts
|
133
|
+
#
|
134
|
+
def default_desc(desc)
|
135
|
+
@default_desc = desc
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns true if this command has the given option defined
|
139
|
+
def has_option?(option) #:nodoc:
|
140
|
+
option = option.gsub(/^\-+/,'')
|
141
|
+
((flags.values.map { |_| [_.name,_.aliases] }) +
|
142
|
+
(switches.values.map { |_| [_.name,_.aliases] })).flatten.map(&:to_s).include?(option)
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.name_as_string(name,negatable=false) #:nodoc:
|
146
|
+
name.to_s
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'coglius/command_line_token.rb'
|
2
|
+
|
3
|
+
module Coglius
|
4
|
+
# An option, not a command or argument, on the command line
|
5
|
+
class CommandLineOption < CommandLineToken #:nodoc:
|
6
|
+
|
7
|
+
attr_accessor :default_value
|
8
|
+
# Command to which this option "belongs", nil if it's a global option
|
9
|
+
attr_accessor :associated_command
|
10
|
+
|
11
|
+
# Creates a new option
|
12
|
+
#
|
13
|
+
# names - Array of symbols or strings representing the names of this switch
|
14
|
+
# options - hash of options:
|
15
|
+
# :desc - the short description
|
16
|
+
# :long_desc - the long description
|
17
|
+
# :default_value - the default value of this option
|
18
|
+
def initialize(names,options = {})
|
19
|
+
super(names,options[:desc],options[:long_desc])
|
20
|
+
@default_value = options[:default_value]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.name_as_string(name,negatable=true)
|
24
|
+
string = name.to_s
|
25
|
+
if string.length == 1
|
26
|
+
"-#{string}"
|
27
|
+
elsif negatable
|
28
|
+
"--[no-]#{string}"
|
29
|
+
else
|
30
|
+
"--#{string}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|