├── testing ├── correct │ ├── empty.ura │ ├── unused_var.ura │ ├── empty_object_2.ura │ ├── importing │ │ ├── three.ura │ │ ├── one.ura │ │ ├── normal.ura │ │ ├── two.ura │ │ ├── with_variable.ura │ │ └── README.md │ ├── empty_object_3.ura │ ├── nan.ura │ ├── empty_object.ura │ ├── escape_sentence.ura │ ├── array_in_object.ura │ ├── bug_trailing_comma.ura │ ├── array_in_object_trailing_comma.ura │ ├── object_without_useless_line.ura │ ├── without_useless_line.ura │ ├── useless_line_in_the_middle_object.ura │ ├── useless_line_on_top.ura │ ├── useless_line_on_bottom.ura │ ├── useless_line_in_the_middle.ura │ ├── literal_string.ura │ ├── multiline_literal_string.ura │ ├── normal_object.ura │ ├── useless_line_on_both.ura │ ├── basic_string.ura │ ├── normal_variable.ura │ ├── object_with_comments.ura │ ├── useless_line_in_the_middle_object_complex.ura │ ├── normal.ura │ ├── multiline_basic_string.ura │ ├── with_comments.ura │ └── full.ura ├── ParseError │ ├── with_dots.ura │ ├── with_dashes.ura │ ├── invalid_object_1.ura │ ├── with_quotes.ura │ ├── invalid_import_1.ura │ ├── invalid_import_2.ura │ ├── invalid_variable_definition_1.ura │ ├── invalid_variable_definition_2.ura │ ├── invalid_variable_definition_3.ura │ ├── invalid_variable_definition_4.ura │ ├── invalid_object_2.ura │ └── invalid_variable_with_object.ura ├── error_reporting │ ├── parsing_error_1.ura │ ├── parsing_error_2.ura │ ├── duplicated_key_error_1.ura │ ├── duplicated_variable_error_1.ura │ ├── missing_variable_error_4.ura │ ├── missing_variable_error_6.ura │ ├── indentation_error_3.ura │ ├── missing_variable_error_1.ura │ ├── duplicated_key_error_2.ura │ ├── indentation_error_4.ura │ ├── missing_variable_error_5.ura │ ├── duplicated_variable_error_2.ura │ ├── missing_variable_error_2.ura │ ├── duplicated_key_error_3.ura │ ├── parsing_error_4.ura │ ├── duplicated_variable_error_3.ura │ ├── indentation_error_1.ura │ ├── parsing_error_3.ura │ ├── indentation_error_2.ura │ ├── missing_variable_error_3.ura │ ├── importing_error_1.ura │ ├── importing_error_2.ura │ └── README.md ├── DuplicatedKeyError │ ├── duplicated_key_aux_1.ura │ ├── duplicated_key_aux_2.ura │ ├── duplicated_key.ura │ └── README.md ├── InvalidIndentationError │ ├── with_tabs.ura │ ├── not_divisible_by_4.ura │ ├── different_chars.ura │ ├── invalid_object_indentation.ura │ └── more_than_4_difference.ura ├── FileNotFoundError │ └── file_not_found.ura ├── DuplicatedVariableError │ ├── duplicated_variable_aux_1.ura │ ├── duplicated_variable_aux_2.ura │ ├── duplicated_variable_1.ura │ ├── duplicated_variable.ura │ └── README.md ├── VariableNotDefinedError │ ├── variable_not_defined_1.ura │ └── variable_not_defined_2.ura ├── DuplicatedImportError │ └── duplicated_imports_simple.ura ├── LICENSE └── README.md ├── gura.nimble ├── license.txt ├── readme.md ├── tests.nim └── gura.nim /testing/correct/empty.ura: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testing/ParseError/with_dots.ura: -------------------------------------------------------------------------------- 1 | with.dot: 5 -------------------------------------------------------------------------------- /testing/ParseError/with_dashes.ura: -------------------------------------------------------------------------------- 1 | with-dashes: 5 -------------------------------------------------------------------------------- /testing/correct/unused_var.ura: -------------------------------------------------------------------------------- 1 | $unused_var: 5 -------------------------------------------------------------------------------- /testing/ParseError/invalid_object_1.ura: -------------------------------------------------------------------------------- 1 | user1: 2 | -------------------------------------------------------------------------------- /testing/ParseError/with_quotes.ura: -------------------------------------------------------------------------------- 1 | "with_quotes": 5 -------------------------------------------------------------------------------- /testing/correct/empty_object_2.ura: -------------------------------------------------------------------------------- 1 | empty_object: empty -------------------------------------------------------------------------------- /testing/correct/importing/three.ura: -------------------------------------------------------------------------------- 1 | from_file_three: true -------------------------------------------------------------------------------- /testing/error_reporting/parsing_error_1.ura: -------------------------------------------------------------------------------- 1 | [invalid: 1 -------------------------------------------------------------------------------- /testing/error_reporting/parsing_error_2.ura: -------------------------------------------------------------------------------- 1 | invalid: 1] -------------------------------------------------------------------------------- /testing/DuplicatedKeyError/duplicated_key_aux_1.ura: -------------------------------------------------------------------------------- 1 | my_value: 10 -------------------------------------------------------------------------------- /testing/DuplicatedKeyError/duplicated_key_aux_2.ura: -------------------------------------------------------------------------------- 1 | my_value: 10 -------------------------------------------------------------------------------- /testing/correct/empty_object_3.ura: -------------------------------------------------------------------------------- 1 | empty_object: empty -------------------------------------------------------------------------------- /testing/InvalidIndentationError/with_tabs.ura: -------------------------------------------------------------------------------- 1 | user: 2 | name: "Gura" -------------------------------------------------------------------------------- /testing/ParseError/invalid_import_1.ura: -------------------------------------------------------------------------------- 1 | import "another_file.ura" -------------------------------------------------------------------------------- /testing/ParseError/invalid_import_2.ura: -------------------------------------------------------------------------------- 1 | import "another_file.ura" -------------------------------------------------------------------------------- /testing/ParseError/invalid_variable_definition_1.ura: -------------------------------------------------------------------------------- 1 | $invalid: true -------------------------------------------------------------------------------- /testing/ParseError/invalid_variable_definition_2.ura: -------------------------------------------------------------------------------- 1 | $invalid: false -------------------------------------------------------------------------------- /testing/ParseError/invalid_variable_definition_3.ura: -------------------------------------------------------------------------------- 1 | $invalid: null -------------------------------------------------------------------------------- /testing/FileNotFoundError/file_not_found.ura: -------------------------------------------------------------------------------- 1 | import "invalid_file.ura" -------------------------------------------------------------------------------- /testing/ParseError/invalid_variable_definition_4.ura: -------------------------------------------------------------------------------- 1 | $invalid: [ 1, 2, 3] -------------------------------------------------------------------------------- /testing/DuplicatedVariableError/duplicated_variable_aux_1.ura: -------------------------------------------------------------------------------- 1 | $my_variable: 1 -------------------------------------------------------------------------------- /testing/DuplicatedVariableError/duplicated_variable_aux_2.ura: -------------------------------------------------------------------------------- 1 | $my_variable: 2 -------------------------------------------------------------------------------- /testing/VariableNotDefinedError/variable_not_defined_1.ura: -------------------------------------------------------------------------------- 1 | test: "$false_var" -------------------------------------------------------------------------------- /testing/VariableNotDefinedError/variable_not_defined_2.ura: -------------------------------------------------------------------------------- 1 | test: $false_var -------------------------------------------------------------------------------- /testing/correct/nan.ura: -------------------------------------------------------------------------------- 1 | # Not a number 2 | sf4: nan 3 | sf5: +nan 4 | sf6: -nan -------------------------------------------------------------------------------- /testing/error_reporting/duplicated_key_error_1.ura: -------------------------------------------------------------------------------- 1 | foo: "bar" 2 | foo: 44.89 -------------------------------------------------------------------------------- /testing/DuplicatedVariableError/duplicated_variable_1.ura: -------------------------------------------------------------------------------- 1 | $a_var: 14 2 | $a_var: 15 -------------------------------------------------------------------------------- /testing/InvalidIndentationError/not_divisible_by_4.ura: -------------------------------------------------------------------------------- 1 | testing: 2 | test: true -------------------------------------------------------------------------------- /testing/error_reporting/duplicated_variable_error_1.ura: -------------------------------------------------------------------------------- 1 | $foo: "bar" 2 | $foo: 44.89 -------------------------------------------------------------------------------- /testing/error_reporting/missing_variable_error_4.ura: -------------------------------------------------------------------------------- 1 | string: "Invalid $variable" -------------------------------------------------------------------------------- /testing/error_reporting/missing_variable_error_6.ura: -------------------------------------------------------------------------------- 1 | import "some/invalid/$variable" -------------------------------------------------------------------------------- /testing/ParseError/invalid_object_2.ura: -------------------------------------------------------------------------------- 1 | test: 2 | "invalid object" 3 | test_2: 5 -------------------------------------------------------------------------------- /testing/correct/importing/one.ura: -------------------------------------------------------------------------------- 1 | import "three.ura" 2 | 3 | from_file_one: 1 4 | 5 | -------------------------------------------------------------------------------- /testing/error_reporting/indentation_error_3.ura: -------------------------------------------------------------------------------- 1 | baz: 2 | bar: 3 | foo: "foz" 4 | -------------------------------------------------------------------------------- /testing/error_reporting/missing_variable_error_1.ura: -------------------------------------------------------------------------------- 1 | foo: $bar # <- $bar is not defined -------------------------------------------------------------------------------- /testing/error_reporting/duplicated_key_error_2.ura: -------------------------------------------------------------------------------- 1 | baz: true 2 | foo: "bar" 3 | foo: 44.89 -------------------------------------------------------------------------------- /testing/correct/empty_object.ura: -------------------------------------------------------------------------------- 1 | # COMMMENT 2 | empty_object: empty 3 | 4 | 5 | 6 | # COMMENT -------------------------------------------------------------------------------- /testing/correct/escape_sentence.ura: -------------------------------------------------------------------------------- 1 | # Should be interpreted as \t\\h\\i\\i 2 | foo: "\t\h\i\\i" -------------------------------------------------------------------------------- /testing/error_reporting/indentation_error_4.ura: -------------------------------------------------------------------------------- 1 | baz: 2 | bar: 3 | foo: "foz" 4 | -------------------------------------------------------------------------------- /testing/error_reporting/missing_variable_error_5.ura: -------------------------------------------------------------------------------- 1 | string: """ 2 | Invalid $variable 3 | """ -------------------------------------------------------------------------------- /testing/error_reporting/duplicated_variable_error_2.ura: -------------------------------------------------------------------------------- 1 | $bar: "test" 2 | $foo: "bar" 3 | $foo: 44.89 -------------------------------------------------------------------------------- /testing/error_reporting/missing_variable_error_2.ura: -------------------------------------------------------------------------------- 1 | correct: true 2 | foo: $bar # <- $bar is not defined -------------------------------------------------------------------------------- /testing/InvalidIndentationError/different_chars.ura: -------------------------------------------------------------------------------- 1 | testing: 2 | test: true # Spaces 3 | test_2: false # Tab -------------------------------------------------------------------------------- /testing/error_reporting/duplicated_key_error_3.ura: -------------------------------------------------------------------------------- 1 | baz: true 2 | object: 3 | foo: "bar" 4 | foo: 44.89 -------------------------------------------------------------------------------- /testing/error_reporting/parsing_error_4.ura: -------------------------------------------------------------------------------- 1 | tango_singers: [ 2 | fest: "Some string" 3 | ] 4 | 5 | 6 | L -------------------------------------------------------------------------------- /testing/error_reporting/duplicated_variable_error_3.ura: -------------------------------------------------------------------------------- 1 | 2 | $bar: "test" 3 | $foo: "bar" 4 | 5 | # COMMENT 6 | $foo: 44.89 -------------------------------------------------------------------------------- /testing/correct/array_in_object.ura: -------------------------------------------------------------------------------- 1 | model: 2 | columns: [ 3 | [ "var1", "str" ], 4 | [ "var2", "str" ] 5 | ] -------------------------------------------------------------------------------- /testing/error_reporting/indentation_error_1.ura: -------------------------------------------------------------------------------- 1 | baz: 2 | bar: "foo" 3 | foo: "foz" # <- This line has a Tab instead of 4 spaces -------------------------------------------------------------------------------- /testing/correct/importing/normal.ura: -------------------------------------------------------------------------------- 1 | import "one.ura" 2 | import "two.ura" 3 | 4 | from_original_1: [1, 2, 5] 5 | from_original_2: false -------------------------------------------------------------------------------- /testing/correct/importing/two.ura: -------------------------------------------------------------------------------- 1 | 2 | from_file_two: 3 | name: "Aníbal" 4 | surname: "Troilo" 5 | year_of_birth: 1914 6 | -------------------------------------------------------------------------------- /testing/error_reporting/parsing_error_3.ura: -------------------------------------------------------------------------------- 1 | tango_singers: [ 2 | fest: "Some string", H # <---- This "H" is a parsing error 3 | ] -------------------------------------------------------------------------------- /testing/correct/bug_trailing_comma.ura: -------------------------------------------------------------------------------- 1 | foo: [ 2 | bar: 3 | baz: [ 4 | far: "faz" 5 | ], 6 | ] 7 | barbaz: "boo" -------------------------------------------------------------------------------- /testing/error_reporting/indentation_error_2.ura: -------------------------------------------------------------------------------- 1 | baz: 2 | bar: "foo" 3 | foo: "foz" # <- This line has 5 spaces (not divisible by 4!) -------------------------------------------------------------------------------- /testing/error_reporting/missing_variable_error_3.ura: -------------------------------------------------------------------------------- 1 | 2 | correct: true 3 | 4 | 5 | # COMMENT 6 | 7 | foo: $bar # <- $bar is not defined -------------------------------------------------------------------------------- /testing/correct/array_in_object_trailing_comma.ura: -------------------------------------------------------------------------------- 1 | model: 2 | columns: [ 3 | [ "var1", "str" ], 4 | [ "var2", "str" ], 5 | ] -------------------------------------------------------------------------------- /testing/correct/object_without_useless_line.ura: -------------------------------------------------------------------------------- 1 | testing: 2 | test: 3 | name: "JWARE" 4 | surname: "Solutions" 5 | test_2: 2 -------------------------------------------------------------------------------- /testing/DuplicatedKeyError/duplicated_key.ura: -------------------------------------------------------------------------------- 1 | # Both have defined a key called "my_value" 2 | import "duplicated_key_aux_1.ura" 3 | import "duplicated_key_aux_2.ura" -------------------------------------------------------------------------------- /testing/ParseError/invalid_variable_with_object.ura: -------------------------------------------------------------------------------- 1 | # INVALID, objects are not allowed as a variable value 2 | $invalid: user: 3 | name: 'Invalid user' -------------------------------------------------------------------------------- /testing/correct/without_useless_line.ura: -------------------------------------------------------------------------------- 1 | a_string: "test string" 2 | int1: +99 3 | int2: 42 4 | int3: 0 5 | int4: -17 6 | int5: 1_000 7 | int6: 5_349_221 8 | int7: 53_49_221 -------------------------------------------------------------------------------- /testing/DuplicatedKeyError/README.md: -------------------------------------------------------------------------------- 1 | # DuplicatedKeyError tests 2 | 3 | The entry point tests is the file `duplicated_key.ura` that imports some of the files in this directory. -------------------------------------------------------------------------------- /testing/correct/importing/with_variable.ura: -------------------------------------------------------------------------------- 1 | $one: "one.ura" 2 | $two: "two.ura" 3 | 4 | import $one 5 | import $two 6 | 7 | from_original_1: [1, 2, 5] 8 | from_original_2: false -------------------------------------------------------------------------------- /testing/InvalidIndentationError/invalid_object_indentation.ura: -------------------------------------------------------------------------------- 1 | user1: 2 | name: "Carlos" 3 | surname: "Gardel" 4 | 5 | user2: 6 | name: # INVALID 7 | surname: "Troilo" -------------------------------------------------------------------------------- /testing/error_reporting/importing_error_1.ura: -------------------------------------------------------------------------------- 1 | import "tests/importing/tests-files/duplicated_variable_aux_1.ura" 2 | import "tests/importing/tests-files/duplicated_variable_aux_1.ura" -------------------------------------------------------------------------------- /testing/DuplicatedVariableError/duplicated_variable.ura: -------------------------------------------------------------------------------- 1 | # Both have defined a variable called "$my_variable" 2 | import "duplicated_variable_aux_1.ura" 3 | import "duplicated_variable_aux_2.ura" -------------------------------------------------------------------------------- /testing/DuplicatedImportError/duplicated_imports_simple.ura: -------------------------------------------------------------------------------- 1 | import "../DuplicatedVariableError/duplicated_variable_aux_1.ura" 2 | import "../DuplicatedVariableError/duplicated_variable_aux_1.ura" -------------------------------------------------------------------------------- /testing/InvalidIndentationError/more_than_4_difference.ura: -------------------------------------------------------------------------------- 1 | # INVALID as second level has block of 8 spaces as indentation 2 | services: 3 | nginx: 4 | host: "127.0.0.1" 5 | port: 80 -------------------------------------------------------------------------------- /testing/error_reporting/importing_error_2.ura: -------------------------------------------------------------------------------- 1 | # COMMENT 2 | 3 | 4 | import "tests/importing/tests-files/duplicated_variable_aux_1.ura" 5 | import "tests/importing/tests-files/duplicated_variable_aux_1.ura" -------------------------------------------------------------------------------- /testing/DuplicatedVariableError/README.md: -------------------------------------------------------------------------------- 1 | # DuplicatedVariableError tests 2 | 3 | The entry point tests are files `duplicated_variable_1.ura` and `duplicated_variable.ura` (this last one imports some of the files in this directory). -------------------------------------------------------------------------------- /testing/correct/useless_line_in_the_middle_object.ura: -------------------------------------------------------------------------------- 1 | testing: 2 | test: 3 | # COMMENT 4 | name: "JWARE" 5 | 6 | # COMMENT 7 | 8 | 9 | surname: "Solutions" 10 | 11 | 12 | 13 | test_2: 2 14 | 15 | -------------------------------------------------------------------------------- /testing/correct/useless_line_on_top.ura: -------------------------------------------------------------------------------- 1 | # Some test comments 2 | 3 | 4 | # Another comment 5 | 6 | 7 | 8 | a_string: "test string" 9 | int1: +99 10 | int2: 42 11 | int3: 0 12 | int4: -17 13 | int5: 1_000 14 | int6: 5_349_221 15 | int7: 53_49_221 -------------------------------------------------------------------------------- /testing/correct/useless_line_on_bottom.ura: -------------------------------------------------------------------------------- 1 | a_string: "test string" 2 | int1: +99 3 | int2: 42 4 | int3: 0 5 | int4: -17 6 | int5: 1_000 7 | int6: 5_349_221 8 | int7: 53_49_221 9 | 10 | # Some test comments 11 | 12 | 13 | # Another comment 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /testing/correct/useless_line_in_the_middle.ura: -------------------------------------------------------------------------------- 1 | a_string: "test string" 2 | int1: +99 3 | int2: 42 4 | int3: 0 5 | 6 | # Some test comments 7 | 8 | 9 | # Another comment 10 | 11 | 12 | 13 | 14 | int4: -17 15 | int5: 1_000 16 | int6: 5_349_221 17 | int7: 53_49_221 -------------------------------------------------------------------------------- /testing/correct/literal_string.ura: -------------------------------------------------------------------------------- 1 | # What you see is what you get. 2 | winpath: 'C:\Users\nodejs\templates' 3 | winpath2: '\\ServerX\admin$\system32\' 4 | quoted: 'John "Dog lover" Wick' 5 | regex: '<\i\c*\s*>' 6 | with_var: '$no_parsed variable!' 7 | escaped_var: '$name is cool' -------------------------------------------------------------------------------- /testing/correct/multiline_literal_string.ura: -------------------------------------------------------------------------------- 1 | regex2: '''I [dw]on't need \d{2} apples''' 2 | lines: ''' 3 | The first newline is 4 | trimmed in raw strings. 5 | All other whitespace 6 | is preserved. 7 | ''' 8 | with_var: '''$no_parsed variable!''' 9 | escaped_var: '''$name is cool''' -------------------------------------------------------------------------------- /testing/correct/normal_object.ura: -------------------------------------------------------------------------------- 1 | user1: 2 | name: "Carlos" 3 | surname: "Gardel" 4 | testing_nested: 5 | nested_1: 1 6 | nested_2: 2 7 | year_of_birth: 1890 8 | 9 | user2: 10 | name: "Aníbal" 11 | surname: "Troilo" 12 | year_of_birth: 1914 13 | -------------------------------------------------------------------------------- /testing/correct/importing/README.md: -------------------------------------------------------------------------------- 1 | ## Importing tests 2 | 3 | This folder contains related tests of [Gura imports functionality][imports]. The entry points are the files `normal.ura` and `with_variable` that import the other files in this directory. 4 | 5 | [imports]: https://gura.netlify.app/docs/spec#imports -------------------------------------------------------------------------------- /testing/correct/useless_line_on_both.ura: -------------------------------------------------------------------------------- 1 | # Some test comments 2 | 3 | 4 | # Another comment 5 | 6 | 7 | 8 | a_string: "test string" 9 | int1: +99 10 | int2: 42 11 | int3: 0 12 | int4: -17 13 | int5: 1_000 14 | int6: 5_349_221 15 | int7: 53_49_221 16 | 17 | # Some test comments 18 | 19 | 20 | # Another comment 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /testing/correct/basic_string.ura: -------------------------------------------------------------------------------- 1 | $name: "Gura" 2 | 3 | str: "I'm a string. \"You can quote me\". Na\bme\tJos\u00E9\nLocation\tSF." 4 | str_2: "I'm a string. \"You can quote me\". Na\bme\tJos\U000000E9\nLocation\tSF." 5 | with_var: "$name is cool" 6 | escaped_var: "\$name is cool" 7 | with_env_var: "$name is $env_var_value cool" # $env_var_value is defined in corresponding test.py -------------------------------------------------------------------------------- /testing/correct/normal_variable.ura: -------------------------------------------------------------------------------- 1 | $my_var: 5 2 | 3 | plain: $my_var 4 | in_array_middle: [1, $my_var, 3] 5 | in_array_last: [1, 2, $my_var] 6 | 7 | $name: "Aníbal" 8 | $surname: "Troilo" 9 | $year_of_birth: 1914 10 | 11 | # Assigning variable value to another variable is allowed 12 | $aux: $name 13 | 14 | in_object: 15 | name: $aux 16 | surname: $surname 17 | year_of_birth: $year_of_birth -------------------------------------------------------------------------------- /testing/correct/object_with_comments.ura: -------------------------------------------------------------------------------- 1 | # COMMENT 2 | user1: 3 | name: "Carlos" 4 | 5 | 6 | # COMMENT 7 | surname: "Gardel" 8 | year_of_birth: 1890 9 | 10 | 11 | 12 | testing_nested: 13 | nested_1: 1 14 | nested_2: 2 15 | 16 | 17 | # COMMENT 18 | user2: # COMMENT 19 | name: "Aníbal" 20 | 21 | # COMMENT 22 | surname: "Troilo" 23 | year_of_birth: 1914 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /gura.nimble: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Gura Configuration Language for Nim 4 | # Copyright (c) Chen Kai-Hung, Ward 5 | # 6 | #==================================================================== 7 | 8 | # Package 9 | version = "0.1.1" 10 | author = "Ward" 11 | description = "Gura Configuration Language for Nim" 12 | license = "MIT" 13 | skipDirs = @["testing"] 14 | skipFiles = @["tests.nim"] 15 | 16 | # Dependencies 17 | requires "npeg >= 1.2.2" 18 | -------------------------------------------------------------------------------- /testing/correct/useless_line_in_the_middle_object_complex.ura: -------------------------------------------------------------------------------- 1 | testing: 2 | test: 3 | # COMMENT 4 | name: "JWARE" 5 | 6 | # COMMENT 7 | 8 | 9 | surname: "Solutions" 10 | skills: 11 | good_testing: false 12 | # COMMENT WITHOUT BLANKS 13 | good_programming: false 14 | # COMMENT WITH BLANKS 15 | good_english: false 16 | 17 | test_2: 2 18 | test_3: 19 | # COMMENT 20 | key_1: true 21 | key_2: false 22 | 23 | key_3: 55.99 24 | 25 | -------------------------------------------------------------------------------- /testing/correct/normal.ura: -------------------------------------------------------------------------------- 1 | integers: [ 1, 2, 3 ] 2 | colors: [ "red", "yellow", "green" ] 3 | nested_arrays_of_ints: [ [ 1, 2 ], [3, 4, 5] ] 4 | nested_mixed_array: [ [ 1, 2 ], ["a", "b", "c"] ] 5 | mixed_with_object: [ 6 | 1, 7 | test: 8 | genaro: "Camele", 9 | 2, 10 | [ 4, 5, 6], 11 | 3 12 | ] 13 | numbers: [ 0.1, 0.2, 0.5, 1, 2, 5 ] 14 | tango_singers: [ 15 | user1: 16 | name: "Carlos" 17 | surname: "Gardel" 18 | year_of_birth: 1890 19 | testing_nested: 20 | nested_1: 1 21 | nested_2: 2, 22 | user2: 23 | name: "Aníbal" 24 | surname: "Troilo" 25 | year_of_birth: 1914 26 | ] 27 | integers_with_new_line: [ 28 | 1, 29 | 2, 30 | 3 31 | ] 32 | separator: [ 33 | a: 1 34 | b: 2, 35 | a: 1, 36 | b: 2 37 | ] -------------------------------------------------------------------------------- /testing/correct/multiline_basic_string.ura: -------------------------------------------------------------------------------- 1 | $roses: "Roses" 2 | 3 | str: """ 4 | Roses are red 5 | Violets are blue""" 6 | 7 | str_2: """Roses are red 8 | Violets are blue""" 9 | 10 | str_3: """Roses are red\nViolets are blue""" 11 | 12 | with_var: """ 13 | $roses are red 14 | Violets are blue""" 15 | 16 | with_env_var: """$env_var_value_multiline are red\nViolets are blue""" # $env_var_value_multiline is defined in corresponding test.py 17 | 18 | 19 | str_with_backslash: """ 20 | The quick brown \ 21 | 22 | 23 | fox jumps over \ 24 | the lazy dog.""" 25 | 26 | str_with_backslash_2: """\ 27 | The quick brown \ 28 | fox jumps over \ 29 | the lazy dog.\ 30 | """ 31 | 32 | str_4: """Here are two quotation marks: "". Simple enough.""" 33 | str_5: """Here are three quotation marks: ""\".""" 34 | str_6: """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" 35 | escaped_var: """\$name is cool""" -------------------------------------------------------------------------------- /testing/correct/with_comments.ura: -------------------------------------------------------------------------------- 1 | # COMMENT 2 | integers: [ 1, 2, 3 ] 3 | colors: [ "red", "yellow", "green" ] 4 | nested_arrays_of_ints: [ [ 1, 2 ], [3, 4, 5] ] 5 | 6 | # COMMENT 7 | 8 | 9 | nested_mixed_array: [ [ 1, 2 ], ["a", "b", "c"] ] 10 | mixed_with_object: [ 11 | 1, 12 | 13 | test: 14 | # COMMENT 15 | genaro: "Camele", 16 | 2, 17 | [ 4, 5, 6], 18 | 3 19 | ] 20 | numbers: [ 0.1, 0.2, 0.5, 1, 2, 5 ] 21 | tango_singers: [ 22 | user1: 23 | name: "Carlos" 24 | surname: "Gardel" 25 | year_of_birth: 1890 26 | # COMMENT 27 | testing_nested: 28 | nested_1: 1 29 | nested_2: 2, 30 | user2: 31 | name: "Aníbal" 32 | surname: "Troilo" 33 | year_of_birth: 1914 34 | # COMMENT 35 | ] 36 | integers_with_new_line: [ 37 | 1, 38 | # COMMENT 39 | 2, 40 | 41 | 42 | 43 | 3 44 | ] 45 | separator: [ 46 | a: 1 47 | b: 2 48 | # COMMENT 49 | , 50 | a: 1, 51 | b: 2 52 | ] -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Chen Kai-Hung, Ward 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /testing/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 JWare Solutions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /testing/correct/full.ura: -------------------------------------------------------------------------------- 1 | a_string: "test string" 2 | int1: +99 3 | int2: 42 4 | int3: 0 5 | int4: -17 6 | int5: 1_000 7 | int6: 5_349_221 8 | int7: 53_49_221 # Indian number system grouping 9 | # Hexadecimal with prefix `0x` 10 | hex1: 0xDEADBEEF 11 | hex2: 0xdeadbeef 12 | hex3: 0xdead_beef 13 | 14 | # Octal with prefix `0o` 15 | oct1: 0o01234567 16 | oct2: 0o755 # useful for Unix file permissions 17 | 18 | # Binary with prefix `0b` 19 | bin1: 0b11010110 20 | 21 | # Fractional 22 | flt1: +1.0 23 | flt2: 3.1415 24 | flt3: -0.01 25 | 26 | # Exponent 27 | flt4: 5e+22 28 | flt5: 1e06 29 | flt6: -2E-2 30 | 31 | # Both 32 | flt7: 6.626e-34 33 | flt8: 224_617.445_991_228 34 | 35 | # Infinity 36 | sf1: inf # Positive infinity 37 | sf2: +inf # Positive infinity 38 | sf3: -inf # Negative infinity 39 | 40 | # Null 41 | null: null 42 | 43 | # Empty objects 44 | empty_single: empty 45 | 46 | # Bool 47 | bool1: true 48 | bool2: false 49 | 50 | # Digits as key 51 | 1234: "1234" 52 | 53 | # Object 54 | services: 55 | nginx: 56 | host: "127.0.0.1" 57 | port: 80 58 | 59 | apache: 60 | virtual_host: "10.10.10.4" 61 | port: 81 62 | 63 | # Arrays 64 | integers: [ 1, 2, 3 ] 65 | colors: [ "red", "yellow", "green" ] 66 | nested_arrays_of_ints: [ [ 1, 2 ], [3, 4, 5] ] 67 | nested_mixed_array: [ [ 1, 2 ], ["a", "b", "c"] ] 68 | 69 | # Mixed-type arrays 70 | numbers: [ 0.1, 0.2, 0.5, 1, 2, 5 ] 71 | tango_singers: [ 72 | user1: 73 | name: "Carlos" 74 | surname: "Gardel" 75 | year_of_birth: 1890, 76 | user2: 77 | name: "Aníbal" 78 | surname: "Troilo" 79 | year_of_birth: 1914 80 | ] 81 | 82 | integers2: [ 83 | 1, 2, 3 84 | ] 85 | 86 | integers3: [ 87 | 1, 88 | 2, # Trailing comma 89 | ] 90 | 91 | 92 | $my_string_var: "127.0.0.1" 93 | $my_integer_var: 8080 94 | 95 | my_server: 96 | host: $my_string_var 97 | empty_nested: empty 98 | port: $my_integer_var 99 | native_auth: true 100 | 101 | $name: "Gura" 102 | gura_is_cool: "$name is cool" -------------------------------------------------------------------------------- /testing/error_reporting/README.md: -------------------------------------------------------------------------------- 1 | # Error reporting tests 2 | 3 | This folder contains several Gura files that return different types of errors. **Their purpose is to check that the line and global position of the Gura text where the error occurs is correctly reported in the implementations**. 4 | 5 | The name of the file indicates which type of error is returned. The line and position for each of the files are listed below: 6 | 7 | - `duplicated_key_error_1.ura`: 8 | - Line: 2 9 | - Global position: 11 10 | - `duplicated_key_error_2.ura`: 11 | - Line: 3 12 | - Global position: 21 13 | - `duplicated_key_error_3.ura`: 14 | - Line: 4 15 | - Global position: 37 16 | - `duplicated_variable_error_1.ura`: 17 | - Line: 2 18 | - Global position: 12 19 | - `duplicated_variable_error_2.ura`: 20 | - Line: 3 21 | - Global position: 25 22 | - `duplicated_variable_error_3.ura`: 23 | - Line: 6 24 | - Global position: 37 25 | - `importing_error_1.ura`: 26 | - Line: 2 27 | - Global position: 74 28 | - `importing_error_2.ura`: 29 | - Line: 5 30 | - Global position: 86 31 | - `indentation_error_1.ura`: 32 | - Line: 3 33 | - Global position: 20 34 | - `indentation_error_2.ura`: 35 | - Line: 3 36 | - Global position: 19 37 | - `indentation_error_3.ura`: 38 | - Line: 3 39 | - Global position: 18 40 | - `indentation_error_4.ura`: 41 | - Line: 3 42 | - Global position: 26 43 | - `missing_variable_error_1.ura`: 44 | - Line: 1 45 | - Global position: 5 46 | - `missing_variable_error_2.ura`: 47 | - Line: 2 48 | - Global position: 19 49 | - `missing_variable_error_3.ura`: 50 | - Line: 7 51 | - Global position: 33 52 | - `missing_variable_error_4.ura`: 53 | - Line: 1 54 | - Global position: 17 55 | - `missing_variable_error_5.ura`: 56 | - Line: 2 57 | - Global position: 24 58 | - `missing_variable_error_6.ura`: 59 | - Line: 1 60 | - Global position: 21 61 | - `parsing_error_1.ura`: 62 | - Line: 1 63 | - Global position: 0 64 | - `parsing_error_2.ura`: 65 | - Line: 1 66 | - Global position: 10 67 | - `parsing_error_3.ura`: 68 | - Line: 2 69 | - Global position: 42 70 | - `parsing_error_4.ura`: 71 | - Line: 6 72 | - Global position: 45 73 | -------------------------------------------------------------------------------- /testing/README.md: -------------------------------------------------------------------------------- 1 | # Gura Compliance 2 | 3 | This repository contains several tests useful for any developer working on a Gura format parser/emitter. 4 | 5 | 6 | ## Usage 7 | 8 | The tests are a series of `.ura` files categorized in different folders. 9 | 10 | - The tests found in the `correct` folder should not throw any errors or exceptions in a Gura format parser or writer implementation. 11 | - Then, there are different tests in folders with the name of the exception that a parser should throw. You can check theYou can check the complete [exceptions list in official docs][exceptions]. 12 | 13 | There may be a `README.md` file inside some folders where extra clarifications are made, some exceptions or usage instructions are detailed. 14 | 15 | Automation of these tests is not possible because exception handling is dependent on each tool used. To make it agnostic to the programming language used by the interested developer, it was decided to provide a series of files that can be easily integrated into any project. 16 | 17 | All available files are for testing the correct parsing of Gura but do not consider methods such as Gura text emission (needed for the `dump` method). These are under the responsibility of the developer working on the implementation. 18 | 19 | If you are interested in practical usage examples, you can check any parser project maintained by the organization (Python, JS/TS, Rust, V). All of them includes all the test listed in this repository. 20 | 21 | 22 | ## Versioning 23 | 24 | The different versions of Gura will be considered in this repository through different [Github releases][releases], where it will be explained, in case new tests are added, what is being considered. And in case new versions of the language are published, and some tests become invalid for them, they can be differentiated through the different versions of the published releases. 25 | 26 | 27 | ## Clarification 28 | 29 | While the use of the resources here is recommended, it should be noted that the purpose of this repository is simply to facilitate the task of developers. You should feel free to use the tests available here as you wish, adding or omitting those you consider necessary. 30 | 31 | 32 | ## License 33 | 34 | This repository is distributed under the terms of the MIT license. 35 | 36 | [exceptions]: https://gura.netlify.app/docs/2.0.0/Developers/parsing#standard-errors 37 | [releases]: https://github.com/gura-conf/testing/releases -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/khchen0915?country.x=TW&locale.x=zh_TW) 2 | 3 | # Gura Configuration Language for Nim 4 | Gura is a file format for configuration files. Gura is as flexible as YAML and simple and readable like TOML. Its syntax is clear and powerful, yet familiar for YAML/TOML users (from https://github.com/gura-conf/gura). 5 | 6 | To learn more about Gura, you can read the [Official Gura Documentation](https://gura.netlify.app/docs/gura). 7 | 8 | This Gura implementation in Nim emphasizes code that is easy to write and read (NPeg is used in both lexing and parsing), rather than optimizing for execution speed. 9 | 10 | ## Examples 11 | ```nim 12 | import gura, json, strutils 13 | 14 | const guraString = """ 15 | # This is a comment in a Gura configuration file. 16 | # Define a variable named `title` with string value "Gura Example" 17 | title: "Gura Example" 18 | 19 | # Define an object with fields `username` and `age` 20 | # with string and integer values, respectively 21 | # Indentation is used to indicate nesting 22 | person: 23 | username: "Stephen" 24 | age: 20 25 | 26 | # Define a list of values 27 | # Line breaks are OK when inside arrays 28 | hosts: [ 29 | "alpha", 30 | "omega" 31 | ] 32 | 33 | # Variables can be defined and referenced to avoid repetition 34 | $foreground: "#FFAH84" 35 | color_scheme: 36 | editor: $foreground 37 | ui: $foreground 38 | 39 | """.unindent(2) 40 | 41 | # Transforms to json node 42 | let node = fromGura(guraString) 43 | 44 | # Access a specific field 45 | echo "Title -> ", node["title"] 46 | echo "My username is ", node["person"]["username"] 47 | for host in node["hosts"]: 48 | echo "Host -> ", host 49 | ``` 50 | 51 | ## Usage 52 | ```nim 53 | proc fromGura(input: string): JsonNode 54 | ## Transforms a Gura string into a JsonNode. 55 | 56 | proc fromGuraFile(path: string): JsonNode 57 | ## Transforms a Gura file into a JsonNode. 58 | 59 | proc toGura(node: JsonNode; indent: Positive = 4): string 60 | ## Transforms a JsonNode into Gura string. 61 | 62 | proc toGuraFile(node: JsonNode; path: string; indent: Positive = 4) 63 | ## Transforms a JsonNode into Gura file. 64 | ``` 65 | 66 | ## Differences from the original Gura 67 | * Indentation can use spaces in multiples of 2 (originally required 68 | multiples of 4). 69 | 70 | * The use of Tab (\t) is not allowed in syntax; it should be replaced 71 | with spaces. 72 | 73 | * Apart from control characters \x00..\x1f and characters used in syntax, 74 | such as (' ', ':', '$', '[', ']', ',', '"', ''', '#'), any other ASCII 75 | or UTF-8 characters can be used as key names or variable names. 76 | 77 | * Commas separating items in an array are optional regardless of their 78 | placement (both [1 2 3] and [1, 2, 3,] are acceptable). However, 79 | a comma after an object always indicates the end of the object definition. 80 | 81 | * Strings (basic, literal, multi-line, etc.) from any Gura can be used for 82 | importing. 83 | 84 | * Imports always use the current file location as the current directory 85 | (similar to Nim import). 86 | 87 | 88 | ## License 89 | Copyright (c) Chen Kai-Hung, Ward. All rights reserved. 90 | 91 | ## Donate 92 | If this project help you reduce time to develop, you can give me a cup of coffee :) 93 | 94 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://paypal.me/khchen0915?country.x=TW&locale.x=zh_TW) 95 | -------------------------------------------------------------------------------- /tests.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Gura Configuration Language for Nim 4 | # Copyright (c) Chen Kai-Hung, Ward 5 | # 6 | #==================================================================== 7 | 8 | import gura, json, unittest, os 9 | 10 | when true: 11 | suite "Test Suites for Gura for Nim": 12 | 13 | test "Parse and Stringify": 14 | putEnv("env_var_value", "very") 15 | putEnv("env_var_value_multiline", "Roses") 16 | 17 | proc check(file: string, result: string) = 18 | let node = fromGuraFile(file) 19 | check $node == result 20 | check $(node.toGura().fromGura()) == result 21 | 22 | check "testing/correct/array_in_object.ura", 23 | """{"model":{"columns":[["var1","str"],["var2","str"]]}}""" 24 | 25 | check "testing/correct/array_in_object_trailing_comma.ura", 26 | """{"model":{"columns":[["var1","str"],["var2","str"]]}}""" 27 | 28 | check "testing/correct/basic_string.ura", 29 | """{"str":"I'm a string. \"You can quote me\". Na\bme\tJosé\nLocation\tSF.","str_2":"I'm a string. \"You can quote me\". Na\bme\tJosé\nLocation\tSF.","with_var":"Gura is cool","escaped_var":"$name is cool","with_env_var":"Gura is very cool"}""" 30 | 31 | check "testing/correct/bug_trailing_comma.ura", 32 | """{"foo":[{"bar":{"baz":[{"far":"faz"}]}}],"barbaz":"boo"}""" 33 | 34 | check "testing/correct/empty.ura", 35 | """{}""" 36 | 37 | check "testing/correct/empty_object.ura", 38 | """{"empty_object":{}}""" 39 | 40 | check "testing/correct/empty_object_2.ura", 41 | """{"empty_object":{}}""" 42 | 43 | check "testing/correct/empty_object_3.ura", 44 | """{"empty_object":{}}""" 45 | 46 | check "testing/correct/escape_sentence.ura", 47 | """{"foo":"\t\\h\\i\\i"}""" 48 | 49 | check "testing/correct/full.ura", 50 | """{"a_string":"test string","int1":99,"int2":42,"int3":0,"int4":-17,"int5":1000,"int6":5349221,"int7":5349221,"hex1":3735928559,"hex2":3735928559,"hex3":3735928559,"oct1":342391,"oct2":493,"bin1":214,"flt1":1.0,"flt2":3.1415,"flt3":-0.01,"flt4":5e+22,"flt5":1000000.0,"flt6":-0.02,"flt7":6.626e-34,"flt8":224617.445991228,"sf1":inf,"sf2":inf,"sf3":-inf,"null":null,"empty_single":{},"bool1":true,"bool2":false,"1234":"1234","services":{"nginx":{"host":"127.0.0.1","port":80},"apache":{"virtual_host":"10.10.10.4","port":81}},"integers":[1,2,3],"colors":["red","yellow","green"],"nested_arrays_of_ints":[[1,2],[3,4,5]],"nested_mixed_array":[[1,2],["a","b","c"]],"numbers":[0.1,0.2,0.5,1,2,5],"tango_singers":[{"user1":{"name":"Carlos","surname":"Gardel","year_of_birth":1890}},{"user2":{"name":"Aníbal","surname":"Troilo","year_of_birth":1914}}],"integers2":[1,2,3],"integers3":[1,2],"my_server":{"host":"127.0.0.1","empty_nested":{},"port":8080,"native_auth":true},"gura_is_cool":"Gura is cool"}""" 51 | 52 | check "testing/correct/literal_string.ura", 53 | """{"winpath":"C:\\Users\\nodejs\\templates","winpath2":"\\\\ServerX\\admin$\\system32\\","quoted":"John \"Dog lover\" Wick","regex":"<\\i\\c*\\s*>","with_var":"$no_parsed variable!","escaped_var":"$name is cool"}""" 54 | 55 | check "testing/correct/multiline_basic_string.ura", 56 | """{"str":"Roses are red\r\nViolets are blue","str_2":"Roses are red\r\nViolets are blue","str_3":"Roses are red\nViolets are blue","with_var":"Roses are red\r\nViolets are blue","with_env_var":"Roses are red\nViolets are blue","str_with_backslash":"The quick brown fox jumps over the lazy dog.","str_with_backslash_2":"The quick brown fox jumps over the lazy dog.","str_4":"Here are two quotation marks: \"\". Simple enough.","str_5":"Here are three quotation marks: \"\"\".","str_6":"Here are fifteen quotation marks: \"\"\"\"\"\"\"\"\"\"\"\"\"\"\".","escaped_var":"$name is cool"}""" 57 | 58 | check "testing/correct/multiline_literal_string.ura", 59 | """{"regex2":"I [dw]on't need \\d{2} apples","lines":"The first newline is\r\ntrimmed in raw strings.\r\n All other whitespace\r\n is preserved.\r\n","with_var":"$no_parsed variable!","escaped_var":"$name is cool"}""" 60 | 61 | check "testing/correct/nan.ura", 62 | """{"sf4":nan,"sf5":nan,"sf6":nan}""" 63 | 64 | check "testing/correct/normal.ura", 65 | """{"integers":[1,2,3],"colors":["red","yellow","green"],"nested_arrays_of_ints":[[1,2],[3,4,5]],"nested_mixed_array":[[1,2],["a","b","c"]],"mixed_with_object":[1,{"test":{"genaro":"Camele"}},2,[4,5,6],3],"numbers":[0.1,0.2,0.5,1,2,5],"tango_singers":[{"user1":{"name":"Carlos","surname":"Gardel","year_of_birth":1890,"testing_nested":{"nested_1":1,"nested_2":2}}},{"user2":{"name":"Aníbal","surname":"Troilo","year_of_birth":1914}}],"integers_with_new_line":[1,2,3],"separator":[{"a":1,"b":2},{"a":1},{"b":2}]}""" 66 | 67 | check "testing/correct/normal_object.ura", 68 | """{"user1":{"name":"Carlos","surname":"Gardel","testing_nested":{"nested_1":1,"nested_2":2},"year_of_birth":1890},"user2":{"name":"Aníbal","surname":"Troilo","year_of_birth":1914}}""" 69 | 70 | check "testing/correct/normal_variable.ura", 71 | """{"plain":5,"in_array_middle":[1,5,3],"in_array_last":[1,2,5],"in_object":{"name":"Aníbal","surname":"Troilo","year_of_birth":1914}}""" 72 | 73 | check "testing/correct/object_without_useless_line.ura", 74 | """{"testing":{"test":{"name":"JWARE","surname":"Solutions"},"test_2":2}}""" 75 | 76 | check "testing/correct/object_with_comments.ura", 77 | """{"user1":{"name":"Carlos","surname":"Gardel","year_of_birth":1890,"testing_nested":{"nested_1":1,"nested_2":2}},"user2":{"name":"Aníbal","surname":"Troilo","year_of_birth":1914}}""" 78 | 79 | check "testing/correct/unused_var.ura", 80 | """{}""" 81 | 82 | check "testing/correct/useless_line_in_the_middle.ura", 83 | """{"a_string":"test string","int1":99,"int2":42,"int3":0,"int4":-17,"int5":1000,"int6":5349221,"int7":5349221}""" 84 | 85 | check "testing/correct/useless_line_in_the_middle_object.ura", 86 | """{"testing":{"test":{"name":"JWARE","surname":"Solutions"},"test_2":2}}""" 87 | 88 | check "testing/correct/useless_line_in_the_middle_object_complex.ura", 89 | """{"testing":{"test":{"name":"JWARE","surname":"Solutions","skills":{"good_testing":false,"good_programming":false,"good_english":false}},"test_2":2,"test_3":{"key_1":true,"key_2":false,"key_3":55.99}}}""" 90 | 91 | check "testing/correct/useless_line_on_both.ura", 92 | """{"a_string":"test string","int1":99,"int2":42,"int3":0,"int4":-17,"int5":1000,"int6":5349221,"int7":5349221}""" 93 | 94 | check "testing/correct/useless_line_on_bottom.ura", 95 | """{"a_string":"test string","int1":99,"int2":42,"int3":0,"int4":-17,"int5":1000,"int6":5349221,"int7":5349221}""" 96 | 97 | check "testing/correct/useless_line_on_top.ura", 98 | """{"a_string":"test string","int1":99,"int2":42,"int3":0,"int4":-17,"int5":1000,"int6":5349221,"int7":5349221}""" 99 | 100 | check "testing/correct/without_useless_line.ura", 101 | """{"a_string":"test string","int1":99,"int2":42,"int3":0,"int4":-17,"int5":1000,"int6":5349221,"int7":5349221}""" 102 | 103 | check "testing/correct/with_comments.ura", 104 | """{"integers":[1,2,3],"colors":["red","yellow","green"],"nested_arrays_of_ints":[[1,2],[3,4,5]],"nested_mixed_array":[[1,2],["a","b","c"]],"mixed_with_object":[1,{"test":{"genaro":"Camele"}},2,[4,5,6],3],"numbers":[0.1,0.2,0.5,1,2,5],"tango_singers":[{"user1":{"name":"Carlos","surname":"Gardel","year_of_birth":1890,"testing_nested":{"nested_1":1,"nested_2":2}}},{"user2":{"name":"Aníbal","surname":"Troilo","year_of_birth":1914}}],"integers_with_new_line":[1,2,3],"separator":[{"a":1,"b":2},{"a":1},{"b":2}]}""" 105 | 106 | check "testing/correct/importing/normal.ura", 107 | """{"from_file_three":true,"from_file_one":1,"from_file_two":{"name":"Aníbal","surname":"Troilo","year_of_birth":1914},"from_original_1":[1,2,5],"from_original_2":false}""" 108 | 109 | check "testing/correct/importing/one.ura", 110 | """{"from_file_three":true,"from_file_one":1}""" 111 | 112 | check "testing/correct/importing/two.ura", 113 | """{"from_file_two":{"name":"Aníbal","surname":"Troilo","year_of_birth":1914}}""" 114 | 115 | check "testing/correct/importing/three.ura", 116 | """{"from_file_three":true}""" 117 | 118 | check "testing/correct/importing/with_variable.ura", 119 | """{"from_file_three":true,"from_file_one":1,"from_file_two":{"name":"Aníbal","surname":"Troilo","year_of_birth":1914},"from_original_1":[1,2,5],"from_original_2":false}""" 120 | 121 | test "Differences from the original Gura": 122 | 123 | proc check(setting: string, result: string) = 124 | let node = fromGura(setting) 125 | check $node == result 126 | check $(node.toGura().fromGura()) == result 127 | 128 | # Indentation can use spaces in multiples of 2 129 | check "a:\n b:\n c:\n empty", """{"a":{"b":{"c":{}}}}""" 130 | 131 | # UTF-8 characters as key names or variable names. 132 | check "$變數: 0\n父:\n 子: $變數", """{"父":{"子":0}}""" 133 | 134 | # Commas separating items in an array are optional 135 | check "a: [1 2 3 'hello' [] empty]", """{"a":[1,2,3,"hello",[],{}]}""" 136 | 137 | test "Error Handling": 138 | template expect(Error: type, file: string, n = -1) = 139 | expect Error: 140 | try: 141 | discard fromGuraFile(file) 142 | except Error as e: 143 | if n >= 1: 144 | check e.line == n 145 | raise e 146 | 147 | expect DuplicatedImportError, "testing/DuplicatedImportError/duplicated_imports_simple.ura", 2 148 | expect DuplicatedKeyError, "testing/DuplicatedKeyError/duplicated_key.ura", 1 149 | expect DuplicatedVariableError, "testing/DuplicatedVariableError/duplicated_variable.ura", 1 150 | expect DuplicatedVariableError, "testing/DuplicatedVariableError/duplicated_variable_1.ura", 2 151 | expect FileNotFoundError, "testing/FileNotFoundError/file_not_found.ura", 1 152 | expect GuraParseError, "testing/InvalidIndentationError/different_chars.ura", 3 153 | expect GuraParseError, "testing/InvalidIndentationError/invalid_object_indentation.ura", 7 154 | # expect GuraError, "testing/InvalidIndentationError/more_than_4_difference.ura" 155 | expect InvalidIndentationError, "testing/InvalidIndentationError/not_divisible_by_4.ura", 2 156 | expect GuraParseError, "testing/InvalidIndentationError/with_tabs.ura", 2 157 | expect InvalidIndentationError, "testing/ParseError/invalid_import_1.ura", 1 158 | # expect GuraError, "testing/ParseError/invalid_import_2.ura" 159 | expect GuraParseError, "testing/ParseError/invalid_object_1.ura", 1 160 | expect GuraParseError, "testing/ParseError/invalid_object_2.ura", 3 161 | expect VariableNotDefinedError, "testing/ParseError/invalid_variable_definition_1.ura", 1 162 | expect VariableNotDefinedError, "testing/ParseError/invalid_variable_definition_2.ura", 1 163 | expect VariableNotDefinedError, "testing/ParseError/invalid_variable_definition_3.ura", 1 164 | expect VariableNotDefinedError, "testing/ParseError/invalid_variable_definition_4.ura", 1 165 | expect VariableNotDefinedError, "testing/ParseError/invalid_variable_with_object.ura", 2 166 | # expect GuraError, "testing/ParseError/with_dashes.ura" 167 | # expect GuraError, "testing/ParseError/with_dots.ura" 168 | expect GuraError, "testing/ParseError/with_quotes.ura", 1 169 | expect VariableNotDefinedError, "testing/VariableNotDefinedError/variable_not_defined_1.ura", 1 170 | expect VariableNotDefinedError, "testing/VariableNotDefinedError/variable_not_defined_2.ura", 1 171 | expect DuplicatedKeyError, "testing/error_reporting/duplicated_key_error_1.ura", 2 172 | expect DuplicatedKeyError, "testing/error_reporting/duplicated_key_error_2.ura", 3 173 | expect DuplicatedKeyError, "testing/error_reporting/duplicated_key_error_3.ura", 4 174 | expect DuplicatedVariableError, "testing/error_reporting/duplicated_variable_error_1.ura", 2 175 | expect DuplicatedVariableError, "testing/error_reporting/duplicated_variable_error_2.ura", 3 176 | expect DuplicatedVariableError, "testing/error_reporting/duplicated_variable_error_3.ura", 6 177 | expect FileNotFoundError, "testing/error_reporting/importing_error_1.ura", 1 178 | expect FileNotFoundError, "testing/error_reporting/importing_error_2.ura", 4 179 | expect GuraParseError, "testing/error_reporting/indentation_error_1.ura", 3 180 | expect InvalidIndentationError, "testing/error_reporting/indentation_error_2.ura", 3 181 | expect GuraParseError, "testing/error_reporting/indentation_error_3.ura", 3 182 | # expect GuraError, "testing/error_reporting/indentation_error_4.ura" 183 | expect VariableNotDefinedError, "testing/error_reporting/missing_variable_error_1.ura", 1 184 | expect VariableNotDefinedError, "testing/error_reporting/missing_variable_error_2.ura", 2 185 | expect VariableNotDefinedError, "testing/error_reporting/missing_variable_error_3.ura", 7 186 | expect VariableNotDefinedError, "testing/error_reporting/missing_variable_error_4.ura", 1 187 | expect VariableNotDefinedError, "testing/error_reporting/missing_variable_error_5.ura", 1 188 | expect VariableNotDefinedError, "testing/error_reporting/missing_variable_error_6.ura", 1 189 | expect GuraParseError, "testing/error_reporting/parsing_error_1.ura", 1 190 | expect GuraParseError, "testing/error_reporting/parsing_error_2.ura", 1 191 | expect GuraParseError, "testing/error_reporting/parsing_error_3.ura", 2 192 | expect GuraParseError, "testing/error_reporting/parsing_error_4.ura", 6 193 | -------------------------------------------------------------------------------- /gura.nim: -------------------------------------------------------------------------------- 1 | #==================================================================== 2 | # 3 | # Gura Configuration Language for Nim 4 | # Copyright (c) Chen Kai-Hung, Ward 5 | # 6 | #==================================================================== 7 | 8 | ##[ 9 | Gura is a file format for configuration files. Gura is as flexible as 10 | YAML and simple and readable like TOML. Its syntax is clear and powerful, 11 | yet familiar for YAML/TOML users (from https://github.com/gura-conf/gura). 12 | 13 | This Gura implementation in Nim emphasizes code that is easy to write 14 | and read (NPeg is used in both lexing and parsing), rather than optimizing 15 | for execution speed. The differences from the original Gura are as follows: 16 | 17 | 1. Indentation can use spaces in multiples of 2 (originally required 18 | multiples of 4). 19 | 20 | 2. The use of Tab (\t) is not allowed in syntax; it should be replaced 21 | with spaces. 22 | 23 | 3. Apart from control characters \x00..\x1f and characters used in syntax, 24 | such as (' ', ':', '$', '[', ']', ',', '"', ''', '#'), any other ASCII 25 | or UTF-8 characters can be used as key names or variable names. 26 | 27 | 4. Commas separating items in an array are optional regardless of their 28 | placement (both [1 2 3] and [1, 2, 3,] are acceptable). However, 29 | a comma after an object always indicates the end of the object definition. 30 | 31 | 5. Strings (basic, literal, multi-line, etc.) from any Gura can be used for 32 | importing. 33 | 34 | 6. Imports always use the current file location as the current directory 35 | (similar to Nim import). 36 | ]## 37 | 38 | 39 | import std/[unicode, strutils, json, tables, os, strformat] 40 | import npeg, npeg/lib/utf8 41 | 42 | type 43 | GuraError* = object of CatchableError 44 | line*: int 45 | filename*: string 46 | 47 | GuraParseError* = object of GuraError 48 | DuplicatedKeyError* = object of GuraError 49 | DuplicatedVariableError* = object of GuraError 50 | DuplicatedImportError* = object of GuraError 51 | VariableNotDefinedError* = object of GuraError 52 | InvalidIndentationError* = object of GuraError 53 | FileNotFoundError* = object of GuraError 54 | 55 | GuraTokenKind = enum 56 | gtKey, gtArrayOpen, gtArrayClose, gtIndent, gtUndent, gtValue 57 | 58 | GuraErrorInfo = object 59 | line: int 60 | filename: string 61 | 62 | GuraToken = object 63 | kind: GuraTokenKind 64 | node: JsonNode 65 | info: GuraErrorInfo 66 | 67 | GuraState = object 68 | depths: seq[seq[int]] 69 | tokens: seq[GuraToken] 70 | variables: TableRef[string, GuraToken] 71 | importFiles: TableRef[string, bool] 72 | info: GuraErrorInfo 73 | 74 | # Npeg uses `==` to check if a subject matches a literal 75 | proc `==`(n: GuraToken, t: GuraTokenKind): bool = n.kind == t 76 | 77 | proc initGuraState(filename = "", 78 | variables = newTable[string, GuraToken](), 79 | importFiles = newTable[string, bool]()): GuraState = 80 | 81 | result.depths = @[@[-1]] 82 | result.tokens = @[] 83 | result.variables = variables 84 | result.importFiles = importFiles 85 | result.info.filename = filename 86 | result.info.line = 0 87 | 88 | proc newGuraError(typ: typedesc, info: GuraErrorInfo, ext = ""): ref GuraError = 89 | var msg = "Line " & $info.line 90 | if info.filename != "": msg.add " (in " & info.filename 91 | if ext != "": msg.add ", " & ext 92 | msg.add ")" 93 | result = newException(typ, msg) 94 | result.line = info.line 95 | result.filename = info.filename 96 | 97 | proc lex(state: var GuraState, input: string) = 98 | 99 | let lexer = peg(gura, st: GuraState): 100 | ws <- *' ' 101 | newline <- "\n" | "\r\n" 102 | # key_chars <- {'A'..'Z','a'..'z','0'..'9', '-', '_'} 103 | key_chars <- utf8.any - {'\x00'..'\x1f', ' ', ':', '$', '[', ']', ',', '"', '\'', '#'} 104 | comment <- '#' * *(utf8.any - newline) 105 | 106 | gura <- *token * ?newline * !1 107 | 108 | token <- indent | import_indent | ignored_indent | imports | variable_def | key | value | array_symbols | 109 | empty_line | comment 110 | 111 | empty_line <- ?comment * newline * ws * ?comment * &newline: 112 | st.info.line.inc 113 | 114 | import_indent <- newline * >ws * &"import": 115 | st.info.line.inc 116 | if $1 != "": # don't allow spaces before import 117 | raise newGuraError(InvalidIndentationError, st.info) 118 | 119 | ignored_indent <- newline * ws * &(utf8.any - {' ', '#', '\r', '\n'}): 120 | st.info.line.inc 121 | 122 | indent <- newline * >ws * &(+key_chars * ':'): 123 | st.info.line.inc 124 | let depth = len($1) 125 | if depth mod 2 != 0: 126 | raise newGuraError(InvalidIndentationError, st.info) 127 | 128 | if depth == st.depths[^1][^1]: 129 | discard 130 | 131 | elif depth > st.depths[^1][^1]: 132 | st.tokens.add GuraToken(kind: gtIndent, info: st.info) 133 | st.depths[^1].add depth 134 | 135 | else: 136 | while depth < st.depths[^1][^1]: 137 | discard st.depths[^1].pop 138 | st.tokens.add GuraToken(kind: gtUndent, info: st.info) 139 | 140 | if depth != st.depths[^1][^1]: 141 | raise newGuraError(InvalidIndentationError, st.info) 142 | 143 | imports <- "import" * +' ' * (string | variable) * ws: 144 | let file = st.tokens.pop 145 | let filename = file.node.getStr() 146 | let fullname = absolutePath(filename) 147 | 148 | if fullname in st.importFiles: 149 | raise newGuraError(DuplicatedImportError, st.info) 150 | st.importFiles[fullname] = true 151 | 152 | var state = initGuraState(filename, st.variables, st.importFiles) 153 | try: 154 | let (dir, _, _) = splitFile(fullname) 155 | let current = getCurrentDir() 156 | setCurrentDir(dir) 157 | defer: 158 | setCurrentDir(current) 159 | 160 | state.lex(readfile(fullname)) 161 | 162 | except IOError, OSError: 163 | raise newGuraError(FileNotFoundError, st.info) 164 | 165 | if state.tokens.len != 0: 166 | if state.tokens[0].kind != gtIndent or state.tokens[^1].kind != gtUndent: 167 | raise newGuraError(GuraParseError, state.tokens[^1].info) 168 | 169 | st.tokens.add state.tokens[1..^2] 170 | 171 | variable_def <- variable_new | variable_copy 172 | 173 | variable_new <- '$' * >(+key_chars) * ':' * ws * (float | integer | string) * ws: 174 | if $1 in st.variables: 175 | raise newGuraError(DuplicatedVariableError, st.info) 176 | else: 177 | st.variables[$1] = st.tokens.pop 178 | 179 | variable_copy <- '$' * >(+key_chars) * ':' * ws * '$' * >(+key_chars) * ws: 180 | if $1 in st.variables: 181 | raise newGuraError(DuplicatedVariableError, st.info) 182 | elif $2 notin st.variables: 183 | raise newGuraError(VariableNotDefinedError, st.info) 184 | else: 185 | st.variables[$1] = GuraToken(kind: gtValue, node: st.variables[$2].node.copy, 186 | info: st.info) 187 | 188 | key <- >(+key_chars) * ':' * ws: 189 | let key = $1 190 | st.tokens.add GuraToken(kind: gtKey, node: newJString(key), info: st.info) 191 | 192 | array_symbols <- >{'[', ']', ','} * ws: 193 | case $1 194 | of "[": 195 | st.tokens.add GuraToken(kind: gtArrayOpen, info: st.info) 196 | # start a new depths stack for array 197 | st.depths.add @[-1] 198 | 199 | of "]": 200 | # undent all till array start 201 | while st.depths[^1].len > 1: 202 | discard st.depths[^1].pop 203 | st.tokens.add GuraToken(kind: gtUndent, info: st.info) 204 | 205 | # drop the depths stack for this array 206 | if st.depths.len > 1: 207 | discard st.depths.pop 208 | else: 209 | raise newGuraError(GuraParseError, st.info) 210 | 211 | st.tokens.add GuraToken(kind: gtArrayClose, info: st.info) 212 | 213 | of ",": 214 | # start a new value also means undent all till array start 215 | while st.depths[^1].len > 1: 216 | discard st.depths[^1].pop 217 | st.tokens.add GuraToken(kind: gtUndent, info: st.info) 218 | 219 | else: discard 220 | 221 | value <- (null | true | false | empty | float | integer | string | variable) * ws 222 | 223 | null <- "null": 224 | st.tokens.add GuraToken(kind: gtValue, node: newJNull(), info: st.info) 225 | 226 | true <- "true": 227 | st.tokens.add GuraToken(kind: gtValue, node: newJBool(true), info: st.info) 228 | 229 | false <- "false": 230 | st.tokens.add GuraToken(kind: gtValue, node: newJBool(false), info: st.info) 231 | 232 | empty <- "empty": 233 | st.tokens.add GuraToken(kind: gtValue, node: newJObject(), info: st.info) 234 | 235 | integer <- hex_int | oct_int | bin_int | dec_int 236 | 237 | dec_int <- ?{'+', '-'} * {'0'..'9'} * *{'0'..'9', '_'}: 238 | try: 239 | st.tokens.add GuraToken(kind: gtValue, node: newJInt(parseBiggestInt($0)), info: st.info) 240 | except ValueError as e: 241 | raise newGuraError(GuraParseError, st.info, e.msg) 242 | 243 | hex_int <- "0x" * {'0'..'9', 'A'..'F', 'a'..'f'} * *{'0'..'9', 'A'..'F', 'a'..'f', '_'}: 244 | try: 245 | st.tokens.add GuraToken(kind: gtValue, node: newJInt(parseHexInt($0)), info: st.info) 246 | except ValueError as e: 247 | raise newGuraError(GuraParseError, st.info, e.msg) 248 | 249 | oct_int <- "0o" * {'0'..'7'} * *{'0'..'7', '_'}: 250 | try: 251 | st.tokens.add GuraToken(kind: gtValue, node: newJInt(parseOctInt($0)), info: st.info) 252 | except ValueError as e: 253 | raise newGuraError(GuraParseError, st.info, e.msg) 254 | 255 | bin_int <- "0b" * {'0', '1'} * *{'0', '1', '_'}: 256 | try: 257 | st.tokens.add GuraToken(kind: gtValue, node: newJInt(parseBinInt($0)), info: st.info) 258 | except ValueError as e: 259 | raise newGuraError(GuraParseError, st.info, e.msg) 260 | 261 | float <- float_int_part * (exp | frac * ?exp) | ?{'+', '-'} * ("inf" | "nan"): 262 | try: 263 | st.tokens.add GuraToken(kind: gtValue, node: newJFloat(parseFloat($0)), info: st.info) 264 | except ValueError as e: 265 | raise newGuraError(GuraParseError, st.info, e.msg) 266 | 267 | float_int_part <- ?{'+', '-'} * {'0'..'9'} * *{'0'..'9', '_'} 268 | frac <- '.' * {'0'..'9'} * *{'0'..'9', '_'} 269 | exp <- i"e" * ?{'+', '-'} * {'0'..'9'} * *{'0'..'9', '_'} 270 | 271 | string <- ml_basic_string | ml_literal_string | basic_string | literal_string: 272 | st.tokens.add GuraToken(kind: gtValue, node: newJString($1), info: st.info) 273 | 274 | basic_string <- '"' * *basic_char * '"': 275 | var str = newStringOfCap(capture.len) 276 | for i in 1 ..< capture.len: 277 | str.add capture[i].s 278 | push str 279 | 280 | basic_char <- escaped | escaped_unicode | interpolation | >(utf8.any - '"') 281 | escaped <- '\\' * >{'"', '\\', 'b', 'f', 'n', 'r', 't', '$'}: 282 | case $1 283 | of "\"": push "\"" 284 | of "\\": push "\\" 285 | of "b": push "\b" 286 | of "f": push "\f" 287 | of "n": push "\n" 288 | of "r": push "\r" 289 | of "t": push "\t" 290 | of "$": push "$" 291 | else: doAssert(false, "unreachable") 292 | 293 | interpolation <- '$' * >(+key_chars) * &(utf8.any - key_chars): 294 | if $1 in st.variables: 295 | let node = st.variables[$1].node.copy() 296 | case node.kind 297 | of JString: 298 | push node.str 299 | else: 300 | push $node 301 | elif existsEnv($1): 302 | push getEnv($1) 303 | else: 304 | raise newGuraError(VariableNotDefinedError, st.info) 305 | 306 | escaped_unicode <- ("\\u" * >Xdigit[4]) | ("\\U" * >Xdigit[8]): 307 | push $Rune(parseHexInt($1)) 308 | 309 | literal_string <- '\'' * >*(utf8.any - '\'') * '\'' 310 | 311 | ml_basic_string <- "\"\"\"" * ?newline * *ml_basic_body * "\"\"\"": 312 | var str = newStringOfCap(capture.len) 313 | for i in 1 ..< capture.len: 314 | str.add capture[i].s 315 | push str 316 | st.info.line.inc(count($0, '\n')) 317 | 318 | ml_basic_body <- escaped | escaped_unicode | interpolation | mlb_escaped_nl | >(utf8.any - "\"\"\"") 319 | mlb_escaped_nl <- '\\' * ws * newline * *(' ' | newline) 320 | 321 | ml_literal_string <- "'''" * ?newline * >*(utf8.any - "'''") * "'''": 322 | push $1 323 | st.info.line.inc(count($0, '\n')) 324 | 325 | variable <- '$' * >(+key_chars) * &(utf8.any - key_chars): 326 | if $1 in st.variables: 327 | let node = st.variables[$1].node.copy() 328 | st.tokens.add GuraToken(kind: gtValue, node: node, info: st.info) 329 | elif existsEnv($1): 330 | st.tokens.add GuraToken(kind: gtValue, node: newJString(getEnv($1)), info: st.info) 331 | else: 332 | raise newGuraError(VariableNotDefinedError, st.info) 333 | 334 | # always assume indent start from 0 335 | # also means the whole gura should be a object 336 | state.depths[^1].add 0 337 | state.tokens.add GuraToken(kind: gtIndent, 338 | info: GuraErrorInfo(line: 1, filename: state.info.filename)) 339 | 340 | if not lexer.match("\n" & input & "\n", state).ok: 341 | raise newGuraError(GuraParseError, state.info) 342 | 343 | while state.depths[^1].len > 1: 344 | discard state.depths[^1].pop 345 | state.tokens.add GuraToken(kind: gtUndent) 346 | 347 | proc parse(state: var GuraState): JsonNode = 348 | 349 | let parser = peg(gura, GuraToken, ret: JsonNode): 350 | gura <- obj * !1: 351 | ret = ($1).node 352 | 353 | obj <- [gtIndent] * *key_val * [gtUndent]: 354 | var obj = newJObject() 355 | var i = 1 356 | while i < capture.len: 357 | let key = (capture[i].s).node.str 358 | let val = (capture[i+1].s).node 359 | if key in obj: 360 | raise newGuraError(DuplicatedKeyError, capture[i].s.info) 361 | 362 | obj[key] = val 363 | i.inc(2) 364 | 365 | push GuraToken(kind: gtValue, node: obj) 366 | 367 | key_val <- >[gtKey] * value: 368 | push $1 369 | push $2 370 | 371 | array <- [gtArrayOpen] * *value * [gtArrayClose]: 372 | var arr = newJArray() 373 | for i in 1 ..< capture.len: 374 | let val = (capture[i].s).node 375 | arr.add val 376 | 377 | push GuraToken(kind: gtValue, node: arr) 378 | 379 | value <- [gtValue] | obj | array: 380 | let token = $0 381 | if token.kind == gtValue: 382 | push token 383 | 384 | else: 385 | # $1 is object or array here 386 | push $1 387 | 388 | let match = parser.match(state.tokens, result) 389 | if not match.ok: 390 | if state.tokens.len > match.matchMax: 391 | let errorToken = state.tokens[match.matchMax] 392 | case errorToken.kind: 393 | of gtIndent: 394 | raise newGuraError(GuraParseError, errorToken.info) 395 | of gtUndent: 396 | raise newGuraError(GuraParseError, state.tokens[match.matchMax-1].info) 397 | else: 398 | raise newGuraError(GuraParseError, errorToken.info) 399 | else: 400 | raise newGuraError(GuraParseError, state.tokens[match.matchLen].info) 401 | 402 | proc toGura(node: JsonNode, output: var string, indent: int, count = 4) = 403 | var indent = indent 404 | case node.kind 405 | of JInt, JFloat, JBool, JNull: 406 | output.add fmt"{node}" 407 | 408 | of JString: 409 | let isLiteral = patt *(utf8.any - {'\x00'..'\x1f', '\''}) * !1 410 | if isLiteral.match(node.str).ok: 411 | output.add fmt"'{node.str}'" 412 | else: 413 | output.add $node 414 | 415 | of JArray: 416 | output.add "[" 417 | for i in 0.. ", node["title"] 491 | echo "My username is ", node["person"]["username"] 492 | for host in node["hosts"]: 493 | echo "Host -> ", host 494 | --------------------------------------------------------------------------------