Class: Babushka::Dep
Instance Attribute Summary collapse
-
#args ⇒ Object
readonly
Returns the value of attribute args.
-
#callstack ⇒ Object
readonly
Returns the value of attribute callstack.
-
#dep_source ⇒ Object
readonly
Returns the value of attribute dep_source.
-
#load_path ⇒ Object
readonly
Returns the value of attribute load_path.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#opts ⇒ Object
readonly
Returns the value of attribute opts.
-
#params ⇒ Object
readonly
Returns the value of attribute params.
Instance Method Summary collapse
-
#basename ⇒ Object
Return this dep's name, first removing the template suffix if one is present.
- #cache_key ⇒ Object
- #context ⇒ Object
-
#contextual_name ⇒ Object
Returns this dep's name, or the full name if the source is remote (i.e. if the full name is required to reference the dep).
-
#full_name ⇒ Object
Returns this dep's name, including its source's name as a prefix.
-
#initialize(name, source, params, opts, block) ⇒ Dep
constructor
Create a new dep named
name
withinsource
, whose implementation is found inblock
. - #inspect ⇒ Object
-
#meet(*args) ⇒ Object
Entry point for a full met?/meet
#process
run. -
#met?(*args) ⇒ Boolean
The entry point for a dry run, where only
met?
blocks will be evaluated. -
#process(and_meet = true) ⇒ Object
Run this dep and its subdeps recursively.
-
#process_as_requirement(and_meet, callstack, cache) ⇒ Object
Process this dep as a requirement of another dep – that is, not as the top-level dep in a tree.
-
#suffix ⇒ Object
Returns the portion of the end of the dep name that looks like a template suffix, if any.
- #suffixed? ⇒ Boolean
-
#template ⇒ Object
Attempt to retrieve the template specified in
opts[:template]
. - #with(*args) ⇒ Object
Methods included from LogHelpers
debug, deprecated!, log, log_block, log_error, log_ok, log_stderr, log_warn, removed!
Constructor Details
#initialize(name, source, params, opts, block) ⇒ Dep
Create a new dep named name
within source
, whose implementation is found in block
. To define deps yourself, you should call dep
(which is Dep::Helpers#dep).
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/babushka/dep.rb', line 35 def initialize name, source, params, opts, block if !name.is_a?(String) raise InvalidDepName, "The dep name #{name.inspect} isn't a string." elsif name.empty? raise InvalidDepName, "Deps can't have empty names." elsif /[[:cntrl:]]/mu =~ name raise InvalidDepName, "The dep name '#{name}' contains nonprintable characters." elsif /\// =~ name raise InvalidDepName, "The dep name '#{name}' contains '/', which isn't allowed (logs are named after deps, and filenames can't contain '/')." elsif /\:/ =~ name raise InvalidDepName, "The dep name '#{name}' contains ':', which isn't allowed (colons separate dep and template names from source prefixes)." elsif !params.all? {|param| param.is_a?(Symbol) } non_symbol_params = params.reject {|p| p.is_a?(Symbol) } 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." else @name, @dep_source, @params, @block = name, source, params, block @args = {} @opts = Base.sources.current_load_opts.merge(opts) @load_path = Base.sources.current_load_path @dep_source.deps.register(self) end end |
Instance Attribute Details
#args ⇒ Object (readonly)
Returns the value of attribute args.
30 31 32 |
# File 'lib/babushka/dep.rb', line 30 def args @args end |
#callstack ⇒ Object (readonly)
Returns the value of attribute callstack.
30 31 32 |
# File 'lib/babushka/dep.rb', line 30 def callstack @callstack end |
#dep_source ⇒ Object (readonly)
Returns the value of attribute dep_source.
30 31 32 |
# File 'lib/babushka/dep.rb', line 30 def dep_source @dep_source end |
#load_path ⇒ Object (readonly)
Returns the value of attribute load_path.
30 31 32 |
# File 'lib/babushka/dep.rb', line 30 def load_path @load_path end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
30 31 32 |
# File 'lib/babushka/dep.rb', line 30 def name @name end |
#opts ⇒ Object (readonly)
Returns the value of attribute opts.
30 31 32 |
# File 'lib/babushka/dep.rb', line 30 def opts @opts end |
#params ⇒ Object (readonly)
Returns the value of attribute params.
30 31 32 |
# File 'lib/babushka/dep.rb', line 30 def params @params end |
Instance Method Details
#basename ⇒ Object
Return this dep's name, first removing the template suffix if one is present.
Note that this only removes the suffix when it was used to define the dep. Dep names that end in something that looks like a template suffix, but didn't match a template and result in a templated dep, won't be touched.
Some examples:
Dep('benhoskings:Chromium.app').basename #=> 'Chromium'
Dep('generated report.pdf').basename #=> "generated report.pdf"
103 104 105 |
# File 'lib/babushka/dep.rb', line 103 def basename suffixed? ? name.sub(/\.#{Regexp.escape(template.name)}$/, '') : name end |
#cache_key ⇒ Object
114 115 116 |
# File 'lib/babushka/dep.rb', line 114 def cache_key DepRequirement.new(name, @params.map {|p| @args.fetch(p) { Parameter.new(name) }.current_value }) end |
#context ⇒ Object
58 59 60 |
# File 'lib/babushka/dep.rb', line 58 def context @context ||= template.context_class.new(self, &@block) end |
#contextual_name ⇒ Object
Returns this dep's name, or the full name if the source is remote (i.e. if the full name is required to reference the dep).
87 88 89 90 |
# File 'lib/babushka/dep.rb', line 87 def contextual_name # TODO This isn't quite right; it should be dep_source.default? instead." dep_source.remote? ? full_name : name end |
#full_name ⇒ Object
Returns this dep's name, including its source's name as a prefix.
The full name is the name you can use to refer to unambiguously refer to this dep on your system; i.e. the name that properly identifies the dep, taking your (possibly customised) source names into account.
81 82 83 |
# File 'lib/babushka/dep.rb', line 81 def full_name "#{dep_source.name}:#{name}" end |
#inspect ⇒ Object
423 424 425 |
# File 'lib/babushka/dep.rb', line 423 def inspect "#<Dep:#{object_id} '#{dep_source.name}:#{name}'>" end |
#meet(*args) ⇒ Object
Entry point for a full met?/meet #process
run.
132 133 134 |
# File 'lib/babushka/dep.rb', line 132 def meet *args with(*args).process(true) end |
#met?(*args) ⇒ Boolean
The entry point for a dry run, where only met?
blocks will be evaluated. This is useful to inspect the current state of a dep tree, without altering the system.
127 128 129 |
# File 'lib/babushka/dep.rb', line 127 def met? *args with(*args).process(false) end |
#process(and_meet = true) ⇒ Object
Run this dep and its subdeps recursively.
The overall flow for a single dep is [met? [, meet, met?]]. That is, met? is checked; if it's false, meet is run and then met? is checked again.
The [meet, met?] component for unmet deps is only performed if and_meet
is true. If it's false, then babushka will just check met? down the tree, returning true if this dep and all its subdeps are already met.
Running the dep involves the following steps:
-
First, the
setup
block is run. -
Next, the dep's dependencies (i.e. the contents of
requires
) are run recursively; this dep early-exits if any of the subdeps couldn't be met (or if this is a dry run, if any of the subdeps are unmet). -
If the dependencies are all met, the
met?
block is run. Ifmet?
returnstrue
, or any true-like value, the dep is already met and there is nothing to do. Otherwise, the dep is unmet, and the following happens:- The +prepare+ task is run. - The +before+ task is run. - If +before+ returned a true-like value, the +meet+ task is run. This is where the actual work of achieving the dep's aim is done. - If +meet+ returned a true-like value, the +after+ task is run.
-
Finally, the
met?
task is run again, to check whether runningmeet
achieved the dep's goal.
The final step is important to understand: the before
/meet
/after
blocks' return values are ignored. The result of a dep is always that of its met?
block, whether it was already met, became met after the meet block was run, or couldn't be met.
Sometimes there are conditions under which a dep is unmeetable. For example, if a dep detects that the existing version of a package is broken in some way that requires manual intervention, then there's no use running the meet
block. In this circumstance, the dep can call #unmeetable!
, which raises an UnmeetableDep
exception. Babushka will rescue it and consider the dep unmeetable (that is, it will just allow the dep to fail without attempting to meet it).
The following describes the return values of the defining components, and of the dep itself.
-
A '-' means the corresponding block wouldn't be run at all.
-
An 'X' means the corresponding value doesn't matter, and is discarded.
Scenario | met? | -> meet | -> met? | dep returns --------------------+--------------+--------------+---------+------------ already met | true | - | - | true met during run | false | X | true | true couldn't be met | false | X | false | false failure during meet | false | #unmeetable! | - | nil unmeetable | #unmeetable! | - | - | nil
Wherever possible, the met?
test shouldn't directly test any work done in the meet
block, only that its overall purpose has been achieved. Just like normal test-driven development, you should test the “what” and not the “how”. Tests involving the “how” are brittle and don't correctly express the dep's intent.
For example, if the purpose of a given dep is to make sure the webserver is running, the contents of the meet
block would probably involve `/etc/init.d/nginx start` or similar, on a Linux system at least. In this case, the met?
block shouldn't test anything involving `/etc/init.d` directly; instead, it should separately test that the webserver is running, for example by using `lsof` to check that something is listening on port 80.
202 203 204 |
# File 'lib/babushka/dep.rb', line 202 def process and_meet = true process_as_requirement(and_meet, [], Babushka::DepCache.new) end |
#process_as_requirement(and_meet, callstack, cache) ⇒ Object
Process this dep as a requirement of another dep – that is, not as the top-level dep in a tree. The difference is that the callstack and dep cache are supplied by the calling dep, instead of created anew.
This method is intended to be called only from deps themselves, as they invoke their requirements (via Dep#run_requirement); to process a dep directly, call Dep#process instead.
213 214 215 216 217 218 219 |
# File 'lib/babushka/dep.rb', line 213 def process_as_requirement and_meet, callstack, cache @callstack = callstack @cache = cache process_with_caching(and_meet) ensure @callstack = @cache = nil end |
#suffix ⇒ Object
Returns the portion of the end of the dep name that looks like a template suffix, if any. Unlike #basename
, this method will return anything that looks like a template suffix, even if it doesn't match a template.
110 111 112 |
# File 'lib/babushka/dep.rb', line 110 def suffix name.scan(DepTemplate::TEMPLATE_NAME_MATCH).flatten.first end |
#suffixed? ⇒ Boolean
419 420 421 |
# File 'lib/babushka/dep.rb', line 419 def suffixed? !opts[:template] && template != self.class.base_template end |
#template ⇒ Object
Attempt to retrieve the template specified in opts[:template]
. If the template name includes a source prefix, it is searched for within the corresponding source. Otherwise, it is searched for in the current source and the core sources.
66 67 68 69 70 71 72 73 74 |
# File 'lib/babushka/dep.rb', line 66 def template @template ||= if opts[:template] Base.sources.template_for(opts[:template], :from => dep_source).tap {|t| raise TemplateNotFound, "There is no template named '#{opts[:template]}' to define '#{name}' against." if t.nil? } else Base.sources.template_for(suffix, :from => dep_source) || self.class.base_template end end |
#with(*args) ⇒ Object
118 119 120 121 122 |
# File 'lib/babushka/dep.rb', line 118 def with *args @args = parse_arguments(args) @context = nil # To re-run param.default() calls, etc, inside deps. self end |