OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 # Example run from the server2/ directory: |
| 7 # $ converter.py ../static/ ../../api templates/articles/ templates/intros/ |
| 8 # templates/public/ static/images/ -r |
| 9 |
| 10 import optparse |
| 11 import os |
| 12 import re |
| 13 import shutil |
| 14 |
| 15 from docs_server_utils import SanitizeAPIName |
| 16 |
| 17 IGNORED_FILES = [ |
| 18 # These are custom files. |
| 19 '404', |
| 20 'api_index', |
| 21 'experimental', |
| 22 'samples', |
| 23 'index', |
| 24 # These are APIs that should not have docs. |
| 25 'test', |
| 26 'experimental_idltest', |
| 27 ] |
| 28 |
| 29 # These are mappings for APIs that have no intros. They are needed because the |
| 30 # names of the JSON files do not give enough information on the actual API name. |
| 31 CUSTOM_MAPPINGS = { |
| 32 'experimental_input_virtual_keyboard': 'experimental_input_virtualKeyboard', |
| 33 'input_ime': 'input_ime' |
| 34 } |
| 35 |
| 36 def _ReadFile(filename): |
| 37 with open(filename, 'r') as f: |
| 38 return f.read() |
| 39 |
| 40 def _WriteFile(filename, data): |
| 41 with open(filename, 'w+') as f: |
| 42 f.write(data) |
| 43 |
| 44 def _UnixName(name): |
| 45 """Returns the unix_style name for a given lowerCamelCase string. |
| 46 Shamelessly stolen from json_schema_compiler/model.py. |
| 47 """ |
| 48 name = os.path.splitext(name)[0] |
| 49 s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name) |
| 50 s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1) |
| 51 return s2.replace('.', '_').lower() |
| 52 |
| 53 def _ListAllAPIs(dirname): |
| 54 all_files = [] |
| 55 for path, dirs, files in os.walk(dirname): |
| 56 if path == '.': |
| 57 all_files.extend([f for f in files |
| 58 if f.endswith('.json') or f.endswith('.idl')]) |
| 59 else: |
| 60 all_files.extend([os.path.join(path, f) for f in files |
| 61 if f.endswith('.json') or f.endswith('.idl')]) |
| 62 dirname = dirname.rstrip('/') + '/' |
| 63 return [f[len(dirname):] for f in all_files |
| 64 if os.path.splitext(f)[0].split('_')[-1] not in ['private', |
| 65 'internal']] |
| 66 |
| 67 def _MakeArticleTemplate(filename): |
| 68 return '{{+partials.standard_article article:intros.%s}}' % filename |
| 69 |
| 70 def _MakeAPITemplate(intro_name, api_name, has_intro): |
| 71 if has_intro: |
| 72 return ('{{+partials.standard_api api:apis.%s intro:intros.%s}}' % |
| 73 (api_name, intro_name)) |
| 74 else: |
| 75 return '{{+partials.standard_api api:apis.%s}}' % api_name |
| 76 |
| 77 def _GetAPIPath(name, api_dir): |
| 78 api_files = _ListAllAPIs(api_dir) |
| 79 for filename in api_files: |
| 80 if name == _UnixName(SanitizeAPIName(filename)): |
| 81 return _UnixName(filename) |
| 82 |
| 83 def _CleanAPIs(source_dir, api_dir, intros_dest, template_dest, exclude): |
| 84 source_files = os.listdir(source_dir) |
| 85 source_files_unix = set(_UnixName(f) for f in source_files) |
| 86 api_files = set(_UnixName(SanitizeAPIName(f)) for f in _ListAllAPIs(api_dir)) |
| 87 intros_files = set(_UnixName(f) for f in os.listdir(intros_dest)) |
| 88 to_delete = api_files - source_files_unix - set(exclude) |
| 89 for filename in os.listdir(template_dest): |
| 90 no_ext = os.path.splitext(filename)[0] |
| 91 # Check for changes like appWindow -> app.window. |
| 92 if (_UnixName(filename) in source_files_unix - set(exclude) and |
| 93 no_ext not in source_files): |
| 94 os.remove(os.path.join(template_dest, filename)) |
| 95 if _UnixName(filename) in to_delete: |
| 96 try: |
| 97 os.remove(os.path.join(intros_dest, filename)) |
| 98 except OSError: |
| 99 pass |
| 100 os.remove(os.path.join(template_dest, filename)) |
| 101 |
| 102 def _FormatFile(contents, path, name, image_dest, replace, is_api): |
| 103 # Copy all images referenced in the page. |
| 104 for image in re.findall(r'src="\.\./images/([^"]*)"', contents): |
| 105 if not replace and os.path.exists(os.path.join(image_dest, image)): |
| 106 continue |
| 107 if '/' in image: |
| 108 try: |
| 109 os.makedirs(os.path.join(image_dest, image.rsplit('/', 1)[0])) |
| 110 except: |
| 111 pass |
| 112 shutil.copy( |
| 113 os.path.join(path, os.pardir, 'images', image), |
| 114 os.path.join(image_dest, image)) |
| 115 contents = re.sub(r'<!--.*?(BEGIN|END).*?-->', r'', contents) |
| 116 contents = re.sub(r'\.\./images', r'{{static}}/images', contents) |
| 117 exp = re.compile(r'<div[^>.]*?id="pageData-showTOC"[^>.]*?>.*?</div>', |
| 118 flags=re.DOTALL) |
| 119 contents = re.sub(exp, r'', contents) |
| 120 exp = re.compile(r'<div[^>.]*?id="pageData-name"[^>.]*?>(.*?)</div>', |
| 121 flags=re.DOTALL) |
| 122 if is_api: |
| 123 contents = re.sub(exp, r'', contents) |
| 124 else: |
| 125 contents = re.sub(exp, r'<h1>\1</h1>', contents) |
| 126 contents = contents.strip() |
| 127 |
| 128 # Attempt to guess if the page has no title. |
| 129 if '<h1' not in contents and not is_api: |
| 130 title = _UnixName(name) |
| 131 title = ' '.join([part[0].upper() + part[1:] for part in title.split('_')]) |
| 132 contents = ('<h1 class="page_title">%s</h1>' % title) + contents |
| 133 return contents |
| 134 |
| 135 def _ProcessName(name): |
| 136 processed_name = [] |
| 137 if name.startswith('experimental_'): |
| 138 name = name[len('experimental_'):] |
| 139 processed_name.append('experimental_') |
| 140 parts = name.split('_') |
| 141 processed_name.append(parts[0]) |
| 142 processed_name.extend([p[0].upper() + p[1:] for p in parts[1:]]) |
| 143 return ''.join(processed_name) |
| 144 |
| 145 def _MoveAllFiles(source_dir, |
| 146 api_dir, |
| 147 articles_dest, |
| 148 intros_dest, |
| 149 template_dest, |
| 150 image_dest, |
| 151 replace=False, |
| 152 exclude_dir=None): |
| 153 if exclude_dir is None: |
| 154 exclude_files = [] |
| 155 else: |
| 156 exclude_files = [_UnixName(f) for f in os.listdir(exclude_dir)] |
| 157 exclude_files.extend(IGNORED_FILES) |
| 158 api_files = _ListAllAPIs(api_dir) |
| 159 original_files = [os.path.splitext(f)[0] for f in os.listdir(template_dest)] |
| 160 if replace: |
| 161 _CleanAPIs(source_dir, api_dir, intros_dest, template_dest, exclude_files) |
| 162 files = set(os.listdir(source_dir)) |
| 163 unix_files = [_UnixName(f) for f in files] |
| 164 for name in [SanitizeAPIName(f) for f in _ListAllAPIs(api_dir)]: |
| 165 if _UnixName(name) not in unix_files: |
| 166 files.add(name + '.html') |
| 167 for file_ in files: |
| 168 if (_UnixName(file_) in exclude_files or |
| 169 file_.startswith('.') or |
| 170 file_.startswith('_')): |
| 171 continue |
| 172 _MoveSingleFile(source_dir, |
| 173 file_, |
| 174 api_dir, |
| 175 articles_dest, |
| 176 intros_dest, |
| 177 template_dest, |
| 178 image_dest, |
| 179 replace=replace, |
| 180 original_files=original_files) |
| 181 |
| 182 def _MoveSingleFile(source_dir, |
| 183 source_file, |
| 184 api_dir, |
| 185 articles_dest, |
| 186 intros_dest, |
| 187 template_dest, |
| 188 image_dest, |
| 189 replace=False, |
| 190 original_files=None): |
| 191 unix_name = _UnixName(source_file) |
| 192 is_api = unix_name in [_UnixName(SanitizeAPIName(f)) |
| 193 for f in _ListAllAPIs(api_dir)] |
| 194 if unix_name in CUSTOM_MAPPINGS: |
| 195 processed_name = CUSTOM_MAPPINGS[unix_name] |
| 196 else: |
| 197 processed_name = os.path.splitext(source_file)[0].replace('.', '_') |
| 198 if (is_api and |
| 199 '_' in source_file.replace('experimental_', '') and |
| 200 not os.path.exists(os.path.join(source_dir, source_file))): |
| 201 processed_name = _ProcessName(processed_name) |
| 202 if original_files is None or processed_name not in original_files: |
| 203 print 'WARNING: The correct name of this file was guessed:' |
| 204 print |
| 205 print '%s -> %s' % (os.path.splitext(source_file)[0], processed_name) |
| 206 print |
| 207 print ('If this is incorrect, change |CUSTOM_MAPPINGS| in chrome/' |
| 208 'common/extensions/docs/server2/converter.py.') |
| 209 try: |
| 210 static_data = _FormatFile(_ReadFile(os.path.join(source_dir, source_file)), |
| 211 source_dir, |
| 212 source_file, |
| 213 image_dest, |
| 214 replace, |
| 215 is_api) |
| 216 except IOError: |
| 217 static_data = None |
| 218 template_file = os.path.join(template_dest, processed_name + '.html') |
| 219 if is_api: |
| 220 template_data = _MakeAPITemplate(processed_name, |
| 221 _GetAPIPath(unix_name, api_dir), |
| 222 static_data is not None) |
| 223 static_file = os.path.join(intros_dest, processed_name + '.html') |
| 224 else: |
| 225 template_data = _MakeArticleTemplate(unix_name) |
| 226 static_file = os.path.join(articles_dest, processed_name + '.html') |
| 227 if replace or not os.path.exists(template_file): |
| 228 _WriteFile(template_file, template_data) |
| 229 if static_data is not None and (replace or not os.path.exists(static_file)): |
| 230 _WriteFile(static_file, static_data) |
| 231 |
| 232 if __name__ == '__main__': |
| 233 parser = optparse.OptionParser( |
| 234 description='Converts static files from the old documentation system to ' |
| 235 'the new one. If run without -f, all the files in |src| will ' |
| 236 'be converted.', |
| 237 usage='usage: %prog [options] static_src_dir [-f static_src_file] ' |
| 238 'api_dir articles_dest intros_dest template_dest image_dest') |
| 239 parser.add_option('-f', |
| 240 '--file', |
| 241 action='store_true', |
| 242 default=False, |
| 243 help='convert single file') |
| 244 parser.add_option('-e', |
| 245 '--exclude', |
| 246 default=None, |
| 247 help='exclude files matching the names in this dir') |
| 248 parser.add_option('-r', |
| 249 '--replace', |
| 250 action='store_true', |
| 251 default=False, |
| 252 help='replace existing files') |
| 253 (opts, args) = parser.parse_args() |
| 254 if (not opts.file and len(args) != 6) or (opts.file and len(args) != 7): |
| 255 parser.error('incorrect number of arguments.') |
| 256 |
| 257 if opts.file: |
| 258 _MoveSingleFile(*args, replace=opts.replace) |
| 259 else: |
| 260 _MoveAllFiles(*args, replace=opts.replace, exclude_dir=opts.exclude) |
OLD | NEW |