diff --git a/dddp/server/.views.py.swp b/dddp/server/.views.py.swp new file mode 100644 index 0000000..934a421 Binary files /dev/null and b/dddp/server/.views.py.swp differ diff --git a/dddp/server/__init__.py b/dddp/server/__init__.py new file mode 100644 index 0000000..cea26b4 --- /dev/null +++ b/dddp/server/__init__.py @@ -0,0 +1 @@ +default_app_config = 'dddp.server.apps.ServerConfig' diff --git a/dddp/server/apps.py b/dddp/server/apps.py new file mode 100644 index 0000000..6a1b915 --- /dev/null +++ b/dddp/server/apps.py @@ -0,0 +1,190 @@ +"""Django DDP Server app config.""" +from __future__ import print_function, absolute_import, unicode_literals + +import io +import os.path + +from django.apps import AppConfig +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from ejson import dumps, loads +import pybars + + +STAR_JSON_SETTING_NAME = 'METEOR_STAR_JSON' + + +def read(path, default=None, encoding='utf8'): + """Read encoded contents from specified path or return default.""" + if not path: + return default + try: + with io.open(path, mode='r', encoding=encoding) as contents: + return contents.read() + except IOError: + if default is not None: + return default + raise + + +def read_json(path): + """Read JSON encoded contents from specified path.""" + with open(path, mode='r') as json_file: + return loads(json_file.read()) + + +class ServerConfig(AppConfig): + + """Django config for dddp.server app.""" + + name = 'dddp.server' + verbose_name = 'Django DDP Meteor Web Server' + + manifest = None + program_json = None + program_json_path = None + runtime_config = None + + star_json = None # top level layout + server_json = None # web server layout + web_browser_json = None # web.browser (client) layout + + url_map = None + internal_map = None + server_load_map = None + template_path = None # web server HTML template path + client_map = None # web.browser (client) URL to path map + html = '\nDDP App' + + def ready(self): + """Configure Django DDP server app.""" + self.url_map = {} + + try: + json_path = getattr(settings, STAR_JSON_SETTING_NAME) + except AttributeError: + raise ImproperlyConfigured( + '%s setting required by dddp.server.view.' % ( + STAR_JSON_SETTING_NAME, + ), + ) + + self.star_json = read_json(json_path) + star_format = self.star_json['format'] + if star_format != 'site-archive-pre1': + raise ValueError( + 'Unknown Meteor star format: %r' % star_format, + ) + programs = { + program['name']: program + for program in self.star_json['programs'] + } + + server_json_path = os.path.join( + os.path.dirname(json_path), + os.path.dirname(programs['server']['path']), + 'program.json', + ) + self.server_json = read_json(server_json_path) + server_format = self.server_json['format'] + if server_format != 'javascript-image-pre1': + raise ValueError( + 'Unknown Meteor server format: %r' % server_format, + ) + self.server_load_map = {} + for item in self.server_json['load']: + item['path_full'] = os.path.join( + os.path.dirname(server_json_path), + item['path'], + ) + self.server_load_map[item['path']] = item + self.url_map[item['path']] = ( + item['path_full'], 'text/javascript' + ) + try: + item['source_map_full'] = os.path.join( + os.path.dirname(server_json_path), + item['sourceMap'], + ) + self.url_map[item['sourceMap']] = ( + item['source_map_full'], 'text/plain' + ) + except KeyError: + pass + self.template_path = os.path.join( + os.path.dirname(server_json_path), + self.server_load_map[ + 'packages/boilerplate-generator.js' + ][ + 'assets' + ][ + 'boilerplate_web.browser.html' + ], + ) + + web_browser_json_path = os.path.join( + os.path.dirname(json_path), + programs['web.browser']['path'], + ) + self.web_browser_json = read_json(web_browser_json_path) + web_browser_format = self.web_browser_json['format'] + if web_browser_format != 'web-program-pre1': + raise ValueError( + 'Unknown Meteor web.browser format: %r' % ( + web_browser_format, + ), + ) + self.client_map = {} + self.internal_map = {} + for item in self.web_browser_json['manifest']: + item['path_full'] = os.path.join( + os.path.dirname(web_browser_json_path), + item['path'], + ) + if item['where'] == 'client': + if '?' in item['url']: + item['url'] = item['url'].split('?', 1)[0] + self.client_map[item['url']] = item + self.url_map[item['url']] = ( + item['path_full'], + { + 'js': 'text/javascript', + 'css': 'text/css', + }.get( + item['type'], 'application/octet-stream', + ), + ) + elif item['where'] == 'internal': + self.internal_map[item['type']] = item + + config = { + 'css': [ + {'url': item['path']} + for item in self.web_browser_json['manifest'] + if item['type'] == 'css' and item['where'] == 'client' + ], + 'js': [ + {'url': item['path']} + for item in self.web_browser_json['manifest'] + if item['type'] == 'js' and item['where'] == 'client' + ], + 'meteorRuntimeConfig': '"%s"' % ( + dumps(self.runtime_config) + ), + 'rootUrlPathPrefix': '/app', + 'bundledJsCssPrefix': '/app/', + 'inlineScriptsAllowed': False, + 'inline': None, + 'head': read( + self.internal_map.get('head', {}).get('path_full', None), + default=u'', + ), + 'body': read( + self.internal_map.get('body', {}).get('path_full', None), + default=u'', + ), + } + tmpl_raw = read(self.template_path, encoding='utf8') + compiler = pybars.Compiler() + tmpl = compiler.compile(tmpl_raw) + self.html = '\n%s' % tmpl(config) diff --git a/dddp/server/views.py b/dddp/server/views.py new file mode 100644 index 0000000..c30fd30 --- /dev/null +++ b/dddp/server/views.py @@ -0,0 +1,49 @@ +"""Django DDP Server views.""" +from __future__ import print_function, absolute_import +from ejson import dumps +from django.apps import apps +from django.http import HttpResponse +from django.views.generic import View + + +STAR_JSON_SETTING_NAME = 'METEOR_STAR_JSON' + + +class MeteorView(View): + + """Django DDP Meteor server view.""" + + http_method_names = ['get', 'head'] + + app = None + runtime_config = None + + def __init__(self, **kwargs): + """Initialisation for Django DDP server view.""" + super(MeteorView, self).__init__(**kwargs) + self.app = apps.get_app_config('server') + + def get(self, request, path): + """Return HTML (or other related content) for Meteor.""" + if path == '/meteor_runtime_config.js': + config = { + 'DDP_DEFAULT_CONNECTION_URL': request.build_absolute_uri('/'), + 'ROOT_URL': request.build_absolute_uri('/'), + 'ROOT_URL_PATH_PREFIX': '', + } + config.update(self.runtime_config) + return HttpResponse( + '__meteor_runtime_config__ = %s;' % dumps(config), + content_type='text/javascript', + ) + try: + file_path, content_type = self.app.url_map[path] + with open(file_path, 'r') as content: + return HttpResponse( + content.read(), + content_type=content_type, + ) + except KeyError: + print(path) + return HttpResponse(self.app.html) + #raise Http404