rubycut-babushka 0.10.6

Sign up to get free protection for your applications and to get access to all the features.
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