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,146 @@
1
+
2
+ module Nucleon
3
+ module Command
4
+ class Bash < Plugin::Command
5
+
6
+ #-----------------------------------------------------------------------------
7
+ # Command plugin interface
8
+
9
+ def normalize
10
+ super
11
+ myself.command = executable(myself)
12
+ logger.info("Setting command executable to #{command}")
13
+ end
14
+
15
+ #-----------------------------------------------------------------------------
16
+ # Command operations
17
+
18
+ def build(components = {}, overrides = nil, override_key = false)
19
+ command = string(components[:command])
20
+ flags = array( components.has_key?(:flags) ? components[:flags] : [] )
21
+ data = string_map(hash( components.has_key?(:data) ? components[:data] : {} ))
22
+ args = array( components.has_key?(:args) ? components[:args] : [] )
23
+ subcommand = hash( components.has_key?(:subcommand) ? components[:subcommand] : {} )
24
+
25
+ override_key = command unless override_key
26
+ override_key = override_key.to_sym
27
+
28
+ command_string = command.dup
29
+ subcommand_string = ''
30
+
31
+ escape_characters = /[\'\"]+/
32
+ escape_replacement = '\"'
33
+
34
+ dash_pattern = /^([\-]+)/
35
+ assignment_pattern = /\=$/
36
+
37
+ logger.info("Building command #{command}")
38
+ logger.debug("Command flags: #{flags.inspect}")
39
+ logger.debug("Command options: #{data.inspect}")
40
+ logger.debug("Command arguments: #{args.inspect}")
41
+ logger.debug("Command has sub command") unless subcommand.empty?
42
+
43
+ logger.debug("Overrides: #{overrides.inspect}")
44
+ logger.debug("Override key: #{override_key}")
45
+
46
+ # Flags
47
+ if overrides && overrides.has_key?(:flags)
48
+ if overrides[:flags].is_a?(Hash)
49
+ if overrides[:flags].has_key?(override_key)
50
+ flags = array(overrides[:flags][override_key])
51
+ end
52
+ else
53
+ flags = array(overrides[:flags])
54
+ end
55
+ end
56
+ flags.each do |flag|
57
+ flag = string(flag)
58
+ if ! flag.empty?
59
+ if flag.match(dash_pattern)
60
+ dashes = $1
61
+ else
62
+ dashes = ( flag.size == 1 ? '-' : '--' )
63
+ end
64
+ command_string << " #{dashes}#{flag}"
65
+ end
66
+ end
67
+
68
+ # Data
69
+ if overrides && overrides.has_key?(:data)
70
+ if overrides[:data].has_key?(override_key)
71
+ data = hash(overrides[:data][override_key])
72
+ else
73
+ override = true
74
+ overrides[:data].each do |key, value|
75
+ if ! value.is_a?(String)
76
+ override = false
77
+ end
78
+ end
79
+ data = hash(overrides[:data]) if override
80
+ end
81
+ end
82
+ data.each do |key, value|
83
+ key = string(key)
84
+ value = string(value).strip.sub(escape_characters, escape_replacement)
85
+
86
+ if key.match(dash_pattern)
87
+ dashes = $1
88
+ else
89
+ dashes = ( key.size == 1 ? '-' : '--' )
90
+ end
91
+ space = ( key.match(assignment_pattern) ? '' : ' ' )
92
+
93
+ command_string << " #{dashes}#{key}#{space}\"#{value}\""
94
+ end
95
+
96
+ # Arguments
97
+ if overrides && overrides.has_key?(:args)
98
+ unless overrides[:args].empty?
99
+ if overrides[:args].is_a?(Hash)
100
+ if overrides[:args].has_key?(override_key)
101
+ args = array(overrides[:args][override_key])
102
+ end
103
+ else
104
+ args = array(overrides[:args])
105
+ end
106
+ end
107
+ end
108
+ args.each do |arg|
109
+ arg = string(arg).sub(escape_characters, escape_replacement)
110
+
111
+ unless arg.empty?
112
+ command_string << " \"#{arg}\""
113
+ end
114
+ end
115
+
116
+ # Subcommand
117
+ subcommand_overrides = ( overrides ? overrides[:subcommand] : nil )
118
+ if subcommand && subcommand.is_a?(Hash) && ! subcommand.empty?
119
+ subcommand_string = build(subcommand, subcommand_overrides)
120
+ end
121
+
122
+ command_string = (command_string + ' ' + subcommand_string).strip
123
+
124
+ logger.debug("Rendered command: #{command_string}")
125
+ return command_string
126
+ end
127
+
128
+ #---
129
+
130
+ def exec(options = {}, overrides = nil, &code)
131
+ config = Config.ensure(options)
132
+ Nucleon.cli_run(build(export, overrides), config.import({ :ui => @ui }), &code)
133
+ end
134
+
135
+ #-----------------------------------------------------------------------------
136
+ # Utilities
137
+
138
+ def executable(options)
139
+ config = Config.ensure(options)
140
+
141
+ return 'nucleon ' + config[:nucleon] if config.get(:nucleon, false)
142
+ config[:command]
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,52 @@
1
+
2
+ module Nucleon
3
+ module Event
4
+ class Regex < Plugin::Event
5
+
6
+ #-----------------------------------------------------------------------------
7
+ # Regular expression event interface
8
+
9
+ def normalize
10
+ super
11
+
12
+ if get(:string)
13
+ myself.pattern = delete(:string)
14
+ end
15
+ end
16
+
17
+ #-----------------------------------------------------------------------------
18
+ # Property accessors / modifiers
19
+
20
+ def pattern(default = '')
21
+ return get(:pattern, default)
22
+ end
23
+
24
+ #---
25
+
26
+ def pattern=pattern
27
+ set(:pattern, string(pattern))
28
+ end
29
+
30
+ #-----------------------------------------------------------------------------
31
+ # Operations
32
+
33
+ def render
34
+ return "#{name}:#{pattern}"
35
+ end
36
+
37
+ #---
38
+
39
+ def check(source)
40
+ if pattern.empty?
41
+ logger.warn("Can not check regex pattern because it is empty")
42
+ else
43
+ success = source.match(/#{pattern}/)
44
+
45
+ logger.debug("Checking regex event with pattern #{pattern}: #{success.inspect}")
46
+ return success
47
+ end
48
+ return true
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,465 @@
1
+
2
+ module Nucleon
3
+ module Project
4
+ class Git < Plugin::Project
5
+
6
+ #-----------------------------------------------------------------------------
7
+ # Project plugin interface
8
+
9
+ def normalize
10
+ super
11
+ end
12
+
13
+ #-----------------------------------------------------------------------------
14
+ # Git interface (local)
15
+
16
+ def ensure_git(reset = false)
17
+ if reset || @git_lib.nil?
18
+ @git_lib = nil
19
+
20
+ if directory.empty?
21
+ logger.warn("Can not manage Git project at #{directory} as it does not exist")
22
+ else
23
+ logger.debug("Ensuring Git instance to manage #{directory}")
24
+ @git_lib = Util::Git.new(directory)
25
+
26
+ if ! @git_lib.nil? && get(:create, false)
27
+ unless File.directory?(directory) && @git_lib.git.exist?
28
+ FileUtils.mkdir_p(directory) unless File.directory?(directory)
29
+ @git_lib.git.init({ :bare => false })
30
+ end
31
+ end
32
+ end
33
+ end
34
+ return myself
35
+ end
36
+ protected :ensure_git
37
+
38
+ #-----------------------------------------------------------------------------
39
+ # Checks
40
+
41
+ def can_persist?
42
+ ensure_git
43
+ return true unless @git_lib.nil?
44
+ return false
45
+ end
46
+
47
+ #---
48
+
49
+ def top?(path)
50
+ git_dir = File.join(path, '.git')
51
+ if File.exist?(git_dir)
52
+ return true if File.directory?(git_dir)
53
+ elsif File.exist?(path) && (path =~ /\.git$/ && File.exist?(File.join(path, 'HEAD')))
54
+ return true
55
+ end
56
+ return false
57
+ end
58
+
59
+ #---
60
+
61
+ def subproject?(path)
62
+ git_dir = File.join(path, '.git')
63
+ if File.exist?(git_dir)
64
+ unless File.directory?(git_dir)
65
+ git_dir = Util::Disk.read(git_dir)
66
+ unless git_dir.nil?
67
+ git_dir = git_dir.gsub(/^gitdir\:\s*/, '').strip
68
+ return true if File.directory?(git_dir)
69
+ end
70
+ end
71
+ end
72
+ return false
73
+ end
74
+
75
+ #---
76
+
77
+ def project_directory?(path, require_top_level = false)
78
+ path = File.expand_path(path)
79
+ git_dir = File.join(path, '.git')
80
+
81
+ if File.exist?(git_dir)
82
+ if File.directory?(git_dir)
83
+ return true
84
+ elsif ! require_top_level
85
+ git_dir = Util::Disk.read(git_dir)
86
+ unless git_dir.nil?
87
+ git_dir = git_dir.gsub(/^gitdir\:\s*/, '').strip
88
+ return true if File.directory?(git_dir)
89
+ end
90
+ end
91
+ elsif File.exist?(path) && (path =~ /\.git$/ && File.exist?(File.join(path, 'HEAD')))
92
+ return true
93
+ end
94
+ return false
95
+ end
96
+
97
+ #---
98
+
99
+ def new?(reset = false)
100
+ if get(:new, nil).nil? || reset
101
+ set(:new, git.native(:rev_parse, { :all => true }).empty?)
102
+ end
103
+ get(:new, false)
104
+ end
105
+
106
+ #-----------------------------------------------------------------------------
107
+ # Property accessors / modifiers
108
+
109
+ def lib
110
+ return @git_lib
111
+ end
112
+
113
+ #---
114
+
115
+ def git
116
+ return lib.git if can_persist?
117
+ return nil
118
+ end
119
+ protected :git
120
+
121
+ #---
122
+
123
+ def set_location(directory)
124
+ super do
125
+ ensure_git(true)
126
+ end
127
+ return myself
128
+ end
129
+
130
+ #---
131
+
132
+ def config(name, options = {})
133
+ return super do |config|
134
+ git.config(config.export, name)
135
+ end
136
+ end
137
+
138
+ #---
139
+
140
+ def set_config(name, value, options = {})
141
+ return super do |config, processed_value|
142
+ git.config(config.export, name, processed_value)
143
+ end
144
+ end
145
+
146
+ #---
147
+
148
+ def delete_config(name, options = {})
149
+ return super do |config|
150
+ git.config(config.import({ :remove_section => true }).export, name)
151
+ end
152
+ end
153
+
154
+ #---
155
+
156
+ def subproject_config(options = {})
157
+ return super do |config|
158
+ result = {}
159
+
160
+ if new?
161
+ logger.debug("Project has no sub project configuration yet (has not been committed to)")
162
+ else
163
+ commit = lib.commit(revision)
164
+ blob = commit.tree/'.gitmodules' unless commit.nil?
165
+
166
+ if blob
167
+ logger.debug("Houston, we have a Git blob!")
168
+
169
+ lines = blob.data.gsub(/\r\n?/, "\n" ).split("\n")
170
+ current = nil
171
+
172
+ lines.each do |line|
173
+ if line =~ /^\[submodule "(.+)"\]$/
174
+ current = $1
175
+ result[current] = {}
176
+ result[current]['id'] = (commit.tree/current).id
177
+
178
+ logger.debug("Reading: #{current}")
179
+
180
+ elsif line =~ /^\t(\w+) = (.+)$/
181
+ result[current][$1] = $2
182
+ result[current]['id'] = (commit.tree/$2).id if $1 == 'path'
183
+ end
184
+ end
185
+ end
186
+ end
187
+ result
188
+ end
189
+ end
190
+
191
+ #-----------------------------------------------------------------------------
192
+ # Basic Git operations
193
+
194
+ def load_revision
195
+ return super do
196
+ if new?
197
+ logger.debug("Project has no current revision yet (has not been committed to)")
198
+ nil
199
+
200
+ else
201
+ current_revision = git.native(:rev_parse, { :abbrev_ref => true }, 'HEAD').strip
202
+
203
+ logger.debug("Current revision: #{current_revision}")
204
+ current_revision
205
+ end
206
+ end
207
+ end
208
+
209
+ #---
210
+
211
+ def checkout(revision)
212
+ return super do |success|
213
+ if new?
214
+ logger.debug("Project can not be checked out (has not been committed to)")
215
+ else
216
+ unless lib.bare
217
+ success = safe_exec(false) do
218
+ git.checkout({ :raise => true }, revision)
219
+ end
220
+ end
221
+ end
222
+ success
223
+ end
224
+ end
225
+
226
+ #---
227
+
228
+ def commit(files = '.', options = {})
229
+ return super do |config, time, user, message|
230
+ safe_exec(false) do
231
+ git.reset({}, 'HEAD') # Clear the index so we get a clean commit
232
+
233
+ files = array(files)
234
+
235
+ logger.debug("Adding files to Git index")
236
+
237
+ git.add({ :raise => true }, files) # Get all added and updated files
238
+ git.add({ :update => true, :raise => true }, files) # Get all deleted files
239
+
240
+ commit_options = {
241
+ :raise => true,
242
+ :m => "#{time} by <#{user}> - #{message}",
243
+ :allow_empty => config.get(:allow_empty, false)
244
+ }
245
+ commit_options[:author] = config[:author] if config.get(:author, false)
246
+
247
+ logger.debug("Composing commit options: #{commit_options.inspect}")
248
+ git.commit(commit_options)
249
+
250
+ new?(true)
251
+ end
252
+ end
253
+ end
254
+
255
+ #-----------------------------------------------------------------------------
256
+ # Subproject operations
257
+
258
+ def load_subprojects(options = {})
259
+ return super do |project_path, data|
260
+ File.exist?(File.join(project_path, '.git'))
261
+ end
262
+ end
263
+
264
+ #---
265
+
266
+ def add_subproject(path, url, revision, options = {})
267
+ return super do |config|
268
+ safe_exec(false) do
269
+ branch_options = ''
270
+ branch_options = [ '-b', config[:revision] ] if config.get(:revision, false)
271
+
272
+ path = config[:path]
273
+ url = config[:url]
274
+
275
+ git.submodule({ :raise => true }, 'add', *branch_options, url, path)
276
+
277
+ config.set(:files, [ '.gitmodules', path ])
278
+ end
279
+ end
280
+ end
281
+
282
+ #---
283
+
284
+ def delete_subproject(path)
285
+ return super do |config|
286
+ safe_exec(false) do
287
+ path = config[:path]
288
+ submodule_key = "submodule.#{path}"
289
+
290
+ logger.debug("Deleting Git configurations for #{submodule_key}")
291
+ delete_config(submodule_key)
292
+ delete_config(submodule_key, { :file => '.gitmodules' })
293
+
294
+ logger.debug("Cleaning Git index cache for #{path}")
295
+ git.rm({ :cached => true }, path)
296
+
297
+ logger.debug("Removing Git submodule directories")
298
+ FileUtils.rm_rf(File.join(directory, path))
299
+ FileUtils.rm_rf(File.join(git.git_dir, 'modules', path))
300
+
301
+ config.set(:files, [ '.gitmodules', path ])
302
+ end
303
+ end
304
+ end
305
+
306
+ #---
307
+
308
+ def update_subprojects
309
+ return super do
310
+ safe_exec(false) do
311
+ git.submodule({ :raise => true, :timeout => false }, 'update', '--init', '--recursive')
312
+ end
313
+ end
314
+ end
315
+
316
+ #-----------------------------------------------------------------------------
317
+ # Remote operations
318
+
319
+ def init_remotes
320
+ return super do
321
+ origin_url = config('remote.origin.url').strip
322
+
323
+ logger.debug("Original origin remote url: #{origin_url}")
324
+ origin_url
325
+ end
326
+ end
327
+
328
+ #---
329
+
330
+ def remote(name)
331
+ return super do
332
+ url = config("remote.#{name}.url").strip
333
+ url.empty? ? nil : url
334
+ end
335
+ end
336
+
337
+ #---
338
+
339
+ def set_remote(name, url)
340
+ return super do |processed_url|
341
+ safe_exec(false) do
342
+ git.remote({ :raise => true }, 'add', name.to_s, processed_url)
343
+ end
344
+ end
345
+ end
346
+
347
+ #---
348
+
349
+ def add_remote_url(name, url, options = {})
350
+ return super do |config, processed_url|
351
+ safe_exec(false) do
352
+ git.remote({
353
+ :raise => true,
354
+ :add => true,
355
+ :delete => config.get(:delete, false),
356
+ :push => config.get(:push, false)
357
+ }, 'set-url', name.to_s, processed_url)
358
+ end
359
+ end
360
+ end
361
+
362
+ #---
363
+
364
+ def delete_remote(name)
365
+ return super do
366
+ if config("remote.#{name}.url").empty?
367
+ logger.debug("Project can not delete remote #{name} because it does not exist yet")
368
+ true
369
+ else
370
+ safe_exec(false) do
371
+ git.remote({ :raise => true }, 'rm', name.to_s)
372
+ end
373
+ end
374
+ end
375
+ end
376
+
377
+ #---
378
+
379
+ def syncronize(cloud, options = {})
380
+ return super do |config|
381
+ config.init(:remote_path, '/var/git')
382
+ config.set(:add, true)
383
+ end
384
+ end
385
+
386
+ #-----------------------------------------------------------------------------
387
+ # SSH operations
388
+
389
+ def pull(remote = :origin, options = {})
390
+ return super do |config, processed_remote|
391
+ flags = []
392
+ flags << :tags if config.get(:tags, true)
393
+
394
+ result = Nucleon.command({
395
+ :command => :git,
396
+ :data => { 'git-dir=' => git.git_dir },
397
+ :subcommand => {
398
+ :command => :pull,
399
+ :flags => flags,
400
+ :args => [ processed_remote, config.get(:revision, get(:revision, :master)) ]
401
+ }
402
+ }, config.get(:provider, :shell)).exec(config) do |op, command, data|
403
+ block_given? ? yield(op, command, data) : true
404
+ end
405
+
406
+ if result.status == code.success
407
+ new?(true)
408
+ true
409
+ else
410
+ false
411
+ end
412
+ end
413
+ end
414
+
415
+ #---
416
+
417
+ def push(remote = :edit, options = {})
418
+ return super do |config, processed_remote|
419
+ push_branch = config.get(:revision, '')
420
+
421
+ flags = []
422
+ flags << :all if push_branch.empty?
423
+ flags << :tags if ! push_branch.empty? && config.get(:tags, true)
424
+
425
+ result = Nucleon.command({
426
+ :command => :git,
427
+ :data => { 'git-dir=' => git.git_dir },
428
+ :subcommand => {
429
+ :command => :push,
430
+ :flags => flags,
431
+ :args => [ processed_remote, push_branch ]
432
+ }
433
+ }, config.get(:provider, :shell)).exec(config) do |op, command, data|
434
+ block_given? ? yield(op, command, data) : true
435
+ end
436
+
437
+ result.status == code.success
438
+ end
439
+ end
440
+
441
+ #-----------------------------------------------------------------------------
442
+ # Utilities
443
+
444
+ def translate_url(host, path, options = {})
445
+ return super do |config|
446
+ user = config.get(:user, 'git')
447
+ auth = config.get(:auth, true)
448
+
449
+ user + (auth ? '@' : '://') + host + (auth ? ':' : '/') + path
450
+ end
451
+ end
452
+
453
+ #---
454
+
455
+ def translate_edit_url(url, options = {})
456
+ return super do |config|
457
+ if matches = url.strip.match(/^(https?|git)\:\/\/([^\/]+)\/(.+)/)
458
+ protocol, host, path = matches.captures
459
+ translate_url(host, path, config.import({ :auth => true }))
460
+ end
461
+ end
462
+ end
463
+ end
464
+ end
465
+ end