rubycut-babushka 0.10.8 → 0.15.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. data/Gemfile +1 -0
  2. data/Gemfile.lock +17 -15
  3. data/README.markdown +163 -41
  4. data/Rakefile +1 -1
  5. data/bin/babushka +1 -1
  6. data/deps/apt.rb +44 -0
  7. data/deps/babushka.rb +54 -42
  8. data/deps/deprecated.rb +16 -0
  9. data/deps/dev.rb +28 -3
  10. data/deps/git.rb +27 -12
  11. data/deps/homebrew.rb +2 -2
  12. data/deps/packages.rb +14 -15
  13. data/deps/pkg_managers.rb +21 -75
  14. data/deps/ruby.rb +5 -19
  15. data/deps/rubygems.rb +3 -3
  16. data/deps/system.rb +2 -2
  17. data/deps/templates/app.rb +60 -41
  18. data/deps/templates/bin.rb +16 -0
  19. data/deps/templates/installer.rb +9 -9
  20. data/deps/templates/lib.rb +17 -0
  21. data/deps/templates/managed.rb +1 -38
  22. data/deps/templates/src.rb +16 -8
  23. data/deps/templates/task.rb +11 -0
  24. data/deps/templates/tmbundle.rb +16 -2
  25. data/lib/babushka.rb +2 -3
  26. data/lib/babushka/accepts_block_for.rb +5 -3
  27. data/lib/babushka/asset.rb +172 -0
  28. data/lib/babushka/base.rb +37 -8
  29. data/lib/babushka/bug_reporter.rb +6 -6
  30. data/lib/babushka/cmdline.rb +11 -10
  31. data/lib/babushka/cmdline/handler.rb +7 -3
  32. data/lib/babushka/cmdline/helpers.rb +15 -23
  33. data/lib/babushka/cmdline/parser.rb +1 -1
  34. data/lib/babushka/core_patches/object.rb +1 -1
  35. data/lib/babushka/core_patches/string.rb +8 -3
  36. data/lib/babushka/current_ruby.rb +44 -0
  37. data/lib/babushka/dep.rb +111 -185
  38. data/lib/babushka/dep_context.rb +8 -3
  39. data/lib/babushka/dep_definer.rb +45 -15
  40. data/lib/babushka/dep_pool.rb +5 -8
  41. data/lib/babushka/{meta_dep.rb → dep_template.rb} +21 -2
  42. data/lib/babushka/dsl.rb +3 -0
  43. data/lib/babushka/git_repo.rb +143 -49
  44. data/lib/babushka/helpers/git_helpers.rb +7 -6
  45. data/lib/babushka/helpers/log_helpers.rb +51 -13
  46. data/lib/babushka/helpers/path_helpers.rb +5 -7
  47. data/lib/babushka/helpers/run_helpers.rb +15 -55
  48. data/lib/babushka/helpers/shell_helpers.rb +18 -26
  49. data/lib/babushka/helpers/uri_helpers.rb +9 -18
  50. data/lib/babushka/lambda_chooser.rb +20 -13
  51. data/lib/babushka/parameter.rb +20 -4
  52. data/lib/babushka/path_checker.rb +72 -0
  53. data/lib/babushka/pkg_helper.rb +38 -13
  54. data/lib/babushka/pkg_helpers/apt_helper.rb +15 -8
  55. data/lib/babushka/pkg_helpers/binpkgsrc_helper.rb +15 -14
  56. data/lib/babushka/pkg_helpers/binports_helper.rb +7 -7
  57. data/lib/babushka/pkg_helpers/brew_helper.rb +17 -25
  58. data/lib/babushka/pkg_helpers/gem_helper.rb +36 -27
  59. data/lib/babushka/pkg_helpers/npm_helper.rb +9 -9
  60. data/lib/babushka/pkg_helpers/pacman_helper.rb +5 -4
  61. data/lib/babushka/pkg_helpers/pip_helper.rb +14 -10
  62. data/lib/babushka/pkg_helpers/unknown_pkg_helper.rb +19 -0
  63. data/lib/babushka/pkg_helpers/yum_helper.rb +1 -1
  64. data/lib/babushka/popen.rb +13 -10
  65. data/lib/babushka/prompt.rb +14 -1
  66. data/lib/babushka/renderable.rb +11 -9
  67. data/lib/babushka/resource.rb +5 -166
  68. data/lib/babushka/run_reporter.rb +12 -3
  69. data/lib/babushka/shell.rb +54 -44
  70. data/lib/babushka/source.rb +41 -20
  71. data/lib/babushka/source_pool.rb +20 -13
  72. data/lib/babushka/system_definitions.rb +11 -3
  73. data/lib/babushka/system_detector.rb +31 -0
  74. data/lib/babushka/system_matcher.rb +53 -0
  75. data/lib/babushka/system_profile.rb +67 -89
  76. data/lib/babushka/task.rb +36 -8
  77. data/lib/babushka/{meta_dep_context.rb → templated_dep_context.rb} +1 -1
  78. data/lib/babushka/vars.rb +46 -4
  79. data/lib/babushka/version_of.rb +35 -17
  80. data/lib/babushka/version_str.rb +12 -8
  81. data/lib/components.rb +9 -8
  82. data/lib/fancypath/fancypath.rb +109 -83
  83. data/lib/inkan/inkan.rb +14 -14
  84. data/lib/{babushka → levenshtein}/levenshtein.rb +0 -0
  85. data/spec/acceptance/acceptance.rb +4 -4
  86. data/spec/acceptance_helper.rb +10 -6
  87. data/spec/babushka/accepts_for_spec.rb +137 -142
  88. data/spec/babushka/accepts_for_support.rb +13 -6
  89. data/spec/babushka/asset_spec.rb +165 -0
  90. data/spec/babushka/cmdline/help_spec.rb +11 -9
  91. data/spec/babushka/cmdline/meet_spec.rb +15 -0
  92. data/spec/babushka/cmdline/version_spec.rb +1 -1
  93. data/spec/babushka/core_patches_spec.rb +9 -0
  94. data/spec/babushka/current_ruby_spec.rb +73 -0
  95. data/spec/babushka/dep_context_spec.rb +27 -13
  96. data/spec/babushka/dep_definer_spec.rb +108 -16
  97. data/spec/babushka/dep_spec.rb +87 -104
  98. data/spec/babushka/dep_template_spec.rb +176 -0
  99. data/spec/babushka/deps_spec.rb +48 -19
  100. data/spec/babushka/gem_helper_spec.rb +46 -59
  101. data/spec/babushka/git_repo_spec.rb +242 -51
  102. data/spec/babushka/ip_spec.rb +11 -11
  103. data/spec/babushka/lambda_chooser_spec.rb +47 -9
  104. data/spec/babushka/parameter_spec.rb +21 -0
  105. data/spec/babushka/path_checker_spec.rb +35 -0
  106. data/spec/babushka/path_helpers_spec.rb +51 -50
  107. data/spec/babushka/prompt_spec.rb +4 -4
  108. data/spec/babushka/renderable_spec.rb +61 -28
  109. data/spec/babushka/shell_helpers_spec.rb +110 -85
  110. data/spec/babushka/shell_spec.rb +15 -0
  111. data/spec/babushka/source_pool_spec.rb +204 -210
  112. data/spec/babushka/source_spec.rb +125 -42
  113. data/spec/babushka/source_support.rb +1 -1
  114. data/spec/babushka/system_profile_spec.rb +86 -49
  115. data/spec/babushka/task_spec.rb +80 -13
  116. data/spec/babushka/vars_spec.rb +2 -1
  117. data/spec/babushka/version_of_spec.rb +29 -2
  118. data/spec/babushka/version_str_spec.rb +91 -65
  119. data/spec/babushka/xml_string_spec.rb +1 -1
  120. data/spec/deps/bad/broken.rb +2 -2
  121. data/spec/deps/bad/working.rb +0 -1
  122. data/spec/deps/good/{meta.rb → template.rb} +0 -0
  123. data/spec/deps/good/test.rb +0 -3
  124. data/spec/deps/outer/deps.rb +0 -2
  125. data/spec/fancypath/fancypath_spec.rb +30 -0
  126. data/spec/inkan/inkan_spec.rb +34 -32
  127. data/spec/spec_helper.rb +7 -50
  128. data/spec/system_detector_spec.rb +70 -0
  129. metadata +163 -177
  130. data/deps/os_x.rb +0 -33
  131. data/deps/templates/ppa.rb +0 -24
  132. data/lib/babushka/core_patches/io.rb +0 -8
  133. data/lib/babushka/dep_runner.rb +0 -85
  134. data/lib/babushka/helpers/suggest_helpers.rb +0 -16
  135. data/lib/babushka/pkg_helpers/base_helper.rb +0 -19
  136. data/lib/babushka/pkg_helpers/macports_helper.rb +0 -22
  137. data/spec/babushka/dep_definer_support.rb +0 -36
  138. data/spec/babushka/meta_dep_definer_spec.rb +0 -127
  139. data/spec/babushka/meta_dep_wrapper_spec.rb +0 -32
  140. data/spec/babushka/resource_spec.rb +0 -141
  141. data/spec/babushka/run_helpers_spec.rb +0 -26
  142. data/spec/babushka/source_pool_support.rb +0 -31
data/lib/babushka/base.rb CHANGED
@@ -1,7 +1,32 @@
1
1
  module Babushka
2
+
3
+ # +host+ is an instance of Babushka::SystemProfile for the system the command
4
+ # was invoked on.
5
+ # If the current system isn't supported, SystemProfile.for_host will return
6
+ # +nil+, and Base.run will fail early. If the system is known but the
7
+ # flavour isn't (e.g. an unknown Linux variant), a generic SystemProfile
8
+ # will be used, which should work for most operations but will fail on deps
9
+ # that attempt to use the package manager, etc.
10
+ def host
11
+ @host ||= Babushka::SystemDetector.profile_for_host
12
+ end
13
+ module_function :host
14
+
15
+ def ruby
16
+ @ruby ||= Babushka::CurrentRuby.new
17
+ end
18
+ module_function :ruby
19
+
2
20
  class Base
3
21
  class << self
4
22
 
23
+ # The time at which babushka was loaded. This is used in LogHelpers to
24
+ # print profiling information via the '--profile' commandline option.
25
+ @@start_time = Time.now
26
+ def start_time
27
+ @@start_time
28
+ end
29
+
5
30
  # +task+ represents the overall job that is being run, and the parts that
6
31
  # are external to running the corresponding dep tree itself - logging, and
7
32
  # var loading and saving in particular.
@@ -17,15 +42,8 @@ module Babushka
17
42
  @cmdline ||= Cmdline::Parser.for(ARGV)
18
43
  end
19
44
 
20
- # +host+ is an instance of Babushka::SystemProfile for the system the command
21
- # was invoked on.
22
- # If the current system isn't supported, SystemProfile.for_host will return
23
- # +nil+, and Base.run will fail early. If the system is known but the
24
- # flavour isn't (e.g. an unknown Linux variant), a generic SystemProfile
25
- # will be used, which should work for most operations but will fail on deps
26
- # that attempt to use the package manager, etc.
27
45
  def host
28
- @host ||= Babushka::SystemProfile.for_host
46
+ Babushka::LogHelpers.removed! :method_name => 'Babushka::Base.host', :instead => "Babushka.host"
29
47
  end
30
48
 
31
49
  # +sources+ is an instance of Babushka::SourcePool, contains all the
@@ -70,6 +88,17 @@ module Babushka
70
88
  end
71
89
  end
72
90
 
91
+ def runtime_info
92
+ @runtime_info ||= "babushka@#{ref} | #{ShellHelpers.which('ruby')}@#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
93
+ end
94
+
95
+ def ref
96
+ @ref ||= begin
97
+ repo = GitRepo.new(Path.path)
98
+ repo.current_head if repo.exists?
99
+ end
100
+ end
101
+
73
102
  def program_name
74
103
  @program_name ||= ENV['PATH'].split(':').include?(File.dirname($0)) ? File.basename($0) : $0
75
104
  end
@@ -39,16 +39,16 @@ module Babushka
39
39
  if response.is_a? Net::HTTPSuccess
40
40
  gist_id = response.body.scan(/<repo>(\d+)<\/repo>/).flatten.first
41
41
  if gist_id.nil?
42
- log "Done, but the report's URL couldn't be parsed. Here's some info:"
43
- log response.body
42
+ log_stderr "Done, but the report's URL couldn't be parsed. Here's some info:"
43
+ log_stderr response.body
44
44
  else
45
45
  log "You can view the report at http://gist.github.com/#{gist_id} - thanks :)"
46
46
  end
47
47
  else
48
- log "Deary me, the bug report couldn't be submitted! Would you mind emailing these two files:"
49
- log ' ' + Base.task.var_path_for(dep)
50
- log ' ' + Base.task.log_path_for(dep)
51
- log "to ben@hoskings.net? Thanks."
48
+ log_stderr "Deary me, the bug report couldn't be submitted! Would you mind emailing these two files:"
49
+ log_stderr ' ' + Base.task.var_path_for(dep)
50
+ log_stderr ' ' + Base.task.log_path_for(dep)
51
+ log_stderr "to ben@hoskings.net? Thanks."
52
52
  end
53
53
  end
54
54
  end
@@ -1,13 +1,14 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module Babushka
4
- module Cmdline
4
+ class Cmdline
5
5
  extend LogHelpers
6
6
 
7
7
  handle('global', "Options that are valid for any handler") {
8
8
  opt '-v', '--version', "Print the current version"
9
9
  opt '-h', '--help', "Show this information"
10
10
  opt '-d', '--debug', "Show more verbose logging, and realtime shell command output"
11
+ opt '-s', '--silent', "Only log errors, running silently on success"
11
12
  opt '--[no-]color',
12
13
  '--[no-]colour', "Disable color in the output"
13
14
  }
@@ -19,13 +20,13 @@ module Babushka
19
20
  Helpers.print_handlers
20
21
  Helpers.print_notes
21
22
  elsif (handler = Handler.for(cmd.argv.first)).nil?
22
- Helpers.log "#{cmd.argv.first.capitalize}? I have honestly never heard of that."
23
+ log "#{cmd.argv.first.capitalize}? I have honestly never heard of that."
23
24
  else
24
- Helpers.log "\n#{handler.name} - #{handler.description}"
25
+ log "\n#{handler.name} - #{handler.description}"
25
26
  cmd.parse(&handler.opt_definer)
26
27
  cmd.print_usage
27
28
  end
28
- Helpers.log "\n"
29
+ log "\n"
29
30
  true
30
31
  }
31
32
 
@@ -45,17 +46,15 @@ module Babushka
45
46
  handle('meet', 'The main one: run a dep and all its dependencies.') {
46
47
  opt '-n', '--dry-run', "Discover the curent state without making any changes"
47
48
  opt '-y', '--defaults', "Assume the default value for all vars without prompting, where possible"
49
+ opt '-u', '--update', "Update referenced sources before loading deps from them"
48
50
  opt '--show-args', "Show the arguments being passed between deps as they're run"
49
- opt '--track-blocks', "Track deps' blocks in TextMate as they're run"
51
+ opt '--profile', "Print a per-line timestamp to the debug log."
50
52
  }.run {|cmd|
51
- # TODO: spec var parsing
52
53
  dep_names, vars = cmd.argv.partition {|arg| arg['='].nil? }
53
54
  if !(bad_var = vars.detect {|var| var[/^\w+=/].nil? }).nil?
54
55
  fail_with "'#{bad_var}' looks like a var but it doesn't make sense."
55
56
  elsif dep_names.empty?
56
57
  fail_with "Nothing to do."
57
- elsif cmd.opts[:track_blocks] && !which('mate')
58
- fail_with "The --track-blocks option requires TextMate, and the `mate` helper.\nOn a Mac, you can install them like so:\n babushka benhoskings:textmate"
59
58
  else
60
59
  Base.task.process dep_names, vars.map {|i|
61
60
  i.split('=', 2)
@@ -117,9 +116,11 @@ module Babushka
117
116
  elsif dep.load_path.nil?
118
117
  fail_with "Can't edit '#{dep.name}, since it wasn't loaded from a file."
119
118
  else
120
- file, line = dep.context.file_and_line
121
- editor_var = ENV['BABUSHKA_EDITOR'] || ENV['VISUAL'] || ENV['EDITOR'] || which('mate') || which('vim') || which('vi')
119
+ file, line = dep.context.source_location
120
+ editor_var = ENV['BABUSHKA_EDITOR'] || ENV['VISUAL'] || ENV['EDITOR'] || which('subl') || which('mate') || which('vim') || which('vi')
122
121
  case editor_var
122
+ when /^subl/
123
+ exec "subl -n '#{file}':#{line}"
123
124
  when /^mate/
124
125
  exec "mate -l#{line} '#{file}'"
125
126
  when /^vim?/, /^nano/, /^pico/, /^emacs/
@@ -1,12 +1,16 @@
1
1
  require 'abbrev'
2
2
 
3
3
  module Babushka
4
- module Cmdline
4
+ class Cmdline
5
5
 
6
- def handle name, description, &blk
6
+ def self.handle name, description, &blk
7
7
  Handler.add name, description, blk
8
8
  end
9
- module_function :handle
9
+
10
+ def self.fail_with message
11
+ log message
12
+ exit 1
13
+ end
10
14
 
11
15
  class Handler
12
16
  def self.add name, description, opt_definer
@@ -1,27 +1,19 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module Babushka
4
- module Cmdline
5
- module_function
6
-
7
- def fail_with message
8
- log message if message.is_a? String
9
- exit 1
10
- end
11
-
12
- module Helpers
4
+ class Cmdline
5
+ class Helpers
13
6
  extend LogHelpers
14
- module_function
15
7
 
16
- def print_version opts = {}
8
+ def self.print_version opts = {}
17
9
  if opts[:full]
18
- log "Babushka v#{VERSION}, (c) 2011 Ben Hoskings <ben@hoskings.net>"
10
+ log "Babushka v#{VERSION} (#{Base.ref}), (c) 2012 Ben Hoskings <ben@hoskings.net>"
19
11
  else
20
- log VERSION
12
+ log "#{VERSION} (#{Base.ref})"
21
13
  end
22
14
  end
23
15
 
24
- def print_usage
16
+ def self.print_usage
25
17
  log "\nThe gist:"
26
18
  log " #{Base.program_name} <command> [options]"
27
19
  log "\nAlso:"
@@ -30,14 +22,14 @@ module Babushka
30
22
  log " #{Base.program_name} babushka # Update babushka itself (what babushka.me/up does)"
31
23
  end
32
24
 
33
- def print_handlers
25
+ def self.print_handlers
34
26
  log "\nCommands:"
35
27
  Handler.all.each {|handler|
36
28
  log " #{handler.name.ljust(10)} #{handler.description}"
37
29
  }
38
30
  end
39
31
 
40
- def print_examples
32
+ def self.print_examples
41
33
  log "\nExamples:"
42
34
  log " # Inspect the 'system' dep (and all its sub-deps) without touching the system.".colorize('grey')
43
35
  log " #{Base.program_name} system --dry-run"
@@ -50,12 +42,12 @@ module Babushka
50
42
  log " #{Base.program_name} 'user setup' --debug"
51
43
  end
52
44
 
53
- def print_notes
45
+ def self.print_notes
54
46
  log "\nCommands can be abbrev'ed, as long as they remain unique."
55
47
  log " e.g. '#{Base.program_name} l' is short for '#{Base.program_name} list'."
56
48
  end
57
49
 
58
- def search_results_for q
50
+ def self.search_results_for q
59
51
  YAML.load(search_webservice_for(q).body).sort_by {|i|
60
52
  -i[:runs_this_week]
61
53
  }.map {|i|
@@ -69,7 +61,7 @@ module Babushka
69
61
  }
70
62
  end
71
63
 
72
- def print_search_results search_term, results
64
+ def self.print_search_results search_term, results
73
65
  log "The webservice knows about #{results.length} dep#{'s' unless results.length == 1} that match#{'es' if results.length == 1} '#{search_term}':"
74
66
  log ""
75
67
  Logging.log_table(
@@ -85,17 +77,17 @@ module Babushka
85
77
  end
86
78
  end
87
79
 
88
- def github_autosource_regex
89
- /^git\:\/\/github\.com\/(.*)\/babushka-deps(\.git)?/
80
+ def self.github_autosource_regex
81
+ /^\w+\:\/\/github\.com\/(.*)\/babushka-deps(\.git)?/
90
82
  end
91
83
 
92
- def search_webservice_for q
84
+ def self.search_webservice_for q
93
85
  Net::HTTP.start('babushka.me') {|http|
94
86
  http.get URI.escape("/deps/search.yaml/#{q}")
95
87
  }
96
88
  end
97
89
 
98
- def generate_list_for to_list, filter_str
90
+ def self.generate_list_for to_list, filter_str
99
91
  context = to_list == :deps ? Base.program_name : ':template =>'
100
92
  match_str = filter_str.try(:downcase)
101
93
  Base.sources.all_present.each {|source|
@@ -1,7 +1,7 @@
1
1
  require 'optparse'
2
2
 
3
3
  module Babushka
4
- module Cmdline
4
+ class Cmdline
5
5
  class Parser
6
6
  include LogHelpers
7
7
 
@@ -11,7 +11,7 @@ class Object
11
11
  # neatly working with an object in some way before returning it:
12
12
  # def valmorphanize
13
13
  # process.tap {|result|
14
- # log "Oops!" unless result
14
+ # log_error "Oops!" unless result
15
15
  # }
16
16
  # end
17
17
  def tap &block
@@ -1,14 +1,15 @@
1
1
  class String
2
- # Return a Dep::Requirement that specifies the dep that should later be
2
+ # Return a DepRequirement that specifies the dep that should later be
3
3
  # called, and the arguments that should be passed. This allows requiring
4
4
  # deps with a less noisy syntax, and the lookup is lazy (it happens at
5
- # the point the dep is invoked, from its parent dep in Dep#process_deps).
5
+ # the point the dep is invoked, from its parent dep in
6
+ # Dep#process_requirements).
6
7
  #
7
8
  # dep 'user has a password', :username do
8
9
  # requires 'user exists'.with(username)
9
10
  # end
10
11
  def with *args
11
- Babushka::Dep::Requirement.new(self, args)
12
+ Babushka::DepRequirement.new(self, args)
12
13
  end
13
14
 
14
15
  # Returns true iff +other+ appears exactly at the start of +self+.
@@ -84,6 +85,10 @@ class String
84
85
  Babushka::VersionStr.new self
85
86
  end
86
87
 
88
+ def colorized?
89
+ self[/\e\[\d/]
90
+ end
91
+
87
92
  # Return a new string with the contents of this string surrounded in escape
88
93
  # sequences such that it will render as described in +description+.
89
94
  # Some examples:
@@ -0,0 +1,44 @@
1
+ module Babushka
2
+ class CurrentRuby
3
+
4
+ def path
5
+ @path ||= Babushka::ShellHelpers.which('ruby').p
6
+ end
7
+
8
+ def rbenv?
9
+ path.to_s[%r{\brbenv/}]
10
+ end
11
+
12
+ def rvm?
13
+ path.to_s[%r{\brvm/}]
14
+ end
15
+
16
+ def bin_dir
17
+ # The directory in which the binaries from gems are found. This is
18
+ # sometimes different to where `gem` itself is running from.
19
+ gem_env.val_for('EXECUTABLE DIRECTORY').p
20
+ end
21
+
22
+ def gem_dir
23
+ gem_env.val_for('INSTALLATION DIRECTORY') / 'gems'
24
+ end
25
+
26
+ def gemspec_dir
27
+ gem_env.val_for('INSTALLATION DIRECTORY') / 'specifications'
28
+ end
29
+
30
+ def version
31
+ @_version ||= Babushka::ShellHelpers.shell('ruby --version').scan(/^ruby (\S+)/).flatten.first.to_version
32
+ end
33
+
34
+ def gem_version
35
+ gem_env.val_for('RUBYGEMS VERSION').to_version
36
+ end
37
+
38
+ private
39
+
40
+ def gem_env
41
+ @_gem_env ||= Babushka::ShellHelpers.shell('gem env')
42
+ end
43
+ end
44
+ end
data/lib/babushka/dep.rb CHANGED
@@ -13,31 +13,19 @@ module Babushka
13
13
  class DepArgumentError < DepDefinitionError
14
14
  end
15
15
 
16
+ # A DepRequirement is a representation of a dep being called - its name,
17
+ # along with the arguments that will be passed to it.
18
+ #
19
+ # DepRequirement is used internally by babushka when deps are required with
20
+ # arguments using "name".with(args). This allows babushka to delay loading
21
+ # the dep in question until the moment it's called.
22
+ class DepRequirement < Struct.new(:name, :args)
23
+ end
24
+
25
+
16
26
  class Dep
17
27
  include LogHelpers
18
28
  extend LogHelpers
19
- include PathHelpers
20
- extend SuggestHelpers
21
-
22
- # This class is used for deps that aren't defined against a meta dep. Using
23
- # this class with the default values it contains means that the code below
24
- # can be simpler, because at the code level everything is defined against
25
- # a 'template' of some sort; some are just BaseTemplate, and some are
26
- # actual meta deps.
27
- class BaseTemplate
28
- def self.contextual_name; name end
29
- def self.suffixed?; false end
30
- def self.context_class; DepContext end
31
- end
32
-
33
- # A Requirement is a representation of a dep being called - its name, along
34
- # with the arguments that will be passed to it.
35
- #
36
- # Requirement is used internally by babushka when deps are required with
37
- # arguments using "name".with(args). This allows babushka to delay loading
38
- # the dep in question until the moment it's called.
39
- class Requirement < Struct.new(:name, :args)
40
- end
41
29
 
42
30
  attr_reader :name, :params, :args, :opts, :vars, :dep_source, :load_path
43
31
  attr_accessor :result_message
@@ -48,7 +36,7 @@ module Babushka
48
36
  def initialize name, source, params, opts, block
49
37
  if name.empty?
50
38
  raise InvalidDepName, "Deps can't have empty names."
51
- elsif /\A[[:print:]]+\z/i !~ name
39
+ elsif /[[:cntrl:]]/mu =~ name
52
40
  raise InvalidDepName, "The dep name '#{name}' contains nonprintable characters."
53
41
  elsif /\// =~ name
54
42
  raise InvalidDepName, "The dep name '#{name}' contains '/', which isn't allowed (logs are named after deps, and filenames can't contain '/')."
@@ -66,24 +54,25 @@ module Babushka
66
54
  @dep_source = source
67
55
  @load_path = Base.sources.current_load_path
68
56
  @dep_source.deps.register self
69
- assign_template if Base.sources.current_real_load_source.nil?
70
- @dep_defined = @_cached_process = nil # false represents failure for these two.
71
57
  end
72
58
  end
73
59
 
74
60
  def context
75
- define! if @context.nil?
76
- @context
61
+ @context ||= template.context_class.new(self, &@block)
77
62
  end
78
63
 
64
+ # Attempt to retrieve the template specified in +opts[:template]+. If the
65
+ # template name includes a source prefix, it is searched for within the
66
+ # corresponding source. Otherwise, it is searched for in the current source
67
+ # and the core sources.
79
68
  def template
80
- assign_template if @template.nil?
81
- @template
82
- end
83
-
84
- # Returns true if +#define!+ has aready successfully run on this dep.
85
- def dep_defined?
86
- @dep_defined
69
+ @template ||= if opts[:template]
70
+ Base.sources.template_for(opts[:template], :from => dep_source).tap {|t|
71
+ raise TemplateNotFound, "There is no template named '#{opts[:template]}' to define '#{name}' against." if t.nil?
72
+ }
73
+ else
74
+ Base.sources.template_for(suffix, :from => dep_source) || self.class.base_template
75
+ end
87
76
  end
88
77
 
89
78
  # Look up the dep specified by +dep_name+, yielding it to the block if it
@@ -93,9 +82,9 @@ module Babushka
93
82
  # this same method on the one chosen by the user, if any.
94
83
  def self.find_or_suggest dep_name, opts = {}, &block
95
84
  if (dep = Dep(dep_name, opts)).nil?
96
- log "#{dep_name.to_s.colorize 'grey'} #{"<- this dep isn't defined!".colorize('red')}"
97
- suggestion = suggest_value_for(dep_name, Base.sources.current_names)
98
- Dep.find_or_suggest suggestion, opts, &block unless suggestion.nil?
85
+ log_stderr "#{dep_name.to_s.colorize 'grey'} #{"<- this dep isn't defined!".colorize('red')}"
86
+ suggestions = Base.sources.current_names.similar_to(dep_name.to_s)
87
+ log "Perhaps you meant #{suggestions.map {|s| "'#{s}'" }.to_list(:conj => 'or')}?".colorize('grey') if suggestions.any?
99
88
  elsif block.nil?
100
89
  dep
101
90
  else
@@ -135,7 +124,11 @@ module Babushka
135
124
  # suffix, if any. Unlike +#basename+, this method will return anything that
136
125
  # looks like a template suffix, even if it doesn't match a template.
137
126
  def suffix
138
- name.scan(MetaDep::TEMPLATE_NAME_MATCH).flatten.first
127
+ name.scan(DepTemplate::TEMPLATE_NAME_MATCH).flatten.first
128
+ end
129
+
130
+ def cache_key
131
+ DepRequirement.new(name, @params.map {|p| @args[p].try(:current_value) })
139
132
  end
140
133
 
141
134
  def with *args
@@ -146,6 +139,7 @@ module Babushka
146
139
  end.map_values {|k,v|
147
140
  Parameter.for(k, v)
148
141
  }
142
+ @context = nil # To re-evaluate parameter.default() and friends.
149
143
  self
150
144
  end
151
145
 
@@ -156,15 +150,13 @@ module Babushka
156
150
  # altering the system. It can cause failures, though, because some deps
157
151
  # have requirements that need to be met before the dep can perform its
158
152
  # +met?+ check.
159
- #
160
- # TODO: In future, there will be support for specifying that in the DSL.
161
153
  def met? *args
162
- with(*args).process :dry_run => true, :top_level => true
154
+ with(*args).process :dry_run => true
163
155
  end
164
156
 
165
157
  # Entry point for a full met?/meet +#process+ run.
166
158
  def meet *args
167
- with(*args).process :dry_run => false, :top_level => true
159
+ with(*args).process :dry_run => false
168
160
  end
169
161
 
170
162
  # Trigger a dep run with this dep at the top of the tree.
@@ -195,7 +187,7 @@ module Babushka
195
187
  # example, if a dep detects that the existing version of a package is
196
188
  # broken in some way that requires manual intervention, then there's no
197
189
  # use running the +meet+ block. In this circumstance, you can call
198
- # +#unmeetable+, which raises an +UnmeetableDep+ exception. Babushka will
190
+ # +#unmeetable!+, which raises an +UnmeetableDep+ exception. Babushka will
199
191
  # rescue it and consider the dep unmeetable (that is, it will just allow
200
192
  # the dep to fail without attempting to meet it).
201
193
  #
@@ -222,64 +214,21 @@ module Babushka
222
214
  # webserver is running, for example by using `netstat` to check that
223
215
  # something is listening on port 80.
224
216
  def process with_opts = {}
225
- task.opts.update with_opts
226
- (cached? ? cached_result : process_and_cache).tap {
227
- Base.sources.uncache! if with_opts[:top_level]
228
- }
229
- end
230
-
231
- private
232
-
233
- def define_or_complain!
234
- @dep_defined = begin
235
- define!
236
- rescue StandardError => e
237
- log_exception_in_dep(e)
238
- false
239
- end
240
- end
241
-
242
- # Attempt to look up the template this dep was defined against (or if no
243
- # template was specified, BaseTemplate), and then define the dep against
244
- # it. If an error occurs, the backtrace point within the dep from which the
245
- # exception was triggered is logged, as well as the actual exception point.
246
- def define!
247
- if dep_defined?
248
- debug "#{name}: already defined."
249
- elsif dep_defined? == false
250
- debug "#{name}: defining already failed."
251
- elsif template
252
- debug "(defining #{name} against #{template.contextual_name})"
253
- define_dep!
254
- end
255
- dep_defined?
217
+ Base.task.cache { process_with_caching(with_opts) }
256
218
  end
257
219
 
258
- # Create a context for this dep from its template, and then process the
259
- # dep's outer block in that context.
260
- #
261
- # This results in the details of the dep being stored, like the
262
- # implementation of +met?+ and +meet+, as well as its +requires+ list and
263
- # any other items defined at the top level.
264
- def define_dep!
265
- @context = template.context_class.new self, &@block
266
- context.define!
267
- @dep_defined = true
220
+ def process_with_caching with_opts = {}
221
+ Base.task.opts.update with_opts
222
+ Base.task.cached(
223
+ cache_key, :hit => lambda {|value| log_cached(value) }
224
+ ) {
225
+ log logging_name, :closing_status => (Base.task.opt(:dry_run) ? :dry_run : true) do
226
+ process!
227
+ end
228
+ }
268
229
  end
269
230
 
270
- # Attempt to retrieve the template specified in +opts[:template]+. If the
271
- # template name includes a source prefix, it is searched for within the
272
- # corresponding source. Otherwise, it is searched for in the current source
273
- # and the core sources.
274
- def assign_template
275
- @template = if opts[:template]
276
- Base.sources.template_for(opts[:template], :from => dep_source).tap {|t|
277
- raise TemplateNotFound, "There is no template named '#{opts[:template]}' to define '#{name}' against." if t.nil?
278
- }
279
- else
280
- Base.sources.template_for(suffix, :from => dep_source) || self.class.base_template
281
- end
282
- end
231
+ private
283
232
 
284
233
  def self.base_template
285
234
  BaseTemplate
@@ -301,32 +250,22 @@ module Babushka
301
250
  if !args.empty? && args.length != params.length
302
251
  raise DepArgumentError, "The dep '#{name}' accepts #{params.length} argument#{'s' unless params.length == 1}, but #{args.length} #{args.length == 1 ? 'was' : 'were'} passed."
303
252
  end
304
- params.inject({}) {|hsh,param| hsh[param] = args.shift; hsh }
253
+ Hash[params.zip(args)]
305
254
  end
306
255
 
307
- def process_and_cache
308
- log logging_name, :closing_status => (task.opt(:dry_run) ? :dry_run : true) do
309
- if dep_defined? == false
310
- # Only log about define errors if the define previously failed...
311
- log_error "This dep isn't defined. Perhaps there was a load error?"
312
- elsif !define_or_complain!
313
- # ... not if it failed as part of this process, since that should log anyway.
314
- elsif task.callstack.include? self
315
- log_error "Oh crap, endless loop! (#{task.callstack.push(self).drop_while {|dep| dep != self }.map(&:name).join(' -> ')})"
316
- elsif !opts[:for].nil? && !Base.host.matches?(opts[:for])
317
- log_ok "Not required on #{Base.host.differentiator_for opts[:for]}."
318
- else
319
- task.callstack.push self
320
- process_this_dep.tap {
321
- task.callstack.pop
322
- }
323
- end
256
+ def process!
257
+ if context.failed?
258
+ log_error "This dep previously failed to load."
259
+ elsif Base.task.callstack.include? self
260
+ log_error "Oh crap, endless loop! (#{Base.task.callstack.push(self).drop_while {|dep| dep != self }.map(&:name).join(' -> ')})"
261
+ elsif !opts[:for].nil? && !Babushka.host.matches?(opts[:for])
262
+ log_ok "Not required on #{Babushka.host.differentiator_for opts[:for]}."
263
+ else
264
+ Base.task.callstack.push self
265
+ process_tree.tap {
266
+ Base.task.callstack.pop
267
+ }
324
268
  end
325
- end
326
-
327
- def process_this_dep
328
- process_task(:setup)
329
- process_deps and process_self
330
269
  rescue UnmeetableDep => e
331
270
  log_error e.message
332
271
  log "I don't know how to fix that, so it's up to you. :)"
@@ -337,32 +276,53 @@ module Babushka
337
276
  nil
338
277
  end
339
278
 
340
- def process_deps accessor = :requires
341
- requirements_for(accessor).send(task.opt(:dry_run) ? :each : :all?) do |requirement|
342
- Dep.find_or_suggest requirement.name, :from => dep_source do |dep|
343
- dep.with(*requirement.args).process
344
- end
279
+ # Process the tree descending from this dep (first the dependencies, then
280
+ # the dep itself).
281
+ def process_tree
282
+ process_task(:setup)
283
+ process_requirements and process_self
284
+ end
285
+
286
+ # Process each of the requirements of this dep in order. If this is a dry
287
+ # run, check every one; otherwise, require success from all and fail fast.
288
+ #
289
+ # Each dep recursively processes its own requirements. Hence, this is the
290
+ # method that recurses down the dep tree.
291
+ def process_requirements accessor = :requires
292
+ if Base.task.opt(:dry_run)
293
+ requirements_for(accessor).map {|r| process_requirement(r) }.all?
294
+ else
295
+ requirements_for(accessor).all? {|r| process_requirement(r) }
296
+ end
297
+ end
298
+
299
+ def process_requirement requirement
300
+ Dep.find_or_suggest requirement.name, :from => dep_source do |dep|
301
+ dep.with(*requirement.args).process_with_caching
345
302
  end
303
+ rescue SourceLoadError => e
304
+ Babushka::Logging.log_exception(e)
346
305
  end
347
306
 
307
+ # Process this dep, assuming all its requirements are satisfied. This is
308
+ # the method that implements the met? -> meet -> met? logic that is what
309
+ # deps are all about. For details, see the documentation for Dep#process.
348
310
  def process_self
349
- cd context.run_in do
350
- process_met_task(:initial => true) {
351
- if task.opt(:dry_run)
352
- false # unmet
311
+ process_met_task(:initial => true) {
312
+ if Base.task.opt(:dry_run)
313
+ false # unmet
314
+ else
315
+ process_task(:prepare)
316
+ if !process_requirements(:requires_when_unmet)
317
+ false # install-time deps unmet
353
318
  else
354
- process_task(:prepare)
355
- if !process_deps(:requires_when_unmet)
356
- false # install-time deps unmet
357
- else
358
- log 'meet' do
359
- process_task(:before) and process_task(:meet) and process_task(:after)
360
- end
361
- process_met_task
319
+ log 'meet' do
320
+ process_task(:before) and process_task(:meet) and process_task(:after)
362
321
  end
322
+ process_met_task
363
323
  end
364
- }
365
- end
324
+ end
325
+ }
366
326
  end
367
327
 
368
328
  def process_met_task task_opts = {}, &block
@@ -372,7 +332,7 @@ module Babushka
372
332
  end
373
333
 
374
334
  def run_met_task task_opts = {}
375
- cache_process(process_task(:met?)).tap {|result|
335
+ process_task(:met?).tap {|result|
376
336
  log result_message, :as => (:error unless result || task_opts[:initial]) unless result_message.nil?
377
337
  self.result_message = nil
378
338
  }
@@ -380,16 +340,15 @@ module Babushka
380
340
 
381
341
  def process_task task_name
382
342
  # log "calling #{name} / #{task_name}"
383
- track_block_for(task_name) if Base.task.opt(:track_blocks)
384
- context.instance_eval(&context.send(task_name))
343
+ context.invoke(task_name)
385
344
  end
386
345
 
387
346
  def requirements_for list_name
388
347
  context.send(list_name).map {|dep_or_requirement|
389
- if dep_or_requirement.is_a?(Requirement)
348
+ if dep_or_requirement.is_a?(DepRequirement)
390
349
  dep_or_requirement
391
350
  else
392
- Requirement.new(dep_or_requirement, [])
351
+ DepRequirement.new(dep_or_requirement, [])
393
352
  end
394
353
  }
395
354
  end
@@ -403,56 +362,23 @@ module Babushka
403
362
  end
404
363
 
405
364
  def log_exception_in_dep e
406
- log_error e.message
365
+ Babushka::Logging.log_exception(e)
407
366
  advice = e.is_a?(DepDefinitionError) ? "Looks like a problem with '#{name}' - check" : "Check"
408
367
  log "#{advice} #{(e.backtrace.detect {|l| l[load_path.to_s] } || load_path).sub(/\:in [^:]+$/, '')}." unless load_path.nil?
409
- debug e.backtrace * "\n"
410
368
  end
411
369
 
412
- def track_block_for task_name
413
- if context.has_block? task_name
414
- file, line = *context.file_and_line_for(task_name)
415
- shell "mate '#{file}' -l #{line}" unless file.nil? || line.nil?
416
- sleep 2
370
+ def log_cached result
371
+ if result
372
+ log "#{Logging::TickChar} #{name} (cached)".colorize('green')
373
+ elsif Base.task.opt(:dry_run)
374
+ log "~ #{name} (cached)".colorize('blue')
417
375
  end
418
376
  end
419
377
 
420
- def cached_result
421
- cached_process.tap {|result|
422
- if result
423
- log "#{Logging::TickChar} #{name} (cached)".colorize('green')
424
- elsif task.opt(:dry_run)
425
- log "~ #{name} (cached)".colorize('blue')
426
- else
427
- log "#{Logging::CrossChar} #{name} (cached)".colorize('red')
428
- end
429
- }
430
- end
431
- def cached?
432
- !@_cached_process.nil?
433
- end
434
- def uncache!
435
- @_cached_process = nil
436
- end
437
- def cached_process
438
- @_cached_process
439
- end
440
- def cache_process value
441
- @_cached_process = (value.nil? ? false : value)
442
- end
443
-
444
378
  def suffixed?
445
379
  !opts[:template] && template != BaseTemplate
446
380
  end
447
381
 
448
- def payload
449
- context.payload
450
- end
451
-
452
- def task
453
- Base.task
454
- end
455
-
456
382
  public
457
383
 
458
384
  def inspect
@@ -460,8 +386,8 @@ module Babushka
460
386
  end
461
387
 
462
388
  def defined_info
463
- if dep_defined?
464
- "#{"(#{'un' unless cached_process}met) " if cached?}<- [#{context.requires.join(', ')}]"
389
+ if context.loaded?
390
+ "<- [#{context.requires.join(', ')}]"
465
391
  else
466
392
  "(not defined yet)"
467
393
  end