| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 
|  | 2 # Use of this source code is govered by a BSD-style | 
|  | 3 # license that can be found in the LICENSE file or at | 
|  | 4 # https://developers.google.com/open-source/licenses/bsd | 
|  | 5 | 
|  | 6 """Some utility classes for interacting with templates.""" | 
|  | 7 | 
|  | 8 import cgi | 
|  | 9 import cStringIO | 
|  | 10 import httplib | 
|  | 11 import logging | 
|  | 12 import time | 
|  | 13 import types | 
|  | 14 | 
|  | 15 from third_party import ezt | 
|  | 16 | 
|  | 17 from protorpc import messages | 
|  | 18 | 
|  | 19 import settings | 
|  | 20 from framework import framework_constants | 
|  | 21 | 
|  | 22 | 
|  | 23 _DISPLAY_VALUE_TRAILING_CHARS = 8 | 
|  | 24 _DISPLAY_VALUE_TIP_CHARS = 120 | 
|  | 25 | 
|  | 26 | 
|  | 27 class PBProxy(object): | 
|  | 28   """Wraps a Protocol Buffer so it is easy to acceess from a template.""" | 
|  | 29 | 
|  | 30   def __init__(self, pb): | 
|  | 31     self.__pb = pb | 
|  | 32 | 
|  | 33   def __getattr__(self, name): | 
|  | 34     """Make the getters template friendly. | 
|  | 35 | 
|  | 36     Psudo-hack alert: When attributes end with _bool, they are converted in | 
|  | 37     to EZT style bools. I.e., if false return None, if true return True. | 
|  | 38 | 
|  | 39     Args: | 
|  | 40       name: the name of the attribute to get. | 
|  | 41 | 
|  | 42     Returns: | 
|  | 43       The value of that attribute (as an EZT bool if the name ends with _bool). | 
|  | 44     """ | 
|  | 45     if name.endswith('_bool'): | 
|  | 46       bool_name = name | 
|  | 47       name = name[0:-5] | 
|  | 48     else: | 
|  | 49       bool_name = None | 
|  | 50 | 
|  | 51     # Make it possible for a PBProxy-local attribute to override the protocol | 
|  | 52     # buffer field, or even to allow attributes to be added to the PBProxy that | 
|  | 53     # the protocol buffer does not even have. | 
|  | 54     if name in self.__dict__: | 
|  | 55       if callable(self.__dict__[name]): | 
|  | 56         val = self.__dict__[name]() | 
|  | 57       else: | 
|  | 58         val = self.__dict__[name] | 
|  | 59 | 
|  | 60       if bool_name: | 
|  | 61         return ezt.boolean(val) | 
|  | 62       return val | 
|  | 63 | 
|  | 64     if bool_name: | 
|  | 65       # return an ezt.boolean for the named field. | 
|  | 66       return ezt.boolean(getattr(self.__pb, name)) | 
|  | 67 | 
|  | 68     val = getattr(self.__pb, name) | 
|  | 69 | 
|  | 70     if isinstance(val, messages.Enum): | 
|  | 71       return int(val)  # TODO(jrobbins): use str() instead | 
|  | 72 | 
|  | 73     if isinstance(val, messages.Message): | 
|  | 74       return PBProxy(val) | 
|  | 75 | 
|  | 76     # Return a list of values whose Message entries | 
|  | 77     # have been wrapped in PBProxies. | 
|  | 78     if isinstance(val, (list, messages.FieldList)): | 
|  | 79       list_to_return = [] | 
|  | 80       for v in val: | 
|  | 81         if isinstance(v, messages.Message): | 
|  | 82           list_to_return.append(PBProxy(v)) | 
|  | 83         else: | 
|  | 84           list_to_return.append(v) | 
|  | 85       return list_to_return | 
|  | 86 | 
|  | 87     return val | 
|  | 88 | 
|  | 89   def DebugString(self): | 
|  | 90     """Return a string representation that is useful in debugging.""" | 
|  | 91     return 'PBProxy(%s)' % self.__pb | 
|  | 92 | 
|  | 93   def __eq__(self, other): | 
|  | 94     # Disable warning about accessing other.__pb. | 
|  | 95     # pylint: disable=protected-access | 
|  | 96     return isinstance(other, PBProxy) and self.__pb == other.__pb | 
|  | 97 | 
|  | 98 | 
|  | 99 _templates = {} | 
|  | 100 | 
|  | 101 | 
|  | 102 def GetTemplate( | 
|  | 103     template_path, compress_whitespace=True, eliminate_blank_lines=False, | 
|  | 104     base_format=ezt.FORMAT_HTML): | 
|  | 105   """Make a MonorailTemplate if needed, or reuse one if possible.""" | 
|  | 106   key = template_path, compress_whitespace, base_format | 
|  | 107   if key in _templates: | 
|  | 108     return _templates[key] | 
|  | 109 | 
|  | 110   template = MonorailTemplate( | 
|  | 111       template_path, compress_whitespace=compress_whitespace, | 
|  | 112       eliminate_blank_lines=eliminate_blank_lines, base_format=base_format) | 
|  | 113   _templates[key] = template | 
|  | 114   return template | 
|  | 115 | 
|  | 116 | 
|  | 117 class cStringIOUnicodeWrapper(object): | 
|  | 118   """Wrapper on cStringIO.StringIO that encodes unicode as UTF-8 as it goes.""" | 
|  | 119 | 
|  | 120   def __init__(self): | 
|  | 121     self.buffer = cStringIO.StringIO() | 
|  | 122 | 
|  | 123   def write(self, s): | 
|  | 124     if isinstance(s, unicode): | 
|  | 125       utf8_s = s.encode('utf-8') | 
|  | 126     else: | 
|  | 127       utf8_s = s | 
|  | 128     self.buffer.write(utf8_s) | 
|  | 129 | 
|  | 130   def getvalue(self): | 
|  | 131     return self.buffer.getvalue() | 
|  | 132 | 
|  | 133 | 
|  | 134 SNIFFABLE_PATTERNS = { | 
|  | 135   '%PDF-': '%NoNoNo-', | 
|  | 136 } | 
|  | 137 | 
|  | 138 | 
|  | 139 class MonorailTemplate(object): | 
|  | 140   """A template with additional functionality.""" | 
|  | 141 | 
|  | 142   def __init__(self, template_path, compress_whitespace=True, | 
|  | 143                eliminate_blank_lines=False, base_format=ezt.FORMAT_HTML): | 
|  | 144     self.template_path = template_path | 
|  | 145     self.template = None | 
|  | 146     self.compress_whitespace = compress_whitespace | 
|  | 147     self.base_format = base_format | 
|  | 148     self.eliminate_blank_lines = eliminate_blank_lines | 
|  | 149 | 
|  | 150   def WriteResponse(self, response, data, content_type=None): | 
|  | 151     """Write the parsed and filled in template to http server.""" | 
|  | 152     if content_type: | 
|  | 153       response.content_type = content_type | 
|  | 154 | 
|  | 155     response.status = data.get('http_response_code', httplib.OK) | 
|  | 156     whole_page = self.GetResponse(data) | 
|  | 157     if data.get('prevent_sniffing'): | 
|  | 158       for sniff_pattern, sniff_replacement in SNIFFABLE_PATTERNS.items(): | 
|  | 159         whole_page = whole_page.replace(sniff_pattern, sniff_replacement) | 
|  | 160     start = time.time() | 
|  | 161     response.write(whole_page) | 
|  | 162     logging.info('wrote response in %dms', int((time.time() - start) * 1000)) | 
|  | 163 | 
|  | 164   def GetResponse(self, data): | 
|  | 165     """Generate the text from the template and return it as a string.""" | 
|  | 166     template = self.GetTemplate() | 
|  | 167     start = time.time() | 
|  | 168     buf = cStringIOUnicodeWrapper() | 
|  | 169     template.generate(buf, data) | 
|  | 170     whole_page = buf.getvalue() | 
|  | 171     logging.info('rendering took %dms', int((time.time() - start) * 1000)) | 
|  | 172     logging.info('whole_page len is %r', len(whole_page)) | 
|  | 173     if self.eliminate_blank_lines: | 
|  | 174       lines = whole_page.split('\n') | 
|  | 175       whole_page = '\n'.join(line for line in lines if line.strip()) | 
|  | 176       logging.info('smaller whole_page len is %r', len(whole_page)) | 
|  | 177       logging.info('smaller rendering took %dms', | 
|  | 178                    int((time.time() - start) * 1000)) | 
|  | 179     return whole_page | 
|  | 180 | 
|  | 181   def GetTemplate(self): | 
|  | 182     """Parse the EZT template, or return an already parsed one.""" | 
|  | 183     # We don't operate directly on self.template to avoid races. | 
|  | 184     template = self.template | 
|  | 185 | 
|  | 186     if template is None or settings.dev_mode: | 
|  | 187       start = time.time() | 
|  | 188       template = ezt.Template( | 
|  | 189           fname=self.template_path, | 
|  | 190           compress_whitespace=self.compress_whitespace, | 
|  | 191           base_format=self.base_format) | 
|  | 192       logging.info('parsed in %dms', int((time.time() - start) * 1000)) | 
|  | 193       self.template = template | 
|  | 194 | 
|  | 195     return template | 
|  | 196 | 
|  | 197   def GetTemplatePath(self): | 
|  | 198     """Accessor for the template path specified in the constructor. | 
|  | 199 | 
|  | 200     Returns: | 
|  | 201       The string path for the template file provided to the constructor. | 
|  | 202     """ | 
|  | 203     return self.template_path | 
|  | 204 | 
|  | 205 | 
|  | 206 class EZTError(object): | 
|  | 207   """This class is a helper class to pass errors to EZT. | 
|  | 208 | 
|  | 209   This class is used to hold information that will be passed to EZT but might | 
|  | 210   be unset. All unset values return None (ie EZT False) | 
|  | 211   Example: page errors | 
|  | 212   """ | 
|  | 213 | 
|  | 214   def __getattr__(self, _name): | 
|  | 215     """This is the EZT retrieval function.""" | 
|  | 216     return None | 
|  | 217 | 
|  | 218   def AnyErrors(self): | 
|  | 219     return len(self.__dict__) != 0 | 
|  | 220 | 
|  | 221   def DebugString(self): | 
|  | 222     return 'EZTError(%s)' % self.__dict__ | 
|  | 223 | 
|  | 224   def SetError(self, name, value): | 
|  | 225     self.__setattr__(name, value) | 
|  | 226 | 
|  | 227   def SetCustomFieldError(self, field_id, value): | 
|  | 228     # This access works because of the custom __getattr__. | 
|  | 229     # pylint: disable=access-member-before-definition | 
|  | 230     # pylint: disable=attribute-defined-outside-init | 
|  | 231     if self.custom_fields is None: | 
|  | 232       self.custom_fields = [] | 
|  | 233     self.custom_fields.append(EZTItem(field_id=field_id, message=value)) | 
|  | 234 | 
|  | 235   any_errors = property(AnyErrors, None) | 
|  | 236 | 
|  | 237 def FitUnsafeText(text, length): | 
|  | 238   """Trim some unsafe (unescaped) text to a specific length. | 
|  | 239 | 
|  | 240   Three periods are appended if trimming occurs. Note that we cannot use | 
|  | 241   the ellipsis character (&hellip) because this is unescaped text. | 
|  | 242 | 
|  | 243   Args: | 
|  | 244     text: the string to fit (ASCII or unicode). | 
|  | 245     length: the length to trim to. | 
|  | 246 | 
|  | 247   Returns: | 
|  | 248     An ASCII or unicode string fitted to the given length. | 
|  | 249   """ | 
|  | 250   if not text: | 
|  | 251     return "" | 
|  | 252 | 
|  | 253   if len(text) <= length: | 
|  | 254     return text | 
|  | 255 | 
|  | 256   return text[:length] + '...' | 
|  | 257 | 
|  | 258 | 
|  | 259 def BytesKbOrMb(num_bytes): | 
|  | 260   """Return a human-readable string representation of a number of bytes.""" | 
|  | 261   if num_bytes < 1024: | 
|  | 262     return '%d bytes' % num_bytes  # e.g., 128 bytes | 
|  | 263   if num_bytes < 99 * 1024: | 
|  | 264     return '%.1f KB' % (num_bytes / 1024.0)  # e.g. 23.4 KB | 
|  | 265   if num_bytes < 1024 * 1024: | 
|  | 266     return '%d KB' % (num_bytes / 1024)  # e.g., 219 KB | 
|  | 267   if num_bytes < 99 * 1024 * 1024: | 
|  | 268     return '%.1f MB' % (num_bytes / 1024.0 / 1024.0)  # e.g., 21.9 MB | 
|  | 269   return '%d MB' % (num_bytes / 1024 / 1024)  # e.g., 100 MB | 
|  | 270 | 
|  | 271 | 
|  | 272 class EZTItem(object): | 
|  | 273   """A class that makes a collection of fields easily accessible in EZT.""" | 
|  | 274 | 
|  | 275   def __init__(self, **kwargs): | 
|  | 276     """Store all the given key-value pairs as fields of this object.""" | 
|  | 277     vars(self).update(kwargs) | 
|  | 278 | 
|  | 279   def __repr__(self): | 
|  | 280     fields = ', '.join('%r: %r' % (k, v) for k, v in | 
|  | 281                        sorted(vars(self).iteritems())) | 
|  | 282     return '%s({%s})' % (self.__class__.__name__, fields) | 
|  | 283 | 
|  | 284 | 
|  | 285 def ExpandLabels(page_data): | 
|  | 286   """If page_data has a 'labels' list, expand it into 'label1', etc. | 
|  | 287 | 
|  | 288   Args: | 
|  | 289     page_data: Template data which may include a 'labels' field. | 
|  | 290   """ | 
|  | 291   label_list = page_data.get('labels', []) | 
|  | 292   if isinstance(label_list, types.StringTypes): | 
|  | 293     label_list = [label.strip() for label in page_data['labels'].split(',')] | 
|  | 294 | 
|  | 295   for i in range(len(label_list)): | 
|  | 296     page_data['label%d' % i] = label_list[i] | 
|  | 297   for i in range(len(label_list), framework_constants.MAX_LABELS): | 
|  | 298     page_data['label%d' % i] = '' | 
|  | 299 | 
|  | 300 | 
|  | 301 class TextRun(object): | 
|  | 302   """A fragment of user-entered text that needs to be safely displyed.""" | 
|  | 303 | 
|  | 304   def __init__(self, content, tag=None, href=None): | 
|  | 305     self.content = content | 
|  | 306     self.tag = tag | 
|  | 307     self.href = href | 
|  | 308     self.title = None | 
|  | 309     self.css_class = None | 
| OLD | NEW | 
|---|