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