locator.py 4.06 KB
Newer Older
1
# coding: utf-8
Kenneth Reitz committed
2 3

"""
4
This module provides a Locator class for finding template files.
Kenneth Reitz committed
5 6 7

"""

8
import os
9
import re
10
import sys
Kenneth Reitz committed
11

12
from pystache import defaults
13 14


15
class Locator(object):
Kenneth Reitz committed
16

17
    def __init__(self, extension=None):
18
        """
19
        Construct a template locator.
20

21 22
        Arguments:

23 24 25
          extension: the template file extension.  Pass False for no
            extension (i.e. to use extensionless template files).
            Defaults to the package default.
26

27
        """
28
        if extension is None:
29
            extension = defaults.TEMPLATE_EXTENSION
30

31
        self.template_extension = extension
Kenneth Reitz committed
32

33 34 35 36
    def get_object_directory(self, obj):
        """
        Return the directory containing an object's defining class.

37 38 39 40
        Returns None if there is no such directory, for example if the
        class was defined in an interactive Python session, or in a
        doctest that appears in a text file (rather than a Python file).

41
        """
42 43 44
        if not hasattr(obj, '__module__'):
            return None

45 46
        module = sys.modules[obj.__module__]

47 48 49 50
        if not hasattr(module, '__file__'):
            # TODO: add a unit test for this case.
            return None

51 52 53 54
        path = module.__file__

        return os.path.dirname(path)

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
    def make_template_name(self, obj):
        """
        Return the canonical template name for an object instance.

        This method converts Python-style class names (PEP 8's recommended
        CamelCase, aka CapWords) to lower_case_with_underscords.  Here
        is an example with code:

        >>> class HelloWorld(object):
        ...     pass
        >>> hi = HelloWorld()
        >>>
        >>> locator = Locator()
        >>> locator.make_template_name(hi)
        'hello_world'

        """
        template_name = obj.__class__.__name__

        def repl(match):
            return '_' + match.group(0).lower()

        return re.sub('[A-Z]', repl, template_name)[1:]

79 80 81 82 83 84 85 86 87
    def make_file_name(self, template_name, template_extension=None):
        """
        Generate and return the file name for the given template name.

        Arguments:

          template_extension: defaults to the instance's extension.

        """
88
        file_name = template_name
89 90 91 92 93 94

        if template_extension is None:
            template_extension = self.template_extension

        if template_extension is not False:
            file_name += os.path.extsep + template_extension
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112

        return file_name

    def _find_path(self, search_dirs, file_name):
        """
        Search for the given file, and return the path.

        Returns None if the file is not found.

        """
        for dir_path in search_dirs:
            file_path = os.path.join(dir_path, file_name)
            if os.path.exists(file_path):
                return file_path

        return None

    def _find_path_required(self, search_dirs, file_name):
Kenneth Reitz committed
113
        """
114
        Return the path to a template with the given file name.
Kenneth Reitz committed
115

116
        """
117
        path = self._find_path(search_dirs, file_name)
Kenneth Reitz committed
118

119 120
        if path is None:
            # TODO: we should probably raise an exception of our own type.
121 122
            raise IOError('Template file %s not found in directories: %s' %
                          (repr(file_name), repr(search_dirs)))
123 124 125

        return path

126
    def find_name(self, template_name, search_dirs):
127 128 129 130 131 132
        """
        Return the path to a template with the given name.

        """
        file_name = self.make_file_name(template_name)

133
        return self._find_path_required(search_dirs, file_name)
134

135
    def find_object(self, obj, search_dirs, file_name=None):
136 137 138 139
        """
        Return the path to a template associated with the given object.

        """
140 141 142 143 144
        if file_name is None:
            # TODO: should we define a make_file_name() method?
            template_name = self.make_template_name(obj)
            file_name = self.make_file_name(template_name)

145 146 147 148
        dir_path = self.get_object_directory(obj)

        if dir_path is not None:
            search_dirs = [dir_path] + search_dirs
Kenneth Reitz committed
149

150
        path = self._find_path_required(search_dirs, file_name)
151 152

        return path