dev-tooling: add plunker generation from _examples

closes #408, Dev Tooling 15
This commit is contained in:
Jay Traband 2015-11-22 20:56:28 -08:00 committed by Ward Bell
parent 643a5bbf63
commit 4bbd9784ef
18 changed files with 367 additions and 6 deletions

4
.gitignore vendored
View File

@ -16,4 +16,6 @@ pubspec.lock
**/resources/zips
public/docs/xref-*.*
_zip-output
www
www
*.plnkr.html
plnkr.html

View File

@ -25,11 +25,13 @@ var TOOLS_PATH = './tools';
var ANGULAR_PROJECT_PATH = '../angular';
var PUBLIC_PATH = './public';
var DOCS_PATH = path.join(PUBLIC_PATH, 'docs');
var EXAMPLES_PATH = path.join(DOCS_PATH, '_examples');
var NOT_API_DOCS_GLOB = path.join(PUBLIC_PATH, './{docs/*/latest/!(api),!(docs)}/**/*');
var RESOURCES_PATH = path.join(PUBLIC_PATH, 'resources');
var docShredder = require(path.resolve(TOOLS_PATH, 'doc-shredder/doc-shredder'));
var exampleZipper = require(path.resolve(TOOLS_PATH, '_example-zipper/exampleZipper'));
var plunkerBuilder = require(path.resolve(TOOLS_PATH, 'plunker-builder/plunkerBuilder'));
var _devguideShredOptions = {
examplesDir: path.join(DOCS_PATH, '_examples'),
@ -49,7 +51,9 @@ var _excludeMatchers = _excludePatterns.map(function(excludePattern){
return new Minimatch(excludePattern)
});
gulp.task('build-plunkers', function() {
return plunkerBuilder.buildPlunkers(EXAMPLES_PATH, gutil.log);
});
// Public tasks
@ -95,10 +99,6 @@ gulp.task('build-js-api-docs', ['_shred-api-examples'], function() {
return buildApiDocs('js');
});
gulp.task('git-changed-examples', ['_shred-devguide-examples'], function(){
var after, sha, messageSuffix;
if (argv.after) {

View File

@ -44,6 +44,7 @@
"indent-string": "^2.1.0",
"jasmine-core": "^2.3.4",
"jasmine-node": "^1.14.5",
"jsdom": "^7.0.2",
"jsonfile": "^2.2.2",
"karma": "^0.13.10",
"karma-chrome-launcher": "^0.2.0",

View File

@ -0,0 +1,5 @@
{
"description": "QuickStart Application",
"files": ["app/app.ts", "index.html"],
"tags": ["quickstart"]
}

View File

@ -0,0 +1,5 @@
{
"description": "Alternate Quickstart",
"files": ["!app/app.ts", "!*.html", "index.1.html" ],
"main": "index.1.html"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,4 @@
{
"description": "Template Syntax Collection"
}

View File

@ -0,0 +1,3 @@
{
"files": ["!*.json"]
}

View File

@ -1,5 +1,7 @@
include ../../../../_includes/_util-fns
a(href='/docs/_examples/template-syntax/ts/src/plnkr.html') Live Example 1
:marked
# Template Syntax
Our Angular application manages what the user sees and does through the interaction of a Component class instance and its user-facing template.
@ -10,6 +12,8 @@ include ../../../../_includes/_util-fns
Well cover these basic elements of Template Syntax
[Live Example 1](/docs/_examples/template-syntax/ts/src/plnkr.html)
>[HTML](#html)
>[Interpolation](#interpolation)

View File

@ -0,0 +1,97 @@
module.exports = {
translate: translate
};
var _rxRules = {
script: {
from: /<script.*".*%tag%".*>.*<\/script>/,
to: '<script src="%tag%"></script>'
},
link: {
from: '/<link rel="stylesheet" href=".*%tag%".*>/',
to: '<link rel="stylesheet" href="%tag%">'
},
config: {
from: /\s*System.config\(\{\s*packages:[\s\S]*\}\}\s*\}\);/m,
to: "\n" +
" System.config({\n" +
" transpiler: 'typescript', \n" +
" typescriptOptions: { emitDecoratorMetadata: true }, \n" +
" packages: {'app': {defaultExtension: 'ts'}} \n" +
" });"
},
};
var _rxData = [
{
pattern: 'script',
from: 'node_modules/systemjs/dist/system.src.js',
to: ['https://rawgithub.com/systemjs/systemjs/0.19.6/dist/system.js', 'https://code.angularjs.org/tools/typescript.js']
// to: ['https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.18.4/system.js', 'https://code.angularjs.org/tools/typescript.js' ]
},
{
pattern: 'script',
from: 'node_modules/angular2/bundles/angular2.dev.js',
to: 'https://code.angularjs.org/2.0.0-alpha.46/angular2.dev.js'
},
{
pattern: 'script',
from: 'node_modules/angular2/bundles/router.dev.js',
to: 'https://code.angularjs.org/2.0.0-alpha.46/router.dev.js'
},
{
pattern: 'script',
from: 'node_modules/angular2/bundles/http.dev.js',
to: 'https://code.angularjs.org/2.0.0-alpha.46/http.dev.js'
},
{
pattern: 'script',
from: 'node_modules/angular2/bundles/testing.js',
to: 'https://code.angularjs.org/2.0.0-alpha.46/testing.js'
},
{
pattern: 'link',
from: 'node_modules/bootstrap/dist/css/bootstrap.min.css',
to: 'https://cdnjs.com/libraries/twitter-bootstrap/3.3.5'
// to: 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css'
},
{
pattern: 'config',
}
];
function translate(html) {
_rxData.forEach(function(rxDatum) {
var rxRule = _rxRules[rxDatum.pattern];
// rxFrom is a rexexp
var rxFrom = rxRule.from;
if (rxDatum.from) {
var from = rxDatum.from.replace('/', '\/');
var rxTemp = rxFrom.toString();
rxTemp = rxTemp.replace('%tag%', from);
rxFrom = rxFromString(rxTemp);
}
// rxTo is a string
var rxTo = rxRule.to;
if (rxDatum.to) {
var to = rxDatum.to;
to = Array.isArray(to) ? to : [to];
to = to.map(function (toItem) {
return rxTo.replace("%tag%", toItem);
});
rxTo = to.join("\n ");
}
html = html.replace(rxFrom, rxTo );
});
return html;
}
function rxFromString(rxString) {
var rx = /^\/(.*)\/(.*)/;
var pieces = rx.exec(rxString);
return RegExp(pieces[1], pieces[2]);
}

View File

@ -0,0 +1,13 @@
Copyright 2010-2015 Google, Inc. http://angularjs.org
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](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.

View File

@ -0,0 +1,221 @@
// Canonical path provides a consistent path (i.e. always forward slashes) across different OSes
var path = require('canonical-path');
var Q = require('q');
var _ = require('lodash');
var jsdom = require("jsdom");
var fs = require("fs");
var globule = require('globule');
var indexHtmlTranslator = require('./indexHtmlTranslator');
var regionExtractor = require('../doc-shredder/regionExtractor');
module.exports = {
buildPlunkers: buildPlunkers
};
function buildPlunkers(basePath, errFn) {
errFn = errFn || function(e) { console.log(e); };
var configExtns = ['plnkr.config', '*.plnkr.config'];
var gpaths = configExtns.map(function(extn) {
return path.join(basePath, '**/' + extn);
});
var fileNames = globule.find(gpaths);
fileNames.forEach(function(configFileName) {
try {
buildPlunkerFrom(configFileName);
} catch (e) {
errFn(e);
}
});
}
// config has
// files: [] - optional array of globs - defaults to all js, ts, html, json, css and md files (with certain files removed)
// description: optional string - description of this plunker - defaults to the title in the index.html page.
// tags: [] - optional array of strings
// main: string - filename of what will become index.html in the plunker - defaults to index.html
function buildPlunkerFrom(configFileName ) {
// replace ending 'plnkr.config' with 'plnkr.html' to create output file name;
var outputFileName = configFileName.substr(0, configFileName.length - 'plnkr.config'.length) + 'plnkr.html';
try {
var config = initConfigAndCollectFileNames(configFileName);
var postData = createPostData(config);
var html = createPlunkerHtml(postData);
fs.writeFileSync(outputFileName, html, 'utf-8');
} catch (e) {
// if we fail delete the outputFile if it exists because it is an old one.
if (existsSync(outputFileName)) {
fs.unlinkSync(outputFileName);
}
throw e;
}
}
function initConfigAndCollectFileNames(configFileName) {
var basePath = path.dirname(configFileName);
var configSrc = fs.readFileSync(configFileName, 'utf-8');
try {
var config = (configSrc && configSrc.trim().length) ? JSON.parse(configSrc) : {};
} catch (e) {
throw new Error("Plunker config - unable to parse json file: " + configFileName + '\n ' + e);
}
var defaultIncludes = ['**/*.ts', '**/*.js', '**/*.css', '**/*.html', '**/*.md', '**/*.json', '**/*.png'];
if (config.files) {
if (config.files.length > 0) {
if (config.files[0].substr(0, 1) == '!') {
config.files = defaultIncludes.concat(config.files);
}
}
} else {
config.files = defaultIncludes;
}
var gpaths = config.files.map(function(fileName) {
fileName = fileName.trim();
if (fileName.substr(0,1) == '!') {
return "!" + path.join(basePath, fileName.substr(1));
} else {
return path.join(basePath, fileName);
}
});
var defaultExcludes = [ '!**/typings/**','!**/tsconfig.json', '!**/plnkr.html', '!**/*.plnkr.html' ];
Array.prototype.push.apply(gpaths, defaultExcludes);
config.fileNames = globule.find(gpaths);
config.basePath = basePath;
return config;
}
function createPostData(config) {
var postData = {};
config.fileNames.forEach(function(fileName) {
var content;
var extn = path.extname(fileName);
if (extn == '.png') {
content = encodeBase64(fileName);
fileName = fileName.substr(0, fileName.length - 4) + '.base64.png'
} else {
content = fs.readFileSync(fileName, 'utf-8');
}
// var escapedValue = escapeHtml(content);
var relativeFileName = path.relative(config.basePath, fileName);
if (relativeFileName == config.main) {
relativeFileName = 'index.html';
}
if (relativeFileName == 'index.html') {
content = indexHtmlTranslator.translate(content);
if (config.description == null) {
// set config.description to title from index.html
var matches = /<title>(.*)<\/title>/.exec(content);
if (matches) {
config.description = matches[1];
}
}
}
content = regionExtractor.removeDocTags(content, extn.substr(1));
postData['files[' + relativeFileName + ']'] = content;
});
postData['files[license.md]'] = fs.readFileSync(path.join(__dirname, "license.md"));
var tags = ['angular2', 'example'].concat(config.tags || []);
tags.forEach(function(tag,ix) {
postData['tags[' + ix + ']'] = tag;
});
// postData['tags[0]'] = "angular2";
// postData['tags[1]'] = "example";
postData.private = true;
postData.description = "Angular 2 Example - " + config.description;
return postData;
}
function existsSync(filename) {
try {
fs.accessSync(filename);
return true;
} catch(ex) {
return false;
}
}
function encodeBase64(file) {
// read binary data
var bitmap = fs.readFileSync(file);
// convert binary data to base64 encoded string
return new Buffer(bitmap).toString('base64');
}
function createPlunkerHtml(postData) {
useNewWindow = false;
var baseHtml = createBasePlunkerHtml(useNewWindow);
var doc = jsdom.jsdom(baseHtml);
var form = doc.querySelector('form');
_.forEach(postData, function(value, key) {
var ele = htmlToElement(doc, '<input type="hidden" name="' + key + '">');
ele.setAttribute('value', value);
form.appendChild(ele)
});
var html = doc.documentElement.outerHTML;
return html;
}
function createBasePlunkerHtml(useNewWindow) {
var url = 'http://plnkr.co/edit/?p=preview';
// If the form posts to target="_blank", pop-up blockers can cause it not to work.
// If a user choses to bypass pop-up blocker one time and click the link, they will arrive at
// a new default plnkr, not a plnkr with the desired template. Given this undesired behavior,
// some may still want to open the plnk in a new window by opting-in via ctrl+click. The
// newWindow param allows for this possibility.
var target = useNewWindow ? '_blank' : '_self';
var html = '<!DOCTYPE html><html lang="en"><body>'
html += '<form id="mainForm" method="post" action="' + url + '" target="' + target + '">'
// html += '<div class="button"><button id="formButton" type="submit">Create Plunker</button></div>'
// html += '</form><script>document.getElementById("formButton").click();</script>'
html += '</form><script>document.getElementById("mainForm").submit();</script>'
html += '</body></html>';
return html;
}
function htmlToElement(document, html) {
var div = document.createElement('div');
div.innerHTML = html;
return div.firstChild;
}
// not currently used.
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
//// Old version - no longer used
//function createPlunkerHtmlAsync(basePath, postData) {
//
// useNewWindow = false;
// jsdom.env({
// html: createBasePlunkerHtml(useNewWindow),
// done: function (err, window) {
// var doc = window.document;
// var form = doc.querySelector('form');
//
// _.forEach(postData, function(value, key) {
// var ele = htmlToElement(doc, '<input type="hidden" name="' + key + '">');
// ele.setAttribute('value', value);
// form.appendChild(ele)
// });
// var html = doc.documentElement.outerHTML;
// var outputFn = path.join(basePath, "plnkr.html");
// fs.writeFileSync(outputFn, html, 'utf-8' );
// }
// });
//}

View File

@ -0,0 +1,6 @@
.l-main-section
a(href='/docs/_examples/template-syntax/ts/src/plnkr.html') Live Example 0
:marked
[Live Example 1](/docs/_examples/template-syntax/ts/src/plnkr.html)
<a href='/docs/_examples/template-syntax/ts/src/plnkr.html'><code>Live Example 2</code></a>