salve/block/base.py
import abc
import os
from salve import paths, with_metaclass
from salve.api import Block
from salve.exceptions import BlockException
class CoreBlock(with_metaclass(abc.ABCMeta, Block)):
"""
A block is the basic unit of configuration.
Typically, blocks describe files, SALVE manifests, patches, etc
This is an ABC that defines the common characteristics of all
blocks in the SALVE Core.
"""
def __init__(self, ty, file_context):
"""
Base CoreBlock constructor.
Args:
@ty
An element of CoreBlock.types, the type of the block.
@file_context
The FileContext of this Block. Used to pass globals and
state information to and from the block.
"""
self.block_type = ty
self.file_context = file_context
# every block holds a hashmap of attribute names to values,
# always initialized as empty
self.attrs = {}
# this is a set of attribute identifiers which always carry
# a path as their value
self.path_attrs = set()
# this is a set of attribute identifiers which must be present
# in order for the block to be valid
self.min_attrs = set()
# the primary attribute of a block is the one handled outside of the
# typically block body parsing, following the block identifier
self.primary_attr = None
def __setitem__(self, attribute_name, value):
"""
Set an attribute of the block to have a specific value. Note
that this is a destructive overwrite if the attribute had a
previous value.
Args:
@attribute_name
The attribute's identifier, typically converted to lower
case.
@value
The value being assigned to the attribute. Typically a
string.
"""
self.attrs[attribute_name] = value
def __getitem__(self, attribute_name):
"""
Return the value of a given attribute of the block.
Args:
@attribute_name
The attribute's identifier. Note that this is case
sensitive.
"""
return self.attrs[attribute_name]
def __contains__(self, attribute_name):
"""
Checks if the block has a value associated with a given
attribute. Returns the T/F value of that check.
Args:
@attribute_name
The attribute's identifier. Note that this is case
sensitive.
"""
return attribute_name in self.attrs
def ensure_has_attrs(self, *args):
"""
Given a list of attributes, checks if the block has each of
those attributes, and raises a BlockException on the first one
that fails.
Args:
@args
A variable argument list of attribute identifiers to be
checked.
"""
for attr in args:
if attr not in self:
raise self.mk_except('Block(ty=' + self.block_type + ') ' +
'missing attr "' + attr + '"')
def mk_except(self, msg):
"""
Create a BlockException from the block. Used to easily pass the
block's context to the exception, and to give any extra
information that might be desirable for a specific block type.
Args:
@msg
The string message that should be reported by the raised
exception.
"""
exc = BlockException(msg, self.file_context)
return exc
def expand_file_paths(self, root_dir):
"""
Expands all relative paths in a block's set of attribute values,
given a directory to act as the prefix to all of the paths. The
attributes are identified as paths based on the block type.
Args:
@root_dir
The directory to be used as a prefix to all relative paths
in the block.
"""
# define a helper to expand attributes with the root_dir
def expand_attr(attrname):
val = self[attrname]
if not paths.is_abs_or_var(val):
self[attrname] = os.path.join(root_dir, val)
# find the minimal set of path attributes
min_path_attrs = self.min_attrs.intersection(self.path_attrs)
# ... and the non-minimal path attributes
non_min_path_attrs = self.path_attrs.difference(min_path_attrs)
# first ensure that the minimal attributes are in place
self.ensure_has_attrs(*list(min_path_attrs))
# and expand each one
for attr in min_path_attrs:
expand_attr(attr)
# then ensure that any of the non-minimal ones that are present
# are also expanded
for attr in non_min_path_attrs:
if attr in self:
expand_attr(attr)