1# coding: utf-8
2
3"""
4This module provides a Loader class for locating and reading templates.
5
6"""
7
8import os
9import sys
10
11from pystache import common
12from pystache import defaults
13from pystache.locator import Locator
14
15
16# We make a function so that the current defaults take effect.
17# TODO: revisit whether this is necessary.
18
19def _make_to_unicode():
20    def to_unicode(s, encoding=None):
21        """
22        Raises a TypeError exception if the given string is already unicode.
23
24        """
25        if encoding is None:
26            encoding = defaults.STRING_ENCODING
27        return unicode(s, encoding, defaults.DECODE_ERRORS)
28    return to_unicode
29
30
31class Loader(object):
32
33    """
34    Loads the template associated to a name or user-defined object.
35
36    All load_*() methods return the template as a unicode string.
37
38    """
39
40    def __init__(self, file_encoding=None, extension=None, to_unicode=None,
41                 search_dirs=None):
42        """
43        Construct a template loader instance.
44
45        Arguments:
46
47          extension: the template file extension, without the leading dot.
48            Pass False for no extension (e.g. to use extensionless template
49            files).  Defaults to the package default.
50
51          file_encoding: the name of the encoding to use when converting file
52            contents to unicode.  Defaults to the package default.
53
54          search_dirs: the list of directories in which to search when loading
55            a template by name or file name.  Defaults to the package default.
56
57          to_unicode: the function to use when converting strings of type
58            str to unicode.  The function should have the signature:
59
60              to_unicode(s, encoding=None)
61
62            It should accept a string of type str and an optional encoding
63            name and return a string of type unicode.  Defaults to calling
64            Python's built-in function unicode() using the package string
65            encoding and decode errors defaults.
66
67        """
68        if extension is None:
69            extension = defaults.TEMPLATE_EXTENSION
70
71        if file_encoding is None:
72            file_encoding = defaults.FILE_ENCODING
73
74        if search_dirs is None:
75            search_dirs = defaults.SEARCH_DIRS
76
77        if to_unicode is None:
78            to_unicode = _make_to_unicode()
79
80        self.extension = extension
81        self.file_encoding = file_encoding
82        # TODO: unit test setting this attribute.
83        self.search_dirs = search_dirs
84        self.to_unicode = to_unicode
85
86    def _make_locator(self):
87        return Locator(extension=self.extension)
88
89    def unicode(self, s, encoding=None):
90        """
91        Convert a string to unicode using the given encoding, and return it.
92
93        This function uses the underlying to_unicode attribute.
94
95        Arguments:
96
97          s: a basestring instance to convert to unicode.  Unlike Python's
98            built-in unicode() function, it is okay to pass unicode strings
99            to this function.  (Passing a unicode string to Python's unicode()
100            with the encoding argument throws the error, "TypeError: decoding
101            Unicode is not supported.")
102
103          encoding: the encoding to pass to the to_unicode attribute.
104            Defaults to None.
105
106        """
107        if isinstance(s, unicode):
108            return unicode(s)
109
110        return self.to_unicode(s, encoding)
111
112    def read(self, path, encoding=None):
113        """
114        Read the template at the given path, and return it as a unicode string.
115
116        """
117        b = common.read(path)
118
119        if encoding is None:
120            encoding = self.file_encoding
121
122        return self.unicode(b, encoding)
123
124    def load_file(self, file_name):
125        """
126        Find and return the template with the given file name.
127
128        Arguments:
129
130          file_name: the file name of the template.
131
132        """
133        locator = self._make_locator()
134
135        path = locator.find_file(file_name, self.search_dirs)
136
137        return self.read(path)
138
139    def load_name(self, name):
140        """
141        Find and return the template with the given template name.
142
143        Arguments:
144
145          name: the name of the template.
146
147        """
148        locator = self._make_locator()
149
150        path = locator.find_name(name, self.search_dirs)
151
152        return self.read(path)
153
154    # TODO: unit-test this method.
155    def load_object(self, obj):
156        """
157        Find and return the template associated to the given object.
158
159        Arguments:
160
161          obj: an instance of a user-defined class.
162
163          search_dirs: the list of directories in which to search.
164
165        """
166        locator = self._make_locator()
167
168        path = locator.find_object(obj, self.search_dirs)
169
170        return self.read(path)
171