1# coding: utf-8 2 3""" 4This module provides a Locator class for finding template files. 5 6""" 7 8import os 9import re 10import sys 11 12from pystache.common import TemplateNotFoundError 13from pystache import defaults 14 15 16class Locator(object): 17 def __init__(self, extension=None): 18 """ 19 Construct a template locator. 20 21 Arguments: 22 23 extension: the template file extension, without the leading dot. 24 Pass False for no extension (e.g. to use extensionless template 25 files). Defaults to the package default. 26 27 """ 28 if extension is None: 29 extension = defaults.TEMPLATE_EXTENSION 30 31 self.template_extension = extension 32 33 def get_object_directory(self, obj): 34 """ 35 Return the directory containing an object's defining class. 36 37 Returns None if there is no such directory, for example if the 38 class was defined in an interactive Python session, or in a 39 doctest that appears in a text file (rather than a Python file). 40 41 """ 42 if not hasattr(obj, '__module__'): 43 return None 44 45 module = sys.modules[obj.__module__] 46 47 if not hasattr(module, '__file__'): 48 # TODO: add a unit test for this case. 49 return None 50 51 path = module.__file__ 52 53 return os.path.dirname(path) 54 55 def make_template_name(self, obj): 56 """ 57 Return the canonical template name for an object instance. 58 59 This method converts Python-style class names (PEP 8's recommended 60 CamelCase, aka CapWords) to lower_case_with_underscords. Here 61 is an example with code: 62 63 >>> class HelloWorld(object): 64 ... pass 65 >>> hi = HelloWorld() 66 >>> 67 >>> locator = Locator() 68 >>> locator.make_template_name(hi) 69 'hello_world' 70 71 """ 72 template_name = obj.__class__.__name__ 73 74 def repl(match): 75 return '_' + match.group(0).lower() 76 77 return re.sub('[A-Z]', repl, template_name)[1:] 78 79 def make_file_name(self, template_name, template_extension=None): 80 """ 81 Generate and return the file name for the given template name. 82 83 Arguments: 84 85 template_extension: defaults to the instance's extension. 86 87 """ 88 file_name = template_name 89 90 if template_extension is None: 91 template_extension = self.template_extension 92 93 if template_extension is not False: 94 file_name += os.path.extsep + template_extension 95 96 return file_name 97 98 def _find_path(self, search_dirs, file_name): 99 """ 100 Search for the given file, and return the path. 101 102 Returns None if the file is not found. 103 104 """ 105 for dir_path in search_dirs: 106 file_path = os.path.join(dir_path, file_name) 107 if os.path.exists(file_path): 108 return file_path 109 110 return None 111 112 def _find_path_required(self, search_dirs, file_name): 113 """ 114 Return the path to a template with the given file name. 115 116 """ 117 path = self._find_path(search_dirs, file_name) 118 119 if path is None: 120 raise TemplateNotFoundError( 121 'File %s not found in dirs: %s' % (repr(file_name), repr(search_dirs)) 122 ) 123 124 return path 125 126 def find_file(self, file_name, search_dirs): 127 """ 128 Return the path to a template with the given file name. 129 130 Arguments: 131 132 file_name: the file name of the template. 133 134 search_dirs: the list of directories in which to search. 135 136 """ 137 return self._find_path_required(search_dirs, file_name) 138 139 def find_name(self, template_name, search_dirs): 140 """ 141 Return the path to a template with the given name. 142 143 Arguments: 144 145 template_name: the name of the template. 146 147 search_dirs: the list of directories in which to search. 148 149 """ 150 file_name = self.make_file_name(template_name) 151 152 return self._find_path_required(search_dirs, file_name) 153 154 def find_object(self, obj, search_dirs, file_name=None): 155 """ 156 Return the path to a template associated with the given object. 157 158 """ 159 if file_name is None: 160 # TODO: should we define a make_file_name() method? 161 template_name = self.make_template_name(obj) 162 file_name = self.make_file_name(template_name) 163 164 dir_path = self.get_object_directory(obj) 165 166 if dir_path is not None: 167 search_dirs = [dir_path] + search_dirs 168 169 path = self._find_path_required(search_dirs, file_name) 170 171 return path 172