coglius 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/Cogfile +15 -0
  2. data/LICENSE +201 -0
  3. data/lib/coglius.rb +29 -0
  4. data/lib/coglius/app.rb +250 -0
  5. data/lib/coglius/app_support.rb +284 -0
  6. data/lib/coglius/command.rb +149 -0
  7. data/lib/coglius/command_line_option.rb +34 -0
  8. data/lib/coglius/command_line_token.rb +62 -0
  9. data/lib/coglius/command_support.rb +214 -0
  10. data/lib/coglius/commands/compound_command.rb +42 -0
  11. data/lib/coglius/commands/doc.rb +215 -0
  12. data/lib/coglius/commands/help.rb +73 -0
  13. data/lib/coglius/commands/help_modules/arg_name_formatter.rb +20 -0
  14. data/lib/coglius/commands/help_modules/command_finder.rb +60 -0
  15. data/lib/coglius/commands/help_modules/command_help_format.rb +138 -0
  16. data/lib/coglius/commands/help_modules/global_help_format.rb +70 -0
  17. data/lib/coglius/commands/help_modules/help_completion_format.rb +31 -0
  18. data/lib/coglius/commands/help_modules/list_formatter.rb +23 -0
  19. data/lib/coglius/commands/help_modules/one_line_wrapper.rb +18 -0
  20. data/lib/coglius/commands/help_modules/options_formatter.rb +49 -0
  21. data/lib/coglius/commands/help_modules/text_wrapper.rb +53 -0
  22. data/lib/coglius/commands/help_modules/tty_only_wrapper.rb +23 -0
  23. data/lib/coglius/commands/help_modules/verbatim_wrapper.rb +16 -0
  24. data/lib/coglius/commands/initconfig.rb +69 -0
  25. data/lib/coglius/commands/rdoc_document_listener.rb +116 -0
  26. data/lib/coglius/commands/scaffold.rb +401 -0
  27. data/lib/coglius/copy_options_to_aliases.rb +33 -0
  28. data/lib/coglius/dsl.rb +221 -0
  29. data/lib/coglius/exceptions.rb +54 -0
  30. data/lib/coglius/flag.rb +68 -0
  31. data/lib/coglius/gli_option_parser.rb +124 -0
  32. data/lib/coglius/option_parser_factory.rb +45 -0
  33. data/lib/coglius/options.rb +23 -0
  34. data/lib/coglius/switch.rb +35 -0
  35. data/lib/coglius/terminal.rb +94 -0
  36. data/lib/coglius/version.rb +5 -0
  37. data/templates/coglius/generator.rb.erb +26 -0
  38. 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