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,62 @@
|
|
1
|
+
module Coglius
|
2
|
+
# Abstract base class for a logical element of a command line, mostly so that subclasses can have similar
|
3
|
+
# initialization and interface
|
4
|
+
class CommandLineToken
|
5
|
+
attr_reader :name #:nodoc:
|
6
|
+
attr_reader :aliases #:nodoc:
|
7
|
+
attr_reader :description #:nodoc:
|
8
|
+
attr_reader :long_description #:nodoc:
|
9
|
+
|
10
|
+
def initialize(names,description,long_description=nil) #:nodoc:
|
11
|
+
@description = description
|
12
|
+
@long_description = long_description
|
13
|
+
@name,@aliases,@names = parse_names(names)
|
14
|
+
end
|
15
|
+
|
16
|
+
def usage #:nodoc:
|
17
|
+
all_forms
|
18
|
+
end
|
19
|
+
|
20
|
+
# Sort based on primary name
|
21
|
+
def <=>(other)
|
22
|
+
self.name.to_s <=> other.name.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
# Returns a string of all possible forms
|
27
|
+
# of this flag. Mostly intended for printing
|
28
|
+
# to the user.
|
29
|
+
def all_forms(joiner=', ')
|
30
|
+
forms = all_forms_a
|
31
|
+
forms.join(joiner)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
# Handles dealing with the "names" param, parsing
|
36
|
+
# it into the primary name and aliases list
|
37
|
+
def parse_names(names)
|
38
|
+
# Allow strings; convert to symbols
|
39
|
+
names = [names].flatten.map { |name| name.to_sym }
|
40
|
+
names_hash = {}
|
41
|
+
names.each do |name|
|
42
|
+
raise ArgumentError.new("#{name} has spaces; they are not allowed") if name.to_s =~ /\s/
|
43
|
+
names_hash[self.class.name_as_string(name)] = true
|
44
|
+
end
|
45
|
+
name = names.shift
|
46
|
+
aliases = names.length > 0 ? names : nil
|
47
|
+
[name,aliases,names_hash]
|
48
|
+
end
|
49
|
+
|
50
|
+
def negatable?
|
51
|
+
false;
|
52
|
+
end
|
53
|
+
|
54
|
+
def all_forms_a
|
55
|
+
forms = [self.class.name_as_string(name,negatable?)]
|
56
|
+
if aliases
|
57
|
+
forms |= aliases.map { |one_alias| self.class.name_as_string(one_alias,negatable?) }.sort { |one,two| one.length <=> two.length }
|
58
|
+
end
|
59
|
+
forms
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module Coglius
|
2
|
+
# Things unrelated to the true public interface of Command that are needed for bookkeeping
|
3
|
+
# and help support. Generally, you shouldn't be calling these methods; they are technically public
|
4
|
+
# but are essentially part of Coglius's internal implementation and subject to change
|
5
|
+
module CommandSupport
|
6
|
+
# The parent of this command, either the Coglius app, or another command
|
7
|
+
attr_accessor :parent
|
8
|
+
|
9
|
+
def context_description
|
10
|
+
"in the command #{name}"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Return true to avoid including this command in your help strings
|
14
|
+
def nodoc
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return the arguments description
|
19
|
+
def arguments_description
|
20
|
+
@arguments_description
|
21
|
+
end
|
22
|
+
|
23
|
+
def arguments_options
|
24
|
+
@arguments_options
|
25
|
+
end
|
26
|
+
|
27
|
+
# If true, this command doesn't want the pre block run before it executes
|
28
|
+
def skips_pre
|
29
|
+
@skips_pre
|
30
|
+
end
|
31
|
+
|
32
|
+
# If true, this command doesn't want the post block run before it executes
|
33
|
+
def skips_post
|
34
|
+
@skips_post
|
35
|
+
end
|
36
|
+
|
37
|
+
# If true, this command doesn't want the around block called
|
38
|
+
def skips_around
|
39
|
+
@skips_around
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return the Array of the command's names
|
43
|
+
def names
|
44
|
+
all_forms
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get an array of commands, ordered by when they were declared
|
48
|
+
def commands_declaration_order # :nodoc:
|
49
|
+
@commands_declaration_order
|
50
|
+
end
|
51
|
+
|
52
|
+
def flag(*names)
|
53
|
+
new_flag = if parent.kind_of? Command
|
54
|
+
parent.flag(*names)
|
55
|
+
else
|
56
|
+
super(*names)
|
57
|
+
end
|
58
|
+
new_flag.associated_command = self
|
59
|
+
new_flag
|
60
|
+
end
|
61
|
+
|
62
|
+
def switch(*names)
|
63
|
+
new_switch = if parent.kind_of? Command
|
64
|
+
parent.switch(*names)
|
65
|
+
else
|
66
|
+
super(*names)
|
67
|
+
end
|
68
|
+
new_switch.associated_command = self
|
69
|
+
new_switch
|
70
|
+
end
|
71
|
+
|
72
|
+
def desc(d)
|
73
|
+
if parent.kind_of? Command
|
74
|
+
parent.desc(d)
|
75
|
+
else
|
76
|
+
super(d)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def long_desc(d)
|
81
|
+
if parent.kind_of? Command
|
82
|
+
parent.long_desc(d)
|
83
|
+
else
|
84
|
+
super(d)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def arg_name(d,options=[])
|
89
|
+
if parent.kind_of? Command
|
90
|
+
parent.arg_name(d,options)
|
91
|
+
else
|
92
|
+
super(d,options)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def default_value(d)
|
97
|
+
if parent.kind_of? Command
|
98
|
+
parent.default_value(d)
|
99
|
+
else
|
100
|
+
super(d)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Get the usage string
|
105
|
+
# CR: This should probably not be here
|
106
|
+
def usage
|
107
|
+
usage = name.to_s
|
108
|
+
usage += ' [command options]' if !flags.empty? || !switches.empty?
|
109
|
+
usage += ' ' + @arguments_description if @arguments_description
|
110
|
+
usage
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return the flags as a Hash
|
114
|
+
def flags
|
115
|
+
@flags ||= {}
|
116
|
+
end
|
117
|
+
# Return the switches as a Hash
|
118
|
+
def switches
|
119
|
+
@switches ||= {}
|
120
|
+
end
|
121
|
+
|
122
|
+
def commands # :nodoc:
|
123
|
+
@commands ||= {}
|
124
|
+
end
|
125
|
+
|
126
|
+
def default_description
|
127
|
+
@default_desc
|
128
|
+
end
|
129
|
+
|
130
|
+
# Executes the command
|
131
|
+
def execute(global_options,options,arguments)
|
132
|
+
subcommand,arguments = find_subcommand(arguments)
|
133
|
+
if subcommand
|
134
|
+
subcommand.execute(global_options,options,arguments)
|
135
|
+
else
|
136
|
+
get_action(arguments).call(global_options,options,arguments)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def topmost_ancestor
|
141
|
+
some_command = self
|
142
|
+
top = some_command
|
143
|
+
while some_command.kind_of? self.class
|
144
|
+
top = some_command
|
145
|
+
some_command = some_command.parent
|
146
|
+
end
|
147
|
+
top
|
148
|
+
end
|
149
|
+
|
150
|
+
def has_action?
|
151
|
+
!!@action
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_default_command
|
155
|
+
@default_command
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def get_action(arguments)
|
161
|
+
if @action
|
162
|
+
@action
|
163
|
+
else
|
164
|
+
generate_error_action(arguments)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def generate_error_action(arguments)
|
169
|
+
lambda { |global_options,options,arguments|
|
170
|
+
if am_subcommand?
|
171
|
+
if arguments.size > 0
|
172
|
+
raise UnknownCommand,"Unknown command '#{arguments[0]}'"
|
173
|
+
else
|
174
|
+
raise BadCommandLine,"Command '#{name}' requires a subcommand"
|
175
|
+
end
|
176
|
+
elsif have_subcommands?
|
177
|
+
raise BadCommandLine,"Command '#{name}' requires a subcommand"
|
178
|
+
else
|
179
|
+
raise "Command '#{name}' has no action block"
|
180
|
+
end
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
def am_subcommand?
|
185
|
+
parent.kind_of?(Command)
|
186
|
+
end
|
187
|
+
|
188
|
+
def have_subcommands?
|
189
|
+
!self.commands.empty?
|
190
|
+
end
|
191
|
+
|
192
|
+
def find_subcommand(arguments)
|
193
|
+
subcommand = find_explicit_subcommand(arguments)
|
194
|
+
if subcommand
|
195
|
+
[subcommand,arguments[1..-1]]
|
196
|
+
else
|
197
|
+
if !@default_command.nil?
|
198
|
+
[find_explicit_subcommand([@default_command.to_s]),arguments]
|
199
|
+
else
|
200
|
+
[false,arguments]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def find_explicit_subcommand(arguments)
|
206
|
+
arguments = Array(arguments)
|
207
|
+
return false if arguments.empty?
|
208
|
+
subcommand_name = arguments.first
|
209
|
+
self.commands.values.find { |command|
|
210
|
+
[command.name,Array(command.aliases)].flatten.map(&:to_s).any? { |name| name == subcommand_name }
|
211
|
+
}
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Coglius
|
2
|
+
module Commands
|
3
|
+
# A command that calls other commands in order
|
4
|
+
class CompoundCommand < Command
|
5
|
+
# base:: object that respondes to +commands+
|
6
|
+
# configuration:: Array of arrays: index 0 is the array of names of this command and index 1
|
7
|
+
# is the names of the compound commands.
|
8
|
+
def initialize(base,configuration,options={})
|
9
|
+
name = configuration.keys.first
|
10
|
+
super(options.merge(:names => [name]))
|
11
|
+
|
12
|
+
command_names = configuration[name]
|
13
|
+
|
14
|
+
check_for_unknown_commands!(base,command_names)
|
15
|
+
|
16
|
+
@wrapped_commands = command_names.map { |name| self.class.find_command(base,name) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute(global_options,options,arguments) #:nodoc:
|
20
|
+
@wrapped_commands.each do |command|
|
21
|
+
command.execute(global_options,options,arguments)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def check_for_unknown_commands!(base,command_names)
|
28
|
+
known_commands = base.commands.keys.map(&:to_s)
|
29
|
+
unknown_commands = command_names.map(&:to_s) - known_commands
|
30
|
+
|
31
|
+
unless unknown_commands.empty?
|
32
|
+
raise "Unknown commands #{unknown_commands.join(',')}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.find_command(base,name)
|
37
|
+
base.commands.values.find { |command| command.name == name }
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
module Coglius
|
2
|
+
module Commands
|
3
|
+
# Takes a DocListener which will be called with all of the meta-data and documentation
|
4
|
+
# about your app, so as to create documentation in whatever format you want
|
5
|
+
class Doc < Command
|
6
|
+
FORMATS = {
|
7
|
+
'rdoc' => Coglius::Commands::RdocDocumentListener,
|
8
|
+
}
|
9
|
+
# Create the Doc generator based on the Coglius app passed in
|
10
|
+
def initialize(app)
|
11
|
+
super(:names => "_doc",
|
12
|
+
:description => "Generate documentation of your application's UI",
|
13
|
+
:long_desc => "Introspects your application's UI meta-data to generate documentation in a variety of formats. This is intended to be extensible via the DocumentListener interface, so that you can provide your own documentation formats without them being a part of Coglius",
|
14
|
+
:skips_pre => true, :skips_post => true, :skips_around => true, :hidden => true)
|
15
|
+
|
16
|
+
@app = app
|
17
|
+
|
18
|
+
desc 'The format name of the documentation to generate or the class name to use to generate it'
|
19
|
+
default_value 'rdoc'
|
20
|
+
arg_name 'name_or_class'
|
21
|
+
flag :format
|
22
|
+
|
23
|
+
action do |global_options,options,arguments|
|
24
|
+
self.document(format_class(options[:format]).new(global_options,options,arguments))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def nodoc
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Generates documentation using the listener
|
33
|
+
def document(document_listener)
|
34
|
+
document_listener.beginning
|
35
|
+
document_listener.program_desc(@app.program_desc) unless @app.program_desc.nil?
|
36
|
+
document_listener.program_long_desc(@app.program_long_desc) unless @app.program_long_desc.nil?
|
37
|
+
document_listener.version(@app.version_string)
|
38
|
+
if any_options?(@app)
|
39
|
+
document_listener.options
|
40
|
+
end
|
41
|
+
document_flags_and_switches(document_listener,
|
42
|
+
@app.flags.values.sort(&by_name),
|
43
|
+
@app.switches.values.sort(&by_name))
|
44
|
+
if any_options?(@app)
|
45
|
+
document_listener.end_options
|
46
|
+
end
|
47
|
+
document_listener.commands
|
48
|
+
document_commands(document_listener,@app)
|
49
|
+
document_listener.end_commands
|
50
|
+
document_listener.ending
|
51
|
+
end
|
52
|
+
|
53
|
+
# Interface for a listener that is called during various parts of the doc process
|
54
|
+
class DocumentListener
|
55
|
+
# Called before processing begins
|
56
|
+
def beginning
|
57
|
+
abstract!
|
58
|
+
end
|
59
|
+
|
60
|
+
# Called when processing has completed
|
61
|
+
def ending
|
62
|
+
abstract!
|
63
|
+
end
|
64
|
+
|
65
|
+
# Gives you the program description
|
66
|
+
def program_desc(desc)
|
67
|
+
abstract!
|
68
|
+
end
|
69
|
+
|
70
|
+
# Gives you the program long description
|
71
|
+
def program_long_desc(desc)
|
72
|
+
abstract!
|
73
|
+
end
|
74
|
+
|
75
|
+
# Gives you the program version
|
76
|
+
def version(version)
|
77
|
+
abstract!
|
78
|
+
end
|
79
|
+
|
80
|
+
# Called at the start of options for the current context
|
81
|
+
def options
|
82
|
+
abstract!
|
83
|
+
end
|
84
|
+
|
85
|
+
# Called when all options for the current context have been vended
|
86
|
+
def end_options
|
87
|
+
abstract!
|
88
|
+
end
|
89
|
+
|
90
|
+
# Called at the start of commands for the current context
|
91
|
+
def commands
|
92
|
+
abstract!
|
93
|
+
end
|
94
|
+
|
95
|
+
# Called when all commands for the current context have been vended
|
96
|
+
def end_commands
|
97
|
+
abstract!
|
98
|
+
end
|
99
|
+
|
100
|
+
# Gives you a flag in the current context
|
101
|
+
def flag(name,aliases,desc,long_desc,default_value,arg_name,must_match,type)
|
102
|
+
abstract!
|
103
|
+
end
|
104
|
+
|
105
|
+
# Gives you a switch in the current context
|
106
|
+
def switch(name,aliases,desc,long_desc,negetable)
|
107
|
+
abstract!
|
108
|
+
end
|
109
|
+
|
110
|
+
# Gives you the name of the current command in the current context
|
111
|
+
def default_command(name)
|
112
|
+
abstract!
|
113
|
+
end
|
114
|
+
|
115
|
+
# Gives you a command in the current context and creates a new context of this command
|
116
|
+
def command(name,aliases,desc,long_desc,arg_name,arg_options)
|
117
|
+
abstract!
|
118
|
+
end
|
119
|
+
|
120
|
+
# Ends a command, and "pops" you back up one context
|
121
|
+
def end_command(name)
|
122
|
+
abstract!
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
def abstract!
|
127
|
+
raise "Subclass must implement"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def format_class(format_name)
|
134
|
+
FORMATS.fetch(format_name) {
|
135
|
+
begin
|
136
|
+
return format_name.split(/::/).reduce(Kernel) { |context,part| context.const_get(part) }
|
137
|
+
rescue => ex
|
138
|
+
raise IndexError,"Couldn't find formatter or class named #{format_name}"
|
139
|
+
end
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
def document_commands(document_listener,context)
|
144
|
+
context.commands.values.reject {|_| _.nodoc }.sort(&by_name).each do |command|
|
145
|
+
call_command_method_being_backwards_compatible(document_listener,command)
|
146
|
+
document_listener.options if any_options?(command)
|
147
|
+
document_flags_and_switches(document_listener,command_flags(command),command_switches(command))
|
148
|
+
document_listener.end_options if any_options?(command)
|
149
|
+
document_listener.commands if any_commands?(command)
|
150
|
+
document_commands(document_listener,command)
|
151
|
+
document_listener.end_commands if any_commands?(command)
|
152
|
+
document_listener.end_command(command.name)
|
153
|
+
end
|
154
|
+
document_listener.default_command(context.get_default_command)
|
155
|
+
end
|
156
|
+
|
157
|
+
def call_command_method_being_backwards_compatible(document_listener,command)
|
158
|
+
command_args = [command.name,
|
159
|
+
Array(command.aliases),
|
160
|
+
command.description,
|
161
|
+
command.long_description,
|
162
|
+
command.arguments_description]
|
163
|
+
if document_listener.method(:command).arity == 6
|
164
|
+
command_args << command.arguments_options
|
165
|
+
end
|
166
|
+
document_listener.command(*command_args)
|
167
|
+
end
|
168
|
+
|
169
|
+
def by_name
|
170
|
+
lambda { |a,b| a.name.to_s <=> b.name.to_s }
|
171
|
+
end
|
172
|
+
|
173
|
+
def command_flags(command)
|
174
|
+
command.topmost_ancestor.flags.values.select { |flag| flag.associated_command == command }.sort(&by_name)
|
175
|
+
end
|
176
|
+
|
177
|
+
def command_switches(command)
|
178
|
+
command.topmost_ancestor.switches.values.select { |switch| switch.associated_command == command }.sort(&by_name)
|
179
|
+
end
|
180
|
+
|
181
|
+
def document_flags_and_switches(document_listener,flags,switches)
|
182
|
+
flags.each do |flag|
|
183
|
+
document_listener.flag(flag.name,
|
184
|
+
Array(flag.aliases),
|
185
|
+
flag.description,
|
186
|
+
flag.long_description,
|
187
|
+
flag.safe_default_value,
|
188
|
+
flag.argument_name,
|
189
|
+
flag.must_match,
|
190
|
+
flag.type)
|
191
|
+
end
|
192
|
+
switches.each do |switch|
|
193
|
+
document_listener.switch(switch.name,
|
194
|
+
Array(switch.aliases),
|
195
|
+
switch.description,
|
196
|
+
switch.long_description,
|
197
|
+
switch.negatable)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def any_options?(context)
|
202
|
+
options = if context.kind_of?(Command)
|
203
|
+
command_flags(context) + command_switches(context)
|
204
|
+
else
|
205
|
+
context.flags.values + context.switches.values
|
206
|
+
end
|
207
|
+
!options.empty?
|
208
|
+
end
|
209
|
+
|
210
|
+
def any_commands?(command)
|
211
|
+
!command.commands.empty?
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|