Class: Babushka::Dep

Inherits:
Object show all
Extended by:
LogHelpers
Includes:
LogHelpers
Defined in:
lib/babushka/dep.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

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

#argsObject (readonly)

Returns the value of attribute args.



30
31
32
# File 'lib/babushka/dep.rb', line 30

def args
  @args
end

#dep_sourceObject (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_pathObject (readonly)

Returns the value of attribute load_path.



30
31
32
# File 'lib/babushka/dep.rb', line 30

def load_path
  @load_path
end

#nameObject (readonly)

Returns the value of attribute name.



30
31
32
# File 'lib/babushka/dep.rb', line 30

def name
  @name
end

#optsObject (readonly)

Returns the value of attribute opts.



30
31
32
# File 'lib/babushka/dep.rb', line 30

def opts
  @opts
end

#paramsObject (readonly)

Returns the value of attribute params.



30
31
32
# File 'lib/babushka/dep.rb', line 30

def params
  @params
end

#result_messageObject

Returns the value of attribute result_message.



31
32
33
# File 'lib/babushka/dep.rb', line 31

def result_message
  @result_message
end

#varsObject (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

#basenameObject

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_keyObject



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

#contextObject



60
61
62
# File 'lib/babushka/dep.rb', line 60

def context
  @context ||= template.context_class.new(self, &@block)
end

#contextual_nameObject

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_infoObject



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

#inspectObject



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.

Returns:

  • (Boolean)


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. If met? returns true, 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

#suffixObject

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

#templateObject

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