Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(69)

Side by Side Diff: chrome/common/extensions/docs/server2/converter.py

Issue 10834130: Extensions Docs Server: Doc conversion script - SVN (Closed) Base URL: https://src.chromium.org/chrome/trunk/src/
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
Property Changes:
Added: svn:executable
+ *
Added: svn:eol-style
+ LF
OLDNEW
(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 json
11 import optparse
12 import os
13 import re
14 import shutil
15 import subprocess
16 import sys
17
18 sys.path.append(os.path.join(sys.path[0], os.pardir, os.pardir, os.pardir,
19 os.pardir, os.pardir, 'tools'))
20 import json_comment_eater
21
22 from converter_html_parser import HandleDocFamily
23 from docs_server_utils import SanitizeAPIName
24
25 IGNORED_FILES = [
26 # These are custom files.
27 '404',
28 'apps_api_index',
29 'api_index',
30 'experimental',
31 'samples',
32 'index',
33 # These are APIs that should not have docs.
34 'test',
35 'experimental_idltest',
36 ]
37
38 # These are mappings for APIs that have no intros. They are needed because the
39 # names of the JSON files do not give enough information on the actual API name.
40 CUSTOM_MAPPINGS = {
41 'experimental_input_virtual_keyboard': 'experimental_input_virtualKeyboard',
42 'input_ime': 'input_ime'
43 }
44
45 # 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.
46 EXTENSIONS_ONLY = [
47 'browserAction',
48 'extension',
49 '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.
50 '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.
51 'input.ime',
52 'omnibox',
53 'pageAction',
54 'scriptBadge',
55 'windows',
56 'experimental.devtools.audits',
57 'experimental.devtools.console',
58 'experimental.discovery',
59 'experimental.infobars',
60 '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.
61 'experimental.offscreenTabs',
62 'experimental.processes',
63 'experimental.speechInput'
64 ]
65
66 def _ReadFile(filename):
67 with open(filename, 'r') as f:
68 return f.read()
69
70 def _WriteFile(filename, data):
71 with open(filename, 'w+') as f:
72 f.write(data)
73
74 def _UnixName(name):
75 """Returns the unix_style name for a given lowerCamelCase string.
76 Shamelessly stolen from json_schema_compiler/model.py.
77 """
78 name = os.path.splitext(name)[0]
79 s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name)
80 s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
81 return s2.replace('.', '_').lower()
82
83 def _GetDestinations(api_name, api_dir):
84 if api_name in EXTENSIONS_ONLY:
85 return ['extensions']
86 if api_name.startswith('app'):
87 return ['apps']
88 with open(os.path.join(api_dir, '_permission_features.json')) as f:
89 permissions = json.loads(json_comment_eater.Nom(f.read()))
90 permissions_key = api_name.replace('experimental_', '').replace('_', '.')
91 if permissions_key in permissions:
92 return_list = []
93 types = permissions[permissions_key]['extension_types']
94 if ('packaged_app' in types or
95 'hosted_app' in types or
96 'platform_app' in types):
97 return_list.append('apps')
98 if 'extension' in types:
99 return_list.append('extensions')
100 return return_list
101 return ['extensions', 'apps']
102
103 def _ListAllAPIs(dirname):
104 all_files = []
105 for path, dirs, files in os.walk(dirname):
106 if path == '.':
107 all_files.extend([f for f in files
108 if f.endswith('.json') or f.endswith('.idl')])
109 else:
110 all_files.extend([os.path.join(path, f) for f in files
111 if f.endswith('.json') or f.endswith('.idl')])
112 dirname = dirname.rstrip('/') + '/'
113 return [f[len(dirname):] for f in all_files
114 if os.path.splitext(f)[0].split('_')[-1] not in ['private',
115 'internal']]
116
117 def _MakeArticleTemplate(filename, path):
118 is_apps = 'is_apps:TRUE' if path == 'apps' else 'is_apps:FALSE'
119 return ('{{+partials.standard_%s_article article:intros.%s %s}}' %
120 (path, filename, is_apps))
121
122 def _MakeAPITemplate(intro_name, path, api_name, has_intro):
123 is_apps = 'is_apps:TRUE' if path == 'apps' else 'is_apps:FALSE'
124 if has_intro:
125 return ('{{+partials.standard_%s_api api:apis.%s intro:intros.%s %s}}' %
126 (path, api_name, intro_name, is_apps))
127 else:
128 return ('{{+partials.standard_%s_api api:apis.%s %s}}' %
129 (path, api_name, is_apps))
130
131 def _GetAPIPath(name, api_dir):
132 api_files = _ListAllAPIs(api_dir)
133 for filename in api_files:
134 if name == _UnixName(SanitizeAPIName(filename)):
135 return _UnixName(filename)
136
137 def _CleanAPIs(source_dir, api_dir, intros_dest, template_dest, exclude):
138 source_files = os.listdir(source_dir)
139 source_files_unix = set(_UnixName(f) for f in source_files)
140 api_files = set(_UnixName(SanitizeAPIName(f)) for f in _ListAllAPIs(api_dir))
141 intros_files = set(_UnixName(f) for f in os.listdir(intros_dest))
142 to_delete = api_files - source_files_unix - set(exclude)
143 for path, dirs, files in os.walk(template_dest):
144 for filename in files:
145 no_ext = os.path.splitext(filename)[0]
146 # Check for changes like appWindow -> app.window.
147 if (_UnixName(filename) in source_files_unix - set(exclude) and
148 no_ext not in source_files):
149 os.remove(os.path.join(path, filename))
150 if _UnixName(filename) in to_delete:
151 try:
152 os.remove(os.path.join(intros_dest, filename))
153 except OSError:
154 pass
155 os.remove(os.path.join(path, filename))
156
157 def _FormatFile(contents, path, name, image_dest, replace, is_api):
158 # Copy all images referenced in the page.
159 for image in re.findall(r'="\.\./images/([^"]*)"', contents):
160 if not replace and os.path.exists(os.path.join(image_dest, image)):
161 continue
162 if '/' in image:
163 try:
164 os.makedirs(os.path.join(image_dest, image.rsplit('/', 1)[0]))
165 except:
166 pass
167 shutil.copy(
168 os.path.join(path, os.pardir, 'images', image),
169 os.path.join(image_dest, image))
170 contents = re.sub(r'<!--.*?(BEGIN|END).*?-->', r'', contents)
171 contents = re.sub(r'\.\./images', r'{{static}}/images', contents)
172 exp = re.compile(r'<div[^>.]*?id="pageData-showTOC"[^>.]*?>.*?</div>',
173 flags=re.DOTALL)
174 contents = re.sub(exp, r'', contents)
175 exp = re.compile(r'<div[^>.]*?id="pageData-name"[^>.]*?>(.*?)</div>',
176 flags=re.DOTALL)
177 if is_api:
178 contents = re.sub(exp, r'', contents)
179 else:
180 contents = re.sub(exp, r'<h1>\1</h1>', contents)
181 contents = contents.strip()
182 contents = HandleDocFamily(contents)
183
184 # Attempt to guess if the page has no title.
185 if '<h1' not in contents and not is_api:
186 title = _UnixName(name)
187 title = ' '.join([part[0].upper() + part[1:] for part in title.split('_')])
188 contents = ('<h1 class="page_title">%s</h1>' % title) + contents
189 return contents
190
191 def _ProcessName(name):
192 processed_name = []
193 if name.startswith('experimental_'):
194 name = name[len('experimental_'):]
195 processed_name.append('experimental_')
196 parts = name.split('_')
197 processed_name.append(parts[0])
198 processed_name.extend([p[0].upper() + p[1:] for p in parts[1:]])
199 return ''.join(processed_name)
200
201 def _MoveAllFiles(source_dir,
202 api_dir,
203 articles_dest,
204 intros_dest,
205 template_dest,
206 image_dest,
207 replace=False,
208 exclude_dir=None,
209 svn=False):
210 if exclude_dir is None:
211 exclude_files = []
212 else:
213 exclude_files = [_UnixName(f) for f in os.listdir(exclude_dir)]
214 exclude_files.extend(IGNORED_FILES)
215 api_files = _ListAllAPIs(api_dir)
216 original_files = []
217 for path, dirs, files in os.walk(template_dest):
218 original_files.extend(files)
219 if replace:
220 _CleanAPIs(source_dir, api_dir, intros_dest, template_dest, exclude_files)
221 files = set(os.listdir(source_dir))
222 unix_files = [_UnixName(f) for f in files]
223 for name in [SanitizeAPIName(f) for f in _ListAllAPIs(api_dir)]:
224 if _UnixName(name) not in unix_files:
225 files.add(name + '.html')
226 for file_ in files:
227 if (_UnixName(file_) in exclude_files or
228 file_.startswith('.') or
229 file_.startswith('_')):
230 continue
231 _MoveSingleFile(source_dir,
232 file_,
233 api_dir,
234 articles_dest,
235 intros_dest,
236 template_dest,
237 image_dest,
238 replace=replace,
239 svn=svn,
240 original_files=original_files)
241
242 def _MoveSingleFile(source_dir,
243 source_file,
244 api_dir,
245 articles_dest,
246 intros_dest,
247 template_dest,
248 image_dest,
249 replace=False,
250 svn=False,
251 original_files=None):
252 unix_name = _UnixName(source_file)
253 is_api = unix_name in [_UnixName(SanitizeAPIName(f))
254 for f in _ListAllAPIs(api_dir)]
255 if unix_name in CUSTOM_MAPPINGS:
256 processed_name = CUSTOM_MAPPINGS[unix_name]
257 else:
258 processed_name = os.path.splitext(source_file)[0].replace('.', '_')
259 if (is_api and
260 '_' in source_file.replace('experimental_', '') and
261 not os.path.exists(os.path.join(source_dir, source_file))):
262 processed_name = _ProcessName(processed_name)
263 if (original_files is None or
264 processed_name + '.html' not in original_files):
265 print 'WARNING: The correct name of this file was guessed:'
266 print
267 print '%s -> %s' % (os.path.splitext(source_file)[0], processed_name)
268 print
269 print ('If this is incorrect, change |CUSTOM_MAPPINGS| in chrome/'
270 'common/extensions/docs/server2/converter.py.')
271 try:
272 static_data = _FormatFile(_ReadFile(os.path.join(source_dir, source_file)),
273 source_dir,
274 source_file,
275 image_dest,
276 replace,
277 is_api)
278 except IOError:
279 static_data = None
280 for path in _GetDestinations(processed_name, api_dir):
281 template_file = os.path.join(template_dest, path, processed_name + '.html')
282 if is_api:
283 template_data = _MakeAPITemplate(processed_name,
284 path,
285 _GetAPIPath(unix_name, api_dir),
286 static_data is not None)
287 static_file = os.path.join(intros_dest, processed_name + '.html')
288 else:
289 template_data = _MakeArticleTemplate(unix_name, path)
290 static_file = os.path.join(articles_dest, processed_name + '.html')
291 if replace or not os.path.exists(template_file):
292 _WriteFile(template_file, template_data)
293 if static_data is not None and (replace or not os.path.exists(static_file)):
294 if svn:
295 if os.path.exists(static_file):
296 subprocess.call(['svn', 'rm', '--force', static_file])
297 subprocess.call(['svn',
298 'cp',
299 os.path.join(source_dir, source_file),
300 static_file])
301 _WriteFile(static_file, static_data)
302
303 if __name__ == '__main__':
304 parser = optparse.OptionParser(
305 description='Converts static files from the old documentation system to '
306 'the new one. If run without -f, all the files in |src| will '
307 'be converted.',
308 usage='usage: %prog [options] static_src_dir [-f static_src_file] '
309 'api_dir articles_dest intros_dest template_dest image_dest')
310 parser.add_option('-f',
311 '--file',
312 action='store_true',
313 default=False,
314 help='convert single file')
315 parser.add_option('-s',
316 '--svn',
317 action='store_true',
318 default=False,
319 help='use svn copy to copy intro files')
320 parser.add_option('-e',
321 '--exclude',
322 default=None,
323 help='exclude files matching the names in this dir')
324 parser.add_option('-r',
325 '--replace',
326 action='store_true',
327 default=False,
328 help='replace existing files')
329 (opts, args) = parser.parse_args()
330 if (not opts.file and len(args) != 6) or (opts.file and len(args) != 7):
331 parser.error('incorrect number of arguments.')
332
333 if opts.file:
334 _MoveSingleFile(*args, replace=opts.replace, svn=opts.svn)
335 else:
336 _MoveAllFiles(*args,
337 replace=opts.replace,
338 exclude_dir=opts.exclude,
339 svn=opts.svn)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698