Skip to content

Commit 652f3cc

Browse files
authored
feat: basic annotation syntax support for lang (#7609)
* chore: basic annotation support * chore: string and escape cases * feat: add basic multiline support * fix: simplify dedentation logic in annotation multiline text block * chore: fix asserts * feat: add annotation support to env and collection * feat: enhance annotation parsing with support for quoted arguments and nested parentheses * refactor: feedback, remove inline annotations * feat: move serializeAnnotations function to utils and update imports
1 parent aa7b8f4 commit 652f3cc

File tree

9 files changed

+1143
-80
lines changed

9 files changed

+1143
-80
lines changed

packages/bruno-lang/v2/src/bruToJson.js

Lines changed: 92 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ const _ = require('lodash');
33
const { safeParseJson, outdentString } = require('./utils');
44
const parseExample = require('./example/bruToJson');
55

6+
// this is done to avoid breaking existing pairlist mapping so
7+
// the key is hidden and not added into the json automatically
8+
const ANNOTATIONS_KEY = Symbol('annotations');
9+
610
/**
711
* A Bru file is made up of blocks.
812
* There are three types of blocks
@@ -55,10 +59,27 @@ const grammar = ohm.grammar(`Bru {
5559
multilinetextblock = multilinetextblockdelimiter (~multilinetextblockdelimiter any)* multilinetextblockdelimiter st* contenttypeannotation?
5660
contenttypeannotation = "@contentType(" (~")" any)* ")"
5761
62+
// Annotation support (decorators on pairs)
63+
annotationname = annotationchar+
64+
annotationchar = ~("(" | ")" | " " | "\\t" | "\\r" | "\\n" | ":") any
65+
annotationsinglequotedargchar = ~"'" any
66+
annotationsinglequotedarg = "'" annotationsinglequotedargchar* "'"
67+
annotationdoublequotedargchar = ~"\\"" any
68+
annotationdoublequotedarg = "\\"" annotationdoublequotedargchar* "\\""
69+
annotationunquotedargchar = ~")" any
70+
annotationunquotedarg = annotationunquotedargchar*
71+
annotationargvalue = annotationsinglequotedarg | annotationdoublequotedarg | annotationunquotedarg
72+
annotationmultilinetextblock = multilinetextblockdelimiter (~multilinetextblockdelimiter any)* multilinetextblockdelimiter
73+
annotationargscontents = annotationmultilinetextblock | annotationargvalue
74+
annotationargs = "(" annotationargscontents ")"
75+
annotation = "@" annotationname annotationargs?
76+
annotationentry = st* annotation ~":" st* nl
77+
pairannotations = annotationentry*
78+
5879
// Dictionary Blocks
59-
dictionary = st* "{" pairlist? tagend
80+
dictionary = st* "{" st* pairlist? tagend
6081
pairlist = optionalnl* pair (~tagend stnl* pair)* (~tagend space)*
61-
pair = st* (quoted_key | key) st* ":" st* value st*
82+
pair = st* pairannotations st* (quoted_key | key) st* ":" st* value st*
6283
disable_char = "~"
6384
quote_char = "\\""
6485
esc_char = "\\\\"
@@ -72,7 +93,7 @@ const grammar = ohm.grammar(`Bru {
7293
// Dictionary for Assert Block
7394
assertdictionary = st* "{" assertpairlist? tagend
7495
assertpairlist = optionalnl* assertpair (~tagend stnl* assertpair)* (~tagend space)*
75-
assertpair = st* assertkey st* ":" st* value st*
96+
assertpair = st* pairannotations st* assertkey st* ":" st* value st*
7697
assertkey = ~tagend assertkeychar*
7798
assertkeychar = ~(tagend | nl | ":") any
7899
@@ -168,12 +189,12 @@ const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => {
168189
return _.map(pairList[0], (pair) => {
169190
let name = _.keys(pair)[0];
170191
let value = pair[name];
192+
const rawAnnotations = pair[ANNOTATIONS_KEY];
171193

172194
if (!parseEnabled) {
173-
return {
174-
name,
175-
value
176-
};
195+
const result = { name, value };
196+
if (rawAnnotations && rawAnnotations.length) result.annotations = rawAnnotations;
197+
return result;
177198
}
178199

179200
let enabled = true;
@@ -182,11 +203,9 @@ const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => {
182203
enabled = false;
183204
}
184205

185-
return {
186-
name,
187-
value,
188-
enabled
189-
};
206+
const result = { name, value, enabled };
207+
if (rawAnnotations && rawAnnotations.length) result.annotations = rawAnnotations;
208+
return result;
190209
});
191210
};
192211

@@ -197,18 +216,16 @@ const mapRequestParams = (pairList = [], type) => {
197216
return _.map(pairList[0], (pair) => {
198217
let name = _.keys(pair)[0];
199218
let value = pair[name];
219+
const rawAnnotations = pair[ANNOTATIONS_KEY];
200220
let enabled = true;
201221
if (name && name.length && name.charAt(0) === '~') {
202222
name = name.slice(1);
203223
enabled = false;
204224
}
205225

206-
return {
207-
name,
208-
value,
209-
enabled,
210-
type
211-
};
226+
const result = { name, value, enabled, type };
227+
if (rawAnnotations && rawAnnotations.length) result.annotations = rawAnnotations;
228+
return result;
212229
});
213230
};
214231

@@ -346,19 +363,67 @@ const sem = grammar.createSemantics().addAttribute('ast', {
346363
{}
347364
);
348365
},
349-
dictionary(_1, _2, pairlist, _3) {
366+
dictionary(_1, _2, _3, pairlist, _4) {
350367
return pairlist.ast;
351368
},
352369
pairlist(_1, pair, _2, rest, _3) {
353370
return [pair.ast, ...rest.ast];
354371
},
355-
pair(_1, key, _2, _3, _4, value, _5) {
372+
pairannotations(entries) {
373+
return entries.ast;
374+
},
375+
annotationentry(_1, annotation, _2, _3) {
376+
return annotation.ast;
377+
},
378+
annotation(_at, name, argsIter) {
379+
const annotObj = { name: name.ast };
380+
const argsArr = argsIter.ast;
381+
if (argsArr.length > 0) {
382+
annotObj.value = argsArr[0];
383+
}
384+
return annotObj;
385+
},
386+
annotationname(chars) {
387+
return chars.sourceString;
388+
},
389+
annotationsinglequotedarg(_open, chars, _close) {
390+
return chars.sourceString;
391+
},
392+
annotationdoublequotedarg(_open, chars, _close) {
393+
return chars.sourceString;
394+
},
395+
annotationunquotedarg(chars) {
396+
return chars.sourceString;
397+
},
398+
annotationargvalue(alt) {
399+
return alt.ast;
400+
},
401+
annotationmultilinetextblock(_1, content, _2) {
402+
const lines = content.sourceString.split('\n');
403+
// NOTE: the number 4 is taken from the `multilinetextblock` implementation
404+
let minIndent = 4;
405+
const dedented = lines.map((line) => (line.trim() === '' ? '' : line.substring(minIndent)));
406+
if (dedented.length > 0 && dedented[0] === '') dedented.shift();
407+
if (dedented.length > 0 && dedented[dedented.length - 1] === '') dedented.pop();
408+
return dedented.join('\n');
409+
},
410+
annotationargscontents(alt) {
411+
return alt.ast;
412+
},
413+
annotationargs(_open, value, _close) {
414+
return value.ast;
415+
},
416+
pair(_1, annotations, _keyindent, key, _2, _3, _4, value, _5) {
356417
let res = {};
357418
if (Array.isArray(value.ast)) {
358419
res[key.ast] = value.ast;
359-
return res;
420+
} else {
421+
res[key.ast] = value.ast ? value.ast.trim() : '';
422+
}
423+
const annotationList = annotations.ast;
424+
if (annotationList && annotationList.length > 0) {
425+
res[ANNOTATIONS_KEY] = annotationList;
360426
}
361-
res[key.ast] = value.ast ? value.ast.trim() : '';
362427
return res;
363428
},
364429
esc_quote_char(_1, quote) {
@@ -378,9 +443,13 @@ const sem = grammar.createSemantics().addAttribute('ast', {
378443
assertpairlist(_1, pair, _2, rest, _3) {
379444
return [pair.ast, ...rest.ast];
380445
},
381-
assertpair(_1, key, _2, _3, _4, value, _5) {
446+
assertpair(_1, annotations, _2, key, _3, _4, _5, value, _6) {
382447
let res = {};
383448
res[key.ast] = value.ast ? value.ast.trim() : '';
449+
const annotationList = annotations.ast;
450+
if (annotationList && annotationList.length > 0) {
451+
res[ANNOTATIONS_KEY] = annotationList;
452+
}
384453
return res;
385454
},
386455
assertkey(chars) {

packages/bruno-lang/v2/src/collectionBruToJson.js

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ const ohm = require('ohm-js');
22
const _ = require('lodash');
33
const { safeParseJson, outdentString } = require('./utils');
44

5+
// this is done to avoid breaking existing pairlist mapping so
6+
// the key is hidden and not added into the json automatically
7+
const ANNOTATIONS_KEY = Symbol('annotations');
8+
59
const grammar = ohm.grammar(`Bru {
610
BruFile = (meta | query | headers | auth | auths | vars | script | tests | docs)*
711
auths = authawsv4 | authbasic | authbearer | authdigest | authNTLM | authOAuth1 | authOAuth2 | authwsse | authapikey | authOauth2Configs
@@ -24,10 +28,27 @@ const grammar = ohm.grammar(`Bru {
2428
multilinetextblockdelimiter = "'''"
2529
multilinetextblock = multilinetextblockdelimiter (~multilinetextblockdelimiter any)* multilinetextblockdelimiter
2630
31+
// Annotation support (decorators on pairs)
32+
annotationname = annotationchar+
33+
annotationchar = ~("(" | ")" | " " | "\\t" | "\\r" | "\\n" | ":") any
34+
annotationsinglequotedargchar = ~"'" any
35+
annotationsinglequotedarg = "'" annotationsinglequotedargchar* "'"
36+
annotationdoublequotedargchar = ~"\\"" any
37+
annotationdoublequotedarg = "\\"" annotationdoublequotedargchar* "\\""
38+
annotationunquotedargchar = ~")" any
39+
annotationunquotedarg = annotationunquotedargchar*
40+
annotationargvalue = annotationsinglequotedarg | annotationdoublequotedarg | annotationunquotedarg
41+
annotationmultilinetextblock = multilinetextblockdelimiter (~multilinetextblockdelimiter any)* multilinetextblockdelimiter
42+
annotationargscontents = annotationmultilinetextblock | annotationargvalue
43+
annotationargs = "(" annotationargscontents ")"
44+
annotation = "@" annotationname annotationargs?
45+
annotationentry = st* annotation ~":" st* nl
46+
pairannotations = annotationentry*
47+
2748
// Dictionary Blocks
2849
dictionary = st* "{" pairlist? tagend
2950
pairlist = optionalnl* pair (~tagend stnl* pair)* (~tagend space)*
30-
pair = st* (quoted_key | key) st* ":" st* value st*
51+
pair = st* pairannotations st* (quoted_key | key) st* ":" st* value st*
3152
disable_char = "~"
3253
quote_char = "\\""
3354
esc_char = "\\\\"
@@ -87,12 +108,12 @@ const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => {
87108
return _.map(pairList[0], (pair) => {
88109
let name = _.keys(pair)[0];
89110
let value = pair[name];
111+
const rawAnnotations = pair[ANNOTATIONS_KEY];
90112

91113
if (!parseEnabled) {
92-
return {
93-
name,
94-
value
95-
};
114+
const result = { name, value };
115+
if (rawAnnotations && rawAnnotations.length) result.annotations = rawAnnotations;
116+
return result;
96117
}
97118

98119
let enabled = true;
@@ -101,11 +122,11 @@ const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => {
101122
enabled = false;
102123
}
103124

104-
return {
105-
name,
106-
value,
107-
enabled
108-
};
125+
const result = { name, value, enabled };
126+
if (rawAnnotations && rawAnnotations.length) {
127+
result.annotations = rawAnnotations;
128+
}
129+
return result;
109130
});
110131
};
111132

@@ -143,9 +164,56 @@ const sem = grammar.createSemantics().addAttribute('ast', {
143164
pairlist(_1, pair, _2, rest, _3) {
144165
return [pair.ast, ...rest.ast];
145166
},
146-
pair(_1, key, _2, _3, _4, value, _5) {
167+
pairannotations(entries) {
168+
return entries.ast;
169+
},
170+
annotationentry(_1, annotation, _2, _3) {
171+
return annotation.ast;
172+
},
173+
annotation(_at, name, argsIter) {
174+
const annotObj = { name: name.ast };
175+
const argsArr = argsIter.ast;
176+
if (argsArr.length > 0) {
177+
annotObj.value = argsArr[0];
178+
}
179+
return annotObj;
180+
},
181+
annotationname(chars) {
182+
return chars.sourceString;
183+
},
184+
annotationsinglequotedarg(_open, chars, _close) {
185+
return chars.sourceString;
186+
},
187+
annotationdoublequotedarg(_open, chars, _close) {
188+
return chars.sourceString;
189+
},
190+
annotationunquotedarg(chars) {
191+
return chars.sourceString;
192+
},
193+
annotationargvalue(alt) {
194+
return alt.ast;
195+
},
196+
annotationmultilinetextblock(_1, content, _2) {
197+
const lines = content.sourceString.split('\n');
198+
let minIndent = 4;
199+
const dedented = lines.map((line) => (line.trim() === '' ? '' : line.substring(minIndent)));
200+
if (dedented.length > 0 && dedented[0] === '') dedented.shift();
201+
if (dedented.length > 0 && dedented[dedented.length - 1] === '') dedented.pop();
202+
return dedented.join('\n');
203+
},
204+
annotationargscontents(alt) {
205+
return alt.ast;
206+
},
207+
annotationargs(_open, value, _close) {
208+
return value.ast;
209+
},
210+
pair(_1, annotations, _2, key, _3, _4, _5, value, _6) {
147211
let res = {};
148212
res[key.ast] = value.ast ? value.ast.trim() : '';
213+
const annotationList = annotations.ast;
214+
if (annotationList && annotationList.length > 0) {
215+
res[ANNOTATIONS_KEY] = annotationList;
216+
}
149217
return res;
150218
},
151219
quoted_key(disabled, _1, chars, _2) {

0 commit comments

Comments
 (0)