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.
- periplus/__init__.py +70 -0
- periplus/checkmap.py +80 -0
- periplus/cli.py +658 -0
- periplus/contract/schema/map.schema.json +138 -0
- periplus/contract/schema/navigation.schema.json +200 -0
- periplus/contract/schema/pack-file.schema.json +1405 -0
- periplus/contract/schema/pack-manifest.schema.json +245 -0
- periplus/contract/schema/project-settings.schema.json +85 -0
- periplus/contract/spec/engine.md +151 -0
- periplus/contract/spec/rules.md +352 -0
- periplus/detect.py +228 -0
- periplus/engine/__init__.py +5 -0
- periplus/engine/check.py +481 -0
- periplus/engine/emit.py +368 -0
- periplus/engine/packload.py +2321 -0
- periplus/engine/pattern.py +27 -0
- periplus/engine/rules.py +1946 -0
- periplus/engine/select.py +491 -0
- periplus/engine/source.py +172 -0
- periplus/engine/tree.py +424 -0
- periplus/errors.py +332 -0
- periplus/init.py +358 -0
- periplus/layout.py +28 -0
- periplus/manifest.py +498 -0
- periplus/map.py +364 -0
- periplus/navigate/__init__.py +1 -0
- periplus/navigate/find.py +143 -0
- periplus/navigate/ids.py +30 -0
- periplus/navigate/listing.py +42 -0
- periplus/navigate/mapfile.py +189 -0
- periplus/navigate/path.py +175 -0
- periplus/navigate/show.py +175 -0
- periplus/navigate/types.py +83 -0
- periplus/navigate/walk.py +138 -0
- periplus/packs/advancedqueue_basic@0.0.1/pack.yaml +14 -0
- periplus/packs/advancedqueue_basic@0.0.1/queue_job_type/queue_job_type.yaml +9 -0
- periplus/packs/advancedqueue_basic@0.0.1/queue_job_type/queue_job_type_annotation.yaml +43 -0
- periplus/packs/advancedqueue_basic@0.0.1/queue_job_type/queue_job_type_attribute.yaml +43 -0
- periplus/packs/ai_basic@0.0.1/ai_agent/ai_agent.yaml +8 -0
- periplus/packs/ai_basic@0.0.1/ai_agent/ai_agent_attribute.yaml +43 -0
- periplus/packs/ai_basic@0.0.1/ai_function_call/ai_function_call.yaml +8 -0
- periplus/packs/ai_basic@0.0.1/ai_function_call/ai_function_call_attribute.yaml +43 -0
- periplus/packs/ai_basic@0.0.1/pack.yaml +15 -0
- periplus/packs/config_pages_basic@0.0.1/bundle/config_pages_bundle_config.yaml +54 -0
- periplus/packs/config_pages_basic@0.0.1/pack.yaml +11 -0
- periplus/packs/crop_basic@0.0.1/bundle/crop_bundle_config.yaml +54 -0
- periplus/packs/crop_basic@0.0.1/pack.yaml +11 -0
- periplus/packs/drupal@0.1.0/block/block_placement.yaml +25 -0
- periplus/packs/drupal@0.1.0/block/block_placement_code.yaml +58 -0
- periplus/packs/drupal@0.1.0/block/block_placement_config.yaml +53 -0
- periplus/packs/drupal@0.1.0/block/block_plugin.yaml +52 -0
- periplus/packs/drupal@0.1.0/block/block_plugin_altered.yaml +61 -0
- periplus/packs/drupal@0.1.0/block/block_plugin_annotation.yaml +69 -0
- periplus/packs/drupal@0.1.0/block/block_plugin_attribute.yaml +56 -0
- periplus/packs/drupal@0.1.0/block/block_plugin_content.yaml +46 -0
- periplus/packs/drupal@0.1.0/block/block_plugin_derived.yaml +110 -0
- periplus/packs/drupal@0.1.0/block/block_plugin_deriver.yaml +30 -0
- periplus/packs/drupal@0.1.0/block/block_type.yaml +14 -0
- periplus/packs/drupal@0.1.0/block/block_type_code.yaml +43 -0
- periplus/packs/drupal@0.1.0/block/block_type_config.yaml +45 -0
- periplus/packs/drupal@0.1.0/cache/cache.yaml +18 -0
- periplus/packs/drupal@0.1.0/cache/cache_backend.yaml +31 -0
- periplus/packs/drupal@0.1.0/component/sdc.yaml +19 -0
- periplus/packs/drupal@0.1.0/component/sdc_definition.yaml +25 -0
- periplus/packs/drupal@0.1.0/component/sdc_use.yaml +25 -0
- periplus/packs/drupal@0.1.0/config/config_object.yaml +65 -0
- periplus/packs/drupal@0.1.0/display/display.yaml +32 -0
- periplus/packs/drupal@0.1.0/display/display_code.yaml +53 -0
- periplus/packs/drupal@0.1.0/display/display_config.yaml +95 -0
- periplus/packs/drupal@0.1.0/entity/entity.yaml +33 -0
- periplus/packs/drupal@0.1.0/entity/entity_class.yaml +60 -0
- periplus/packs/drupal@0.1.0/entity/entity_save.yaml +27 -0
- periplus/packs/drupal@0.1.0/entity/entity_type_annotation.yaml +85 -0
- periplus/packs/drupal@0.1.0/entity/entity_type_attribute.yaml +76 -0
- periplus/packs/drupal@0.1.0/field/field_instance_code.yaml +40 -0
- periplus/packs/drupal@0.1.0/field/field_instance_config.yaml +58 -0
- periplus/packs/drupal@0.1.0/field/field_storage.yaml +37 -0
- periplus/packs/drupal@0.1.0/field/field_storage_code.yaml +39 -0
- periplus/packs/drupal@0.1.0/field/field_storage_config.yaml +24 -0
- periplus/packs/drupal@0.1.0/form/form.yaml +48 -0
- periplus/packs/drupal@0.1.0/form/form_class.yaml +50 -0
- periplus/packs/drupal@0.1.0/form/form_id.yaml +48 -0
- periplus/packs/drupal@0.1.0/form/form_methods.yaml +39 -0
- periplus/packs/drupal@0.1.0/hook/hook.yaml +8 -0
- periplus/packs/drupal@0.1.0/hook/hook_attribute.yaml +28 -0
- periplus/packs/drupal@0.1.0/module/module.yaml +8 -0
- periplus/packs/drupal@0.1.0/module/module_info.yaml +50 -0
- periplus/packs/drupal@0.1.0/pack.yaml +184 -0
- periplus/packs/drupal@0.1.0/plugin/plugin.yaml +17 -0
- periplus/packs/drupal@0.1.0/plugin/plugin_annotation.yaml +120 -0
- periplus/packs/drupal@0.1.0/recommendations/recommendations.yaml +14 -0
- periplus/packs/drupal@0.1.0/service/service.yaml +19 -0
- periplus/packs/drupal@0.1.0/service/service_autowire.yaml +28 -0
- periplus/packs/drupal@0.1.0/service/service_definition.yaml +24 -0
- periplus/packs/drupal@0.1.0/service/service_use.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/action/action.yaml +9 -0
- periplus/packs/drupal_basic@0.3.0/action/action_annotation.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/action/action_attribute.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/block/block_placement.yaml +16 -0
- periplus/packs/drupal_basic@0.3.0/block/block_placement_config.yaml +51 -0
- periplus/packs/drupal_basic@0.3.0/block_plugin/block_plugin.yaml +14 -0
- periplus/packs/drupal_basic@0.3.0/block_plugin/block_plugin_annotation.yaml +47 -0
- periplus/packs/drupal_basic@0.3.0/block_plugin/block_plugin_attribute.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/block_type/block_type.yaml +8 -0
- periplus/packs/drupal_basic@0.3.0/block_type/block_type_config.yaml +54 -0
- periplus/packs/drupal_basic@0.3.0/boundary/boundary_types.yaml +19 -0
- periplus/packs/drupal_basic@0.3.0/bundle/bundle.yaml +20 -0
- periplus/packs/drupal_basic@0.3.0/bundle/bundle_config.yaml +393 -0
- periplus/packs/drupal_basic@0.3.0/call/call.yaml +349 -0
- periplus/packs/drupal_basic@0.3.0/component/component.yaml +30 -0
- periplus/packs/drupal_basic@0.3.0/config/config_object.yaml +119 -0
- periplus/packs/drupal_basic@0.3.0/constraint/constraint.yaml +8 -0
- periplus/packs/drupal_basic@0.3.0/constraint/constraint_annotation.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/constraint/constraint_attribute.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/display/display.yaml +30 -0
- periplus/packs/drupal_basic@0.3.0/display/display_config.yaml +203 -0
- periplus/packs/drupal_basic@0.3.0/display/display_mode_config.yaml +60 -0
- periplus/packs/drupal_basic@0.3.0/entity_type/config_entity_type_annotation.yaml +46 -0
- periplus/packs/drupal_basic@0.3.0/entity_type/config_entity_type_attribute.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/entity_type/content_entity_type_annotation.yaml +46 -0
- periplus/packs/drupal_basic@0.3.0/entity_type/content_entity_type_attribute.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/entity_type/entity_type.yaml +16 -0
- periplus/packs/drupal_basic@0.3.0/field/field_instance_config.yaml +116 -0
- periplus/packs/drupal_basic@0.3.0/field/field_storage.yaml +21 -0
- periplus/packs/drupal_basic@0.3.0/field/field_storage_config.yaml +45 -0
- periplus/packs/drupal_basic@0.3.0/field_formatter/field_formatter.yaml +8 -0
- periplus/packs/drupal_basic@0.3.0/field_formatter/field_formatter_annotation.yaml +46 -0
- periplus/packs/drupal_basic@0.3.0/field_formatter/field_formatter_attribute.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/field_type/field_type_annotation.yaml +46 -0
- periplus/packs/drupal_basic@0.3.0/field_type/field_type_attribute.yaml +50 -0
- periplus/packs/drupal_basic@0.3.0/field_widget/field_widget_annotation.yaml +46 -0
- periplus/packs/drupal_basic@0.3.0/field_widget/field_widget_attribute.yaml +50 -0
- periplus/packs/drupal_basic@0.3.0/form/form_id.yaml +80 -0
- periplus/packs/drupal_basic@0.3.0/hook/hook.yaml +100 -0
- periplus/packs/drupal_basic@0.3.0/hook/hook_attribute.yaml +64 -0
- periplus/packs/drupal_basic@0.3.0/hook/hook_theme_include.yaml +34 -0
- periplus/packs/drupal_basic@0.3.0/library/library.yaml +12 -0
- periplus/packs/drupal_basic@0.3.0/library/library_definition.yaml +58 -0
- periplus/packs/drupal_basic@0.3.0/local_action/local_action.yaml +12 -0
- periplus/packs/drupal_basic@0.3.0/local_action/local_action_definition.yaml +49 -0
- periplus/packs/drupal_basic@0.3.0/mail/mail.yaml +8 -0
- periplus/packs/drupal_basic@0.3.0/mail/mail_attribute.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/media_source/media_source.yaml +8 -0
- periplus/packs/drupal_basic@0.3.0/media_source/media_source_annotation.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/media_source/media_source_attribute.yaml +79 -0
- periplus/packs/drupal_basic@0.3.0/menu_link/menu_link.yaml +18 -0
- periplus/packs/drupal_basic@0.3.0/menu_link/menu_link_definition.yaml +48 -0
- periplus/packs/drupal_basic@0.3.0/module/module.yaml +7 -0
- periplus/packs/drupal_basic@0.3.0/module/module_info.yaml +58 -0
- periplus/packs/drupal_basic@0.3.0/pack.yaml +528 -0
- periplus/packs/drupal_basic@0.3.0/permission/permission.yaml +7 -0
- periplus/packs/drupal_basic@0.3.0/permission/permission_definition.yaml +27 -0
- periplus/packs/drupal_basic@0.3.0/queue_worker/queue_worker.yaml +9 -0
- periplus/packs/drupal_basic@0.3.0/queue_worker/queue_worker_annotation.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/queue_worker/queue_worker_attribute.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/rest_resource/rest_resource.yaml +8 -0
- periplus/packs/drupal_basic@0.3.0/rest_resource/rest_resource_annotation.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/rest_resource/rest_resource_attribute.yaml +43 -0
- periplus/packs/drupal_basic@0.3.0/role/role.yaml +14 -0
- periplus/packs/drupal_basic@0.3.0/role/role_config.yaml +44 -0
- periplus/packs/drupal_basic@0.3.0/route/route.yaml +42 -0
- periplus/packs/drupal_basic@0.3.0/route/route_definition.yaml +113 -0
- periplus/packs/drupal_basic@0.3.0/route/route_options.yaml +55 -0
- periplus/packs/drupal_basic@0.3.0/route/route_requirements.yaml +108 -0
- periplus/packs/drupal_basic@0.3.0/service/service.yaml +56 -0
- periplus/packs/drupal_basic@0.3.0/service/service_definition.yaml +181 -0
- periplus/packs/drupal_basic@0.3.0/template/template.yaml +86 -0
- periplus/packs/drupal_basic@0.3.0/template/template_route_link.yaml +30 -0
- periplus/packs/drupal_basic@0.3.0/theme/theme.yaml +15 -0
- periplus/packs/drupal_basic@0.3.0/theme/theme_info.yaml +61 -0
- periplus/packs/drupal_basic@0.3.0/theme_hook/template_suggestion.yaml +29 -0
- periplus/packs/drupal_basic@0.3.0/theme_hook/theme_hook.yaml +87 -0
- periplus/packs/drupal_basic@0.3.0/theme_hook/theme_hook_render.yaml +20 -0
- periplus/packs/drupal_basic@0.3.0/url/url.yaml +88 -0
- periplus/packs/drupal_basic@0.3.0/views_plugin/views_field_attribute.yaml +80 -0
- periplus/packs/drupal_basic@0.3.0/views_plugin/views_filter_attribute.yaml +80 -0
- periplus/packs/drupal_basic@0.3.0/views_plugin/views_plugin.yaml +15 -0
- periplus/packs/drupal_basic@0.3.0/views_plugin/views_style_attribute.yaml +80 -0
- periplus/packs/drupal_js_basic@0.0.1/attachment/attachment.yaml +12 -0
- periplus/packs/drupal_js_basic@0.0.1/attachment/attachment_in_php.yaml +15 -0
- periplus/packs/drupal_js_basic@0.0.1/attachment/attachment_in_php_array.yaml +52 -0
- periplus/packs/drupal_js_basic@0.0.1/attachment/attachment_in_twig.yaml +26 -0
- periplus/packs/drupal_js_basic@0.0.1/attachment/php_function_holder.yaml +13 -0
- periplus/packs/drupal_js_basic@0.0.1/behavior/behavior.yaml +198 -0
- periplus/packs/drupal_js_basic@0.0.1/pack.yaml +153 -0
- periplus/packs/drupal_js_basic@0.0.1/setting/setting.yaml +13 -0
- periplus/packs/drupal_js_basic@0.0.1/setting/setting_in_js.yaml +15 -0
- periplus/packs/drupal_js_basic@0.0.1/setting/setting_in_php.yaml +92 -0
- periplus/packs/drush_basic@0.0.1/command/command.yaml +15 -0
- periplus/packs/drush_basic@0.0.1/command/command_annotation.yaml +60 -0
- periplus/packs/drush_basic@0.0.1/command/command_attribute.yaml +88 -0
- periplus/packs/drush_basic@0.0.1/pack.yaml +29 -0
- periplus/packs/eck_basic@0.0.1/bundle/eck_bundle_config.yaml +56 -0
- periplus/packs/eck_basic@0.0.1/entity_type/eck_entity_type_config.yaml +26 -0
- periplus/packs/eck_basic@0.0.1/pack.yaml +28 -0
- periplus/packs/go@0.0.2/interfaces/satisfies.yaml +47 -0
- periplus/packs/go@0.0.2/pack.yaml +51 -0
- periplus/packs/go@0.0.2/types/find_declarations.yaml +137 -0
- periplus/packs/go@0.0.2/types/node_types.yaml +36 -0
- periplus/packs/go_basic@0.0.3/pack.yaml +104 -0
- periplus/packs/go_basic@0.0.3/rules/calls.yaml +25 -0
- periplus/packs/go_basic@0.0.3/rules/declarations.yaml +40 -0
- periplus/packs/go_basic@0.0.3/rules/imports.yaml +16 -0
- periplus/packs/go_basic@0.0.3/types/node_types.yaml +15 -0
- periplus/packs/js_basic@0.0.1/function/function.yaml +68 -0
- periplus/packs/js_basic@0.0.1/pack.yaml +50 -0
- periplus/packs/laravel_basic@0.0.5/pack.yaml +313 -0
- periplus/packs/laravel_basic@0.0.5/rules/classes.yaml +42 -0
- periplus/packs/laravel_basic@0.0.5/rules/config.yaml +229 -0
- periplus/packs/laravel_basic@0.0.5/rules/routes.yaml +210 -0
- periplus/packs/laravel_basic@0.0.5/rules/types.yaml +50 -0
- periplus/packs/laravel_basic@0.0.5/rules/views.yaml +100 -0
- periplus/packs/paragraphs_basic@0.0.1/bundle/paragraph_bundle_config.yaml +54 -0
- periplus/packs/paragraphs_basic@0.0.1/pack.yaml +12 -0
- periplus/packs/php@0.1.0/edges/edge_kinds.yaml +64 -0
- periplus/packs/php@0.1.0/edges/find_calls.yaml +34 -0
- periplus/packs/php@0.1.0/edges/find_imports.yaml +18 -0
- periplus/packs/php@0.1.0/edges/find_member_access.yaml +22 -0
- periplus/packs/php@0.1.0/edges/find_structure.yaml +38 -0
- periplus/packs/php@0.1.0/embedded/embedded_regions.yaml +19 -0
- periplus/packs/php@0.1.0/imports/imports.yaml +20 -0
- periplus/packs/php@0.1.0/pack.yaml +117 -0
- periplus/packs/php@0.1.0/resolution/excluded_tokens.yaml +23 -0
- periplus/packs/php@0.1.0/resolution/resolution.yaml +35 -0
- periplus/packs/php@0.1.0/types/find_declarations.yaml +102 -0
- periplus/packs/php@0.1.0/types/node_types.yaml +55 -0
- periplus/packs/php_basic@0.2.0/edges/edge_kinds.yaml +48 -0
- periplus/packs/php_basic@0.2.0/edges/find_structure.yaml +750 -0
- periplus/packs/php_basic@0.2.0/imports/imports.yaml +19 -0
- periplus/packs/php_basic@0.2.0/pack.yaml +94 -0
- periplus/packs/php_basic@0.2.0/types/find_declarations.yaml +267 -0
- periplus/packs/php_basic@0.2.0/types/node_types.yaml +55 -0
- periplus/packs/profile_basic@0.0.1/bundle/profile_bundle_config.yaml +54 -0
- periplus/packs/profile_basic@0.0.1/pack.yaml +12 -0
- periplus/packs/salesforce_basic@0.0.1/event/salesforce_event.yaml +36 -0
- periplus/packs/salesforce_basic@0.0.1/pack.yaml +31 -0
- periplus/packs/salesforce_basic@0.0.1/salesforce_mapping/salesforce_mapping.yaml +19 -0
- periplus/packs/salesforce_basic@0.0.1/salesforce_mapping/salesforce_mapping_config.yaml +83 -0
- periplus/packs/salesforce_basic@0.0.1/salesforce_mapping_field/salesforce_mapping_field.yaml +8 -0
- periplus/packs/salesforce_basic@0.0.1/salesforce_mapping_field/salesforce_mapping_field_annotation.yaml +43 -0
- periplus/packs/twig@0.0.1/pack.yaml +26 -0
- periplus/packs/twig@0.0.1/types/template.yaml +57 -0
- periplus/packs/twig_basic@0.1.0/pack.yaml +11 -0
- periplus/packs/twig_tweak_basic@0.0.1/pack.yaml +27 -0
- periplus/packs/twig_tweak_basic@0.0.1/rules/block.yaml +20 -0
- periplus/packs/twig_tweak_basic@0.0.1/rules/entity.yaml +35 -0
- periplus/packs/twig_tweak_basic@0.0.1/rules/field.yaml +21 -0
- periplus/packs/twig_tweak_basic@0.0.1/rules/menu.yaml +21 -0
- periplus/packs/webform_basic@0.0.1/bundle/webform_submission_bundle_config.yaml +54 -0
- periplus/packs/webform_basic@0.0.1/pack.yaml +20 -0
- periplus/packs/webform_basic@0.0.1/webform_handler/webform_handler.yaml +8 -0
- periplus/packs/webform_basic@0.0.1/webform_handler/webform_handler_annotation.yaml +43 -0
- periplus/packs/yaml@0.0.1/pack.yaml +72 -0
- periplus/packs/yaml@0.0.1/types/find_mapping_entry.yaml +21 -0
- periplus/packs/yaml@0.0.1/types/yaml_types.yaml +44 -0
- periplus/packs/yaml_basic@0.1.0/pack.yaml +10 -0
- periplus/packs.py +447 -0
- periplus/preflight.py +102 -0
- periplus/py.typed +0 -0
- periplus/report.py +596 -0
- periplus/resolution.py +355 -0
- periplus/settings.py +617 -0
- periplus/settings.stub.yml +99 -0
- periplus/spec.py +774 -0
- periplus/update.py +197 -0
- periplus/validate.py +655 -0
- periplus_map-0.2.1.dist-info/METADATA +566 -0
- periplus_map-0.2.1.dist-info/RECORD +272 -0
- periplus_map-0.2.1.dist-info/WHEEL +4 -0
- periplus_map-0.2.1.dist-info/entry_points.txt +2 -0
- periplus_map-0.2.1.dist-info/licenses/LICENSE +202 -0
- periplus_map-0.2.1.dist-info/licenses/NOTICE +2 -0
periplus/cli.py
ADDED
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
"""The console script: argument parsing and wiring, and nothing else.
|
|
2
|
+
|
|
3
|
+
Every unit of logic lives in the package. This module reads ``sys.argv``, calls one entry point,
|
|
4
|
+
and turns what comes back into a process status. A branch here that is not parsing or wiring
|
|
5
|
+
belongs in a module beside this one.
|
|
6
|
+
|
|
7
|
+
Three modules are imported at module level and no others: ``errors``, ``preflight``, and the
|
|
8
|
+
standard library. Everything else is imported inside ``main()``, after the dependency check has
|
|
9
|
+
run. That ordering is the whole of "a missing dependency fails at startup, named" — a module-level
|
|
10
|
+
``import periplus.resolution`` would pull ``ruamel.yaml`` onto this file's import path and the run
|
|
11
|
+
would die of an ``ImportError`` before the check that exists to name the problem ever executed.
|
|
12
|
+
|
|
13
|
+
``periplus.report`` is imported inside ``main()`` for the same reason, and that is also why
|
|
14
|
+
``--format``'s choices are written here as a literal rather than read off ``report.RENDERERS``. A
|
|
15
|
+
test asserts the two agree.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import argparse
|
|
21
|
+
import io
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
from collections.abc import Sequence
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
import periplus
|
|
29
|
+
from periplus.errors import ExitCode
|
|
30
|
+
from periplus.preflight import check_runtime_dependencies
|
|
31
|
+
|
|
32
|
+
__all__ = ["build_parser", "main"]
|
|
33
|
+
|
|
34
|
+
#: argparse gained ``color`` in 3.14 and defaults it to ``True``, consulting ``os.isatty()`` — so
|
|
35
|
+
#: the same source writes ANSI escapes into a usage line on 3.14 and plain text on 3.11. This tool
|
|
36
|
+
#: declares 3.11 through 3.14 and promises output that does not depend on where it ran,
|
|
37
|
+
#: and `_pin_the_output_stream` below already pays for the same property on stdout.
|
|
38
|
+
#:
|
|
39
|
+
#: A mapping rather than a branch inside ``build_parser``, because on 3.11 through 3.13 the keyword
|
|
40
|
+
#: is a ``TypeError`` and the parser has to be built without it — measured on both interpreters.
|
|
41
|
+
#:
|
|
42
|
+
#: ``dict[str, Any]`` and not ``dict[str, bool]``: a ``**mapping`` splat is checked against every
|
|
43
|
+
#: parameter of the callee, so the narrower type is four ``arg-type`` errors rather than none —
|
|
44
|
+
#: measured — against ``prog``, ``usage``, ``parents`` and ``formatter_class``, only two of which
|
|
45
|
+
#: are strings. What that gives up is that a misspelled key typechecks — inert below 3.14, a
|
|
46
|
+
#: ``TypeError`` on every invocation above it. That trade is recorded here and asserted nowhere:
|
|
47
|
+
#: no test reads the mapping's contents.
|
|
48
|
+
#: The parser is built from configuration the way ``MERGE_RULES`` is a table rather than a chain of
|
|
49
|
+
#: conditions, and ``build_parser`` stays a sequence of ``add_argument`` calls.
|
|
50
|
+
#:
|
|
51
|
+
#: One keyword on the top parser covers the subparsers too: ``add_subparsers`` assigns
|
|
52
|
+
#: ``action._color = self.color``, overriding the ``True`` that ``_SubParsersAction.__init__``
|
|
53
|
+
#: hardcodes. Verified by running the coloured and uncoloured cases in one command.
|
|
54
|
+
_COLOUR: dict[str, Any] = {"color": False} if sys.version_info >= (3, 14) else {}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
58
|
+
"""Return the argument parser.
|
|
59
|
+
|
|
60
|
+
``--version`` is a plain flag rather than ``action="version"`` on purpose: argparse's version
|
|
61
|
+
action calls ``sys.exit`` from inside the parser, and the entrypoint contract says ``main``
|
|
62
|
+
returns an integer and never exits the process. A test that called a parser which exits could
|
|
63
|
+
only assert on ``SystemExit``, which is a worse test of the same thing.
|
|
64
|
+
|
|
65
|
+
The subparsers are added without ``required=True``, and that is forced rather than chosen.
|
|
66
|
+
argparse checks a required subcommand *after* it has consumed the options, so
|
|
67
|
+
``periplus --version`` on a required-subparser parser is a usage error — which would break
|
|
68
|
+
the two tests that pin ``main(["--version"]) == 0``. ``main`` raises the
|
|
69
|
+
usage error itself, through ``parser.error``, which is argparse's own message, argparse's own
|
|
70
|
+
stream and argparse's own status; what it is not is argparse's own *decision*.
|
|
71
|
+
"""
|
|
72
|
+
parser = argparse.ArgumentParser(
|
|
73
|
+
prog="periplus",
|
|
74
|
+
description="A schema-first, deterministic codebase mapper, configurable to any stack.",
|
|
75
|
+
**_COLOUR,
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument(
|
|
78
|
+
"--version",
|
|
79
|
+
action="store_true",
|
|
80
|
+
help="print the installed version and exit",
|
|
81
|
+
)
|
|
82
|
+
# No hand-written ``metavar``. It read ``{resolve}`` and existed only because a one-member
|
|
83
|
+
# list read badly; with three members argparse's own default renders ``{init,spec,status}``.
|
|
84
|
+
# Deleting it is the twelfth rename site, and the one a search for the quoted literal misses
|
|
85
|
+
# because the name sat inside a longer string.
|
|
86
|
+
subcommands = parser.add_subparsers(dest="command")
|
|
87
|
+
subcommands.add_parser(
|
|
88
|
+
"init",
|
|
89
|
+
help="create this project's .periplus directory and a stub settings file",
|
|
90
|
+
description="Create the project directory, its pack directory and a stub settings file.",
|
|
91
|
+
)
|
|
92
|
+
spec = subcommands.add_parser(
|
|
93
|
+
"spec",
|
|
94
|
+
help="print the pack contract: the schemas, the rule format, the engine, and what is open",
|
|
95
|
+
description="Publish the contract a pack is authored against, and stop.",
|
|
96
|
+
)
|
|
97
|
+
spec.add_argument(
|
|
98
|
+
"pack",
|
|
99
|
+
nargs="?",
|
|
100
|
+
default=None,
|
|
101
|
+
metavar="PACK",
|
|
102
|
+
help="a pack name, to add what its manifest declares and what it inherits",
|
|
103
|
+
)
|
|
104
|
+
spec.add_argument(
|
|
105
|
+
"--format",
|
|
106
|
+
# The same two names again, written out for the same reason the block below gives:
|
|
107
|
+
# `periplus.spec` imports `report`, which reaches `ruamel.yaml`, so reading
|
|
108
|
+
# `spec.RENDERERS` here would put a runtime distribution on this module's import path. A
|
|
109
|
+
# test asserts these two names equal `tuple(spec.RENDERERS)`.
|
|
110
|
+
choices=("text", "json"),
|
|
111
|
+
default="text",
|
|
112
|
+
help="how to print the contract (default: text)",
|
|
113
|
+
)
|
|
114
|
+
spec.add_argument(
|
|
115
|
+
"--summary",
|
|
116
|
+
action="store_true",
|
|
117
|
+
help="print the summary block only, without the documents' contents",
|
|
118
|
+
)
|
|
119
|
+
check = subcommands.add_parser(
|
|
120
|
+
"validate",
|
|
121
|
+
help="check one pack's files against the shipped schemas and name what disagrees",
|
|
122
|
+
description="Check a pack against the schemas it is written against, and stop.",
|
|
123
|
+
)
|
|
124
|
+
# Required, unlike `spec`'s. A bare `periplus validate` would check the file `init` writes,
|
|
125
|
+
# and a test that loads that file asserts that property rather than a second command surface.
|
|
126
|
+
check.add_argument(
|
|
127
|
+
"pack",
|
|
128
|
+
metavar="PACK",
|
|
129
|
+
help="the pack to check: NAME, or NAME@VERSION to name one of several copies",
|
|
130
|
+
)
|
|
131
|
+
check.add_argument(
|
|
132
|
+
"--format",
|
|
133
|
+
# The same two names as a literal, for the third time and for the reason the two blocks
|
|
134
|
+
# around it give: `periplus.validate` imports `jsonschema` and reaches `ruamel.yaml`
|
|
135
|
+
# through `resolution`, so reading `validate.RENDERERS` here would put two runtime
|
|
136
|
+
# distributions on this module's import path. A test asserts these two names equal
|
|
137
|
+
# `tuple(validate.RENDERERS)`.
|
|
138
|
+
choices=("text", "json"),
|
|
139
|
+
default="text",
|
|
140
|
+
help="how to print the findings (default: text)",
|
|
141
|
+
)
|
|
142
|
+
refresh = subcommands.add_parser(
|
|
143
|
+
"update",
|
|
144
|
+
help="move this project's pins to the rutter versions that are installed",
|
|
145
|
+
description="Rewrite each pin whose rutter is installed at another version, and stop.",
|
|
146
|
+
)
|
|
147
|
+
refresh.add_argument(
|
|
148
|
+
"--format",
|
|
149
|
+
# A literal for the reason `validate`'s is: `periplus.update` reaches `ruamel.yaml`.
|
|
150
|
+
choices=("text", "json"),
|
|
151
|
+
default="text",
|
|
152
|
+
help="how to print the report (default: text)",
|
|
153
|
+
)
|
|
154
|
+
extract = subcommands.add_parser(
|
|
155
|
+
"map",
|
|
156
|
+
help="extract the map of this project and write it",
|
|
157
|
+
description="Run the loaded packs' rules over the project, write the map, and stop.",
|
|
158
|
+
)
|
|
159
|
+
extract.add_argument(
|
|
160
|
+
"--format",
|
|
161
|
+
# The same two names as a literal, for the reason the blocks above give: `periplus.map`
|
|
162
|
+
# imports `jsonschema`. A test asserts these two names equal `tuple(map.RENDERERS)`.
|
|
163
|
+
choices=("text", "json"),
|
|
164
|
+
default="text",
|
|
165
|
+
help="how to print the report (default: text)",
|
|
166
|
+
)
|
|
167
|
+
extract.add_argument(
|
|
168
|
+
"--settings",
|
|
169
|
+
type=Path,
|
|
170
|
+
default=None,
|
|
171
|
+
metavar="PATH",
|
|
172
|
+
help="the project settings file to read, instead of the one the project root implies",
|
|
173
|
+
)
|
|
174
|
+
extract.add_argument(
|
|
175
|
+
"--output",
|
|
176
|
+
type=Path,
|
|
177
|
+
default=None,
|
|
178
|
+
metavar="PATH",
|
|
179
|
+
help="where to write the map (default: .periplus/map.json under the project root)",
|
|
180
|
+
)
|
|
181
|
+
types = subcommands.add_parser(
|
|
182
|
+
"types",
|
|
183
|
+
help="list the node types and edge kinds in the written map",
|
|
184
|
+
description="Count the node types and edge kinds the map holds, and stop.",
|
|
185
|
+
)
|
|
186
|
+
types.add_argument(
|
|
187
|
+
"--format",
|
|
188
|
+
# A literal for the reason `map`'s is: `periplus.navigate` imports `jsonschema`. A test
|
|
189
|
+
# asserts these names equal `tuple(types.RENDERERS)`.
|
|
190
|
+
choices=("text", "json"),
|
|
191
|
+
default="text",
|
|
192
|
+
help="how to print the report (default: text)",
|
|
193
|
+
)
|
|
194
|
+
types.add_argument(
|
|
195
|
+
"--map",
|
|
196
|
+
type=Path,
|
|
197
|
+
default=None,
|
|
198
|
+
metavar="PATH",
|
|
199
|
+
help="the map file to read (default: .periplus/map.json under the project root)",
|
|
200
|
+
)
|
|
201
|
+
check = subcommands.add_parser(
|
|
202
|
+
"check-map",
|
|
203
|
+
help="check the written map against the map schema",
|
|
204
|
+
description="Check every node and edge of the map against the map schema, and stop.",
|
|
205
|
+
)
|
|
206
|
+
check.add_argument(
|
|
207
|
+
"--format",
|
|
208
|
+
# A literal for the reason `map`'s is: `periplus.checkmap` imports `jsonschema`. A test
|
|
209
|
+
# asserts these names equal `tuple(checkmap.RENDERERS)`.
|
|
210
|
+
choices=("text", "json"),
|
|
211
|
+
default="text",
|
|
212
|
+
help="how to print the report (default: text)",
|
|
213
|
+
)
|
|
214
|
+
check.add_argument(
|
|
215
|
+
"--map",
|
|
216
|
+
type=Path,
|
|
217
|
+
default=None,
|
|
218
|
+
metavar="PATH",
|
|
219
|
+
help="the map file to check (default: .periplus/map.json under the project root)",
|
|
220
|
+
)
|
|
221
|
+
find = subcommands.add_parser(
|
|
222
|
+
"find",
|
|
223
|
+
help="find the nodes whose id holds a text",
|
|
224
|
+
description="List the nodes whose id holds TEXT, closest match first, and stop.",
|
|
225
|
+
)
|
|
226
|
+
find.add_argument("text", metavar="TEXT", help="the text to look for in node ids")
|
|
227
|
+
find.add_argument("--type", default=None, metavar="T", help="keep type T and its subtypes")
|
|
228
|
+
find.add_argument(
|
|
229
|
+
"--limit", type=int, default=50, metavar="N", help="rows to show; 0 shows all (default: 50)"
|
|
230
|
+
)
|
|
231
|
+
find.add_argument(
|
|
232
|
+
"--map",
|
|
233
|
+
type=Path,
|
|
234
|
+
default=None,
|
|
235
|
+
metavar="PATH",
|
|
236
|
+
help="the map file to read (default: .periplus/map.json under the project root)",
|
|
237
|
+
)
|
|
238
|
+
find.add_argument(
|
|
239
|
+
"--format",
|
|
240
|
+
# A literal for the reason `types`' is; a test asserts it equals `tuple(find.RENDERERS)`.
|
|
241
|
+
choices=("text", "json"),
|
|
242
|
+
default="text",
|
|
243
|
+
help="how to print the report (default: text)",
|
|
244
|
+
)
|
|
245
|
+
show = subcommands.add_parser(
|
|
246
|
+
"show",
|
|
247
|
+
help="show one node and the edges at it",
|
|
248
|
+
description="Show the node ID and every edge leaving or arriving at it, and stop.",
|
|
249
|
+
)
|
|
250
|
+
show.add_argument("id", metavar="ID", help="the node id, as periplus find prints it")
|
|
251
|
+
show.add_argument(
|
|
252
|
+
"--limit", type=int, default=50, metavar="N", help="rows to show; 0 shows all (default: 50)"
|
|
253
|
+
)
|
|
254
|
+
show.add_argument(
|
|
255
|
+
"--map",
|
|
256
|
+
type=Path,
|
|
257
|
+
default=None,
|
|
258
|
+
metavar="PATH",
|
|
259
|
+
help="the map file to read (default: .periplus/map.json under the project root)",
|
|
260
|
+
)
|
|
261
|
+
show.add_argument(
|
|
262
|
+
"--format",
|
|
263
|
+
# A literal for the reason `types`' is; a test asserts it equals `tuple(show.RENDERERS)`.
|
|
264
|
+
choices=("text", "json"),
|
|
265
|
+
default="text",
|
|
266
|
+
help="how to print the report (default: text)",
|
|
267
|
+
)
|
|
268
|
+
for name, helptext in (
|
|
269
|
+
("neighbors", "list the items an item's edges lead to"),
|
|
270
|
+
("affected", "list the items whose edges lead to an item"),
|
|
271
|
+
):
|
|
272
|
+
walking = subcommands.add_parser(name, help=helptext, description=f"{helptext}, and stop.")
|
|
273
|
+
walking.add_argument("id", metavar="ID", help="the node id, as periplus find prints it")
|
|
274
|
+
walking.add_argument(
|
|
275
|
+
"--depth",
|
|
276
|
+
type=int,
|
|
277
|
+
default=1,
|
|
278
|
+
metavar="N",
|
|
279
|
+
help="edges to follow, 1 or more (default: 1)",
|
|
280
|
+
)
|
|
281
|
+
walking.add_argument(
|
|
282
|
+
"--kind",
|
|
283
|
+
action="append",
|
|
284
|
+
default=[],
|
|
285
|
+
metavar="K",
|
|
286
|
+
help="keep only edges of kind K; repeat for several",
|
|
287
|
+
)
|
|
288
|
+
walking.add_argument(
|
|
289
|
+
"--limit",
|
|
290
|
+
type=int,
|
|
291
|
+
default=50,
|
|
292
|
+
metavar="N",
|
|
293
|
+
help="rows to show; 0 shows all (default: 50)",
|
|
294
|
+
)
|
|
295
|
+
walking.add_argument(
|
|
296
|
+
"--map",
|
|
297
|
+
type=Path,
|
|
298
|
+
default=None,
|
|
299
|
+
metavar="PATH",
|
|
300
|
+
help="the map file to read (default: .periplus/map.json under the project root)",
|
|
301
|
+
)
|
|
302
|
+
walking.add_argument(
|
|
303
|
+
"--format",
|
|
304
|
+
# A literal for the reason `types`' is; a test asserts it equals the RENDERERS.
|
|
305
|
+
choices=("text", "json"),
|
|
306
|
+
default="text",
|
|
307
|
+
help="how to print the report (default: text)",
|
|
308
|
+
)
|
|
309
|
+
path = subcommands.add_parser(
|
|
310
|
+
"path",
|
|
311
|
+
help="show every shortest chain of edges from one item to another",
|
|
312
|
+
description="Show the shortest directed chains of edges from FROM to TO, and stop.",
|
|
313
|
+
)
|
|
314
|
+
path.add_argument("source", metavar="FROM", help="the node id the chains start at")
|
|
315
|
+
path.add_argument("target", metavar="TO", help="the node id the chains end at")
|
|
316
|
+
path.add_argument(
|
|
317
|
+
"--kind",
|
|
318
|
+
action="append",
|
|
319
|
+
default=[],
|
|
320
|
+
metavar="K",
|
|
321
|
+
help="follow only edges of kind K; repeat for several",
|
|
322
|
+
)
|
|
323
|
+
path.add_argument(
|
|
324
|
+
"--limit",
|
|
325
|
+
type=int,
|
|
326
|
+
default=10,
|
|
327
|
+
metavar="N",
|
|
328
|
+
help="chains to show; 0 shows all (default: 10)",
|
|
329
|
+
)
|
|
330
|
+
path.add_argument(
|
|
331
|
+
"--map",
|
|
332
|
+
type=Path,
|
|
333
|
+
default=None,
|
|
334
|
+
metavar="PATH",
|
|
335
|
+
help="the map file to read (default: .periplus/map.json under the project root)",
|
|
336
|
+
)
|
|
337
|
+
path.add_argument(
|
|
338
|
+
"--format",
|
|
339
|
+
# A literal for the reason `types`' is; a test asserts it equals `tuple(path.RENDERERS)`.
|
|
340
|
+
choices=("text", "json"),
|
|
341
|
+
default="text",
|
|
342
|
+
help="how to print the report (default: text)",
|
|
343
|
+
)
|
|
344
|
+
status = subcommands.add_parser(
|
|
345
|
+
"status",
|
|
346
|
+
help="report the settings, the pack search path and the directories on it",
|
|
347
|
+
description="Report what this project resolves to, and stop.",
|
|
348
|
+
)
|
|
349
|
+
status.add_argument(
|
|
350
|
+
"--format",
|
|
351
|
+
# The two names are written out here and read off `report.RENDERERS` nowhere. Reading them
|
|
352
|
+
# would put `from periplus.report import RENDERERS` on this module's import path, and
|
|
353
|
+
# `report` imports `resolution`, which imports `settings`, which imports `ruamel.yaml` — so
|
|
354
|
+
# a missing distribution would raise `ImportError` before the check that exists to name it.
|
|
355
|
+
# That is the one property this whole package is arranged around. A test asserts these two
|
|
356
|
+
# names equal `tuple(RENDERERS)`, which is the tie that a duplicated constant needs.
|
|
357
|
+
choices=("text", "json"),
|
|
358
|
+
default="text",
|
|
359
|
+
help="how to print the report (default: text)",
|
|
360
|
+
)
|
|
361
|
+
status.add_argument(
|
|
362
|
+
"--settings",
|
|
363
|
+
type=Path,
|
|
364
|
+
default=None,
|
|
365
|
+
metavar="PATH",
|
|
366
|
+
help="the project settings file to read, instead of the one the project root implies",
|
|
367
|
+
)
|
|
368
|
+
return parser
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
372
|
+
"""Parse arguments, dispatch, and return the process status.
|
|
373
|
+
|
|
374
|
+
Returns an integer for every path this function decides, and the interpreter turns it into
|
|
375
|
+
the exit status because ``[project.scripts]`` points here.
|
|
376
|
+
|
|
377
|
+
One honest limit, because an earlier version of this docstring overstated it: ``SystemExit`` is
|
|
378
|
+
raised from inside argparse for ``--help``, for an unrecognised argument, and now for a bare
|
|
379
|
+
``periplus`` — the last through the ``parser.error`` call below, which is argparse's own way of
|
|
380
|
+
reporting a usage error. The design accepts that for usage errors. So the guarantee is that
|
|
381
|
+
nothing *here* exits the process for a result it computed, not that no call under it can — an
|
|
382
|
+
embedder calling ``main(["--help"])`` or ``main([])`` gets ``SystemExit``, and a test asserting
|
|
383
|
+
on those paths must expect it.
|
|
384
|
+
"""
|
|
385
|
+
# Before `parse_args`, and not merely before the branches below it. argparse writes `--help`
|
|
386
|
+
# to stdout from inside `parse_args` and exits there, so a pin placed after it leaves that one
|
|
387
|
+
# path writing through whatever the console handed over — measured: `main(["--help"])` left
|
|
388
|
+
# `sys.stdout.encoding` at `ascii` and emitted CRLF terminators, while `main(["--version"])`
|
|
389
|
+
# left it `utf-8`. This is the first statement in the function for that reason.
|
|
390
|
+
_pin_the_output_stream()
|
|
391
|
+
|
|
392
|
+
parser = build_parser()
|
|
393
|
+
args = parser.parse_args(argv)
|
|
394
|
+
|
|
395
|
+
if args.version:
|
|
396
|
+
# Through the package's own accessor rather than a second importlib.metadata call.
|
|
397
|
+
# Two code paths reading one number is how they drift, and the accessor exists for this.
|
|
398
|
+
# Answered before the dependency check, so `--version` works on a broken install — which
|
|
399
|
+
# is what a person runs first when they are trying to find out what they have.
|
|
400
|
+
print(periplus.__version__)
|
|
401
|
+
return ExitCode.OK
|
|
402
|
+
|
|
403
|
+
# Startup step 1, still. A bare `periplus` is a usage error and is decided here, above the
|
|
404
|
+
# dependency check, so it exits 2 on a broken install as well as on a working one. The two
|
|
405
|
+
# paths cannot reach each other: the parse decides the bare case and the check decides the
|
|
406
|
+
# broken-install case.
|
|
407
|
+
if args.command is None:
|
|
408
|
+
parser.error("a subcommand is required")
|
|
409
|
+
|
|
410
|
+
# Startup step 2. Every absent distribution is named, not just the first, because a person who
|
|
411
|
+
# installs one and re-runs to be told about the second has been made to do the tool's work.
|
|
412
|
+
#
|
|
413
|
+
# To stderr, and stdout stays empty. The design's rule is that the report is the data and goes
|
|
414
|
+
# to stdout in whichever format, *including the problems it found*, and that stderr carries
|
|
415
|
+
# only what is not part of the report. This message is not part of the report, because on this
|
|
416
|
+
# path there is no report: step 2 runs before step 3 calls `resolve()`, nothing is constructed
|
|
417
|
+
# and nothing is rendered. It is a startup diagnostic about an install, not a finding about a
|
|
418
|
+
# codebase.
|
|
419
|
+
#
|
|
420
|
+
# The rule's own purpose settles it the same way. It exists so that "a consumer parsing
|
|
421
|
+
# --format json reads one stream and gets one document whether the run succeeded or failed".
|
|
422
|
+
# A bare text line on stdout would hand that consumer a malformed document — strictly worse
|
|
423
|
+
# than an empty stdout and a status of 9.
|
|
424
|
+
dependencies = check_runtime_dependencies()
|
|
425
|
+
missing = [status.distribution for status in dependencies if not status.present]
|
|
426
|
+
if missing:
|
|
427
|
+
for distribution in missing:
|
|
428
|
+
print(
|
|
429
|
+
f"{parser.prog}: required distribution is not installed: {distribution}",
|
|
430
|
+
file=sys.stderr,
|
|
431
|
+
)
|
|
432
|
+
return ExitCode.MISSING_DEPENDENCY
|
|
433
|
+
|
|
434
|
+
# This lazy import is load-bearing, not tidiness. `init.py` imports `_escape`
|
|
435
|
+
# from `periplus.report`, which pulls `resolution`, `settings` and `packs` behind it, so
|
|
436
|
+
# `import periplus.init` reaches both runtime distributions. At module level that would put
|
|
437
|
+
# `ruamel.yaml` and `platformdirs` on this file's import path and the run would die of an
|
|
438
|
+
# `ImportError` before the check above ever executed — the exact failure "a missing dependency
|
|
439
|
+
# fails at startup, named" exists to stop. Measured, not reasoned: with a module-level import
|
|
440
|
+
# reaching a runtime distribution planted here,
|
|
441
|
+
# `test_a_missing_dependency_ends_the_run_at_exit_9_naming_every_one` fails on its
|
|
442
|
+
# `ImportError` assertion.
|
|
443
|
+
#
|
|
444
|
+
# `periplus init` therefore exits 9 on an install missing either distribution: one startup order
|
|
445
|
+
# for the whole tool rather than a branch deciding which subcommand is exempt, and `--version`
|
|
446
|
+
# above remains the question answered before any check.
|
|
447
|
+
if args.command == "init":
|
|
448
|
+
from periplus.detect import detect
|
|
449
|
+
from periplus.init import initialise_project, render_text
|
|
450
|
+
|
|
451
|
+
# Detection runs here rather than inside `initialise_project`, so that the command that
|
|
452
|
+
# creates a directory is not also the command that reads every pack manifest on the search
|
|
453
|
+
# path. `detect` never raises and reports nothing: a repository it recognises nothing in is
|
|
454
|
+
# the ordinary case, and `periplus status` is what reports on the search path.
|
|
455
|
+
here = Path.cwd()
|
|
456
|
+
detected = detect(here, os.environ, dependencies)
|
|
457
|
+
# Named apart from the resolution report below. One `report` for both is a type error
|
|
458
|
+
# under strict, and it would read as one thing being two.
|
|
459
|
+
# The detections, not their pins. `initialise_project` derives what to write from what was
|
|
460
|
+
# found, so the pins in the file and the evidence in the report cannot disagree.
|
|
461
|
+
created = initialise_project(here, detected=detected)
|
|
462
|
+
sys.stdout.write(render_text(created))
|
|
463
|
+
return created.exit_code
|
|
464
|
+
|
|
465
|
+
# Imported here for the same reason `init` is, and dispatched here rather than below because
|
|
466
|
+
# `spec` is not a resolution report and shares no rendering with one. Whether this run touches
|
|
467
|
+
# the filesystem outside the package at all is `spec()`'s decision and not a branch in this
|
|
468
|
+
# file: with no pack named there is nothing to look up, and the module states that as a
|
|
469
|
+
# property with a test behind it.
|
|
470
|
+
if args.command == "spec":
|
|
471
|
+
from periplus.spec import RENDERERS as SPEC_RENDERERS
|
|
472
|
+
from periplus.spec import SUMMARY_RENDERERS as SPEC_SUMMARY_RENDERERS
|
|
473
|
+
from periplus.spec import spec
|
|
474
|
+
|
|
475
|
+
contract = spec(
|
|
476
|
+
start=Path.cwd(),
|
|
477
|
+
env=os.environ,
|
|
478
|
+
dependencies=dependencies,
|
|
479
|
+
requested=args.pack,
|
|
480
|
+
)
|
|
481
|
+
# `--summary` picks the table and `--format` picks the entry in it, so the flag is one
|
|
482
|
+
# lookup here rather than an argument threaded through two renderers. Whole documents stay
|
|
483
|
+
# the default: the consumer is a model, and making it run the command twice to reach the
|
|
484
|
+
# schemas it came for costs more than the bytes it saves.
|
|
485
|
+
renderers = SPEC_SUMMARY_RENDERERS if args.summary else SPEC_RENDERERS
|
|
486
|
+
sys.stdout.write(renderers[args.format](contract))
|
|
487
|
+
return contract.exit_code
|
|
488
|
+
|
|
489
|
+
# Imported here for the same reason `init` and `spec` are, and more sharply: `periplus.validate`
|
|
490
|
+
# imports `jsonschema` at its module level. That is the third runtime distribution, it arrived
|
|
491
|
+
# after this file's arrangement was settled, and a module-level import of this module here would
|
|
492
|
+
# break the startup property for it exactly as one reaching `ruamel.yaml` broke it for the
|
|
493
|
+
# first two. A test imports `periplus.cli` in a child and asserts that neither
|
|
494
|
+
# `periplus.validate` nor `jsonschema` is in `sys.modules` afterwards.
|
|
495
|
+
if args.command == "validate":
|
|
496
|
+
from periplus.validate import RENDERERS as VALIDATE_RENDERERS
|
|
497
|
+
from periplus.validate import validate
|
|
498
|
+
|
|
499
|
+
findings = validate(
|
|
500
|
+
start=Path.cwd(),
|
|
501
|
+
env=os.environ,
|
|
502
|
+
dependencies=dependencies,
|
|
503
|
+
requested=args.pack,
|
|
504
|
+
)
|
|
505
|
+
sys.stdout.write(VALIDATE_RENDERERS[args.format](findings))
|
|
506
|
+
return findings.exit_code
|
|
507
|
+
|
|
508
|
+
# Imported here because `periplus.update` imports `ruamel.yaml`.
|
|
509
|
+
if args.command == "update":
|
|
510
|
+
from periplus.update import RENDERERS as UPDATE_RENDERERS
|
|
511
|
+
from periplus.update import update_pins
|
|
512
|
+
|
|
513
|
+
updated = update_pins(start=Path.cwd(), env=os.environ, dependencies=dependencies)
|
|
514
|
+
sys.stdout.write(UPDATE_RENDERERS[args.format](updated))
|
|
515
|
+
return updated.exit_code
|
|
516
|
+
|
|
517
|
+
# Imported here for the reason `validate` is: `periplus.map` imports `jsonschema`.
|
|
518
|
+
if args.command == "map":
|
|
519
|
+
from periplus.map import RENDERERS as MAP_RENDERERS
|
|
520
|
+
from periplus.map import run
|
|
521
|
+
|
|
522
|
+
written = run(
|
|
523
|
+
start=Path.cwd(),
|
|
524
|
+
env=os.environ,
|
|
525
|
+
dependencies=dependencies,
|
|
526
|
+
settings_path=args.settings,
|
|
527
|
+
output=args.output,
|
|
528
|
+
)
|
|
529
|
+
sys.stdout.write(MAP_RENDERERS[args.format](written))
|
|
530
|
+
return written.exit_code
|
|
531
|
+
|
|
532
|
+
if getattr(args, "limit", 0) < 0:
|
|
533
|
+
parser.error("--limit must be 0 or more")
|
|
534
|
+
|
|
535
|
+
# Imported here for the reason `map` is: `periplus.navigate` imports `jsonschema`.
|
|
536
|
+
if args.command == "types":
|
|
537
|
+
from periplus.navigate.types import RENDERERS as TYPES_RENDERERS
|
|
538
|
+
from periplus.navigate.types import run as run_types
|
|
539
|
+
|
|
540
|
+
listed = run_types(start=Path.cwd(), env=os.environ, map_path=args.map)
|
|
541
|
+
sys.stdout.write(TYPES_RENDERERS[args.format](listed))
|
|
542
|
+
return listed.exit_code
|
|
543
|
+
|
|
544
|
+
if args.command == "check-map":
|
|
545
|
+
from periplus.checkmap import RENDERERS as CHECK_RENDERERS
|
|
546
|
+
from periplus.checkmap import run as run_check
|
|
547
|
+
|
|
548
|
+
checked = run_check(start=Path.cwd(), env=os.environ, map_path=args.map)
|
|
549
|
+
sys.stdout.write(CHECK_RENDERERS[args.format](checked))
|
|
550
|
+
return checked.exit_code
|
|
551
|
+
|
|
552
|
+
if args.command == "find":
|
|
553
|
+
from periplus.navigate.find import RENDERERS as FIND_RENDERERS
|
|
554
|
+
from periplus.navigate.find import run as run_find
|
|
555
|
+
|
|
556
|
+
found = run_find(
|
|
557
|
+
start=Path.cwd(),
|
|
558
|
+
env=os.environ,
|
|
559
|
+
text=args.text,
|
|
560
|
+
type_name=args.type,
|
|
561
|
+
limit=args.limit,
|
|
562
|
+
map_path=args.map,
|
|
563
|
+
)
|
|
564
|
+
sys.stdout.write(FIND_RENDERERS[args.format](found))
|
|
565
|
+
return found.exit_code
|
|
566
|
+
|
|
567
|
+
if args.command == "show":
|
|
568
|
+
from periplus.navigate.show import RENDERERS as SHOW_RENDERERS
|
|
569
|
+
from periplus.navigate.show import run as run_show
|
|
570
|
+
|
|
571
|
+
shown = run_show(
|
|
572
|
+
start=Path.cwd(),
|
|
573
|
+
env=os.environ,
|
|
574
|
+
node_id=args.id,
|
|
575
|
+
limit=args.limit,
|
|
576
|
+
map_path=args.map,
|
|
577
|
+
)
|
|
578
|
+
sys.stdout.write(SHOW_RENDERERS[args.format](shown))
|
|
579
|
+
return shown.exit_code
|
|
580
|
+
|
|
581
|
+
if args.command in ("neighbors", "affected"):
|
|
582
|
+
from periplus.navigate.walk import RENDERERS as WALK_RENDERERS
|
|
583
|
+
from periplus.navigate.walk import run as run_walk
|
|
584
|
+
|
|
585
|
+
if args.depth < 1:
|
|
586
|
+
parser.error("--depth must be 1 or more")
|
|
587
|
+
walked = run_walk(
|
|
588
|
+
start=Path.cwd(),
|
|
589
|
+
env=os.environ,
|
|
590
|
+
node_id=args.id,
|
|
591
|
+
direction="out" if args.command == "neighbors" else "in",
|
|
592
|
+
depth=args.depth,
|
|
593
|
+
kinds=args.kind,
|
|
594
|
+
limit=args.limit,
|
|
595
|
+
map_path=args.map,
|
|
596
|
+
)
|
|
597
|
+
sys.stdout.write(WALK_RENDERERS[args.format](walked))
|
|
598
|
+
return walked.exit_code
|
|
599
|
+
|
|
600
|
+
if args.command == "path":
|
|
601
|
+
from periplus.navigate.path import RENDERERS as PATH_RENDERERS
|
|
602
|
+
from periplus.navigate.path import run as run_path
|
|
603
|
+
|
|
604
|
+
chained = run_path(
|
|
605
|
+
start=Path.cwd(),
|
|
606
|
+
env=os.environ,
|
|
607
|
+
from_id=args.source,
|
|
608
|
+
to_id=args.target,
|
|
609
|
+
kinds=args.kind,
|
|
610
|
+
limit=args.limit,
|
|
611
|
+
map_path=args.map,
|
|
612
|
+
)
|
|
613
|
+
sys.stdout.write(PATH_RENDERERS[args.format](chained))
|
|
614
|
+
return chained.exit_code
|
|
615
|
+
|
|
616
|
+
# Startup step 3. Imported here and never at module level: everything below this line reaches
|
|
617
|
+
# `ruamel.yaml` and `platformdirs`, and the check above is what makes reaching them safe.
|
|
618
|
+
from periplus.report import RENDERERS
|
|
619
|
+
from periplus.resolution import resolve
|
|
620
|
+
|
|
621
|
+
# Step 2's tuple is passed forward rather than recomputed. It is the only call site, and a
|
|
622
|
+
# second call from inside `resolve` would run the check after the failure it exists to prevent.
|
|
623
|
+
report = resolve(
|
|
624
|
+
start=Path.cwd(),
|
|
625
|
+
env=os.environ,
|
|
626
|
+
dependencies=dependencies,
|
|
627
|
+
settings_path=args.settings,
|
|
628
|
+
pack_checks=True,
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
# Startup step 4. The report is the data, so it goes to stdout in whichever format, including
|
|
632
|
+
# the problems it found, and the status carries the verdict.
|
|
633
|
+
sys.stdout.write(RENDERERS[args.format](report))
|
|
634
|
+
return report.exit_code
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def _pin_the_output_stream() -> None:
|
|
638
|
+
"""Write UTF-8 with LF line endings, whatever the console would otherwise have done.
|
|
639
|
+
|
|
640
|
+
Two different failures, one call. A non-UTF-8 default console encoding mangles or refuses a
|
|
641
|
+
directory name; Windows text mode translates every LF into CRLF, which breaks the report's "LF
|
|
642
|
+
line endings" rule outright and would make two runs on two platforms differ in bytes.
|
|
643
|
+
|
|
644
|
+
**Neither half is reproducible on this machine.** This is Linux, where the default is already
|
|
645
|
+
UTF-8 with LF, so this is reasoned from documented CPython behaviour rather than observed. It
|
|
646
|
+
is also belt and braces for the encoding half: ``render_text`` emits ASCII only, and
|
|
647
|
+
``render_json`` is called with ``ensure_ascii=True``, so there is nothing in either document
|
|
648
|
+
that a console encoding could refuse.
|
|
649
|
+
|
|
650
|
+
Guarded by the type rather than by ``hasattr``, because the guard is a real case and not a
|
|
651
|
+
workaround. The case is **not** pytest's capture, as instrumenting it
|
|
652
|
+
shows: ``EncodedFile`` and ``CaptureIO`` both subclass ``io.TextIOWrapper``, and
|
|
653
|
+
across the whole suite the guard excluded capture zero times. What it does exclude is a plain
|
|
654
|
+
``io.StringIO`` — the shape a caller gets from ``contextlib.redirect_stdout``, which one test
|
|
655
|
+
here uses — and that has no stream to reconfigure.
|
|
656
|
+
"""
|
|
657
|
+
if isinstance(sys.stdout, io.TextIOWrapper):
|
|
658
|
+
sys.stdout.reconfigure(encoding="utf-8", newline="\n")
|