This is a more general version of CreateShadows script (on The primary purpose of writing it is to allow for different node types to be created depending on the extension of a given file. For example you may want to treat ".txt" files as @auto nodes but treat ".py" files as @shadow nodes.

This script, presently called LeoDirectory, also includes the following features:

  • Packaged as a class for subclassing and easy overriding of the headline/body creation functions.
  • Leo tree is now sorted as default (option to turn sorting off) (Thanks to thyrsus for the suggestion)

As before, make sure to delete residual .leo_shadow directories after reruns or you will end up with buggy parsing. See CreateShadows for a button to make this easy.

Here is the code:

from os import listdir
from fnmatch import fnmatch
from os.path import join, abspath, basename, normpath, isfile, isdir, splitext

DEFAULT_IGNORE = ['*.pyc', '*.leo', '*.gif', '*.png', '*.jpg', '*.json']
DEFAULT_PARSE = { 'py':'shadow', 'txt':'auto', 'css':'auto', 'html':'auto'}

class LeoDirectory(object):
    def __init__(self, path, to_parse=DEFAULT_PARSE, to_ignore=DEFAULT_IGNORE,
            relative_paths=True, sort=True):
        self.path = path
        self.to_parse = to_parse
        self.to_ignore = to_ignore
        self.relative_paths = relative_paths
        self.sort = sort
        assert isdir(path), "%s is not a directory" % path

    def match_to_ignore(self, string):
        return any(fnmatch(string, p) for p in self.to_ignore)

    def is_ignorable(self, string):
        return any([string.startswith('.'), self.match_to_ignore(string)])

    def parse(self, path):
        name, ext = splitext(path)
        ext = ext[1:]
        return name, ext

    def is_parsable(self, path):
        if isfile(path):
            name, ext = self.parse(path)
            if ext in self.to_parse:
                return True
        if isdir(path):
            # is directory can pass
  '---> '+ path)
            return True

        return False

    def headline_from_path(self, path):
        filename = basename(path)
        name, ext = self.parse(path)
        nodetype = self.to_parse[ext] if (ext in self.to_parse) else 'asis'
        return "@%s %s" % (nodetype, filename)

    def body_from_path(self, path):
        body = "@path %s" % normpath(path)
        return body

    def leo_from_directory(self, directory, parent=None, isroot=True, sort=True):
        if not self.relative_paths: directory = abspath(directory)
        if isroot:
            body = self.body_from_path(directory)
            c.setBodyString(p, body)

        dirlist = sorted(listdir(directory)) if sort else listdir(directory)
        for name in dirlist:
            if self.is_ignorable(name):
      "ignore: "+name)
            path = join(directory, name)
            if isfile(path):
      'file:', path)
                headline = self.headline_from_path(path)
                if parent:
                    node = parent
                    node = p
                child = node.insertAsLastChild()
      'dir:', path)
                headline = basename(path)
                body = self.body_from_path(path)
                if parent:
                    node = parent
                    node = p
                child = node.insertAsLastChild()
                self.leo_from_directory(path, parent=child, isroot=False)

    def render(self):
            self.leo_from_directory(self.path, sort=self.sort)




LeoDirectory in 2010 --terry_n_brown, Mon, 14 Jun 2010 20:44:49 -0700 reply

I hadn't noticed this before, it's similar to plugin, and some other older plugins. It's @<file> type by extension idea might be worth lifting, otherwise I think (biased, of course) that is more current / comes with leo by default.