Automatically generate example and tutorial lists.
Reduce code duplication in the example and tutorial build scripts.  This moves the enumeration, standardization, and sorting of the tutorial and examples to a single location (examples/build-utils.js).
ADD
examples/build-utils.js
+var fs = require('fs-extra');
+var glob = require('glob').sync;
+var path = require('path');
+var yaml = require('js-yaml');
+
+/**
+ * Get a sorted list of tutorials or examples, each of which contains an object
+ * with a variety of attributes:
+ * - `baseName`Css: a list of css files, if any specified by the record.
+ * - `baseName`Js: a list of js files, if any specified by the record.
+ * - main: the first js file in the previous list. May be `undefined` if that
+ * list is empty.
+ * - bundle: '../bundle.js' (a fixed string).
+ * - dir: the directory of the record.
+ * - path: the last component of the record's directory, or the value
+ * specified by the record.
+ * - output: an output path based on `outputDir`, `rootDir`, and `path`.
+ *
+ * @param {string} rootDir The location to search for files. Any directory
+ * with a json file called `baseName`.json will be parsed.
+ * @param {string} baseName The name of the json file. Also use for attribute
+ * names.
+ * @param {string} outputDir The root of a location to empty and copy. The
+ * actual output is stored at `outputDir`/`rootDir`/<record name>.
+ */
+function getList(rootDir, baseName, outputDir) {
+ var list = glob(rootDir + '/*/' + baseName + '.json')
+ .map(function (f) {
+ // /path/to/<baseName>.json
+ f = path.resolve(f);
+
+ // content of <baseName>.json
+ var json = fs.readJSONSync(path.resolve(f));
+
+ // directory of the file
+ var dir = path.dirname(f);
+
+ // by default, assume the path is where the files are located.
+ json.path = json.path || path.basename(dir);
+ json[baseName + 'Css'] = json[baseName + 'Css'] || [];
+ json[baseName + 'Js'] = json[baseName + 'Js'] || [];
+ if (json[baseName + 'Js'].length) {
+ json.main = path.resolve(dir, json[baseName + 'Js'][0]);
+ }
+ json.bundle = '../bundle.js';
+ json.dir = dir;
+
+ json.output = path.resolve(outputDir, rootDir, json.path);
+ // create, empty, and copy the source directory
+ fs.emptyDirSync(json.output);
+ fs.copySync(json.dir, json.output);
+
+ return json;
+ });
+
+ /* Sort records. Recods are sorted by level, order, title, and path.
+ * undefined or null levels ard orders are sorted after defined values.
+ * level should be used for the approximate significant of the record, and
+ * order for making specific records appear sooner in the list. */
+ list.sort(function (a, b) {
+ if (a.level !== b.level) {
+ return a.level === undefined ? 1 : b.level === undefined ? -1 : a.level - b.level;
+ }
+ if (a.order !== b.order) {
+ return a.order === undefined ? 1 : b.order === undefined ? -1 : a.order - b.order;
+ }
+ if (a.title !== b.title) {
+ return a.title < b.title ? -1 : 1;
+ }
+ return a.path < b.path ? -1 : 1;
+ });
+
+ return list;
+}
+
+/**
+ * Emit a yml list based on the records returned from `getList`.
+ *
+ * @param {string} dir The directory to write the file.
+ * @param {string} filename The name of the file to write.
+ * @param {array} An array from `getList` to emit.
+ */
+function writeYamlList(dir, filename, records) {
+ if (!fs.existsSync(dir)) {
+ fs.mkdirSync(dir);
+ }
+ fs.writeFileSync(
+ path.resolve(dir, filename),
+ yaml.safeDump(records.filter(function (t) {
+ return !t.disabled;
+ }).map(function (t) {
+ return {
+ name: t.path,
+ title: t.title,
+ description: (t.about || {}).text || t.title
+ };
+ }))
+ );
+}
+
+module.exports = {
+ getList: getList,
+ writeYamlList: writeYamlList
+};

MODIFY
examples/build-website.js
examples/build-website.js
var path = require('path');
-var glob = require('glob').sync;
var fs = require('fs-extra');
var docco = require('docco').document;
var pug = require('pug');
+var buildUtils = require('./build-utils');
+
// generate the examples
fs.ensureDirSync('website/source/examples');
-glob('examples/*/example.json')
- .map(function (f) {
- // /path/to/example.json
- f = path.resolve(f);
-
- // content of example.json
- var json = fs.readJSONSync(path.resolve(f));
-
- // directory of the example
- var dir = path.dirname(f);
-
- // by default, assume the path is where the files are located
- json.path = json.path || path.basename(dir);
- json.exampleCss = json.exampleCss || [];
- json.exampleJs = json.exampleJs || [];
-
- // the main js file for the example
- var main = path.resolve(dir, json.exampleJs[0]);
-
- // the output directory where the example will be compiled
- var output = path.resolve('website', 'source', 'examples', json.path);
-
- // create, empty, and copy the source directory
- fs.emptyDirSync(output);
- fs.copySync(dir, output);
-
+var examples = buildUtils.getList('examples', 'example', path.resolve('website', 'source'));
+examples.map(function (json) {
// make docco documentation in:
// dist/examples/<name>/docs/
- if (json.exampleJs.length) {
+ if (json.main) {
docco({
- args: [main],
- output: path.resolve(output, 'docs'),
+ args: [json.main],
+ output: path.resolve(json.output, 'docs'),
layout: 'classic'
}, function () {
// simplify the docco output to reduce the output size by
// removing the unnecessary public/ directory
- fs.removeSync(path.resolve(output, 'docs', 'public'));
+ fs.removeSync(path.resolve(json.output, 'docs', 'public'));
});
}
- json.docHTML = 'docs/' + path.basename(main).replace(/js$/, 'html');
-
- json.bundle = '../bundle.js';
+ json.docHTML = 'docs/' + path.basename(json.main).replace(/js$/, 'html');
var pugTemplate = '';
- var pugFile = path.relative('.', path.resolve(dir, 'index.pug'));
- if (fs.existsSync(path.resolve(dir, 'index.pug'))) {
+ var pugFile = path.relative('.', path.resolve(json.dir, 'index.pug'));
+ if (fs.existsSync(path.resolve(json.dir, 'index.pug'))) {
pugTemplate = fs.readFileSync(pugFile, 'utf8');
pugTemplate = pugTemplate.replace('extends ../common/index.pug', '');
pugTemplate = pugTemplate.replace('block append mainContent', '');
@@ -68,7 +43,7 @@ exampleCss: ${JSON.stringify(json.exampleCss)}
exampleJs: ${JSON.stringify(json.exampleJs)}
---
` + html;
- fs.writeFileSync(path.resolve(output, 'index.html'), html);
- return json;
+ fs.writeFileSync(path.resolve(json.output, 'index.html'), html);
});
+buildUtils.writeYamlList(path.resolve('website', 'source', '_data'), 'examples.yml', examples);

MODIFY
examples/build.js
examples/build.js
var path = require('path');
-var glob = require('glob').sync;
var fs = require('fs-extra');
var docco = require('docco').document;
var pug = require('pug');
+var buildUtils = require('./build-utils');
+
// generate the examples
fs.ensureDirSync('dist/examples');
-var examples = glob('examples/*/example.json')
- .map(function (f) {
- // /path/to/example.json
- f = path.resolve(f);
-
- // content of example.json
- var json = fs.readJSONSync(path.resolve(f));
-
- // directory of the example
- var dir = path.dirname(f);
-
- // by default, assume the path is where the files are located
- json.path = json.path || path.basename(dir);
- json.exampleCss = json.exampleCss || [];
- json.exampleJs = json.exampleJs || [];
-
- // the main js file for the example
- var main = path.resolve(dir, json.exampleJs[0]);
-
- // the output directory where the example will be compiled
- var output = path.resolve('dist', 'examples', json.path);
-
- // create, empty, and copy the source directory
- fs.emptyDirSync(output);
- fs.copySync(dir, output);
+var examples = buildUtils.getList('examples', 'example', 'dist');
+examples.map(function (json) {
// make docco documentation in:
// dist/examples/<name>/docs/
if (json.exampleJs.length) {
docco({
- args: [main],
- output: path.resolve(output, 'docs'),
+ args: [json.main],
+ output: path.resolve(json.output, 'docs'),
layout: 'classic'
}, function () {
// simplify the docco output to reduce the output size by
// removing the unnecessary public/ directory
- fs.removeSync(path.resolve(output, 'docs', 'public'));
+ fs.removeSync(path.resolve(json.output, 'docs', 'public'));
});
}
- json.docHTML = 'docs/' + path.basename(main).replace(/js$/, 'html');
-
- json.bundle = '../bundle.js';
+ json.docHTML = 'docs/' + path.basename(json.main).replace(/js$/, 'html');
- var pugFile = path.relative('.', path.resolve(dir, 'index.pug'));
- if (!fs.existsSync(path.resolve(dir, 'index.pug'))) {
- pugFile = path.relative('.', path.resolve(dir, '../common/index.pug'));
+ var pugFile = path.relative('.', path.resolve(json.dir, 'index.pug'));
+ if (!fs.existsSync(path.resolve(json.dir, 'index.pug'))) {
+ pugFile = path.relative('.', path.resolve(json.dir, '../common/index.pug'));
}
var fn = pug.compileFile(pugFile, {pretty: true});
- fs.writeFileSync(path.resolve(output, 'index.html'), fn(json));
- return json;
+ fs.writeFileSync(path.resolve(json.output, 'index.html'), fn(json));
});
// copy common files

MODIFY
examples/hurricanes/example.json
examples/hurricanes/example.json
"title": "Hurricane tracking data",
"exampleCss": ["main.css"],
"exampleJs": ["main.js"],
+ "level": 0,
+ "order": 0,
"about": {
"text": "Explore historic hurricane paths since 1980 on an interactive map."
}

MODIFY
examples/sld/example.json
examples/sld/example.json
{
- "title": "Adding styles to WMS raster layers",
+ "title": "Styling WMS raster layers",
"exampleCss": ["main.css"],
"exampleJs": ["main.js"],
"about": {

MODIFY
package.json
package.json
"jsdoc-autoprivate": "0.0.1",
"json-loader": "^0.5.4",
"jstransformer-markdown-it": "^2.0.0",
+ "js-yaml": "^3.10.0",
"karma": "^0.13.22",
"karma-coverage": "^1.0.0",
"karma-chrome-launcher": "^2.2.0",
"stop-test": "forever stop ./testing/test-runners/server.js",
"docs": "jsdoc --pedantic -d dist/apidocs -r src -c jsdoc.conf.json",
"website": "cd website && npx hexo server",
- "build-website": "npm run build && cp -a dist/built/. website/source/built && npm run build-website-examples && npm run build-website-tutorials && npm run docs && cp -a dist/apidocs/. website/source/apidocs && cd website && rm -f db.json && npx hexo generate"
+ "build-website": "npm run build && cp -a dist/built/. website/source/built && npm run build-website-examples && npm run build-website-tutorials && npm run docs && cp -ar dist/data/. website/source/data && cp -ar dist/apidocs/. website/source/apidocs && cd website && rm -f db.json && npx hexo generate"
},
"keywords": [
"map",

MODIFY
tutorials/build-website.js
tutorials/build-website.js
var path = require('path');
-var glob = require('glob').sync;
var fs = require('fs-extra');
var pug = require('pug');
+var buildUtils = require('../examples/build-utils');
+
// generate the tutorials
fs.ensureDirSync('website/source/tutorials');
-glob('tutorials/*/tutorial.json')
- .map(function (f) {
- // /path/to/tutorial.json
- f = path.resolve(f);
-
- // content of tutorial.json
- var json = fs.readJSONSync(path.resolve(f));
-
- // directory of the tutorial
- var dir = path.dirname(f);
-
- // by default, assume the path is where the files are located.
- json.path = json.path || path.basename(dir);
- json.tutorialCss = json.tutorialCss || [];
- json.tutorialJs = json.tutorialJs || [];
- json.bundle = '../bundle.js';
+var tutorials = buildUtils.getList('tutorials', 'tutorial', path.resolve('website', 'source'));
- // the output directory where the tutorial will be compiled
- var output = path.resolve('website', 'source', 'tutorials', json.path);
-
- // create, empty, and copy the source directory
- fs.emptyDirSync(output);
- fs.copySync(dir, output);
-
- var pugTemplate = fs.readFileSync(path.relative('.', path.resolve(dir, 'index.pug')), 'utf8');
+tutorials.map(function (json) {
+ var pugTemplate = fs.readFileSync(path.relative('.', path.resolve(json.dir, 'index.pug')), 'utf8');
pugTemplate = pugTemplate.replace('extends ../common/index.pug', 'extends ../common/index-website.pug');
var fn = pug.compile(pugTemplate, {
pretty: false,
- filename: path.relative('.', path.resolve(dir, 'index.pug'))
+ filename: path.relative('.', path.resolve(json.dir, 'index.pug'))
});
var html = fn(json);
html = `---
@@ -45,9 +25,10 @@ tutorialCss: ${JSON.stringify(json.tutorialCss)}
tutorialJs: ${JSON.stringify(json.tutorialJs)}
---
` + html;
- fs.writeFileSync(path.resolve(output, 'index.html'), html);
- return json;
+ fs.writeFileSync(path.resolve(json.output, 'index.html'), html);
});
// copy common files
fs.copySync('tutorials/common', 'website/source/tutorials/common');
+
+buildUtils.writeYamlList(path.resolve('website', 'source', '_data'), 'tutorials.yml', tutorials);

MODIFY
tutorials/build.js
tutorials/build.js
var path = require('path');
-var glob = require('glob').sync;
var fs = require('fs-extra');
var pug = require('pug');
+var buildUtils = require('../examples/build-utils');
+
// generate the tutorials
fs.ensureDirSync('dist/tutorials');
-var tutorials = glob('tutorials/*/tutorial.json')
- .map(function (f) {
- // /path/to/tutorial.json
- f = path.resolve(f);
-
- // content of tutorial.json
- var json = fs.readJSONSync(path.resolve(f));
-
- // directory of the tutorial
- var dir = path.dirname(f);
-
- // by default, assume the path is where the files are located.
- json.path = json.path || path.basename(dir);
- json.tutorialCss = json.tutorialCss || [];
- json.tutorialJs = json.tutorialJs || [];
- json.bundle = '../bundle.js';
-
- // the output directory where the tutorial will be compiled
- var output = path.resolve('dist', 'tutorials', json.path);
-
- // create, empty, and copy the source directory
- fs.emptyDirSync(output);
- fs.copySync(dir, output);
-
- var fn = pug.compileFile(path.relative('.', path.resolve(dir, 'index.pug')), {pretty: true});
- fs.writeFileSync(path.resolve(output, 'index.html'), fn(json));
- return json;
- });
+var tutorials = buildUtils.getList('tutorials', 'tutorial', 'dist');
-/* Sort tutorials. Tutorials are sorted by level, order, title, and path.
- * undefined or null levels are orders are sorted after defined values. level
- * should be used for the approximate difficulty of the tutorial, and order for
- * making specific tutorials appear sooner in the list. */
-tutorials.sort(function (a, b) {
- if (a.level !== b.level) {
- return a.level === undefined ? 1 : b.level === undefined ? -1 : a.level - b.level;
- }
- if (a.order !== b.order) {
- return a.order === undefined ? 1 : b.order === undefined ? -1 : a.order - b.order;
- }
- if (a.title !== b.title) {
- return a.title < b.title ? -1 : 1;
- }
- return a.path < b.path ? -1 : 1;
+tutorials.map(function (json) {
+ var fn = pug.compileFile(path.relative('.', path.resolve(json.dir, 'index.pug')), {pretty: true});
+ fs.writeFileSync(path.resolve(json.output, 'index.html'), fn(json));
});
// copy common files

MODIFY
website/.gitignore
website/.gitignore
@@ -5,7 +5,9 @@ db.json
node_modules/
public/
.deploy*/
-/source/examples/**
-/source/tutorials/**
-/source/apidocs/**
-/source/data
+source/_data/**
+source/apidocs/**
+source/built/**
+source/data/**
+source/examples/**
+source/tutorials/**

MODIFY
website/_config.yml
website/_config.yml
@@ -23,6 +23,7 @@ public_dir: public
skip_render:
- "**/*.js"
- "examples/*/*.json"
+ - "tutorials/*/*.json"
- "data/**/*"
- "apidocs/**/*"

DELETE
website/source/_data/examples.yml
-- name: hurricanes
- title: Hurricane tracking data
- description: Explore historic hurricane paths since 1980 on an interactive map.
-
-- name: heatmap
- title: Heatmap Feature
- description: This example shows how to add a heatmap to a map.
-
-- name: deepzoom
- title: Deepzoom tiled image example
- description: Rendering a tiled image using the Deepzoom protocol.
-
-- name: annotations
- title: Annotation Layer
- description: Add annotations, such as marked rectangles, to a map.
-
-- name: choropleth
- title: Choropleth plot
- description: Add choropleth features to a map. Choropleths color each region in a set of regions based on a scalar value for each region.
-
-- name: contour
- title: Contour plot
- description: Add contour features to a map. Contours color a region based on an array of scalar values.
-
-- name: geoJSON
- title: GeoJSON file reader
- description: Shows how to use geoJSON reader and use properties to adjust the style of the features. You can edit the json file while the features on the map update in response.
-
-- name: legend
- title: Feature legends
- description: This example shows how to create a legend to describe feature styles on a map.
-
-- name: lines
- title: Line Feature
- description: This example shows the variations that are available for line features.
-
-- name: pixelmap
- title: Pixelmap feature
- description: This example shows how to use a pixelmap feature. The pixelmap colors areas based on an index derived from an image and some data per index.
-
-- name: polygons
- title: Polygon features
- description: This example shows how to add polygons to a map.
-
-- name: quads
- title: Quad features
- description: This example shows how to add dynamic quads to a map.
-
-- name: reprojection
- title: Reproject tiles
- description: Shows how you can reproject tiles from one projection to another. The source tiles are in Web Mercator, but can be drawn in many different projections.
-
-- name: sld
- title: Adding styles to WMS raster layers
- description: Rendering tiles from a WMS server by customizing the style.
-
-- name: tiles
- title: Tile layer with controllable options
- description: This example shows the options on tile layers. The code and query parameters allow even more options to be adjusted.
-
-- name: transitions
- title: Using animated transitions
- description: Using animations to transition between points on a map.
-
-- name: widgets
- title: Map Widgets
- description: This example shows how the widgets API can be used to place elements on top of the map.
-
-- name: wms
- title: Rendering tiles from a WMS server
- description: Rendering tiles from a WMS server by customizing the tile URL of an OSM layer.
-

DELETE
website/source/_data/tutorials.yml
-- name: basic
- title: Simple Map
- description: Make a simple map. Includes HTML, CSS, and Javascript
-
-- name: tile_source
- title: Changing Tile Source
- description: Make a simple map and change the tile source
-
-- name: wms
- title: Using a WMS Tile Source
- description: Make a map with a custom function for getting tiles from a WMS server. This also shows how to get tile bounds in different coordinates
\ No newline at end of file

Automatically generate example and tutorial lists.

Reduce code duplication in the example and tutorial build scripts.  This moves the enumeration, standardization, and sorting of the tutorial and examples to a single location (examples/build-utils.js).