Class: Babushka::Dep
Instance Attribute Summary collapse
-
#args ⇒ Object
readonly
Returns the value of attribute args.
-
#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.
-
#result_message ⇒ Object
Returns the value of attribute result_message.
-
#vars ⇒ Object
readonly
Returns the value of attribute vars.
Class Method Summary collapse
-
.find_or_suggest(dep_name, opts = {}, &block) ⇒ Object
Look up the dep specified by
dep_name
, yielding it to the block if it was found.
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, including the source name as a prefix if this dep is in a cloneable source.
- #defined_info ⇒ Object
-
#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
Entry point for a dry
#process
run, where onlymet?
blocks will be evaluated. -
#process(with_opts = {}) ⇒ Object
Trigger a dep run with this dep at the top of the tree.
- #process_with_caching(with_opts = {}) ⇒ Object
-
#suffix ⇒ Object
Returns the portion of the end of the dep name that looks like a template suffix, if any.
-
#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).
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/babushka/dep.rb', line 36 def initialize name, source, params, opts, block if 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 = name.to_s @params = params @args = {} @opts = Base.sources.current_load_opts.merge(opts) @block = block @dep_source = source @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 |
#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 |
#result_message ⇒ Object
Returns the value of attribute result_message.
31 32 33 |
# File 'lib/babushka/dep.rb', line 31 def @result_message end |
#vars ⇒ Object (readonly)
Returns the value of attribute vars.
30 31 32 |
# File 'lib/babushka/dep.rb', line 30 def vars @vars end |
Class Method Details
.find_or_suggest(dep_name, opts = {}, &block) ⇒ Object
Look up the dep specified by dep_name
, yielding it to the block if it was found.
If no such dep exists, search for other similarly spelt deps and re-call this same method on the one chosen by the user, if any.
83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/babushka/dep.rb', line 83 def self.find_or_suggest dep_name, opts = {}, &block if (dep = Dep(dep_name, opts)).nil? log_stderr "#{dep_name.to_s.colorize 'grey'} #{"<- this dep isn't defined!".colorize('red')}" suggestions = Base.sources.current_names.similar_to(dep_name.to_s) log "Perhaps you meant #{suggestions.map {|s| "'#{s}'" }.to_list(:conj => 'or')}?".colorize('grey') if suggestions.any? elsif block.nil? dep else block.call dep end 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"
119 120 121 |
# File 'lib/babushka/dep.rb', line 119 def basename suffixed? ? name.sub(/\.#{Regexp.escape(template.name)}$/, '') : name end |
#cache_key ⇒ Object
130 131 132 |
# File 'lib/babushka/dep.rb', line 130 def cache_key DepRequirement.new(name, @params.map {|p| @args[p].try(:current_value) }) end |
#context ⇒ Object
60 61 62 |
# File 'lib/babushka/dep.rb', line 60 def context @context ||= template.context_class.new(self, &@block) end |
#contextual_name ⇒ Object
Returns this dep’s name, including the source name as a prefix if this dep is in a cloneable source.
A cloneable source is one that babushka knows how to automatically update; i.e. a source that babushka could have installed itself.
In effect, a cloneable source is one whose deps you prefix when you run them, so this method returns the dep’s name in the same form as you would refer to it on the commandline or within a require
call in another dep.
104 105 106 |
# File 'lib/babushka/dep.rb', line 104 def contextual_name dep_source.cloneable? ? "#{dep_source.name}:#{name}" : name end |
#defined_info ⇒ Object
388 389 390 391 392 393 394 |
# File 'lib/babushka/dep.rb', line 388 def defined_info if context.loaded? "<- [#{context.requires.join(', ')}]" else "(not defined yet)" end end |
#inspect ⇒ Object
384 385 386 |
# File 'lib/babushka/dep.rb', line 384 def inspect "#<Dep:#{object_id} #{"#{dep_source.name}:" unless dep_source.nil?}'#{name}' #{defined_info}>" end |
#meet(*args) ⇒ Object
Entry point for a full met?/meet #process
run.
158 159 160 |
# File 'lib/babushka/dep.rb', line 158 def meet *args with(*args).process :dry_run => false end |
#met?(*args) ⇒ Boolean
Entry point for a dry #process
run, where only met?
blocks will be evaluated.
This is useful to inspect the current state of a dep tree, without altering the system. It can cause failures, though, because some deps have requirements that need to be met before the dep can perform its met?
check.
153 154 155 |
# File 'lib/babushka/dep.rb', line 153 def met? *args with(*args).process :dry_run => true end |
#process(with_opts = {}) ⇒ Object
Trigger a dep run with this dep at the top of the tree.
Running the dep involves the following:
-
First, the
setup
block is run. -
Next, the dep’s dependencies (i.e. the contents of
requires
) are run recursively by calling#process
on each; this dep’s#process
early-exits if any of the subdeps fail. -
Next, 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 running +meet+ has achieved the dep's goal.
The final step is important to understand. The meet
block is run unconditionally, and its return value is ignored, apart from it determining whether to run the after
block. The result of a dep is always taken from its met?
block, whether it was already met, unmeetable, or met during the run.
Sometimes there are conditions under which a dep can’t be met. 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, you 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 a few components, and of the dep itself.
-
A ‘-’ means the corresponding block wouldn’t be run at all.
-
An ‘X’ means the corresponding return value doesn’t matter, and is discarded.
Initial state | initial met? | meet | subsequent met? | dep returns ----------------+----------------------+-------+-----------------+------------ already met | true | - | - | true unmeetable | UnmeetableDep raised | - | - | false couldn't be met | false | X | false | false met during run | false | X | true | true
Wherever possible, the met?
test shouldn’t directly test that the meet
block performed specific tasks; only that its overall purpose has been achieved. 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 `netstat` to check that something is listening on port 80.
216 217 218 |
# File 'lib/babushka/dep.rb', line 216 def process with_opts = {} Base.task.cache { process_with_caching(with_opts) } end |
#process_with_caching(with_opts = {}) ⇒ Object
220 221 222 223 224 225 226 227 228 229 |
# File 'lib/babushka/dep.rb', line 220 def process_with_caching with_opts = {} Base.task.opts.update with_opts Base.task.cached( cache_key, :hit => lambda {|value| log_cached(value) } ) { log logging_name, :closing_status => (Base.task.opt(:dry_run) ? :dry_run : true) do process! end } 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.
126 127 128 |
# File 'lib/babushka/dep.rb', line 126 def suffix name.scan(DepTemplate::TEMPLATE_NAME_MATCH).flatten.first 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.
68 69 70 71 72 73 74 75 76 |
# File 'lib/babushka/dep.rb', line 68 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
134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/babushka/dep.rb', line 134 def with *args @args = if args.map(&:class) == [Hash] parse_named_arguments(args.first) else parse_positional_arguments(args) end.map_values {|k,v| Parameter.for(k, v) } @context = nil # To re-evaluate parameter.default() and friends. self end |