Contact: fumanchu@aminus.org

Log in as guest/misc to create tickets

root/cobbler.py

Revision 134 (checked in by fumanchu, 4 years ago)

cobbler: better docstring.

  • Property svn:eol-style set to native
Line 
1 """Cobbler, the simplest Python templating system that could possibly work.
2
3
4 http://www.geocities.com/baby-lemonade/safairy.html explains it all, really:
5     
6     At the foot of the bed, Otumba kept wogs for poisonous snacks
7     such as the deadly cobbler and apply python.
8
9 Features:
10
11     * Dict-like interface; keys map to %(named)s format args.
12     * Re-usable; modify any/all params and assemble() as needed.
13     * str()-able, unicode() too
14     * Encoding-aware; just set Template.encoding before load_template().
15     * Stray "%" chars auto-replaced with "%%".
16     * Builtin subclasses for HTML Option, Checkbox, Hidden, and TableRows.
17     * Custom subclasses are a snap!
18
19 CherryPy example:
20
21     import os.path
22     localDir = os.path.dirname(__file__)
23
24     import cobbler
25     import myapp
26
27     class Root:
28         
29         def default(self, city):
30             main = cobbler.Template(localDir, 'templates', 'default.html')
31             main[u'City'] = myapp.safe_html(city)
32             main[u'ZipCodes'] = myapp.zips_for_city(city)
33             return main
34         default.exposed = True
35
36 default.html:
37
38     <html>
39     <head><title>Zip Codes for %(City)s</title></head>
40     <body>%(ZipCodes)s</body>
41     </html>
42
43 """
44
45 import codecs
46 import os
47 import re
48 from xml.sax.saxutils import quoteattr as quote
49
50
51 def warn(msg):
52     # Override this if you want warnings to go to your log.
53     import warnings
54     warnings.warn(msg)
55
56
57 class Template(object):
58     """Interpolates Python values with a block of source text.
59     
60     The source text is usually written in a markup language like HTML.
61     String-formatting is used to substitute Python values into the text.
62     For example, the source text "Hello, %(audience)s!" might be
63     interpolated with {'audience': 'World'} to produce "Hello, World!".
64     """
65    
66     params = {}
67     encoding = "ISO-8859-1"
68     templateText = u''
69    
70     def __init__(self, *fileparts):
71         self.exhausted = False
72         self.params = self.__class__.params.copy()
73         self.templateText = self.__class__.templateText
74         if fileparts:
75             self.templateText = self.load_template(*fileparts)
76    
77     def load_template(self, *fileparts):
78         """Return the source text from a file."""
79         newFileName = os.path.join(*fileparts)
80         infile = codecs.open(newFileName, "r", self.encoding)
81         t = infile.read()
82         infile.close()
83         t = re.sub(r'\r\n', u'\n', t)
84         # Replace single % with %% if not part of %(key)s clause
85         t = re.sub(r'(?<!%)%(?![%\(])', u'%%', t)
86         return t
87    
88     def assemble(self, final_params={}):
89         """Return the template text, interpolated with all params.
90         
91         final_params are discarded. They are NOT merged into self.params.
92         """
93         all = final_params.copy()
94         all.update(self.params)
95         return self.templateText % all
96    
97     def __getitem__(self, key):
98         return self.params[key]
99    
100     def __setitem__(self, key, value):
101         self.params[key] = value
102    
103     def __delitem__(self, key):
104         del self.params[key]
105    
106     def update(self, mapping):
107         self.params.update(mapping)
108    
109     def __iter__(self):
110         self.exhausted = False
111         return self
112    
113     def next(self):
114         if self.exhausted:
115             raise StopIteration
116         else:
117             self.exhausted = True
118             return self.assemble()
119    
120     def __str__(self):
121         return self.assemble()
122    
123     def __unicode__(self):
124         return self.assemble()
125
126
127 class OptionElement(Template):
128     """A Template for generating HTML <option> elements.
129     
130     Usage:
131     
132      1. When the value is distinct from the display:
133             opts = {}
134             for eachKey, eachItem in aCollection.items():
135                 opts[eachKey] = eachItem.value('Name')
136             output = OptionElement().assemble_all(opts, selItem)
137     
138      2. When the value is the same as the display:
139             opts = ['value1', 'value2', 'value3']
140             output = OptionElement().assemble_all(opts, selItem)
141     """
142    
143     templateText = (u'<option value="%(value)s"%(isSelected)s>'
144                     u'%(display)s</option>\n')
145    
146     def assemble_all(self, optionList, matchValue, sortByDisplay=True):
147         """Interpose supplied parameters into the predetermined template.
148         
149         If optionList is a sequence of strings,
150             they will be interposed as the 'display' variables.
151         If optionList is a sequence of tuples,
152             they will be interposed as ('value', 'display').
153         If optionList is a dictionary of strings,
154             it will be interposed as {'value': 'display'}.
155         """
156         if optionList:
157             if isinstance(optionList, dict):
158                 optionList = [(k, v) for k, v in optionList.iteritems()]
159                 if sortByDisplay:
160                     optionList.sort(lambda x, y: cmp(x[1], y[1]))
161             elif isinstance(optionList, list):
162                 if isinstance(optionList[0], tuple):
163                     if sortByDisplay:
164                         optionList.sort(lambda x, y: cmp(x[1], y[1]))
165                 else:
166                     if sortByDisplay:
167                         optionList.sort()
168        
169         tParam = []
170         matchValue = "%s" % matchValue
171         for eachValue in optionList:
172             sel = ""
173             # Since values may not be strings, coerce to string now in
174             # order to test equality, .startswith in next instructions.
175             if isinstance(eachValue, tuple):
176                 val = "%s" % eachValue[0]
177                 display = "%s" % eachValue[1]
178             else:
179                 val = display = ("%s" % eachValue)
180            
181             if val == matchValue:
182                 sel = " selected='selected' "
183            
184             if display.startswith(" ") or display.endswith(" "):
185                 msg = ("Option element has leading or trailing spaces in "
186                        "display:\n'%s'.\nSome browsers strip such spaces "
187                        "on display and when submitting HTML forms." % val)
188                 warn(msg)
189            
190             tParam.append(self.assemble({u'isSelected': sel,
191                                          u'value': val,
192                                          u'display': display,
193                                          }))
194         return u"".join(tParam)
195
196
197 class CheckboxElement(Template):
198     """A Template for generating HTML <input type='checkbox'> elements
199     
200     Usage:
201     
202     output = cobbler.CheckboxElement().assemble_all(name, checked)
203     """
204    
205     def __init__(self, value=None):
206         Template.__init__(self)
207         if value is None:
208             self.templateText = (u'<input type="checkbox" '
209                                  u'name="%(name)s"%(checked)s />')
210         else:
211             self.templateText = (u'<input type="checkbox" name="%(name)s" '
212                                  u'value=' + quote(value) + '%(checked)s />')
213    
214     def assemble_all(self, name, checked):
215         chk = u''
216         if checked:
217             chk = u' checked="checked" '
218         return self.assemble({u'name': name, u'checked': chk})
219
220
221 class HiddenElement(Template):
222     """A Template for generating HTML <input type='hidden'> elements
223     
224     Usage:
225         output = cobbler.HiddenElement().assemble_all(key)
226     """
227    
228     templateText = u'<input type="hidden" name=%(name)s value=%(value)s />'
229    
230     def assemble_all(self, name, value):
231         return self.assemble({u'name': quote(name), u'value': quote(value)})
232
233
234 class TableRows(Template):
235     """Template for generating HTML <tr><td></td><td></td></tr> elements."""
236    
237     def __init__(self, trClass=None):
238         self.exhausted = False
239         self.trClass = trClass
240         self.rows = []
241    
242     def add_row(self, *args):
243         self.rows.append(args)
244    
245     def assemble(self, final_rows=[]):
246         if self.trClass is None:
247             tr = u'<tr>'
248         else:
249             tr = u'<tr class=' + quote(self.trClass) + u'>'
250        
251         output = []
252         for row in (self.rows + final_rows):
253             output.append(tr)
254             for col in row:
255                 output.append(u"    <td>%s</td>" % col)
256             output.append("</tr>")
257         return u'\n'.join(output)
258
Note: See TracBrowser for help on using the browser.