Index: chrome/common/extensions/docs/server2/converter.py |
=================================================================== |
--- chrome/common/extensions/docs/server2/converter.py (revision 0) |
+++ chrome/common/extensions/docs/server2/converter.py (revision 0) |
@@ -0,0 +1,339 @@ |
+#!/usr/bin/env python |
+# Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+# Example run from the server2/ directory: |
+# $ converter.py ../static/ ../../api templates/articles/ templates/intros/ |
+# templates/public/ static/images/ -r |
+ |
+import json |
+import optparse |
+import os |
+import re |
+import shutil |
+import subprocess |
+import sys |
+ |
+sys.path.append(os.path.join(sys.path[0], os.pardir, os.pardir, os.pardir, |
+ os.pardir, os.pardir, 'tools')) |
+import json_comment_eater |
+ |
+from converter_html_parser import HandleDocFamily |
+from docs_server_utils import SanitizeAPIName |
+ |
+IGNORED_FILES = [ |
+ # These are custom files. |
+ '404', |
+ 'apps_api_index', |
+ 'api_index', |
+ 'experimental', |
+ 'samples', |
+ 'index', |
+ # These are APIs that should not have docs. |
+ 'test', |
+ 'experimental_idltest', |
+] |
+ |
+# These are mappings for APIs that have no intros. They are needed because the |
+# names of the JSON files do not give enough information on the actual API name. |
+CUSTOM_MAPPINGS = { |
+ 'experimental_input_virtual_keyboard': 'experimental_input_virtualKeyboard', |
+ 'input_ime': 'input_ime' |
+} |
+ |
+# From api_page_generator.js:548. |
not at google - send to devlin
2012/08/07 02:42:14
Make comment like
These are the extension-only AP
cduvall
2012/08/07 20:21:38
Done.
|
+EXTENSIONS_ONLY = [ |
+ 'browserAction', |
+ 'extension', |
+ 'fileBrowserHandler', |
not at google - send to devlin
2012/08/07 02:42:14
this is in _permission_features.json so you should
cduvall
2012/08/07 20:21:38
Done.
|
+ 'fontSettings', |
not at google - send to devlin
2012/08/07 02:42:14
ditto, and looks like it's available for platform_
cduvall
2012/08/07 20:21:38
Done.
|
+ 'input.ime', |
+ 'omnibox', |
+ 'pageAction', |
+ 'scriptBadge', |
+ 'windows', |
+ 'experimental.devtools.audits', |
+ 'experimental.devtools.console', |
+ 'experimental.discovery', |
+ 'experimental.infobars', |
+ 'experimental.commands', |
not at google - send to devlin
2012/08/07 02:42:14
not experimental anymore, so can levae this out.
cduvall
2012/08/07 20:21:38
Done.
|
+ 'experimental.offscreenTabs', |
+ 'experimental.processes', |
+ 'experimental.speechInput' |
+] |
+ |
+def _ReadFile(filename): |
+ with open(filename, 'r') as f: |
+ return f.read() |
+ |
+def _WriteFile(filename, data): |
+ with open(filename, 'w+') as f: |
+ f.write(data) |
+ |
+def _UnixName(name): |
+ """Returns the unix_style name for a given lowerCamelCase string. |
+ Shamelessly stolen from json_schema_compiler/model.py. |
+ """ |
+ name = os.path.splitext(name)[0] |
+ s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name) |
+ s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1) |
+ return s2.replace('.', '_').lower() |
+ |
+def _GetDestinations(api_name, api_dir): |
+ if api_name in EXTENSIONS_ONLY: |
+ return ['extensions'] |
+ if api_name.startswith('app'): |
+ return ['apps'] |
+ with open(os.path.join(api_dir, '_permission_features.json')) as f: |
+ permissions = json.loads(json_comment_eater.Nom(f.read())) |
+ permissions_key = api_name.replace('experimental_', '').replace('_', '.') |
+ if permissions_key in permissions: |
+ return_list = [] |
+ types = permissions[permissions_key]['extension_types'] |
+ if ('packaged_app' in types or |
+ 'hosted_app' in types or |
+ 'platform_app' in types): |
+ return_list.append('apps') |
+ if 'extension' in types: |
+ return_list.append('extensions') |
+ return return_list |
+ return ['extensions', 'apps'] |
+ |
+def _ListAllAPIs(dirname): |
+ all_files = [] |
+ for path, dirs, files in os.walk(dirname): |
+ if path == '.': |
+ all_files.extend([f for f in files |
+ if f.endswith('.json') or f.endswith('.idl')]) |
+ else: |
+ all_files.extend([os.path.join(path, f) for f in files |
+ if f.endswith('.json') or f.endswith('.idl')]) |
+ dirname = dirname.rstrip('/') + '/' |
+ return [f[len(dirname):] for f in all_files |
+ if os.path.splitext(f)[0].split('_')[-1] not in ['private', |
+ 'internal']] |
+ |
+def _MakeArticleTemplate(filename, path): |
+ is_apps = 'is_apps:TRUE' if path == 'apps' else 'is_apps:FALSE' |
+ return ('{{+partials.standard_%s_article article:intros.%s %s}}' % |
+ (path, filename, is_apps)) |
+ |
+def _MakeAPITemplate(intro_name, path, api_name, has_intro): |
+ is_apps = 'is_apps:TRUE' if path == 'apps' else 'is_apps:FALSE' |
+ if has_intro: |
+ return ('{{+partials.standard_%s_api api:apis.%s intro:intros.%s %s}}' % |
+ (path, api_name, intro_name, is_apps)) |
+ else: |
+ return ('{{+partials.standard_%s_api api:apis.%s %s}}' % |
+ (path, api_name, is_apps)) |
+ |
+def _GetAPIPath(name, api_dir): |
+ api_files = _ListAllAPIs(api_dir) |
+ for filename in api_files: |
+ if name == _UnixName(SanitizeAPIName(filename)): |
+ return _UnixName(filename) |
+ |
+def _CleanAPIs(source_dir, api_dir, intros_dest, template_dest, exclude): |
+ source_files = os.listdir(source_dir) |
+ source_files_unix = set(_UnixName(f) for f in source_files) |
+ api_files = set(_UnixName(SanitizeAPIName(f)) for f in _ListAllAPIs(api_dir)) |
+ intros_files = set(_UnixName(f) for f in os.listdir(intros_dest)) |
+ to_delete = api_files - source_files_unix - set(exclude) |
+ for path, dirs, files in os.walk(template_dest): |
+ for filename in files: |
+ no_ext = os.path.splitext(filename)[0] |
+ # Check for changes like appWindow -> app.window. |
+ if (_UnixName(filename) in source_files_unix - set(exclude) and |
+ no_ext not in source_files): |
+ os.remove(os.path.join(path, filename)) |
+ if _UnixName(filename) in to_delete: |
+ try: |
+ os.remove(os.path.join(intros_dest, filename)) |
+ except OSError: |
+ pass |
+ os.remove(os.path.join(path, filename)) |
+ |
+def _FormatFile(contents, path, name, image_dest, replace, is_api): |
+ # Copy all images referenced in the page. |
+ for image in re.findall(r'="\.\./images/([^"]*)"', contents): |
+ if not replace and os.path.exists(os.path.join(image_dest, image)): |
+ continue |
+ if '/' in image: |
+ try: |
+ os.makedirs(os.path.join(image_dest, image.rsplit('/', 1)[0])) |
+ except: |
+ pass |
+ shutil.copy( |
+ os.path.join(path, os.pardir, 'images', image), |
+ os.path.join(image_dest, image)) |
+ contents = re.sub(r'<!--.*?(BEGIN|END).*?-->', r'', contents) |
+ contents = re.sub(r'\.\./images', r'{{static}}/images', contents) |
+ exp = re.compile(r'<div[^>.]*?id="pageData-showTOC"[^>.]*?>.*?</div>', |
+ flags=re.DOTALL) |
+ contents = re.sub(exp, r'', contents) |
+ exp = re.compile(r'<div[^>.]*?id="pageData-name"[^>.]*?>(.*?)</div>', |
+ flags=re.DOTALL) |
+ if is_api: |
+ contents = re.sub(exp, r'', contents) |
+ else: |
+ contents = re.sub(exp, r'<h1>\1</h1>', contents) |
+ contents = contents.strip() |
+ contents = HandleDocFamily(contents) |
+ |
+ # Attempt to guess if the page has no title. |
+ if '<h1' not in contents and not is_api: |
+ title = _UnixName(name) |
+ title = ' '.join([part[0].upper() + part[1:] for part in title.split('_')]) |
+ contents = ('<h1 class="page_title">%s</h1>' % title) + contents |
+ return contents |
+ |
+def _ProcessName(name): |
+ processed_name = [] |
+ if name.startswith('experimental_'): |
+ name = name[len('experimental_'):] |
+ processed_name.append('experimental_') |
+ parts = name.split('_') |
+ processed_name.append(parts[0]) |
+ processed_name.extend([p[0].upper() + p[1:] for p in parts[1:]]) |
+ return ''.join(processed_name) |
+ |
+def _MoveAllFiles(source_dir, |
+ api_dir, |
+ articles_dest, |
+ intros_dest, |
+ template_dest, |
+ image_dest, |
+ replace=False, |
+ exclude_dir=None, |
+ svn=False): |
+ if exclude_dir is None: |
+ exclude_files = [] |
+ else: |
+ exclude_files = [_UnixName(f) for f in os.listdir(exclude_dir)] |
+ exclude_files.extend(IGNORED_FILES) |
+ api_files = _ListAllAPIs(api_dir) |
+ original_files = [] |
+ for path, dirs, files in os.walk(template_dest): |
+ original_files.extend(files) |
+ if replace: |
+ _CleanAPIs(source_dir, api_dir, intros_dest, template_dest, exclude_files) |
+ files = set(os.listdir(source_dir)) |
+ unix_files = [_UnixName(f) for f in files] |
+ for name in [SanitizeAPIName(f) for f in _ListAllAPIs(api_dir)]: |
+ if _UnixName(name) not in unix_files: |
+ files.add(name + '.html') |
+ for file_ in files: |
+ if (_UnixName(file_) in exclude_files or |
+ file_.startswith('.') or |
+ file_.startswith('_')): |
+ continue |
+ _MoveSingleFile(source_dir, |
+ file_, |
+ api_dir, |
+ articles_dest, |
+ intros_dest, |
+ template_dest, |
+ image_dest, |
+ replace=replace, |
+ svn=svn, |
+ original_files=original_files) |
+ |
+def _MoveSingleFile(source_dir, |
+ source_file, |
+ api_dir, |
+ articles_dest, |
+ intros_dest, |
+ template_dest, |
+ image_dest, |
+ replace=False, |
+ svn=False, |
+ original_files=None): |
+ unix_name = _UnixName(source_file) |
+ is_api = unix_name in [_UnixName(SanitizeAPIName(f)) |
+ for f in _ListAllAPIs(api_dir)] |
+ if unix_name in CUSTOM_MAPPINGS: |
+ processed_name = CUSTOM_MAPPINGS[unix_name] |
+ else: |
+ processed_name = os.path.splitext(source_file)[0].replace('.', '_') |
+ if (is_api and |
+ '_' in source_file.replace('experimental_', '') and |
+ not os.path.exists(os.path.join(source_dir, source_file))): |
+ processed_name = _ProcessName(processed_name) |
+ if (original_files is None or |
+ processed_name + '.html' not in original_files): |
+ print 'WARNING: The correct name of this file was guessed:' |
+ print '%s -> %s' % (os.path.splitext(source_file)[0], processed_name) |
+ print ('If this is incorrect, change |CUSTOM_MAPPINGS| in chrome/' |
+ 'common/extensions/docs/server2/converter.py.') |
+ try: |
+ static_data = _FormatFile(_ReadFile(os.path.join(source_dir, source_file)), |
+ source_dir, |
+ source_file, |
+ image_dest, |
+ replace, |
+ is_api) |
+ except IOError: |
+ static_data = None |
+ for path in _GetDestinations(processed_name, api_dir): |
+ template_file = os.path.join(template_dest, path, processed_name + '.html') |
+ if is_api: |
+ template_data = _MakeAPITemplate(processed_name, |
+ path, |
+ _GetAPIPath(unix_name, api_dir), |
+ static_data is not None) |
+ static_file = os.path.join(intros_dest, processed_name + '.html') |
+ else: |
+ template_data = _MakeArticleTemplate(unix_name, path) |
+ static_file = os.path.join(articles_dest, processed_name + '.html') |
+ if replace or not os.path.exists(template_file): |
+ _WriteFile(template_file, template_data) |
+ if static_data is not None and (replace or not os.path.exists(static_file)): |
+ if svn: |
+ if os.path.exists(static_file): |
+ subprocess.call(['svn', 'rm', '--force', static_file]) |
+ subprocess.call(['svn', |
+ 'cp', |
+ os.path.join(source_dir, source_file), |
+ static_file]) |
+ _WriteFile(static_file, static_data) |
+ |
+if __name__ == '__main__': |
+ parser = optparse.OptionParser( |
+ description='Converts static files from the old documentation system to ' |
+ 'the new one. If run without -f, all the files in |src| will ' |
+ 'be converted.', |
+ usage='usage: %prog [options] static_src_dir [-f static_src_file] ' |
+ 'api_dir articles_dest intros_dest template_dest image_dest') |
+ parser.add_option('-f', |
+ '--file', |
+ action='store_true', |
+ default=False, |
+ help='convert single file') |
+ parser.add_option('-s', |
+ '--svn', |
+ action='store_true', |
+ default=False, |
+ help='use svn copy to copy intro files') |
+ parser.add_option('-e', |
+ '--exclude', |
+ default=None, |
+ help='exclude files matching the names in this dir') |
+ parser.add_option('-r', |
+ '--replace', |
+ action='store_true', |
+ default=False, |
+ help='replace existing files') |
+ (opts, args) = parser.parse_args() |
+ if (not opts.file and len(args) != 6) or (opts.file and len(args) != 7): |
+ parser.error('incorrect number of arguments.') |
+ |
+ if opts.file: |
+ _MoveSingleFile(*args, replace=opts.replace, svn=opts.svn) |
+ else: |
+ _MoveAllFiles(*args, |
+ replace=opts.replace, |
+ exclude_dir=opts.exclude, |
+ svn=opts.svn) |
Property changes on: chrome/common/extensions/docs/server2/converter.py |
___________________________________________________________________ |
Added: svn:executable |
+ * |
Added: svn:eol-style |
+ LF |