nucleon 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/Gemfile +4 -8
  2. data/Gemfile.lock +0 -28
  3. data/README.rdoc +13 -5
  4. data/Rakefile +9 -1
  5. data/VERSION +1 -1
  6. data/bin/nucleon +55 -0
  7. data/lib/core/codes.rb +107 -0
  8. data/lib/core/config/collection.rb +57 -0
  9. data/lib/core/config/options.rb +70 -0
  10. data/lib/core/config.rb +342 -0
  11. data/lib/core/core.rb +54 -0
  12. data/lib/core/errors.rb +84 -0
  13. data/lib/core/facade.rb +283 -0
  14. data/lib/core/gems.rb +80 -0
  15. data/lib/core/manager.rb +594 -0
  16. data/lib/core/mixin/action/commit.rb +58 -0
  17. data/lib/core/mixin/action/project.rb +53 -0
  18. data/lib/core/mixin/action/push.rb +52 -0
  19. data/lib/core/mixin/config/collection.rb +53 -0
  20. data/lib/core/mixin/config/options.rb +39 -0
  21. data/lib/core/mixin/macro/object_interface.rb +361 -0
  22. data/lib/core/mixin/macro/plugin_interface.rb +380 -0
  23. data/lib/core/mixin/settings.rb +46 -0
  24. data/lib/core/mixin/sub_config.rb +148 -0
  25. data/lib/core/mod/hash.rb +29 -0
  26. data/lib/core/plugin/action.rb +371 -0
  27. data/lib/core/plugin/base.rb +313 -0
  28. data/lib/core/plugin/command.rb +98 -0
  29. data/lib/core/plugin/event.rb +53 -0
  30. data/lib/core/plugin/extension.rb +12 -0
  31. data/lib/core/plugin/project.rb +890 -0
  32. data/lib/core/plugin/template.rb +80 -0
  33. data/lib/core/plugin/translator.rb +38 -0
  34. data/lib/core/util/cli.rb +353 -0
  35. data/lib/core/util/console.rb +237 -0
  36. data/lib/core/util/data.rb +404 -0
  37. data/lib/core/util/disk.rb +114 -0
  38. data/lib/core/util/git.rb +43 -0
  39. data/lib/core/util/liquid.rb +17 -0
  40. data/lib/core/util/logger.rb +147 -0
  41. data/lib/core/util/package.rb +93 -0
  42. data/lib/core/util/shell.rb +239 -0
  43. data/lib/nucleon/action/add.rb +69 -0
  44. data/lib/nucleon/action/create.rb +52 -0
  45. data/lib/nucleon/action/extract.rb +49 -0
  46. data/lib/nucleon/action/remove.rb +51 -0
  47. data/lib/nucleon/action/save.rb +53 -0
  48. data/lib/nucleon/action/update.rb +37 -0
  49. data/lib/nucleon/command/bash.rb +146 -0
  50. data/lib/nucleon/event/regex.rb +52 -0
  51. data/lib/nucleon/project/git.rb +465 -0
  52. data/lib/nucleon/project/github.rb +108 -0
  53. data/lib/nucleon/template/json.rb +16 -0
  54. data/lib/nucleon/template/wrapper.rb +16 -0
  55. data/lib/nucleon/template/yaml.rb +16 -0
  56. data/lib/nucleon/translator/json.rb +27 -0
  57. data/lib/nucleon/translator/yaml.rb +27 -0
  58. data/lib/nucleon.rb +18 -15
  59. data/locales/en.yml +3 -132
  60. data/nucleon.gemspec +66 -27
  61. data/spec/core/util/console_spec.rb +489 -0
  62. metadata +109 -96
@@ -0,0 +1,371 @@
1
+
2
+ module Nucleon
3
+ module Plugin
4
+ class Action < Base
5
+
6
+ #-----------------------------------------------------------------------------
7
+ # Default option interface
8
+
9
+ class Option
10
+ def initialize(provider, name, type, default, locale = nil, &validator)
11
+ @provider = provider
12
+ @name = name
13
+ @type = type
14
+ @default = default
15
+ @locale = locale.nil? ? "nucleon.actions.#{provider}.options.#{name}" : locale
16
+ @validator = validator if validator
17
+ end
18
+
19
+ #---
20
+
21
+ attr_reader :provider, :name, :type
22
+ attr_accessor :default, :locale, :validator
23
+
24
+ #---
25
+
26
+ def validate(value, *args)
27
+ success = true
28
+ if @validator
29
+ success = @validator.call(value, *args)
30
+ end
31
+ success
32
+ end
33
+ end
34
+
35
+ #-----------------------------------------------------------------------------
36
+ # Action plugin interface
37
+
38
+ def self.exec_safe(provider, options)
39
+ action_result = nil
40
+
41
+ begin
42
+ logger = Nucleon.logger
43
+
44
+ logger.debug("Running nucleon action #{provider} with #{options.inspect}")
45
+ action = Nucleon.action(provider, options)
46
+ exit_status = action.execute
47
+ action_result = action.result
48
+
49
+ rescue Exception => error
50
+ logger.error("Nucleon action #{provider} experienced an error:")
51
+ logger.error(error.inspect)
52
+ logger.error(error.message)
53
+ logger.error(Nucleon::Util::Data.to_yaml(error.backtrace))
54
+
55
+ Nucleon.ui.error(error.message, { :prefix => false }) if error.message
56
+
57
+ exit_status = error.status_code if error.respond_to?(:status_code)
58
+ end
59
+
60
+ Nucleon.remove_plugin(action) if action
61
+
62
+ exit_status = Nucleon.code.unknown_status unless exit_status.is_a?(Integer)
63
+ { :status => exit_status, :result => action_result }
64
+ end
65
+
66
+ def self.exec(provider, options, quiet = true)
67
+ exec_safe(provider, { :settings => Config.ensure(options), :quiet => quiet })
68
+ end
69
+
70
+ def self.exec_cli(provider, args, quiet = false)
71
+ results = exec_safe(provider, { :args => args, :quiet => quiet })
72
+ results[:status]
73
+ end
74
+
75
+ #---
76
+
77
+ def normalize
78
+ args = array(delete(:args, []))
79
+
80
+ @action_interface = Util::Liquid.new do |method, method_args|
81
+ options = {}
82
+ options = method_args[0] if method_args.length > 0
83
+
84
+ quiet = true
85
+ quiet = method_args[1] if method_args.length > 1
86
+
87
+ myself.class.exec(method, options, quiet)
88
+ end
89
+
90
+ set(:config, Config.new)
91
+
92
+ if get(:settings, nil)
93
+ # Internal processing
94
+ configure
95
+ set(:processed, true)
96
+ set(:settings, Config.ensure(get(:settings)))
97
+
98
+ Nucleon.log_level = settings[:log_level] if settings.has_key?(:log_level)
99
+ else
100
+ # External processing
101
+ set(:settings, Config.new)
102
+ configure
103
+ parse_base(args)
104
+ end
105
+ end
106
+
107
+ #-----------------------------------------------------------------------------
108
+ # Checks
109
+
110
+ def processed?
111
+ get(:processed, false)
112
+ end
113
+
114
+ #-----------------------------------------------------------------------------
115
+ # Property accessor / modifiers
116
+
117
+ def config
118
+ get(:config)
119
+ end
120
+
121
+ #---
122
+
123
+ def config_subset(names)
124
+ Util::Data.subset(config, names)
125
+ end
126
+
127
+ #---
128
+
129
+ def settings
130
+ get(:settings)
131
+ end
132
+
133
+ #---
134
+
135
+ def register(name, type, default, locale = nil)
136
+ name = name.to_sym
137
+
138
+ if block_given?
139
+ option = Option.new(plugin_provider, name, type, default, locale) do |value, success|
140
+ yield(value, success)
141
+ end
142
+ else
143
+ option = Option.new(plugin_provider, name, type, default, locale)
144
+ end
145
+
146
+ config[name] = option
147
+ settings[name] = option.default if settings[name].nil?
148
+ end
149
+
150
+ #---
151
+
152
+ def remove(names)
153
+ Util::Data.rm_keys(config, names)
154
+ Util::Data.rm_keys(settings, names)
155
+ end
156
+
157
+ #---
158
+
159
+ def ignore
160
+ []
161
+ end
162
+
163
+ def options
164
+ config.keys - arguments - ignore
165
+ end
166
+
167
+ def arguments
168
+ []
169
+ end
170
+
171
+ #---
172
+
173
+ def configure(executable_name = 'nucleon')
174
+ yield if block_given?
175
+
176
+ usage = "#{executable_name} #{plugin_provider} "
177
+ arguments.each do |arg|
178
+ arg_config = config[arg.to_sym]
179
+
180
+ if arg_config.type == :array
181
+ usage << "<#{arg}> ..."
182
+ else
183
+ usage << "<#{arg}> "
184
+ end
185
+ end
186
+ myself.usage = usage
187
+ myself
188
+ end
189
+
190
+ #---
191
+
192
+ def usage=usage
193
+ set(:usage, usage)
194
+ end
195
+
196
+ def usage
197
+ get(:usage, '')
198
+ end
199
+
200
+ #---
201
+
202
+ def help
203
+ return @parser.help if @parser
204
+ usage
205
+ end
206
+
207
+ #---
208
+
209
+ def result=result
210
+ set(:result, result)
211
+ end
212
+
213
+ def result
214
+ get(:result, nil)
215
+ end
216
+
217
+ #-----------------------------------------------------------------------------
218
+ # Operations
219
+
220
+ def parse_base(args)
221
+ logger.info("Parsing action #{plugin_provider} with: #{args.inspect}")
222
+
223
+ @parser = Util::CLI::Parser.new(args, usage) do |parser|
224
+ parse(parser)
225
+ extension(:parse, { :parser => parser, :config => config })
226
+ end
227
+
228
+ if @parser
229
+ if @parser.processed
230
+ set(:processed, true)
231
+ settings.import(Util::Data.merge([ @parser.options, @parser.arguments ], true))
232
+ logger.debug("Parse successful: #{export.inspect}")
233
+
234
+ elsif @parser.options[:help] && ! quiet?
235
+ puts I18n.t('nucleon.core.exec.help.usage') + ': ' + help + "\n"
236
+
237
+ else
238
+ if @parser.options[:help]
239
+ logger.debug("Help wanted but running in silent mode")
240
+ else
241
+ logger.warn("Parse failed for unknown reasons")
242
+ end
243
+ end
244
+ end
245
+ end
246
+
247
+ #---
248
+
249
+ def parse(parser)
250
+
251
+ generate = lambda do |format, name|
252
+ formats = [ :option, :arg ]
253
+ types = [ :bool, :int, :float, :str, :array ]
254
+ name = name.to_sym
255
+
256
+ if config.export.has_key?(name) && formats.include?(format.to_sym)
257
+ option_config = config[name]
258
+ type = option_config.type
259
+ default = option_config.default
260
+ locale = option_config.locale
261
+
262
+ if types.include?(type.to_sym)
263
+ value_label = "#{type.to_s.upcase}"
264
+
265
+ if type == :bool
266
+ parser.send("option_#{type}", name, default, "--[no-]#{name}", locale)
267
+ elsif format == :arg
268
+ parser.send("#{format}_#{type}", name, default, locale)
269
+ else
270
+ if type == :array
271
+ parser.send("option_#{type}", name, default, "--#{name} #{value_label},...", locale)
272
+ else
273
+ parser.send("option_#{type}", name, default, "--#{name} #{value_label}", locale)
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ #---
281
+
282
+ options.each do |name|
283
+ generate.call(:option, name)
284
+ end
285
+
286
+ arguments.each do |name|
287
+ generate.call(:arg, name)
288
+ end
289
+ end
290
+
291
+ #---
292
+
293
+ def validate
294
+ # TODO: Add extension hooks and logging
295
+
296
+ # Validate all of the configurations
297
+ success = true
298
+ config.export.each do |name, option|
299
+ success = false unless option.validate(settings[name])
300
+ end
301
+ if success
302
+ # Check for missing arguments (in case of internal execution mode)
303
+ arguments.each do |name|
304
+ if settings[name.to_sym].nil?
305
+ warn('nucleon.core.exec.errors.missing_argument', { :name => name })
306
+ success = false
307
+ end
308
+ end
309
+ end
310
+ success
311
+ end
312
+
313
+ #---
314
+
315
+ def execute
316
+ logger.info("Executing action #{plugin_provider}")
317
+
318
+ myself.status = code.success
319
+ myself.result = nil
320
+
321
+ if processed?
322
+ begin
323
+ yield if block_given? && extension_check(:exec_init)
324
+ myself.status = extension_set(:exec_exit)
325
+ ensure
326
+ cleanup
327
+ end
328
+ else
329
+ if @parser.options[:help]
330
+ myself.status = code.help_wanted
331
+ else
332
+ myself.status = code.action_unprocessed
333
+ end
334
+ end
335
+
336
+ myself.status = code.unknown_status unless status.is_a?(Integer)
337
+
338
+ if processed? && status != code.success
339
+ logger.warn("Execution failed for #{plugin_provider} with status #{status}: #{export.inspect}")
340
+ alert(Codes.render_index(status))
341
+ end
342
+
343
+ status
344
+ end
345
+
346
+ #---
347
+
348
+ def run
349
+ @action_interface
350
+ end
351
+
352
+ #---
353
+
354
+ def cleanup
355
+ logger.info("Running cleanup for action #{plugin_provider}")
356
+
357
+ yield if block_given?
358
+
359
+ # Nothing to do right now
360
+ extension(:cleanup)
361
+ end
362
+
363
+ #-----------------------------------------------------------------------------
364
+ # Output
365
+
366
+ def render_options
367
+ settings
368
+ end
369
+ end
370
+ end
371
+ end
@@ -0,0 +1,313 @@
1
+
2
+ module Nucleon
3
+ module Plugin
4
+ class Base < Core
5
+
6
+ # All Plugin classes should directly or indirectly extend Base
7
+
8
+ def initialize(type, provider, options)
9
+ config = Util::Data.clean(Config.ensure(options))
10
+ name = Util::Data.ensure_value(config.delete(:plugin_name), config.delete(:name, provider))
11
+
12
+ @quiet = config.delete(:quiet, false)
13
+
14
+ set_meta(config.delete(:meta, Config.new))
15
+
16
+ # No logging statements aove this line!!
17
+ super(config.import({ :logger => "#{plugin_type}->#{plugin_provider}" }))
18
+ myself.plugin_name = name
19
+
20
+ logger.debug("Normalizing #{plugin_type} plugin #{plugin_name} with meta data: #{meta.inspect}")
21
+ normalize
22
+ end
23
+
24
+ #---
25
+
26
+ def method_missing(method, *args, &block)
27
+ return nil
28
+ end
29
+
30
+ #-----------------------------------------------------------------------------
31
+ # Checks
32
+
33
+ def initialized?(options = {})
34
+ return true
35
+ end
36
+
37
+ #---
38
+
39
+ def quiet?
40
+ @quiet
41
+ end
42
+
43
+ #-----------------------------------------------------------------------------
44
+ # Property accessor / modifiers
45
+
46
+ def myself
47
+ return current_actor if respond_to?(:current_actor) # Celluloid enhanced plugin
48
+ self
49
+ end
50
+ alias_method :me, :myself
51
+
52
+ #---
53
+
54
+ def quiet=quiet
55
+ @quiet = quiet
56
+ end
57
+
58
+ #---
59
+
60
+ def meta
61
+ return @meta
62
+ end
63
+
64
+ #---
65
+
66
+ def set_meta(meta)
67
+ @meta = Config.ensure(meta)
68
+ end
69
+
70
+ #---
71
+
72
+ def plugin_namespace
73
+ return meta.get(:namespace)
74
+ end
75
+
76
+ #---
77
+
78
+ def plugin_type
79
+ return meta.get(:type)
80
+ end
81
+
82
+ #---
83
+
84
+ def plugin_provider
85
+ return meta.get(:provider)
86
+ end
87
+
88
+ #---
89
+
90
+ def plugin_name
91
+ return meta.get(:name)
92
+ end
93
+
94
+ def plugin_name=plugin_name
95
+ meta.set(:name, string(plugin_name))
96
+ end
97
+
98
+ #---
99
+
100
+ def plugin_directory
101
+ return meta.get(:directory)
102
+ end
103
+
104
+ #---
105
+
106
+ def plugin_file
107
+ return meta.get(:file)
108
+ end
109
+
110
+ #---
111
+
112
+ def plugin_instance_name
113
+ return meta.get(:instance_name)
114
+ end
115
+
116
+ #---
117
+
118
+ def plugin_parent=parent
119
+ meta.set(:parent, parent) if parent.is_a?(Nucleon::Plugin::Base)
120
+ end
121
+
122
+ def plugin_parent
123
+ return meta.get(:parent)
124
+ end
125
+
126
+ #-----------------------------------------------------------------------------
127
+ # Status codes
128
+
129
+ def code
130
+ Nucleon.code
131
+ end
132
+
133
+ def codes(*codes)
134
+ Nucleon.codes(*codes)
135
+ end
136
+
137
+ #---
138
+
139
+ def status=status
140
+ meta.set(:status, status)
141
+ end
142
+
143
+ def status
144
+ meta.get(:status, code.unknown_status)
145
+ end
146
+
147
+ #-----------------------------------------------------------------------------
148
+ # Plugin operations
149
+
150
+ def normalize
151
+ # Implement in sub classes
152
+ end
153
+
154
+ #-----------------------------------------------------------------------------
155
+ # Extensions
156
+
157
+ def hook_method(hook)
158
+ "#{plugin_type}_#{plugin_provider}_#{hook}"
159
+ end
160
+
161
+ #---
162
+
163
+ def extension(hook, options = {}, &code)
164
+ Nucleon.exec(hook_method(hook), Config.ensure(options).import({ :plugin => myself }), &code)
165
+ end
166
+
167
+ #---
168
+
169
+ def extended_config(type, options = {})
170
+ Nucleon.config(type, Config.ensure(options).import({ :plugin => myself }))
171
+ end
172
+
173
+ #---
174
+
175
+ def extension_check(hook, options = {})
176
+ Nucleon.check(hook_method(hook), Config.ensure(options).import({ :plugin => myself }))
177
+ end
178
+
179
+ #---
180
+
181
+ def extension_set(hook, value, options = {})
182
+ Nucleon.set(hook_method(hook), value, Config.ensure(options).import({ :plugin => myself }))
183
+ end
184
+
185
+ #---
186
+
187
+ def extension_collect(hook, options = {})
188
+ Nucleon.collect(hook_method(hook), Config.ensure(options).import({ :plugin => myself }))
189
+ end
190
+
191
+ #-----------------------------------------------------------------------------
192
+ # Output
193
+
194
+ def render_options
195
+ export
196
+ end
197
+ protected :render_options
198
+
199
+ #---
200
+
201
+ def render(display, options = {})
202
+ ui.info(display.strip, options) unless quiet? || display.strip.empty?
203
+ end
204
+
205
+ #---
206
+
207
+ def info(name, options = {})
208
+ ui.info(I18n.t(name, Util::Data.merge([ Config.ensure(render_options).export, options ], true))) unless quiet?
209
+ end
210
+
211
+ #---
212
+
213
+ def alert(display, options = {})
214
+ ui.warn(display.strip, options) unless quiet? || display.strip.empty?
215
+ end
216
+
217
+ #---
218
+
219
+ def warn(name, options = {})
220
+ ui.warn(I18n.t(name, Util::Data.merge([ Config.ensure(render_options).export, options ], true))) unless quiet?
221
+ end
222
+
223
+ #---
224
+
225
+ def error(name, options = {})
226
+ ui.error(I18n.t(name, Util::Data.merge([ Config.ensure(render_options).export, options ], true))) unless quiet?
227
+ end
228
+
229
+ #---
230
+
231
+ def success(name, options = {})
232
+ ui.success(I18n.t(name, Util::Data.merge([ Config.ensure(render_options).export, options ], true))) unless quiet?
233
+ end
234
+
235
+ #-----------------------------------------------------------------------------
236
+ # Utilities
237
+
238
+ def self.build_info(type, data)
239
+ plugins = []
240
+
241
+ if data.is_a?(Hash)
242
+ data = [ data ]
243
+ end
244
+
245
+ logger.debug("Building plugin list of #{type} from data: #{data.inspect}")
246
+
247
+ if data.is_a?(Array)
248
+ data.each do |info|
249
+ unless Util::Data.empty?(info)
250
+ info = translate(info)
251
+
252
+ if Util::Data.empty?(info[:provider])
253
+ info[:provider] = Nucleon.type_default(type)
254
+ end
255
+
256
+ logger.debug("Translated plugin info: #{info.inspect}")
257
+
258
+ plugins << info
259
+ end
260
+ end
261
+ end
262
+ return plugins
263
+ end
264
+
265
+ #---
266
+
267
+ def self.translate(data)
268
+ logger.debug("Translating data to internal plugin structure: #{data.inspect}")
269
+ return ( data.is_a?(Hash) ? symbol_map(data) : {} )
270
+ end
271
+
272
+ #---
273
+
274
+ def self.init_plugin_collection
275
+ logger.debug("Initializing plugin collection interface at #{Time.now}")
276
+
277
+ include Celluloid
278
+ include Mixin::Settings
279
+ include Mixin::SubConfig
280
+
281
+ extend Mixin::Macro::PluginInterface
282
+ end
283
+
284
+ #---
285
+
286
+ def safe_exec(return_result = true, &code)
287
+ begin
288
+ result = code.call
289
+ return result if return_result
290
+ return true
291
+
292
+ rescue Exception => error
293
+ logger.error(error.inspect)
294
+ logger.error(error.message)
295
+
296
+ ui.error(error.message, { :prefix => false }) if error.message
297
+ end
298
+ return false
299
+ end
300
+
301
+ #---
302
+
303
+ def admin_exec(return_result = true, &code)
304
+ if Nucleon.admin?
305
+ safe_exec(return_result, &code) if block_given?
306
+ else
307
+ ui.warn("The #{plugin_provider} action must be run as a machine administrator")
308
+ myself.status = code.access_denied
309
+ end
310
+ end
311
+ end
312
+ end
313
+ end