plain-parser 0.1.2.dev7__tar.gz → 0.1.3.dev8__tar.gz

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 (153) hide show
  1. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/PKG-INFO +1 -1
  2. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/src/plain_parser/plain_file.py +12 -3
  3. plain_parser-0.1.3.dev8/tests/data/requires/shared_export_above.plain +12 -0
  4. plain_parser-0.1.3.dev8/tests/data/requires/shared_export_base.plain +16 -0
  5. plain_parser-0.1.3.dev8/tests/data/requires/shared_export_middle.plain +12 -0
  6. plain_parser-0.1.3.dev8/tests/data/requires/shared_export_top.plain +13 -0
  7. plain_parser-0.1.3.dev8/tests/test_requires.py +269 -0
  8. plain_parser-0.1.2.dev7/tests/test_requires.py +0 -60
  9. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/.flake8 +0 -0
  10. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/.github/dependabot.yml +0 -0
  11. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/.github/workflows/lint-and-test.yml +0 -0
  12. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/.github/workflows/publish-dev-to-pypi.yml +0 -0
  13. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/.github/workflows/publish-to-pypi.yml +0 -0
  14. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/.gitignore +0 -0
  15. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/LICENSE +0 -0
  16. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/README.md +0 -0
  17. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/pyproject.toml +0 -0
  18. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/src/plain_parser/__init__.py +0 -0
  19. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/src/plain_parser/cli.py +0 -0
  20. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/src/plain_parser/concept_utils.py +0 -0
  21. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/src/plain_parser/exceptions.py +0 -0
  22. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/src/plain_parser/graph.py +0 -0
  23. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/src/plain_parser/liquid_nodes.py +0 -0
  24. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/src/plain_parser/loaders.py +0 -0
  25. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/src/plain_parser/plain_spec.py +0 -0
  26. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/__init__.py +0 -0
  27. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/conftest.py +0 -0
  28. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/acceptance_test_binary_resource.plain +0 -0
  29. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/acceptance_test_resource.plain +0 -0
  30. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/base64_resource.plain +0 -0
  31. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/binary.bin +0 -0
  32. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/binary_resource.plain +0 -0
  33. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/code_variable_conflict.plain +0 -0
  34. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/code_variable_template.plain +0 -0
  35. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/missing_resource.plain +0 -0
  36. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/not_plain.md +0 -0
  37. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/notes.md +0 -0
  38. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/requires_binary_resource_base.plain +0 -0
  39. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/requires_binary_resource_top.plain +0 -0
  40. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/subdir/nested.plain +0 -0
  41. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/undefined_concept.plain +0 -0
  42. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/cli/valid.plain +0 -0
  43. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/imports/circular_imports_1.plain +0 -0
  44. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/imports/circular_imports_2.plain +0 -0
  45. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/imports/circular_imports_main.plain +0 -0
  46. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/imports/diamond_import_1.plain +0 -0
  47. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/imports/diamond_import_2.plain +0 -0
  48. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/imports/diamond_import_common.plain +0 -0
  49. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/imports/diamond_imports_main.plain +0 -0
  50. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/imports/import_with_functionalities.plain +0 -0
  51. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/imports/imports_module_with_functionalities.plain +0 -0
  52. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/imports/non_existent_import.plain +0 -0
  53. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfile/add_new_task_modal_specification.yaml +0 -0
  54. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfile/duplicate_specification_heading.plain +0 -0
  55. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfile/empty_functional_specs_section.plain +0 -0
  56. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfile/invalid_specification_order.plain +0 -0
  57. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfile/missing_non_functional_requirements.plain +0 -0
  58. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfile/no_functional_specs_section.plain +0 -0
  59. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfile/plain_source_with_absolute_link.plain +0 -0
  60. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfile/plain_source_with_missing_link.plain +0 -0
  61. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfile/plain_source_with_url_link.plain +0 -0
  62. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfile/task_list_ui_specification.yaml +0 -0
  63. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfile/task_manager_with_reference_links.plain +0 -0
  64. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfile/without_non_functional_requirement.plain +0 -0
  65. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/concept_validation_acceptance_tests.plain +0 -0
  66. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/concept_validation_acceptance_tests_nondefined.plain +0 -0
  67. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/concept_validation_defined_nondefined.plain +0 -0
  68. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/concept_validation_defined_nondefined_2.plain +0 -0
  69. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/concept_validation_definition.plain +0 -0
  70. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/concept_validation_noconcepts.plain +0 -0
  71. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/concept_validation_nonconcept.plain +0 -0
  72. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/concept_validation_nondefined.plain +0 -0
  73. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/concept_validation_redefinition.plain +0 -0
  74. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/concept_validation_several_concepts.plain +0 -0
  75. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/concept_validation_valid.plain +0 -0
  76. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/cyclic_definitions.plain +0 -0
  77. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/cyclic_definitions_across_import.plain +0 -0
  78. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/cyclic_definitions_long.plain +0 -0
  79. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/cyclic_definitions_sub_bullet.plain +0 -0
  80. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts.plain +0 -0
  81. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_base.plain +0 -0
  82. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_defaults_exported.plain +0 -0
  83. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_example.plain +0 -0
  84. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_imported_exported.plain +0 -0
  85. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_inherited_reexported.plain +0 -0
  86. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_malformed.plain +0 -0
  87. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_missing_definition.plain +0 -0
  88. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_missing_definition_example.plain +0 -0
  89. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_multiple_per_bullet.plain +0 -0
  90. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_multiple_per_bullet_undefined.plain +0 -0
  91. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_nested.plain +0 -0
  92. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_nested_example.plain +0 -0
  93. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_no_definitions.plain +0 -0
  94. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_not_declared.plain +0 -0
  95. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_not_declared_example.plain +0 -0
  96. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_transitive_example.plain +0 -0
  97. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_transitive_l1.plain +0 -0
  98. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/exported_concepts_transitive_l2.plain +0 -0
  99. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/plain_file_parser_with_comments.plain +0 -0
  100. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/plain_file_with_comments_indented.plain +0 -0
  101. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/regular_plain_source.plain +0 -0
  102. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/required_concepts.plain +0 -0
  103. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/required_concepts_defs.plain +0 -0
  104. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/required_concepts_example.plain +0 -0
  105. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/required_concepts_l1.plain +0 -0
  106. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/required_concepts_l2.plain +0 -0
  107. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/required_concepts_missing.plain +0 -0
  108. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/required_concepts_module.plain +0 -0
  109. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/required_concepts_partial.plain +0 -0
  110. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/required_concepts_partial_duplicate.plain +0 -0
  111. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/requires_without_definitions_example.plain +0 -0
  112. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/topological_sort.plain +0 -0
  113. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/plainfileparser/topological_sort_not_referenced.plain +0 -0
  114. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/chain_base.plain +0 -0
  115. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/chain_fork_top.plain +0 -0
  116. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/chain_middle.plain +0 -0
  117. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/chain_top.plain +0 -0
  118. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/circular_requires_main.plain +0 -0
  119. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/circular_requires_sub.plain +0 -0
  120. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/diamond_requires_1.plain +0 -0
  121. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/diamond_requires_2.plain +0 -0
  122. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/diamond_requires_common.plain +0 -0
  123. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/diamond_requires_main.plain +0 -0
  124. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/independent_requires_1.plain +0 -0
  125. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/independent_requires_2.plain +0 -0
  126. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/independent_requires_main.plain +0 -0
  127. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/invalid_ancestor_base.plain +0 -0
  128. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/invalid_ancestor_top.plain +0 -0
  129. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/non_existent_require.plain +0 -0
  130. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/normal_requires_1.plain +0 -0
  131. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/normal_requires_2.plain +0 -0
  132. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/normal_requires_common.plain +0 -0
  133. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/requires/normal_requires_main.plain +0 -0
  134. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/sample_base64_image.txt +0 -0
  135. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/simple.plain +0 -0
  136. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/templates/block_level_include.plain +0 -0
  137. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/templates/code_variables.plain +0 -0
  138. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/templates/header.plain +0 -0
  139. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/templates/implement.plain +0 -0
  140. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/templates/implement_2.plain +0 -0
  141. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/templates/malformed_tag.plain +0 -0
  142. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/templates/template_include.plain +0 -0
  143. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/templates/test_hardest_problem.plain +0 -0
  144. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/data/templates/unterminated_output.plain +0 -0
  145. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/test_cli.py +0 -0
  146. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/test_concept_utils.py +0 -0
  147. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/test_exceptions.py +0 -0
  148. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/test_graph.py +0 -0
  149. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/test_imports.py +0 -0
  150. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/test_loaders.py +0 -0
  151. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/test_plainfile.py +0 -0
  152. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/test_plainfileparser.py +0 -0
  153. {plain_parser-0.1.2.dev7 → plain_parser-0.1.3.dev8}/tests/test_plainspec.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: plain-parser
3
- Version: 0.1.2.dev7
3
+ Version: 0.1.3.dev8
4
4
  Summary: Parser for ***plain specification files
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -715,11 +715,13 @@ def process_required_modules(
715
715
  all_required_modules: list[str],
716
716
  modules_trace: list[str],
717
717
  chain: list[tuple[str, dict]],
718
+ module_exports: dict[str, list[mistletoe.block_token.token]],
718
719
  ) -> list[mistletoe.block_token.token]:
719
720
  """Parse and fully validate every required module, deepest first.
720
721
 
721
- Appends ``(module name, marshalled plain source tree)`` to ``chain`` for each module not already in it
722
- and returns the exported definitions of the directly required modules.
722
+ Appends ``(module name, marshalled plain source tree)`` to ``chain`` for each module not already in it,
723
+ records each module's exported definitions in ``module_exports``, and returns the exported definitions
724
+ of the directly required modules.
723
725
  """
724
726
  exported_definitions = list[mistletoe.block_token.token]()
725
727
  for module_name in required_modules:
@@ -727,6 +729,8 @@ def process_required_modules(
727
729
  raise PlainSyntaxError(f"Plain syntax error: Circular required module detected: {module_name}.")
728
730
 
729
731
  if len(all_required_modules) > 0 and module_name == all_required_modules[-1]:
732
+ # Already processed as an earlier entry; its exports are still visible to this module.
733
+ exported_definitions.extend(module_exports[module_name])
730
734
  continue
731
735
 
732
736
  code_variables: dict = {}
@@ -750,8 +754,10 @@ def process_required_modules(
750
754
  all_required_modules,
751
755
  modules_trace + [module_name],
752
756
  chain,
757
+ module_exports,
753
758
  )
754
759
 
760
+ own_exported_definitions = list[mistletoe.block_token.token]()
755
761
  if EXPORTED_CONCEPTS_DIRECTIVE in plain_file_parse_result.plain_source_obj.metadata:
756
762
  exported_concepts = list[str]()
757
763
  for concept in plain_file_parse_result.plain_source_obj.metadata[EXPORTED_CONCEPTS_DIRECTIVE]:
@@ -759,11 +765,13 @@ def process_required_modules(
759
765
 
760
766
  with PlainRenderer() as renderer:
761
767
  for exported_concept in exported_concepts:
762
- exported_definitions.extend(
768
+ own_exported_definitions.extend(
763
769
  concept_utils.find_concept_definitions_in_plain_source(
764
770
  exported_concept, plain_file_parse_result.plain_source, renderer
765
771
  )
766
772
  )
773
+ module_exports[module_name] = own_exported_definitions
774
+ exported_definitions.extend(own_exported_definitions)
767
775
 
768
776
  marshalled_plain_source = validate_and_marshall_module(
769
777
  plain_file_parse_result, module_name, ancestor_exported_definitions, code_variables, template_dirs
@@ -889,6 +897,7 @@ def _parse_module(
889
897
  all_required_modules=[],
890
898
  modules_trace=[],
891
899
  chain=chain,
900
+ module_exports={},
892
901
  )
893
902
 
894
903
  marshalled_plain_source = validate_and_marshall_module(
@@ -0,0 +1,12 @@
1
+ ---
2
+ requires:
3
+ - shared_export_top
4
+ ---
5
+
6
+ ***implementation reqs***
7
+
8
+ - Above implementation requirement.
9
+
10
+ ***functional specs***
11
+
12
+ - An above functionality.
@@ -0,0 +1,16 @@
1
+ ---
2
+ exported_concepts:
3
+ - :KeyStore:
4
+ ---
5
+
6
+ ***definitions***
7
+
8
+ - :KeyStore: holds encryption keys.
9
+
10
+ ***implementation reqs***
11
+
12
+ - Base implementation requirement.
13
+
14
+ ***functional specs***
15
+
16
+ - :KeyStore: stores a key.
@@ -0,0 +1,12 @@
1
+ ---
2
+ requires:
3
+ - shared_export_base
4
+ ---
5
+
6
+ ***implementation reqs***
7
+
8
+ - The functions of :KeyStore: are used to obtain keys.
9
+
10
+ ***functional specs***
11
+
12
+ - A middle functionality.
@@ -0,0 +1,13 @@
1
+ ---
2
+ requires:
3
+ - shared_export_base
4
+ - shared_export_middle
5
+ ---
6
+
7
+ ***implementation reqs***
8
+
9
+ - Keys are read through :KeyStore:.
10
+
11
+ ***functional specs***
12
+
13
+ - A top functionality.
@@ -0,0 +1,269 @@
1
+ import pytest
2
+
3
+ from plain_parser import plain_file
4
+ from plain_parser.exceptions import ModuleDoesNotExistError, PlainSyntaxError
5
+
6
+
7
+ def test_non_existent_require(get_test_data_path):
8
+ with pytest.raises(ModuleDoesNotExistError, match="Module does not exist"):
9
+ plain_file.plain_file_parser("non_existent_require.plain", [get_test_data_path("data/requires")])
10
+
11
+
12
+ def test_independent_requires(get_test_data_path):
13
+ with pytest.raises(Exception, match="There must be a fixed order how required modules are dependent"):
14
+ plain_file.plain_file_parser("independent_requires_main.plain", [get_test_data_path("data/requires")])
15
+
16
+
17
+ def test_diamond_requires(get_test_data_path):
18
+ with pytest.raises(Exception, match="There must be a fixed order how required modules are dependent"):
19
+ plain_file.plain_file_parser("diamond_requires_main.plain", [get_test_data_path("data/requires")])
20
+
21
+
22
+ def test_circular_requires(get_test_data_path):
23
+ with pytest.raises(Exception, match="Circular required module detected"):
24
+ plain_file.plain_file_parser("circular_requires_main.plain", [get_test_data_path("data/requires")])
25
+
26
+
27
+ def test_normal_requires(get_test_data_path):
28
+ plain_file.plain_file_parser("normal_requires_main.plain", [get_test_data_path("data/requires")])
29
+
30
+
31
+ def test_parse_module_chain_orders_required_modules_first(get_test_data_path):
32
+ chain = plain_file.parse_module_chain("chain_top.plain", [get_test_data_path("data/requires")])
33
+
34
+ assert [module_name for module_name, _ in chain] == ["chain_base", "chain_middle", "chain_top"]
35
+ for _, plain_source_tree in chain:
36
+ assert isinstance(plain_source_tree, dict)
37
+ assert "functional specs" in plain_source_tree
38
+
39
+
40
+ def test_parse_module_chain_single_module(get_test_data_path):
41
+ chain = plain_file.parse_module_chain("chain_base.plain", [get_test_data_path("data/requires")])
42
+
43
+ assert [module_name for module_name, _ in chain] == ["chain_base"]
44
+
45
+
46
+ def test_parse_module_chain_deduplicates_shared_ancestor(get_test_data_path):
47
+ chain = plain_file.parse_module_chain("chain_fork_top.plain", [get_test_data_path("data/requires")])
48
+
49
+ assert [module_name for module_name, _ in chain] == ["chain_base", "chain_middle", "chain_fork_top"]
50
+
51
+
52
+ def test_parse_module_chain_propagates_required_module_errors(get_test_data_path):
53
+ with pytest.raises(ModuleDoesNotExistError, match="Module does not exist"):
54
+ plain_file.parse_module_chain("non_existent_require.plain", [get_test_data_path("data/requires")])
55
+
56
+
57
+ def test_plain_file_parser_validates_required_modules(get_test_data_path):
58
+ # The ancestor is fully validated during the parse of the top module, not only when the chain is walked.
59
+ with pytest.raises(Exception, match="Concept :UndefinedThing: is not defined"):
60
+ plain_file.plain_file_parser("invalid_ancestor_top.plain", [get_test_data_path("data/requires")])
61
+
62
+
63
+ def write_module(directory, module_name, exported_concepts, required_modules, definitions, functional_spec):
64
+ exports = "".join(f" - {concept}\n" for concept in exported_concepts)
65
+ requires = "".join(f" - {required_module}\n" for required_module in required_modules)
66
+ (directory / f"{module_name}.plain").write_text(
67
+ "---\n"
68
+ + (f"exported_concepts:\n{exports}" if exported_concepts else "")
69
+ + (f"requires:\n{requires}" if required_modules else "")
70
+ + "---\n\n"
71
+ "***definitions***\n\n"
72
+ + "".join(f"- {definition}\n\n" for definition in definitions)
73
+ + "***implementation reqs***\n\n- An implementation requirement.\n\n"
74
+ f"***functional specs***\n\n- {functional_spec}\n"
75
+ )
76
+
77
+
78
+ # Top requires [Base, Middle] and Middle requires Base. Above requires Top.
79
+ def test_middle_module_sees_exports_of_base_module(get_test_data_path):
80
+ # shared_export_top requires [base, middle]; middle uses :KeyStore: exported by base.
81
+ plain_file.plain_file_parser("shared_export_top.plain", [get_test_data_path("data/requires")])
82
+
83
+
84
+ def test_middle_module_sees_exports_of_base_module_when_parsing_above_module(get_test_data_path):
85
+ # The skip of Base happens in a nested call of process_required_modules.
86
+ chain = plain_file.parse_module_chain("shared_export_above.plain", [get_test_data_path("data/requires")])
87
+
88
+ assert [module_name for module_name, _ in chain] == [
89
+ "shared_export_base",
90
+ "shared_export_middle",
91
+ "shared_export_top",
92
+ "shared_export_above",
93
+ ]
94
+
95
+
96
+ def test_middle_module_redefining_an_export_of_base_module_is_rejected(tmp_path):
97
+ write_module(tmp_path, "Base", [":BaseConcept:"], [], [":BaseConcept: is defined by Base."], "Base works.")
98
+ write_module(
99
+ tmp_path, "Middle", [], ["Base"], [":BaseConcept: is defined again by Middle."], "Middle uses :BaseConcept:."
100
+ )
101
+ write_module(tmp_path, "Top", [], ["Base", "Middle"], [], "Top uses :BaseConcept:.")
102
+
103
+ with pytest.raises(PlainSyntaxError, match="Concepts were defined multiple times: :BaseConcept:"):
104
+ plain_file.parse_module_chain("Top.plain", [str(tmp_path)])
105
+
106
+
107
+ def test_base_module_may_export_nothing(tmp_path):
108
+ write_module(tmp_path, "Base", [], [], [":BaseConcept: is defined by Base."], "Base works.")
109
+ write_module(
110
+ tmp_path, "Middle", [":MiddleConcept:"], ["Base"], [":MiddleConcept: is defined by Middle."], "Middle works."
111
+ )
112
+ write_module(tmp_path, "Top", [], ["Base", "Middle"], [], "Top uses :MiddleConcept:.")
113
+
114
+ chain = plain_file.parse_module_chain("Top.plain", [str(tmp_path)])
115
+
116
+ assert [module_name for module_name, _ in chain] == ["Base", "Middle", "Top"]
117
+
118
+
119
+ def write_modules_using_required_exports(directory, requires_by_module):
120
+ # Writes one <name>.plain per entry, with the listed modules as its requires. Each module exports
121
+ # :<name>Concept: and uses the exported concept of every module it requires, so an export that does
122
+ # not reach a dependent module fails the parse with "Concept ... is not defined".
123
+ for module_name, required_modules in requires_by_module.items():
124
+ used_concepts = "".join(f" :{required_module}Concept:" for required_module in required_modules)
125
+ write_module(
126
+ directory,
127
+ module_name,
128
+ exported_concepts=[f":{module_name}Concept:"],
129
+ required_modules=required_modules,
130
+ definitions=[f":{module_name}Concept: is defined by {module_name}."],
131
+ functional_spec=f":{module_name}Concept: uses{used_concepts or ' nothing'}.",
132
+ )
133
+
134
+
135
+ # In each diagram, a line down from one module to another means the upper module requires the lower one.
136
+
137
+ # Each module requires the modules below it. The parser must return the chain deepest first.
138
+ ACCEPTED_CHAINS = [
139
+ # Top
140
+ # | \
141
+ # | Middle
142
+ # | /
143
+ # Base
144
+ pytest.param(
145
+ {"Top": ["Base", "Middle"], "Middle": ["Base"], "Base": []},
146
+ ["Base", "Middle", "Top"],
147
+ id="top-also-requires-base-before-middle",
148
+ ),
149
+ # Same shape, but Top lists Base after Middle.
150
+ pytest.param(
151
+ {"Top": ["Middle", "Base"], "Middle": ["Base"], "Base": []},
152
+ ["Base", "Middle", "Top"],
153
+ id="top-also-requires-base-after-middle",
154
+ marks=pytest.mark.xfail(strict=True, reason="an ancestor must be listed before its dependent"),
155
+ ),
156
+ # Top
157
+ # |
158
+ # Upper every module also requires every module below it
159
+ # |
160
+ # Middle
161
+ # |
162
+ # Base
163
+ pytest.param(
164
+ {"Top": ["Base", "Middle", "Upper"], "Upper": ["Base", "Middle"], "Middle": ["Base"], "Base": []},
165
+ ["Base", "Middle", "Upper", "Top"],
166
+ id="upper-also-requires-base-and-middle",
167
+ marks=pytest.mark.xfail(strict=True, reason="only the last processed module is recognised as repeated"),
168
+ ),
169
+ ]
170
+
171
+
172
+ # Two branches that meet at a shared base. Rejected with the fixed-order error.
173
+ DIAMONDS = [
174
+ # Top
175
+ # / \
176
+ # Left Right
177
+ # \ /
178
+ # Base
179
+ pytest.param(
180
+ {"Top": ["Left", "Right"], "Left": ["Base"], "Right": ["Base"], "Base": []},
181
+ id="two-branches-share-base",
182
+ ),
183
+ # Same shape, but Top lists Right before Left.
184
+ pytest.param(
185
+ {"Top": ["Right", "Left"], "Left": ["Base"], "Right": ["Base"], "Base": []},
186
+ id="two-branches-share-base-right-listed-first",
187
+ ),
188
+ # Top
189
+ # / \
190
+ # Left Right
191
+ # | |
192
+ # LeftBase RightBase
193
+ # \ /
194
+ # Base
195
+ pytest.param(
196
+ {
197
+ "Top": ["Left", "Right"],
198
+ "Left": ["LeftBase"],
199
+ "Right": ["RightBase"],
200
+ "LeftBase": ["Base"],
201
+ "RightBase": ["Base"],
202
+ "Base": [],
203
+ },
204
+ id="two-branches-share-base-two-levels-down",
205
+ ),
206
+ # Top
207
+ # | \
208
+ # | Right
209
+ # | / \
210
+ # Left Other
211
+ # \ /
212
+ # Base
213
+ pytest.param(
214
+ {"Top": ["Left", "Right"], "Left": ["Base"], "Other": ["Base"], "Right": ["Left", "Other"], "Base": []},
215
+ id="branches-below-right-share-base",
216
+ ),
217
+ # Top
218
+ # / | \
219
+ # Left | Right
220
+ # \ | /
221
+ # Base
222
+ pytest.param(
223
+ {"Top": ["Base", "Left", "Right"], "Left": ["Base"], "Right": ["Base"], "Base": []},
224
+ id="two-branches-share-base-top-also-requires-base",
225
+ ),
226
+ ]
227
+
228
+
229
+ # Branches that share no base. Rejected with the fixed-order error.
230
+ INDEPENDENT_BRANCHES = [
231
+ # Top
232
+ # / \
233
+ # Left Right
234
+ pytest.param({"Top": ["Left", "Right"], "Left": [], "Right": []}, id="two-modules-without-requires"),
235
+ # Top
236
+ # / \
237
+ # Left Right
238
+ # |
239
+ # Other
240
+ pytest.param(
241
+ {"Top": ["Left", "Right"], "Left": [], "Right": ["Other"], "Other": []},
242
+ id="branches-without-shared-base",
243
+ ),
244
+ ]
245
+
246
+
247
+ @pytest.mark.parametrize("requires_by_module, expected_chain", ACCEPTED_CHAINS)
248
+ def test_requires_chain_is_accepted(tmp_path, requires_by_module, expected_chain):
249
+ write_modules_using_required_exports(tmp_path, requires_by_module)
250
+
251
+ chain = plain_file.parse_module_chain("Top.plain", [str(tmp_path)])
252
+
253
+ assert [module_name for module_name, _ in chain] == expected_chain
254
+
255
+
256
+ @pytest.mark.parametrize("requires_by_module", DIAMONDS)
257
+ def test_requires_diamond_is_rejected(tmp_path, requires_by_module):
258
+ write_modules_using_required_exports(tmp_path, requires_by_module)
259
+
260
+ with pytest.raises(PlainSyntaxError, match="There must be a fixed order how required modules are dependent"):
261
+ plain_file.parse_module_chain("Top.plain", [str(tmp_path)])
262
+
263
+
264
+ @pytest.mark.parametrize("requires_by_module", INDEPENDENT_BRANCHES)
265
+ def test_requires_independent_branches_are_rejected(tmp_path, requires_by_module):
266
+ write_modules_using_required_exports(tmp_path, requires_by_module)
267
+
268
+ with pytest.raises(PlainSyntaxError, match="There must be a fixed order how required modules are dependent"):
269
+ plain_file.parse_module_chain("Top.plain", [str(tmp_path)])
@@ -1,60 +0,0 @@
1
- import pytest
2
-
3
- from plain_parser import plain_file
4
- from plain_parser.exceptions import ModuleDoesNotExistError
5
-
6
-
7
- def test_non_existent_require(get_test_data_path):
8
- with pytest.raises(ModuleDoesNotExistError, match="Module does not exist"):
9
- plain_file.plain_file_parser("non_existent_require.plain", [get_test_data_path("data/requires")])
10
-
11
-
12
- def test_independent_requires(get_test_data_path):
13
- with pytest.raises(Exception, match="There must be a fixed order how required modules are dependent"):
14
- plain_file.plain_file_parser("independent_requires_main.plain", [get_test_data_path("data/requires")])
15
-
16
-
17
- def test_diamond_requires(get_test_data_path):
18
- with pytest.raises(Exception, match="There must be a fixed order how required modules are dependent"):
19
- plain_file.plain_file_parser("diamond_requires_main.plain", [get_test_data_path("data/requires")])
20
-
21
-
22
- def test_circular_requires(get_test_data_path):
23
- with pytest.raises(Exception, match="Circular required module detected"):
24
- plain_file.plain_file_parser("circular_requires_main.plain", [get_test_data_path("data/requires")])
25
-
26
-
27
- def test_normal_requires(get_test_data_path):
28
- plain_file.plain_file_parser("normal_requires_main.plain", [get_test_data_path("data/requires")])
29
-
30
-
31
- def test_parse_module_chain_orders_required_modules_first(get_test_data_path):
32
- chain = plain_file.parse_module_chain("chain_top.plain", [get_test_data_path("data/requires")])
33
-
34
- assert [module_name for module_name, _ in chain] == ["chain_base", "chain_middle", "chain_top"]
35
- for _, plain_source_tree in chain:
36
- assert isinstance(plain_source_tree, dict)
37
- assert "functional specs" in plain_source_tree
38
-
39
-
40
- def test_parse_module_chain_single_module(get_test_data_path):
41
- chain = plain_file.parse_module_chain("chain_base.plain", [get_test_data_path("data/requires")])
42
-
43
- assert [module_name for module_name, _ in chain] == ["chain_base"]
44
-
45
-
46
- def test_parse_module_chain_deduplicates_shared_ancestor(get_test_data_path):
47
- chain = plain_file.parse_module_chain("chain_fork_top.plain", [get_test_data_path("data/requires")])
48
-
49
- assert [module_name for module_name, _ in chain] == ["chain_base", "chain_middle", "chain_fork_top"]
50
-
51
-
52
- def test_parse_module_chain_propagates_required_module_errors(get_test_data_path):
53
- with pytest.raises(ModuleDoesNotExistError, match="Module does not exist"):
54
- plain_file.parse_module_chain("non_existent_require.plain", [get_test_data_path("data/requires")])
55
-
56
-
57
- def test_plain_file_parser_validates_required_modules(get_test_data_path):
58
- # The ancestor is fully validated during the parse of the top module, not only when the chain is walked.
59
- with pytest.raises(Exception, match="Concept :UndefinedThing: is not defined"):
60
- plain_file.plain_file_parser("invalid_ancestor_top.plain", [get_test_data_path("data/requires")])