示例#1
0
    def plan_layers(self, layers, output_files):
        next_config = BuildConfig()
        next_config.add_config(layers["layers"][0].config)

        layers["layers"][-1].url = self.name

        for i, layer in enumerate(layers["layers"]):
            log.info("Processing layer: %s%s", layer.url,
                     "" if 'deps' in layer.directory.splitall()
                     else " (from %s)" % layer.directory.relpath())
            if i + 1 < len(layers["layers"]):
                next_layer = layers["layers"][i + 1]
                next_config = next_config.add_config(next_layer.config)
            else:
                # Add an empty level to the configs to represent that there
                # is no layer after the current one.  This is important for
                # the IgnoreTactic, which needs to look ahead so that it can
                # handle ignoring entire directories.
                next_config = next_config.add_config({})
            list(e for e in utils.walk(layer.directory,
                                       self.build_tactics,
                                       layer=layer,
                                       next_config=next_config,
                                       output_files=output_files))
        plan = [t for t in output_files.values() if t]
        return plan
示例#2
0
    def plan_layers(self, layers, output_files):
        next_config = BuildConfig()
        next_config.add_config(layers["layers"][0].config)

        layers["layers"][-1].url = self.name

        for i, layer in enumerate(layers["layers"]):
            log.info(
                "Processing layer: %s%s", layer.url,
                "" if 'deps' in layer.directory.splitall() else " (from %s)" %
                layer.directory.relpath())
            if i + 1 < len(layers["layers"]):
                next_layer = layers["layers"][i + 1]
                next_config = next_config.add_config(next_layer.config)
            else:
                # Add an empty level to the configs to represent that there
                # is no layer after the current one.  This is important for
                # the IgnoreTactic, which needs to look ahead so that it can
                # handle ignoring entire directories.
                next_config = next_config.add_config({})
            list(e for e in utils.walk(layer.directory,
                                       self.build_tactics,
                                       layer=layer,
                                       next_config=next_config,
                                       output_files=output_files))
        plan = [t for t in output_files.values() if t]
        return plan
示例#3
0
    def plan_layers(self, layers, output_files):
        config = BuildConfig()
        cfgfn = layers["layers"][0] / BuildConfig.DEFAULT_FILE
        if cfgfn.exists():
            config = config.add_config(
                cfgfn, True)
        else:
            cfgfn = layers["layers"][0] / BuildConfig.OLD_CONFIG
            config = config.add_config(
                cfgfn, True)

        layers["layers"][-1].url = self.name

        for i, layer in enumerate(layers["layers"]):
            log.info("Processing layer: %s", layer.url)
            if i + 1 < len(layers["layers"]):
                next_layer = layers["layers"][i + 1]
                config = config.add_config(
                    next_layer / BuildConfig.DEFAULT_FILE, True)
            list(e for e in utils.walk(layer.directory,
                                       self.build_tactics,
                                       current=layer,
                                       config=config,
                                       output_files=output_files))
        plan = [t for t in output_files.values() if t]
        return plan
示例#4
0
 def lint(self):
     ""  # suppress inherited doc
     # Ensure that the interface layer used is valid.
     impl = self.interface.directory / self.role + '.py'
     if not impl.exists():
         log.error('Missing implementation for interface role: %s.py',
                   self.role)
         return False
     valid = True
     ignorer = utils.ignore_matcher(self.config.ignores +
                                    self.interface.config.ignores)
     for entry, _ in utils.walk(self.interface.directory,
                                lambda x: True,
                                matcher=ignorer,
                                kind="files"):
         if entry.splitext()[1] != ".py":
             continue
         relpath = entry.relpath(self._target.directory)
         target = self._target.directory / relpath
         if not target.exists():
             continue
         unchanged = utils.delta_python_dump(entry, target,
                                             from_name=relpath)
         if not unchanged:
             valid = False
     return valid
示例#5
0
def inspect(charm, force_styling=False):
    tw = utils.TermWriter(force_styling=force_styling)
    manp = charm / ".composer.manifest"
    comp = charm / "composer.yaml"
    if not manp.exists() or not comp.exists():
        return
    manifest = json.loads(manp.text())
    composer = yaml.load(comp.open())
    a, c, d = utils.delta_signatures(manp)

    # ordered list of layers used for legend
    layers = list(manifest['layers'])

    def get_depth(e):
        rel = e.relpath(charm)
        depth = len(rel.splitall()) - 2
        return rel, depth

    def get_suffix(rel):
        suffix = ""
        if rel in a:
            suffix = "+"
        elif rel in c:
            suffix = "*"
        return suffix

    def get_color(rel):
        # name of layer this belongs to
        color = tw.term.normal
        if rel in manifest['signatures']:
            layer = manifest['signatures'][rel][0]
            layer_key = layers.index(layer)
            color = getattr(tw, theme.get(layer_key, "normal"))
        else:
            if entry.isdir():
                color = tw.blue
        return color

    tw.write("Inspect %s\n" % composer["is"])
    for layer in layers:
        tw.write("# {color}{layer}{t.normal}\n",
                 color=getattr(tw, theme.get(layers.index(layer), "normal")),
                 layer=layer)
    tw.write("\n")
    tw.write("{t.blue}{target}{t.normal}\n", target=charm)

    ignorer = utils.ignore_matcher(config.DEFAULT_IGNORES)
    walk = sorted(utils.walk(charm, get_depth), key=lambda x: x[1][0])
    for i in range(len(walk) - 1):
        entry, (rel, depth) = walk[i]
        nEnt, (nrel, ndepth) = walk[i + 1]
        if not ignorer(rel):
            continue

        tw.write("{prefix}{layerColor}{entry} "
                 "{t.bold}{suffix}{t.normal}\n",
                 prefix=get_prefix(walk, i, depth, ndepth),
                 layerColor=get_color(rel),
                 suffix=get_suffix(rel),
                 entry=rel.name)
示例#6
0
 def lint(self):
     ""  # suppress inherited doc
     # Ensure that the interface layer used is valid.
     impl = self.interface.directory / self.role + '.py'
     if not impl.exists():
         log.error('Missing implementation for interface role: %s.py',
                   self.role)
         return False
     valid = True
     ignorer = utils.ignore_matcher(self.config.ignores +
                                    self.interface.config.ignores +
                                    self.config.excludes +
                                    self.interface.config.excludes)
     for entry, _ in utils.walk(self.interface.directory,
                                lambda x: True,
                                matcher=ignorer,
                                kind="files"):
         if entry.splitext()[1] != ".py":
             continue
         relpath = entry.relpath(self._target.directory)
         target = self._target.directory / relpath
         if not target.exists():
             continue
         unchanged = utils.delta_python_dump(entry,
                                             target,
                                             from_name=relpath)
         if not unchanged:
             valid = False
     return valid
示例#7
0
 def sign(self):
     """return sign in the form {relpath: (origin layer, SHA256)}
     """
     sigs = {}
     for entry, sig in utils.walk(self.target, utils.sign, kind="files"):
         relpath = entry.relpath(self._target.directory)
         sigs[relpath] = (self.interface.url, "static", sig)
     return sigs
示例#8
0
 def sign(self):
     """return sign in the form {relpath: (origin layer, SHA256)}
     """
     sigs = {}
     for entry, sig in utils.walk(self.target, utils.sign, kind="files"):
         relpath = entry.relpath(self._target.directory)
         sigs[relpath] = (self.interface.url, "static", sig)
     return sigs
示例#9
0
 def sign(self):
     ""  # suppress inherited doc
     # Sign all of the files that were put into place.
     sigs = {}
     for entry, sig in utils.walk(self.target, utils.sign, kind="files"):
         relpath = entry.relpath(self._target.directory)
         sigs[relpath] = (self.interface.url, "static", sig)
     return sigs
示例#10
0
 def sign(self):
     ""  # suppress inherited doc
     # Sign all of the files that were put into place.
     sigs = {}
     for entry, sig in utils.walk(self.target,
                                  utils.sign, kind="files"):
         relpath = entry.relpath(self._target.directory)
         sigs[relpath] = (self.interface.url, "static", sig)
     return sigs
示例#11
0
 def sign(self):
     ""  # suppress inherited doc
     sigs = {}
     for d in self._tracked:
         if d.isdir():
             for entry, sig in utils.walk(d, utils.sign, kind="files"):
                 relpath = entry.relpath(self.target.directory)
                 sigs[relpath] = (self.layer.url, "dynamic", sig)
         elif d.isfile():
             relpath = d.relpath(self.target.directory)
             sigs[relpath] = (self.layer.url, "dynamic", utils.sign(d))
     return sigs
示例#12
0
 def sign(self):
     """return sign in the form {relpath: (origin layer, SHA256)}
     """
     sigs = {}
     for d in self._tracked:
         if d.isdir():
             for entry, sig in utils.walk(d, utils.sign, kind="files"):
                 relpath = entry.relpath(self.target.directory)
                 sigs[relpath] = (self.current.url, "dynamic", sig)
         elif d.isfile():
             relpath = d.relpath(self.target.directory)
             sigs[relpath] = (self.current.url, "dynamic", utils.sign(d))
     return sigs
示例#13
0
 def sign(self):
     """return sign in the form {relpath: (origin layer, SHA256)}
     """
     sigs = {}
     for d in self._tracked:
         if d.isdir():
             for entry, sig in utils.walk(d, utils.sign, kind="files"):
                 relpath = entry.relpath(self.target.directory)
                 sigs[relpath] = (self.current.url, "dynamic", sig)
         elif d.isfile():
             relpath = d.relpath(self.target.directory)
             sigs[relpath] = (self.current.url, "dynamic", utils.sign(d))
     return sigs
示例#14
0
 def sign(self):
     ""  # suppress inherited doc
     sigs = {}
     for d in self._tracked:
         if d.isdir():
             for entry, sig in utils.walk(d,
                                          utils.sign, kind="files"):
                 relpath = entry.relpath(self.target.directory)
                 sigs[relpath] = (self.layer.url, "dynamic", sig)
         elif d.isfile():
             relpath = d.relpath(self.target.directory)
             sigs[relpath] = (
                 self.layer.url, "dynamic", utils.sign(d))
     return sigs
示例#15
0
 def __call__(self):
     # copy the entire tree into the
     # hooks/relations/<interface>
     # directory
     log.debug("Copying Interface %s: %s", self.interface.name, self.target)
     ignorer = utils.ignore_matcher(self.config.ignores)
     for entity, _ in utils.walk(self.interface.directory,
                                 lambda x: True,
                                 matcher=ignorer,
                                 kind="files"):
         target = entity.relpath(self.interface.directory)
         target = (self.target / target).normpath()
         target.parent.makedirs_p()
         entity.copy2(target)
     init = self.target / "__init__.py"
     if not init.exists():
         # ensure we can import from here directly
         init.touch()
示例#16
0
 def __call__(self):
     # copy the entire tree into the
     # hooks/relations/<interface>
     # directory
     log.debug("Copying Interface %s: %s", self.interface.name, self.target)
     # Ensure the path exists
     if self.target.exists():
         # XXX: fix this to do actual updates
         return
     ignorer = utils.ignore_matcher(self.config.ignores)
     for entity, _ in utils.walk(self.interface.directory, lambda x: True, matcher=ignorer, kind="files"):
         target = entity.relpath(self.interface.directory)
         target = (self.target / target).normpath()
         target.parent.makedirs_p()
         entity.copy2(target)
     init = self.target / "__init__.py"
     if not init.exists():
         # ensure we can import from here directly
         init.touch()
示例#17
0
    def plan_layers(self, layers, output_files):
        current_config = BuildConfig()
        next_config = current_config.add_config(layers["layers"][0].config)

        layers["layers"][-1].url = self.name

        for i, layer in enumerate(layers["layers"]):
            log.info(
                "Processing layer: %s%s", layer.url,
                "" if layer.directory.startswith(self.cache_dir) else
                " (from %s)" % layer.directory.relpath())
            current_config = current_config.add_config(layer.config)
            if i + 1 < len(layers["layers"]):
                next_layer = layers["layers"][i + 1]
                next_config = next_config.add_config(next_layer.config)
            else:
                # Add an empty level to the configs to represent that there
                # is no layer after the current one.  This is important for
                # the IgnoreTactic, which needs to look ahead so that it can
                # handle ignoring entire directories.
                next_config = next_config.add_config({})
            list(e for e in utils.walk(layer.directory,
                                       self.build_tactics,
                                       layer=layer,
                                       next_config=next_config,
                                       current_config=current_config,
                                       output_files=output_files))
        if self.wheelhouse_overrides:
            existing_tactic = output_files.get('wheelhouse.txt')
            wh_over_layer = Layer('--wheelhouse-overrides',
                                  layers["layers"][-1].target_repo.dirname())
            wh_over_layer.directory = layers["layers"][-1].directory
            output_files['wheelhouse.txt'] = WheelhouseTactic(
                self.wheelhouse_overrides,
                self.target,
                wh_over_layer,
                next_config,
            )
            output_files['wheelhouse.txt'].purge_wheels = True
            if existing_tactic is not None:
                output_files['wheelhouse.txt'].combine(existing_tactic)
        plan = [t for t in output_files.values() if t]
        return plan
示例#18
0
    def plan_layers(self, layers, output_files):
        config = ComposerConfig()
        config = config.add_config(
            layers["layers"][0] / ComposerConfig.DEFAULT_FILE, True)

        layers["layers"][-1].url = self.name

        for i, layer in enumerate(layers["layers"]):
            log.info("Processing layer: %s", layer.url)
            if i + 1 < len(layers["layers"]):
                next_layer = layers["layers"][i + 1]
                config = config.add_config(
                    next_layer / ComposerConfig.DEFAULT_FILE, True)
            list(e for e in utils.walk(layer.directory,
                                       self.build_tactics,
                                       current=layer,
                                       config=config,
                                       output_files=output_files))
        plan = [t for t in output_files.values() if t]
        return plan
示例#19
0
    def plan_layers(self, layers, output_files):
        current_config = BuildConfig()
        next_config = current_config.add_config(layers["layers"][0].config)

        layers["layers"][-1].url = self.name

        for i, layer in enumerate(layers["layers"]):
            log.info("Processing layer: %s%s", layer.url,
                     "" if layer.directory.startswith(self.cache_dir)
                     else " (from %s)" % layer.directory.relpath())
            current_config = current_config.add_config(layer.config)
            if i + 1 < len(layers["layers"]):
                next_layer = layers["layers"][i + 1]
                next_config = next_config.add_config(next_layer.config)
            else:
                # Add an empty level to the configs to represent that there
                # is no layer after the current one.  This is important for
                # the IgnoreTactic, which needs to look ahead so that it can
                # handle ignoring entire directories.
                next_config = next_config.add_config({})
            list(e for e in utils.walk(layer.directory,
                                       self.build_tactics,
                                       layer=layer,
                                       next_config=next_config,
                                       current_config=current_config,
                                       output_files=output_files))
        if self.wheelhouse_overrides:
            existing_tactic = output_files.get('wheelhouse.txt')
            output_files['wheelhouse.txt'] = WheelhouseTactic(
                str(self.wheelhouse_overrides),
                self.target,
                layers["layers"][-1],
                next_config,
            )
            output_files['wheelhouse.txt'].purge_wheels = True
            if existing_tactic is not None:
                output_files['wheelhouse.txt'].combine(existing_tactic)
        plan = [t for t in output_files.values() if t]
        return plan
示例#20
0
def inspect(charm, force_styling=False, annotate=False):
    tw = utils.TermWriter(force_styling=force_styling)
    manp = charm / ".build.manifest"
    comp = charm / "layer.yaml"
    if not manp.exists() or not comp.exists():
        return
    manifest = json.loads(manp.text())
    composer = yaml.safe_load(comp.open())
    a, c, d = utils.delta_signatures(manp)

    # ordered list of layers used for legend
    if isinstance(manifest['layers'][0], dict):
        layers = [layer['url'] for layer in manifest['layers']]
    else:
        layers = list(manifest['layers'])
    layers.reverse()
    while layers[0].startswith('interface:'):
        layers.append(layers.pop(0))

    def get_depth(e):
        rel = e.relpath(charm)
        depth = len(rel.splitall()) - 2
        return rel, depth

    def get_suffix(rel):
        suffix = ""
        if rel in a:
            suffix = "+"
        elif rel in c:
            suffix = "*"
        return suffix

    def get_color(rel):
        # name of layer this belongs to
        color = tw.term.normal
        if rel in manifest['signatures']:
            layer = manifest['signatures'][rel][0]
            if layer in layers:
                layer_key = layers.index(layer)
            else:
                # handle special build created artifacts, which have
                # a "layer name" of "build" (mostly the manifest itself)
                layer_key = -1
            color = getattr(tw, theme.get(layer_key, "normal"))
        else:
            if entry.isdir():
                color = tw.blue
        return color

    tw.write("Inspect %s\n" % composer["is"])
    if tw.does_styling or force_styling:
        tw.write("\n")
        tw.write("Color key:\n")
        for i, layer in enumerate(layers):
            tw.write("# {color}{layer}{t.normal}\n",
                     color=getattr(tw, theme.get(i, "normal")),
                     layer=layer)
    else:
        # force annotations if we can't use color
        annotate = True
    tw.write("\n")
    tw.write("{t.blue}{target}{t.normal}\n", target=charm)

    ignorer = utils.ignore_matcher(config.DEFAULT_IGNORES)
    walk = sorted(utils.walk(charm, get_depth), key=lambda x: x[1][0])
    for i in range(len(walk) - 1):
        entry, (rel, depth) = walk[i]
        nEnt, (nrel, ndepth) = walk[i + 1]
        if not ignorer(rel):
            continue

        if annotate and rel in manifest['signatures']:
            layer_name = manifest['signatures'][rel][0]
            if layer_name == 'build':
                # handle special build created artifacts, which have
                # a "layer name" of "build" (mostly the manifest itself)
                annotation = ' ({}build artifact{})'.format(
                    tw.bright_black, tw.normal)
            else:
                annotation = ' (from {}{}{})'.format(get_color(rel),
                                                     layer_name, tw.normal)
        else:
            annotation = ''
        tw.write(
            "{prefix}{layerColor}{entry} "
            "{t.bold}{suffix}{t.normal}{annotation}\n",
            prefix=get_prefix(walk, i, depth, ndepth),
            layerColor=get_color(rel),
            suffix=get_suffix(rel),
            entry=rel.name,
            annotation=annotation)
示例#21
0
    def plan_layers(self, layers, output_files):
        current_config = BuildConfig()
        next_config = current_config.add_config(layers["layers"][0].config)

        layers["layers"][-1].url = self.name

        for i, layer in enumerate(layers["layers"]):
            log.info(
                "Processing layer: %s%s", layer.url,
                "" if layer.directory.startswith(self.cache_dir) else
                " (from %s)" % layer.directory.relpath())
            current_config = current_config.add_config(layer.config)
            if i + 1 < len(layers["layers"]):
                next_layer = layers["layers"][i + 1]
                next_config = next_config.add_config(next_layer.config)
            else:
                # Add an empty level to the configs to represent that there
                # is no layer after the current one.  This is important for
                # the IgnoreTactic, which needs to look ahead so that it can
                # handle ignoring entire directories.
                next_config = next_config.add_config({})
            list(e for e in utils.walk(layer.directory,
                                       self.build_tactics,
                                       layer=layer,
                                       next_config=next_config,
                                       current_config=current_config,
                                       output_files=output_files))
        # now we do update the wheelhouse.txt output file with the lock file if
        # necessary.
        if not getattr(self, 'ignore_lock_file', False):
            lines = self.generate_python_modules_from_lock_file()
            # override any existing lines with the python modules from the lock
            # file.
            existing_tactic = output_files.get('wheelhouse.txt')
            lock_layer = Layer('lockfile-wheelhouse',
                               layers["layers"][-1].target_repo.dirname())
            lock_layer.directory = layers["layers"][-1].directory
            wh_tactic = WheelhouseTactic(
                "",
                self.target,
                lock_layer,
                next_config,
            )
            wh_tactic.lines = lines
            wh_tactic.purge_wheels = True
            if existing_tactic is not None:
                wh_tactic.combine(existing_tactic)
            output_files["wheelhouse.txt"] = wh_tactic

        if self.wheelhouse_overrides:
            existing_tactic = output_files.get('wheelhouse.txt')
            wh_over_layer = Layer('--wheelhouse-overrides',
                                  layers["layers"][-1].target_repo.dirname())
            wh_over_layer.directory = layers["layers"][-1].directory
            output_files['wheelhouse.txt'] = WheelhouseTactic(
                self.wheelhouse_overrides,
                self.target,
                wh_over_layer,
                next_config,
            )
            output_files['wheelhouse.txt'].purge_wheels = True
            if existing_tactic is not None:
                output_files['wheelhouse.txt'].combine(existing_tactic)
        plan = [t for t in output_files.values() if t]
        return plan
示例#22
0
def inspect(charm, force_styling=False, annotate=False):
    tw = utils.TermWriter(force_styling=force_styling)
    manp = charm / ".build.manifest"
    comp = charm / "layer.yaml"
    if not manp.exists() or not comp.exists():
        return
    manifest = json.loads(manp.text())
    composer = yaml.safe_load(comp.open())
    a, c, d = utils.delta_signatures(manp)

    # ordered list of layers used for legend
    if isinstance(manifest['layers'][0], dict):
        layers = [layer['url'] for layer in manifest['layers']]
    else:
        layers = list(manifest['layers'])
    layers.reverse()
    while layers[0].startswith('interface:'):
        layers.append(layers.pop(0))

    def get_depth(e):
        rel = e.relpath(charm)
        depth = len(rel.splitall()) - 2
        return rel, depth

    def get_suffix(rel):
        suffix = ""
        if rel in a:
            suffix = "+"
        elif rel in c:
            suffix = "*"
        return suffix

    def get_color(rel):
        # name of layer this belongs to
        color = tw.term.normal
        if rel in manifest['signatures']:
            layer = manifest['signatures'][rel][0]
            if layer in layers:
                layer_key = layers.index(layer)
            else:
                # handle special build created artifacts, which have
                # a "layer name" of "build" (mostly the manifest itself)
                layer_key = -1
            color = getattr(tw, theme.get(layer_key, "normal"))
        else:
            if entry.isdir():
                color = tw.blue
        return color

    tw.write("Inspect %s\n" % composer["is"])
    if tw.does_styling or force_styling:
        tw.write("\n")
        tw.write("Color key:\n")
        for i, layer in enumerate(layers):
            tw.write("# {color}{layer}{t.normal}\n",
                     color=getattr(tw, theme.get(i, "normal")),
                     layer=layer)
    else:
        # force annotations if we can't use color
        annotate = True
    tw.write("\n")
    tw.write("{t.blue}{target}{t.normal}\n", target=charm)

    ignorer = utils.ignore_matcher(config.DEFAULT_IGNORES)
    walk = sorted(utils.walk(charm, get_depth),
                  key=lambda x: x[1][0])
    for i in range(len(walk) - 1):
        entry, (rel, depth) = walk[i]
        nEnt, (nrel, ndepth) = walk[i + 1]
        if not ignorer(rel):
            continue

        if annotate and rel in manifest['signatures']:
            layer_name = manifest['signatures'][rel][0]
            if layer_name == 'build':
                # handle special build created artifacts, which have
                # a "layer name" of "build" (mostly the manifest itself)
                annotation = ' ({}build artifact{})'.format(tw.bright_black,
                                                            tw.normal)
            else:
                annotation = ' (from {}{}{})'.format(get_color(rel),
                                                     layer_name,
                                                     tw.normal)
        else:
            annotation = ''
        tw.write("{prefix}{layerColor}{entry} "
                 "{t.bold}{suffix}{t.normal}{annotation}\n",
                 prefix=get_prefix(walk, i, depth, ndepth),
                 layerColor=get_color(rel),
                 suffix=get_suffix(rel),
                 entry=rel.name,
                 annotation=annotation)
示例#23
0
def inspect(charm, force_styling=False):
    tw = utils.TermWriter(force_styling=force_styling)
    manp = charm / ".composer.manifest"
    comp = charm / "composer.yaml"
    if not manp.exists() or not comp.exists():
        return
    manifest = json.loads(manp.text())
    composer = yaml.load(comp.open())
    a, c, d = utils.delta_signatures(manp)

    # ordered list of layers used for legend
    layers = list(manifest['layers'])

    def get_depth(e):
        rel = e.relpath(charm)
        depth = len(rel.splitall()) - 2
        return rel, depth

    def get_suffix(rel):
        suffix = ""
        if rel in a:
            suffix = "+"
        elif rel in c:
            suffix = "*"
        return suffix

    def get_color(rel):
        # name of layer this belongs to
        color = tw.term.normal
        if rel in manifest['signatures']:
            layer = manifest['signatures'][rel][0]
            layer_key = layers.index(layer)
            color = getattr(tw, theme.get(layer_key, "normal"))
        else:
            if entry.isdir():
                color = tw.blue
        return color

    tw.write("Inspect %s\n" % composer["is"])
    for layer in layers:
        tw.write("# {color}{layer}{t.normal}\n",
                 color=getattr(tw, theme.get(
                     layers.index(layer), "normal")),
                 layer=layer)
    tw.write("\n")
    tw.write("{t.blue}{target}{t.normal}\n", target=charm)

    ignorer = utils.ignore_matcher(config.DEFAULT_IGNORES)
    walk = sorted(utils.walk(charm, get_depth),
                    key=lambda x: x[1][0])
    for i in range(len(walk) - 1):
        entry, (rel, depth) = walk[i]
        nEnt, (nrel, ndepth) = walk[i + 1]
        if not ignorer(rel):
            continue

        tw.write("{prefix}{layerColor}{entry} "
                    "{t.bold}{suffix}{t.normal}\n",
                    prefix=get_prefix(walk, i, depth, ndepth),
                    layerColor=get_color(rel),
                    suffix=get_suffix(rel),
                    entry=rel.name)