1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
11 from extensions_paths
import EXAMPLES
12 import third_party
.json_schema_compiler
.json_comment_eater
as json_comment_eater
16 _DEFAULT_ICON_PATH
= 'images/sample-default-icon.png'
19 class SamplesDataSource(object):
20 '''Constructs a list of samples and their respective files and api calls.
22 class Factory(object):
23 '''A factory to create SamplesDataSource instances bound to individual
28 app_samples_file_system
,
32 self
._host
_file
_system
= host_file_system
33 self
._app
_samples
_file
_system
= app_samples_file_system
34 self
._ref
_resolver
= ref_resolver
35 self
._base
_path
= base_path
36 self
._extensions
_cache
= compiled_fs_factory
.Create(
38 self
._MakeSamplesList
,
40 category
='extensions')
41 self
._extensions
_text
_cache
= compiled_fs_factory
.ForUnicode(
43 self
._apps
_cache
= compiled_fs_factory
.Create(
44 app_samples_file_system
,
45 lambda *args
: self
._MakeSamplesList
(*args
, is_apps
=True),
48 self
._apps
_text
_cache
= compiled_fs_factory
.ForUnicode(
49 app_samples_file_system
)
51 def Create(self
, request
):
52 '''Returns a new SamplesDataSource bound to |request|.
54 return SamplesDataSource(self
._extensions
_cache
,
59 def _GetAPIItems(self
, js_file
):
60 chrome_pattern
= r
'chrome[\w.]+'
61 # Add API calls that appear normally, like "chrome.runtime.connect".
62 calls
= set(re
.findall(chrome_pattern
, js_file
))
63 # Add API calls that have been assigned into variables, like
64 # "var storageArea = chrome.storage.sync; storageArea.get", which should
65 # be expanded like "chrome.storage.sync.get".
66 for match
in re
.finditer(r
'var\s+(\w+)\s*=\s*(%s);' % chrome_pattern
,
68 var_name
, api_prefix
= match
.groups()
69 for var_match
in re
.finditer(r
'\b%s\.([\w.]+)\b' % re
.escape(var_name
),
71 api_suffix
, = var_match
.groups()
72 calls
.add('%s.%s' % (api_prefix
, api_suffix
))
75 def _GetDataFromManifest(self
, path
, text_cache
, file_system
):
76 manifest
= text_cache
.GetFromFile(path
+ '/manifest.json').Get()
78 manifest_json
= json
.loads(json_comment_eater
.Nom(manifest
))
79 except ValueError as e
:
80 logging
.error('Error parsing manifest.json for %s: %s' % (path
, e
))
83 'name': manifest_json
.get('name', ''),
84 'description': manifest_json
.get('description', None),
85 'icon': manifest_json
.get('icons', {}).get('128', None),
86 'default_locale': manifest_json
.get('default_locale', None),
89 if not l10n_data
['default_locale']:
91 locales_path
= path
+ '/_locales/'
92 locales_dir
= file_system
.ReadSingle(locales_path
).Get()
94 def load_locale_json(path
):
95 return (path
, json
.loads(text_cache
.GetFromFile(path
).Get()))
98 locales_json
= [load_locale_json(locales_path
+ f
+ 'messages.json')
100 except ValueError as e
:
101 logging
.error('Error parsing locales files for %s: %s' % (path
, e
))
103 for path
, json_
in locales_json
:
104 l10n_data
['locales'][path
[len(locales_path
):].split('/')[0]] = json_
107 def _MakeSamplesList(self
, base_path
, files
, is_apps
=False):
108 file_system
= (self
._app
_samples
_file
_system
if is_apps
else
109 self
._host
_file
_system
)
110 text_cache
= (self
._apps
_text
_cache
if is_apps
else
111 self
._extensions
_text
_cache
)
113 for filename
in sorted(files
):
114 if filename
.rsplit('/')[-1] != 'manifest.json':
117 # This is a little hacky, but it makes a sample page.
118 sample_path
= filename
.rsplit('/', 1)[-2]
119 sample_files
= [path
for path
in files
120 if path
.startswith(sample_path
+ '/')]
121 js_files
= [path
for path
in sample_files
if path
.endswith('.js')]
122 js_contents
= [text_cache
.GetFromFile(
123 posixpath
.join(base_path
, js_file
)).Get()
124 for js_file
in js_files
]
126 for js
in js_contents
:
127 api_items
.update(self
._GetAPIItems
(js
))
130 for item
in sorted(api_items
):
131 if len(item
.split('.')) < 3:
133 if item
.endswith('.removeListener') or item
.endswith('.hasListener'):
135 if item
.endswith('.addListener'):
136 item
= item
[:-len('.addListener')]
137 if item
.startswith('chrome.'):
138 item
= item
[len('chrome.'):]
139 ref_data
= self
._ref
_resolver
.GetLink(item
)
140 # TODO(kalman): What about references like chrome.storage.sync.get?
141 # That should link to either chrome.storage.sync or
142 # chrome.storage.StorageArea.get (or probably both).
143 # TODO(kalman): Filter out API-only references? This can happen when
144 # the API namespace is assigned to a variable, but it's very hard to
149 'name': ref_data
['text'],
150 'link': ref_data
['href']
154 url
= url_constants
.GITHUB_BASE
+ '/' + sample_path
155 icon_base
= url_constants
.RAW_GITHUB_BASE
+ '/' + sample_path
158 extension_sample_path
= posixpath
.join('examples', sample_path
)
159 url
= extension_sample_path
160 icon_base
= extension_sample_path
161 download_url
= extension_sample_path
+ '.zip'
163 manifest_data
= self
._GetDataFromManifest
(
164 posixpath
.join(base_path
, sample_path
),
167 if manifest_data
['icon'] is None:
168 icon_path
= posixpath
.join(
169 self
._base
_path
, 'static', _DEFAULT_ICON_PATH
)
171 icon_path
= '%s/%s' % (icon_base
, manifest_data
['icon'])
172 manifest_data
.update({
174 'download_url': download_url
,
176 'files': [f
.replace(sample_path
+ '/', '') for f
in sample_files
],
177 'api_calls': api_calls
179 samples_list
.append(manifest_data
)
188 self
._extensions
_cache
= extensions_cache
189 self
._apps
_cache
= apps_cache
190 self
._base
_path
= base_path
191 self
._request
= request
193 def _GetSampleId(self
, sample_name
):
194 return sample_name
.lower().replace(' ', '-')
196 def _GetAcceptedLanguages(self
):
197 accept_language
= self
._request
.headers
.get('Accept-Language', None)
198 if accept_language
is None:
200 return [lang_with_q
.split(';')[0].strip()
201 for lang_with_q
in accept_language
.split(',')]
203 def FilterSamples(self
, key
, api_name
):
204 '''Fetches and filters the list of samples specified by |key|, returning
205 only the samples that use the API |api_name|. |key| is either 'apps' or
208 return [sample
for sample
in self
.get(key
) if any(
209 call
['name'].startswith(api_name
+ '.')
210 for call
in sample
['api_calls'])]
212 def _CreateSamplesDict(self
, key
):
214 samples_list
= self
._apps
_cache
.GetFromFileListing('').Get()
216 samples_list
= self
._extensions
_cache
.GetFromFileListing(EXAMPLES
).Get()
218 for dict_
in samples_list
:
220 description
= dict_
['description']
221 if description
is None:
223 if name
.startswith('__MSG_') or description
.startswith('__MSG_'):
225 # Copy the sample dict so we don't change the dict in the cache.
226 sample_data
= dict_
.copy()
227 name_key
= name
[len('__MSG_'):-len('__')]
228 description_key
= description
[len('__MSG_'):-len('__')]
229 locale
= sample_data
['default_locale']
230 for lang
in self
._GetAcceptedLanguages
():
231 if lang
in sample_data
['locales']:
234 locale_data
= sample_data
['locales'][locale
]
235 sample_data
['name'] = locale_data
[name_key
]['message']
236 sample_data
['description'] = locale_data
[description_key
]['message']
237 sample_data
['id'] = self
._GetSampleId
(sample_data
['name'])
238 except Exception as e
:
239 logging
.error(traceback
.format_exc())
240 # Revert the sample to the original dict.
242 return_list
.append(sample_data
)
244 dict_
['id'] = self
._GetSampleId
(name
)
245 return_list
.append(dict_
)
250 'apps': lambda: self
._CreateSamplesDict
('apps'),
251 'extensions': lambda: self
._CreateSamplesDict
('extensions')
252 }.get(key
, lambda: {})()