1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-25 16:22:43 +01:00

Celerity, a Haste-style static resource management system.

This commit is contained in:
epriestley 2011-01-25 09:59:31 -08:00
parent fed4c583c5
commit 7bb0db1365
23 changed files with 531 additions and 9 deletions

View file

@ -0,0 +1,32 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
$include_path = ini_get('include_path');
ini_set('include_path', $include_path.':'.dirname(__FILE__).'/../../');
@require_once 'libphutil/src/__phutil_library_init__.php';
if (!@constant('__LIBPHUTIL__')) {
echo "ERROR: Unable to load libphutil. Update your PHP 'include_path' to ".
"include the parent directory of libphutil/.\n";
exit(1);
}
if (!ini_get('date.timezone')) {
date_default_timezone_set('America/Los_Angeles');
}
phutil_load_library(dirname(__FILE__).'/../src/');

102
scripts/celerity_mapper.php Executable file
View file

@ -0,0 +1,102 @@
#!/usr/bin/env php
<?php
require_once dirname(__FILE__).'/__init_script__.php';
if ($argc != 2) {
$self = basename($argv[0]);
echo "usage: {$self} <webroot>\n";
exit(1);
}
phutil_require_module('phutil', 'filesystem');
phutil_require_module('phutil', 'filesystem/filefinder');
phutil_require_module('phutil', 'future/exec');
phutil_require_module('phutil', 'parser/docblock');
$root = Filesystem::resolvePath($argv[1]);
echo "Finding static resources...\n";
$files = id(new FileFinder($root))
->withType('f')
->withSuffix('js')
->withSuffix('css')
->setGenerateChecksums(true)
->find();
echo "Processing ".count($files)." files";
$file_map = array();
foreach ($files as $path => $hash) {
echo ".";
$name = '/'.Filesystem::readablePath($path, $root);
$file_map[$name] = array(
'hash' => $hash,
'disk' => $path,
);
}
echo "\n";
$runtime_map = array();
$parser = new PhutilDocblockParser();
foreach ($file_map as $path => $info) {
$data = Filesystem::readFile($info['disk']);
$matches = array();
$ok = preg_match('@/[*][*].*?[*]/@s', $data, $matches);
if (!$ok) {
throw new Exception(
"File {$path} does not have a header doc comment. Encode dependency ".
"data in a header docblock.");
}
list($description, $metadata) = $parser->parse($matches[0]);
$provides = preg_split('/\s+/', trim(idx($metadata, 'provides')));
$requires = preg_split('/\s+/', trim(idx($metadata, 'requires')));
$provides = array_filter($provides);
$requires = array_filter($requires);
if (count($provides) !== 1) {
throw new Exception(
"File {$path} must @provide exactly one Celerity target.");
}
$provides = reset($provides);
$type = 'js';
if (preg_match('/\.css$/', $path)) {
$type = 'css';
}
$path = '/res/'.substr($info['hash'], 0, 8).$path;
$runtime_map[$provides] = array(
'path' => $path,
'type' => $type,
'requires' => $requires,
);
}
$runtime_map = var_export($runtime_map, true);
$runtime_map = preg_replace('/\s+$/m', '', $runtime_map);
$runtime_map = preg_replace('/array \(/', 'array(', $runtime_map);
$resource_map = <<<EOFILE
<?php
/**
* This file is automatically generated. Use 'celerity_mapper.php' to rebuild
* it.
* @generated
*/
celerity_register_resource_map({$runtime_map});
EOFILE;
echo "Writing map...\n";
Filesystem::writeFile(
$root.'/../src/__celerity_resource_map__.php',
$resource_map);
echo "Done.\n";

View file

@ -0,0 +1,58 @@
<?php
/**
* This file is automatically generated. Use 'celerity_mapper.php' to rebuild
* it.
* @generated
*/
celerity_register_resource_map(array(
'phabricator-core-css' =>
array(
'path' => '/res/ffa0140c/rsrc/css/base.css',
'type' => 'css',
'requires' =>
array(
),
),
'phabricator-syntax-css' =>
array(
'path' => '/res/bf911307/rsrc/css/syntax.css',
'type' => 'css',
'requires' =>
array(
),
),
'javelin-init-dev' =>
array(
'path' => '/res/c57a9e89/rsrc/js/javelin/init.dev.js',
'type' => 'js',
'requires' =>
array(
),
),
'javelin-init-prod' =>
array(
'path' => '/res/f0172c54/rsrc/js/javelin/init.min.js',
'type' => 'js',
'requires' =>
array(
),
),
'javelin-lib-dev' =>
array(
'path' => '/res/3e747182/rsrc/js/javelin/javelin.dev.js',
'type' => 'js',
'requires' =>
array(
),
),
'javelin-lib-prod' =>
array(
'path' => '/res/9438670e/rsrc/js/javelin/javelin.min.js',
'type' => 'js',
'requires' =>
array(
),
),
));

View file

@ -46,6 +46,10 @@ phutil_register_library_map(array(
'AphrontURIMapper' => 'aphront/mapper',
'AphrontView' => 'view/base',
'AphrontWebpageResponse' => 'aphront/response/webpage',
'CelerityAPI' => 'infratructure/celerity/api',
'CelerityResourceController' => 'infratructure/celerity/controller',
'CelerityResourceMap' => 'infratructure/celerity/map',
'CelerityStaticResourceResponse' => 'infratructure/celerity/response',
'ConduitAPIMethod' => 'applications/conduit/method/base',
'ConduitAPIRequest' => 'applications/conduit/protocol/request',
'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect',
@ -121,10 +125,12 @@ phutil_register_library_map(array(
array(
'_qsprintf_check_scalar_type' => 'storage/qsprintf',
'_qsprintf_check_type' => 'storage/qsprintf',
'celerity_register_resource_map' => 'infratructure/celerity/map',
'qsprintf' => 'storage/qsprintf',
'queryfx' => 'storage/queryfx',
'queryfx_all' => 'storage/queryfx',
'queryfx_one' => 'storage/queryfx',
'require_celerity_resource' => 'infratructure/celerity/api',
'vqsprintf' => 'storage/qsprintf',
'vqueryfx' => 'storage/queryfx',
'xsprintf_query' => 'storage/qsprintf',
@ -161,6 +167,7 @@ phutil_register_library_map(array(
'AphrontSideNavView' => 'AphrontView',
'AphrontTableView' => 'AphrontView',
'AphrontWebpageResponse' => 'AphrontResponse',
'CelerityResourceController' => 'AphrontController',
'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod',

View file

@ -81,6 +81,11 @@ class AphrontDefaultApplicationConfiguration
'changeset/(?<id>\d+)/$' => 'DifferentialChangesetViewController',
),
'/res/' => array(
'(?<hash>[a-f0-9]{8})/(?<path>[^.]+\.(?:css|js))$'
=> 'CelerityResourceController',
),
'.*' => 'AphrontDefaultApplicationController',
);
}

View file

@ -96,7 +96,7 @@ final class DifferentialDiffTableOfContentsView extends AphrontView {
$rows[] =
'<tr>'.
'<td class="differential-toc-char" title={$chartitle}>'.$char.'</td>'.
'<td class="differential-toc-char" title='.$chartitle.'>'.$char.'</td>'.
'<td class="differential-toc-prop">'.$pchar.'</td>'.
'<td class="differential-toc-ftype">'.$desc.'</td>'.
'<td class="differential-toc-file">'.$link.$lines.'</td>'.

View file

@ -0,0 +1,35 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class CelerityAPI {
private static $response;
public static function getStaticResourceResponse() {
if (empty(self::$response)) {
self::$response = new CelerityStaticResourceResponse();
}
return self::$response;
}
}
function require_celerity_resource($symbol) {
$response = CelerityAPI::getStaticResourceResponse();
$response->requireResource($symbol);
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'infratructure/celerity/response');
phutil_require_source('CelerityAPI.php');

View file

@ -0,0 +1,64 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class CelerityResourceController extends AphrontController {
private $path;
private $hash;
public function willProcessRequest(array $data) {
$this->path = $data['path'];
$this->hash = $data['hash'];
}
public function processRequest() {
$path = $this->path;
// Sanity checking to keep this from exposing anything sensitive.
$path = preg_replace('@(//|\\.\\.)@', '', $path);
$matches = null;
if (!preg_match('/\.(css|js)$/', $path, $matches)) {
throw new Exception("Only CSS and JS resources may be served.");
}
$type = $matches[1];
$root = dirname(phutil_get_library_root('phabricator'));
try {
$data = Filesystem::readFile($root.'/webroot/'.$path);
} catch (Exception $ex) {
return new Aphront404Response();
}
$response = new AphrontFileResponse();
$response->setContent($data);
switch ($type) {
case 'css':
$response->setMimeType("text/css; charset=utf-8");
break;
case 'js':
$response->setMimeType("text/javascript; charset=utf-8");
break;
}
return $response;
}
}

View file

@ -0,0 +1,16 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/controller');
phutil_require_module('phabricator', 'aphront/response/file');
phutil_require_module('phutil', 'filesystem');
phutil_require_module('phutil', 'moduleutils');
phutil_require_source('CelerityResourceController.php');

View file

@ -0,0 +1,75 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class CelerityResourceMap {
private static $instance;
private $resourceMap;
public static function getInstance() {
if (empty(self::$instance)) {
self::$instance = new CelerityResourceMap();
$root = phutil_get_library_root('phabricator');
$ok = @include_once $root.'/__celerity_resource_map__.php';
if (!$ok) {
throw new Exception("Failed to load Celerity resource map!");
}
}
return self::$instance;
}
public function setResourceMap($resource_map) {
$this->resourceMap = $resource_map;
return $this;
}
public function resolveResources(array $symbols) {
$map = array();
foreach ($symbols as $symbol) {
if (!empty($map[$symbol])) {
continue;
}
$this->resolveResource($map, $symbol);
}
return $map;
}
private function resolveResource(array &$map, $symbol) {
if (empty($this->resourceMap[$symbol])) {
throw new Exception(
"Attempting to resolve unknown resource, '{$symbol}'.");
}
$info = $this->resourceMap[$symbol];
foreach ($info['requires'] as $requires) {
if (!empty($map[$requires])) {
continue;
}
$this->resolveResource($map, $requires);
}
$map[$symbol] = $info;
}
}
function celerity_register_resource_map(array $map) {
$instance = CelerityResourceMap::getInstance();
$instance->setResourceMap($map);
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phutil', 'moduleutils');
phutil_require_source('CelerityResourceMap.php');

View file

@ -0,0 +1,63 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class CelerityStaticResourceResponse {
private $symbols = array();
private $needsResolve = true;
private $resolved;
public function requireResource($symbol) {
$this->symbols[$symbol] = true;
$this->needsResolve = true;
return $this;
}
private function resolveResources() {
if ($this->needsResolve) {
$map = CelerityResourceMap::getInstance();
$this->resolved = $map->resolveResources(array_keys($this->symbols));
$this->needsResolve = false;
}
return $this;
}
public function renderResourcesOfType($type) {
$this->resolveResources();
$output = array();
foreach ($this->resolved as $resource) {
if ($resource['type'] == $type) {
$output[] = $this->renderResource($resource);
}
}
return implode("\n", $output);
}
private function renderResource(array $resource) {
switch ($resource['type']) {
case 'css':
$path = phutil_escape_html($resource['path']);
return '<link rel="stylesheet" type="text/css" href="'.$path.'" />';
case 'js':
$path = phutil_escape_html($resource['path']);
return '<script type="text/javascript" src="'.$path.'" />';
}
throw new Exception("Unable to render resource.");
}
}

View file

@ -0,0 +1,14 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'infratructure/celerity/map');
phutil_require_module('phutil', 'markup');
phutil_require_source('CelerityStaticResourceResponse.php');

View file

@ -41,8 +41,14 @@ class AphrontPageView extends AphrontView {
return '';
}
protected function willRenderPage() {
return;
}
public function render() {
$this->willRenderPage();
$title = $this->getTitle();
$head = $this->getHead();
$body = $this->getBody();

View file

@ -23,6 +23,7 @@ class PhabricatorStandardPageView extends AphrontPageView {
private $tabs = array();
private $selectedTab;
private $glyph;
private $bodyContent;
public function setApplicationName($application_name) {
$this->applicationName = $application_name;
@ -52,10 +53,18 @@ class PhabricatorStandardPageView extends AphrontPageView {
return $this->getGlyph().' '.parent::getTitle();
}
protected function willRenderPage() {
require_celerity_resource('phabricator-core-css');
$this->bodyContent = $this->renderChildren();
}
protected function getHead() {
$response = CelerityAPI::getStaticResourceResponse();
return
'<link rel="stylesheet" type="text/css" href="/rsrc/css/base.css" />'.
'<link rel="stylesheet" type="text/css" href="/rsrc/css/syntax.css" />'.
$response->renderResourcesOfType('css').
'<script type="text/javascript">window.__DEV__=1;</script>'.
'<script type="text/javascript" src="/rsrc/js/javelin/init.dev.js">'.
'</script>';
@ -102,20 +111,18 @@ class PhabricatorStandardPageView extends AphrontPageView {
phutil_escape_html($this->getApplicationName())).
$tabs.
'</div>'.
$this->renderChildren().
$this->bodyContent.
'<div style="clear: both;"></div>'.
'</div>';
}
protected function getTail() {
$response = CelerityAPI::getStaticResourceResponse();
return
'<script type="text/javascript" src="/rsrc/js/javelin/javelin.dev.js">'.
'</script>'.
$response->renderResourcesOfType('js').
'<script type="text/javascript">'.
'JX.Stratcom.mergeData(0, {});'.
'</script>';
;
}
}

View file

@ -6,6 +6,7 @@
phutil_require_module('phabricator', 'infratructure/celerity/api');
phutil_require_module('phabricator', 'view/page/base');
phutil_require_module('phutil', 'markup');

View file

@ -1,3 +1,7 @@
/**
* @provides phabricator-core-css
*/
html {
overflow-y: scroll;
}

View file

@ -1,3 +1,7 @@
/**
* @provides phabricator-syntax-css
*/
.remarkup-code .uu { /* Forbidden Unicode */
color: #aa0066;
}

View file

@ -1,3 +1,4 @@
/** @provides javelin-init-dev */
/**
* Javelin core; installs Javelin and Stratcom event delegation.
*

View file

@ -1 +1,2 @@
/** @provides javelin-init-prod */
(function(){if(window.JX)return;window.JX={};window.__DEV__=window.__DEV__||0;var d=false;var f=[];var e=[];var h=document.documentElement;var b=!!h.addEventListener;JX.__rawEventQueue=function(o){e.push(o);var j=JX.Stratcom;if(j&&j.ready){var m=e;e=[];for(var l=0;l<m.length;++l){var k=m[l];try{var test=k.type;}catch(p){continue;}if(!d&&k.type=='domready'){document.body&&(document.body.id=null);d=true;for(var l=0;l<f.length;l++)f[l]();}j.dispatch(k);}}else{var n=o.srcElement||o.target;if(n&&(o.type in {click:1,submit:1})&&(/ FI_CAPTURE /).test(' '+n.className+' ')){o.returnValue=false;o.preventDefault&&o.preventDefault();document.body.id='event_capture';if(!add_event_listener&&document.createEventObject){e.pop();e.push(document.createEventObject(o));}return false;}}};JX.enableDispatch=function(j,k){if(j.addEventListener){j.addEventListener(k,JX.__rawEventQueue,true);}else if(j.attachEvent)j.attachEvent('on'+k,JX.__rawEventQueue);};var a=['click','change','keypress','mousedown','mouseover','mouseout','mouseup','keydown','drop','dragenter','dragleave','dragover'];if(!b)a.push('focusin','focusout');if(window.opera){a.push('focus','blur');}else a.push('submit');for(var c=0;c<a.length;++c)JX.enableDispatch(h,a[c]);var i=[('onpagehide' in window)?'pagehide':'unload','resize','focus','blur'];for(var c=0;c<i.length;++c)JX.enableDispatch(window,i[c]);JX.__simulate=function(k,event){if(!b){var j={target:k,type:event};JX.__rawEventQueue(j);if(j.returnValue===false)return false;}};if(b){document.addEventListener('DOMContentLoaded',function(){JX.__rawEventQueue({type:'domready'});},true);}else{var g="if (this.readyState == 'complete') {"+"JX.__rawEventQueue({type: 'domready'});"+"}";document.write('<script'+' defer="defer"'+' src="javascript:void(0)"'+' onreadystatechange="'+g+'"'+'><\/sc'+'ript\>');}JX.onload=function(j){if(d){j();}else f.push(j);};})();

View file

@ -1,3 +1,5 @@
/** @provides javelin-lib-dev */
/**
* Javelin utility functions.
*

File diff suppressed because one or more lines are too long