diff --git a/README.md b/README.md index 31068c2b..0467d75f 100644 --- a/README.md +++ b/README.md @@ -872,6 +872,76 @@ expression are also tagged. } ``` +## Blocked Properties with ⛔ +Properties whose names start with "⛔" are completely ignored during template processing. This is useful for: + +1. Adding notes or comments that should not be evaluated +2. Temporarily disabling parts of a template +3. Keeping sensitive or debug information in the template but preventing its evaluation + +When a property name starts with "⛔", the MetaInfoProducer will skip that property and all of its children, effectively making them invisible to the template processor. + +```json +> .init -f "example/ex25.json" +{ + "a": 42, + "⛔note": "${$string('should not be evaluated: ') & a}", + "c": "${a}" +} +> .out +{ + "a": 42, + "⛔note": "${$string('should not be evaluated: ') & a}", + "c": 42 +} +> .init -f "example/ex26.json" +{ + "a": 42, + "⛔note": "${$string('Will NOT be evaluated: ') & a}", + "note": "${$string('Will be evaluated: ') & a}", + "config": { + "⛔productionDb": "${$env('DB_PROD_URL', 'mysql://prod.example.com:3306')}", + "productionDb": "${$env('DB_PROD_URL', 'mysql://prod.example.com:3306')}", + "localDb": "mysql://localhost:3306" + }, + "features": { + "⛔experimental": { + "enabled": true, + "endpoint": "${$string('https://api.example.com/config/') & 'experimental'}" + }, + "experimental": { + "enabled": true, + "endpoint": "${$string('https://api.example.com/config/') & 'experimental'}" + } + } +} +> .out +{ + "a": 42, + "⛔note": "${$string('Will NOT be evaluated: ') & a}", + "note": "Will be evaluated: 42", + "config": { + "⛔productionDb": "${$env('DB_PROD_URL', 'mysql://prod.example.com:3306')}", + "productionDb": "mysql://prod.example.com:3306", + "localDb": "mysql://localhost:3306" + }, + "features": { + "⛔experimental": { + "enabled": true, + "endpoint": "${$string('https://api.example.com/config/') & 'experimental'}" + }, + "experimental": { + "enabled": true, + "endpoint": "https://api.example.com/config/experimental" + } + } +} +``` + +In these examples, `⛔...` will be completely ignored during template processing - the expression will not be evaluated. + +Note that array elements with "⛔" in their values will still be processed, as array indices are numbers, not property names. Only object properties that start with "⛔" are blocked. + # Generative Templates Templates can contain generative expressions that cause their content to change over time. For instance the `$setInterval` function behaves exactly as it does in Javascript. Below, diff --git a/example/ex25.json b/example/ex25.json new file mode 100644 index 00000000..9b022603 --- /dev/null +++ b/example/ex25.json @@ -0,0 +1,5 @@ +{ + "a": 42, + "⛔note": "${$string('should not be evaluated: ') & a}", + "c": "${a}" +} \ No newline at end of file diff --git a/example/ex26.json b/example/ex26.json new file mode 100644 index 00000000..e189abd8 --- /dev/null +++ b/example/ex26.json @@ -0,0 +1,20 @@ +{ + "a": 42, + "⛔note": "${$string('Will NOT be evaluated: ') & a}", + "note": "${$string('Will be evaluated: ') & a}", + "config": { + "⛔productionDb": "${$env('DB_PROD_URL', 'mysql://prod.example.com:3306')}", + "productionDb": "${$env('DB_PROD_URL', 'mysql://prod.example.com:3306')}", + "localDb": "mysql://localhost:3306" + }, + "features": { + "⛔experimental": { + "enabled": true, + "endpoint": "${$string('https://api.example.com/config/') & 'experimental'}" + }, + "experimental": { + "enabled": true, + "endpoint": "${$string('https://api.example.com/config/') & 'experimental'}" + } + } +} \ No newline at end of file diff --git a/src/MetaInfoProducer.ts b/src/MetaInfoProducer.ts index 9cf9db1e..c68aeb69 100644 --- a/src/MetaInfoProducer.ts +++ b/src/MetaInfoProducer.ts @@ -57,6 +57,8 @@ export default class MetaInfoProducer { const emit: MetaInfo[] = []; async function getPaths(o:any, path: JsonPointerStructureArray = [], isTemp=false) { + if (String(path.at(-1))[0] === '⛔') return; + const type = typeof o; const metaInfo: MetaInfo = { "materialized__": true, diff --git a/src/test/MetaInfoProducer.test.js b/src/test/MetaInfoProducer.test.js index 19b3a672..e6b2c8d3 100644 --- a/src/test/MetaInfoProducer.test.js +++ b/src/test/MetaInfoProducer.test.js @@ -1117,5 +1117,261 @@ test("temp vars 3", async () => { ]); }); +test("blocked paths with ⛔ character 1", async () => { + const template = { + "a": 42, + "⛔b": "Should be ignored", + "c": "${a}" + }; + const metaInfos = await MetaInfoProducer.getMetaInfos(template); + expect(JSON.parse(stringify(metaInfos))).toEqual([ + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "jsonPointer__": [ + "a" + ], + "materialized__": true, + "parent__": [], + "tags__": [], + "temp__": false, + "treeHasExpressions__": false + }, + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "exprRootPath__": null, + "exprTargetJsonPointer__": [], + "expr__": "a", + "jsonPointer__": [ + "c" + ], + "materialized__": true, + "parent__": [], + "tags__": [], + "temp__": false, + "treeHasExpressions__": true + }, + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "jsonPointer__": [], + "materialized__": true, + "parent__": [], + "tags__": [], + "temp__": false, + "treeHasExpressions__": true + } + ]); +}); + +test("blocked paths with ⛔ character 2", async () => { + const template = { + "a": 42, + "b": { + "normal": "value", + "⛔secret": { + "nested": "This should be ignored" + } + }, + "c": "${a}" + }; + const metaInfos = await MetaInfoProducer.getMetaInfos(template); + expect(JSON.parse(stringify(metaInfos))).toEqual([ + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "jsonPointer__": [ + "a" + ], + "materialized__": true, + "parent__": [], + "tags__": [], + "temp__": false, + "treeHasExpressions__": false + }, + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "jsonPointer__": [ + "b", + "normal" + ], + "materialized__": true, + "parent__": [ + "b" + ], + "tags__": [], + "temp__": false, + "treeHasExpressions__": false + }, + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "jsonPointer__": [ + "b" + ], + "materialized__": true, + "parent__": [], + "tags__": [], + "temp__": false, + "treeHasExpressions__": false + }, + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "exprRootPath__": null, + "exprTargetJsonPointer__": [], + "expr__": "a", + "jsonPointer__": [ + "c" + ], + "materialized__": true, + "parent__": [], + "tags__": [], + "temp__": false, + "treeHasExpressions__": true + }, + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "jsonPointer__": [], + "materialized__": true, + "parent__": [], + "tags__": [], + "temp__": false, + "treeHasExpressions__": true + } + ]); +}); + +test("blocked paths with ⛔ character in array", async () => { + const template = { + "a": 42, + "arr": [ + "normal", + "⛔blocked", // This should be included since array indices are numbers, not strings with ⛔ + { + "⛔key": "blocked object key", + "normal": "normal object key" + } + ] + }; + const metaInfos = await MetaInfoProducer.getMetaInfos(template); + expect(JSON.parse(stringify(metaInfos))).toEqual([ + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "jsonPointer__": [ + "a" + ], + "materialized__": true, + "parent__": [], + "tags__": [], + "temp__": false, + "treeHasExpressions__": false + }, + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "jsonPointer__": [ + "arr", + 0 + ], + "materialized__": true, + "parent__": [ + "arr" + ], + "tags__": [], + "temp__": false, + "treeHasExpressions__": false + }, + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "jsonPointer__": [ + "arr", + 1 + ], + "materialized__": true, + "parent__": [ + "arr" + ], + "tags__": [], + "temp__": false, + "treeHasExpressions__": false + }, + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "jsonPointer__": [ + "arr", + 2, + "normal" + ], + "materialized__": true, + "parent__": [ + "arr", + 2 + ], + "tags__": [], + "temp__": false, + "treeHasExpressions__": false + }, + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "jsonPointer__": [ + "arr", + 2 + ], + "materialized__": true, + "parent__": [ + "arr" + ], + "tags__": [], + "temp__": false, + "treeHasExpressions__": false + }, + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "jsonPointer__": [ + "arr" + ], + "materialized__": true, + "parent__": [], + "tags__": [], + "temp__": false, + "treeHasExpressions__": false + }, + { + "absoluteDependencies__": [], + "dependees__": [], + "dependencies__": [], + "jsonPointer__": [], + "materialized__": true, + "parent__": [], + "tags__": [], + "temp__": false, + "treeHasExpressions__": false + } + ]); +}); +