Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support files listed in nonRenderableFiles with conditionalFiles #363

Merged
merged 7 commits into from
May 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Usage: cli [options] <asyncapi> <template>
Options:
-V, --version output the version number
-d, --disable-hook <hookType> disable a specific hook type
--debug enable more specific errors in the console. At the moment it only shows specific errors about filters. Keep in mind that as a result errors about template are less descriptive
--debug enable more specific errors in the console.
-i, --install installs the template and its dependencies (defaults to false)
-n, --no-overwrite <glob> glob or path of the file(s) to skip when regenerating
-o, --output <outputDir> directory where to put the generated files (defaults to current directory)
Expand Down
2 changes: 1 addition & 1 deletion cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ program
template = tmpl;
})
.option('-d, --disable-hook <hookType>', 'disable a specific hook type', disableHooksParser)
.option('--debug', 'enable more specific errors in the console. At the moment it only shows specific errors about filters. Keep in mind that as a result errors about template are less descriptive')
.option('--debug', 'enable more specific errors in the console')
.option('-i, --install', 'installs the template and its dependencies (defaults to false)')
.option('-n, --no-overwrite <glob>', 'glob or path of the file(s) to skip when regenerating', noOverwriteParser)
.option('-o, --output <outputDir>', 'directory where to put the generated files (defaults to current directory)', parseOutput, process.cwd())
Expand Down
20 changes: 10 additions & 10 deletions docs/authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,9 @@ The `generator` property from `package.json` file must contain a JSON object tha
|`parameters[param].description`| String | A user-friendly description about the parameter.
|`parameters[param].default`| Any | Default value of the parameter if not specified. Shouldn't be used for mandatory `required=true` parameters.
|`parameters[param].required`| Boolean | Whether the parameter is required or not.
|`conditionalFiles`| Object[String, Object] | An object containing all the file paths that should be conditionally rendered. Each key represents a file path and each value must be an object with the keys `subject` and `validation`.
|`conditionalFiles[filePath].subject`| String | The `subject` is a [JMESPath](http://jmespath.org/) query to grab the value you want to apply the condition to. It queries an object with the whole AsyncAPI document and, when specified, the given server. The object looks like this: `{ asyncapi: { ... }, server: { ... } }`.
|`conditionalFiles[filePath].validation`| Object | The `validation` is a JSON Schema Draft 07 object. This JSON Schema definition will be applied to the JSON value resulting from the `subject` query. If validation doesn't have errors, the condition is met, and therefore the given file will be rendered. Otherwise, the file is ignored.
|`conditionalFiles`| Object[String, Object] | An object containing all the file paths that should be conditionally rendered. Each key represents a file path and each value must be an object with the keys `subject` and `validation`. The file path should be relative to the `template` directory inside the template.
|`conditionalFiles[filePath].subject`| String | The `subject` is a [JMESPath](http://jmespath.org/) query to grab the value you want to apply the condition to. It queries an object with the whole AsyncAPI document and, when specified, the given server. The object looks like this: `{ asyncapi: { ... }, server: { ... } }`. If template supports `server` parameter, you can access server details like for example protocol this way: `server.protocol`. During validation with `conditionalFiles` only the server that template user selected is available in the specification file. For more information about `server` parameter [read about special parameters](#special-parameters).
|`conditionalFiles[filePath].validation`| Object | The `validation` is a JSON Schema Draft 07 object. This JSON Schema definition will be applied to the JSON value resulting from the `subject` query. If validation doesn't have errors, the condition is met, and therefore the given file will be rendered. Otherwise, the file is ignored. Check [JSON Schema Validation](https://json-schema.org/draft-07/json-schema-validation.html#rfc.section.6) document for a list of all possible validation keywords.
|`nonRenderableFiles`| [String] | A list of file paths or [globs](https://en.wikipedia.org/wiki/Glob_(programming)) that must be copied "as-is" to the target directory, i.e., without performing any rendering process. This is useful when you want to copy binary files.
|`generator`| [String] | A string representing the Generator version-range the template is compatible with. This value must follow the [semver](https://docs.npmjs.com/misc/semver) syntax. E.g., `>=1.0.0`, `>=1.0.0 <=2.0.0`, `~1.0.0`, `^1.0.0`, `1.0.0`, etc.
|`filters`| [String] | A list of modules containing functions that can be used as Nunjucks filters. In case of external modules, remember they need to be added as a dependency in `package.json` of your template.
Expand All @@ -211,17 +211,17 @@ The `generator` property from `package.json` file must contain a JSON object tha
}
},
"conditionalFiles": {
"src/api/adapters/amqp.js": {
"path/to/file/that/is/relative/to/template/dir/test-amqp.js": {
"subject": "server.protocol",
"validation": {
"const": "amqp"
}
},
"src/api/adapters/mqtt.js": {
"subject": "server.protocol",
"validation": {
"const": "mqtt"
}
"path/to/file/that/is/relative/to/template/dir/support.html": {
"subject": "info.contact",
"validation": {
"required": ["url"]
}
}
},
"nonRenderableFiles": [
Expand All @@ -244,4 +244,4 @@ There are some template parameters that have a special meaning:

|Name|Description|
|---|---|
|`server`| It is used to let the template know which server you want to use. In some cases, this may be required. For instance, when generating code that connects to a specific server. If your template need your users to specify a server, use this parameter.
|`server`| It is used to let the template know which server from the AsyncAPI specification file you want to use. In some cases, this may be required. For instance, when generating code that connects to a specific server. Use this parameter in case your template relies on users' information about what server from the specification file they want to use during generation. You also need this parameter if you want to use `server.protocol` notation within `conditionalFiles` configuration option. Once you decide to specify this parameter for your template, it is recommended you make it a mandatory parameter otherwise feature like `conditionalFiles` is not going to work if your users do not use this parameter obligatory.
14 changes: 8 additions & 6 deletions lib/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const jmespath = require('jmespath');
const filenamify = require('filenamify');
const git = require('simple-git/promise');
const npmi = require('npmi');
const log = require('loglevel');
const { validateTemplateConfig } = require('./templateConfigValidator');
const {
convertMapToObject,
Expand Down Expand Up @@ -158,6 +159,8 @@ class Generator {
throw new Error('Parameter entrypoint is required when using output = "string"');
}

if (this.debug) log.setLevel('debug');
Copy link
Member

@Tenischev Tenischev May 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice idea! Could we pass debug flag into template? So in template we also could write some debug logs. (Ofc this suggestion is for separate issue)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you for sure have access to it in hooks out of the box, but filters and Nunjucks, that would be awesome, please write an issue for it


const { name: templatePkgName, path: templatePkgPath } = await this.installTemplate(this.install);
this.templateDir = templatePkgPath;
this.templateName = templatePkgName;
Expand Down Expand Up @@ -332,7 +335,7 @@ class Generator {
// The template name doesn't look like a file system path but we find
// that the package is already installed and it's a symbolic link.
const { resolvedLink } = await getLocalTemplateDetails(templatePath);
console.info(`This template has already been installed and it's pointing to your filesystem at ${resolvedLink}.`);
log.debug(`This template has already been installed and it's pointing to your filesystem at ${resolvedLink}.`);
}
installedPkg = require(path.resolve(templatePath, PACKAGE_JSON_FILENAME));
}
Expand Down Expand Up @@ -546,10 +549,6 @@ class Generator {

if (shouldIgnoreFile(relativeSourceFile)) return;

if (this.isNonRenderableFile(relativeSourceFile)) {
return await copyFile(sourceFile, targetFile);
}

const shouldOverwriteFile = await this.shouldOverwriteFile(relativeTargetFile);
if (!shouldOverwriteFile) return;

Expand All @@ -561,13 +560,16 @@ class Generator {
server: server ? server.json() : undefined,
},
}, this.templateConfig.conditionalFiles[relativeSourceFile].subject);

if (!source) return log.debug(`${relativeSourceFile} was not generated because ${this.templateConfig.conditionalFiles[relativeSourceFile].subject} specified in template configuration in conditionalFiles was not found in provided AsyncAPI specification file`);

if (source) {
const validate = this.templateConfig.conditionalFiles[relativeSourceFile].validate;
const valid = validate(source);
if (!valid) return;
if (!valid) return log.debug(`${relativeSourceFile} was not generated because condition specified for this file in template configuration in conditionalFiles matched`);
}
}
if (this.isNonRenderableFile(relativeSourceFile)) return await copyFile(sourceFile, targetFile);

const parsedContent = await this.renderFile(asyncapiDocument, sourceFile);
const stats = fs.statSync(sourceFile);
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@
"fs.extra": "^1.3.2",
"jmespath": "^0.15.0",
"js-yaml": "^3.13.1",
"levenshtein-edit-distance": "^2.0.5",
"loglevel": "^1.6.8",
"markdown-it": "^8.4.1",
"minimatch": "^3.0.4",
"node-fetch": "^2.6.0",
"npmi": "^4.0.0",
"nunjucks": "^3.2.0",
"semver": "^7.3.2",
"simple-git": "^1.131.0",
"levenshtein-edit-distance": "^2.0.5"
"simple-git": "^1.131.0"
},
"devDependencies": {
"@semantic-release/commit-analyzer": "^8.0.1",
Expand Down
3 changes: 3 additions & 0 deletions test/__mocks__/loglevel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const log = jest.genMockFromModule('loglevel');

module.exports = log;
7 changes: 4 additions & 3 deletions test/generator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
const fs = require('fs');
const path = require('path');
const Generator = require('../lib/generator');
const log = require('loglevel');

const streetlightYAML = fs.readFileSync(path.resolve(__dirname, './docs/streetlights.yml'), 'utf8');

Expand Down Expand Up @@ -343,13 +344,13 @@ describe('Generator', () => {
});

it('works with an npm package that has already been installed as a local template', async () => {
console.info = jest.fn();
log.debug = jest.fn();
utils.__isFileSystemPathValue = false;
utils.__isLocalTemplateValue = true;
utils.__getLocalTemplateDetailsResolvedLinkValue = '/path/to/template/nameOfTestTemplate';
const gen = new Generator('nameOfTestTemplate', __dirname);
const gen = new Generator('nameOfTestTemplate', __dirname, {debug: true});
await gen.installTemplate();
expect(console.info).toHaveBeenCalledWith('This template has already been installed and it\'s pointing to your filesystem at /path/to/template/nameOfTestTemplate.');
expect(log.debug).toHaveBeenCalledWith('This template has already been installed and it\'s pointing to your filesystem at /path/to/template/nameOfTestTemplate.');
expect(npmiMock).toHaveBeenCalledTimes(0);
});

Expand Down