├── bru-spec-v1.md
├── license.md
└── readme.md
/bru-spec-v1.md:
--------------------------------------------------------------------------------
1 | # Bru Lang (Specification Version 1.0)
2 |
3 | > [!NOTE]
4 | > This is a draft specification for Bru. It is not yet ratified.
5 |
6 | Bru is a data definition language optimized for use with [Bruno][] to describe
7 | an API request in a file.
8 |
9 | Here is an example `.bru` file:
10 |
11 | ```hjson
12 | http: {
13 | method: GET
14 | url: https://www.usebruno.com/hello
15 |
16 | headers: {
17 | Content-Type: application/json
18 | }
19 |
20 | body: {
21 | type: xml
22 | data: '''
23 |
24 | Bru
25 |
26 | '''
27 | }
28 | }
29 | ```
30 |
31 | Bru is:
32 |
33 | - Human-readable. There is no "compact" representation. However there is
34 | a canonical format.
35 |
36 | - Information-dense. The supported map structure is a [multimap][] allowing
37 | duplicate keys.
38 |
39 | - Extensible. It supports an annotation format that consumers of Bru can use to
40 | enforce particular values or provide extra functionality.
41 |
42 | The syntax has some similarities to [Hjson][].
43 |
44 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
45 | "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
46 | interpreted as described in [RFC 2119][rfc2119].
47 |
48 | ## File Extension and Proposed Media Type
49 |
50 | Bru language files have the media type `application/bruno+bru` (currently
51 | unregistered).
52 |
53 | Bru language files SHOULD have the extension `.bru`.
54 |
55 | ## Data Types
56 |
57 | Bru supports four primitive data types and three composite data types.
58 |
59 | - null
60 | - boolean
61 | - number
62 | - string
63 | - [multistring][]
64 | - [array][]
65 | - [multimap][]
66 |
67 | ```ebnf
68 | VALUE ::= NULL | BOOLEAN | NUMBER | STRING | MULTISTRING | ARRAY | MULTIMAP
69 | ```
70 |
71 | ### Null
72 |
73 | Null is an explicit non-value, written as `null`.
74 |
75 | ```ebnf
76 | NULL ::= 'null'
77 | ```
78 |
79 | ### Boolean
80 |
81 | There are two boolean values, `true` and `false`.
82 |
83 | ```ebnf
84 | BOOLEAN ::= 'true' | 'false'
85 | ```
86 |
87 | ### Number
88 |
89 | > [!TIP]
90 | > In most contexts, bare numeric values will be treated as numbers. If the value
91 | > `200` should be stored as a string, it MUST be quoted (`"200"` or `'200'`).
92 |
93 | Numbers are integer or floating point values. Numbers MAY begin with a sign and
94 | MUST contain at least one ASCII digit. Numbers MAY have a fractional part that
95 | MUST start with the ASCII decimal point (`.`) and be followed by at least one
96 | ASCII digit. Numbers MAY have an exponent part that MUST start with the ASCII
97 | letter `e` or `E`, MAY have a sign, and MUST have at least one ASCII digit.
98 | Numbers must match the regular expression
99 | `^[-+]?[0-9]+(?:\.[0-9]+)?(?:e[-+]?[0-9]+)`.
100 |
101 | ```ebnf
102 | DIGIT ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
103 | SIGN ::= '-' | '+'
104 | EXP ::= ('e' | 'E') SIGN? DIGIT+
105 | INTEGER ::= SIGN? DIGIT+
106 | FRACTION ::= '.' DIGIT+
107 | NUMBER ::= INTEGER FRACTION? EXP?
108 | ```
109 |
110 | All whitespace surrounding a numeric value SHALL be ignored. Whitespace within
111 | a numeric value MUST prevent parsing the value as a number.
112 |
113 | Bru parsers SHOULD retain the string representation of a number value and SHOULD
114 | prefer the string representation of the value if there is a loss of fidelity. If
115 | the parsed value cannot be reserialized without a loss of fidelity, it should be
116 | retained as a string and the parsed value discarded.
117 |
118 | > [!NOTE]
119 | > In JavaScript, this usually means that the value is between
120 | > `Number.MIN_SAFE_INTEGER` and `Number.MAX_SAFE_INTEGER`
121 |
122 | ### String
123 |
124 | A Bru string may be quoted with paired single quotes (`'`) or double quotes
125 | (`"`), but most strings do _not_ need to be quoted if they can unambiguously be
126 | interpreted as a string.
127 |
128 | If a string is unquoted, whitespace _around_ the string is trimmed, but
129 | whitespace _within_ the string are not. That is,
130 |
131 | ```
132 | hello, world
133 | ```
134 |
135 | will become `hello, world`.
136 |
137 | Strings MUST be quoted if leading or trailing whitespace is significant, begin
138 | with any of the characters `{}[],:'"#` or contains a comma (`,`).
139 |
140 | > [!NOTE]
141 | > If the string should be a string version of a number, `true`, `false`, or
142 | > `null`, it MUST be quoted or the parser MAY treat the value as the primitive
143 | > data type. That is, `key: null` means a key with a `null` value, but `key:
144 | "null"` means a key with a string value of `"null"`.
145 |
146 | Within quoted strings, the backslash (`\`) SHALL act as an escape character as
147 | with JSON strings. Unquoted strings MUST NOT treat the backslash (`\`) as an
148 | escape, so `\t` is treated as `'\\t'`.
149 |
150 | ### Multistring
151 |
152 | Bru supports multi-line strings with either paired triple single quotes (`'''`)
153 | or triple double quotes (`"""`). The contents of multistrings MUST be indented
154 | an additional level, and the closing triple quotes MUST match the indentation of
155 | the line containing the opening quotes.
156 |
157 | Multistring quotes MUST NOT be followed by any non-whitespace character. Closing
158 | multistring quotes MUST be preceded only by whitespace.
159 |
160 | A Bru parser MUST suspend normal parsing until the matching closing triple quote
161 | at the same indentation level is found. No Bru language constructs will be
162 | matched within a multistring.
163 |
164 | Within a multistring, the leading whitespace up to the correct indentation level
165 | will be trimmed, but all other whitespace SHALL be preserved.
166 |
167 | Examples for this should be clearer:
168 |
169 | ```
170 | {
171 | key: '''
172 | the string begins two spaces more than key
173 | newlines are preserved, except for the
174 | last line. If the final newline is required,
175 | leave a blank line.
176 |
177 | '''
178 |
179 | array: [
180 | '''
181 | Multistrings are permitted in arrays,
182 | too.
183 | This indentation is kept, but not the newline.
184 | '''
185 |
186 | '''
187 | '''
188 | This contains a multistring within a multistring,
189 | but it is just a string.
190 | '''
191 | '''
192 | ]
193 | }
194 | ```
195 |
196 | The equivalent values in JSON would be:
197 |
198 | ```json
199 | {
200 | "key": "the string begins two spaces more than key\nnewlines are preserved, except for the\nlast line. If the final newline is required,\nleave a blank line.\n",
201 | "extra": "extra line",
202 | "array": [
203 | "Multistrings are permitted in arrays,\ntoo.\n This indentation is kept, but not the newline.",
204 | "'''\n This contains a multistring within a multistring,\n but it is just a string.\n'''"
205 | ]
206 | }
207 | ```
208 |
209 | #### Array
210 |
211 | An array is a list of any Bru data type. It MUST be enclosed in square brackets
212 | (`[`, `]`). Each entry MUST start on its own line, separated by newlines. String
213 | values in an array follow [string](#String) quoting rules.
214 |
215 | A Bru parser MUST ignore blank lines between entries (`null` values MUST be
216 | explicitly entered).
217 |
218 | Commas MAY be used to separate array entries written over multiple lines, but
219 | their use MUST shift the parser so that commas MUST be used for all entries.
220 | This permits table-style arrays:
221 |
222 | An empty array MAY be written as `[]`.
223 |
224 | ```
225 | {
226 | array: [
227 | 1
228 | 2
229 | 3
230 | mixed values
231 | "[are okay]"
232 |
233 | {
234 | as: {
235 | are: [
236 | composite
237 | objects
238 | ]
239 | }
240 | }
241 |
242 | [
243 | or
244 | arrays
245 | ]
246 |
247 | null
248 | ]
249 |
250 | empty: []
251 | }
252 | ```
253 |
254 | #### Multimap
255 |
256 | A dictionary allowing duplicate keys. It MUST be enclosed in curly braces
257 | (`{}`). Key and value pairs are each placed on a new line with keys separated
258 | from values by a single colon (`:`).
259 |
260 | Keys MAY be unquoted if they match the pattern `^[_a-zA-Z][-_a-zA-Z0-9]*$`. All
261 | other keys must be quoted. That is, `Content-Type` is a valid unquoted key, but
262 | `"Content:Type"` or `"-Content-Type"` must be quoted.
263 |
264 | Values MAY be any valid Bru data type. Composite data type delimiters _must_
265 | follow the key.
266 |
267 | Some application contexts (such as HTTP headers or query parameters) MAY apply
268 | additional restrictions on valid keys or value types.
269 |
270 | An empty multimap MAY be written as `{}`.
271 |
272 | ```hjson
273 | {
274 | name: Bru
275 | social: {
276 | github: https://github.com/usebruno/bru-lang
277 | twitter: https://twitter.com/use_bruno
278 | }
279 | }
280 | ```
281 |
282 | The top level of a Bru document is an implicit multimap that does not require
283 | braces.
284 |
285 | ```hjson
286 | http: {
287 | headers: {
288 | # ...
289 | }
290 | query: {
291 | # ...
292 | }
293 | }
294 | ```
295 |
296 | The absence of a value following a key before the end of the line will result in
297 | an empty string, making this:
298 |
299 | ```hjson
300 | {
301 | name:
302 | social: {
303 | }
304 | }
305 | ```
306 |
307 | equivalent to:
308 |
309 | ```json
310 | {
311 | "name": "",
312 | "social": {}
313 | }
314 | ```
315 |
316 | ### Annotations
317 |
318 | Annotations are used to provide additional information about a key-value pair
319 | instance within a multimap.
320 |
321 | An annotation MUST be on a single line preceding the key and MUST start with
322 | `@` and end at the next newline. The name of an annotation MUST match the
323 | pattern `^[_a-zA-Z][-_a-zA-Z0-9]*$`.
324 |
325 | Annotations MAY have arguments which MUST be wrapped in parentheses (`()`).
326 | Multiple arguments MUST be separated by commas `,`. Annotation arguments MUST be
327 | primitive types (`null`, `boolean`, `string`, or `number`). Annotation string
328 | arguments SHOULD be quoted.
329 |
330 | ```groovy
331 | http: {
332 | method: GET
333 | url: https://www.usebruno.com/hello
334 | headers: {
335 | Content-Type: application/json
336 |
337 | @disabled
338 | @description(This is a sample request)
339 | Authorization: Bearer {{token}}
340 | }
341 | param: {
342 | query: {
343 | @description('The status of the user')
344 | @enum('active', 'inactive')
345 | status: 'active'
346 | }
347 | }
348 | }
349 | ```
350 |
351 | ## Basic Formatting (Whitespace and Indentation)
352 |
353 | Bru is block-oriented and uses both delimiters and indentation to ensure
354 | correctness. When a new block context is started, the contents of the block MUST
355 | be indented to the next level. Each level of indentation in Bru is signified
356 | with two ASCII space characters (`0x20`).
357 |
358 | Interior whitespace is not considered significant in Bru, except as noted for
359 | [string](#String) and [multistring](#Multistring) values.
360 |
361 | Blank lines MUST be ignored except as noted for [multistring](#multistring)
362 | values.
363 |
364 | Bru parsers MUST treat Windows line endings (`CRLF`) as equivalent to Unix line
365 | endings (`LF`).
366 |
367 | ## Comments
368 |
369 | Comments in Bru are line-oriented and MUST begin with the hash character (`#`),
370 | which MUST be preceded only by whitespace: a comment MUST be on its own line.
371 |
372 | If a comment marker is found on a line after a quoted string (`'string'` or
373 | `"string"`) or after syntactically significant value markers (`:{}[]`), a parser
374 | MUST treat that as a syntax error.
375 |
376 | ```
377 | http: {
378 | # this is a comment
379 | key: value # this part of the string value
380 | extra: # this is not a comment and causes a parsing error
381 | }
382 | ```
383 |
384 | Leading or trailing whitespace for comments SHALL be adjusted by a Bru formatter
385 | and a Bru parser MAY warn about comment-like lines.
386 |
387 | ## Serialization
388 |
389 | Bru files SHOULD be serialized using platform native line endings (`CRLF` on
390 | Windows, `LF` otherwise).
391 |
392 | ## Original Authors
393 |
394 | - [Anoop M D](https://github.com/helloanoop)
395 | - [Ajai Shankar](https://github.com/ajaishankar)
396 | - [Austin Ziegler](https://github.com/halostatue)
397 |
398 | [bruno]: https://www.usebruno.com
399 | [hjson]: https://hjson.github.io
400 | [json]: https://json.org
401 | [multimap]: https://en.wikipedia.org/wiki/Multimap
402 | [array]: https://en.wikipedia.org/wiki/Array_data_structure
403 | [multistring]: https://en.wikipedia.org/wiki/Here_document
404 | [rfc2119]: https://www.rfc-editor.org/rfc/rfc2119
405 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 |
2 | MIT License
3 |
4 | Copyright (c) 2024 Anoop M D and Contributors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Bru Lang
2 |
3 | Bru is a simple markup language with [JSON](https://json.org)-like semantics.
4 |
5 | It's currently used in [Bruno](https://www.usebruno.com) to save details of an api request in a file.
6 |
7 | Here is a sample `.bru` file:
8 | ```groovy
9 | http: {
10 | method: 'GET'
11 | url: 'https://www.usebruno.com/hello'
12 | headers: {
13 | Content-Type: 'application/json'
14 | }
15 | body: {
16 | type: 'xml'
17 | data: '''
18 |
19 | Bru
20 |
21 | '''
22 | }
23 | }
24 | ```
25 |
26 | ## Design Goals
27 | * Human readable
28 | * Easy to represent multi line strings
29 | * Support duplicate keys in dictionary - Multimap
30 | * Indentation based syntax
31 | * Annotations for providing additional information
32 |
33 | The top level of a `.bru` file is an implicit Multimap. It does not require
34 | or support braces.
35 |
36 | Except where noted, blank lines and whitespace are ignored.
37 |
38 | ## Data Types
39 |
40 | ### Primitive Types
41 | There are 4 primitive types in Bru.
42 | * String
43 | * Number
44 | * Boolean
45 | * Null
46 |
47 | The string type slightly differs from JSON in that a Bru string is
48 | a single-quoted and is always UTF-8. It may contain any printable UTF-8
49 | character except `'` or `\n`. Whitespace is considered significant inside of the
50 | quoted string.
51 |
52 | ### Composite Types
53 | There are 3 composite types in Bru.
54 | * [Multimap](https://en.wikipedia.org/wiki/Multimap)
55 | * [Array](https://en.wikipedia.org/wiki/Array_data_structure)
56 | * [Multiline String](https://en.wikipedia.org/wiki/Here_document)
57 |
58 | #### Multimap
59 | Multimap is a dictionary (key-value pair) with duplicate keys, enclosed in curly braces (`{}`). Keys and Values are separated by a colon `:` and key-value pairs are separated by a newline (`\n`).
60 |
61 | All keys are unquoted strings and must not contain spaces. Values may be primitive or composite types. Some contexts may offer further restrictions not required by the Bru language.
62 |
63 | ```groovy
64 | {
65 | name: 'Bru'
66 | social: {
67 | github: 'https://github.com/usebruno/bru-lang'
68 | twitter: 'https://twitter.com/use_bruno'
69 | }
70 | }
71 | ```
72 |
73 | #### Array
74 | Array is a list of values separated by a newline (`\n`), enclosed in square brackets (`[]`).
75 | ```groovy
76 | tags: [
77 | 'markup langauge'
78 | 'dsl'
79 | ]
80 | ```
81 |
82 | #### Multiline String
83 | Multiline string is a string that spans multiple lines, enclosed in triple single/double quotes (`'''`). The
84 | content *must* begin on the line after the opening triple quote and the
85 | closing triple quote *must* be on the line after the end of the multiline
86 | string. Multiline strings must be indented two spaces more than the key
87 | which contains them. This indentation is removed on parsing.
88 |
89 | ```groovy
90 | article: {
91 | title: 'Bru Lang'
92 | content: '''
93 | Bru is a simple markup language with json like semantics.
94 | It's currently used in Bruno to save details of an api request in a file.
95 | '''
96 | }
97 | ```
98 |
99 | In the above example, the content will be:
100 |
101 | ```text
102 | Bru is a simple markup language with json like semantics.
103 | It's currently used in Bruno to save details of an api request in a file.
104 | ```
105 |
106 | ### Annotations
107 | Annotations are used to provide additional information about a key-value pair. An annotation starts with (`@`) and ends with a newline (`\n`). Arguments may be passed to an annotation in parentheses (`()`) and separated by commas. Argument values may *only* be primitive types.
108 |
109 | ```groovy
110 | http: {
111 | method: 'GET'
112 | url: 'https://www.usebruno.com/hello'
113 | headers: {
114 | Content-Type: 'application/json'
115 |
116 | @disabled
117 | @description('This is a sample request')
118 | Authorization: 'Bearer{{token}}'
119 | }
120 | param: {
121 | query: {
122 | @description('The status of the user')
123 | @enum('active', 'inactive')
124 | status: 'active'
125 | }
126 | }
127 | }
128 | ```
129 |
130 |
131 | ### Comments
132 |
133 | Comments start with `#` and end with a newline `\n`.
134 | ```bash
135 | # This is a comment
136 |
137 | http: {
138 | # This is a comment, too
139 | }
140 | ```
141 |
142 | ### Original Authors
143 | * [Anoop M D](https://github.com/helloanoop)
144 | * [Ajai Shankar](https://github.com/ajaishankar)
145 | * [Austin Ziegler](https://github.com/halostatue)
--------------------------------------------------------------------------------