rubycut-babushka 0.10.6

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 (171) hide show
  1. data/Gemfile +8 -0
  2. data/Gemfile.lock +31 -0
  3. data/README.markdown +246 -0
  4. data/Rakefile +26 -0
  5. data/bin/babushka +11 -0
  6. data/deps/babushka.rb +101 -0
  7. data/deps/dev.rb +12 -0
  8. data/deps/fhs.rb +31 -0
  9. data/deps/git.rb +29 -0
  10. data/deps/homebrew.rb +30 -0
  11. data/deps/os_x.rb +33 -0
  12. data/deps/packages.rb +22 -0
  13. data/deps/pkg_managers.rb +110 -0
  14. data/deps/ruby.rb +23 -0
  15. data/deps/rubygems.rb +24 -0
  16. data/deps/system.rb +10 -0
  17. data/deps/templates/app.rb +68 -0
  18. data/deps/templates/external.rb +12 -0
  19. data/deps/templates/installer.rb +31 -0
  20. data/deps/templates/managed.rb +105 -0
  21. data/deps/templates/ppa.rb +24 -0
  22. data/deps/templates/src.rb +42 -0
  23. data/deps/templates/tmbundle.rb +15 -0
  24. data/lib/babushka.rb +28 -0
  25. data/lib/babushka/accepts_block_for.rb +72 -0
  26. data/lib/babushka/accepts_list_for.rb +49 -0
  27. data/lib/babushka/accepts_value_for.rb +24 -0
  28. data/lib/babushka/base.rb +78 -0
  29. data/lib/babushka/bug_reporter.rb +55 -0
  30. data/lib/babushka/cmdline.rb +133 -0
  31. data/lib/babushka/cmdline/handler.rb +41 -0
  32. data/lib/babushka/cmdline/helpers.rb +127 -0
  33. data/lib/babushka/cmdline/parser.rb +69 -0
  34. data/lib/babushka/colorizer.rb +59 -0
  35. data/lib/babushka/core_patches/array.rb +171 -0
  36. data/lib/babushka/core_patches/blank.rb +22 -0
  37. data/lib/babushka/core_patches/bytes.rb +52 -0
  38. data/lib/babushka/core_patches/hash.rb +107 -0
  39. data/lib/babushka/core_patches/hashish.rb +14 -0
  40. data/lib/babushka/core_patches/integer.rb +25 -0
  41. data/lib/babushka/core_patches/io.rb +8 -0
  42. data/lib/babushka/core_patches/numeric.rb +16 -0
  43. data/lib/babushka/core_patches/object.rb +27 -0
  44. data/lib/babushka/core_patches/string.rb +116 -0
  45. data/lib/babushka/core_patches/symbol.rb +12 -0
  46. data/lib/babushka/core_patches/try.rb +15 -0
  47. data/lib/babushka/core_patches/uri.rb +24 -0
  48. data/lib/babushka/dep.rb +470 -0
  49. data/lib/babushka/dep_context.rb +18 -0
  50. data/lib/babushka/dep_definer.rb +115 -0
  51. data/lib/babushka/dep_pool.rb +49 -0
  52. data/lib/babushka/dep_runner.rb +85 -0
  53. data/lib/babushka/dsl.rb +26 -0
  54. data/lib/babushka/git_repo.rb +185 -0
  55. data/lib/babushka/helpers/git_helpers.rb +32 -0
  56. data/lib/babushka/helpers/log_helpers.rb +176 -0
  57. data/lib/babushka/helpers/path_helpers.rb +34 -0
  58. data/lib/babushka/helpers/run_helpers.rb +145 -0
  59. data/lib/babushka/helpers/shell_helpers.rb +229 -0
  60. data/lib/babushka/helpers/suggest_helpers.rb +16 -0
  61. data/lib/babushka/helpers/uri_helpers.rb +36 -0
  62. data/lib/babushka/ip.rb +160 -0
  63. data/lib/babushka/lambda_chooser.rb +40 -0
  64. data/lib/babushka/levenshtein.rb +125 -0
  65. data/lib/babushka/meta_dep.rb +65 -0
  66. data/lib/babushka/meta_dep_context.rb +15 -0
  67. data/lib/babushka/parameter.rb +143 -0
  68. data/lib/babushka/pkg_helper.rb +81 -0
  69. data/lib/babushka/pkg_helpers/apt_helper.rb +61 -0
  70. data/lib/babushka/pkg_helpers/base_helper.rb +19 -0
  71. data/lib/babushka/pkg_helpers/binpkgsrc_helper.rb +48 -0
  72. data/lib/babushka/pkg_helpers/binports_helper.rb +34 -0
  73. data/lib/babushka/pkg_helpers/brew_helper.rb +110 -0
  74. data/lib/babushka/pkg_helpers/gem_helper.rb +120 -0
  75. data/lib/babushka/pkg_helpers/macports_helper.rb +22 -0
  76. data/lib/babushka/pkg_helpers/npm_helper.rb +45 -0
  77. data/lib/babushka/pkg_helpers/pacman_helper.rb +27 -0
  78. data/lib/babushka/pkg_helpers/pip_helper.rb +45 -0
  79. data/lib/babushka/pkg_helpers/src_helper.rb +16 -0
  80. data/lib/babushka/pkg_helpers/yum_helper.rb +25 -0
  81. data/lib/babushka/popen.rb +40 -0
  82. data/lib/babushka/prompt.rb +176 -0
  83. data/lib/babushka/renderable.rb +67 -0
  84. data/lib/babushka/resource.rb +215 -0
  85. data/lib/babushka/run_reporter.rb +60 -0
  86. data/lib/babushka/shell.rb +108 -0
  87. data/lib/babushka/source.rb +216 -0
  88. data/lib/babushka/source_pool.rb +146 -0
  89. data/lib/babushka/system_definitions.rb +97 -0
  90. data/lib/babushka/system_profile.rb +210 -0
  91. data/lib/babushka/task.rb +142 -0
  92. data/lib/babushka/vars.rb +108 -0
  93. data/lib/babushka/version_of.rb +65 -0
  94. data/lib/babushka/version_str.rb +57 -0
  95. data/lib/babushka/xml_string.rb +28 -0
  96. data/lib/components.rb +82 -0
  97. data/lib/fancypath/fancypath.rb +200 -0
  98. data/lib/inkan/inkan.rb +76 -0
  99. data/spec/acceptance/acceptance.rb +43 -0
  100. data/spec/acceptance_helper.rb +113 -0
  101. data/spec/archives/Blah.app.zip +0 -0
  102. data/spec/archives/archive.tar +0 -0
  103. data/spec/archives/archive.tar.bz2 +0 -0
  104. data/spec/archives/archive.tar.gz +0 -0
  105. data/spec/archives/archive.tbz2 +0 -0
  106. data/spec/archives/archive.tgz +0 -0
  107. data/spec/archives/archive.zip +0 -0
  108. data/spec/archives/content.txt +5 -0
  109. data/spec/archives/invalid_archive +5 -0
  110. data/spec/archives/nested archive/content.txt +5 -0
  111. data/spec/archives/nested_archive.tar +0 -0
  112. data/spec/archives/really_a_gzip.zip +0 -0
  113. data/spec/archives/test-0.3.1.tgz +0 -0
  114. data/spec/archives/tgz_archive +0 -0
  115. data/spec/archives/zip_without_extension +0 -0
  116. data/spec/babushka/accepts_for_spec.rb +174 -0
  117. data/spec/babushka/accepts_for_support.rb +72 -0
  118. data/spec/babushka/cmdline/console_spec.rb +11 -0
  119. data/spec/babushka/cmdline/help_spec.rb +61 -0
  120. data/spec/babushka/cmdline/version_spec.rb +10 -0
  121. data/spec/babushka/core_patches_spec.rb +171 -0
  122. data/spec/babushka/dep_context_spec.rb +58 -0
  123. data/spec/babushka/dep_definer_spec.rb +152 -0
  124. data/spec/babushka/dep_definer_support.rb +36 -0
  125. data/spec/babushka/dep_spec.rb +567 -0
  126. data/spec/babushka/dep_support.rb +29 -0
  127. data/spec/babushka/deps_spec.rb +113 -0
  128. data/spec/babushka/gem_helper_spec.rb +90 -0
  129. data/spec/babushka/git_repo_spec.rb +396 -0
  130. data/spec/babushka/ip_spec.rb +131 -0
  131. data/spec/babushka/lambda_chooser_spec.rb +115 -0
  132. data/spec/babushka/meta_dep_definer_spec.rb +127 -0
  133. data/spec/babushka/meta_dep_wrapper_spec.rb +32 -0
  134. data/spec/babushka/parameter_spec.rb +135 -0
  135. data/spec/babushka/path_helpers_spec.rb +102 -0
  136. data/spec/babushka/prompt_spec.rb +188 -0
  137. data/spec/babushka/renderable_spec.rb +100 -0
  138. data/spec/babushka/resource_spec.rb +141 -0
  139. data/spec/babushka/run_helpers_spec.rb +26 -0
  140. data/spec/babushka/shell_helpers_spec.rb +244 -0
  141. data/spec/babushka/shell_spec.rb +19 -0
  142. data/spec/babushka/source_pool_spec.rb +320 -0
  143. data/spec/babushka/source_pool_support.rb +31 -0
  144. data/spec/babushka/source_spec.rb +382 -0
  145. data/spec/babushka/source_support.rb +17 -0
  146. data/spec/babushka/system_profile_spec.rb +61 -0
  147. data/spec/babushka/task_spec.rb +141 -0
  148. data/spec/babushka/uri_spec.rb +13 -0
  149. data/spec/babushka/vars_spec.rb +59 -0
  150. data/spec/babushka/version_of_spec.rb +110 -0
  151. data/spec/babushka/version_str_spec.rb +130 -0
  152. data/spec/babushka/version_str_support.rb +37 -0
  153. data/spec/babushka/xml_string_spec.rb +98 -0
  154. data/spec/deps/bad/broken.rb +7 -0
  155. data/spec/deps/bad/working.rb +3 -0
  156. data/spec/deps/good/meta.rb +14 -0
  157. data/spec/deps/good/test.rb +11 -0
  158. data/spec/deps/outer/deps.rb +19 -0
  159. data/spec/deps/outer/more deps.rb +11 -0
  160. data/spec/deps/params/params.rb +10 -0
  161. data/spec/fancypath/fancypath_spec.rb +272 -0
  162. data/spec/fancypath_support.rb +10 -0
  163. data/spec/inkan/inkan_spec.rb +217 -0
  164. data/spec/renderable/different_example.conf.erb +4 -0
  165. data/spec/renderable/example.conf.erb +3 -0
  166. data/spec/renderable/example.sh +6 -0
  167. data/spec/renderable/with_binding.conf.erb +4 -0
  168. data/spec/renderable/xml_example.conf.erb +8 -0
  169. data/spec/repos/remote.git.tgz +0 -0
  170. data/spec/spec_helper.rb +87 -0
  171. metadata +238 -0
@@ -0,0 +1,470 @@
1
+ module Babushka
2
+
3
+ class UnmeetableDep < RuntimeError
4
+ end
5
+ class DepDefinitionError < ArgumentError
6
+ end
7
+ class InvalidDepName < DepDefinitionError
8
+ end
9
+ class TemplateNotFound < DepDefinitionError
10
+ end
11
+ class DepParameterError < DepDefinitionError
12
+ end
13
+ class DepArgumentError < DepDefinitionError
14
+ end
15
+
16
+ class Dep
17
+ include LogHelpers
18
+ 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
+
42
+ attr_reader :name, :params, :args, :opts, :vars, :dep_source, :load_path
43
+ attr_accessor :result_message
44
+
45
+ # Create a new dep named +name+ within +source+, whose implementation is
46
+ # found in +block+. To define deps yourself, you should call +dep+ (which
47
+ # is +Dep::Helpers#dep+).
48
+ def initialize name, source, params, opts, block
49
+ if name.empty?
50
+ raise InvalidDepName, "Deps can't have empty names."
51
+ elsif /\A[[:print:]]+\z/i !~ name
52
+ raise InvalidDepName, "The dep name '#{name}' contains nonprintable characters."
53
+ elsif /\// =~ name
54
+ raise InvalidDepName, "The dep name '#{name}' contains '/', which isn't allowed (logs are named after deps, and filenames can't contain '/')."
55
+ elsif /\:/ =~ name
56
+ raise InvalidDepName, "The dep name '#{name}' contains ':', which isn't allowed (colons separate dep and template names from source prefixes)."
57
+ elsif !params.all? {|param| param.is_a?(Symbol) }
58
+ non_symbol_params = params.reject {|p| p.is_a?(Symbol) }
59
+ raise DepParameterError, "The dep '#{name}' has #{'a ' if non_symbol_params.length == 1}non-symbol param#{'s' if non_symbol_params.length > 1} #{non_symbol_params.map(&:inspect).to_list}, which #{non_symbol_params.length == 1 ? "isn't" : "aren't"} allowed."
60
+ else
61
+ @name = name.to_s
62
+ @params = params
63
+ @args = {}
64
+ @opts = Base.sources.current_load_opts.merge(opts)
65
+ @block = block
66
+ @dep_source = source
67
+ @load_path = Base.sources.current_load_path
68
+ @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
+ end
72
+ end
73
+
74
+ def context
75
+ define! if @context.nil?
76
+ @context
77
+ end
78
+
79
+ 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
87
+ end
88
+
89
+ # Look up the dep specified by +dep_name+, yielding it to the block if it
90
+ # was found.
91
+ #
92
+ # If no such dep exists, search for other similarly spelt deps and re-call
93
+ # this same method on the one chosen by the user, if any.
94
+ def self.find_or_suggest dep_name, opts = {}, &block
95
+ 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?
99
+ elsif block.nil?
100
+ dep
101
+ else
102
+ block.call dep
103
+ end
104
+ end
105
+
106
+ # Returns this dep's name, including the source name as a prefix if this
107
+ # dep is in a cloneable source.
108
+ #
109
+ # A cloneable source is one that babushka knows how to automatically
110
+ # update; i.e. a source that babushka could have installed itself.
111
+ #
112
+ # In effect, a cloneable source is one whose deps you prefix when you run
113
+ # them, so this method returns the dep's name in the same form as you would
114
+ # refer to it on the commandline or within a +require+ call in another dep.
115
+ def contextual_name
116
+ dep_source.cloneable? ? "#{dep_source.name}:#{name}" : name
117
+ end
118
+
119
+ # Return this dep's name, first removing the template suffix if one is
120
+ # present.
121
+ #
122
+ # Note that this only removes the suffix when it was used to define the
123
+ # dep. Dep names that end in something that looks like a template suffix,
124
+ # but didn't match a template and result in a templated dep, won't be
125
+ # touched.
126
+ #
127
+ # Some examples:
128
+ # Dep('benhoskings:Chromium.app').basename #=> 'Chromium'
129
+ # Dep('generated report.pdf').basename #=> "generated report.pdf"
130
+ def basename
131
+ suffixed? ? name.sub(/\.#{Regexp.escape(template.name)}$/, '') : name
132
+ end
133
+
134
+ # Returns the portion of the end of the dep name that looks like a template
135
+ # suffix, if any. Unlike +#basename+, this method will return anything that
136
+ # looks like a template suffix, even if it doesn't match a template.
137
+ def suffix
138
+ name.scan(MetaDep::TEMPLATE_NAME_MATCH).flatten.first
139
+ end
140
+
141
+ def with *args
142
+ @args = if args.map(&:class) == [Hash]
143
+ parse_named_arguments(args.first)
144
+ else
145
+ parse_positional_arguments(args)
146
+ end.map_values {|k,v|
147
+ Parameter.for(k, v)
148
+ }
149
+ self
150
+ end
151
+
152
+ # Entry point for a dry +#process+ run, where only +met?+ blocks will be
153
+ # evaluated.
154
+ #
155
+ # This is useful to inspect the current state of a dep tree, without
156
+ # altering the system. It can cause failures, though, because some deps
157
+ # have requirements that need to be met before the dep can perform its
158
+ # +met?+ check.
159
+ #
160
+ # TODO: In future, there will be support for specifying that in the DSL.
161
+ def met? *args
162
+ with(*args).process :dry_run => true, :top_level => true
163
+ end
164
+
165
+ # Entry point for a full met?/meet +#process+ run.
166
+ def meet *args
167
+ with(*args).process :dry_run => false, :top_level => true
168
+ end
169
+
170
+ # Trigger a dep run with this dep at the top of the tree.
171
+ #
172
+ # Running the dep involves the following:
173
+ # - First, the +setup+ block is run.
174
+ # - Next, the dep's dependencies (i.e. the contents of +requires+) are
175
+ # run recursively by calling +#process+ on each; this dep's +#process+
176
+ # early-exits if any of the subdeps fail.
177
+ # - Next, the +met?+ block is run. If +met?+ returns +true+, or any
178
+ # true-like value, the dep is already met and there is nothing to do.
179
+ # Otherwise, the dep is unmet, and the following happens:
180
+ # - The +prepare+ task is run
181
+ # - The +before+ task is run
182
+ # - If +before+ returned a true-like value, the +meet+ task is run.
183
+ # This is where the actual work of achieving the dep's aim is done.
184
+ # - If +meet+ returned a true-like value, the +after+ task is run.
185
+ # - Finally, the +met?+ task is run again, to check whether running
186
+ # +meet+ has achieved the dep's goal.
187
+ #
188
+ # The final step is important to understand. The +meet+ block is run
189
+ # unconditionally, and its return value is ignored, apart from it
190
+ # determining whether to run the +after+ block. The result of a dep is
191
+ # always taken from its +met?+ block, whether it was already met,
192
+ # unmeetable, or met during the run.
193
+ #
194
+ # Sometimes there are conditions under which a dep can't be met. For
195
+ # example, if a dep detects that the existing version of a package is
196
+ # broken in some way that requires manual intervention, then there's no
197
+ # use running the +meet+ block. In this circumstance, you can call
198
+ # +#unmeetable+, which raises an +UnmeetableDep+ exception. Babushka will
199
+ # rescue it and consider the dep unmeetable (that is, it will just allow
200
+ # the dep to fail without attempting to meet it).
201
+ #
202
+ # The following describes the return values of a few components, and of
203
+ # the dep itself.
204
+ # - A '-' means the corresponding block wouldn't be run at all.
205
+ # - An 'X' means the corresponding return value doesn't matter, and is
206
+ # discarded.
207
+ #
208
+ # Initial state | initial met? | meet | subsequent met? | dep returns
209
+ # ----------------+----------------------+-------+-----------------+------------
210
+ # already met | true | - | - | true
211
+ # unmeetable | UnmeetableDep raised | - | - | false
212
+ # couldn't be met | false | X | false | false
213
+ # met during run | false | X | true | true
214
+ #
215
+ # Wherever possible, the +met?+ test shouldn't directly test that the
216
+ # +meet+ block performed specific tasks; only that its overall purpose has
217
+ # been achieved. For example, if the purpose of a given dep is to make sure
218
+ # the webserver is running, the contents of the +meet+ block would probably
219
+ # involve `/etc/init.d/nginx start` or similar, on a Linux system at least.
220
+ # In this case, the +met?+ block shouldn't test anything involving
221
+ # `/etc/init.d` directly; instead, it should separately test that the
222
+ # webserver is running, for example by using `netstat` to check that
223
+ # something is listening on port 80.
224
+ 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?
256
+ end
257
+
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
268
+ end
269
+
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
283
+
284
+ def self.base_template
285
+ BaseTemplate
286
+ end
287
+
288
+ def parse_named_arguments args
289
+ if (non_symbol = args.keys.reject {|key| key.is_a?(Symbol) }).any?
290
+ # We sort here so we can spec the exception message across different rubies.
291
+ non_symbol = non_symbol.sort_by(&:to_s)
292
+ raise DepArgumentError, "The dep '#{name}' received #{'a ' if non_symbol.length == 1}non-symbol argument#{'s' if non_symbol.length > 1} #{non_symbol.map(&:inspect).to_list}."
293
+ elsif (unexpected = args.keys - params).any?
294
+ unexpected = unexpected.sort_by(&:to_s)
295
+ raise DepArgumentError, "The dep '#{name}' received #{'an ' if unexpected.length == 1}unexpected argument#{'s' if unexpected.length > 1} #{unexpected.map(&:inspect).to_list}."
296
+ end
297
+ args
298
+ end
299
+
300
+ def parse_positional_arguments args
301
+ if !args.empty? && args.length != params.length
302
+ raise DepArgumentError, "The dep '#{name}' accepts #{params.length} argument#{'s' unless params.length == 1}, but #{args.length} #{args.length == 1 ? 'was' : 'were'} passed."
303
+ end
304
+ params.inject({}) {|hsh,param| hsh[param] = args.shift; hsh }
305
+ end
306
+
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
324
+ end
325
+ end
326
+
327
+ def process_this_dep
328
+ process_task(:setup)
329
+ process_deps and process_self
330
+ rescue UnmeetableDep => e
331
+ log_error e.message
332
+ log "I don't know how to fix that, so it's up to you. :)"
333
+ nil
334
+ rescue StandardError => e
335
+ log_exception_in_dep e
336
+ Base.task.reportable = e.is_a?(DepDefinitionError)
337
+ nil
338
+ end
339
+
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
345
+ end
346
+ end
347
+
348
+ def process_self
349
+ cd context.run_in do
350
+ process_met_task(:initial => true) {
351
+ if task.opt(:dry_run)
352
+ false # unmet
353
+ 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
362
+ end
363
+ end
364
+ }
365
+ end
366
+ end
367
+
368
+ def process_met_task task_opts = {}, &block
369
+ # Explicitly return false to distinguish unmet deps from failed
370
+ # ones -- those return nil.
371
+ run_met_task(task_opts) || block.try(:call) || false
372
+ end
373
+
374
+ def run_met_task task_opts = {}
375
+ cache_process(process_task(:met?)).tap {|result|
376
+ log result_message, :as => (:error unless result || task_opts[:initial]) unless result_message.nil?
377
+ self.result_message = nil
378
+ }
379
+ end
380
+
381
+ def process_task task_name
382
+ # 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))
385
+ end
386
+
387
+ def requirements_for list_name
388
+ context.send(list_name).map {|dep_or_requirement|
389
+ if dep_or_requirement.is_a?(Requirement)
390
+ dep_or_requirement
391
+ else
392
+ Requirement.new(dep_or_requirement, [])
393
+ end
394
+ }
395
+ end
396
+
397
+ def logging_name
398
+ if Base.task.opt(:show_args) || Base.task.opt(:debug)
399
+ "#{contextual_name}(#{args.values.map(&:description).join(', ')})"
400
+ else
401
+ contextual_name
402
+ end
403
+ end
404
+
405
+ def log_exception_in_dep e
406
+ log_error e.message
407
+ advice = e.is_a?(DepDefinitionError) ? "Looks like a problem with '#{name}' - check" : "Check"
408
+ 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
+ end
411
+
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
417
+ end
418
+ end
419
+
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
+ def suffixed?
445
+ !opts[:template] && template != BaseTemplate
446
+ end
447
+
448
+ def payload
449
+ context.payload
450
+ end
451
+
452
+ def task
453
+ Base.task
454
+ end
455
+
456
+ public
457
+
458
+ def inspect
459
+ "#<Dep:#{object_id} #{"#{dep_source.name}:" unless dep_source.nil?}'#{name}' #{defined_info}>"
460
+ end
461
+
462
+ def defined_info
463
+ if dep_defined?
464
+ "#{"(#{'un' unless cached_process}met) " if cached?}<- [#{context.requires.join(', ')}]"
465
+ else
466
+ "(not defined yet)"
467
+ end
468
+ end
469
+ end
470
+ end