coglius 0.0.1
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/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
|