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. | |
not at google - send to devlin
2012/08/01 20:41:04
seems like most of these you could figure out base
cduvall
2012/08/02 00:54:06
Done.
| |
31 CUSTOM_MAPPINGS = { | |
32 'experimental_bookmark_manager': 'experimental_bookmarkManager', | |
33 'experimental_input_virtual_keyboard': 'experimental_input_virtualKeyboard', | |
34 'experimental_media_galleries': 'experimental_mediaGalleries', | |
35 'experimental_offscreen_tabs': 'experimental_offscreenTabs', | |
36 'input_ime': 'input_ime', | |
37 'file_system': 'fileSystem', | |
38 'page_actions': 'pageActions', | |
39 'script_badge': 'scriptBadge' | |
40 } | |
41 | |
42 def _ReadFile(filename): | |
43 with open(filename, 'r') as f: | |
44 return f.read() | |
45 | |
46 def _WriteFile(filename, data): | |
47 with open(filename, 'w+') as f: | |
48 f.write(data) | |
49 | |
50 def _UnixName(name): | |
51 """Returns the unix_style name for a given lowerCamelCase string. | |
52 Shamelessly stolen from json_schema_compiler/model.py. | |
53 """ | |
54 name = os.path.splitext(name)[0] | |
55 s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name) | |
56 s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1) | |
57 return s2.replace('.', '_').lower() | |
58 | |
59 def _ListAllAPIs(dirname): | |
60 all_files = [] | |
61 for path, dirs, files in os.walk(dirname): | |
62 if path == '.': | |
63 all_files.extend([f for f in files | |
64 if f.endswith('.json') or f.endswith('.idl')]) | |
65 else: | |
66 all_files.extend([os.path.join(path, f) for f in files | |
67 if f.endswith('.json') or f.endswith('.idl')]) | |
68 dirname = dirname.rstrip('/') + '/' | |
69 return [f[len(dirname):] for f in all_files | |
70 if os.path.splitext(f)[0].split('_')[-1] not in ['private', | |
71 'internal']] | |
72 | |
73 def _MakeArticleTemplate(filename): | |
74 return '{{+partials.standard_article article:intros.%s}}' % filename | |
75 | |
76 def _MakeAPITemplate(intro_name, api_name, has_intro): | |
77 if has_intro: | |
78 return ('{{+partials.standard_api api:apis.%s intro:intros.%s}}' % | |
79 (api_name, intro_name)) | |
80 else: | |
81 return '{{+partials.standard_api api:apis.%s}}' % api_name | |
82 | |
83 def _GetAPIPath(name, api_dir): | |
84 api_files = _ListAllAPIs(api_dir) | |
85 for filename in api_files: | |
86 if name == _UnixName(SanitizeAPIName(filename)): | |
87 return _UnixName(filename) | |
88 | |
89 def _CleanAPIs(source_dir, api_dir, intros_dest, template_dest, exclude): | |
90 source_files = os.listdir(source_dir) | |
91 source_files_unix = set(_UnixName(f) for f in source_files) | |
92 api_files = set(_UnixName(SanitizeAPIName(f)) for f in _ListAllAPIs(api_dir)) | |
93 intros_files = set(_UnixName(f) for f in os.listdir(intros_dest)) | |
94 to_delete = api_files - source_files_unix - set(exclude) | |
95 for filename in os.listdir(template_dest): | |
96 no_ext = os.path.splitext(filename)[0] | |
97 # Check for changes like appWindow -> app.window. | |
98 if (_UnixName(filename) in source_files_unix - set(exclude) and | |
99 no_ext not in source_files): | |
100 os.remove(os.path.join(template_dest, filename)) | |
101 if _UnixName(filename) in to_delete: | |
102 try: | |
103 os.remove(os.path.join(intros_dest, filename)) | |
104 except OSError: | |
105 pass | |
106 os.remove(os.path.join(template_dest, filename)) | |
107 | |
108 def _FormatFile(contents, path, name, image_dest, replace, is_api): | |
109 # Copy all images referenced in the page. | |
110 for image in re.findall(r'src="\.\./images/([^"]*)"', contents): | |
111 if not replace and os.path.exists(os.path.join(image_dest, image)): | |
112 continue | |
113 if '/' in image: | |
114 try: | |
115 os.makedirs(os.path.join(image_dest, image.rsplit('/', 1)[0])) | |
116 except: | |
117 pass | |
118 shutil.copy( | |
119 os.path.join(path, os.pardir, 'images', image), | |
120 os.path.join(image_dest, image)) | |
121 contents = re.sub(r'<!--.*(BEGIN|END).*-->', r'', contents) | |
122 contents = re.sub(r'\.\./images', r'{{static}}/images', contents) | |
123 contents = re.sub(r'<div.*id="pageData-showTOC".*>.*</div>', r'', contents) | |
124 if is_api: | |
125 contents = re.sub(r'<div.*id="pageData-name".*>.*</div>', r'', contents) | |
126 else: | |
127 contents = re.sub(r'<div.*id="pageData-name".*>(.*)</div>', | |
128 r'<h1 class="page_title">\1</h1>', | |
129 contents) | |
130 # Remove blank lines. | |
131 contents = '\n'.join([line for line in contents.split('\n') if line.strip()]) | |
132 | |
133 # Attempt to guess if the page has no title. | |
134 if '<h1' not in contents and not is_api: | |
135 title = _UnixName(name) | |
136 title = ' '.join([part[0].upper() + part[1:] for part in title.split('_')]) | |
137 contents = ('<h1 class="page_title">%s</h1>' % title) + contents | |
138 return contents | |
139 | |
140 def _MoveAllFiles(source_dir, | |
141 api_dir, | |
142 articles_dest, | |
143 intros_dest, | |
144 template_dest, | |
145 image_dest, | |
146 replace=False, | |
147 exclude_dir=None): | |
148 if exclude_dir is None: | |
149 exclude_files = [] | |
150 else: | |
151 exclude_files = [_UnixName(f) for f in os.listdir(exclude_dir)] | |
152 exclude_files.extend(IGNORED_FILES) | |
153 api_files = _ListAllAPIs(api_dir) | |
154 if replace: | |
155 _CleanAPIs(source_dir, api_dir, intros_dest, template_dest, exclude_files) | |
156 files = set(os.listdir(source_dir)) | |
157 unix_files = [_UnixName(f) for f in files] | |
158 for name in [SanitizeAPIName(f) for f in _ListAllAPIs(api_dir)]: | |
159 if _UnixName(name) not in unix_files: | |
160 files.add(name + '.html') | |
161 for file_ in files: | |
162 if (_UnixName(file_) in exclude_files or | |
163 file_.startswith('.') or | |
164 file_.startswith('_')): | |
165 continue | |
166 _MoveSingleFile(source_dir, | |
167 file_, | |
168 api_dir, | |
169 articles_dest, | |
170 intros_dest, | |
171 template_dest, | |
172 image_dest, | |
173 replace) | |
174 | |
175 def _MoveSingleFile(source_dir, | |
176 source_file, | |
177 api_dir, | |
178 articles_dest, | |
179 intros_dest, | |
180 template_dest, | |
181 image_dest, | |
182 replace=False): | |
183 unix_name = _UnixName(source_file) | |
184 is_api = unix_name in [_UnixName(SanitizeAPIName(f)) | |
185 for f in _ListAllAPIs(api_dir)] | |
186 if unix_name in CUSTOM_MAPPINGS: | |
187 processed_name = CUSTOM_MAPPINGS[unix_name] | |
188 else: | |
189 processed_name = os.path.splitext(source_file)[0].replace('.', '_') | |
190 if (is_api and | |
191 '_' in processed_name.replace('experimental_', '') and | |
192 not os.path.exists(os.path.join(source_dir, source_file))): | |
193 print ('*******************************************************\n' | |
194 'NOTICE: No new-style doc generated for %s.\n\n' | |
195 'Please add a custom mapping for %s to the |CUSTOM_MAPPINGS|' | |
196 ' section in:\n' | |
197 'chrome/common/extensions/docs/server2/converter.py\n' | |
198 '*******************************************************' % | |
199 (source_file, source_file)) | |
200 return | |
201 try: | |
202 static_data = _FormatFile(_ReadFile(os.path.join(source_dir, source_file)), | |
203 source_dir, | |
204 source_file, | |
205 image_dest, | |
206 replace, | |
207 is_api) | |
208 except IOError: | |
209 static_data = None | |
210 template_file = os.path.join(template_dest, processed_name + '.html') | |
211 if is_api: | |
212 template_data = _MakeAPITemplate(processed_name, | |
213 _GetAPIPath(unix_name, api_dir), | |
214 static_data is not None) | |
215 static_file = os.path.join(intros_dest, processed_name + '.html') | |
216 else: | |
217 template_data = _MakeArticleTemplate(unix_name) | |
218 static_file = os.path.join(articles_dest, processed_name + '.html') | |
219 if replace or not os.path.exists(template_file): | |
220 _WriteFile(template_file, template_data) | |
221 if static_data is not None and (replace or not os.path.exists(static_file)): | |
222 _WriteFile(static_file, static_data) | |
223 | |
224 if __name__ == '__main__': | |
225 parser = optparse.OptionParser( | |
226 description='Converts static files from the old documentation system to ' | |
227 'the new one. If run without -f, all the files in |src| will ' | |
228 'be converted.', | |
229 usage='usage: %prog [options] static_src_dir [-f static_src_file] ' | |
230 'api_dir articles_dest intros_dest template_dest image_dest') | |
231 parser.add_option('-f', | |
232 '--file', | |
233 action='store_true', | |
234 default=False, | |
235 help='convert single file') | |
236 parser.add_option('-e', | |
237 '--exclude', | |
238 default=None, | |
239 help='exclude files matching the names in this dir') | |
240 parser.add_option('-r', | |
241 '--replace', | |
242 action='store_true', | |
243 default=False, | |
244 help='replace existing files') | |
245 (opts, args) = parser.parse_args() | |
246 if (not opts.file and len(args) != 6) or (opts.file and len(args) != 7): | |
247 parser.error('incorrect number of arguments.') | |
248 | |
249 if opts.file: | |
250 _MoveSingleFile(*args, replace=opts.replace) | |
251 else: | |
252 _MoveAllFiles(*args, replace=opts.replace, exclude_dir=opts.exclude) | |
OLD | NEW |