enolib 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +52 -0
  4. data/lib/enolib.rb +42 -0
  5. data/lib/enolib/constants.rb +16 -0
  6. data/lib/enolib/context.rb +220 -0
  7. data/lib/enolib/elements/element.rb +42 -0
  8. data/lib/enolib/elements/element_base.rb +141 -0
  9. data/lib/enolib/elements/empty.rb +9 -0
  10. data/lib/enolib/elements/field.rb +63 -0
  11. data/lib/enolib/elements/fieldset.rb +151 -0
  12. data/lib/enolib/elements/fieldset_entry.rb +15 -0
  13. data/lib/enolib/elements/list.rb +107 -0
  14. data/lib/enolib/elements/list_item.rb +13 -0
  15. data/lib/enolib/elements/missing/missing_element_base.rb +44 -0
  16. data/lib/enolib/elements/missing/missing_empty.rb +13 -0
  17. data/lib/enolib/elements/missing/missing_field.rb +13 -0
  18. data/lib/enolib/elements/missing/missing_fieldset.rb +29 -0
  19. data/lib/enolib/elements/missing/missing_fieldset_entry.rb +13 -0
  20. data/lib/enolib/elements/missing/missing_list.rb +33 -0
  21. data/lib/enolib/elements/missing/missing_section.rb +105 -0
  22. data/lib/enolib/elements/missing/missing_section_element.rb +53 -0
  23. data/lib/enolib/elements/missing/missing_value_element_base.rb +21 -0
  24. data/lib/enolib/elements/section.rb +560 -0
  25. data/lib/enolib/elements/section_element.rb +141 -0
  26. data/lib/enolib/elements/value_element_base.rb +79 -0
  27. data/lib/enolib/errors.rb +25 -0
  28. data/lib/enolib/errors/parsing.rb +136 -0
  29. data/lib/enolib/errors/selections.rb +83 -0
  30. data/lib/enolib/errors/validation.rb +146 -0
  31. data/lib/enolib/grammar_regexp.rb +103 -0
  32. data/lib/enolib/lookup.rb +235 -0
  33. data/lib/enolib/messages/de.rb +79 -0
  34. data/lib/enolib/messages/en.rb +79 -0
  35. data/lib/enolib/messages/es.rb +79 -0
  36. data/lib/enolib/parse.rb +9 -0
  37. data/lib/enolib/parser.rb +708 -0
  38. data/lib/enolib/register.rb +24 -0
  39. data/lib/enolib/reporters/html_reporter.rb +115 -0
  40. data/lib/enolib/reporters/reporter.rb +258 -0
  41. data/lib/enolib/reporters/terminal_reporter.rb +107 -0
  42. data/lib/enolib/reporters/text_reporter.rb +46 -0
  43. metadata +130 -0
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # GENERATED ON 2019-04-10T13:57:55 - DO NOT EDIT MANUALLY
4
+
5
+ module Enolib
6
+ module Messages
7
+ module De
8
+ CONTENT_HEADER = 'Inhalt'
9
+ EXPECTED_EMPTY = 'Ein leeres Element wurde erwartet.'
10
+ EXPECTED_FIELD = 'Ein Feld wurde erwartet.'
11
+ EXPECTED_FIELDS = 'Nur Felder wurden erwartet.'
12
+ EXPECTED_FIELDSET = 'Ein Fieldset wurde erwartet.'
13
+ EXPECTED_FIELDSET_ENTRY = 'Ein Fieldset Eintrag wurde erwartet.'
14
+ EXPECTED_FIELDSETS = 'Nur Fieldsets wurden erwartet.'
15
+ EXPECTED_LIST = 'Eine Liste wurde erwartet.'
16
+ EXPECTED_LIST_ITEM = 'Ein Listen Eintrag wurde erwartet.'
17
+ EXPECTED_LISTS = 'Nur Listen wurden erwartet.'
18
+ EXPECTED_SECTION = 'Eine Sektion wurde erwartet.'
19
+ EXPECTED_SECTIONS = 'Nur Sektionen wurden erwartet.'
20
+ EXPECTED_SINGLE_ELEMENT = 'Nur ein einzelnes Element wurde erwartet.'
21
+ EXPECTED_SINGLE_EMPTY = 'Nur ein einzelnes leeres Element wurde erwartet.'
22
+ EXPECTED_SINGLE_FIELD = 'Nur ein einzelnes Feld wurde erwartet.'
23
+ EXPECTED_SINGLE_FIELDSET = 'Nur ein einzelnes Fieldset wurde erwartet.'
24
+ EXPECTED_SINGLE_FIELDSET_ENTRY = 'Nur ein einzelner Fieldset Eintrag wurde erwartet.'
25
+ EXPECTED_SINGLE_LIST = 'Nur eine einzelne Liste wurde erwartet.'
26
+ EXPECTED_SINGLE_SECTION = 'Nur eine einzelne Sektion wurde erwartet.'
27
+ GUTTER_HEADER = 'Zeile'
28
+ MISSING_COMMENT = 'Ein erforderlicher Kommentar zu diesem Feld fehlt.'
29
+ MISSING_ELEMENT = 'Ein einzelnes Element ist erforderlich - es kann einen beliebigen Schlüssel haben.'
30
+ MISSING_EMPTY = 'Ein einzelnes leeres Element ist erforderlich - es kann einen beliebigen Schlüssel haben.'
31
+ MISSING_FIELD = 'Ein einzelnes Feld ist erforderlich - es kann einen beliebigen Schlüssel haben.'
32
+ MISSING_FIELDSET = 'Ein einzelnes Fieldset ist erforderlich - es kann einen beliebigen Schlüssel haben.'
33
+ MISSING_FIELDSET_ENTRY = 'Ein einzelner Fieldset Eintrag ist erforderlich - er kann einen beliebigen Schlüssel haben.'
34
+ MISSING_LIST = 'Eine einzelne Liste ist erforderlich - sie kann einen beliebigen Schlüssel haben.'
35
+ MISSING_SECTION = 'Eine einzelne Sektion ist erforderlich - sie kann einen beliebigen Schlüssel haben.'
36
+ UNEXPECTED_ELEMENT = 'Dieses Element wurde nicht erwartet, prüfe ob es am richtigen Platz ist und dass der Schlüssel keine Tippfehler enthält.'
37
+ def self.comment_error(message) "Es gibt ein Problem mit dem Kommentar dieses Elements: #{message}" end
38
+ def self.cyclic_dependency(line, key) "In Zeile #{line} wird '#{key}' in sich selbst kopiert." end
39
+ def self.expected_empty_with_key(key) "Ein leeres Element mit dem Schlüssel '#{key}' wurde erwartet." end
40
+ def self.expected_field_with_key(key) "Ein Feld mit dem Schlüssel '#{key}' wurde erwartet." end
41
+ def self.expected_fields_with_key(key) "Nur Felder mit dem Schlüssel '#{key}' wurden erwartet." end
42
+ def self.expected_fieldset_with_key(key) "Ein Fieldset mit dem Schlüssel '#{key}' wurde erwartet." end
43
+ def self.expected_fieldsets_with_key(key) "Nur Fieldsets mit dem Schlüssel '#{key}' wurden erwartet." end
44
+ def self.expected_list_with_key(key) "Eine Liste mit dem Schlüssel '#{key}' wurde erwartet." end
45
+ def self.expected_lists_with_key(key) "Nur Listen mit dem Schlüssel '#{key}' wurden erwartet." end
46
+ def self.expected_section_with_key(key) "Eine Sektion mit dem Schlüssel '#{key}' wurde erwartet." end
47
+ def self.expected_sections_with_key(key) "Nur Sektionen mit dem Schlüssel '#{key}' wurden erwartet." end
48
+ def self.expected_single_element_with_key(key) "Nur ein einzelnes Element mit dem Schlüssel '#{key}' wurde erwartet." end
49
+ def self.expected_single_empty_with_key(key) "Nur ein einzelnes leeres Element mit dem Schlüssel '#{key}' wurde erwartet." end
50
+ def self.expected_single_field_with_key(key) "Nur ein einzelnes Feld mit dem Schlüssel '#{key}' wurde erwartet." end
51
+ def self.expected_single_fieldset_entry_with_key(key) "Nur ein einzelner Fieldset Eintrag mit dem Schlüssel '#{key}' wurde erwartet." end
52
+ def self.expected_single_fieldset_with_key(key) "Nur ein einzelnes Fieldset mit dem Schlüssel '#{key}' wurde erwartet." end
53
+ def self.expected_single_list_with_key(key) "Nur eine einzelne Liste mit dem Schlüssel '#{key}' wurde erwartet." end
54
+ def self.expected_single_section_with_key(key) "Nur eine einzelne Sektion mit dem Schlüssel '#{key}' wurde erwartet." end
55
+ def self.invalid_line(line) "Zeile #{line} folgt keinem spezifierten Muster." end
56
+ def self.key_error(message) "Es gibt ein Problem mit dem Schlüssel dieses Elements: #{message}" end
57
+ def self.missing_element_for_continuation(line) "Zeile #{line} enthält eine Zeilenfortsetzung ohne dass davor ein fortsetzbares Element begonnen wurde." end
58
+ def self.missing_element_with_key(key) "Das Element '#{key}' fehlt - falls es angegeben wurde eventuell nach Tippfehlern Ausschau halten und auch die Gross/Kleinschreibung beachten." end
59
+ def self.missing_empty_with_key(key) "Das leere Element '#{key}' fehlt - falls es angegeben wurde eventuell nach Tippfehlern Ausschau halten und auch die Gross/Kleinschreibung beachten." end
60
+ def self.missing_field_value(key) "Das Feld '#{key}' muss einen Wert enthalten." end
61
+ def self.missing_field_with_key(key) "Das Feld '#{key}' fehlt - falls es angegeben wurde eventuell nach Tippfehlern Ausschau halten und auch die Gross/Kleinschreibung beachten." end
62
+ def self.missing_fieldset_entry_value(key) "Der Fieldset Eintrag '#{key}' muss einen Wert enthalten." end
63
+ def self.missing_fieldset_entry_with_key(key) "Der Fieldset Eintrag '#{key}' fehlt - falls er angegeben wurde eventuell nach Tippfehlern Ausschau halten und auch die Gross/Kleinschreibung beachten." end
64
+ def self.missing_fieldset_for_fieldset_entry(line) "Zeile #{line} enthält einen Fieldset Eintrag ohne dass davor ein Fieldset begonnen wurde." end
65
+ def self.missing_fieldset_with_key(key) "Das Fieldset '#{key}' fehlt - falls es angegeben wurde eventuell nach Tippfehlern Ausschau halten und auch die Gross/Kleinschreibung beachten." end
66
+ def self.missing_list_for_list_item(line) "Zeile #{line} enthält einen Listeneintrag ohne dass davor ein eine Liste begonnen wurde." end
67
+ def self.missing_list_item_value(key) "Die Liste '#{key}' darf keine leeren Einträge enthalten." end
68
+ def self.missing_list_with_key(key) "Die Liste '#{key}' fehlt - falls sie angegeben wurde eventuell nach Tippfehlern Ausschau halten und auch die Gross/Kleinschreibung beachten." end
69
+ def self.missing_section_with_key(key) "Die Sektion '#{key}' fehlt - falls sie angegeben wurde eventuell nach Tippfehlern Ausschau halten und auch die Gross/Kleinschreibung beachten." end
70
+ def self.non_section_element_not_found(line, key) "In Zeile #{line} soll das Nicht-Sektions Element '#{key}' kopiert werden, es wurde aber nicht gefunden." end
71
+ def self.section_hierarchy_layer_skip(line) "Zeile #{line} beginnt eine Sektion die mehr als eine Ebene tiefer liegt als die aktuelle." end
72
+ def self.section_not_found(line, key) "In Zeile #{line} soll die Sektion '#{key}' kopiert werden, sie wurde aber nicht gefunden." end
73
+ def self.two_or_more_templates_found(key) "Es gibt mindestens zwei Elemente mit dem Schlüssel '#{key}' die hier zum kopieren in Frage kommen, es ist nicht klar welches kopiert werden soll." end
74
+ def self.unterminated_escaped_key(line) "In Zeile #{line} wird der Schlüssel eines Elements escaped, jedoch wird diese Escape Sequenz bis zum Ende der Zeile nicht mehr beendet." end
75
+ def self.unterminated_multiline_field(key, line) "Das Mehrzeilenfeld '#{key}' dass in Zeile #{line} beginnt wird bis zum Ende des Dokuments nicht mehr beendet." end
76
+ def self.value_error(message) "Es gibt ein Problem mit dem Wert dieses Elements: #{message}" end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # GENERATED ON 2019-04-10T13:57:55 - DO NOT EDIT MANUALLY
4
+
5
+ module Enolib
6
+ module Messages
7
+ module En
8
+ CONTENT_HEADER = 'Content'
9
+ EXPECTED_EMPTY = 'An empty element was expected.'
10
+ EXPECTED_FIELD = 'A field was expected.'
11
+ EXPECTED_FIELDS = 'Only fields were expected.'
12
+ EXPECTED_FIELDSET = 'A fieldset was expected.'
13
+ EXPECTED_FIELDSET_ENTRY = 'A fieldset entry was expected.'
14
+ EXPECTED_FIELDSETS = 'Only fieldsets were expected.'
15
+ EXPECTED_LIST = 'A list was expected.'
16
+ EXPECTED_LIST_ITEM = 'A list item was expected.'
17
+ EXPECTED_LISTS = 'Only lists were expected.'
18
+ EXPECTED_SECTION = 'A section was expected.'
19
+ EXPECTED_SECTIONS = 'Only sections were expected.'
20
+ EXPECTED_SINGLE_ELEMENT = 'Only a single element was expected.'
21
+ EXPECTED_SINGLE_EMPTY = 'Only a single empty element was expected.'
22
+ EXPECTED_SINGLE_FIELD = 'Only a single field was expected.'
23
+ EXPECTED_SINGLE_FIELDSET = 'Only a single fieldset was expected.'
24
+ EXPECTED_SINGLE_FIELDSET_ENTRY = 'Only a single fieldset entry was expected.'
25
+ EXPECTED_SINGLE_LIST = 'Only a single list was expected.'
26
+ EXPECTED_SINGLE_SECTION = 'Only a single section was expected.'
27
+ GUTTER_HEADER = 'Line'
28
+ MISSING_COMMENT = 'A required comment for this element is missing.'
29
+ MISSING_ELEMENT = 'A single element is required - it can have any key.'
30
+ MISSING_EMPTY = 'A single empty element is required - it can have any key.'
31
+ MISSING_FIELD = 'A single field is required - it can have any key.'
32
+ MISSING_FIELDSET = 'A single fieldset is required - it can have any key.'
33
+ MISSING_FIELDSET_ENTRY = 'A single fieldset entry is required - it can have any key.'
34
+ MISSING_LIST = 'A single list is required - it can have any key.'
35
+ MISSING_SECTION = 'A single section is required - it can have any key.'
36
+ UNEXPECTED_ELEMENT = 'This element was not expected, make sure it is at the right place in the document and that its key is not mis-typed.'
37
+ def self.comment_error(message) "There is a problem with the comment of this element: #{message}" end
38
+ def self.cyclic_dependency(line, key) "In line #{line} '#{key}' is copied into itself." end
39
+ def self.expected_empty_with_key(key) "An empty element with the key '#{key}' was expected." end
40
+ def self.expected_field_with_key(key) "A field with the key '#{key}' was expected." end
41
+ def self.expected_fields_with_key(key) "Only fields with the key '#{key}' were expected." end
42
+ def self.expected_fieldset_with_key(key) "A fieldset with the key '#{key}' was expected." end
43
+ def self.expected_fieldsets_with_key(key) "Only fieldsets with the key '#{key}' were expected." end
44
+ def self.expected_list_with_key(key) "A list with the key '#{key}' was expected." end
45
+ def self.expected_lists_with_key(key) "Only lists with the key '#{key}' were expected." end
46
+ def self.expected_section_with_key(key) "A section with the key '#{key}' was expected." end
47
+ def self.expected_sections_with_key(key) "Only sections with the key '#{key}' were expected." end
48
+ def self.expected_single_element_with_key(key) "Only a single element with the key '#{key}' was expected." end
49
+ def self.expected_single_empty_with_key(key) "Only a single empty element with the key '#{key}' was expected." end
50
+ def self.expected_single_field_with_key(key) "Only a single field with the key '#{key}' was expected." end
51
+ def self.expected_single_fieldset_entry_with_key(key) "Only a single fieldset entry with the key '#{key}' was expected." end
52
+ def self.expected_single_fieldset_with_key(key) "Only a single fieldset with the key '#{key}' was expected." end
53
+ def self.expected_single_list_with_key(key) "Only a single list with the key '#{key}' was expected." end
54
+ def self.expected_single_section_with_key(key) "Only a single section with the key '#{key}' was expected." end
55
+ def self.invalid_line(line) "Line #{line} does not follow any specified pattern." end
56
+ def self.key_error(message) "There is a problem with the key of this element: #{message}" end
57
+ def self.missing_element_for_continuation(line) "Line #{line} contains a line continuation without a continuable element being specified before." end
58
+ def self.missing_element_with_key(key) "The element '#{key}' is missing - in case it has been specified look for typos and also check for correct capitalization." end
59
+ def self.missing_empty_with_key(key) "The empty element '#{key}' is missing - in case it has been specified look for typos and also check for correct capitalization." end
60
+ def self.missing_field_value(key) "The field '#{key}' must contain a value." end
61
+ def self.missing_field_with_key(key) "The field '#{key}' is missing - in case it has been specified look for typos and also check for correct capitalization." end
62
+ def self.missing_fieldset_entry_value(key) "The fieldset entry '#{key}' must contain a value." end
63
+ def self.missing_fieldset_entry_with_key(key) "The fieldset entry '#{key}' is missing - in case it has been specified look for typos and also check for correct capitalization." end
64
+ def self.missing_fieldset_for_fieldset_entry(line) "Line #{line} contains a fieldset entry without a fieldset being specified before." end
65
+ def self.missing_fieldset_with_key(key) "The fieldset '#{key}' is missing - in case it has been specified look for typos and also check for correct capitalization." end
66
+ def self.missing_list_for_list_item(line) "Line #{line} contains a list item without a list being specified before." end
67
+ def self.missing_list_item_value(key) "The list '#{key}' may not contain empty items." end
68
+ def self.missing_list_with_key(key) "The list '#{key}' is missing - in case it has been specified look for typos and also check for correct capitalization." end
69
+ def self.missing_section_with_key(key) "The section '#{key}' is missing - in case it has been specified look for typos and also check for correct capitalization." end
70
+ def self.non_section_element_not_found(line, key) "In line #{line} the non-section element '#{key}' should be copied, but it was not found." end
71
+ def self.section_hierarchy_layer_skip(line) "Line #{line} starts a section that is more than one level deeper than the current one." end
72
+ def self.section_not_found(line, key) "In line #{line} the section '#{key}' should be copied, but it was not found." end
73
+ def self.two_or_more_templates_found(key) "There are at least two elements with the key '#{key}' that qualify for being copied here, it is not clear which to copy." end
74
+ def self.unterminated_escaped_key(line) "In line #{line} the key of an element is escaped, but the escape sequence is not terminated until the end of the line." end
75
+ def self.unterminated_multiline_field(key, line) "The multiline field '#{key}' starting in line #{line} is not terminated until the end of the document." end
76
+ def self.value_error(message) "There is a problem with the value of this element: #{message}" end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # GENERATED ON 2019-04-10T13:57:55 - DO NOT EDIT MANUALLY
4
+
5
+ module Enolib
6
+ module Messages
7
+ module Es
8
+ CONTENT_HEADER = 'Contenido'
9
+ EXPECTED_EMPTY = 'Se esperaba un elemento vacío.'
10
+ EXPECTED_FIELD = 'Se esperaba una casilla.'
11
+ EXPECTED_FIELDS = 'Solo se esperaban casillas.'
12
+ EXPECTED_FIELDSET = 'Se esperaba una collecíon de casillas.'
13
+ EXPECTED_FIELDSET_ENTRY = 'Se esperaba una casilla de collecíon.'
14
+ EXPECTED_FIELDSETS = 'Solo se esperaban colleciones de casillas.'
15
+ EXPECTED_LIST = 'Se esperaba una lista.'
16
+ EXPECTED_LIST_ITEM = 'Se esperaba una entrada de lista.'
17
+ EXPECTED_LISTS = 'Solo se esperaban listas.'
18
+ EXPECTED_SECTION = 'Se esperaba una sección.'
19
+ EXPECTED_SECTIONS = 'Solo se esperaban secciones.'
20
+ EXPECTED_SINGLE_ELEMENT = 'Solo se esperaba un único elemento.'
21
+ EXPECTED_SINGLE_EMPTY = 'Solo se esperaba un único elemento vacío.'
22
+ EXPECTED_SINGLE_FIELD = 'Solo se esperaba una sola casilla.'
23
+ EXPECTED_SINGLE_FIELDSET = 'Solo se esperaba una sola collecíon de casillas.'
24
+ EXPECTED_SINGLE_FIELDSET_ENTRY = 'Solo se esperaba una sola casilla de collecíon.'
25
+ EXPECTED_SINGLE_LIST = 'Solo se esperaba una sola lista.'
26
+ EXPECTED_SINGLE_SECTION = 'Solo se esperaba una sola sección.'
27
+ GUTTER_HEADER = 'Línea'
28
+ MISSING_COMMENT = 'Falta un comentario necesario para este elemento.'
29
+ MISSING_ELEMENT = 'Se requiere un único elemento - puede tener cualquier clave.'
30
+ MISSING_EMPTY = 'Se requiere un único elemento vacío - puede tener cualquier clave.'
31
+ MISSING_FIELD = 'Se requiere una sola casilla - puede tener cualquier clave.'
32
+ MISSING_FIELDSET = 'Se requiere una sola collecíon de casillas - puede tener cualquier clave.'
33
+ MISSING_FIELDSET_ENTRY = 'Se requiere una sola casilla de collecíon - puede tener cualquier clave.'
34
+ MISSING_LIST = 'Se requiere una sola lista - puede tener cualquier clave.'
35
+ MISSING_SECTION = 'Se requiere una sola sección - puede tener cualquier clave.'
36
+ UNEXPECTED_ELEMENT = 'Este elemento no se esperaba, averigua si es en el sitio correcto y que no contiene un error tipográfico la clave.'
37
+ def self.comment_error(message) "Hay un problema con el comentario de este elemento: #{message}" end
38
+ def self.cyclic_dependency(line, key) "En la línea #{line} '#{key}' se copia en sí mismo." end
39
+ def self.expected_empty_with_key(key) "Se esperaba un elemento vacío con la clave '#{key}'." end
40
+ def self.expected_field_with_key(key) "Se esperaba una casilla con la clave '#{key}'." end
41
+ def self.expected_fields_with_key(key) "Solo se esperaban casillas con la clave '#{key}'." end
42
+ def self.expected_fieldset_with_key(key) "Se esperaba una collecíon de casillas con la clave '#{key}'." end
43
+ def self.expected_fieldsets_with_key(key) "Solo se esperaban colleciones de casillas con la clave '#{key}'." end
44
+ def self.expected_list_with_key(key) "Se esperaba una lista con la clave '#{key}'." end
45
+ def self.expected_lists_with_key(key) "Solo se esperaban listas con la clave '#{key}'." end
46
+ def self.expected_section_with_key(key) "Se esperaba una sección con la clave '#{key}'." end
47
+ def self.expected_sections_with_key(key) "Solo se esperaban secciones con la clave '#{key}'." end
48
+ def self.expected_single_element_with_key(key) "Solo se esperaba un único elemento con la clave '#{key}'." end
49
+ def self.expected_single_empty_with_key(key) "Solo se esperaba un único elemento vacío con la clave '#{key}'." end
50
+ def self.expected_single_field_with_key(key) "Solo se esperaba una sola casilla con la clave '#{key}'." end
51
+ def self.expected_single_fieldset_entry_with_key(key) "Solo se esperaba una sola casilla de collecíon con la clave '#{key}'." end
52
+ def self.expected_single_fieldset_with_key(key) "Solo se esperaba una sola collecíon de casillas con la clave '#{key}'." end
53
+ def self.expected_single_list_with_key(key) "Solo se esperaba una sola lista con la clave '#{key}'." end
54
+ def self.expected_single_section_with_key(key) "Solo se esperaba una sola sección con la clave '#{key}'." end
55
+ def self.invalid_line(line) "Línea #{line} no sigue un patrón especificado." end
56
+ def self.key_error(message) "Hay un problema con la clave de este elemento: #{message}" end
57
+ def self.missing_element_for_continuation(line) "Línea #{line} contiene una continuacíon de línea sin un elemento que se puede continuar empezado antes." end
58
+ def self.missing_element_with_key(key) "Falta el elemento '#{key}' - si se proporcionó, mira por errores ortográficos y también distingue entre mayúsculas y minúsculas." end
59
+ def self.missing_empty_with_key(key) "Falta el elemento vacío '#{key}' - si se proporcionó, mira por errores ortográficos y también distingue entre mayúsculas y minúsculas." end
60
+ def self.missing_field_value(key) "La casilla '#{key}' debe contener un valor." end
61
+ def self.missing_field_with_key(key) "Falta la casilla '#{key}' - si se proporcionó, mira por errores ortográficos y también distingue entre mayúsculas y minúsculas." end
62
+ def self.missing_fieldset_entry_value(key) "La casilla de collecíon '#{key}' debe contener un valor." end
63
+ def self.missing_fieldset_entry_with_key(key) "Falta la casilla de collecíon '#{key}' - si se proporcionó, mira por errores ortográficos y también distingue entre mayúsculas y minúsculas." end
64
+ def self.missing_fieldset_for_fieldset_entry(line) "Línea #{line} contiene una casilla de collecíon sin una collecíon de casillas empezada antes." end
65
+ def self.missing_fieldset_with_key(key) "Falta la collecíon de casillas '#{key}' - si se proporcionó, mira por errores ortográficos y también distingue entre mayúsculas y minúsculas." end
66
+ def self.missing_list_for_list_item(line) "Línea #{line} contiene una entrada de lista sin una lista empezada antes." end
67
+ def self.missing_list_item_value(key) "La lista '#{key}' no debe contener entradas vacías." end
68
+ def self.missing_list_with_key(key) "Falta la lista '#{key}' - si se proporcionó, mira por errores ortográficos y también distingue entre mayúsculas y minúsculas." end
69
+ def self.missing_section_with_key(key) "Falta la sección '#{key}' - si se proporcionó, mira por errores ortográficos y también distingue entre mayúsculas y minúsculas." end
70
+ def self.non_section_element_not_found(line, key) "En la línea #{line} debe ser copiado el elemento no sección '#{key}', pero no se encontró." end
71
+ def self.section_hierarchy_layer_skip(line) "Línea #{line} inicia una sección que es más de un nivel más bajo el actual." end
72
+ def self.section_not_found(line, key) "En la línea #{line} debe ser copiado la sección '#{key}', pero no se encontró." end
73
+ def self.two_or_more_templates_found(key) "Hay como mínimo dos elementos con la clave '#{key}' que se clasifiquen para estar copiado, no está claro cual debe ser copiado." end
74
+ def self.unterminated_escaped_key(line) "En la línea #{line}, la clave de un elemento se escapa, pero esta secuencia de escape no termina hasta el final de la línea." end
75
+ def self.unterminated_multiline_field(key, line) "La casilla de múltiples líneas '#{key}' que comienza en la línea #{line} no termina hasta el final del documento." end
76
+ def self.value_error(message) "Hay un problema con el valor de este elemento: #{message}" end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ def self.parse(input, **options)
5
+ context = Context.new(input, **options)
6
+
7
+ Section.new(context, context.document)
8
+ end
9
+ end
@@ -0,0 +1,708 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Enolib
4
+ class Parser
5
+ def initialize(context)
6
+ @context = context
7
+ @depth = 0
8
+ @index = 0
9
+ @line = 0
10
+ @unresolved_non_section_elements = nil
11
+ @unresolved_sections = nil
12
+ end
13
+
14
+ def run
15
+ if @context.input.empty?
16
+ @context.line_count = 1
17
+ return
18
+ end
19
+
20
+ comments = nil
21
+ last_continuable_element = nil
22
+ last_non_section_element = nil
23
+ last_section = @context.document
24
+
25
+ while @index < @context.input.length
26
+ match = Grammar::REGEX.match(@context.input, @index)
27
+
28
+ unless match && match.begin(0) == @index
29
+ instruction = tokenize_error_context
30
+ raise Errors::Parsing.invalid_line(@context, instruction)
31
+ end
32
+
33
+ instruction = {
34
+ index: @index,
35
+ line: @line,
36
+ ranges: {
37
+ line: match.offset(0)
38
+ }
39
+ }
40
+
41
+ multiline_field = false
42
+
43
+ if match[Grammar::EMPTY_LINE_INDEX]
44
+
45
+ instruction[:type] = :empty_line
46
+
47
+ if comments
48
+ @context.meta.concat(comments)
49
+ comments = nil
50
+ end
51
+
52
+ elsif match[Grammar::ELEMENT_OPERATOR_INDEX]
53
+
54
+ if comments
55
+ instruction[:comments] = comments
56
+ comments = nil
57
+ end
58
+
59
+ instruction[:key] = match[Grammar::KEY_UNESCAPED_INDEX]
60
+
61
+ if instruction[:key]
62
+ instruction[:ranges][:element_operator] = match.offset(Grammar::ELEMENT_OPERATOR_INDEX)
63
+ instruction[:ranges][:key] = match.offset(Grammar::KEY_UNESCAPED_INDEX)
64
+ else
65
+ instruction[:key] = match[Grammar::KEY_ESCAPED_INDEX]
66
+ instruction[:ranges][:element_operator] = match.offset(Grammar::ELEMENT_OPERATOR_INDEX)
67
+ instruction[:ranges][:escape_begin_operator] = match.offset(Grammar::KEY_ESCAPE_BEGIN_OPERATOR_INDEX)
68
+ instruction[:ranges][:escape_end_operator] = match.offset(Grammar::KEY_ESCAPE_END_OPERATOR_INDEX)
69
+ instruction[:ranges][:key] = match.offset(Grammar::KEY_ESCAPED_INDEX)
70
+ end
71
+
72
+ value = match[Grammar::FIELD_VALUE_INDEX]
73
+
74
+ if value
75
+ instruction[:ranges][:value] = match.offset(Grammar::FIELD_VALUE_INDEX)
76
+ instruction[:type] = :field
77
+ instruction[:value] = value
78
+ else
79
+ instruction[:type] = :empty_element
80
+ end
81
+
82
+ instruction[:parent] = last_section
83
+ last_section[:elements].push(instruction)
84
+ last_continuable_element = instruction
85
+ last_non_section_element = instruction
86
+
87
+ elsif match[Grammar::LIST_ITEM_OPERATOR_INDEX]
88
+
89
+ if comments
90
+ instruction[:comments] = comments
91
+ comments = nil
92
+ end
93
+
94
+ instruction[:ranges][:item_operator] = match.offset(Grammar::LIST_ITEM_OPERATOR_INDEX)
95
+ instruction[:type] = :list_item
96
+
97
+ value = match[Grammar::LIST_ITEM_VALUE_INDEX]
98
+
99
+ if value
100
+ instruction[:ranges][:value] = match.offset(Grammar::LIST_ITEM_VALUE_INDEX)
101
+ instruction[:value] = value
102
+ end
103
+
104
+ if !last_non_section_element
105
+ instruction = tokenize_error_context
106
+ raise Errors::Parsing.missing_list_for_list_item(@context, instruction)
107
+ elsif last_non_section_element[:type] == :list
108
+ last_non_section_element[:items].push(instruction)
109
+ elsif last_non_section_element[:type] == :empty_element
110
+ last_non_section_element[:items] = [instruction]
111
+ last_non_section_element[:type] = :list
112
+ else
113
+ instruction = tokenize_error_context
114
+ raise Errors::Parsing.missing_list_for_list_item(@context, instruction)
115
+ end
116
+
117
+ instruction[:parent] = last_non_section_element
118
+ last_continuable_element = instruction
119
+
120
+ elsif match[Grammar::FIELDSET_ENTRY_OPERATOR_INDEX]
121
+
122
+ if comments
123
+ instruction[:comments] = comments
124
+ comments = nil
125
+ end
126
+
127
+ instruction[:type] = :fieldset_entry
128
+
129
+ instruction[:key] = match[Grammar::KEY_UNESCAPED_INDEX]
130
+
131
+ if instruction[:key]
132
+ instruction[:ranges][:key] = match.offset(Grammar::KEY_UNESCAPED_INDEX)
133
+ instruction[:ranges][:entry_operator] = match.offset(Grammar::FIELDSET_ENTRY_OPERATOR_INDEX)
134
+ else
135
+ instruction[:key] = match[Grammar::KEY_ESCAPED_INDEX]
136
+ instruction[:ranges][:entry_operator] = match.offset(Grammar::FIELDSET_ENTRY_OPERATOR_INDEX)
137
+ instruction[:ranges][:escape_begin_operator] = match.offset(Grammar::KEY_ESCAPE_BEGIN_OPERATOR_INDEX)
138
+ instruction[:ranges][:escape_end_operator] = match.offset(Grammar::KEY_ESCAPE_END_OPERATOR_INDEX)
139
+ instruction[:ranges][:key] = match.offset(Grammar::KEY_ESCAPED_INDEX)
140
+ end
141
+
142
+ value = match[Grammar::FIELDSET_ENTRY_VALUE_INDEX]
143
+
144
+ if value
145
+ instruction[:ranges][:value] = match.offset(Grammar::FIELDSET_ENTRY_VALUE_INDEX)
146
+ instruction[:value] = value
147
+ end
148
+
149
+ if !last_non_section_element
150
+ instruction = tokenize_error_context
151
+ raise Errors::Parsing.missing_fieldset_for_fieldset_entry(@context, instruction)
152
+ elsif last_non_section_element[:type] == :fieldset
153
+ last_non_section_element[:entries].push(instruction)
154
+ elsif last_non_section_element[:type] == :empty_element
155
+ last_non_section_element[:entries] = [instruction]
156
+ last_non_section_element[:type] = :fieldset
157
+ else
158
+ instruction = tokenize_error_context
159
+ raise Errors::Parsing.missing_fieldset_for_fieldset_entry(@context, instruction)
160
+ end
161
+
162
+ instruction[:parent] = last_non_section_element
163
+ last_continuable_element = instruction
164
+
165
+ elsif match[Grammar::SPACED_LINE_CONTINUATION_OPERATOR_INDEX]
166
+
167
+ if !last_continuable_element
168
+ instruction = tokenize_error_context
169
+ raise Errors::Parsing.missing_element_for_continuation(@context, instruction)
170
+ end
171
+
172
+ instruction[:spaced] = true
173
+ instruction[:ranges][:spaced_line_continuation_operator] = match.offset(Grammar::SPACED_LINE_CONTINUATION_OPERATOR_INDEX)
174
+ instruction[:type] = :continuation
175
+
176
+ value = match[Grammar::SPACED_LINE_CONTINUATION_VALUE_INDEX]
177
+
178
+ if value
179
+ instruction[:ranges][:value] = match.offset(Grammar::SPACED_LINE_CONTINUATION_VALUE_INDEX)
180
+ instruction[:value] = value
181
+ end
182
+
183
+ if last_continuable_element.has_key?(:continuations)
184
+ last_continuable_element[:continuations].push(instruction)
185
+ else
186
+ if last_continuable_element[:type] == :empty_element
187
+ last_continuable_element[:type] = :field
188
+ end
189
+
190
+ last_continuable_element[:continuations] = [instruction]
191
+ end
192
+
193
+ if comments
194
+ @context.meta.concat(comments)
195
+ comments = nil
196
+ end
197
+
198
+ elsif match[Grammar::DIRECT_LINE_CONTINUATION_OPERATOR_INDEX]
199
+
200
+ if !last_continuable_element
201
+ instruction = tokenize_error_context
202
+ raise Errors::Parsing.missing_element_for_continuation(@context, instruction)
203
+ end
204
+
205
+ instruction[:ranges][:direct_line_continuation_operator] = match.offset(Grammar::DIRECT_LINE_CONTINUATION_OPERATOR_INDEX)
206
+ instruction[:type] = :continuation
207
+
208
+ value = match[Grammar::DIRECT_LINE_CONTINUATION_VALUE_INDEX]
209
+
210
+ if value
211
+ instruction[:ranges][:value] = match.offset(Grammar::DIRECT_LINE_CONTINUATION_VALUE_INDEX)
212
+ instruction[:value] = value
213
+ end
214
+
215
+ if last_continuable_element.has_key?(:continuations)
216
+ last_continuable_element[:continuations].push(instruction)
217
+ else
218
+ if last_continuable_element[:type] == :empty_element
219
+ last_continuable_element[:type] = :field
220
+ end
221
+
222
+ last_continuable_element[:continuations] = [instruction]
223
+ end
224
+
225
+ if comments
226
+ @context.meta.concat(comments)
227
+ comments = nil
228
+ end
229
+
230
+ elsif match[Grammar::SECTION_OPERATOR_INDEX]
231
+
232
+ if comments
233
+ instruction[:comments] = comments
234
+ comments = nil
235
+ end
236
+
237
+ instruction[:elements] = []
238
+ instruction[:ranges][:section_operator] = match.offset(Grammar::SECTION_OPERATOR_INDEX)
239
+ instruction[:type] = :section
240
+
241
+ new_depth = instruction[:ranges][:section_operator][1] - instruction[:ranges][:section_operator][0]
242
+
243
+ if new_depth == @depth + 1
244
+ instruction[:parent] = last_section
245
+ @depth = new_depth
246
+ elsif new_depth == @depth
247
+ instruction[:parent] = last_section[:parent]
248
+ elsif new_depth < @depth
249
+ while new_depth < @depth
250
+ last_section = last_section[:parent]
251
+ @depth -= 1
252
+ end
253
+
254
+ instruction[:parent] = last_section[:parent]
255
+ else
256
+ instruction = tokenize_error_context
257
+ raise Errors::Parsing.section_hierarchy_layer_skip(@context, instruction, last_section)
258
+ end
259
+
260
+ instruction[:parent][:elements].push(instruction)
261
+ last_section = instruction
262
+
263
+ instruction[:key] = match[Grammar::SECTION_KEY_UNESCAPED_INDEX]
264
+
265
+ if instruction[:key]
266
+ instruction[:ranges][:key] = match.offset(Grammar::SECTION_KEY_UNESCAPED_INDEX)
267
+ else
268
+ instruction[:key] = match[Grammar::SECTION_KEY_ESCAPED_INDEX]
269
+ instruction[:ranges][:escape_begin_operator] = match.offset(Grammar::SECTION_KEY_ESCAPE_BEGIN_OPERATOR_INDEX)
270
+ instruction[:ranges][:escape_end_operator] = match.offset(Grammar::SECTION_KEY_ESCAPE_END_OPERATOR_INDEX)
271
+ instruction[:ranges][:key] = match.offset(Grammar::SECTION_KEY_ESCAPED_INDEX)
272
+ end
273
+
274
+ template = match[Grammar::SECTION_TEMPLATE_INDEX]
275
+
276
+ if template
277
+ instruction[:ranges][:template] = match.offset(Grammar::SECTION_TEMPLATE_INDEX)
278
+ instruction[:template] = template
279
+
280
+ parent = instruction[:parent]
281
+ while parent[:type] != :document
282
+ parent[:deep_resolve] = true
283
+ parent = parent[:parent]
284
+ end
285
+
286
+ copy_operator_offset = match.offset(Grammar::SECTION_COPY_OPERATOR_INDEX)
287
+
288
+ if copy_operator_offset[1] - copy_operator_offset[0] == 2
289
+ instruction[:deep_copy] = true
290
+ instruction[:ranges][:deep_copy_operator] = copy_operator_offset
291
+ else
292
+ instruction[:ranges][:copy_operator] = copy_operator_offset
293
+ end
294
+
295
+ @unresolved_sections = {} unless @unresolved_sections
296
+
297
+ if @unresolved_sections.has_key?(template)
298
+ @unresolved_sections[template][:targets].push(instruction)
299
+ else
300
+ @unresolved_sections[template] = { targets: [instruction] }
301
+ end
302
+
303
+ instruction[:copy] = @unresolved_sections[template]
304
+ end
305
+
306
+ last_continuable_element = nil
307
+ last_non_section_element = nil
308
+
309
+ elsif match[Grammar::MULTILINE_FIELD_OPERATOR_INDEX]
310
+
311
+ if comments
312
+ instruction[:comments] = comments
313
+ comments = nil
314
+ end
315
+
316
+ operator = match[Grammar::MULTILINE_FIELD_OPERATOR_INDEX]
317
+ key = match[Grammar::MULTILINE_FIELD_KEY_INDEX]
318
+
319
+ instruction[:key] = key
320
+ instruction[:parent] = last_section
321
+ instruction[:ranges][:multiline_field_operator] = match.offset(Grammar::MULTILINE_FIELD_OPERATOR_INDEX)
322
+ instruction[:ranges][:key] = match.offset(Grammar::MULTILINE_FIELD_KEY_INDEX)
323
+ instruction[:type] = :multiline_field_begin
324
+
325
+ @index = match.end(0)
326
+
327
+ last_section[:elements].push(instruction)
328
+ last_continuable_element = nil
329
+ last_non_section_element = instruction
330
+
331
+ terminator_regex = /\n[^\S\n]*(#{operator})(?!-)[^\S\n]*(#{Regexp.escape(key)})[^\S\n]*(?=\n|$)/
332
+ terminator_match = terminator_regex.match(@context.input, @index)
333
+
334
+ @index += 1 # move past current char (\n) into next line
335
+ @line += 1
336
+
337
+ unless terminator_match
338
+ tokenize_error_context
339
+ raise Errors::Parsing.unterminated_multiline_field(@context, instruction)
340
+ end
341
+
342
+ end_of_multiline_field_index = terminator_match.begin(0)
343
+
344
+ if end_of_multiline_field_index != @index - 1
345
+ instruction[:lines] = []
346
+
347
+ loop do
348
+ end_of_line_index = @context.input.index("\n", @index)
349
+
350
+ if !end_of_line_index || end_of_line_index >= end_of_multiline_field_index
351
+ last_non_section_element[:lines].push(
352
+ line: @line,
353
+ ranges: {
354
+ line: [@index, end_of_multiline_field_index],
355
+ value: [@index, end_of_multiline_field_index]
356
+ },
357
+ type: :multiline_field_value
358
+ )
359
+
360
+ @index = end_of_multiline_field_index + 1
361
+ @line += 1
362
+
363
+ break
364
+ else
365
+ last_non_section_element[:lines].push(
366
+ line: @line,
367
+ ranges: {
368
+ line: [@index, end_of_line_index],
369
+ value: [@index, end_of_line_index]
370
+ },
371
+ type: :multiline_field_value
372
+ )
373
+
374
+ @index = end_of_line_index + 1
375
+ @line += 1
376
+ end
377
+ end
378
+ end
379
+
380
+ instruction = {
381
+ length: terminator_match.end(0),
382
+ line: @line,
383
+ ranges: {
384
+ key: terminator_match.offset(2),
385
+ line: [@index, terminator_match.end(0)],
386
+ multiline_field_operator: terminator_match.offset(1),
387
+ },
388
+ type: :multiline_field_end
389
+ }
390
+
391
+ last_non_section_element[:end] = instruction
392
+ last_non_section_element = nil
393
+
394
+ @index = terminator_match.end(0) + 1
395
+ @line += 1
396
+
397
+ multiline_field = true
398
+
399
+ elsif match[Grammar::COMMENT_OPERATOR_INDEX]
400
+
401
+ if comments
402
+ comments.push(instruction)
403
+ else
404
+ comments = [instruction]
405
+ end
406
+
407
+ instruction[:ranges][:comment_operator] = match.offset(Grammar::COMMENT_OPERATOR_INDEX)
408
+ instruction[:type] = :comment
409
+
410
+ comment = match[Grammar::COMMENT_VALUE_INDEX]
411
+
412
+ if comment
413
+ instruction[:comment] = comment
414
+ instruction[:ranges][:comment] = match.offset(Grammar::COMMENT_VALUE_INDEX)
415
+ end
416
+
417
+ elsif match[Grammar::COPY_OPERATOR_INDEX]
418
+
419
+ if comments
420
+ instruction[:comments] = comments
421
+ comments = nil
422
+ end
423
+
424
+ template = match[Grammar::TEMPLATE_INDEX]
425
+
426
+ instruction[:ranges][:copy_operator] = match.offset(Grammar::COPY_OPERATOR_INDEX)
427
+ instruction[:ranges][:template] = match.offset(Grammar::TEMPLATE_INDEX)
428
+ instruction[:template] = template
429
+ instruction[:type] = :empty_element
430
+
431
+ instruction[:key] = match[Grammar::KEY_UNESCAPED_INDEX]
432
+
433
+ if instruction[:key]
434
+ instruction[:ranges][:key] = match.offset(Grammar::KEY_UNESCAPED_INDEX)
435
+ else
436
+ instruction[:key] = match[Grammar::KEY_ESCAPED_INDEX]
437
+ instruction[:ranges][:escape_begin_operator] = match.offset(Grammar::KEY_ESCAPE_BEGIN_OPERATOR_INDEX)
438
+ instruction[:ranges][:escape_end_operator] = match.offset(Grammar::KEY_ESCAPE_END_OPERATOR_INDEX)
439
+ instruction[:ranges][:key] = match.offset(Grammar::KEY_ESCAPED_INDEX)
440
+ end
441
+
442
+ instruction[:parent] = last_section
443
+ last_section[:elements].push(instruction)
444
+ last_continuable_element = nil
445
+ last_non_section_element = instruction
446
+
447
+ @unresolved_non_section_elements = {} unless @unresolved_non_section_elements
448
+
449
+ if @unresolved_non_section_elements.has_key?(template)
450
+ @unresolved_non_section_elements[template][:targets].push(instruction)
451
+ else
452
+ @unresolved_non_section_elements[template] = { targets: [instruction] }
453
+ end
454
+
455
+ instruction[:copy] = @unresolved_non_section_elements[template]
456
+ end
457
+
458
+ unless multiline_field
459
+ @index = match.end(0) + 1
460
+ @line += 1
461
+ end
462
+ end
463
+
464
+ if @context.input[-1] == "\n"
465
+ @context.line_count = @line + 1
466
+ else
467
+ @context.line_count = @line
468
+ end
469
+
470
+ @context.meta.concat(comments) if comments
471
+
472
+ resolve if @unresolved_non_section_elements || @unresolved_sections
473
+ end
474
+
475
+ private
476
+
477
+ def consolidate_non_section_elements(element, template)
478
+ if template.has_key?(:comments) && !element.has_key?(:comments)
479
+ element[:comments] = template[:comments]
480
+ end
481
+
482
+ case element[:type]
483
+ when :empty_element
484
+ case template[:type]
485
+ when :multiline_field_begin
486
+ element[:type] = :field
487
+ mirror(element, template)
488
+ when :field
489
+ element[:type] = :field
490
+ mirror(element, template)
491
+ when :fieldset
492
+ element[:type] = :fieldset
493
+ mirror(element, template)
494
+ when :list
495
+ element[:type] = :list
496
+ mirror(element, template)
497
+ end
498
+ when :fieldset
499
+ case template[:type]
500
+ when :fieldset
501
+ element[:extend] = template
502
+ when :field, :list, :multiline_field_begin
503
+ raise Errors::Parsing.missing_fieldset_for_fieldset_entry(@context, element[:entries].first)
504
+ end
505
+ when :list
506
+ case template[:type]
507
+ when :list
508
+ element[:extend] = template
509
+ when :field, :fieldset, :multiline_field_begin
510
+ raise Errors::Parsing.missing_list_for_list_item(@context, element[:items].first)
511
+ end
512
+ end
513
+ end
514
+
515
+ def consolidate_sections(section, template, deep_merge)
516
+ if template.has_key?(:comments) && !section.has_key?(:comments)
517
+ section[:comments] = template[:comments]
518
+ end
519
+
520
+ if section[:elements].empty?
521
+ mirror(section, template)
522
+ else
523
+ # TODO: Handle possibility of two templates (one hardcoded in the document, one implicitly derived through deep merging)
524
+ # Possibly also elswhere (e.g. up there in the mirror branch?)
525
+ section[:extend] = template
526
+
527
+ return unless deep_merge
528
+
529
+ merge_map = {}
530
+
531
+ section[:elements].each do |section_element|
532
+ if section_element[:type] != :section || merge_map.has_key?(section_element[:key])
533
+ merge_map[section_element[:key]] = false # non-mergable (no section or multiple instructions with same key)
534
+ else
535
+ merge_map[section_element[:key]] = { section: section_element }
536
+ end
537
+ end
538
+
539
+ template[:elements].each do |section_element|
540
+ if merge_map.has_key?(section_element[:key])
541
+ merger = merge_map[section_element[:key]]
542
+
543
+ next unless merger
544
+
545
+ if section_element[:type] != :section || merger.has_key?(:template)
546
+ merge_map[section_element[:key]] = false # non-mergable (no section or multiple template instructions with same key)
547
+ else
548
+ merger[:template] = section_element
549
+ end
550
+ end
551
+ end
552
+
553
+ merge_map.each_value do |merger|
554
+ if merger && merger.has_key?(:template)
555
+ consolidate_sections(merger[:section], merger[:template], true)
556
+ end
557
+ end
558
+ end
559
+ end
560
+
561
+ def mirror(element, template)
562
+ if template.has_key?(:mirror)
563
+ element[:mirror] = template[:mirror]
564
+ else
565
+ element[:mirror] = template
566
+ end
567
+ end
568
+
569
+ def index(section)
570
+ section[:elements].each do |element|
571
+ if element[:type] == :section
572
+ index(element)
573
+
574
+ if @unresolved_sections &&
575
+ @unresolved_sections.has_key?(element[:key]) &&
576
+ element[:key] != element[:template]
577
+ copy_data = @unresolved_sections[element[:key]]
578
+
579
+ if copy_data.has_key?(:template)
580
+ raise Errors::Parsing.two_or_more_templates_found(@context, copy_data[:targets].first, copy_data[:template], element)
581
+ end
582
+
583
+ copy_data[:template] = element
584
+ end
585
+ elsif @unresolved_non_section_elements &&
586
+ @unresolved_non_section_elements.has_key?(element[:key]) &&
587
+ element[:key] != element[:template]
588
+ copy_data = @unresolved_non_section_elements[element[:key]]
589
+
590
+ if copy_data.has_key?(:template)
591
+ raise Errors::Parsing.two_or_more_templates_found(@context, copy_data[:targets].first, copy_data[:template], element)
592
+ end
593
+
594
+ copy_data[:template] = element
595
+ end
596
+ end
597
+ end
598
+
599
+ def resolve
600
+ index(@context.document)
601
+
602
+ if @unresolved_non_section_elements
603
+ @unresolved_non_section_elements.each_value do |copy|
604
+ unless copy.has_key?(:template)
605
+ raise Errors::Parsing.non_section_element_not_found(@context, copy[:targets].first)
606
+ end
607
+
608
+ copy[:targets].each do |target|
609
+ resolve_non_section_element(target) if target.has_key?(:copy)
610
+ end
611
+ end
612
+ end
613
+
614
+ if @unresolved_sections
615
+ @unresolved_sections.each_value do |copy|
616
+ unless copy.has_key?(:template)
617
+ raise Errors::Parsing.section_not_found(@context, copy[:targets].first)
618
+ end
619
+
620
+ copy[:targets].each do |target|
621
+ resolve_section(target) if target.has_key?(:copy)
622
+ end
623
+ end
624
+ end
625
+ end
626
+
627
+ def resolve_non_section_element(element, previous_elements = [])
628
+ if previous_elements.include?(element)
629
+ raise Errors::Parsing.cyclic_dependency(@context, element, previous_elements)
630
+ end
631
+
632
+ template = element[:copy][:template]
633
+
634
+ if template.has_key?(:copy)
635
+ resolve_non_section_element(template, [*previous_elements, element])
636
+ end
637
+
638
+ consolidate_non_section_elements(element, template)
639
+
640
+ element.delete(:copy)
641
+ end
642
+
643
+ def resolve_section(section, previous_sections = [])
644
+ if previous_sections.include?(section)
645
+ raise Errors::Parsing.cyclic_dependency(@context, section, previous_sections)
646
+ end
647
+
648
+ if section.has_key?(:deep_resolve)
649
+ section[:elements].each do |section_element|
650
+ if section_element[:type] == :section &&
651
+ (section_element.has_key?(:copy) || section_element.has_key?(:deep_resolve))
652
+ resolve_section(section_element, [*previous_sections, section])
653
+ end
654
+ end
655
+ end
656
+
657
+ if section.has_key?(:copy)
658
+ template = section[:copy][:template]
659
+
660
+ if template.has_key?(:copy) || template.has_key?(:deep_resolve)
661
+ resolve_section(template, [*previous_sections, section])
662
+ end
663
+
664
+ consolidate_sections(section, template, section.has_key?(:deep_copy))
665
+
666
+ section.delete(:copy)
667
+ end
668
+ end
669
+
670
+ def tokenize_error_context
671
+ first_instruction = nil
672
+
673
+ loop do
674
+ end_of_line_index = @context.input.index("\n", @index)
675
+
676
+ if end_of_line_index
677
+ instruction = {
678
+ line: @line,
679
+ ranges: { line: [@index, end_of_line_index] }
680
+ }
681
+
682
+ @context.meta.push(instruction)
683
+
684
+ if first_instruction
685
+ instruction[:type] = :unparsed
686
+ else
687
+ first_instruction = instruction
688
+ end
689
+
690
+ @index = end_of_line_index + 1
691
+ @line += 1
692
+ else
693
+ instruction = {
694
+ line: @line,
695
+ ranges: { line: [@index, @context.input.length]}
696
+ }
697
+
698
+ instruction[:type] = :unparsed if first_instruction
699
+
700
+ @context.line_count = @line + 1
701
+ @context.meta.push(instruction)
702
+
703
+ return first_instruction || instruction
704
+ end
705
+ end
706
+ end
707
+ end
708
+ end