summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorahernandez.miralles@gmail.com <ahernandez.miralles@gmail.com@0039d316-1c4b-4281-b951-d872f2087c98>2014-08-07 22:05:04 +0000
committerahernandez.miralles@gmail.com <ahernandez.miralles@gmail.com@0039d316-1c4b-4281-b951-d872f2087c98>2014-08-07 22:05:04 +0000
commit1d0e548a86cae4b6274fe9eef38b96a8e49aa12a (patch)
treeb27dc9eb3319da3c9eaa93bb30493b6a6f63aac5
parent177489835f6e988ff03548e579b50013785007e5 (diff)
downloadchromium_src-1d0e548a86cae4b6274fe9eef38b96a8e49aa12a.zip
chromium_src-1d0e548a86cae4b6274fe9eef38b96a8e49aa12a.tar.gz
chromium_src-1d0e548a86cae4b6274fe9eef38b96a8e49aa12a.tar.bz2
Docserver: Factor SamplesModel out of SamplesDataSource
BUG=275039 NOTRY=True Review URL: https://codereview.chromium.org/437323003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@288149 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/common/extensions/docs/server2/api_data_source.py25
-rw-r--r--chrome/common/extensions/docs/server2/app.yaml2
-rw-r--r--chrome/common/extensions/docs/server2/caching_file_system.py5
-rw-r--r--chrome/common/extensions/docs/server2/chained_compiled_file_system.py4
-rw-r--r--chrome/common/extensions/docs/server2/compiled_file_system.py5
-rw-r--r--chrome/common/extensions/docs/server2/cron.yaml2
-rw-r--r--chrome/common/extensions/docs/server2/data_source_registry.py2
-rw-r--r--chrome/common/extensions/docs/server2/platform_bundle.py30
-rw-r--r--chrome/common/extensions/docs/server2/samples_data_source.py289
-rwxr-xr-xchrome/common/extensions/docs/server2/samples_data_source_test.py34
-rw-r--r--chrome/common/extensions/docs/server2/samples_model.py170
-rwxr-xr-xchrome/common/extensions/docs/server2/samples_model_test.py44
-rw-r--r--chrome/common/extensions/docs/server2/server_instance.py21
-rw-r--r--chrome/common/extensions/docs/server2/template_renderer.py13
14 files changed, 322 insertions, 324 deletions
diff --git a/chrome/common/extensions/docs/server2/api_data_source.py b/chrome/common/extensions/docs/server2/api_data_source.py
index c57ecc5..56fae41 100644
--- a/chrome/common/extensions/docs/server2/api_data_source.py
+++ b/chrome/common/extensions/docs/server2/api_data_source.py
@@ -9,19 +9,7 @@ from file_system import FileNotFoundError
from future import Future, All
from jsc_view import JSCView, GetEventByNameFromEvents
from platform_util import GetPlatforms
-
-
-class _LazySamplesGetter(object):
- '''This class is needed so that an extensions API page does not have to fetch
- the apps samples page and vice versa.
- '''
-
- def __init__(self, api_name, samples):
- self._api_name = api_name
- self._samples = samples
-
- def get(self, key):
- return self._samples.FilterSamples(key, self._api_name)
+from samples_data_source import CreateSamplesView
class APIDataSource(DataSource):
@@ -43,7 +31,7 @@ class APIDataSource(DataSource):
# This caches the result of _LoadEventByName.
self._event_byname_futures = {}
- self._samples = server_instance.samples_data_source_factory.Create(request)
+ self._request = request
def _LoadEventByName(self, platform):
'''All events have some members in common. We source their description
@@ -84,9 +72,12 @@ class APIDataSource(DataSource):
# Parsing samples on the preview server takes seconds and doesn't add
# anything. Don't do it.
if not IsPreviewServer():
- jsc_view['samples'] = _LazySamplesGetter(
- jsc_view['name'],
- self._samples)
+ samples_model = self._platform_bundle.GetSamplesModel(platform)
+ # Creates an object that lazily gets samples.
+ jsc_view['samples'] = type('getter', (object,), {
+ 'get': lambda _, platform: CreateSamplesView(
+ samples_model.FilterSamples(jsc_view['name']), self._request)
+ })()
return jsc_view
return Future(callback=resolve)
diff --git a/chrome/common/extensions/docs/server2/app.yaml b/chrome/common/extensions/docs/server2/app.yaml
index b6ebba1..8398139 100644
--- a/chrome/common/extensions/docs/server2/app.yaml
+++ b/chrome/common/extensions/docs/server2/app.yaml
@@ -1,5 +1,5 @@
application: chrome-apps-doc
-version: 3-39-1
+version: 3-39-2
runtime: python27
api_version: 1
threadsafe: false
diff --git a/chrome/common/extensions/docs/server2/caching_file_system.py b/chrome/common/extensions/docs/server2/caching_file_system.py
index c0ff5d0..15a81a36 100644
--- a/chrome/common/extensions/docs/server2/caching_file_system.py
+++ b/chrome/common/extensions/docs/server2/caching_file_system.py
@@ -7,7 +7,7 @@ import sys
from file_system import FileSystem, StatInfo, FileNotFoundError
from future import Future
-from path_util import IsDirectory
+from path_util import IsDirectory, ToDirectory
from third_party.json_schema_compiler.memoize import memoize
@@ -43,8 +43,7 @@ class CachingFileSystem(FileSystem):
# Always stat the parent directory, since it will have the stat of the child
# anyway, and this gives us an entire directory's stat info at once.
dir_path, file_path = posixpath.split(path)
- if dir_path and not dir_path.endswith('/'):
- dir_path += '/'
+ dir_path = ToDirectory(dir_path)
def make_stat_info(dir_stat):
'''Converts a dir stat into the correct resulting StatInfo; if the Stat
diff --git a/chrome/common/extensions/docs/server2/chained_compiled_file_system.py b/chrome/common/extensions/docs/server2/chained_compiled_file_system.py
index 6fbd31e..74e2a59 100644
--- a/chrome/common/extensions/docs/server2/chained_compiled_file_system.py
+++ b/chrome/common/extensions/docs/server2/chained_compiled_file_system.py
@@ -6,6 +6,7 @@ from compiled_file_system import CompiledFileSystem
from docs_server_utils import StringIdentity
from file_system import FileNotFoundError
from future import Future
+from path_util import ToDirectory
class ChainedCompiledFileSystem(object):
@@ -52,8 +53,7 @@ class ChainedCompiledFileSystem(object):
lambda compiled_fs: compiled_fs.GetFileVersion(path))
def GetFromFileListing(self, path):
- if not path.endswith('/'):
- path += '/'
+ path = ToDirectory(path)
return self._GetImpl(
path,
lambda compiled_fs: compiled_fs.GetFromFileListing(path),
diff --git a/chrome/common/extensions/docs/server2/compiled_file_system.py b/chrome/common/extensions/docs/server2/compiled_file_system.py
index 2f7769e..58ab967 100644
--- a/chrome/common/extensions/docs/server2/compiled_file_system.py
+++ b/chrome/common/extensions/docs/server2/compiled_file_system.py
@@ -8,7 +8,7 @@ import schema_util
from docs_server_utils import ToUnicode
from file_system import FileNotFoundError
from future import Future
-from path_util import AssertIsDirectory, AssertIsFile
+from path_util import AssertIsDirectory, AssertIsFile, ToDirectory
from third_party.handlebar import Handlebar
from third_party.json_schema_compiler import json_parse
from third_party.json_schema_compiler.memoize import memoize
@@ -236,8 +236,7 @@ class CompiledFileSystem(object):
return self._file_system.Stat(path).version
def GetFileListingVersion(self, path):
- if not path.endswith('/'):
- path += '/'
+ path = ToDirectory(path)
cache_entry = self._list_object_store.Get(path).Get()
if cache_entry is not None:
return cache_entry.version
diff --git a/chrome/common/extensions/docs/server2/cron.yaml b/chrome/common/extensions/docs/server2/cron.yaml
index 4ac706a..f03c1d9 100644
--- a/chrome/common/extensions/docs/server2/cron.yaml
+++ b/chrome/common/extensions/docs/server2/cron.yaml
@@ -2,4 +2,4 @@ cron:
- description: Repopulates all cached data.
url: /_cron
schedule: every 5 minutes
- target: 3-39-1
+ target: 3-39-2
diff --git a/chrome/common/extensions/docs/server2/data_source_registry.py b/chrome/common/extensions/docs/server2/data_source_registry.py
index c864561..10d32df 100644
--- a/chrome/common/extensions/docs/server2/data_source_registry.py
+++ b/chrome/common/extensions/docs/server2/data_source_registry.py
@@ -7,6 +7,7 @@ from api_list_data_source import APIListDataSource
from data_source import DataSource
from manifest_data_source import ManifestDataSource
from permissions_data_source import PermissionsDataSource
+from samples_data_source import SamplesDataSource
from sidenav_data_source import SidenavDataSource
from strings_data_source import StringsDataSource
from template_data_source import (
@@ -22,6 +23,7 @@ _all_data_sources = {
'manifest_source': ManifestDataSource,
'partials': PartialDataSource,
'permissions': PermissionsDataSource,
+ 'samples': SamplesDataSource,
'sidenavs': SidenavDataSource,
'strings': StringsDataSource,
'whatsNew' : WhatsNewDataSource
diff --git a/chrome/common/extensions/docs/server2/platform_bundle.py b/chrome/common/extensions/docs/server2/platform_bundle.py
index 77c7263..2c40608 100644
--- a/chrome/common/extensions/docs/server2/platform_bundle.py
+++ b/chrome/common/extensions/docs/server2/platform_bundle.py
@@ -5,10 +5,13 @@
from api_categorizer import APICategorizer
from api_models import APIModels
from availability_finder import AvailabilityFinder
+from empty_dir_file_system import EmptyDirFileSystem
+from environment import IsDevServer
from features_bundle import FeaturesBundle
from future import All
from platform_util import GetPlatforms, PlatformToExtensionType
from reference_resolver import ReferenceResolver
+from samples_model import SamplesModel
class _PlatformData(object):
@@ -18,6 +21,7 @@ class _PlatformData(object):
self.reference_resolver = None
self.availability_finder = None
self.api_categorizer = None
+ self.samples_model = None
class PlatformBundle(object):
@@ -28,14 +32,38 @@ class PlatformBundle(object):
compiled_fs_factory,
host_fs_at_trunk,
host_file_system_iterator,
- object_store_creator):
+ object_store_creator,
+ base_path):
self._branch_utility = branch_utility
self._compiled_fs_factory = compiled_fs_factory
self._host_fs_at_trunk = host_fs_at_trunk
self._host_file_system_iterator = host_file_system_iterator
self._object_store_creator = object_store_creator
+ self._base_path = base_path
self._platform_data = dict((p, _PlatformData()) for p in GetPlatforms())
+ def GetSamplesModel(self, platform):
+ if self._platform_data[platform].samples_model is None:
+ # Note: samples are super slow in the dev server because it doesn't
+ # support async fetch, so disable them.
+ if IsDevServer():
+ extension_samples_fs = EmptyDirFileSystem()
+ app_samples_fs = EmptyDirFileSystem()
+ else:
+ extension_samples_fs = self._host_fs_at_trunk
+ # TODO(kalman): Re-enable the apps samples, see http://crbug.com/344097.
+ app_samples_fs = EmptyDirFileSystem()
+ #app_samples_fs = github_file_system_provider.Create(
+ # 'GoogleChrome', 'chrome-app-samples')
+ self._platform_data[platform].samples_model = SamplesModel(
+ extension_samples_fs,
+ app_samples_fs,
+ self._compiled_fs_factory,
+ self.GetReferenceResolver(platform),
+ self._base_path,
+ platform)
+ return self._platform_data[platform].samples_model
+
def GetFeaturesBundle(self, platform):
if self._platform_data[platform].features_bundle is None:
self._platform_data[platform].features_bundle = FeaturesBundle(
diff --git a/chrome/common/extensions/docs/server2/samples_data_source.py b/chrome/common/extensions/docs/server2/samples_data_source.py
index a692e97..eabcb28 100644
--- a/chrome/common/extensions/docs/server2/samples_data_source.py
+++ b/chrome/common/extensions/docs/server2/samples_data_source.py
@@ -2,252 +2,77 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import json
import logging
-import posixpath
-import re
import traceback
+from data_source import DataSource
from extensions_paths import EXAMPLES
-import third_party.json_schema_compiler.json_comment_eater as json_comment_eater
-import url_constants
+from future import All, Future
+from platform_util import GetPlatforms
-_DEFAULT_ICON_PATH = 'images/sample-default-icon.png'
+def _GetSampleId(sample_name):
+ return sample_name.lower().replace(' ', '-')
-class SamplesDataSource(object):
- '''Constructs a list of samples and their respective files and api calls.
- '''
- class Factory(object):
- '''A factory to create SamplesDataSource instances bound to individual
- Requests.
- '''
- def __init__(self,
- host_file_system,
- app_samples_file_system,
- compiled_fs_factory,
- platform_bundle,
- base_path):
- self._host_file_system = host_file_system
- self._app_samples_file_system = app_samples_file_system
- self._platform_bundle = platform_bundle
- self._base_path = base_path
- self._extensions_cache = compiled_fs_factory.Create(
- host_file_system,
- self._MakeSamplesList,
- SamplesDataSource,
- category='extensions')
- self._extensions_text_cache = compiled_fs_factory.ForUnicode(
- host_file_system)
- self._apps_cache = compiled_fs_factory.Create(
- app_samples_file_system,
- lambda *args: self._MakeSamplesList(*args, is_apps=True),
- SamplesDataSource,
- category='apps')
- self._apps_text_cache = compiled_fs_factory.ForUnicode(
- app_samples_file_system)
+def GetAcceptedLanguages(request):
+ if request is None:
+ return []
+ accept_language = request.headers.get('Accept-Language', None)
+ if accept_language is None:
+ return []
+ return [lang_with_q.split(';')[0].strip()
+ for lang_with_q in accept_language.split(',')]
- def Create(self, request):
- '''Returns a new SamplesDataSource bound to |request|.
- '''
- return SamplesDataSource(self._extensions_cache,
- self._apps_cache,
- self._base_path,
- request)
- def _GetAPIItems(self, js_file):
- chrome_pattern = r'chrome[\w.]+'
- # Add API calls that appear normally, like "chrome.runtime.connect".
- calls = set(re.findall(chrome_pattern, js_file))
- # Add API calls that have been assigned into variables, like
- # "var storageArea = chrome.storage.sync; storageArea.get", which should
- # be expanded like "chrome.storage.sync.get".
- for match in re.finditer(r'var\s+(\w+)\s*=\s*(%s);' % chrome_pattern,
- js_file):
- var_name, api_prefix = match.groups()
- for var_match in re.finditer(r'\b%s\.([\w.]+)\b' % re.escape(var_name),
- js_file):
- api_suffix, = var_match.groups()
- calls.add('%s.%s' % (api_prefix, api_suffix))
- return calls
-
- def _GetDataFromManifest(self, path, text_cache, file_system):
- manifest = text_cache.GetFromFile(path + '/manifest.json').Get()
+def CreateSamplesView(samples_list, request):
+ return_list = []
+ for dict_ in samples_list:
+ name = dict_['name']
+ description = dict_['description']
+ if description is None:
+ description = ''
+ if name.startswith('__MSG_') or description.startswith('__MSG_'):
try:
- manifest_json = json.loads(json_comment_eater.Nom(manifest))
- except ValueError as e:
- logging.error('Error parsing manifest.json for %s: %s' % (path, e))
- return None
- l10n_data = {
- 'name': manifest_json.get('name', ''),
- 'description': manifest_json.get('description', None),
- 'icon': manifest_json.get('icons', {}).get('128', None),
- 'default_locale': manifest_json.get('default_locale', None),
- 'locales': {}
- }
- if not l10n_data['default_locale']:
- return l10n_data
- locales_path = path + '/_locales/'
- locales_dir = file_system.ReadSingle(locales_path).Get()
- if locales_dir:
- def load_locale_json(path):
- return (path, json.loads(text_cache.GetFromFile(path).Get()))
-
- try:
- locales_json = [load_locale_json(locales_path + f + 'messages.json')
- for f in locales_dir]
- except ValueError as e:
- logging.error('Error parsing locales files for %s: %s' % (path, e))
- else:
- for path, json_ in locales_json:
- l10n_data['locales'][path[len(locales_path):].split('/')[0]] = json_
- return l10n_data
-
- def _MakeSamplesList(self, base_path, files, is_apps=False):
- file_system = (self._app_samples_file_system if is_apps else
- self._host_file_system)
- text_cache = (self._apps_text_cache if is_apps else
- self._extensions_text_cache)
- samples_list = []
- for filename in sorted(files):
- if filename.rsplit('/')[-1] != 'manifest.json':
- continue
-
- # This is a little hacky, but it makes a sample page.
- sample_path = filename.rsplit('/', 1)[-2]
- sample_files = [path for path in files
- if path.startswith(sample_path + '/')]
- js_files = [path for path in sample_files if path.endswith('.js')]
- js_contents = [text_cache.GetFromFile(
- posixpath.join(base_path, js_file)).Get()
- for js_file in js_files]
- api_items = set()
- for js in js_contents:
- api_items.update(self._GetAPIItems(js))
-
- api_calls = []
- for item in sorted(api_items):
- if len(item.split('.')) < 3:
- continue
- if item.endswith('.removeListener') or item.endswith('.hasListener'):
- continue
- if item.endswith('.addListener'):
- item = item[:-len('.addListener')]
- if item.startswith('chrome.'):
- item = item[len('chrome.'):]
- ref_data = self._platform_bundle.GetReferenceResolver(
- 'apps' if is_apps else 'extensions').GetLink(item)
- # TODO(kalman): What about references like chrome.storage.sync.get?
- # That should link to either chrome.storage.sync or
- # chrome.storage.StorageArea.get (or probably both).
- # TODO(kalman): Filter out API-only references? This can happen when
- # the API namespace is assigned to a variable, but it's very hard to
- # to disambiguate.
- if ref_data is None:
- continue
- api_calls.append({
- 'name': ref_data['text'],
- 'link': ref_data['href']
- })
-
- if is_apps:
- url = url_constants.GITHUB_BASE + '/' + sample_path
- icon_base = url_constants.RAW_GITHUB_BASE + '/' + sample_path
- download_url = url
- else:
- extension_sample_path = posixpath.join('examples', sample_path)
- url = extension_sample_path
- icon_base = extension_sample_path
- download_url = extension_sample_path + '.zip'
-
- manifest_data = self._GetDataFromManifest(
- posixpath.join(base_path, sample_path),
- text_cache,
- file_system)
- if manifest_data['icon'] is None:
- icon_path = posixpath.join(
- self._base_path, 'static', _DEFAULT_ICON_PATH)
- else:
- icon_path = '%s/%s' % (icon_base, manifest_data['icon'])
- manifest_data.update({
- 'icon': icon_path,
- 'download_url': download_url,
- 'url': url,
- 'files': [f.replace(sample_path + '/', '') for f in sample_files],
- 'api_calls': api_calls
- })
- samples_list.append(manifest_data)
+ # Copy the sample dict so we don't change the dict in the cache.
+ sample_data = dict_.copy()
+ name_key = name[len('__MSG_'):-len('__')]
+ description_key = description[len('__MSG_'):-len('__')]
+ locale = sample_data['default_locale']
+ for lang in GetAcceptedLanguages(request):
+ if lang in sample_data['locales']:
+ locale = lang
+ break
+ locale_data = sample_data['locales'][locale]
+ sample_data['name'] = locale_data[name_key]['message']
+ sample_data['description'] = locale_data[description_key]['message']
+ sample_data['id'] = _GetSampleId(sample_data['name'])
+ except Exception:
+ logging.error(traceback.format_exc())
+ # Revert the sample to the original dict.
+ sample_data = dict_
+ return_list.append(sample_data)
+ else:
+ dict_['id'] = _GetSampleId(name)
+ return_list.append(dict_)
+ return return_list
- return samples_list
- def __init__(self,
- extensions_cache,
- apps_cache,
- base_path,
- request):
- self._extensions_cache = extensions_cache
- self._apps_cache = apps_cache
- self._base_path = base_path
+class SamplesDataSource(DataSource):
+ '''Constructs a list of samples and their respective files and api calls.
+ '''
+ def __init__(self, server_instance, request):
+ self._platform_bundle = server_instance.platform_bundle
self._request = request
- def _GetSampleId(self, sample_name):
- return sample_name.lower().replace(' ', '-')
+ def _GetImpl(self, platform):
+ cache = self._platform_bundle.GetSamplesModel(platform).GetCache()
+ create_view = lambda samp_list: CreateSamplesView(samp_list, self._request)
+ return cache.GetFromFileListing('' if platform == 'apps'
+ else EXAMPLES).Then(create_view)
- def _GetAcceptedLanguages(self):
- accept_language = self._request.headers.get('Accept-Language', None)
- if accept_language is None:
- return []
- return [lang_with_q.split(';')[0].strip()
- for lang_with_q in accept_language.split(',')]
-
- def FilterSamples(self, key, api_name):
- '''Fetches and filters the list of samples specified by |key|, returning
- only the samples that use the API |api_name|. |key| is either 'apps' or
- 'extensions'.
- '''
- return [sample for sample in self.get(key) if any(
- call['name'].startswith(api_name + '.')
- for call in sample['api_calls'])]
-
- def _CreateSamplesDict(self, key):
- if key == 'apps':
- samples_list = self._apps_cache.GetFromFileListing('').Get()
- else:
- samples_list = self._extensions_cache.GetFromFileListing(EXAMPLES).Get()
- return_list = []
- for dict_ in samples_list:
- name = dict_['name']
- description = dict_['description']
- if description is None:
- description = ''
- if name.startswith('__MSG_') or description.startswith('__MSG_'):
- try:
- # Copy the sample dict so we don't change the dict in the cache.
- sample_data = dict_.copy()
- name_key = name[len('__MSG_'):-len('__')]
- description_key = description[len('__MSG_'):-len('__')]
- locale = sample_data['default_locale']
- for lang in self._GetAcceptedLanguages():
- if lang in sample_data['locales']:
- locale = lang
- break
- locale_data = sample_data['locales'][locale]
- sample_data['name'] = locale_data[name_key]['message']
- sample_data['description'] = locale_data[description_key]['message']
- sample_data['id'] = self._GetSampleId(sample_data['name'])
- except Exception as e:
- logging.error(traceback.format_exc())
- # Revert the sample to the original dict.
- sample_data = dict_
- return_list.append(sample_data)
- else:
- dict_['id'] = self._GetSampleId(name)
- return_list.append(dict_)
- return return_list
+ def get(self, platform):
+ return self._GetImpl(platform).Get()
- def get(self, key):
- return {
- 'apps': lambda: self._CreateSamplesDict('apps'),
- 'extensions': lambda: self._CreateSamplesDict('extensions')
- }.get(key, lambda: {})()
+ def Cron(self):
+ return All([self._GetImpl(platform) for platform in GetPlatforms()])
diff --git a/chrome/common/extensions/docs/server2/samples_data_source_test.py b/chrome/common/extensions/docs/server2/samples_data_source_test.py
deleted file mode 100755
index b798469..0000000
--- a/chrome/common/extensions/docs/server2/samples_data_source_test.py
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/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.
-
-import json
-import os
-import sys
-import unittest
-
-from samples_data_source import SamplesDataSource
-from servlet import Request
-from test_util import Server2Path
-
-
-class SamplesDataSourceTest(unittest.TestCase):
- def setUp(self):
- self._base_path = Server2Path('test_data', 'samples_data_source')
-
- def _ReadLocalFile(self, filename):
- with open(os.path.join(self._base_path, filename), 'r') as f:
- return f.read()
-
- def _FakeGet(self, key):
- return json.loads(self._ReadLocalFile(key))
-
- def testFilterSamples(self):
- sds = SamplesDataSource({}, {}, '.', Request.ForTest('/'))
- sds.get = self._FakeGet
- self.assertEquals(json.loads(self._ReadLocalFile('expected.json')),
- sds.FilterSamples('samples.json', 'bobaloo'))
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/chrome/common/extensions/docs/server2/samples_model.py b/chrome/common/extensions/docs/server2/samples_model.py
new file mode 100644
index 0000000..fa66dc3
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/samples_model.py
@@ -0,0 +1,170 @@
+# Copyright 2014 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.
+
+import json
+import logging
+import posixpath
+import re
+
+from extensions_paths import EXAMPLES
+from samples_data_source import SamplesDataSource
+import third_party.json_schema_compiler.json_comment_eater as json_comment_eater
+import url_constants
+
+
+_DEFAULT_ICON_PATH = 'images/sample-default-icon.png'
+
+
+def _GetAPIItems(js_file):
+ chrome_pattern = r'chrome[\w.]+'
+ # Add API calls that appear normally, like "chrome.runtime.connect".
+ calls = set(re.findall(chrome_pattern, js_file))
+ # Add API calls that have been assigned into variables, like
+ # "var storageArea = chrome.storage.sync; storageArea.get", which should
+ # be expanded like "chrome.storage.sync.get".
+ for match in re.finditer(r'var\s+(\w+)\s*=\s*(%s);' % chrome_pattern,
+ js_file):
+ var_name, api_prefix = match.groups()
+ for var_match in re.finditer(r'\b%s\.([\w.]+)\b' % re.escape(var_name),
+ js_file):
+ api_suffix, = var_match.groups()
+ calls.add('%s.%s' % (api_prefix, api_suffix))
+ return calls
+
+
+class SamplesModel(object):
+ def __init__(self,
+ extension_samples_file_system,
+ app_samples_file_system,
+ compiled_fs_factory,
+ reference_resolver,
+ base_path,
+ platform):
+ self._samples_fs = (extension_samples_file_system if
+ platform == 'extensions' else app_samples_file_system)
+ self._samples_cache = compiled_fs_factory.Create(
+ self._samples_fs,
+ self._MakeSamplesList,
+ SamplesDataSource,
+ category=platform)
+ self._text_cache = compiled_fs_factory.ForUnicode(self._samples_fs)
+ self._reference_resolver = reference_resolver
+ self._base_path = base_path
+ self._platform = platform
+
+ def GetCache(self):
+ return self._samples_cache
+
+ def FilterSamples(self, api_name):
+ '''Fetches and filters the list of samples for this platform, returning
+ only the samples that use the API |api_name|.
+ '''
+ samples_list = self._samples_cache.GetFromFileListing(
+ '' if self._platform == 'apps' else EXAMPLES).Get()
+ return [sample for sample in samples_list if any(
+ call['name'].startswith(api_name + '.')
+ for call in sample['api_calls'])]
+
+ def _GetDataFromManifest(self, path, file_system):
+ manifest = self._text_cache.GetFromFile(path + '/manifest.json').Get()
+ try:
+ manifest_json = json.loads(json_comment_eater.Nom(manifest))
+ except ValueError as e:
+ logging.error('Error parsing manifest.json for %s: %s' % (path, e))
+ return None
+ l10n_data = {
+ 'name': manifest_json.get('name', ''),
+ 'description': manifest_json.get('description', None),
+ 'icon': manifest_json.get('icons', {}).get('128', None),
+ 'default_locale': manifest_json.get('default_locale', None),
+ 'locales': {}
+ }
+ if not l10n_data['default_locale']:
+ return l10n_data
+ locales_path = path + '/_locales/'
+ locales_dir = file_system.ReadSingle(locales_path).Get()
+ if locales_dir:
+ def load_locale_json(path):
+ return (path, json.loads(self._text_cache.GetFromFile(path).Get()))
+
+ try:
+ locales_json = [load_locale_json(locales_path + f + 'messages.json')
+ for f in locales_dir]
+ except ValueError as e:
+ logging.error('Error parsing locales files for %s: %s' % (path, e))
+ else:
+ for path, json_ in locales_json:
+ l10n_data['locales'][path[len(locales_path):].split('/')[0]] = json_
+ return l10n_data
+
+ def _MakeSamplesList(self, base_path, files):
+ samples_list = []
+ for filename in sorted(files):
+ if filename.rsplit('/')[-1] != 'manifest.json':
+ continue
+
+ # This is a little hacky, but it makes a sample page.
+ sample_path = filename.rsplit('/', 1)[-2]
+ sample_files = [path for path in files
+ if path.startswith(sample_path + '/')]
+ js_files = [path for path in sample_files if path.endswith('.js')]
+ js_contents = [self._text_cache.GetFromFile(
+ posixpath.join(base_path, js_file)).Get()
+ for js_file in js_files]
+ api_items = set()
+ for js in js_contents:
+ api_items.update(_GetAPIItems(js))
+
+ api_calls = []
+ for item in sorted(api_items):
+ if len(item.split('.')) < 3:
+ continue
+ if item.endswith('.removeListener') or item.endswith('.hasListener'):
+ continue
+ if item.endswith('.addListener'):
+ item = item[:-len('.addListener')]
+ if item.startswith('chrome.'):
+ item = item[len('chrome.'):]
+ ref_data = self._reference_resolver.GetLink(item)
+ # TODO(kalman): What about references like chrome.storage.sync.get?
+ # That should link to either chrome.storage.sync or
+ # chrome.storage.StorageArea.get (or probably both).
+ # TODO(kalman): Filter out API-only references? This can happen when
+ # the API namespace is assigned to a variable, but it's very hard to
+ # to disambiguate.
+ if ref_data is None:
+ continue
+ api_calls.append({
+ 'name': ref_data['text'],
+ 'link': ref_data['href']
+ })
+
+ if self._platform == 'apps':
+ url = url_constants.GITHUB_BASE + '/' + sample_path
+ icon_base = url_constants.RAW_GITHUB_BASE + '/' + sample_path
+ download_url = url
+ else:
+ extension_sample_path = posixpath.join('examples', sample_path)
+ url = extension_sample_path
+ icon_base = extension_sample_path
+ download_url = extension_sample_path + '.zip'
+
+ manifest_data = self._GetDataFromManifest(
+ posixpath.join(base_path, sample_path),
+ self._samples_fs)
+ if manifest_data['icon'] is None:
+ icon_path = posixpath.join(
+ self._base_path, 'static', _DEFAULT_ICON_PATH)
+ else:
+ icon_path = '%s/%s' % (icon_base, manifest_data['icon'])
+ manifest_data.update({
+ 'icon': icon_path,
+ 'download_url': download_url,
+ 'url': url,
+ 'files': [f.replace(sample_path + '/', '') for f in sample_files],
+ 'api_calls': api_calls
+ })
+ samples_list.append(manifest_data)
+
+ return samples_list
diff --git a/chrome/common/extensions/docs/server2/samples_model_test.py b/chrome/common/extensions/docs/server2/samples_model_test.py
new file mode 100755
index 0000000..c12f504
--- /dev/null
+++ b/chrome/common/extensions/docs/server2/samples_model_test.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+import json
+import os
+import unittest
+
+from server_instance import ServerInstance
+from test_file_system import TestFileSystem
+from test_util import Server2Path
+
+
+def _ReadLocalFile(filename):
+ base_path = Server2Path('test_data', 'samples_data_source')
+ with open(os.path.join(base_path, filename), 'r') as f:
+ return f.read()
+
+
+class _FakeCache(object):
+ def __init__(self, obj):
+ self._cache = obj
+
+ def GetFromFileListing(self, _):
+ getter = lambda: 0
+ getter.Get = lambda: self._cache
+ return getter
+
+
+class SamplesModelSourceTest(unittest.TestCase):
+ def setUp(self):
+ server_instance = ServerInstance.ForTest(file_system=TestFileSystem({}))
+ self._samples_model = server_instance.platform_bundle.GetSamplesModel(
+ 'apps')
+ self._samples_model._samples_cache = _FakeCache(json.loads(_ReadLocalFile(
+ 'samples.json')))
+
+ def testFilterSamples(self):
+ self.assertEquals(json.loads(_ReadLocalFile('expected.json')),
+ self._samples_model.FilterSamples('bobaloo'))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/chrome/common/extensions/docs/server2/server_instance.py b/chrome/common/extensions/docs/server2/server_instance.py
index 6d3deee..a790821 100644
--- a/chrome/common/extensions/docs/server2/server_instance.py
+++ b/chrome/common/extensions/docs/server2/server_instance.py
@@ -73,25 +73,8 @@ class ServerInstance(object):
self.compiled_fs_factory,
host_fs_at_trunk,
self.host_file_system_iterator,
- self.object_store_creator)
-
- # Note: samples are super slow in the dev server because it doesn't support
- # async fetch, so disable them.
- if IsDevServer():
- extension_samples_fs = EmptyDirFileSystem()
- app_samples_fs = EmptyDirFileSystem()
- else:
- extension_samples_fs = host_fs_at_trunk
- # TODO(kalman): Re-enable the apps samples, see http://crbug.com/344097.
- app_samples_fs = EmptyDirFileSystem()
- #app_samples_fs = github_file_system_provider.Create(
- # 'GoogleChrome', 'chrome-app-samples')
- self.samples_data_source_factory = SamplesDataSource.Factory(
- extension_samples_fs,
- app_samples_fs,
- CompiledFileSystem.Factory(object_store_creator),
- self.platform_bundle,
- base_path)
+ self.object_store_creator,
+ self.base_path)
self.content_providers = ContentProviders(
object_store_creator,
diff --git a/chrome/common/extensions/docs/server2/template_renderer.py b/chrome/common/extensions/docs/server2/template_renderer.py
index ccab9f9..6b7e7c4 100644
--- a/chrome/common/extensions/docs/server2/template_renderer.py
+++ b/chrome/common/extensions/docs/server2/template_renderer.py
@@ -28,7 +28,7 @@ class TemplateRenderer(object):
rendering the template.
'''
assert isinstance(template, Handlebar), type(template)
- render_context = self._CreateDataSources(request)
+ render_context = CreateDataSources(self._server_instance, request)
if data_sources is not None:
render_context = dict((name, d) for name, d in render_context.iteritems()
if name in data_sources)
@@ -38,15 +38,6 @@ class TemplateRenderer(object):
'extensions_samples_url': EXTENSIONS_SAMPLES,
'static': self._server_instance.base_path + 'static',
})
- if additional_context:
- render_context.update(additional_context)
+ render_context.update(additional_context or {})
render_data = template.Render(render_context)
return render_data.text, render_data.errors
-
- def _CreateDataSources(self, request):
- server_instance = self._server_instance
- data_sources = CreateDataSources(server_instance, request=request)
- data_sources.update({
- 'samples': server_instance.samples_data_source_factory.Create(request),
- })
- return data_sources