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,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
|