abstra 3.23.7__py3-none-any.whl → 3.23.8__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.
- {abstra-3.23.7.dist-info → abstra-3.23.8.dist-info}/METADATA +1 -1
- {abstra-3.23.7.dist-info → abstra-3.23.8.dist-info}/RECORD +188 -186
- abstra_internals/consts/filepaths.py +1 -1
- abstra_internals/controllers/codebase.py +9 -18
- abstra_internals/controllers/codebase_events.py +6 -4
- abstra_internals/controllers/main.py +56 -67
- abstra_internals/interface/cli/deploy.py +1 -3
- abstra_internals/repositories/linter/repository.py +9 -40
- abstra_internals/repositories/linter/rules/__init__.py +2 -0
- abstra_internals/repositories/linter/rules/big_py_files.py +40 -0
- abstra_internals/repositories/linter/rules/big_py_files_test.py +93 -0
- abstra_internals/repositories/linter/rules/duplicate_package_in_requirements.py +16 -4
- abstra_internals/repositories/linter/rules/duplicate_package_in_requirements_test.py +24 -3
- abstra_internals/repositories/linter/rules/env_in_bundle.py +3 -6
- abstra_internals/repositories/linter/rules/env_in_bundle_test.py +4 -3
- abstra_internals/repositories/linter/rules/missing_packages_in_requirements.py +12 -1
- abstra_internals/repositories/linter/rules/syntax_errors.py +2 -19
- abstra_internals/repositories/linter/rules/venv_in_bundle.py +3 -2
- abstra_internals/server/routes/requirements.py +7 -3
- abstra_internals/server/routes/workspace.py +0 -10
- abstra_internals/services/fs.py +144 -99
- abstra_internals/services/fs_test.py +303 -8
- abstra_internals/services/requirements.py +271 -81
- abstra_internals/services/requirements_test.py +528 -164
- abstra_internals/templates/__init__.py +3 -1
- abstra_statics/dist/assets/AbstraButton.vue_vue_type_script_setup_true_lang.205eb76e.js +2 -0
- abstra_statics/dist/assets/AbstraLogo.vue_vue_type_script_setup_true_lang.32a17b0c.js +2 -0
- abstra_statics/dist/assets/ApiKeys.7fefafb4.js +2 -0
- abstra_statics/dist/assets/App.c29df54f.js +2 -0
- abstra_statics/dist/assets/App.vue_vue_type_style_index_0_lang.efd9d3fa.js +2 -0
- abstra_statics/dist/assets/{BaseLayout.8bd18c5f.js → BaseLayout.b60c33b8.js} +2 -2
- abstra_statics/dist/assets/{Billing.877a4614.js → Billing.b9f77b65.js} +2 -2
- abstra_statics/dist/assets/{Breadcrumb.f312111a.js → Breadcrumb.d0cc2c91.js} +2 -2
- abstra_statics/dist/assets/{Builds.a2c45c39.js → Builds.18e4ba1a.js} +2 -2
- abstra_statics/dist/assets/{Card.5f504e7b.js → Card.957a87b2.js} +2 -2
- abstra_statics/dist/assets/{CircularLoading.6f511e29.js → CircularLoading.3e4ddd6d.js} +2 -2
- abstra_statics/dist/assets/CloseCircleOutlined.4e05b917.js +2 -0
- abstra_statics/dist/assets/{ConnectorsView.4e22242f.js → ConnectorsView.562a5007.js} +2 -2
- abstra_statics/dist/assets/ConsoleOmniChat.vue_vue_type_script_setup_true_lang.1d09cfdd.js +2 -0
- abstra_statics/dist/assets/ContentLayout.7d5c9f09.js +2 -0
- abstra_statics/dist/assets/{CrudView.5a642b48.js → CrudView.8fb84eac.js} +2 -2
- abstra_statics/dist/assets/DocsButton.vue_vue_type_script_setup_true_lang.0555d923.js +2 -0
- abstra_statics/dist/assets/{EditorLogin.d2224782.js → EditorLogin.02eb6050.js} +2 -2
- abstra_statics/dist/assets/{EditorsView.5e769180.js → EditorsView.f0ea00fc.js} +2 -2
- abstra_statics/dist/assets/EnvVars.063644bb.js +2 -0
- abstra_statics/dist/assets/{Error.dd899e38.js → Error.a81122c4.js} +2 -2
- abstra_statics/dist/assets/ExclamationCircleOutlined.d410fb9a.js +2 -0
- abstra_statics/dist/assets/Files.f23b9c53.js +2 -0
- abstra_statics/dist/assets/{Form.9eebd960.js → Form.bfea5673.js} +2 -2
- abstra_statics/dist/assets/{FormRunner.1c6a88dd.js → FormRunner.8bbe841e.js} +2 -2
- abstra_statics/dist/assets/Home.3bf2f131.js +2 -0
- abstra_statics/dist/assets/Home.42964d5b.js +2 -0
- abstra_statics/dist/assets/{Live.a691b0eb.js → Live.5dc821b6.js} +2 -2
- abstra_statics/dist/assets/LoadingContainer.6e72d482.js +2 -0
- abstra_statics/dist/assets/LoadingOutlined.ee72932a.js +2 -0
- abstra_statics/dist/assets/Login.0b618d09.js +2 -0
- abstra_statics/dist/assets/{Login.f96858b0.js → Login.c702642b.js} +2 -2
- abstra_statics/dist/assets/{Login.vue_vue_type_script_setup_true_lang.7d56cca3.js → Login.vue_vue_type_script_setup_true_lang.a92a80d0.js} +2 -2
- abstra_statics/dist/assets/Logo.83b476a4.js +2 -0
- abstra_statics/dist/assets/{Logs.8426d360.js → Logs.eebc0504.js} +2 -2
- abstra_statics/dist/assets/{LogsController.318117fd.js → LogsController.eb1b811d.js} +2 -2
- abstra_statics/dist/assets/Main.88719eb3.js +2 -0
- abstra_statics/dist/assets/{MockForm.f8600bec.js → MockForm.c3318be9.js} +2 -2
- abstra_statics/dist/assets/Navbar.abf206e9.js +2 -0
- abstra_statics/dist/assets/{NewEditor.d3300cf0.css → NewEditor.0044878f.css} +1 -1
- abstra_statics/dist/assets/NewEditor.5650f697.js +8 -0
- abstra_statics/dist/assets/OidcLoginCallback.df2bdeb0.js +2 -0
- abstra_statics/dist/assets/OidcLogoutCallback.2ba5316d.js +2 -0
- abstra_statics/dist/assets/{OmniChat.7660057c.css → OmniChat.8a35a659.css} +1 -1
- abstra_statics/dist/assets/{OmniChat.097bec71.js → OmniChat.de828c54.js} +4 -4
- abstra_statics/dist/assets/{OnboardingView.c9a3343e.js → OnboardingView.1c034f0d.js} +2 -2
- abstra_statics/dist/assets/{Organization.0833b7fe.js → Organization.855f95a9.js} +2 -2
- abstra_statics/dist/assets/Organizations.3db82ab2.js +2 -0
- abstra_statics/dist/assets/{PhArrowCounterClockwise.vue.aaa06bc0.js → PhArrowCounterClockwise.vue.76c9e146.js} +2 -2
- abstra_statics/dist/assets/{PhArrowSquareOut.vue.ee4af292.js → PhArrowSquareOut.vue.ecfa9cb2.js} +2 -2
- abstra_statics/dist/assets/{PhBookBookmark.vue.681c5036.js → PhBookBookmark.vue.f8f803d9.js} +2 -2
- abstra_statics/dist/assets/{PhChats.vue.d61c3615.js → PhChats.vue.dfda946c.js} +2 -2
- abstra_statics/dist/assets/{PhClockCounterClockwise.vue.0457e9b2.js → PhClockCounterClockwise.vue.cd77fd26.js} +2 -2
- abstra_statics/dist/assets/{PhCopy.vue.391b0ef7.js → PhCopy.vue.a63b48dd.js} +2 -2
- abstra_statics/dist/assets/{PhCopySimple.vue.e887b43c.js → PhCopySimple.vue.ecffb042.js} +2 -2
- abstra_statics/dist/assets/{PhCube.vue.d070a184.js → PhCube.vue.b5b96a33.js} +2 -2
- abstra_statics/dist/assets/{PhDotsThreeVertical.vue.f4b60771.js → PhDotsThreeVertical.vue.2db678ef.js} +2 -2
- abstra_statics/dist/assets/PhDownloadSimple.vue.cbca4f9b.js +2 -0
- abstra_statics/dist/assets/{PhFolderPlus.vue.d5788203.js → PhFolderPlus.vue.dfb9b117.js} +2 -2
- abstra_statics/dist/assets/{PhGear.vue.e2b120bb.js → PhGear.vue.6e1aeed0.js} +2 -2
- abstra_statics/dist/assets/{PhKey.vue.cf1e08ca.js → PhKey.vue.50d82bb5.js} +2 -2
- abstra_statics/dist/assets/{PhPencil.vue.20f1b3c4.js → PhPencil.vue.ae2943da.js} +2 -2
- abstra_statics/dist/assets/{PhPencilSimple.vue.ec2125f5.js → PhPencilSimple.vue.9042e169.js} +2 -2
- abstra_statics/dist/assets/{PhPencilSimpleLine.vue.22e75a5a.js → PhPencilSimpleLine.vue.f840cf0d.js} +2 -2
- abstra_statics/dist/assets/{PhRocket.vue.27c6f935.js → PhRocket.vue.3b5927aa.js} +2 -2
- abstra_statics/dist/assets/{PhSignOut.vue.61b63ec0.js → PhSignOut.vue.d00d3657.js} +2 -2
- abstra_statics/dist/assets/{PhSparkle.vue.fd6a9ad7.js → PhSparkle.vue.8a94f3a0.js} +2 -2
- abstra_statics/dist/assets/{PhUserList.vue.abdd6da1.js → PhUserList.vue.3791cb59.js} +2 -2
- abstra_statics/dist/assets/{PhUsersThree.vue.85d1a1f0.js → PhUsersThree.vue.ef0376b6.js} +2 -2
- abstra_statics/dist/assets/{PhWebhooksLogo.vue.00b65b2c.js → PhWebhooksLogo.vue.fe81fb65.js} +2 -2
- abstra_statics/dist/assets/{PlayerConfigProvider.10f46997.js → PlayerConfigProvider.2acd3a77.js} +2 -2
- abstra_statics/dist/assets/{PlayerNavbar.f2f66852.js → PlayerNavbar.13876ce7.js} +2 -2
- abstra_statics/dist/assets/Project.0277535f.js +2 -0
- abstra_statics/dist/assets/{ProjectLogin.7660cd84.js → ProjectLogin.46d5036b.js} +2 -2
- abstra_statics/dist/assets/{ProjectSettings.50027450.js → ProjectSettings.652a838b.js} +2 -2
- abstra_statics/dist/assets/{ProjectsView.107f5e34.js → ProjectsView.c5ec993b.js} +2 -2
- abstra_statics/dist/assets/{SaveButton.cd025dae.js → SaveButton.ac38b361.js} +2 -2
- abstra_statics/dist/assets/{files.f66880c3.js → ScrollArea.vue_vue_type_script_setup_true_lang.a58564d3.js} +2 -2
- abstra_statics/dist/assets/{Sidebar.c3d5d187.js → Sidebar.56e51ab5.js} +2 -2
- abstra_statics/dist/assets/Sql.3cdc910a.css +1 -0
- abstra_statics/dist/assets/Sql.6961306b.js +5 -0
- abstra_statics/dist/assets/Steps.f820fb18.js +2 -0
- abstra_statics/dist/assets/{TableEditor.7b07ece4.js → TableEditor.df6a4852.js} +2 -2
- abstra_statics/dist/assets/Tables.198b84c5.js +2 -0
- abstra_statics/dist/assets/{TablesDiagram.6736c045.js → TablesDiagram.811d464d.js} +3 -3
- abstra_statics/dist/assets/TablesTabs.vue_vue_type_script_setup_true_lang.e6074880.js +2 -0
- abstra_statics/dist/assets/{Tasks.6660de00.js → Tasks.df69d20e.js} +2 -2
- abstra_statics/dist/assets/{UploadOutlined.732440a5.js → UploadOutlined.b601a592.js} +2 -2
- abstra_statics/dist/assets/{View.283e52c1.js → View.7a2ccc33.js} +2 -2
- abstra_statics/dist/assets/View.vue_vue_type_script_setup_true_lang.424410a2.js +2 -0
- abstra_statics/dist/assets/{Watermark.6076ef47.js → Watermark.40c8c47e.js} +2 -2
- abstra_statics/dist/assets/{WebEditor.6a012d5b.js → WebEditor.82a3accf.js} +2 -2
- abstra_statics/dist/assets/WidgetPreview.0f938200.js +2 -0
- abstra_statics/dist/assets/ant-design.544de4a0.js +2 -0
- abstra_statics/dist/assets/{apiKey.1c96dd66.js → apiKey.95126643.js} +2 -2
- abstra_statics/dist/assets/asyncComputed.1b787534.js +2 -0
- abstra_statics/dist/assets/{build.656c5601.js → build.997fec15.js} +2 -2
- abstra_statics/dist/assets/colorHelpers.d4f3f275.js +2 -0
- abstra_statics/dist/assets/{console.9b13e1da.js → console.54ab975c.js} +4 -4
- abstra_statics/dist/assets/{constants.733c6549.js → constants.36bf7d70.js} +2 -2
- abstra_statics/dist/assets/{cssMode.6d17ca95.js → cssMode.c1aa21d7.js} +2 -2
- abstra_statics/dist/assets/{datetime.adbf692e.js → datetime.adc0acc4.js} +2 -2
- abstra_statics/dist/assets/dayjs.a6bd0ee0.js +2 -0
- abstra_statics/dist/assets/editor.0ce52658.js +2 -0
- abstra_statics/dist/assets/editor.main.353e9a8f.js +2 -0
- abstra_statics/dist/assets/fetch.83d89bdc.js +2 -0
- abstra_statics/dist/assets/{folder.9092348a.js → folder.41d37eb7.js} +2 -2
- abstra_statics/dist/assets/{freemarker2.82f2cb8c.js → freemarker2.40778f3f.js} +2 -2
- abstra_statics/dist/assets/{handlebars.36ec2a3c.js → handlebars.92d4ff2a.js} +2 -2
- abstra_statics/dist/assets/{html.845da565.js → html.b3c0c53a.js} +3 -3
- abstra_statics/dist/assets/{htmlMode.980f76b4.js → htmlMode.1212da49.js} +2 -2
- abstra_statics/dist/assets/{index.5dbe93c3.js → index.36d8b30a.js} +2 -2
- abstra_statics/dist/assets/{index.55d10b71.js → index.37c46161.js} +2 -2
- abstra_statics/dist/assets/index.3d2a7b6d.js +2 -0
- abstra_statics/dist/assets/{index.81a2ae08.js → index.3e7471f4.js} +2 -2
- abstra_statics/dist/assets/{index.1551abd6.js → index.3ff1c0be.js} +2 -2
- abstra_statics/dist/assets/{index.b3b62f71.js → index.5e22e010.js} +2 -2
- abstra_statics/dist/assets/{index.0f357aec.js → index.9e0166fe.js} +2 -2
- abstra_statics/dist/assets/{index.1e12c884.js → index.a7b8e25e.js} +2 -2
- abstra_statics/dist/assets/{index.a2b9d34b.js → index.d181a22c.js} +2 -2
- abstra_statics/dist/assets/{javascript.b0154182.js → javascript.f9710043.js} +3 -3
- abstra_statics/dist/assets/{jsonMode.f86e9042.js → jsonMode.16d00c0d.js} +2 -2
- abstra_statics/dist/assets/{jwt-decode.esm.d86c27e0.js → jwt-decode.esm.992666e9.js} +8 -8
- abstra_statics/dist/assets/{linters.9ba6d5f8.js → linters.3c5f0c83.js} +2 -2
- abstra_statics/dist/assets/{liquid.029287f8.js → liquid.9a777e1a.js} +3 -3
- abstra_statics/dist/assets/{member.3aca30ee.js → member.6b2b3438.js} +2 -2
- abstra_statics/dist/assets/{metadata.18d0a278.js → metadata.cfad5458.js} +2 -2
- abstra_statics/dist/assets/omniChatStore.2b85c342.js +8 -0
- abstra_statics/dist/assets/{organization.8b2c1c53.js → organization.53636095.js} +2 -2
- abstra_statics/dist/assets/player.5b7eaa25.js +2 -0
- abstra_statics/dist/assets/{plotly.min.10467de2.js → plotly.min.9b8bef5c.js} +2 -2
- abstra_statics/dist/assets/polling.88a266b3.js +2 -0
- abstra_statics/dist/assets/{project.33809d47.js → project.76f0af14.js} +2 -2
- abstra_statics/dist/assets/{python.ee23fd86.js → python.ae8270c8.js} +3 -3
- abstra_statics/dist/assets/{razor.4ae6d2a2.js → razor.4f24e19e.js} +3 -3
- abstra_statics/dist/assets/{record.d087b37e.js → record.87ef3b68.js} +2 -2
- abstra_statics/dist/assets/redirect.2fa4f8cf.js +2 -0
- abstra_statics/dist/assets/{repository.02efcdbd.js → repository.9d5310b6.js} +2 -2
- abstra_statics/dist/assets/{repository.6fa74dff.js → repository.af418855.js} +2 -2
- abstra_statics/dist/assets/{router.7936fd78.js → router.1324a1a9.js} +3 -3
- abstra_statics/dist/assets/router.55c0ff56.js +2 -0
- abstra_statics/dist/assets/{string.360236ba.js → string.f6a7565f.js} +2 -2
- abstra_statics/dist/assets/{tables.d580be9d.js → tables.c26107a1.js} +2 -2
- abstra_statics/dist/assets/{tasksController.b66c85ee.js → tasksController.22584849.js} +2 -2
- abstra_statics/dist/assets/{toggleHighContrast.510bdb1d.js → toggleHighContrast.fc60753d.js} +49 -49
- abstra_statics/dist/assets/{tsMode.5c0f732d.js → tsMode.a4b9b524.js} +2 -2
- abstra_statics/dist/assets/{typescript.0643a053.js → typescript.ab90fd1d.js} +2 -2
- abstra_statics/dist/assets/url.f490879d.js +2 -0
- abstra_statics/dist/assets/userStore.9e7a540a.js +2 -0
- abstra_statics/dist/assets/uuid.9161765c.js +2 -0
- abstra_statics/dist/assets/{vue-flow-background.011d27ef.js → vue-flow-background.8e4c22c3.js} +2 -2
- abstra_statics/dist/assets/{vue-quill.esm-bundler.487756a5.js → vue-quill.esm-bundler.64c5837f.js} +2 -2
- abstra_statics/dist/assets/{workspaceStore.87f8dbc6.js → workspaceStore.4f0c433f.js} +2 -2
- abstra_statics/dist/assets/{xml.c3c548ab.js → xml.3541c340.js} +3 -3
- abstra_statics/dist/assets/{yaml.0d909e29.js → yaml.314312d8.js} +3 -3
- abstra_statics/dist/console.html +15 -15
- abstra_statics/dist/editor.html +11 -11
- abstra_statics/dist/player.html +9 -9
- tests/e2e/test_crud_files.py +0 -1
- tests/e2e/test_requirements.py +41 -4
- abstra_statics/dist/assets/AbstraButton.vue_vue_type_script_setup_true_lang.9812dba9.js +0 -2
- abstra_statics/dist/assets/AbstraLogo.vue_vue_type_script_setup_true_lang.0c707a8b.js +0 -2
- abstra_statics/dist/assets/ApiKeys.902caf82.js +0 -2
- abstra_statics/dist/assets/App.f0468c7f.js +0 -2
- abstra_statics/dist/assets/App.vue_vue_type_style_index_0_lang.864018b5.js +0 -2
- abstra_statics/dist/assets/CloseCircleOutlined.2815d641.js +0 -2
- abstra_statics/dist/assets/ConsoleOmniChat.vue_vue_type_script_setup_true_lang.17b546d4.js +0 -2
- abstra_statics/dist/assets/ContentLayout.c7733a0e.js +0 -2
- abstra_statics/dist/assets/DocsButton.vue_vue_type_script_setup_true_lang.2c5aae83.js +0 -2
- abstra_statics/dist/assets/EnvVars.74e357b2.js +0 -2
- abstra_statics/dist/assets/ExclamationCircleOutlined.9b25ffda.js +0 -2
- abstra_statics/dist/assets/Files.d9621f31.js +0 -2
- abstra_statics/dist/assets/Home.9531e545.js +0 -2
- abstra_statics/dist/assets/Home.b12bb81a.js +0 -2
- abstra_statics/dist/assets/LoadingContainer.c40ae513.js +0 -2
- abstra_statics/dist/assets/LoadingOutlined.b607eff2.js +0 -2
- abstra_statics/dist/assets/Login.5f104bb1.js +0 -2
- abstra_statics/dist/assets/Logo.a34929e1.js +0 -2
- abstra_statics/dist/assets/Main.7030ea1d.js +0 -2
- abstra_statics/dist/assets/Navbar.a1055174.js +0 -2
- abstra_statics/dist/assets/NewEditor.6b2cb8e6.js +0 -8
- abstra_statics/dist/assets/OidcLoginCallback.445dd392.js +0 -2
- abstra_statics/dist/assets/OidcLogoutCallback.485fb0b9.js +0 -2
- abstra_statics/dist/assets/Organizations.1c35b6b8.js +0 -2
- abstra_statics/dist/assets/PhDownloadSimple.vue.3444d06b.js +0 -2
- abstra_statics/dist/assets/Project.2fdca57c.js +0 -2
- abstra_statics/dist/assets/Sql.1afe0bac.css +0 -1
- abstra_statics/dist/assets/Sql.7d92acbb.js +0 -5
- abstra_statics/dist/assets/Steps.b12e16c6.js +0 -2
- abstra_statics/dist/assets/Tables.aa6b418c.js +0 -2
- abstra_statics/dist/assets/TablesTabs.vue_vue_type_script_setup_true_lang.95ea10aa.js +0 -2
- abstra_statics/dist/assets/View.vue_vue_type_script_setup_true_lang.483e52f9.js +0 -2
- abstra_statics/dist/assets/WidgetPreview.b01eed73.js +0 -2
- abstra_statics/dist/assets/ant-design.4302db30.js +0 -2
- abstra_statics/dist/assets/asyncComputed.59410422.js +0 -2
- abstra_statics/dist/assets/colorHelpers.2a607581.js +0 -2
- abstra_statics/dist/assets/dayjs.9e279491.js +0 -2
- abstra_statics/dist/assets/editor.7e30500a.js +0 -2
- abstra_statics/dist/assets/editor.main.4675b13a.js +0 -2
- abstra_statics/dist/assets/fetch.13b54f0f.js +0 -2
- abstra_statics/dist/assets/index.4ecba4f7.js +0 -2
- abstra_statics/dist/assets/omniChatStore.16b8f156.js +0 -10
- abstra_statics/dist/assets/player.7f570660.js +0 -2
- abstra_statics/dist/assets/polling.0b08b681.js +0 -2
- abstra_statics/dist/assets/redirect.c06a7828.js +0 -2
- abstra_statics/dist/assets/router.7071f838.js +0 -2
- abstra_statics/dist/assets/url.b31d406a.js +0 -2
- abstra_statics/dist/assets/userStore.f2537ff3.js +0 -2
- abstra_statics/dist/assets/uuid.d6d43ef5.js +0 -2
- {abstra-3.23.7.dist-info → abstra-3.23.8.dist-info}/WHEEL +0 -0
- {abstra-3.23.7.dist-info → abstra-3.23.8.dist-info}/entry_points.txt +0 -0
- {abstra-3.23.7.dist-info → abstra-3.23.8.dist-info}/top_level.txt +0 -0
|
@@ -5,7 +5,10 @@ from abstra_internals.repositories.linter.models import (
|
|
|
5
5
|
LinterIssue,
|
|
6
6
|
LinterRule,
|
|
7
7
|
)
|
|
8
|
-
from abstra_internals.services.requirements import
|
|
8
|
+
from abstra_internals.services.requirements import (
|
|
9
|
+
RequirementsRepository,
|
|
10
|
+
requirement_to_dict,
|
|
11
|
+
)
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
class MergeDuplicatePackages(LinterFix):
|
|
@@ -52,9 +55,18 @@ class DuplicatePackagesInRequirements(LinterRule):
|
|
|
52
55
|
duplicates = requirements.get_duplicates()
|
|
53
56
|
issues = []
|
|
54
57
|
for name, versions in duplicates.items():
|
|
58
|
+
# Extract exact versions from specifiers
|
|
59
|
+
version_list = []
|
|
60
|
+
for r in versions:
|
|
61
|
+
req_dict = requirement_to_dict(r)
|
|
62
|
+
exact_version = None
|
|
63
|
+
for spec in req_dict.get("specifiers", []):
|
|
64
|
+
if spec["operator"] == "==":
|
|
65
|
+
exact_version = spec["version"]
|
|
66
|
+
break
|
|
67
|
+
version_list.append(exact_version)
|
|
68
|
+
|
|
55
69
|
issues.append(
|
|
56
|
-
DuplicatePackagesInRequirementsFound(
|
|
57
|
-
name=name, versions=[r.version for r in versions]
|
|
58
|
-
)
|
|
70
|
+
DuplicatePackagesInRequirementsFound(name=name, versions=version_list)
|
|
59
71
|
)
|
|
60
72
|
return issues
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from abstra_internals.repositories.linter.rules.duplicate_package_in_requirements import (
|
|
2
2
|
DuplicatePackagesInRequirements,
|
|
3
3
|
)
|
|
4
|
-
from abstra_internals.services.requirements import
|
|
4
|
+
from abstra_internals.services.requirements import (
|
|
5
|
+
RequirementsRepository,
|
|
6
|
+
requirement_to_dict,
|
|
7
|
+
)
|
|
5
8
|
from tests.fixtures import BaseTest
|
|
6
9
|
|
|
7
10
|
|
|
@@ -49,7 +52,16 @@ class TestDuplicatePackagesInRequirements(BaseTest):
|
|
|
49
52
|
requirements = RequirementsRepository.load()
|
|
50
53
|
self.assertEqual(len(requirements.libraries), 1)
|
|
51
54
|
self.assertEqual(requirements.libraries[0].name, "abstra")
|
|
52
|
-
|
|
55
|
+
|
|
56
|
+
# Use helper function to get version from Requirement
|
|
57
|
+
req_dict = requirement_to_dict(requirements.libraries[0])
|
|
58
|
+
# Extract exact version from specifiers
|
|
59
|
+
exact_version = None
|
|
60
|
+
for spec in req_dict.get("specifiers", []):
|
|
61
|
+
if spec["operator"] == "==":
|
|
62
|
+
exact_version = spec["version"]
|
|
63
|
+
break
|
|
64
|
+
self.assertEqual(exact_version, "1.0.0")
|
|
53
65
|
|
|
54
66
|
def test_invalid_with_distinct_versions_choose_second(self):
|
|
55
67
|
requirements_txt = self.root / "requirements.txt"
|
|
@@ -66,4 +78,13 @@ class TestDuplicatePackagesInRequirements(BaseTest):
|
|
|
66
78
|
requirements = RequirementsRepository.load()
|
|
67
79
|
self.assertEqual(len(requirements.libraries), 1)
|
|
68
80
|
self.assertEqual(requirements.libraries[0].name, "abstra")
|
|
69
|
-
|
|
81
|
+
|
|
82
|
+
# Use helper function to get version from Requirement
|
|
83
|
+
req_dict = requirement_to_dict(requirements.libraries[0])
|
|
84
|
+
# Extract exact version from specifiers
|
|
85
|
+
exact_version = None
|
|
86
|
+
for spec in req_dict.get("specifiers", []):
|
|
87
|
+
if spec["operator"] == "==":
|
|
88
|
+
exact_version = spec["version"]
|
|
89
|
+
break
|
|
90
|
+
self.assertEqual(exact_version, "2.0.0")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
+
from abstra_internals.consts.filepaths import ABSTRA_IGNORE_FILEPATH
|
|
3
4
|
from abstra_internals.repositories.linter.models import (
|
|
4
5
|
LinterFix,
|
|
5
6
|
LinterIssue,
|
|
@@ -13,7 +14,7 @@ class AddEnvToAbstraIgnore(LinterFix):
|
|
|
13
14
|
label = "Add env to abstra ignore"
|
|
14
15
|
|
|
15
16
|
def fix(self):
|
|
16
|
-
abstraignore_file = Settings.root_path /
|
|
17
|
+
abstraignore_file = Settings.root_path / ABSTRA_IGNORE_FILEPATH
|
|
17
18
|
with abstraignore_file.open("a") as file:
|
|
18
19
|
file.write("\n.env")
|
|
19
20
|
|
|
@@ -34,11 +35,7 @@ class EnvInBundle(LinterRule):
|
|
|
34
35
|
if not env_file.exists():
|
|
35
36
|
return []
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if FileSystemService.is_ignored(
|
|
40
|
-
ignored_patterns, env_file.relative_to(Settings.root_path)
|
|
41
|
-
):
|
|
38
|
+
if FileSystemService.is_ignored(env_file):
|
|
42
39
|
return []
|
|
43
40
|
|
|
44
41
|
return [EnvInBundleFound()]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from unittest import TestCase
|
|
2
2
|
|
|
3
|
+
from abstra_internals.consts.filepaths import ABSTRA_IGNORE_FILEPATH
|
|
3
4
|
from abstra_internals.repositories.linter.rules.env_in_bundle import EnvInBundle
|
|
4
5
|
from tests.fixtures import clear_dir, init_dir
|
|
5
6
|
|
|
@@ -20,7 +21,7 @@ class EnvInBundleTest(TestCase):
|
|
|
20
21
|
def test_env_on_bundle_valid_with_env(self):
|
|
21
22
|
rule = EnvInBundle()
|
|
22
23
|
env_file = self.root / ".env"
|
|
23
|
-
abstraignore_file = self.root /
|
|
24
|
+
abstraignore_file = self.root / ABSTRA_IGNORE_FILEPATH
|
|
24
25
|
abstraignore_file.write_text(".env")
|
|
25
26
|
env_file.touch()
|
|
26
27
|
self.assertEqual(len(rule.find_issues()), 0)
|
|
@@ -34,7 +35,7 @@ class EnvInBundleTest(TestCase):
|
|
|
34
35
|
def test_env_on_bundle_invalid_with_abstraignore_file(self):
|
|
35
36
|
env_file = self.root / ".env"
|
|
36
37
|
env_file.touch()
|
|
37
|
-
abstraignore_file = self.root /
|
|
38
|
+
abstraignore_file = self.root / ABSTRA_IGNORE_FILEPATH
|
|
38
39
|
abstraignore_file.touch()
|
|
39
40
|
rule = EnvInBundle()
|
|
40
41
|
self.assertEqual(len(rule.find_issues()), 1)
|
|
@@ -46,7 +47,7 @@ class EnvInBundleTest(TestCase):
|
|
|
46
47
|
self.assertEqual(len(rule.find_issues()), 1)
|
|
47
48
|
rule.find_issues()[0].fixes[0].fix()
|
|
48
49
|
self.assertEqual(len(rule.find_issues()), 0)
|
|
49
|
-
abstraignore_file = self.root /
|
|
50
|
+
abstraignore_file = self.root / ABSTRA_IGNORE_FILEPATH
|
|
50
51
|
self.assertTrue(abstraignore_file.exists())
|
|
51
52
|
with abstraignore_file.open("r") as file:
|
|
52
53
|
content = file.read()
|
|
@@ -8,6 +8,7 @@ from abstra_internals.repositories.linter.models import (
|
|
|
8
8
|
from abstra_internals.services.requirements import (
|
|
9
9
|
RequirementRecommendation,
|
|
10
10
|
RequirementsRepository,
|
|
11
|
+
requirement_to_dict,
|
|
11
12
|
)
|
|
12
13
|
|
|
13
14
|
|
|
@@ -21,7 +22,17 @@ class AddMissingPackagesToRequirements(LinterFix):
|
|
|
21
22
|
def fix(self):
|
|
22
23
|
requirements = RequirementsRepository.load()
|
|
23
24
|
requirement_name = self.requirement_recommendation.requirement.name
|
|
24
|
-
|
|
25
|
+
requirement_dict = requirement_to_dict(
|
|
26
|
+
self.requirement_recommendation.requirement
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Extract exact version from specifiers if available
|
|
30
|
+
requirement_version = None
|
|
31
|
+
for spec in requirement_dict.get("specifiers", []):
|
|
32
|
+
if spec["operator"] == "==":
|
|
33
|
+
requirement_version = spec["version"]
|
|
34
|
+
break
|
|
35
|
+
|
|
25
36
|
requirements.add(requirement_name, requirement_version)
|
|
26
37
|
RequirementsRepository.save(requirements)
|
|
27
38
|
|
|
@@ -1,32 +1,15 @@
|
|
|
1
|
-
import webbrowser
|
|
2
|
-
from pathlib import Path
|
|
3
1
|
from typing import List
|
|
4
2
|
|
|
5
|
-
from abstra_internals.repositories.linter.models import
|
|
6
|
-
LinterFix,
|
|
7
|
-
LinterIssue,
|
|
8
|
-
LinterRule,
|
|
9
|
-
)
|
|
3
|
+
from abstra_internals.repositories.linter.models import LinterIssue, LinterRule
|
|
10
4
|
from abstra_internals.repositories.project.project import LocalProjectRepository
|
|
11
5
|
from abstra_internals.utils.file import silent_traverse_code
|
|
12
6
|
|
|
13
7
|
|
|
14
|
-
class OpenBrokenFile(LinterFix):
|
|
15
|
-
file: Path
|
|
16
|
-
|
|
17
|
-
def __init__(self, file: Path) -> None:
|
|
18
|
-
self.label = f"Open {file}"
|
|
19
|
-
self.file = file
|
|
20
|
-
|
|
21
|
-
def fix(self):
|
|
22
|
-
webbrowser.open(self.file.absolute().as_uri())
|
|
23
|
-
|
|
24
|
-
|
|
25
8
|
class SyntaxErrorFound(LinterIssue):
|
|
26
9
|
def __init__(self, error: SyntaxError) -> None:
|
|
27
10
|
self.label = str(error)
|
|
28
11
|
if error.filename is not None:
|
|
29
|
-
self.fixes = [
|
|
12
|
+
self.fixes = []
|
|
30
13
|
|
|
31
14
|
|
|
32
15
|
class SyntaxErrors(LinterRule):
|
|
@@ -2,6 +2,7 @@ import sys
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import List
|
|
4
4
|
|
|
5
|
+
from abstra_internals.consts.filepaths import ABSTRA_IGNORE_FILEPATH
|
|
5
6
|
from abstra_internals.repositories.linter.models import (
|
|
6
7
|
LinterFix,
|
|
7
8
|
LinterIssue,
|
|
@@ -36,7 +37,7 @@ class AddVenvToAbstraIgnore(LinterFix):
|
|
|
36
37
|
root_path, prefix_path = get_root_and_prefix_path()
|
|
37
38
|
venv_folder = prefix_path.replace(root_path, "").lstrip("/")
|
|
38
39
|
|
|
39
|
-
abstraignore_file = Settings.root_path /
|
|
40
|
+
abstraignore_file = Settings.root_path / ABSTRA_IGNORE_FILEPATH
|
|
40
41
|
with abstraignore_file.open("a") as file:
|
|
41
42
|
file.write("\n")
|
|
42
43
|
file.write(venv_folder)
|
|
@@ -57,7 +58,7 @@ class VenvInBundle(LinterRule):
|
|
|
57
58
|
return prefix_path.startswith(root_path)
|
|
58
59
|
|
|
59
60
|
def virtualenv_in_abstraignore(self) -> bool:
|
|
60
|
-
abstraignore_file = Settings.root_path /
|
|
61
|
+
abstraignore_file = Settings.root_path / ABSTRA_IGNORE_FILEPATH
|
|
61
62
|
if not abstraignore_file.exists():
|
|
62
63
|
return False
|
|
63
64
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import flask
|
|
2
2
|
|
|
3
3
|
from abstra_internals.controllers.main import MainController
|
|
4
|
-
from abstra_internals.services.requirements import
|
|
4
|
+
from abstra_internals.services.requirements import (
|
|
5
|
+
RequirementsRepository,
|
|
6
|
+
create_requirement,
|
|
7
|
+
uninstall_requirement,
|
|
8
|
+
)
|
|
5
9
|
from abstra_internals.usage import editor_usage
|
|
6
10
|
|
|
7
11
|
|
|
@@ -40,8 +44,8 @@ def get_editor_bp(controller: MainController):
|
|
|
40
44
|
|
|
41
45
|
@bp.post("/<name>/uninstall")
|
|
42
46
|
def _uninstall_requirement(name: str):
|
|
43
|
-
req =
|
|
44
|
-
streamer = req
|
|
47
|
+
req = create_requirement(name)
|
|
48
|
+
streamer = uninstall_requirement(req)
|
|
45
49
|
if streamer is None:
|
|
46
50
|
flask.abort(403)
|
|
47
51
|
reqs = RequirementsRepository.load()
|
|
@@ -41,16 +41,6 @@ def get_editor_bp(controller: MainController):
|
|
|
41
41
|
except Exception as e:
|
|
42
42
|
return {"error": f"Could not get version: {str(e)}"}, 500
|
|
43
43
|
|
|
44
|
-
@bp.post("/open-file")
|
|
45
|
-
@editor_usage
|
|
46
|
-
def _open_file():
|
|
47
|
-
if not flask.request.json:
|
|
48
|
-
flask.abort(400)
|
|
49
|
-
file_path = flask.request.json["path"]
|
|
50
|
-
mode = flask.request.json.get("mode", "file")
|
|
51
|
-
controller.open_file(file_path, create_if_not_exists=True, mode=mode)
|
|
52
|
-
return {"success": True}
|
|
53
|
-
|
|
54
44
|
@bp.post("/init-file")
|
|
55
45
|
@editor_usage
|
|
56
46
|
def _init_file():
|
abstra_internals/services/fs.py
CHANGED
|
@@ -1,25 +1,11 @@
|
|
|
1
|
+
import fnmatch
|
|
1
2
|
import os
|
|
2
|
-
import re
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import List, Optional, Tuple
|
|
5
5
|
|
|
6
|
-
from abstra_internals.consts.filepaths import
|
|
7
|
-
ABSTRA_IGNORE_FILEPATH,
|
|
8
|
-
DOT_ABSTRA_DIR,
|
|
9
|
-
GIT_DIR_PATH,
|
|
10
|
-
)
|
|
6
|
+
from abstra_internals.consts.filepaths import ABSTRA_IGNORE_FILEPATH
|
|
11
7
|
from abstra_internals.settings import Settings
|
|
12
8
|
|
|
13
|
-
SKIPPED_DIRNAMES = {
|
|
14
|
-
"__pycache__",
|
|
15
|
-
"venv",
|
|
16
|
-
".venv",
|
|
17
|
-
".vscode",
|
|
18
|
-
".cursor",
|
|
19
|
-
".ruff_cache",
|
|
20
|
-
".abstra",
|
|
21
|
-
}
|
|
22
|
-
|
|
23
9
|
|
|
24
10
|
class FileSystemService:
|
|
25
11
|
@staticmethod
|
|
@@ -38,14 +24,14 @@ class FileSystemService:
|
|
|
38
24
|
include_dirs: bool = False,
|
|
39
25
|
use_ignore: bool = True,
|
|
40
26
|
allowed_suffixes: Optional[List[str]] = None,
|
|
41
|
-
|
|
27
|
+
recursive: bool = True,
|
|
42
28
|
) -> List[Path]:
|
|
43
29
|
"""
|
|
44
30
|
List all files in the given directory, optionally filtering by suffixes
|
|
45
31
|
and applying ignore files.
|
|
46
32
|
Args:
|
|
47
33
|
dir (Path): The directory to list.
|
|
48
|
-
apply_ignore_files (bool): Whether to apply ignore files: .gitignore and .
|
|
34
|
+
apply_ignore_files (bool): Whether to apply ignore files: .gitignore and .gitignore.
|
|
49
35
|
allowed_suffixes (Optional[List[str]]): List of allowed file suffixes. If None, all files are included.
|
|
50
36
|
"""
|
|
51
37
|
return FileSystemService.list_paths(
|
|
@@ -53,7 +39,7 @@ class FileSystemService:
|
|
|
53
39
|
include_dirs=include_dirs,
|
|
54
40
|
use_ignore=use_ignore,
|
|
55
41
|
allowed_suffixes=allowed_suffixes,
|
|
56
|
-
|
|
42
|
+
recursive=recursive,
|
|
57
43
|
)
|
|
58
44
|
|
|
59
45
|
@staticmethod
|
|
@@ -63,7 +49,7 @@ class FileSystemService:
|
|
|
63
49
|
include_dirs: bool = True,
|
|
64
50
|
use_ignore: bool = True,
|
|
65
51
|
allowed_suffixes: Optional[List[str]] = None,
|
|
66
|
-
|
|
52
|
+
recursive: bool = True,
|
|
67
53
|
) -> List[Path]:
|
|
68
54
|
""" "
|
|
69
55
|
List all files and directories in the given directory, optionally filtering by suffixes
|
|
@@ -71,7 +57,7 @@ class FileSystemService:
|
|
|
71
57
|
Args:
|
|
72
58
|
dir (Path): The directory to list.
|
|
73
59
|
include_dirs (bool): Whether to include directories in the results.
|
|
74
|
-
apply_ignore_files (bool): Whether to apply ignore files: .gitignore and .
|
|
60
|
+
apply_ignore_files (bool): Whether to apply ignore files: .gitignore and .gitignore.
|
|
75
61
|
allowed_suffixes (Optional[List[str]]): List of allowed file suffixes. If None, all files are included.
|
|
76
62
|
"""
|
|
77
63
|
if not dirpath.exists():
|
|
@@ -80,22 +66,7 @@ class FileSystemService:
|
|
|
80
66
|
if not dirpath.is_dir():
|
|
81
67
|
raise ValueError(f"Provided path {dirpath} is not a directory.")
|
|
82
68
|
|
|
83
|
-
|
|
84
|
-
*FileSystemService.load_ignore_patterns(
|
|
85
|
-
dirpath, ignore_dotenv=ignore_dotenv
|
|
86
|
-
),
|
|
87
|
-
*FileSystemService.load_ignore_patterns(
|
|
88
|
-
Path.cwd(), ignore_dotenv=ignore_dotenv
|
|
89
|
-
),
|
|
90
|
-
]
|
|
91
|
-
|
|
92
|
-
if use_ignore and FileSystemService.is_ignored(ignored_patterns, dirpath):
|
|
93
|
-
return []
|
|
94
|
-
|
|
95
|
-
if dirpath.name in SKIPPED_DIRNAMES:
|
|
96
|
-
return []
|
|
97
|
-
|
|
98
|
-
if dirpath == FileSystemService.venv_path():
|
|
69
|
+
if use_ignore and (FileSystemService.is_ignored(dirpath)):
|
|
99
70
|
return []
|
|
100
71
|
|
|
101
72
|
matches: List[Path] = []
|
|
@@ -103,28 +74,20 @@ class FileSystemService:
|
|
|
103
74
|
matches.append(dirpath)
|
|
104
75
|
|
|
105
76
|
for child in dirpath.iterdir():
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
not use_ignore
|
|
111
|
-
or not FileSystemService.is_ignored(
|
|
112
|
-
ignored_patterns, child.relative_to(dirpath)
|
|
113
|
-
)
|
|
114
|
-
)
|
|
77
|
+
if use_ignore and (FileSystemService.is_ignored(child)):
|
|
78
|
+
continue
|
|
79
|
+
if child.is_dir() or FileSystemService._suffix_allowed(
|
|
80
|
+
child, allowed_suffixes
|
|
115
81
|
):
|
|
116
82
|
matches.append(child)
|
|
117
|
-
|
|
118
|
-
if use_ignore and FileSystemService.is_ignored(
|
|
119
|
-
ignored_patterns, child.relative_to(dirpath)
|
|
120
|
-
):
|
|
121
|
-
continue
|
|
83
|
+
if child.is_dir() and recursive:
|
|
122
84
|
matches.extend(
|
|
123
85
|
FileSystemService.list_paths(
|
|
124
86
|
child,
|
|
125
87
|
include_dirs=include_dirs,
|
|
126
88
|
use_ignore=use_ignore,
|
|
127
89
|
allowed_suffixes=allowed_suffixes,
|
|
90
|
+
recursive=True,
|
|
128
91
|
)
|
|
129
92
|
)
|
|
130
93
|
return sorted(matches, key=lambda p: p.as_posix())
|
|
@@ -165,13 +128,8 @@ class FileSystemService:
|
|
|
165
128
|
- Case-sensitive search
|
|
166
129
|
"""
|
|
167
130
|
results: List[Tuple[str, int, str]] = []
|
|
168
|
-
ignored_patterns = FileSystemService.load_ignore_patterns(
|
|
169
|
-
root_dir, ignore_dotenv=False
|
|
170
|
-
)
|
|
171
131
|
for file_path in root_dir.glob(glob):
|
|
172
|
-
if file_path.is_file() and not FileSystemService.is_ignored(
|
|
173
|
-
ignored_patterns, file_path.relative_to(root_dir)
|
|
174
|
-
):
|
|
132
|
+
if file_path.is_file() and not FileSystemService.is_ignored(root_dir):
|
|
175
133
|
try:
|
|
176
134
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
177
135
|
for line_number, line in enumerate(f, start=1):
|
|
@@ -211,52 +169,139 @@ class FileSystemService:
|
|
|
211
169
|
return any(path.suffix.lower() == suffix.lower() for suffix in allowed_suffixes)
|
|
212
170
|
|
|
213
171
|
@staticmethod
|
|
214
|
-
def is_ignored(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
172
|
+
def is_ignored(path: Path):
|
|
173
|
+
path = path.resolve()
|
|
174
|
+
|
|
175
|
+
# Look for ignore file starting from the path's directory up to the root
|
|
176
|
+
# For files that don't exist, we need to check if it would be a file based on the path structure
|
|
177
|
+
# If the path has a suffix or ends with a filename-like component, treat it as a file
|
|
178
|
+
if path.exists():
|
|
179
|
+
current_dir = path.parent if path.is_file() else path
|
|
180
|
+
else:
|
|
181
|
+
# For non-existent paths, assume it's a file if it has a suffix or doesn't end with '/'
|
|
182
|
+
current_dir = (
|
|
183
|
+
path.parent if path.suffix or not str(path).endswith("/") else path
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
while current_dir != current_dir.parent: # Stop at filesystem root
|
|
187
|
+
ignore_file = current_dir / ABSTRA_IGNORE_FILEPATH
|
|
188
|
+
if ignore_file.exists():
|
|
189
|
+
try:
|
|
190
|
+
patterns = ignore_file.read_text().splitlines()
|
|
191
|
+
for pattern in patterns:
|
|
192
|
+
pattern = pattern.strip()
|
|
193
|
+
if pattern and not pattern.startswith("#"):
|
|
194
|
+
if FileSystemService._matches_gitignore_pattern(
|
|
195
|
+
path, pattern, current_dir
|
|
196
|
+
):
|
|
197
|
+
return True
|
|
198
|
+
except (IOError, UnicodeDecodeError):
|
|
199
|
+
continue
|
|
200
|
+
current_dir = current_dir.parent
|
|
201
|
+
|
|
218
202
|
return False
|
|
219
203
|
|
|
220
204
|
@staticmethod
|
|
221
|
-
def
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
205
|
+
def _matches_gitignore_pattern(path: Path, pattern: str, ignore_dir: Path) -> bool:
|
|
206
|
+
"""
|
|
207
|
+
Check if a path matches a gitignore pattern according to git rules.
|
|
208
|
+
"""
|
|
209
|
+
# Handle trailing slash - only matches directories
|
|
210
|
+
is_dir_only_pattern = pattern.endswith("/")
|
|
211
|
+
if is_dir_only_pattern:
|
|
212
|
+
pattern = pattern.rstrip("/")
|
|
213
|
+
# If pattern is for directories only, the path must be a directory
|
|
214
|
+
if path.exists() and not path.is_dir():
|
|
215
|
+
return False
|
|
216
|
+
# For non-existent paths, assume it's a directory if it doesn't have a suffix
|
|
217
|
+
elif not path.exists() and path.suffix:
|
|
218
|
+
return False
|
|
233
219
|
|
|
220
|
+
# Get relative path from ignore file directory
|
|
234
221
|
try:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
with open(git_ignore_path, "r", encoding="utf-8") as f:
|
|
240
|
-
ignored.extend(f.read().splitlines())
|
|
241
|
-
except (IOError, UnicodeDecodeError) as e:
|
|
242
|
-
print(f"Warning: Could not read ignore file: {e}")
|
|
243
|
-
return []
|
|
222
|
+
relative_path = path.relative_to(ignore_dir)
|
|
223
|
+
except ValueError:
|
|
224
|
+
# Path is not under the ignore directory
|
|
225
|
+
return False
|
|
244
226
|
|
|
245
|
-
|
|
246
|
-
FileSystemService._make_ignore_regex(p)
|
|
247
|
-
for p in ignored
|
|
248
|
-
if p
|
|
249
|
-
and not p.startswith("#")
|
|
250
|
-
and not p.startswith("!")
|
|
251
|
-
and (not p == ".env" or ignore_dotenv)
|
|
252
|
-
]
|
|
227
|
+
relative_str = relative_path.as_posix()
|
|
253
228
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
229
|
+
# Handle leading slash - pattern is relative to ignore file directory only
|
|
230
|
+
if pattern.startswith("/"):
|
|
231
|
+
pattern = pattern.lstrip("/")
|
|
232
|
+
# For patterns starting with /, only match at the immediate level
|
|
233
|
+
return fnmatch.fnmatch(relative_str, pattern)
|
|
234
|
+
|
|
235
|
+
# Handle double asterisk patterns
|
|
236
|
+
if "**" in pattern:
|
|
237
|
+
# Handle special cases for ** patterns
|
|
238
|
+
if pattern.startswith("**/"):
|
|
239
|
+
# **/foo matches foo at any level
|
|
240
|
+
sub_pattern = pattern[3:] # Remove **/
|
|
241
|
+
if fnmatch.fnmatch(path.name, sub_pattern):
|
|
242
|
+
return True
|
|
243
|
+
if fnmatch.fnmatch(relative_str, sub_pattern):
|
|
244
|
+
return True
|
|
245
|
+
# Also try matching at any directory level
|
|
246
|
+
parts = relative_path.parts
|
|
247
|
+
for i in range(len(parts)):
|
|
248
|
+
sub_path = "/".join(parts[i:])
|
|
249
|
+
if fnmatch.fnmatch(sub_path, sub_pattern):
|
|
250
|
+
return True
|
|
251
|
+
elif pattern.endswith("/**"):
|
|
252
|
+
# foo/** matches everything inside foo directory, NOT foo itself
|
|
253
|
+
base_pattern = pattern[:-3] # Remove /**
|
|
254
|
+
if relative_str.startswith(base_pattern + "/"):
|
|
255
|
+
return True
|
|
256
|
+
else:
|
|
257
|
+
# General ** handling - a/**/b patterns
|
|
258
|
+
# Split pattern on /** to handle complex cases
|
|
259
|
+
if "/**/" in pattern:
|
|
260
|
+
# Pattern like a/**/b
|
|
261
|
+
parts = pattern.split("/**/")
|
|
262
|
+
if len(parts) == 2:
|
|
263
|
+
prefix, suffix = parts
|
|
264
|
+
# Check if path matches prefix/**/suffix pattern
|
|
265
|
+
# This should match prefix/suffix, prefix/x/suffix, etc.
|
|
266
|
+
if relative_str == f"{prefix}/{suffix}":
|
|
267
|
+
return True
|
|
268
|
+
if fnmatch.fnmatch(relative_str, pattern.replace("**", "*")):
|
|
269
|
+
return True
|
|
270
|
+
# Try matching at different levels
|
|
271
|
+
path_parts = relative_path.parts
|
|
272
|
+
for i in range(len(path_parts)):
|
|
273
|
+
sub_path = "/".join(path_parts[i:])
|
|
274
|
+
test_pattern = pattern.replace("**", "*")
|
|
275
|
+
if fnmatch.fnmatch(sub_path, test_pattern):
|
|
276
|
+
return True
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
# Simple ** replacement
|
|
280
|
+
expanded_pattern = pattern.replace("**", "*")
|
|
281
|
+
if fnmatch.fnmatch(relative_str, expanded_pattern):
|
|
282
|
+
return True
|
|
283
|
+
# Also try matching the pattern at any directory level
|
|
284
|
+
parts = relative_path.parts
|
|
285
|
+
for i in range(len(parts)):
|
|
286
|
+
sub_path = "/".join(parts[i:])
|
|
287
|
+
if fnmatch.fnmatch(sub_path, expanded_pattern):
|
|
288
|
+
return True
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
# For patterns with slash in middle, treat as relative to ignore file
|
|
292
|
+
if "/" in pattern:
|
|
293
|
+
return fnmatch.fnmatch(relative_str, pattern)
|
|
294
|
+
|
|
295
|
+
# For patterns without slash, can match at any level
|
|
296
|
+
# Try matching the filename only
|
|
297
|
+
if fnmatch.fnmatch(path.name, pattern):
|
|
298
|
+
return True
|
|
299
|
+
|
|
300
|
+
# Try matching at any directory level
|
|
301
|
+
parts = relative_path.parts
|
|
302
|
+
for i in range(len(parts)):
|
|
303
|
+
sub_path = "/".join(parts[i:])
|
|
304
|
+
if fnmatch.fnmatch(sub_path, pattern):
|
|
305
|
+
return True
|
|
306
|
+
|
|
307
|
+
return False
|