| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/python2.4 | |
| 2 # Copyright 2009, Google Inc. | |
| 3 # All rights reserved. | |
| 4 # | |
| 5 # Redistribution and use in source and binary forms, with or without | |
| 6 # modification, are permitted provided that the following conditions are | |
| 7 # met: | |
| 8 # | |
| 9 # * Redistributions of source code must retain the above copyright | |
| 10 # notice, this list of conditions and the following disclaimer. | |
| 11 # * Redistributions in binary form must reproduce the above | |
| 12 # copyright notice, this list of conditions and the following disclaimer | |
| 13 # in the documentation and/or other materials provided with the | |
| 14 # distribution. | |
| 15 # * Neither the name of Google Inc. nor the names of its | |
| 16 # contributors may be used to endorse or promote products derived from | |
| 17 # this software without specific prior written permission. | |
| 18 # | |
| 19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 30 | |
| 31 | |
| 32 """HTML formatter for ezt docs.""" | |
| 33 | |
| 34 | |
| 35 import os | |
| 36 import os.path | |
| 37 import re | |
| 38 import shutil | |
| 39 import sys | |
| 40 | |
| 41 | |
| 42 class DoxygenJavascriptifier(object): | |
| 43 """Convert C++ documentation into Javascript style. | |
| 44 | |
| 45 Documentation may be automatically generated using doxygen. However, | |
| 46 doxygen only outputs C++ formatted documentation, that is it uses C++ | |
| 47 conventions for specifying many items even if the java option is selected in | |
| 48 the configuration file. | |
| 49 | |
| 50 In order to make the documentation look like Javascript documentation, | |
| 51 additional corrections must be made which this class takes care of. | |
| 52 | |
| 53 Since doxygen allows you to name functions and class whatever you wish, this | |
| 54 class assumes that you have already chosen to name them with standard | |
| 55 Javascript conventions (such as camel-case) and therefore does not try to | |
| 56 correct that. | |
| 57 | |
| 58 Though many of the replacements are simply eliminated, they are left as | |
| 59 individual items in the dictionary for clarity. | |
| 60 | |
| 61 Attributes: | |
| 62 cpp_to_js: a list of c++ to javascript replacement tuples | |
| 63 static_function_regex: regular expression describing a static function | |
| 64 declaration | |
| 65 """ | |
| 66 | |
| 67 global_function_tag = '[<a class="doxygen-global">global function</a>]' | |
| 68 static_function2_regex = re.compile(r'<li>(.*)static(.*)$') | |
| 69 cpp_to_js = [(re.compile(r'::'), '.'), | |
| 70 (re.compile(r'\[pure\]'), ''), | |
| 71 (re.compile(r'\[pure virtual\]'), ''), | |
| 72 (re.compile(r'\[virtual\]'), ''), | |
| 73 (re.compile(r'\[protected\]'), ''), | |
| 74 (re.compile(r'\[static\]'), global_function_tag), | |
| 75 (re.compile(r'\[explicit\]'), ''), | |
| 76 (re.compile(r'\bvirtual\b'), ''), | |
| 77 (re.compile(r'\bpure\b'), ''), | |
| 78 (re.compile(r'\bprotected\b'), ''), | |
| 79 (re.compile(r'\bvoid\b'), ''), | |
| 80 (re.compile(r'\bunsigned\b'), ''), | |
| 81 (re.compile(r'\bstatic\b'), ''), | |
| 82 (re.compile(r'\bStatic\b'), 'Global'), | |
| 83 (re.compile(r'\bint\b'), 'Number'), | |
| 84 (re.compile(r'\bfloat\b'), 'Number'), | |
| 85 (re.compile(r'\bNULL\b'), 'null'), | |
| 86 (re.compile(r'=0'), ''), | |
| 87 # Replace "std.vector<class_name>" with "Array" since vectors | |
| 88 # don't exist in Javascript. There is no type for Array in | |
| 89 # Javascript. | |
| 90 (re.compile(r'std.vector<[\w\s<>"/.=+]*>'), 'Array'), | |
| 91 # Remove mention of Vectormath.Aos namespace since it is | |
| 92 # invisible to Javascript. | |
| 93 (re.compile(r'Vectormath.Aos.Vector3'), 'Vector3'), | |
| 94 (re.compile(r'Vectormath.Aos.Vector4'), 'Vector4'), | |
| 95 (re.compile(r'Vectormath.Aos.Point3'), 'Point3'), | |
| 96 (re.compile(r'Vectormath.Aos.Matrix4'), 'Matrix4'), | |
| 97 (re.compile(r'Vectormath.Aos.Quat'), 'Quat'), | |
| 98 (re.compile(r'\bconst\b'), '')] | |
| 99 | |
| 100 def Javascriptify(self, lines_list): | |
| 101 """Replaces C++-style items with more Javascript-like ones. | |
| 102 | |
| 103 This includes removing many identifiers such as pure, virtual, protected, | |
| 104 static and explicit which are not used in Javascript and replacing | |
| 105 notation such as '::' with its corresponding Javascript equivalent, | |
| 106 '.' in this case. | |
| 107 | |
| 108 Args: | |
| 109 lines_list: the list of lines to correct. | |
| 110 Returns: | |
| 111 the corrected lines in javascript format. | |
| 112 """ | |
| 113 for index, line in enumerate(lines_list): | |
| 114 line = self.LabelStaticAsGlobal(line) | |
| 115 for cpp_expression, js_expression in self.cpp_to_js: | |
| 116 line = re.sub(cpp_expression, js_expression, line) | |
| 117 lines_list[index] = line | |
| 118 return lines_list | |
| 119 | |
| 120 def LabelStaticAsGlobal(self, line): | |
| 121 """If a function is static, we indicate it is a global function. | |
| 122 | |
| 123 Since static does not make sense for Javascript we refer to these | |
| 124 functions as global. To designate global function, we place a | |
| 125 [global function] tag after the function declaration. Doxygen by | |
| 126 default leaves the static descriptor in front of the function so | |
| 127 this function looks for the descriptor, removes it and puts the | |
| 128 tag at the end. | |
| 129 | |
| 130 Args: | |
| 131 line: the line to correct. | |
| 132 Returns: | |
| 133 the corrected line. | |
| 134 """ | |
| 135 match = re.search(self.static_function2_regex, line) | |
| 136 if match is not None: | |
| 137 line = '<li>%s%s %s\n' % (match.group(1), match.group(2), | |
| 138 self.global_function_tag) | |
| 139 return line | |
| 140 | |
| 141 | |
| 142 class EZTFormatter(object): | |
| 143 """This class converts doxygen output into ezt ready files. | |
| 144 | |
| 145 When doxygen outputs files, it contains its own formatting such as adding | |
| 146 navigation tabs to the top of each page. This class contains several | |
| 147 functions for stripping down doxygen output and formatting doxygen output | |
| 148 to be easily parsed by ezt. | |
| 149 | |
| 150 This class assumes that any header/footer boilerplate code (that is code | |
| 151 added by the user rather than doxygen at the start or end of a file) is | |
| 152 indicated by special indicator strings surrounding it. Namely at the | |
| 153 start of boilerplate code: 'BOILERPLATE - DO NOT EDIT THIS BLOCK' and | |
| 154 at the end of boilerplate code: 'END OF BOILERPLATE'. | |
| 155 | |
| 156 Attributes: | |
| 157 content_start_regex: regular expression for end of boilerplate code | |
| 158 content_end_regex: regular expression for start of boilerplate code | |
| 159 div_regex: regular expression of html div | |
| 160 div_end_regex: regular expression of closing html div | |
| 161 navigation_regex: regular expression indicating doxygen navigation tabs | |
| 162 html_suffix_regex: regular expression html file suffix | |
| 163 current_directory: location of files being worked on | |
| 164 originals_directory: directory to store copies of original html files | |
| 165 modifiers: list of functions to apply to files passed in | |
| 166 """ | |
| 167 content_start_regex = re.compile('END OF BOILERPLATE') | |
| 168 content_end_regex = re.compile('BOILERPLATE - DO NOT EDIT THIS BLOCK') | |
| 169 div_regex = re.compile('<div(.*)>') | |
| 170 div_end_regex = re.compile('</div>') | |
| 171 navigation_regex = re.compile('<div class=\"tabs[0-9]*\"(.*)>') | |
| 172 summary_regex = re.compile('<div class=\"summary[0-9]*\"(.*)>') | |
| 173 html_suffix_regex = re.compile('\.html$') | |
| 174 | |
| 175 def __init__(self, current_directory): | |
| 176 | |
| 177 self.current_directory = current_directory | |
| 178 self.originals_directory = os.path.join(current_directory, 'original_html') | |
| 179 if not os.path.exists(self.originals_directory): | |
| 180 os.mkdir(self.originals_directory) | |
| 181 self.modifiers = [] | |
| 182 | |
| 183 def RegisterModifier(self, modifier): | |
| 184 """Adds the modifier function to the list of modifier functions. | |
| 185 | |
| 186 Args: | |
| 187 modifier: the function modifies a list of strings of code | |
| 188 """ | |
| 189 self.modifiers += [modifier] | |
| 190 | |
| 191 def MakeBackupCopy(self, input_file): | |
| 192 """Makes backup copy of input_file by copying to originals directory. | |
| 193 | |
| 194 Args: | |
| 195 input_file: basename of file to backup. | |
| 196 """ | |
| 197 shutil.copy(os.path.join(self.current_directory, input_file), | |
| 198 self.originals_directory) | |
| 199 | |
| 200 def PerformFunctionsOnFile(self, input_file): | |
| 201 """Opens a file as a list, modifies the list and writes to same file. | |
| 202 | |
| 203 Args: | |
| 204 input_file: the file being operated on. | |
| 205 """ | |
| 206 infile = open(input_file, 'r') | |
| 207 lines_list = infile.readlines() | |
| 208 infile.close() | |
| 209 | |
| 210 for function in self.modifiers: | |
| 211 lines_list = function(lines_list) | |
| 212 | |
| 213 outfile = open(input_file, 'w') | |
| 214 outfile.writelines(lines_list) | |
| 215 outfile.close() | |
| 216 | |
| 217 def FixBrackets(self, lines_list): | |
| 218 """Adjust brackets to be ezt style. | |
| 219 | |
| 220 Fix the brackets such that any open bracket '[' becomes '[[]' in order to | |
| 221 not conflict with the brackets ezt uses as special characters. | |
| 222 | |
| 223 EZT uses boilerplate code at the beginning and end of each file. When | |
| 224 editing a file, however, we don't want to touch the boilerplate code. | |
| 225 We can look for special indicators through regular expressions of | |
| 226 where the content lies between the boilerplate sections and then only | |
| 227 apply the desired function line by line to the html between those | |
| 228 sections. | |
| 229 | |
| 230 This is a destructive operation and will write over the input file. | |
| 231 | |
| 232 Args: | |
| 233 lines_list: the file to fix broken into lines. | |
| 234 Returns: | |
| 235 the lines_list with correctly formatted brackets. | |
| 236 """ | |
| 237 outside_boilerplate = False | |
| 238 for index, line in enumerate(lines_list): | |
| 239 if self.content_end_regex.search(line) and outside_boilerplate: | |
| 240 # We've reached the end of content; return our work | |
| 241 return lines_list | |
| 242 elif self.content_start_regex.search(line): | |
| 243 # We've found the start of real content, end of boilerplate | |
| 244 outside_boilerplate = True | |
| 245 elif outside_boilerplate: | |
| 246 # Inside real content; fix brackets | |
| 247 lines_list[index] = line.replace('[', '[[]') | |
| 248 else: | |
| 249 pass # Inside boilerplate. | |
| 250 return lines_list | |
| 251 | |
| 252 def RemoveNavigation(self, lines_list): | |
| 253 """Remove html defining navigation tabs. | |
| 254 | |
| 255 This function removes the lines between the <div class ="tabs">...</div> | |
| 256 which will eliminate the navigation tabs Doxygen outputs at the top of | |
| 257 its documentation. These are extraneous for Google's codesite which | |
| 258 produces its own navigation using the ezt templates. | |
| 259 | |
| 260 Nested <div>'s are handled, but it currently expects no more than one | |
| 261 <div>...</div> set per line. | |
| 262 | |
| 263 This is a destructive operation and will write over the input file. | |
| 264 | |
| 265 Args: | |
| 266 lines_list: the lines of the file to fix. | |
| 267 Returns: | |
| 268 the lines_list reformatted to not include tabs div set. | |
| 269 """ | |
| 270 start_index = -1 | |
| 271 div_queue = [] | |
| 272 start_end_pairs = {} | |
| 273 for index, line in enumerate(lines_list): | |
| 274 # Start by looking for the start of the navigation or summary section | |
| 275 if self.navigation_regex.search(line) or self.summary_regex.search(line): | |
| 276 start_index = index | |
| 277 if self.div_regex.search(line): | |
| 278 if start_index > -1: | |
| 279 div_queue.append(index) | |
| 280 if self.div_end_regex.search(line): | |
| 281 if start_index > -1: | |
| 282 # If we pop our start_index, we have found the matching </div> | |
| 283 # to our <div class="tabs"> | |
| 284 if div_queue.pop() == start_index: | |
| 285 start_end_pairs[start_index] = index | |
| 286 start_index = -1 | |
| 287 | |
| 288 # Delete the offending lines | |
| 289 keys = start_end_pairs.keys() | |
| 290 keys.sort() | |
| 291 keys.reverse() | |
| 292 for k in keys: | |
| 293 del lines_list[k:start_end_pairs[k]+1] | |
| 294 return lines_list | |
| 295 | |
| 296 def RenameFiles(self, input_file): | |
| 297 """Fix file suffixes from .html to .ezt. | |
| 298 | |
| 299 This will also backup original html files to the originals directory. | |
| 300 | |
| 301 This operation will overwrite the previous ezt file. | |
| 302 | |
| 303 NOTE: If the originals directory is not a directory, the file will | |
| 304 be overwritten. | |
| 305 | |
| 306 Args: | |
| 307 input_file: the file to fix. | |
| 308 """ | |
| 309 # If the file does not end in .html, we do not want to continue | |
| 310 if self.html_suffix_regex.search(input_file): | |
| 311 new_name = re.sub(self.html_suffix_regex, '.ezt', input_file) | |
| 312 shutil.copy(input_file, self.originals_directory) | |
| 313 # Rename file. If file exists, rename will not overwrite, and produce | |
| 314 # OSError. So delete the old file first in that case. | |
| 315 try: | |
| 316 os.rename(input_file, new_name) | |
| 317 except OSError: | |
| 318 os.remove(new_name) | |
| 319 os.rename(input_file, new_name) | |
| 320 | |
| 321 | |
| 322 def main(): | |
| 323 usage_string = ('This script is meant to convert .html files into .ezt files ' | |
| 324 'in addition to eliminating any C++-style notations and ' | |
| 325 'replacing them with their Javascript-style counter parts.\n' | |
| 326 'Note: This will write over existing files as well as nuke ' | |
| 327 'any associated directories such as "original_html" located ' | |
| 328 'in the same directory as the first argument\n' | |
| 329 'Usage: ezt_formatter.py <html_file(s)>') | |
| 330 | |
| 331 files = sys.argv[1:] | |
| 332 if not files: | |
| 333 # We have no files, so return, showing usage | |
| 334 print usage_string | |
| 335 return | |
| 336 | |
| 337 # current_directory should be the same for all files | |
| 338 current_directory = os.path.dirname(files[0]) | |
| 339 formatter = EZTFormatter(current_directory) | |
| 340 #dj = DoxygenJavascriptifier() | |
| 341 formatter.RegisterModifier(formatter.RemoveNavigation) | |
| 342 #formatter.RegisterModifier(dj.Javascriptify) | |
| 343 formatter.RegisterModifier(formatter.FixBrackets) | |
| 344 | |
| 345 for f in files: | |
| 346 formatter.PerformFunctionsOnFile(f) | |
| 347 formatter.RenameFiles(f) | |
| 348 | |
| 349 formatter.MakeBackupCopy('stylesheet.css') | |
| 350 | |
| 351 if __name__ == '__main__': | |
| 352 main() | |
| OLD | NEW |