nucleon 0.1.0 → 0.1.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 (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