2017-01-26 09:03:53 -05:00
|
|
|
const blockC = require('./region-matchers/block-c');
|
|
|
|
const html = require('./region-matchers/html');
|
|
|
|
const inlineC = require('./region-matchers/inline-c');
|
|
|
|
const inlineCOnly = require('./region-matchers/inline-c-only');
|
|
|
|
const inlineHash = require('./region-matchers/inline-hash');
|
|
|
|
const DEFAULT_PLASTER = '. . .';
|
2017-04-26 12:58:57 -04:00
|
|
|
const {mapObject} = require('../../helpers/utils');
|
2017-01-26 09:03:53 -05:00
|
|
|
|
|
|
|
module.exports = function regionParser() {
|
|
|
|
return regionParserImpl;
|
|
|
|
};
|
|
|
|
|
|
|
|
regionParserImpl.regionMatchers = {
|
|
|
|
ts: inlineC,
|
|
|
|
js: inlineC,
|
|
|
|
es6: inlineC,
|
|
|
|
dart: inlineC,
|
|
|
|
html: html,
|
2019-06-26 17:23:32 -04:00
|
|
|
svg: html,
|
2017-01-26 09:03:53 -05:00
|
|
|
css: blockC,
|
|
|
|
yaml: inlineHash,
|
2019-06-26 17:23:32 -04:00
|
|
|
yml: inlineHash,
|
feat(aio): support annotating JSON files with doc-regions
This change allows the example writer to add doc-region annotations to
files that do not allow comments. This is done by creating a clone of the
file and adding `.annotated` to the file name. This new file can contain
inline `// ...` comments that can be used to annotate the doc regions.
Example:
**package.json**
```
{
"name": "angular.io",
"version": "0.0.0",
"main": "index.js",
"repository": "git@github.com:angular/angular.git",
"author": "Angular",
"license": "MIT",
"private": true,
}
````
**package.json.annotated**
```
{
"name": "angular.io",
// #docregion version
"version": "0.0.0",
// #enddocregion
"main": "index.js",
"repository": "git@github.com:angular/angular.git",
"author": "Angular",
"license": "MIT",
"private": true,
}
````
This region can then be referenced in examples just like any other doc region:
```
{@example 'package.json' region="version"}
```
2017-02-21 08:09:57 -05:00
|
|
|
jade: inlineCOnly,
|
2019-06-26 17:23:32 -04:00
|
|
|
pug: inlineCOnly,
|
2017-02-22 13:12:34 -05:00
|
|
|
json: inlineC,
|
feat(aio): support annotating JSON files with doc-regions
This change allows the example writer to add doc-region annotations to
files that do not allow comments. This is done by creating a clone of the
file and adding `.annotated` to the file name. This new file can contain
inline `// ...` comments that can be used to annotate the doc regions.
Example:
**package.json**
```
{
"name": "angular.io",
"version": "0.0.0",
"main": "index.js",
"repository": "git@github.com:angular/angular.git",
"author": "Angular",
"license": "MIT",
"private": true,
}
````
**package.json.annotated**
```
{
"name": "angular.io",
// #docregion version
"version": "0.0.0",
// #enddocregion
"main": "index.js",
"repository": "git@github.com:angular/angular.git",
"author": "Angular",
"license": "MIT",
"private": true,
}
````
This region can then be referenced in examples just like any other doc region:
```
{@example 'package.json' region="version"}
```
2017-02-21 08:09:57 -05:00
|
|
|
'json.annotated': inlineC
|
2017-01-26 09:03:53 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param contents string
|
|
|
|
* @param fileType string
|
|
|
|
* @returns {contents: string, regions: {[regionName: string]: string}}
|
|
|
|
*/
|
|
|
|
function regionParserImpl(contents, fileType) {
|
|
|
|
const regionMatcher = regionParserImpl.regionMatchers[fileType];
|
|
|
|
const openRegions = [];
|
|
|
|
const regionMap = {};
|
|
|
|
|
|
|
|
if (regionMatcher) {
|
|
|
|
let plaster = regionMatcher.createPlasterComment(DEFAULT_PLASTER);
|
|
|
|
const lines = contents.split(/\r?\n/).filter((line, index) => {
|
|
|
|
const startRegion = line.match(regionMatcher.regionStartMatcher);
|
|
|
|
const endRegion = line.match(regionMatcher.regionEndMatcher);
|
|
|
|
const updatePlaster = line.match(regionMatcher.plasterMatcher);
|
|
|
|
|
|
|
|
// start region processing
|
|
|
|
if (startRegion) {
|
|
|
|
// open up the specified region
|
2017-02-02 03:19:20 -05:00
|
|
|
const regionNames = getRegionNames(startRegion[1]);
|
|
|
|
if (regionNames.length === 0) {
|
|
|
|
regionNames.push('');
|
2017-01-26 09:03:53 -05:00
|
|
|
}
|
2017-02-02 03:19:20 -05:00
|
|
|
regionNames.forEach(regionName => {
|
|
|
|
const region = regionMap[regionName];
|
|
|
|
if (region) {
|
|
|
|
if (region.open) {
|
|
|
|
throw new RegionParserError(
|
|
|
|
`Tried to open a region, named "${regionName}", that is already open`, index);
|
|
|
|
}
|
|
|
|
region.open = true;
|
2017-03-31 19:07:14 -04:00
|
|
|
if (plaster) {
|
|
|
|
region.lines.push(plaster);
|
|
|
|
}
|
2017-02-02 03:19:20 -05:00
|
|
|
} else {
|
|
|
|
regionMap[regionName] = {lines: [], open: true};
|
|
|
|
}
|
|
|
|
openRegions.push(regionName);
|
|
|
|
});
|
2017-01-26 09:03:53 -05:00
|
|
|
|
|
|
|
// end region processing
|
|
|
|
} else if (endRegion) {
|
|
|
|
if (openRegions.length === 0) {
|
|
|
|
throw new RegionParserError('Tried to close a region when none are open', index);
|
|
|
|
}
|
|
|
|
// close down the specified region (or most recent if no name is given)
|
2017-02-02 03:19:20 -05:00
|
|
|
const regionNames = getRegionNames(endRegion[1]);
|
|
|
|
if (regionNames.length === 0) {
|
|
|
|
regionNames.push(openRegions[openRegions.length - 1]);
|
2017-01-26 09:03:53 -05:00
|
|
|
}
|
2017-02-02 03:19:20 -05:00
|
|
|
|
|
|
|
regionNames.forEach(regionName => {
|
|
|
|
const region = regionMap[regionName];
|
|
|
|
if (!region || !region.open) {
|
|
|
|
throw new RegionParserError(
|
|
|
|
`Tried to close a region, named "${regionName}", that is not open`, index);
|
|
|
|
}
|
|
|
|
region.open = false;
|
|
|
|
removeLast(openRegions, regionName);
|
|
|
|
});
|
2017-01-26 09:03:53 -05:00
|
|
|
|
|
|
|
// doc plaster processing
|
|
|
|
} else if (updatePlaster) {
|
2017-03-31 19:07:14 -04:00
|
|
|
const plasterString = updatePlaster[1].trim();
|
|
|
|
plaster = plasterString ? regionMatcher.createPlasterComment(plasterString) : '';
|
2017-01-26 09:03:53 -05:00
|
|
|
|
|
|
|
// simple line of content processing
|
|
|
|
} else {
|
|
|
|
openRegions.forEach(regionName => regionMap[regionName].lines.push(line));
|
|
|
|
// do not filter out this line from the content
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this line contained an annotation so let's filter it out
|
|
|
|
return false;
|
|
|
|
});
|
2017-02-22 13:12:34 -05:00
|
|
|
if (!regionMap['']) {
|
|
|
|
regionMap[''] = {lines};
|
|
|
|
}
|
2017-01-26 09:03:53 -05:00
|
|
|
return {
|
|
|
|
contents: lines.join('\n'),
|
2017-03-27 03:57:23 -04:00
|
|
|
regions: mapObject(regionMap, (regionName, region) => leftAlign(region.lines).join('\n'))
|
2017-01-26 09:03:53 -05:00
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return {contents, regions: {}};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-02 03:19:20 -05:00
|
|
|
function getRegionNames(input) {
|
2017-02-21 12:54:57 -05:00
|
|
|
return (input.trim() === '') ? [] : input.split(',').map(name => name.trim());
|
2017-01-26 09:03:53 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function removeLast(array, item) {
|
|
|
|
const index = array.lastIndexOf(item);
|
|
|
|
array.splice(index, 1);
|
|
|
|
}
|
|
|
|
|
2017-03-27 03:57:23 -04:00
|
|
|
function leftAlign(lines) {
|
|
|
|
let indent = Number.MAX_VALUE;
|
|
|
|
lines.forEach(line => {
|
|
|
|
const lineIndent = line.search(/\S/);
|
|
|
|
if (lineIndent !== -1) {
|
|
|
|
indent = Math.min(lineIndent, indent);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return lines.map(line => line.substr(indent));
|
|
|
|
}
|
|
|
|
|
2017-02-21 02:25:27 -05:00
|
|
|
function RegionParserError(message, index) {
|
|
|
|
const lineNum = index + 1;
|
2017-01-26 09:03:53 -05:00
|
|
|
this.message = `regionParser: ${message} (at line ${lineNum}).`;
|
|
|
|
this.lineNum = lineNum;
|
|
|
|
this.stack = (new Error()).stack;
|
|
|
|
}
|
|
|
|
RegionParserError.prototype = Object.create(Error.prototype);
|
2017-04-01 14:34:10 -04:00
|
|
|
RegionParserError.prototype.constructor = RegionParserError;
|