├── 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) --------------------------------------------------------------------------------