periplus-map 0.2.1__py3-none-any.whl

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 (272) hide show
  1. periplus/__init__.py +70 -0
  2. periplus/checkmap.py +80 -0
  3. periplus/cli.py +658 -0
  4. periplus/contract/schema/map.schema.json +138 -0
  5. periplus/contract/schema/navigation.schema.json +200 -0
  6. periplus/contract/schema/pack-file.schema.json +1405 -0
  7. periplus/contract/schema/pack-manifest.schema.json +245 -0
  8. periplus/contract/schema/project-settings.schema.json +85 -0
  9. periplus/contract/spec/engine.md +151 -0
  10. periplus/contract/spec/rules.md +352 -0
  11. periplus/detect.py +228 -0
  12. periplus/engine/__init__.py +5 -0
  13. periplus/engine/check.py +481 -0
  14. periplus/engine/emit.py +368 -0
  15. periplus/engine/packload.py +2321 -0
  16. periplus/engine/pattern.py +27 -0
  17. periplus/engine/rules.py +1946 -0
  18. periplus/engine/select.py +491 -0
  19. periplus/engine/source.py +172 -0
  20. periplus/engine/tree.py +424 -0
  21. periplus/errors.py +332 -0
  22. periplus/init.py +358 -0
  23. periplus/layout.py +28 -0
  24. periplus/manifest.py +498 -0
  25. periplus/map.py +364 -0
  26. periplus/navigate/__init__.py +1 -0
  27. periplus/navigate/find.py +143 -0
  28. periplus/navigate/ids.py +30 -0
  29. periplus/navigate/listing.py +42 -0
  30. periplus/navigate/mapfile.py +189 -0
  31. periplus/navigate/path.py +175 -0
  32. periplus/navigate/show.py +175 -0
  33. periplus/navigate/types.py +83 -0
  34. periplus/navigate/walk.py +138 -0
  35. periplus/packs/advancedqueue_basic@0.0.1/pack.yaml +14 -0
  36. periplus/packs/advancedqueue_basic@0.0.1/queue_job_type/queue_job_type.yaml +9 -0
  37. periplus/packs/advancedqueue_basic@0.0.1/queue_job_type/queue_job_type_annotation.yaml +43 -0
  38. periplus/packs/advancedqueue_basic@0.0.1/queue_job_type/queue_job_type_attribute.yaml +43 -0
  39. periplus/packs/ai_basic@0.0.1/ai_agent/ai_agent.yaml +8 -0
  40. periplus/packs/ai_basic@0.0.1/ai_agent/ai_agent_attribute.yaml +43 -0
  41. periplus/packs/ai_basic@0.0.1/ai_function_call/ai_function_call.yaml +8 -0
  42. periplus/packs/ai_basic@0.0.1/ai_function_call/ai_function_call_attribute.yaml +43 -0
  43. periplus/packs/ai_basic@0.0.1/pack.yaml +15 -0
  44. periplus/packs/config_pages_basic@0.0.1/bundle/config_pages_bundle_config.yaml +54 -0
  45. periplus/packs/config_pages_basic@0.0.1/pack.yaml +11 -0
  46. periplus/packs/crop_basic@0.0.1/bundle/crop_bundle_config.yaml +54 -0
  47. periplus/packs/crop_basic@0.0.1/pack.yaml +11 -0
  48. periplus/packs/drupal@0.1.0/block/block_placement.yaml +25 -0
  49. periplus/packs/drupal@0.1.0/block/block_placement_code.yaml +58 -0
  50. periplus/packs/drupal@0.1.0/block/block_placement_config.yaml +53 -0
  51. periplus/packs/drupal@0.1.0/block/block_plugin.yaml +52 -0
  52. periplus/packs/drupal@0.1.0/block/block_plugin_altered.yaml +61 -0
  53. periplus/packs/drupal@0.1.0/block/block_plugin_annotation.yaml +69 -0
  54. periplus/packs/drupal@0.1.0/block/block_plugin_attribute.yaml +56 -0
  55. periplus/packs/drupal@0.1.0/block/block_plugin_content.yaml +46 -0
  56. periplus/packs/drupal@0.1.0/block/block_plugin_derived.yaml +110 -0
  57. periplus/packs/drupal@0.1.0/block/block_plugin_deriver.yaml +30 -0
  58. periplus/packs/drupal@0.1.0/block/block_type.yaml +14 -0
  59. periplus/packs/drupal@0.1.0/block/block_type_code.yaml +43 -0
  60. periplus/packs/drupal@0.1.0/block/block_type_config.yaml +45 -0
  61. periplus/packs/drupal@0.1.0/cache/cache.yaml +18 -0
  62. periplus/packs/drupal@0.1.0/cache/cache_backend.yaml +31 -0
  63. periplus/packs/drupal@0.1.0/component/sdc.yaml +19 -0
  64. periplus/packs/drupal@0.1.0/component/sdc_definition.yaml +25 -0
  65. periplus/packs/drupal@0.1.0/component/sdc_use.yaml +25 -0
  66. periplus/packs/drupal@0.1.0/config/config_object.yaml +65 -0
  67. periplus/packs/drupal@0.1.0/display/display.yaml +32 -0
  68. periplus/packs/drupal@0.1.0/display/display_code.yaml +53 -0
  69. periplus/packs/drupal@0.1.0/display/display_config.yaml +95 -0
  70. periplus/packs/drupal@0.1.0/entity/entity.yaml +33 -0
  71. periplus/packs/drupal@0.1.0/entity/entity_class.yaml +60 -0
  72. periplus/packs/drupal@0.1.0/entity/entity_save.yaml +27 -0
  73. periplus/packs/drupal@0.1.0/entity/entity_type_annotation.yaml +85 -0
  74. periplus/packs/drupal@0.1.0/entity/entity_type_attribute.yaml +76 -0
  75. periplus/packs/drupal@0.1.0/field/field_instance_code.yaml +40 -0
  76. periplus/packs/drupal@0.1.0/field/field_instance_config.yaml +58 -0
  77. periplus/packs/drupal@0.1.0/field/field_storage.yaml +37 -0
  78. periplus/packs/drupal@0.1.0/field/field_storage_code.yaml +39 -0
  79. periplus/packs/drupal@0.1.0/field/field_storage_config.yaml +24 -0
  80. periplus/packs/drupal@0.1.0/form/form.yaml +48 -0
  81. periplus/packs/drupal@0.1.0/form/form_class.yaml +50 -0
  82. periplus/packs/drupal@0.1.0/form/form_id.yaml +48 -0
  83. periplus/packs/drupal@0.1.0/form/form_methods.yaml +39 -0
  84. periplus/packs/drupal@0.1.0/hook/hook.yaml +8 -0
  85. periplus/packs/drupal@0.1.0/hook/hook_attribute.yaml +28 -0
  86. periplus/packs/drupal@0.1.0/module/module.yaml +8 -0
  87. periplus/packs/drupal@0.1.0/module/module_info.yaml +50 -0
  88. periplus/packs/drupal@0.1.0/pack.yaml +184 -0
  89. periplus/packs/drupal@0.1.0/plugin/plugin.yaml +17 -0
  90. periplus/packs/drupal@0.1.0/plugin/plugin_annotation.yaml +120 -0
  91. periplus/packs/drupal@0.1.0/recommendations/recommendations.yaml +14 -0
  92. periplus/packs/drupal@0.1.0/service/service.yaml +19 -0
  93. periplus/packs/drupal@0.1.0/service/service_autowire.yaml +28 -0
  94. periplus/packs/drupal@0.1.0/service/service_definition.yaml +24 -0
  95. periplus/packs/drupal@0.1.0/service/service_use.yaml +43 -0
  96. periplus/packs/drupal_basic@0.3.0/action/action.yaml +9 -0
  97. periplus/packs/drupal_basic@0.3.0/action/action_annotation.yaml +43 -0
  98. periplus/packs/drupal_basic@0.3.0/action/action_attribute.yaml +43 -0
  99. periplus/packs/drupal_basic@0.3.0/block/block_placement.yaml +16 -0
  100. periplus/packs/drupal_basic@0.3.0/block/block_placement_config.yaml +51 -0
  101. periplus/packs/drupal_basic@0.3.0/block_plugin/block_plugin.yaml +14 -0
  102. periplus/packs/drupal_basic@0.3.0/block_plugin/block_plugin_annotation.yaml +47 -0
  103. periplus/packs/drupal_basic@0.3.0/block_plugin/block_plugin_attribute.yaml +43 -0
  104. periplus/packs/drupal_basic@0.3.0/block_type/block_type.yaml +8 -0
  105. periplus/packs/drupal_basic@0.3.0/block_type/block_type_config.yaml +54 -0
  106. periplus/packs/drupal_basic@0.3.0/boundary/boundary_types.yaml +19 -0
  107. periplus/packs/drupal_basic@0.3.0/bundle/bundle.yaml +20 -0
  108. periplus/packs/drupal_basic@0.3.0/bundle/bundle_config.yaml +393 -0
  109. periplus/packs/drupal_basic@0.3.0/call/call.yaml +349 -0
  110. periplus/packs/drupal_basic@0.3.0/component/component.yaml +30 -0
  111. periplus/packs/drupal_basic@0.3.0/config/config_object.yaml +119 -0
  112. periplus/packs/drupal_basic@0.3.0/constraint/constraint.yaml +8 -0
  113. periplus/packs/drupal_basic@0.3.0/constraint/constraint_annotation.yaml +43 -0
  114. periplus/packs/drupal_basic@0.3.0/constraint/constraint_attribute.yaml +43 -0
  115. periplus/packs/drupal_basic@0.3.0/display/display.yaml +30 -0
  116. periplus/packs/drupal_basic@0.3.0/display/display_config.yaml +203 -0
  117. periplus/packs/drupal_basic@0.3.0/display/display_mode_config.yaml +60 -0
  118. periplus/packs/drupal_basic@0.3.0/entity_type/config_entity_type_annotation.yaml +46 -0
  119. periplus/packs/drupal_basic@0.3.0/entity_type/config_entity_type_attribute.yaml +43 -0
  120. periplus/packs/drupal_basic@0.3.0/entity_type/content_entity_type_annotation.yaml +46 -0
  121. periplus/packs/drupal_basic@0.3.0/entity_type/content_entity_type_attribute.yaml +43 -0
  122. periplus/packs/drupal_basic@0.3.0/entity_type/entity_type.yaml +16 -0
  123. periplus/packs/drupal_basic@0.3.0/field/field_instance_config.yaml +116 -0
  124. periplus/packs/drupal_basic@0.3.0/field/field_storage.yaml +21 -0
  125. periplus/packs/drupal_basic@0.3.0/field/field_storage_config.yaml +45 -0
  126. periplus/packs/drupal_basic@0.3.0/field_formatter/field_formatter.yaml +8 -0
  127. periplus/packs/drupal_basic@0.3.0/field_formatter/field_formatter_annotation.yaml +46 -0
  128. periplus/packs/drupal_basic@0.3.0/field_formatter/field_formatter_attribute.yaml +43 -0
  129. periplus/packs/drupal_basic@0.3.0/field_type/field_type_annotation.yaml +46 -0
  130. periplus/packs/drupal_basic@0.3.0/field_type/field_type_attribute.yaml +50 -0
  131. periplus/packs/drupal_basic@0.3.0/field_widget/field_widget_annotation.yaml +46 -0
  132. periplus/packs/drupal_basic@0.3.0/field_widget/field_widget_attribute.yaml +50 -0
  133. periplus/packs/drupal_basic@0.3.0/form/form_id.yaml +80 -0
  134. periplus/packs/drupal_basic@0.3.0/hook/hook.yaml +100 -0
  135. periplus/packs/drupal_basic@0.3.0/hook/hook_attribute.yaml +64 -0
  136. periplus/packs/drupal_basic@0.3.0/hook/hook_theme_include.yaml +34 -0
  137. periplus/packs/drupal_basic@0.3.0/library/library.yaml +12 -0
  138. periplus/packs/drupal_basic@0.3.0/library/library_definition.yaml +58 -0
  139. periplus/packs/drupal_basic@0.3.0/local_action/local_action.yaml +12 -0
  140. periplus/packs/drupal_basic@0.3.0/local_action/local_action_definition.yaml +49 -0
  141. periplus/packs/drupal_basic@0.3.0/mail/mail.yaml +8 -0
  142. periplus/packs/drupal_basic@0.3.0/mail/mail_attribute.yaml +43 -0
  143. periplus/packs/drupal_basic@0.3.0/media_source/media_source.yaml +8 -0
  144. periplus/packs/drupal_basic@0.3.0/media_source/media_source_annotation.yaml +43 -0
  145. periplus/packs/drupal_basic@0.3.0/media_source/media_source_attribute.yaml +79 -0
  146. periplus/packs/drupal_basic@0.3.0/menu_link/menu_link.yaml +18 -0
  147. periplus/packs/drupal_basic@0.3.0/menu_link/menu_link_definition.yaml +48 -0
  148. periplus/packs/drupal_basic@0.3.0/module/module.yaml +7 -0
  149. periplus/packs/drupal_basic@0.3.0/module/module_info.yaml +58 -0
  150. periplus/packs/drupal_basic@0.3.0/pack.yaml +528 -0
  151. periplus/packs/drupal_basic@0.3.0/permission/permission.yaml +7 -0
  152. periplus/packs/drupal_basic@0.3.0/permission/permission_definition.yaml +27 -0
  153. periplus/packs/drupal_basic@0.3.0/queue_worker/queue_worker.yaml +9 -0
  154. periplus/packs/drupal_basic@0.3.0/queue_worker/queue_worker_annotation.yaml +43 -0
  155. periplus/packs/drupal_basic@0.3.0/queue_worker/queue_worker_attribute.yaml +43 -0
  156. periplus/packs/drupal_basic@0.3.0/rest_resource/rest_resource.yaml +8 -0
  157. periplus/packs/drupal_basic@0.3.0/rest_resource/rest_resource_annotation.yaml +43 -0
  158. periplus/packs/drupal_basic@0.3.0/rest_resource/rest_resource_attribute.yaml +43 -0
  159. periplus/packs/drupal_basic@0.3.0/role/role.yaml +14 -0
  160. periplus/packs/drupal_basic@0.3.0/role/role_config.yaml +44 -0
  161. periplus/packs/drupal_basic@0.3.0/route/route.yaml +42 -0
  162. periplus/packs/drupal_basic@0.3.0/route/route_definition.yaml +113 -0
  163. periplus/packs/drupal_basic@0.3.0/route/route_options.yaml +55 -0
  164. periplus/packs/drupal_basic@0.3.0/route/route_requirements.yaml +108 -0
  165. periplus/packs/drupal_basic@0.3.0/service/service.yaml +56 -0
  166. periplus/packs/drupal_basic@0.3.0/service/service_definition.yaml +181 -0
  167. periplus/packs/drupal_basic@0.3.0/template/template.yaml +86 -0
  168. periplus/packs/drupal_basic@0.3.0/template/template_route_link.yaml +30 -0
  169. periplus/packs/drupal_basic@0.3.0/theme/theme.yaml +15 -0
  170. periplus/packs/drupal_basic@0.3.0/theme/theme_info.yaml +61 -0
  171. periplus/packs/drupal_basic@0.3.0/theme_hook/template_suggestion.yaml +29 -0
  172. periplus/packs/drupal_basic@0.3.0/theme_hook/theme_hook.yaml +87 -0
  173. periplus/packs/drupal_basic@0.3.0/theme_hook/theme_hook_render.yaml +20 -0
  174. periplus/packs/drupal_basic@0.3.0/url/url.yaml +88 -0
  175. periplus/packs/drupal_basic@0.3.0/views_plugin/views_field_attribute.yaml +80 -0
  176. periplus/packs/drupal_basic@0.3.0/views_plugin/views_filter_attribute.yaml +80 -0
  177. periplus/packs/drupal_basic@0.3.0/views_plugin/views_plugin.yaml +15 -0
  178. periplus/packs/drupal_basic@0.3.0/views_plugin/views_style_attribute.yaml +80 -0
  179. periplus/packs/drupal_js_basic@0.0.1/attachment/attachment.yaml +12 -0
  180. periplus/packs/drupal_js_basic@0.0.1/attachment/attachment_in_php.yaml +15 -0
  181. periplus/packs/drupal_js_basic@0.0.1/attachment/attachment_in_php_array.yaml +52 -0
  182. periplus/packs/drupal_js_basic@0.0.1/attachment/attachment_in_twig.yaml +26 -0
  183. periplus/packs/drupal_js_basic@0.0.1/attachment/php_function_holder.yaml +13 -0
  184. periplus/packs/drupal_js_basic@0.0.1/behavior/behavior.yaml +198 -0
  185. periplus/packs/drupal_js_basic@0.0.1/pack.yaml +153 -0
  186. periplus/packs/drupal_js_basic@0.0.1/setting/setting.yaml +13 -0
  187. periplus/packs/drupal_js_basic@0.0.1/setting/setting_in_js.yaml +15 -0
  188. periplus/packs/drupal_js_basic@0.0.1/setting/setting_in_php.yaml +92 -0
  189. periplus/packs/drush_basic@0.0.1/command/command.yaml +15 -0
  190. periplus/packs/drush_basic@0.0.1/command/command_annotation.yaml +60 -0
  191. periplus/packs/drush_basic@0.0.1/command/command_attribute.yaml +88 -0
  192. periplus/packs/drush_basic@0.0.1/pack.yaml +29 -0
  193. periplus/packs/eck_basic@0.0.1/bundle/eck_bundle_config.yaml +56 -0
  194. periplus/packs/eck_basic@0.0.1/entity_type/eck_entity_type_config.yaml +26 -0
  195. periplus/packs/eck_basic@0.0.1/pack.yaml +28 -0
  196. periplus/packs/go@0.0.2/interfaces/satisfies.yaml +47 -0
  197. periplus/packs/go@0.0.2/pack.yaml +51 -0
  198. periplus/packs/go@0.0.2/types/find_declarations.yaml +137 -0
  199. periplus/packs/go@0.0.2/types/node_types.yaml +36 -0
  200. periplus/packs/go_basic@0.0.3/pack.yaml +104 -0
  201. periplus/packs/go_basic@0.0.3/rules/calls.yaml +25 -0
  202. periplus/packs/go_basic@0.0.3/rules/declarations.yaml +40 -0
  203. periplus/packs/go_basic@0.0.3/rules/imports.yaml +16 -0
  204. periplus/packs/go_basic@0.0.3/types/node_types.yaml +15 -0
  205. periplus/packs/js_basic@0.0.1/function/function.yaml +68 -0
  206. periplus/packs/js_basic@0.0.1/pack.yaml +50 -0
  207. periplus/packs/laravel_basic@0.0.5/pack.yaml +313 -0
  208. periplus/packs/laravel_basic@0.0.5/rules/classes.yaml +42 -0
  209. periplus/packs/laravel_basic@0.0.5/rules/config.yaml +229 -0
  210. periplus/packs/laravel_basic@0.0.5/rules/routes.yaml +210 -0
  211. periplus/packs/laravel_basic@0.0.5/rules/types.yaml +50 -0
  212. periplus/packs/laravel_basic@0.0.5/rules/views.yaml +100 -0
  213. periplus/packs/paragraphs_basic@0.0.1/bundle/paragraph_bundle_config.yaml +54 -0
  214. periplus/packs/paragraphs_basic@0.0.1/pack.yaml +12 -0
  215. periplus/packs/php@0.1.0/edges/edge_kinds.yaml +64 -0
  216. periplus/packs/php@0.1.0/edges/find_calls.yaml +34 -0
  217. periplus/packs/php@0.1.0/edges/find_imports.yaml +18 -0
  218. periplus/packs/php@0.1.0/edges/find_member_access.yaml +22 -0
  219. periplus/packs/php@0.1.0/edges/find_structure.yaml +38 -0
  220. periplus/packs/php@0.1.0/embedded/embedded_regions.yaml +19 -0
  221. periplus/packs/php@0.1.0/imports/imports.yaml +20 -0
  222. periplus/packs/php@0.1.0/pack.yaml +117 -0
  223. periplus/packs/php@0.1.0/resolution/excluded_tokens.yaml +23 -0
  224. periplus/packs/php@0.1.0/resolution/resolution.yaml +35 -0
  225. periplus/packs/php@0.1.0/types/find_declarations.yaml +102 -0
  226. periplus/packs/php@0.1.0/types/node_types.yaml +55 -0
  227. periplus/packs/php_basic@0.2.0/edges/edge_kinds.yaml +48 -0
  228. periplus/packs/php_basic@0.2.0/edges/find_structure.yaml +750 -0
  229. periplus/packs/php_basic@0.2.0/imports/imports.yaml +19 -0
  230. periplus/packs/php_basic@0.2.0/pack.yaml +94 -0
  231. periplus/packs/php_basic@0.2.0/types/find_declarations.yaml +267 -0
  232. periplus/packs/php_basic@0.2.0/types/node_types.yaml +55 -0
  233. periplus/packs/profile_basic@0.0.1/bundle/profile_bundle_config.yaml +54 -0
  234. periplus/packs/profile_basic@0.0.1/pack.yaml +12 -0
  235. periplus/packs/salesforce_basic@0.0.1/event/salesforce_event.yaml +36 -0
  236. periplus/packs/salesforce_basic@0.0.1/pack.yaml +31 -0
  237. periplus/packs/salesforce_basic@0.0.1/salesforce_mapping/salesforce_mapping.yaml +19 -0
  238. periplus/packs/salesforce_basic@0.0.1/salesforce_mapping/salesforce_mapping_config.yaml +83 -0
  239. periplus/packs/salesforce_basic@0.0.1/salesforce_mapping_field/salesforce_mapping_field.yaml +8 -0
  240. periplus/packs/salesforce_basic@0.0.1/salesforce_mapping_field/salesforce_mapping_field_annotation.yaml +43 -0
  241. periplus/packs/twig@0.0.1/pack.yaml +26 -0
  242. periplus/packs/twig@0.0.1/types/template.yaml +57 -0
  243. periplus/packs/twig_basic@0.1.0/pack.yaml +11 -0
  244. periplus/packs/twig_tweak_basic@0.0.1/pack.yaml +27 -0
  245. periplus/packs/twig_tweak_basic@0.0.1/rules/block.yaml +20 -0
  246. periplus/packs/twig_tweak_basic@0.0.1/rules/entity.yaml +35 -0
  247. periplus/packs/twig_tweak_basic@0.0.1/rules/field.yaml +21 -0
  248. periplus/packs/twig_tweak_basic@0.0.1/rules/menu.yaml +21 -0
  249. periplus/packs/webform_basic@0.0.1/bundle/webform_submission_bundle_config.yaml +54 -0
  250. periplus/packs/webform_basic@0.0.1/pack.yaml +20 -0
  251. periplus/packs/webform_basic@0.0.1/webform_handler/webform_handler.yaml +8 -0
  252. periplus/packs/webform_basic@0.0.1/webform_handler/webform_handler_annotation.yaml +43 -0
  253. periplus/packs/yaml@0.0.1/pack.yaml +72 -0
  254. periplus/packs/yaml@0.0.1/types/find_mapping_entry.yaml +21 -0
  255. periplus/packs/yaml@0.0.1/types/yaml_types.yaml +44 -0
  256. periplus/packs/yaml_basic@0.1.0/pack.yaml +10 -0
  257. periplus/packs.py +447 -0
  258. periplus/preflight.py +102 -0
  259. periplus/py.typed +0 -0
  260. periplus/report.py +596 -0
  261. periplus/resolution.py +355 -0
  262. periplus/settings.py +617 -0
  263. periplus/settings.stub.yml +99 -0
  264. periplus/spec.py +774 -0
  265. periplus/update.py +197 -0
  266. periplus/validate.py +655 -0
  267. periplus_map-0.2.1.dist-info/METADATA +566 -0
  268. periplus_map-0.2.1.dist-info/RECORD +272 -0
  269. periplus_map-0.2.1.dist-info/WHEEL +4 -0
  270. periplus_map-0.2.1.dist-info/entry_points.txt +2 -0
  271. periplus_map-0.2.1.dist-info/licenses/LICENSE +202 -0
  272. periplus_map-0.2.1.dist-info/licenses/NOTICE +2 -0
@@ -0,0 +1,481 @@
1
+ """Step one and a half: refuse what the loaded packs say about themselves and cannot honour.
2
+
3
+ The checks read the packs' own documents, not only the rules the engine supports, so a rule of a
4
+ shape the engine does not execute is still checked for what it declares. Nothing here opens a
5
+ source file.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Mapping, Sequence
11
+ from typing import Any
12
+
13
+ from periplus.engine.emit import type_chain
14
+ from periplus.engine.packload import (
15
+ _BOUNDARY_SHAPE,
16
+ RuleSet,
17
+ load_rules,
18
+ node_types,
19
+ twin_type,
20
+ unexecuted,
21
+ )
22
+ from periplus.engine.select import FileTypes, extended, folder_values, reachable
23
+ from periplus.engine.source import DATA_READERS
24
+ from periplus.engine.tree import grammar_problems
25
+ from periplus.errors import ExitCode, HarnessError, Problem
26
+ from periplus.manifest import MANIFEST_FILENAME, LoadedPack
27
+ from periplus.validate import _check_directory, _load, _yaml_files
28
+
29
+ __all__ = ["check_packs", "checked", "reachable"]
30
+
31
+
32
+ def checked(
33
+ packs: Sequence[LoadedPack],
34
+ values: Mapping[str, str] | None = None,
35
+ origins: Mapping[str, str] | None = None,
36
+ ) -> tuple[RuleSet | None, list[Problem]]:
37
+ """The packs' rules and every problem ``periplus map`` refuses them for before it reads a
38
+ source file; ``None`` in place of the rules when a rule file cannot be read. ``values`` are the
39
+ project's settings values, and ``origins`` the settings file of each.
40
+
41
+ A pack that fails its schema does not hide the problems of the packs that do not depend on it.
42
+ """
43
+ schema, failed = _schema_problems(packs)
44
+ sound = [pack for pack in packs if not reachable(pack, packs) & failed]
45
+ try:
46
+ rule_set = load_rules(sound, values, origins)
47
+ except HarnessError as error:
48
+ return None, [*schema, error.problem()]
49
+ return rule_set, [*schema, *rule_set.conflicts, *check_packs(sound, rule_set)]
50
+
51
+
52
+ def _schema_problems(packs: Sequence[LoadedPack]) -> tuple[list[Problem], set[str]]:
53
+ """Each loaded pack checked as ``periplus validate`` checks it, one problem per error.
54
+
55
+ The second value names the packs that failed.
56
+ """
57
+ problems: list[Problem] = []
58
+ failed: set[str] = set()
59
+ for pack in packs:
60
+ found: list[Problem] = []
61
+ _, errors = _check_directory(pack.directory, found)
62
+ problems.extend(found)
63
+ problems.extend(
64
+ Problem(
65
+ ExitCode.SCHEMA_INVALID,
66
+ f"{pack.name.pack}/{error.file} {error.pointer}: {error.message}"
67
+ + (f"; write {_BOUNDARY_SHAPE}" if error.pointer.startswith("$.boundary") else ""),
68
+ {"pack": pack.name.pack, "file": error.file, "pointer": error.pointer},
69
+ )
70
+ for error in errors
71
+ )
72
+ if found or errors:
73
+ failed.add(pack.name.pack)
74
+ return problems, failed
75
+
76
+
77
+ def check_packs(packs: Sequence[LoadedPack], rule_set: RuleSet) -> list[Problem]:
78
+ """Every problem the loaded packs have, sorted by code and message; empty when none.
79
+
80
+ A type or an edge kind must be declared by the pack that names it or by a pack it depends on,
81
+ directly or not. An edge rule whose ``this_node`` end has a twin node rule must give that end a
82
+ type its kind allows. Rule names are unique within a pack, every rule is one the engine can
83
+ execute, no folder name has two values across the packs that neither pack's dependency settles,
84
+ a ``files.extends`` beside ``add_extensions`` names one pack, no ending or whole file name is
85
+ claimed for two file types read the same way, a type read as a tree belongs to a pack that
86
+ pins a grammar, and every grammar pin is met.
87
+ """
88
+ documents = {pack.name.pack: _documents(pack) for pack in packs}
89
+ reach = {pack.name.pack: reachable(pack, packs) for pack in packs}
90
+ declared_types = {
91
+ name: {t for _, document in documents[name] for t in _declared(document, "node_types")}
92
+ | {t for _, document in documents[name] for t in _declared(document, "roles")}
93
+ for name in documents
94
+ }
95
+ kinds = {
96
+ name: {
97
+ k for _, document in documents[name] for k in _declared(document, "edge_kinds", "kind")
98
+ }
99
+ for name in documents
100
+ }
101
+ rule_files = {
102
+ (owner, str(rule.get("rule"))): file
103
+ for owner, found in documents.items()
104
+ for file, document in found
105
+ for rule in _rules(document)
106
+ }
107
+ problems: list[Problem] = []
108
+ for pack in packs:
109
+ name = pack.name.pack
110
+ visible = reach[name]
111
+ types = set().union(*(declared_types[p] for p in visible))
112
+ known_kinds = set().union(*(kinds[p] for p in visible))
113
+ files_of: dict[str, list[str]] = {}
114
+ for file, document in documents[name]:
115
+ for rule in _rules(document):
116
+ files_of.setdefault(str(rule.get("rule")), []).append(file)
117
+ problems.extend(_undeclared(name, file, document, types, known_kinds))
118
+ problems.extend(_unexecuted_keys(name, file, document, "file."))
119
+ problems.extend(_illegal_ends(name, file, document, documents[name], rule_set))
120
+ problems.extend(
121
+ _unexecuted_keys(name, MANIFEST_FILENAME, pack.manifest.document, "manifest.")
122
+ )
123
+ abstract = {
124
+ str(entry.get("name"))
125
+ for _, document in documents[name]
126
+ for entry in _entries(document, "node_types")
127
+ if entry.get("abstract") is True
128
+ }
129
+ # Only a node is refused: an edge end may be typed by an abstract type, which any of its
130
+ # descendants then meets.
131
+ problems.extend(
132
+ Problem(
133
+ ExitCode.PACK_RULES_UNREADABLE,
134
+ f"{rule.pack}/{rule_files.get((rule.pack, rule.name), rule.name)}: the rule "
135
+ f"{rule.name} emits the abstract type {rule.node_type}, which {name} declares "
136
+ "abstract",
137
+ {"pack": rule.pack, "rule": rule.name, "type": rule.node_type, "declared_by": name},
138
+ )
139
+ for rule in rule_set.rules
140
+ if rule.supported and rule.node_type in abstract
141
+ )
142
+ problems.extend(
143
+ Problem(
144
+ ExitCode.RULE_DUPLICATED,
145
+ f"{name}: the rule {rule} is written {len(files)} times, in {', '.join(files)}",
146
+ {"pack": name, "rule": rule},
147
+ )
148
+ for rule, files in files_of.items()
149
+ if len(files) > 1
150
+ )
151
+ problems.extend(
152
+ Problem(
153
+ ExitCode.RULE_NOT_EXECUTABLE,
154
+ f"{name}/{', '.join(files_of.get(rule.name, [rule.name]))}: the rule {rule.name} "
155
+ f"cannot be executed: {rule.reason}",
156
+ {"pack": name, "rule": rule.name},
157
+ )
158
+ for rule in rule_set.rules
159
+ if rule.pack == name and not rule.supported
160
+ )
161
+ problems.extend(_folder_conflicts(packs))
162
+ problems.extend(_extends_ambiguous(packs))
163
+ problems.extend(_type_conflicts(packs))
164
+ problems.extend(grammar_problems(packs))
165
+ return sorted(problems, key=lambda problem: (int(problem.code), problem.message))
166
+
167
+
168
+ def _documents(pack: LoadedPack) -> list[tuple[str, Mapping[str, Any]]]:
169
+ """Each rule file of the pack, as its relative path and its parsed mapping."""
170
+ found: list[tuple[str, Mapping[str, Any]]] = []
171
+ for relative, node in _yaml_files(pack.directory):
172
+ document, _ = _load(node, [])
173
+ if relative != MANIFEST_FILENAME and isinstance(document, Mapping):
174
+ found.append((relative, document))
175
+ return found
176
+
177
+
178
+ def _unexecuted_keys(
179
+ pack: str, file: str, document: Mapping[str, Any], prefix: str
180
+ ) -> list[Problem]:
181
+ """Each key the document holds that the schemas accept and nothing executes."""
182
+ return [
183
+ Problem(
184
+ ExitCode.RULE_NOT_EXECUTABLE,
185
+ f"{pack}/{file}: the key {key} is accepted and not executed",
186
+ {"pack": pack, "file": file, "key": key},
187
+ )
188
+ for key in unexecuted(document, prefix)
189
+ ]
190
+
191
+
192
+ def _entries(document: Mapping[str, Any], section: str) -> list[Mapping[str, Any]]:
193
+ entries = document.get(section)
194
+ return [e for e in (entries if isinstance(entries, list) else []) if isinstance(e, Mapping)]
195
+
196
+
197
+ def _declared(document: Mapping[str, Any], section: str, key: str = "name") -> list[str]:
198
+ entries = document.get(section)
199
+ return [
200
+ entry[key]
201
+ for entry in (entries if isinstance(entries, list) else [])
202
+ if isinstance(entry, Mapping) and isinstance(entry.get(key), str)
203
+ ]
204
+
205
+
206
+ def _rules(document: Mapping[str, Any]) -> list[Mapping[str, Any]]:
207
+ rules = document.get("rules")
208
+ return [r for r in (rules if isinstance(rules, list) else []) if isinstance(r, Mapping)]
209
+
210
+
211
+ def _strings(value: object) -> list[str]:
212
+ if isinstance(value, str):
213
+ return [value]
214
+ return [item for item in (value if isinstance(value, list) else []) if isinstance(item, str)]
215
+
216
+
217
+ def _emits(rule: Mapping[str, Any], key: str) -> list[Any]:
218
+ emits = rule.get("emits")
219
+ return [e[key] for e in (emits if isinstance(emits, list) else []) if _has(e, key)]
220
+
221
+
222
+ def _has(emit: object, key: str) -> bool:
223
+ return isinstance(emit, Mapping) and key in emit
224
+
225
+
226
+ def _undeclared(
227
+ pack: str,
228
+ file: str,
229
+ document: Mapping[str, Any],
230
+ types: set[str],
231
+ kinds: set[str],
232
+ ) -> list[Problem]:
233
+ """The types and edge kinds this file names that no reachable pack declares."""
234
+ named: list[tuple[str, str]] = []
235
+ needs = document.get("needs")
236
+ for section in ("types", "roles"):
237
+ listed = needs.get(section) if isinstance(needs, Mapping) else None
238
+ named.extend((f"needs.{section}", t) for t in _strings(listed))
239
+ for section in ("node_types", "edge_kinds"):
240
+ entries = document.get(section)
241
+ for entry in entries if isinstance(entries, list) else []:
242
+ if isinstance(entry, Mapping):
243
+ label = f"{section[:-1]} {entry.get('name', entry.get('kind'))}"
244
+ ends = (
245
+ (entry.get("parent"),)
246
+ if section == "node_types"
247
+ else (
248
+ entry.get("from"),
249
+ entry.get("to"),
250
+ )
251
+ )
252
+ named.extend((label, t) for end in ends for t in _strings(end))
253
+ problems = [
254
+ Problem(
255
+ ExitCode.UNDECLARED_TYPE,
256
+ f"{pack}/{file}: {where} names the type {name}, which neither {pack} nor a pack it "
257
+ "depends on declares",
258
+ {"pack": pack, "file": file, "type": name},
259
+ )
260
+ for where, name in named
261
+ if name not in types
262
+ ]
263
+ for rule in _rules(document):
264
+ rule_name = rule.get("rule")
265
+ used = node_types(rule)
266
+ used += [t for key in ("type", "role") for t in _strings(_emits(rule, key))]
267
+ used += [
268
+ edge[side]["declared"]
269
+ for edge in _emits(rule, "edge")
270
+ for side in ("from", "to")
271
+ if isinstance(edge, Mapping) and _has(edge.get(side), "declared")
272
+ ]
273
+ problems.extend(
274
+ Problem(
275
+ ExitCode.UNDECLARED_TYPE,
276
+ f"{pack}/{file}: the rule {rule_name} emits the type {name}, which neither "
277
+ f"{pack} nor a pack it depends on declares",
278
+ {"pack": pack, "file": file, "rule": str(rule_name), "type": name},
279
+ )
280
+ for name in used
281
+ if isinstance(name, str) and name not in types
282
+ )
283
+ problems.extend(
284
+ Problem(
285
+ ExitCode.UNDECLARED_EDGE_KIND,
286
+ f"{pack}/{file}: the rule {rule_name} emits the edge kind {edge['kind']}, which "
287
+ f"neither {pack} nor a pack it depends on declares",
288
+ {"pack": pack, "file": file, "rule": str(rule_name), "kind": str(edge["kind"])},
289
+ )
290
+ for edge in _emits(rule, "edge")
291
+ if isinstance(edge, Mapping) and edge.get("kind") not in kinds
292
+ )
293
+ found = rule.get("match")
294
+ match: Mapping[str, Any] = found if isinstance(found, Mapping) else {}
295
+ problems.extend(
296
+ Problem(
297
+ ExitCode.UNDECLARED_TYPE,
298
+ f"{pack}/{file}: the rule {rule_name} matches the type {name}, which neither "
299
+ f"{pack} nor a pack it depends on declares",
300
+ {"pack": pack, "file": file, "rule": str(rule_name), "type": name},
301
+ )
302
+ for name in _strings(match.get("node_type"))
303
+ if name not in types
304
+ )
305
+ follow = match.get("follow")
306
+ steps = follow if isinstance(follow, list) else []
307
+ problems.extend(
308
+ Problem(
309
+ ExitCode.UNDECLARED_EDGE_KIND,
310
+ f"{pack}/{file}: the rule {rule_name} follows the edge kind {kind}, which "
311
+ f"neither {pack} nor a pack it depends on declares",
312
+ {"pack": pack, "file": file, "rule": str(rule_name), "kind": kind},
313
+ )
314
+ for step in steps
315
+ if isinstance(step, Mapping)
316
+ for kind in _strings(step.get("edge")) + _strings(step.get("ancestors"))
317
+ if kind not in kinds
318
+ )
319
+ return problems
320
+
321
+
322
+ def _illegal_ends(
323
+ pack: str,
324
+ file: str,
325
+ document: Mapping[str, Any],
326
+ siblings: Sequence[tuple[str, Mapping[str, Any]]],
327
+ rule_set: RuleSet,
328
+ ) -> list[Problem]:
329
+ """The edge rules whose ``this_node`` end has a twin of a type the kind does not allow there,
330
+ or whose to end, or ``declared`` end at either side, names a type the kind does not allow
331
+ there."""
332
+ problems: list[Problem] = []
333
+ for rule in _rules(document):
334
+ for edge in _emits(rule, "edge"):
335
+ kind = rule_set.edge_kinds.get(str(edge.get("kind"))) if _has(edge, "kind") else None
336
+ end = edge["to"] if isinstance(edge.get("to"), Mapping) else {}
337
+ for named in [end["type"]] if end.get("type") else _strings(end.get("types")):
338
+ if not kind or set(kind.to_types) & set(type_chain(rule_set.types, named)):
339
+ continue
340
+ problems.append(
341
+ Problem(
342
+ ExitCode.EDGE_ILLEGAL,
343
+ f"{pack}/{file}: the rule {rule.get('rule')} names its to end a {named}, "
344
+ f"and the edge {kind.name} allows {', '.join(kind.to_types)} there",
345
+ {"pack": pack, "file": file, "rule": str(rule.get("rule"))},
346
+ )
347
+ )
348
+ for side, allowed in (("from", kind.from_types), ("to", kind.to_types)) if kind else ():
349
+ end = edge[side] if isinstance(edge.get(side), Mapping) else {}
350
+ named = end.get("declared")
351
+ # A type no pack declares is refused as undeclared instead.
352
+ if (
353
+ not isinstance(named, str)
354
+ or named not in rule_set.types
355
+ or set(allowed) & set(type_chain(rule_set.types, named))
356
+ ):
357
+ continue
358
+ problems.append(
359
+ Problem(
360
+ ExitCode.EDGE_ILLEGAL,
361
+ f"{pack}/{file}: the rule {rule.get('rule')} names its {side} end "
362
+ f"{{declared: {named}}}, and the edge {edge['kind']} allows "
363
+ f"{', '.join(allowed)} there",
364
+ {"pack": pack, "file": file, "rule": str(rule.get("rule"))},
365
+ )
366
+ )
367
+ twin = twin_type(rule, [other for _, d in siblings for other in _rules(d)])
368
+ if kind is None or twin is None:
369
+ continue
370
+ allowed_by_end = (("from", kind.from_types), ("to", kind.to_types))
371
+ for end, allowed in allowed_by_end:
372
+ chain = set(type_chain(rule_set.types, twin))
373
+ if edge.get(end) == "this_node" and not set(allowed) & chain:
374
+ problems.append(
375
+ Problem(
376
+ ExitCode.EDGE_ILLEGAL,
377
+ f"{pack}/{file}: the rule {rule.get('rule')} has an illegal end: "
378
+ f"the edge {kind.name} runs {end} this_node, a {twin}, and the kind "
379
+ f"allows {', '.join(allowed)} there",
380
+ {"pack": pack, "file": file, "rule": str(rule.get("rule"))},
381
+ )
382
+ )
383
+ return problems
384
+
385
+
386
+ def _folder_conflicts(packs: Sequence[LoadedPack]) -> list[Problem]:
387
+ """Each folder name two loaded packs give different values, settings or no settings, where
388
+ neither pack depends on the other."""
389
+ values, settled = folder_values(packs)
390
+ return [
391
+ Problem(
392
+ ExitCode.FOLDER_UNRESOLVED,
393
+ f"folder {name} has different values in the loaded packs: "
394
+ + "; ".join(
395
+ f"{', '.join(value)} in "
396
+ + ", ".join(f"{p}/{MANIFEST_FILENAME}" for p in sorted(owners))
397
+ for value, owners in sorted(seen.items())
398
+ ),
399
+ {"folder": name},
400
+ )
401
+ for name, seen in values.items()
402
+ if settled[name] is None
403
+ ]
404
+
405
+
406
+ def _extends_ambiguous(packs: Sequence[LoadedPack]) -> list[Problem]:
407
+ """Each pack whose ``files.extends`` names several packs beside ``add_extensions``."""
408
+ problems = []
409
+ for pack in packs:
410
+ files = pack.manifest.document.get("files")
411
+ if isinstance(files, Mapping) and "add_extensions" in files and len(extended(files)) > 1:
412
+ problems.append(
413
+ Problem(
414
+ ExitCode.PACK_RULES_UNREADABLE,
415
+ f"{pack.name.pack}/{MANIFEST_FILENAME}: files.extends names "
416
+ f"{', '.join(sorted(extended(files)))} beside add_extensions, which does not "
417
+ "say which pack's extension list grows",
418
+ {"pack": pack.name.pack},
419
+ )
420
+ )
421
+ return problems
422
+
423
+
424
+ def _type_conflicts(packs: Sequence[LoadedPack]) -> list[Problem]:
425
+ """Each ending or whole file name claimed for file types of different names read the same way,
426
+ whichever form each pack writes, each data type whose format no reader reads, and each type
427
+ read as a tree whose pack pins no grammar. Two types of different readers may share an ending,
428
+ as a tree type and a text type do. A short-form ``files.extensions`` list claims each of its
429
+ endings for the type named by that ending, so two short lists sharing an ending agree."""
430
+ pinned = {p.name.pack for p in packs if isinstance(p.manifest.document.get("grammar"), Mapping)}
431
+ claims: dict[tuple[str, str], dict[frozenset[str], set[str]]] = {}
432
+ problems = []
433
+ for kind in FileTypes.build(packs, {}).types:
434
+ for ending in kind.endings:
435
+ # An ending another pack's add_extensions gave a short-form type keeps its names.
436
+ named = {n for n in kind.names if n.removeprefix(".") == ending}
437
+ names = kind.names if kind.declared or not named else frozenset({ending})
438
+ claim = (f"ending {ending}", kind.reader)
439
+ claims.setdefault(claim, {}).setdefault(names, set()).add(kind.pack)
440
+ for whole in kind.whole:
441
+ claim = (f"name {whole}", kind.reader)
442
+ claims.setdefault(claim, {}).setdefault(kind.names, set()).add(kind.pack)
443
+ if kind.reader == "data" and kind.format not in DATA_READERS:
444
+ problems.append(
445
+ Problem(
446
+ ExitCode.PACK_RULES_UNREADABLE,
447
+ f"{kind.pack}/{MANIFEST_FILENAME}: the file type "
448
+ f"{', '.join(sorted(kind.names))} is read as data in the format "
449
+ f"{kind.format}, which no reader of the engine reads",
450
+ {
451
+ "pack": kind.pack,
452
+ "type": ", ".join(sorted(kind.names)),
453
+ "format": kind.format,
454
+ },
455
+ )
456
+ )
457
+ if kind.reader == "tree" and kind.pack not in pinned:
458
+ problems.append(
459
+ Problem(
460
+ ExitCode.PACK_RULES_UNREADABLE,
461
+ f"{kind.pack}/{MANIFEST_FILENAME}: the file type "
462
+ f"{', '.join(sorted(kind.names))} is read as a tree, and the pack pins no "
463
+ "grammar",
464
+ {"pack": kind.pack},
465
+ )
466
+ )
467
+ problems.extend(
468
+ Problem(
469
+ ExitCode.PACK_RULES_UNREADABLE,
470
+ f"the file {claim} is claimed for different file types read as {reader}: "
471
+ + "; ".join(
472
+ f"{', '.join(sorted(names))} in "
473
+ + ", ".join(f"{p}/{MANIFEST_FILENAME}" for p in sorted(owners))
474
+ for names, owners in sorted(seen.items(), key=lambda item: sorted(item[0]))
475
+ ),
476
+ {"claim": claim, "reader": reader},
477
+ )
478
+ for (claim, reader), seen in sorted(claims.items())
479
+ if len(seen) > 1
480
+ )
481
+ return problems