From f965424faf1c5e6183eaa55bd3e9e080d66cab07 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 14 Apr 2024 22:06:24 +0200 Subject: Merging upstream version 0.1.22. Signed-off-by: Daniel Baumann --- GETTING-STARTED-WITH-dh-debputy.md | 188 +++++++++++++-------- MANIFEST-FORMAT.md | 2 +- debputy.pod | 76 ++++++--- src/debputy/commands/debputy_cmd/__main__.py | 30 ++-- .../commands/debputy_cmd/lint_and_lsp_cmds.py | 6 +- src/debputy/commands/debputy_cmd/plugin_cmds.py | 16 +- src/debputy/deb_packaging_support.py | 72 +++++--- src/debputy/debhelper_emulation.py | 15 +- src/debputy/dh_migration/migration.py | 4 +- src/debputy/dh_migration/migrators_impl.py | 31 ++-- src/debputy/filesystem_scan.py | 2 +- src/debputy/linting/lint_impl.py | 4 +- src/debputy/lsp/lsp_debian_changelog.py | 124 ++++++++++++++ src/debputy/lsp/lsp_debian_control.py | 130 ++++---------- src/debputy/lsp/lsp_debian_copyright.py | 156 ++++------------- src/debputy/lsp/lsp_debian_debputy_manifest.py | 10 +- src/debputy/lsp/lsp_debian_rules.py | 53 +++--- src/debputy/lsp/lsp_dispatch.py | 123 +++++++++++--- src/debputy/lsp/lsp_features.py | 22 +++ src/debputy/lsp/lsp_generic_deb822.py | 161 ++++++++++++++++++ src/debputy/lsp/quickfixes.py | 1 + src/debputy/lsp/spellchecking.py | 2 +- .../lsp/vendoring/_deb822_repro/__init__.py | 2 +- .../lsp/vendoring/_deb822_repro/locatable.py | 2 +- src/debputy/lsp/vendoring/_deb822_repro/parsing.py | 2 +- src/debputy/lsp/vendoring/_deb822_repro/tokens.py | 2 +- src/debputy/manifest_parser/declarative_parser.py | 4 +- src/debputy/plugin/api/impl.py | 8 +- src/debputy/plugin/api/test_api/test_spec.py | 4 +- src/debputy/plugin/debputy/binary_package_rules.py | 10 +- src/debputy/plugin/debputy/manifest_root_rules.py | 10 +- src/debputy/plugin/debputy/private_api.py | 38 ++--- src/debputy/transformation_rules.py | 2 +- 33 files changed, 833 insertions(+), 479 deletions(-) diff --git a/GETTING-STARTED-WITH-dh-debputy.md b/GETTING-STARTED-WITH-dh-debputy.md index 5d5d253..d14e6e4 100644 --- a/GETTING-STARTED-WITH-dh-debputy.md +++ b/GETTING-STARTED-WITH-dh-debputy.md @@ -4,20 +4,16 @@ _This is [how-to guide] and is primarily aimed at getting a task done._ -This document will help you convert a Debian source package using the `dh` sequencer from debhelper to -use `dh-debputy`. Prerequisites for this how-to guide: +This document will help you convert a Debian source package using the `dh` sequencer from `debhelper` to +use `dh-debputy`, where `debputy` is integrated with `debhelper`. Prerequisites for this how-to guide: - * You have a Debian source package using the `dh` sequencer. Ideally a simple one as not all packages - can be converted at this time. Note that `debputy` does *not* interact well with most third-party - `dh` addons. You are recommended to start with source packages without third-party `dh` addons. + * You have a Debian source package using the `dh` sequencer on `debhelper` compat level 12 or later. * It is strongly recommended that your package is bit-for-bit reproducible before starting the conversion as that makes it easier to spot bugs introduced by the conversion! The output of `debputy` will *not* be bit-for-bit reproducible with `debhelper` in all cases. However, the differences should be easy to review with `diffoscope` if the package was already bit-for-bit reproducible. - - Note that `debputy` does not use `strip-nondeterminism`. The bit-for-bit reproducible property should - ideally not rely on `strip-nondeterminism` for now. -Note that during the conversion (particularly Step 1 and Step 2), you may find that `debputy` cannot +Note that during the conversion (particularly Step 2 and Step 3), you may find that `debputy` cannot support the requirements for your package for now. Feel free to file an issue for what is holding you back in the [debputy issue tracker]. @@ -31,40 +27,79 @@ how these concepts relates to `debian/control` (the source control file). Additionally, since this is about `debputy` integration with debhelper, the reader is expected to be familiar with `debhelper` (notably the `dh` style `debian/rules`). -## Step 1: Have `debputy` convert relevant debhelper files +## Step 1: Choose the level of migration target -The `debputy` integration with debhelper removes (replaces) some debhelper tools, but does **not** +When migrating a package to `debputy`, you have to choose at which level you want `debputy` to manage +your packaging. At the time of writing, your options are: + + 1) `dh-sequence-zz-debputy-rrr`: Minimal integration necessary to get support for `Rules-Requires-Root: no` + in all packages. It is compatible with most `dh` based packages as it only replaces very few helpers + and therefore usually also requires very little migration. + + 2) `dh-sequence-zz-debputy`: In this integration mode, `debhelper` manages the upstream build system + (that is, anything up to and including `dh_auto_install`). Everything else is managed by `debputy`. + This mode is less likely to be compatible with complex packaging at the time of writing. Notably, + most `debhelper` add-ons will not be compatible with `debputy` in this integration mode. + - For this integration mode, you are recommended to pick a simple package as many packages cannot be + converted at this time. Note that this mode does *not* interact well with most third-party + `dh` addons. You are recommended to start with source packages without third-party `dh` addons. + +Since you can always migrate from "less integrated" to "more integrated", you are recommended to start +with `dh-sequence-zz-debputy-rrr` first. If that works, you can re-run the migration with +`dh-sequence-zz-debputy` as the target to see if further integration seems feasible / desirable. + +Note: More options may appear in the future. + +## Step 2: Have `debputy` convert relevant `debhelper` files + +The `debputy` integration with `debhelper` removes (replaces) some debhelper tools, but does **not** read their relevant config files. These should instead be converted in to the new format. You can have `debputy` convert these for you by using the following command: # Dry-run conversion, creates `debian/debputy-manifest.new` if anything is migrated and prints a summary - # of what is done. Remove the --no-act when you are ready to commit the conversion. - $ debputy migrate-from-dh --no-act + # of what is done and what you manually need to fix. Replace `--no-apply-changes` with `--apply-changes` + # when you are ready to commit the conversion. + # - Replace MIGRATION_TARGET in the command with the concrete target from step 1 + $ debputy migrate-from-dh --migration-target MIGRATION_TARGET --no-apply-changes + +Note: Running `debputy migrate-from-dh ...` multiple times is supported and often recommended as it can +help you assert that all detectable issues have been fixed. If relevant, `debputy` may inform you that: - 1) the package is using an unsupported feature. Unless that functionality can easily be removed (e.g., it is now + 1) the package needs to change build dependencies or/and activate `dh` add-ons. Concretely, it will ask you to + ensure that the relevant `dh` add-on is added for `debputy` sequence matching the migration target. Additionally, + it may ask you to add build-dependencies for activating `debputy` plugins to match certain `dh` add-ons. + - Note that if `debputy` asks you to add a `debputy` plugin without removing the `dh` add-on it supposedly + replaces then keep the `dh` active. Sometimes the add-on is only partly converted and sometimes the `dh` + sequence is used to pull relevant build-dependencies. + + 2) the package is using an unsupported feature. Unless that functionality can easily be removed (e.g., it is now obsolete) or easily replaced, then you probably do not want to convert the package to `debputy` at this time. * One common source of unsupported features are dh hook targets (such as override targets), which will be covered in slightly more detail in the next section. If `debputy` detected any hook targets, it is probably worth it to - check if these can be migrated before continuing. + check if these can be migrated before continuing or run earlier in a hook target that is not removed. + * Other cases can be that the package uses a feature, where `debputy` behaves differently than `debhelper` would + at the given compat level. In this case, `debputy` will recommend that you perform a compat upgrade in + `debhelper` before migrating to `debputy`. * It is worth noting that the migration tool will update an existing manifest when re-run. You can safely "jump" around in the order of the steps, when you try to migrate, if that better suits you. - 2) the migration would trigger a conflict. This can occur for two reasons: + + 3) the migration would trigger a conflict. This can occur for two reasons: * The debhelper configuration has the conflict (example [#934499]), where debhelper is being lenient and ignores the problem. In this case, you need to resolve the conflict in the debhelper config and re-run `debputy`. * The package has a manifest already with a similar (but not identical) definition of what the migration would generate. In this case, you need to reconcile the definitions manually (and removing one of them). After that you can re-run `debputy`. - As an example, if you had a `debian/foo.links` (with `foo` being defined in `debian/control`) containing the following: usr/share/foo/my-first-symlink usr/share/bar/symlink-target usr/lib/{{DEB_HOST_MULTIARCH}}/my-second-symlink usr/lib/{{DEB_HOST_MULTIARCH}}/baz/symlink-target -The `debputy migrate-from-dh` tool would generate a manifest looking something like this: +The `debputy migrate-from-dh --migration-target dh-sequence-zz-debputy` tool would generate a manifest looking +something like this: manifest-version: "0.1" packages: @@ -77,10 +112,47 @@ The `debputy migrate-from-dh` tool would generate a manifest looking something l path: usr/lib/{{DEB_HOST_MULTIARCH}}/my-second-symlink target: /usr/lib/{{DEB_HOST_MULTIARCH}}/baz/symlink-target -## Step 2: Migrate override/hook-targets in debian/rules -Once you activate the `debputy` debhelper integration (see Step 3), the following debhelper tools will be -removed from the `dh` sequence. +## Step 3: Migrate override/hook-targets in debian/rules + +Have a look at the hook targets that `debputy migrate-from-dh` flags as unsupported and migrate them to +`debputy` features or move them to earlier hook targets that are supported. See also the subsections +below for concrete advice on how to deal with override or hook targets for some of these tools. However, +since `debhelper` hooks are arbitrary code execution hooks, there will be many cases that the guide will +not be able to cover or where `debputy` may not have the feature to support your hook target. + +While you could manually force any of the removed `debhelper` tools to be run via a hook target, they are +very likely to feature interact with `debputy`. Either causing `debputy` to replace their output completely +or by having the tool overwrite what `debputy` did (depending on the exact order). If you find, you +*really* need to run one of these tools, because `debputy` is not supporting a particular feature they have, +then you are probably better off not migrate to this level of `debputy` integration at this time. + + +### Affected debhelper command list for `dh-sequence-zz-debputy-rrr` integration mode + +The following `debhelper` commands are replaced in the `dh-sequence-zz-debputy-rrr` integration mode. Generally, +`debputy migrate-from-dh` will warn you if there is anything to worry about in relation to these commands. + + * `dh_fixperms` + * `dh_gencontrol` + * `dh_md5sums` + * `dh_builddeb` + +In case something is flagged, it is most likely a hook target, which either have to be converted to `debputy`'s +features or moved earlier. The most common cases are hook targets for `dh_fixperms` and `dh_gencontrol`, which +have sections below advising how to approach those. + +### Affected debhelper command list for `dh-sequence-zz-debputy` integration mode + +The following `debhelper` commands are replaced in the `dh-sequence-zz-debputy` integration mode. The +`debputy migrate-from-dh` command will warn you where it can when some of these are used. However, some +usage may only become apparent during package build. Therefore, `debputy migrate-from-dh` can be silent +even though `debputy` during the build will flag an unsupported feature. + +You are recommended to skim through this list for commands where your package might be using non-trivial +or non-default features, as those are likely to prevent migration at this time. Pay extra attention to +commands marked with **(!)** as `debputy` has zero or almost zero support for features from these +commands. Other tools will have some form of support (often at least a commonly used flow/feature set). * `dh_install` * `dh_installdocs` @@ -93,10 +165,14 @@ removed from the `dh` sequence. * `dh_installdebconf` **(!)** * `dh_installemacsen` **(!)** * `dh_installinfo` - * `dh_installinit` **(!)** + * `dh_installinit` + - Only default service mode (enable, standard restart) are supported. Any use of `--no-start`, etc. + is unsupported * `dh_installsysusers` * `dh_installtmpfiles` - * `dh_installsystemd` **(!)** + * `dh_installsystemd` + - Only default service mode (enable, standard restart) are supported. Any use of `--no-start`, etc. + is unsupported * `dh_installsystemduser` **(!)** * `dh_installmenu` **(!)** * `dh_installmime` @@ -129,53 +205,49 @@ removed from the `dh` sequence. * `dh_installdeb` * `dh_gencontrol` * `dh_md5sums` - * `dh_builddeb` -Have a look at `debian/rules` and migrate any overrides you have for them to `debputy` features or other -hook targets. See also the subsections below for concrete advice on how to deal with override or hook targets -for some of these tools. However, since `debhelper` hooks are arbitrary code execution hooks, there will be -many cases that the guide will not be able to cover or where `debputy` may not have the feature to support your -hook target. - -While you could manually force these tools to be run via a hook target, they are very likely to feature -interact with `debputy`. Either causing `debputy` to replace their output completely or by having the tool -overwrite what `debputy` did (depending on the exact order). If you find, you *really* need to run one of these -tools, because `debputy` is not supporting a particular feature they have, then you are probably better off waiting -with migrating the package in question. - -Tools marked with **(!)** have no debputy support at all. If you rely on these tools, migration is unlikely -to succeed. Other tools will have some form of support (often at least a commonly used flow/feature set). -Where possible, `debputy migrate-from-dh --no-act` will detect these completely unsupported tools via existence -of their config files or indirectly debhelper hook targets for these tools. However, some tools may only be -detected late into the build (which is the case with `dh_usrlocal`). +As mentioned, `debputy migrate-from-dh --no-act` will detect these completely unsupported tools via existence +of their config files or indirectly debhelper hook targets for these tools where possible. However, some +tools may only be detected late into the build (which is the case with `dh_usrlocal` as a concrete +example). ### Review and migrate any installation code from `dh_install`, `dh_installdocs`, etc. (if any) +_This is section does **not** apply to the `dh-sequence-zz-debputy-rrr` integration mode._ + All code in `debian/rules` that involves copying or moving files into packages or around in packages must be moved to the manifest's `installations` section. The migration tool will attempt to auto-migrate any rules from `debian/install` (etc.). However, be aware of: 1) The migration tool assumes none of install rules overlap. Most packages do not have overlapping install rules as it tends to lead to file conflicts. If the install rules overlap, `debputy` will - detect it at runtime and stop with an error. In that case, you will have to tweak the migrated rules - manually. - 2) Any overrule target that copies or moves files around in packages must be moved to `installations` + detect it at *runtime* (not migration time) and stop with an error. In that case, you will have to + tweak the migrated rules manually. + + 2) Any hook target that copies or moves files around in packages must be moved to `installations` (per source) or `transformations` (per package) depending on the case. + - For source packages installing content via `debian/tmp`, you can use `install` to rename paths as you install them and `discard` (under `installations`) to ignore paths that should not be installed. + - For source packages installing content via `debian/`, then everything in there is "auto-installed". If you need to tweak that content, you can use `remove` or `move` transformations (under `transformations`) for manipulation the content. -Note that the migrator "blindly" appends new rules to the bottom of `installations` if you have any existing +Keep in mind that the migrator "blindly" appends new rules to the bottom of `installations` if you have any existing rules (per "none of the install rules overlap"-logic mentioned above). If you cannot convert all debhelper config files in one go, or you manually created some installation rules before running the migrator, you may need to manually re-order the generated installation rules to avoid conflicts due to inadequate ordering. You probably want to do so any way for readability/grouping purposes. +Note: For very complex hook targets that manipulate context in packages, it is possible to keep the logic in +`debian/rules` by moving the logic to `execute_after_dh_auto_install`. This will mainly be useful if you are +using complex rules for pruning or moving around files in a way that are not easily representable via globs. #### Double-check the `language` settings on all `install-man` rules in `installations` +_This is section does **not** apply to the `dh-sequence-zz-debputy-rrr` integration mode._ + The `dh_installman` tool auto-detects the language for manpages via two different methods: 1) By path name (Does the installation path look like `man//man
/...`?) @@ -183,17 +255,15 @@ The `dh_installman` tool auto-detects the language for manpages via two differen Both methods are used in order with first match being the method of choice. Unfortunately, the second method is prune to false-positives. Does `foo.pl.1` mean a Polish translation of `foo.1` or is it the -manpage for a Perl script called `foo.pl` (similar happens for languages/file extensions). +manpage for a Perl script called `foo.pl` (similar happens for other languages/file extensions). To avoid this issue, `debputy` uses 1) by default and only that. Option 2) can be chosen by setting -`language: derive-from-basename` on the concrete install rule. The problem is that the migration tool -has to guess, and it is hard to tell if rules like `man/*.1` would need option 2) - especially when the -migration tool does not attempt to resolve the glob (which it currently does not). +`language: derive-from-basename` on the concrete installation rule. The problem is that the migration tool +has to guess, and it is hard to tell if rules like `man/*.1` would need option 2). Therefore, take a critical look at the generated `install-man` rules and the generated `language` property (or lack thereof). - ### Convert your overrides or excludes for `dh_fixperms` (if any) The `debputy` tool will normalize permissions like `dh_fixperms` during package build. If you have @@ -274,25 +344,7 @@ _Remember to merge your manifest with previous steps rather than replacing it!_ `debputy migrate-from-dh` will merge its changes into existing manifests and can safely be re-run after adding/writing this base manifest. -## Step 3: Adding the `dh-sequence-zz-debputy` sequence to Build-Depends - -The recommended way to do so is to add `dh-sequence-zz-debputy` to the `Build-Depends:` field. - -The `zz-` in `zz-debputy` is there to ensure the `debputy` add-on is loaded last by debhelper for -the rare case there any other debhelper addons still active as the `debputy` sequence does not really -play well with other `dh` addons. When there are other add-ons, it is generally better for `debputy` to -be loaded last (as add-on order matters per [#885580]). - -## Step 4: Replace third-party dh add-ons to debputy plugins - -Packages using third-party `dh` add-ons may need to replace these with `debputy` plugins assuming the -add-on has a `debputy` plugin to replace it in the first place. To request a `debputy` plugin, you -will have to add `debputy-plugin-X` into your `Build-Depends`, where `X` is the name of the plugin. - -Note that `debputy` does not support the same features as `debhelper` at the moment for conditional -plugin loading. Therefore, the plugins must be in `Build-Depends`. - -## Step 5: Verify the build +## Step 4: Verify the build At this stage, if there are no errors in your previous steps, you should be able to build your changed package with `debputy`. We recommend that you take time to verify this. For some packages, diff --git a/MANIFEST-FORMAT.md b/MANIFEST-FORMAT.md index 13bedf7..1b369cf 100644 --- a/MANIFEST-FORMAT.md +++ b/MANIFEST-FORMAT.md @@ -931,7 +931,7 @@ It works similar to the `install` rule except: must be provided). 2) Both `as` and `dest-dirs` are now list of paths must have at least two paths when provided. -Please see `debputy plugin show plugable-manifest-rules multi-dest-install` for the full documentation. +Please see `debputy plugin show pluggable-manifest-rules multi-dest-install` for the full documentation. # Binary package rules diff --git a/debputy.pod b/debputy.pod index 593f02d..e01f5a5 100644 --- a/debputy.pod +++ b/debputy.pod @@ -50,6 +50,10 @@ The B command will attempt to migrate the current package to B< For this command to be successful, it must be run from the root of an unpacked Debian source package and the package should be using the B sequencer. +If you are looking to migrate to B from B, you may want to have a look at the +B file (in the source root or in F). That document +is a how-to guide that as extended advice on migration from B. + The migration can rerun with newer version of B that might provide more features since the previous one even inside the same level of adoption. As an example, B added support for automatic relationship substvars. Re-running the migrator for an already migrated package can be used to detect any @@ -57,15 +61,16 @@ unnecessary explicit relationship substvars as unnecessary. The default migration target is based on existing features in the packaging where present. As an example, a build-dependency on B will make the migration tool only run migrators relevant -for B. If the migration cannot identify any target markers, it has a built-in -default target. The default target will change over time as B and the migrator mature. The -B<--migration-target> option can be used to overrule this automatic detection. This can be useful both to -expand on the migration level without performing any changes or to choose a more conservative initial -migration level. +for B (assuming no other markers are present). If the migration cannot identify +any target markers, it has a built-in default target. The default target will change over time as +B and the migrator mature. The B<--migration-target> option can be used to overrule this automatic +detection. This can be useful both to expand on the migration level without performing any changes or to +choose a non default initial migration level. Generally, the migration tool can take you from "less adoption" towards "more adoption" of B but not the inverse. As an example, migrating from B towards B -is supposed, but the reverse is not. +is supposed, but the reverse is not. Use of version control is recommended for undoing transition if +necessary. If any migrations involve creating a new or changing an existing F, the migration tool will first write a draft to F. From there, it may be renamed to @@ -92,6 +97,11 @@ to be reduced to a warning. This is only useful to reduce issues to warnings if you are reasonable sure that you can remove the feature or convert it to something that B supports. +In some cases, it might be helpful to comment out the offending feature and re-run the migration rather +than using B<--acceptable.migration-issues>. As an example, if a single line of F file +is problematic, commenting it out will have B migrate the rest of the file for you leaving you +only to manually migrate a single line. + =item B<--apply-changes>, B<--no-act>, B<--no-apply-changes> These options decide whether the migration tool should perform destructive actions such as overwriting the @@ -103,7 +113,7 @@ control systems. If support for detecting version control systems is added, this Note that the migration may replace B regardless of this option as that is its output for the updated draft manifest. -The B<--no-act> is an alias of B<--no-apply-changes> to match the name that B command use. +The B<--no-act> is an alias of B<--no-apply-changes> to match the name that B commands use. =back @@ -165,15 +175,15 @@ A short comparison of B vs. other tools: =item B The language server feature from B provides an interactive and online version of the linting -from B directly in any LSP capable editor (with the proper glue configuration). The LSP +from B directly in any LSP capable editor with the proper glue configuration. The LSP feature gives you instant gratification, some additional editor-only features and interactive choices of available quickfixes. The "downside" of the B feature is that it requires a LSP capable editor and each editor has their own glue configuration. Since the B language server is new, almost no editor has built-in -glue configuration meaning it has a steeper "learning curve" to get started. Additionally, some times -you want the checks for CI checks or the current state of the package (without having to open each -file in an editor). Here B covers the same issues without the need for anything else. +glue configuration meaning it has a steeper learning curve to get started. Additionally, some times +you want the checks for CI checks or the current state of the package without having to open each +file in an editor. Here B covers the same issues without the need for anything else. =item B @@ -210,7 +220,7 @@ Start the B language server (per B specificat Many modern editors can delegate language support to a B or indirectly via other features like supporting B (which in turn can delegate to a language server). The B tool provides one for many common packaging formats via the B subcommand for file -formats such as B, B and B. +formats such as B, B and B (DEP-5). You will often need some editor specific glue configuration to link a given file format or name to the language server. The B might provide an example glue snippet for @@ -255,8 +265,15 @@ in B is that you cannot interactively choose which quickfix to app On save actions (currently only "prune trailing whitespace"). +=item - + +Folding ranges (multi-line comments). + =back +Note these features are subject to the editor supporting them, correct language IDs being +passed to B, etc. + =item lsp editor-config B Provide an example configuration glue for using the B with the given editor @@ -373,25 +390,25 @@ This topic provides a listing of all plugins that B is aware of. This topic can only used with B and not with B. -=item plugable-manifest-rules (aliases: pmr, p-m-r) +=item pluggable-manifest-rules (aliases: pmr, p-m-r) The B manifest provides a number of places where the packager can provide a number of different rules such as B vs. B vs. B under B. These are called -plugable manifest rules and this topic provides insights to which rules are available where. +pluggable manifest rules and this topic provides insights to which rules are available where. -When used with B, B will list all plugable manifest rules available. When used with B, +When used with B, B will list all pluggable manifest rules available. When used with B, a rule name must be provided and B will then provide details about the rule. These details include attributes available (where applicable) and any reference documentation provided by the plugin. As an example, here is how you get the details about the install rule: - debputy plugin show plugable-manifest-rules install + debputy plugin show pluggable-manifest-rules install When a rule name is ambiguous, B will ask that you use B instead of just B. As an example: - debputy plugin show plugable-manifest-rules TransformationRule::remove - debputy plugin show plugable-manifest-rules DpkgMaintscriptHelperCommand::remove + debputy plugin show pluggable-manifest-rules TransformationRule::remove + debputy plugin show pluggable-manifest-rules DpkgMaintscriptHelperCommand::remove Note the type names (such as B) are currently an implementation detail and may change in the future. @@ -458,7 +475,7 @@ As an example: This topic cover type mappings that explains how some non-trivial types are interpreted. These covers types like B and B, which are used by other features such as -plugable manifest rules. +pluggable manifest rules. When used with B, any plugin provided documentation and example inputs will be displayed for that rule. @@ -583,9 +600,7 @@ This integration level replaces the minimal number of commands necessary to prov support for B package (even those needing static ownership). The sequence is often compatible with other B sequences. To use this B integration level, any custom file ownership and mode I be migrated to the B. Custom binary package version (B<-v> to -B) is supported via the manifest. In this integration level, the B -keyword and all B cannot be used. Installing files into the package is still done via -helpers such as B. +B) is supported via the manifest. This migration level corresponds to a B on B. @@ -611,6 +626,23 @@ dh_builddeb =back +Note the following B features are disabled in this integration mode: + +=over 4 + +=item - + +Installation rule (the B keyword in the manifest). Any installation of content +that should go resulting B<.deb> or B<.udeb> should happen via B's mechanisms such +as B. + +=item - + +Metadata detectors from plugins. Instead, substvars, maintscripts and triggers are handled and generated +per B conventions. + +=back + =item dh-sequence-zz-debputy With this integration level, B will take over all installation of files into the packages. diff --git a/src/debputy/commands/debputy_cmd/__main__.py b/src/debputy/commands/debputy_cmd/__main__.py index d894731..27edf49 100644 --- a/src/debputy/commands/debputy_cmd/__main__.py +++ b/src/debputy/commands/debputy_cmd/__main__.py @@ -39,6 +39,7 @@ from debputy.commands.debputy_cmd.context import ( from debputy.commands.debputy_cmd.dc_util import flatten_ppfs from debputy.commands.debputy_cmd.output import _stream_to_pager from debputy.dh_migration.migrators import MIGRATORS +from debputy.dh_migration.migrators_impl import read_dh_addon_sequences from debputy.exceptions import ( DebputyRuntimeError, PluginNotFoundError, @@ -93,8 +94,6 @@ from debputy.dh_migration.models import AcceptableMigrationIssues from debputy.packages import BinaryPackage from debputy.debhelper_emulation import ( dhe_pkgdir, - parse_drules_for_addons, - extract_dh_addons_from_control, ) from debputy.deb_packaging_support import ( @@ -1053,19 +1052,6 @@ def _merge_ppfs( _merge_list(details, "documentation-uris", documentation_uris) -def _is_debputy_package(context: CommandContext, dh_rules_addons: Set[str]) -> bool: - drules = context.debian_dir.get("rules") - sequences = set() - source_package = context.source_package() - if drules is not None and not drules.is_dir: - parse_drules_for_addons(drules, dh_rules_addons) - extract_dh_addons_from_control(source_package.fields, sequences) - sequences.update(dh_rules_addons) - return ( - "debputy" in sequences or "zz-debputy" in sequences or "zz_debputy" in sequences - ) - - def _extract_dh_compat_level() -> Tuple[Optional[int], int]: try: output = subprocess.check_output( @@ -1254,8 +1240,18 @@ def _annotate_debian_directory(context: CommandContext) -> None: annotated: List[PackagingFileInfo] = [] seen_paths = set() - drules_sequences = set() - is_debputy_package = _is_debputy_package(context, drules_sequences) + r = read_dh_addon_sequences(context.debian_dir) + if r is not None: + bd_sequences, dr_sequences = r + drules_sequences = bd_sequences | dr_sequences + else: + drules_sequences = set() + is_debputy_package = ( + "debputy" in drules_sequences + or "zz-debputy" in drules_sequences + or "zz_debputy" in drules_sequences + or "zz-debputy-rrr" in drules_sequences + ) dh_compat_level, dh_assistant_exit_code = _extract_dh_compat_level() dh_issues = [] diff --git a/src/debputy/commands/debputy_cmd/lint_and_lsp_cmds.py b/src/debputy/commands/debputy_cmd/lint_and_lsp_cmds.py index 0f2ae0f..b30b98d 100644 --- a/src/debputy/commands/debputy_cmd/lint_and_lsp_cmds.py +++ b/src/debputy/commands/debputy_cmd/lint_and_lsp_cmds.py @@ -14,6 +14,7 @@ _EDITOR_SNIPPETS = { ;; Add to ~/.emacs or ~/.emacs.d/init.el and then activate via `M-x eglot`. ;; ;; Requires: apt install elpa-dpkg-dev-el + ;; Recommends: apt install elpa-markdown-mode ;; Make emacs recognize debian/debputy.manifest as a YAML file (add-to-list 'auto-mode-alist '("/debian/debputy.manifest\\'" . yaml-mode)) @@ -52,13 +53,14 @@ _EDITOR_SNIPPETS = { # Inform vim/ycm about the debputy LSP let g:ycm_language_server = [ \\ { 'name': 'debputy', - \\ 'filetypes': [ 'debcontrol', 'debcopyright', 'debchangelog', 'make'], + \\ 'filetypes': [ 'debcontrol', 'debcopyright', 'debchangelog', 'make', 'yaml'], \\ 'cmdline': [ 'debputy', 'lsp', 'server' ] \\ }, \\ ] packadd! youcompleteme - nmap d (YCMHover) + # Add relevant ycm keybinding such as: + # nmap d (YCMHover) """ ), } diff --git a/src/debputy/commands/debputy_cmd/plugin_cmds.py b/src/debputy/commands/debputy_cmd/plugin_cmds.py index 3d8bdcb..54acdc5 100644 --- a/src/debputy/commands/debputy_cmd/plugin_cmds.py +++ b/src/debputy/commands/debputy_cmd/plugin_cmds.py @@ -437,8 +437,8 @@ def _parser_type_name(v: Union[str, Type[Any]]) -> str: @plugin_list_cmds.register_subcommand( - ["plugable-manifest-rules", "p-m-r", "pmr"], - help_description="Plugable manifest rules (such as install rules)", + ["pluggable-manifest-rules", "p-m-r", "pmr"], + help_description="Pluggable manifest rules (such as install rules)", argparser=TEXT_CSV_FORMAT_NO_STABILITY_PROMISE, ) def _plugin_cmd_list_manifest_rules(context: CommandContext) -> None: @@ -953,8 +953,8 @@ def _plugin_cmd_show_ppf(context: CommandContext) -> None: @plugin_show_cmds.register_subcommand( - ["plugable-manifest-rules", "p-m-r", "pmr"], - help_description="Plugable manifest rules (such as install rules)", + ["pluggable-manifest-rules", "p-m-r", "pmr"], + help_description="Pluggable manifest rules (such as install rules)", argparser=add_arg( "pmr_rule_name", metavar="rule-name", @@ -991,8 +991,8 @@ def _plugin_cmd_show_manifest_rule(context: CommandContext) -> None: if len(matched) != 1 and (matched or rule_name != "::"): if not matched: _error( - f"Could not find any plugable manifest rule related to {parsed_args.pmr_rule_name}." - f" Please use `debputy plugin list plugable-manifest-rules` to see the list of rules." + f"Could not find any pluggable manifest rule related to {parsed_args.pmr_rule_name}." + f" Please use `debputy plugin list pluggable-manifest-rules` to see the list of rules." ) match_a = matched[0][0] match_b = matched[1][0] @@ -1000,7 +1000,7 @@ def _plugin_cmd_show_manifest_rule(context: CommandContext) -> None: f"The name {rule_name} was ambiguous and matched multiple rule types. Please use" f" ::{rule_name} to clarify which rule to use" f" (such as {_parser_type_name(match_a)}::{rule_name} or {_parser_type_name(match_b)}::{rule_name})." - f" Please use `debputy plugin list plugable-manifest-rules` to see the list of rules." + f" Please use `debputy plugin list pluggable-manifest-rules` to see the list of rules." ) if matched: @@ -1154,7 +1154,7 @@ def _render_discard_rule( @plugin_show_cmds.register_subcommand( ["automatic-discard-rules", "a-d-r"], - help_description="Plugable manifest rules (such as install rules)", + help_description="Pluggable manifest rules (such as install rules)", argparser=add_arg( "discard_rule", metavar="automatic-discard-rule", diff --git a/src/debputy/deb_packaging_support.py b/src/debputy/deb_packaging_support.py index 4cb4e8f..863e394 100644 --- a/src/debputy/deb_packaging_support.py +++ b/src/debputy/deb_packaging_support.py @@ -1148,6 +1148,7 @@ def _generate_dbgsym_control_file_if_relevant( dbgsym_root_dir: str, dbgsym_ids: str, multi_arch: Optional[str], + dctrl: str, extra_common_params: Sequence[str], ) -> None: section = binary_package.archive_section @@ -1170,13 +1171,18 @@ def _generate_dbgsym_control_file_if_relevant( extra_params.append(f"-VInstalled-Size={total_size}") extra_params.extend(extra_common_params) - package = binary_package.name + package = ( + binary_package.name + if dctrl == "debian/control" + else f"{binary_package.name}-dbgsym" + ) dpkg_cmd = [ "dpkg-gencontrol", f"-p{package}", # FIXME: Support d/.changelog at some point. "-ldebian/changelog", "-T/dev/null", + f"-c{dctrl}", f"-P{dbgsym_root_dir}", f"-DPackage={package}-dbgsym", "-DDepends=" + package + " (= ${binary:Version})", @@ -1339,14 +1345,22 @@ def dpkg_field_list_pkg_dep() -> Sequence[str]: def _handle_relationship_substvars( source: SourcePackage, - dctrl: BinaryPackage, + dctrl_file: BinaryPackage, substvars: FlushableSubstvars, + has_dbgsym: bool, ) -> Optional[str]: relationship_fields = dpkg_field_list_pkg_dep() relationship_fields_lc = frozenset(x.lower() for x in relationship_fields) substvar_fields = collections.defaultdict(list) + needs_dbgsym_stanza = False for substvar_name, substvar in substvars.as_substvar.items(): - if substvar.assignment_operator == "$=" or ":" not in substvar_name: + if ":" not in substvar_name: + continue + if substvar.assignment_operator in ("$=", "!="): + # Will create incorrect results if there is a dbgsym and we do nothing + needs_dbgsym_stanza = True + + if substvar.assignment_operator == "$=": # Automatically handled; no need for manual merging. continue _, field = substvar_name.rsplit(":", 1) @@ -1354,10 +1368,14 @@ def _handle_relationship_substvars( if field_lc not in relationship_fields_lc: continue substvar_fields[field_lc].append("${" + substvar_name + "}") - if not substvar_fields: + + if not has_dbgsym: + needs_dbgsym_stanza = False + + if not substvar_fields and not needs_dbgsym_stanza: return None - replacement_stanza = debian.deb822.Deb822(dctrl.fields) + replacement_stanza = debian.deb822.Deb822(dctrl_file.fields) for field_name in relationship_fields: field_name_lc = field_name.lower() @@ -1375,7 +1393,7 @@ def _handle_relationship_substvars( final_value = f"{existing_value}, {substvars_part}" replacement_stanza[field_name] = final_value - tmpdir = generated_content_dir(package=dctrl) + tmpdir = generated_content_dir(package=dctrl_file) with tempfile.NamedTemporaryFile( mode="wb", dir=tmpdir, @@ -1388,6 +1406,17 @@ def _handle_relationship_substvars( debian.deb822.Deb822(source.fields).dump(fd) fd.write(b"\n") replacement_stanza.dump(fd) + + if has_dbgsym: + # Minimal stanza to avoid substvars warnings. Most fields are still set + # via -D. + dbgsym_stanza = Deb822() + dbgsym_stanza["Package"] = f"{dctrl_file.name}-dbgsym" + dbgsym_stanza["Architecture"] = dctrl_file.fields["Architecture"] + dbgsym_stanza["Description"] = f"debug symbols for {dctrl_file.name}" + fd.write(b"\n") + dbgsym_stanza.dump(fd) + return fd.name @@ -1424,9 +1453,11 @@ def _generate_control_files( ' accordingly in the binary. If this auto-correction is wrong, please add "Multi-Arch: no" to the' ' relevant part of "debian/control" to disable this feature.' ) - extra_params_specific.append(f"-DMulti-Arch={ma_value}") + # We want this to apply to the `-dbgsym` package as well to avoid + # lintian `debug-package-for-multi-arch-same-pkg-not-coinstallable` + extra_common_params.append(f"-DMulti-Arch={ma_value}") elif ma_value == "no": - extra_params_specific.append("-UMulti-Arch") + extra_common_params.append("-UMulti-Arch") dbgsym_root_dir = dhe_dbgsym_root_dir(binary_package) dbgsym_ids = " ".join(dbgsym_build_ids) if dbgsym_build_ids else "" @@ -1436,15 +1467,26 @@ def _generate_control_files( _t64_migration_substvar(binary_package, control_output_dir, substvars) with substvars.flush() as flushed_substvars: - if dbgsym_root_fs is not None and any( + has_dbgsym = dbgsym_root_fs is not None and any( f for f in dbgsym_root_fs.all_paths() if f.is_file - ): + ) + dctrl_file = _handle_relationship_substvars( + source_package, + binary_package, + substvars, + has_dbgsym, + ) + if dctrl_file is None: + dctrl_file = "debian/control" + + if has_dbgsym: _generate_dbgsym_control_file_if_relevant( binary_package, dbgsym_root_fs, dbgsym_root_dir, dbgsym_ids, ma_value, + dctrl_file, extra_common_params, ) generate_md5sums_file( @@ -1454,21 +1496,13 @@ def _generate_control_files( elif dbgsym_ids: extra_common_params.append(f"-DBuild-Ids={dbgsym_ids}") - dctrl = _handle_relationship_substvars( - source_package, - binary_package, - substvars, - ) - if dctrl is None: - dctrl = "debian/control" - ctrl_file = os.path.join(control_output_dir, "control") dpkg_cmd = [ "dpkg-gencontrol", f"-p{package}", # FIXME: Support d/.changelog at some point. "-ldebian/changelog", - f"-c{dctrl}", + f"-c{dctrl_file}", f"-T{flushed_substvars}", f"-O{ctrl_file}", f"-P{control_output_dir}", diff --git a/src/debputy/debhelper_emulation.py b/src/debputy/debhelper_emulation.py index 88352bd..38d9a15 100644 --- a/src/debputy/debhelper_emulation.py +++ b/src/debputy/debhelper_emulation.py @@ -241,14 +241,13 @@ _FIND_DH_WITH = re.compile(r"--with(?:\s+|=)(\S+)") _DEP_REGEX = re.compile("^([a-z0-9][-+.a-z0-9]+)", re.ASCII) -def parse_drules_for_addons(debian_rules: VirtualPath, sequences: Set[str]) -> None: - with debian_rules.open() as fd: - for line in fd: - if not line.startswith("\tdh "): - continue - for match in _FIND_DH_WITH.finditer(line): - sequence_def = match.group(1) - sequences.update(sequence_def.split(",")) +def parse_drules_for_addons(lines: Iterable[str], sequences: Set[str]) -> None: + for line in lines: + if not line.startswith("\tdh "): + continue + for match in _FIND_DH_WITH.finditer(line): + sequence_def = match.group(1) + sequences.update(sequence_def.split(",")) def extract_dh_addons_from_control( diff --git a/src/debputy/dh_migration/migration.py b/src/debputy/dh_migration/migration.py index 1366f22..bcdd3f9 100644 --- a/src/debputy/dh_migration/migration.py +++ b/src/debputy/dh_migration/migration.py @@ -170,7 +170,9 @@ def _check_migration_target( f'Using "{resolved_migration_target}" as migration target based on the packaging' ) else: - _info(f'Using "{resolved_migration_target}" as default migration target.') + _info( + f'Using "{resolved_migration_target}" as default migration target. Use --migration-target to choose!' + ) return resolved_migration_target diff --git a/src/debputy/dh_migration/migrators_impl.py b/src/debputy/dh_migration/migrators_impl.py index 6613c25..7856d27 100644 --- a/src/debputy/dh_migration/migrators_impl.py +++ b/src/debputy/dh_migration/migrators_impl.py @@ -1,5 +1,6 @@ import collections import dataclasses +import functools import json import os import re @@ -675,16 +676,15 @@ def migrate_install_file( current_sources.extend(sources) continue key = (dest_dir, dhe_line.conditional_key()) + ctor = functools.partial( + SourcesAndConditional, + dest_dir=dest_dir, + conditional=dhe_line.conditional(), + ) md = _fetch_or_create( sources_by_destdir, key, - # Use named parameters to avoid warnings about the values possible changing - # in the next iteration. We always resolve the lambda in this iteration, so - # the bug is non-existent. However, that is harder for a linter to prove. - lambda *, dest=dest_dir, dhe=dhe_line: SourcesAndConditional( - dest_dir=dest, - conditional=dhe.conditional(), - ), + ctor, ) md.sources.extend(sources) @@ -908,10 +908,13 @@ def migrate_installinfo_file( info_files_by_condition: Dict[Tuple[str, ...], InfoFilesDefinition] = {} for dhe_line in content: key = dhe_line.conditional_key() + ctr = functools.partial( + InfoFilesDefinition, conditional=dhe_line.conditional() + ) info_def = _fetch_or_create( info_files_by_condition, key, - lambda: InfoFilesDefinition(conditional=dhe_line.conditional()), + ctr, ) info_def.sources.extend( _normalize_path(w, with_prefix=False) for w in dhe_line.tokens @@ -1023,12 +1026,15 @@ def migrate_installman_file( else: language = None key = (language, dhe_line.conditional_key()) + ctor = functools.partial( + ManpageDefinition, + language=language, + conditional=dhe_line.conditional(), + ) manpage_def = _fetch_or_create( complex_definitions, key, - lambda: ManpageDefinition( - language=language, conditional=dhe_line.conditional() - ), + ctor, ) manpage_def.sources.extend(sources) else: @@ -1483,7 +1489,8 @@ def read_dh_addon_sequences( drules = debian_dir.get("rules") if drules and drules.is_file: - parse_drules_for_addons(drules, dr_sequences) + with drules.open() as fd: + parse_drules_for_addons(fd, dr_sequences) with ctrl_file.open() as fd: ctrl = list(Deb822.iter_paragraphs(fd)) diff --git a/src/debputy/filesystem_scan.py b/src/debputy/filesystem_scan.py index f7f97c2..dec123c 100644 --- a/src/debputy/filesystem_scan.py +++ b/src/debputy/filesystem_scan.py @@ -325,7 +325,7 @@ class VirtualPathBase(VirtualPath, ABC): if current.path in link_expansions: # This is our loop detection for now. It might have some false positives where you # could safely resolve the same symlink twice. However, given that this use-case is - # basically none existent in practice for packaging, we just stop here for now. + # basically non-existent in practice for packaging, we just stop here for now. raise SymlinkLoopError( f'The path "{path}" traversed the symlink "{current.path}" multiple' " times. Currently, traversing the same symlink twice is considered" diff --git a/src/debputy/linting/lint_impl.py b/src/debputy/linting/lint_impl.py index 68be9d9..7248022 100644 --- a/src/debputy/linting/lint_impl.py +++ b/src/debputy/linting/lint_impl.py @@ -26,7 +26,7 @@ from debputy.lsp.lsp_debian_changelog import _lint_debian_changelog from debputy.lsp.lsp_debian_control import _lint_debian_control from debputy.lsp.lsp_debian_copyright import _lint_debian_copyright from debputy.lsp.lsp_debian_debputy_manifest import _lint_debian_debputy_manifest -from debputy.lsp.lsp_debian_rules import _lint_debian_rules +from debputy.lsp.lsp_debian_rules import _lint_debian_rules_impl from debputy.lsp.quickfixes import provide_standard_quickfixes_from_diagnostics from debputy.lsp.spellchecking import disable_spellchecking from debputy.lsp.text_edit import ( @@ -40,7 +40,7 @@ LINTER_FORMATS = { "debian/control": _lint_debian_control, "debian/copyright": _lint_debian_copyright, "debian/changelog": _lint_debian_changelog, - "debian/rules": _lint_debian_rules, + "debian/rules": _lint_debian_rules_impl, "debian/debputy.manifest": _lint_debian_debputy_manifest, } diff --git a/src/debputy/lsp/lsp_debian_changelog.py b/src/debputy/lsp/lsp_debian_changelog.py index 3ec0b4d..77df145 100644 --- a/src/debputy/lsp/lsp_debian_changelog.py +++ b/src/debputy/lsp/lsp_debian_changelog.py @@ -1,4 +1,5 @@ import sys +from email.utils import parsedate_to_datetime from typing import ( Union, List, @@ -26,6 +27,7 @@ from lsprotocol.types import ( from debputy.lsp.lsp_features import lsp_diagnostics, lsp_standard_handler from debputy.lsp.quickfixes import ( provide_standard_quickfixes_from_diagnostics, + propose_correct_text_quick_fix, ) from debputy.lsp.spellchecking import spellcheck_line from debputy.lsp.text_util import ( @@ -52,6 +54,17 @@ _LANGUAGE_IDS = [ "debchangelog", ] +_WEEKDAYS_BY_IDX = [ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", +] +_KNOWN_WEEK_DAYS = frozenset(_WEEKDAYS_BY_IDX) + DOCUMENT_VERSION_TABLE: Dict[str, int] = {} @@ -106,6 +119,114 @@ def _diagnostics_debian_changelog( yield from scanner +def _check_footer_line( + line: str, + line_no: int, + lines: List[str], + position_codec: LintCapablePositionCodec, +) -> Iterator[Diagnostic]: + try: + end_email_idx = line.rindex("> ") + except ValueError: + # Syntax error; flag later + return + line_len = len(line) + start_date_idx = end_email_idx + 3 + # 3 characters for the day name (Mon), then a comma plus a space followed by the + # actual date. The 6 characters limit is a gross under estimation of the real + # size. + if line_len < start_date_idx + 6: + range_server_units = Range( + Position( + line_no, + start_date_idx, + ), + Position( + line_no, + line_len, + ), + ) + yield Diagnostic( + position_codec.range_to_client_units(lines, range_server_units), + "Expected a date in RFC822 format (Tue, 12 Mar 2024 12:34:56 +0000)", + severity=DiagnosticSeverity.Error, + source="debputy", + ) + return + day_name_range_server_units = Range( + Position( + line_no, + start_date_idx, + ), + Position( + line_no, + start_date_idx + 3, + ), + ) + day_name = line[start_date_idx : start_date_idx + 3] + if day_name not in _KNOWN_WEEK_DAYS: + yield Diagnostic( + position_codec.range_to_client_units(lines, day_name_range_server_units), + "Expected a three letter date here (Mon, Tue, ..., Sun).", + severity=DiagnosticSeverity.Error, + source="debputy", + ) + return + + date_str = line[start_date_idx + 5 :] + + if line[start_date_idx + 3 : start_date_idx + 5] != ", ": + sep = line[start_date_idx + 3 : start_date_idx + 5] + range_server_units = Range( + Position( + line_no, + start_date_idx + 3, + ), + Position( + line_no, + start_date_idx + 4, + ), + ) + yield Diagnostic( + position_codec.range_to_client_units(lines, range_server_units), + f'Improper formatting of date. Expected ", " here, not "{sep}"', + severity=DiagnosticSeverity.Error, + source="debputy", + ) + return + + try: + # FIXME: this parser is too forgiving (it ignores trailing garbage) + date = parsedate_to_datetime(date_str) + except ValueError as e: + range_server_units = Range( + Position( + line_no, + start_date_idx + 5, + ), + Position( + line_no, + line_len, + ), + ) + yield Diagnostic( + position_codec.range_to_client_units(lines, range_server_units), + f"Unable to the date as a valid RFC822 date: {e.args[0]}", + severity=DiagnosticSeverity.Error, + source="debputy", + ) + return + expected_week_day = _WEEKDAYS_BY_IDX[date.weekday()] + if expected_week_day != day_name: + yield Diagnostic( + position_codec.range_to_client_units(lines, day_name_range_server_units), + f"The date was a {expected_week_day}day.", + severity=DiagnosticSeverity.Warning, + source="debputy", + data=[propose_correct_text_quick_fix(expected_week_day)], + ) + + def _scan_debian_changelog_for_diagnostics( lines: List[str], position_codec: LintCapablePositionCodec, @@ -123,6 +244,9 @@ def _scan_debian_changelog_for_diagnostics( line = line.rstrip() if not line: continue + if line.startswith(" --"): + diagnostics.extend(_check_footer_line(line, line_no, lines, position_codec)) + continue if not line.startswith(" "): continue # minus 1 for newline diff --git a/src/debputy/lsp/lsp_debian_control.py b/src/debputy/lsp/lsp_debian_control.py index d00f1c2..f73612f 100644 --- a/src/debputy/lsp/lsp_debian_control.py +++ b/src/debputy/lsp/lsp_debian_control.py @@ -9,20 +9,6 @@ from typing import ( List, ) -from debputy.lsp.vendoring._deb822_repro import ( - parse_deb822_file, - Deb822FileElement, - Deb822ParagraphElement, -) -from debputy.lsp.vendoring._deb822_repro.parsing import ( - Deb822KeyValuePairElement, - LIST_SPACE_SEPARATED_INTERPRETATION, -) -from debputy.lsp.vendoring._deb822_repro.tokens import ( - Deb822Token, - tokenize_deb822_file, - Deb822FieldNameToken, -) from lsprotocol.types import ( DiagnosticSeverity, Range, @@ -30,7 +16,6 @@ from lsprotocol.types import ( Position, DidOpenTextDocumentParams, DidChangeTextDocumentParams, - FoldingRangeKind, FoldingRange, FoldingRangeParams, CompletionItem, @@ -38,7 +23,6 @@ from lsprotocol.types import ( CompletionParams, TEXT_DOCUMENT_DID_OPEN, TEXT_DOCUMENT_DID_CHANGE, - TEXT_DOCUMENT_FOLDING_RANGE, TEXT_DOCUMENT_COMPLETION, TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL, DiagnosticRelatedInformation, @@ -58,7 +42,6 @@ from debputy.lsp.lsp_debian_control_reference_data import ( DctrlKnownField, BINARY_FIELDS, SOURCE_FIELDS, - FieldValueClass, DctrlFileMetadata, ) from debputy.lsp.lsp_features import ( @@ -66,8 +49,15 @@ from debputy.lsp.lsp_features import ( lsp_completer, lsp_hover, lsp_standard_handler, + lsp_folding_ranges, + lsp_semantic_tokens_full, +) +from debputy.lsp.lsp_generic_deb822 import ( + deb822_completer, + deb822_hover, + deb822_folding_ranges, + deb822_semantic_tokens_full, ) -from debputy.lsp.lsp_generic_deb822 import deb822_completer, deb822_hover from debputy.lsp.quickfixes import ( propose_remove_line_quick_fix, range_compatible_with_remove_line_fix, @@ -82,6 +72,19 @@ from debputy.lsp.text_util import ( detect_possible_typo, te_range_to_lsp, ) +from debputy.lsp.vendoring._deb822_repro import ( + parse_deb822_file, + Deb822FileElement, + Deb822ParagraphElement, +) +from debputy.lsp.vendoring._deb822_repro.parsing import ( + Deb822KeyValuePairElement, + LIST_SPACE_SEPARATED_INTERPRETATION, +) +from debputy.lsp.vendoring._deb822_repro.tokens import ( + Deb822Token, + Deb822FieldNameToken, +) from debputy.util import _info, _error try: @@ -106,33 +109,9 @@ _LANGUAGE_IDS = [ ] -SEMANTIC_TOKENS_LEGEND = SemanticTokensLegend( - token_types=["keyword"], - token_modifiers=[], -) _DCTRL_FILE_METADATA = DctrlFileMetadata() -def register_dctrl_lsp(ls: "LanguageServer") -> None: - try: - from debputy.lsp.vendoring._deb822_repro.locatable import Locatable - except ImportError: - _error( - 'Sorry; this feature requires a newer version of python-debian (with "Locatable").' - ) - - ls.feature(TEXT_DOCUMENT_DID_OPEN)(_diagnostics_debian_control) - ls.feature(TEXT_DOCUMENT_DID_CHANGE)(_diagnostics_debian_control) - ls.feature(TEXT_DOCUMENT_FOLDING_RANGE)(_detect_folding_ranges_debian_control) - ls.feature(TEXT_DOCUMENT_COMPLETION)(_debian_control_completions) - ls.feature(TEXT_DOCUMENT_CODE_ACTION)(provide_standard_quickfixes_from_diagnostics) - ls.feature(TEXT_DOCUMENT_HOVER)(_debian_control_hover) - ls.feature(TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL)(on_save_trim_end_of_line_whitespace) - ls.feature(TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, SEMANTIC_TOKENS_LEGEND)( - _handle_semantic_tokens_full - ) - - lsp_standard_handler(_LANGUAGE_IDS, TEXT_DOCUMENT_CODE_ACTION) lsp_standard_handler(_LANGUAGE_IDS, TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL) @@ -153,36 +132,12 @@ def _debian_control_completions( return deb822_completer(ls, params, _DCTRL_FILE_METADATA) -def _detect_folding_ranges_debian_control( +@lsp_folding_ranges(_LANGUAGE_IDS) +def _debian_control_folding_ranges( ls: "LanguageServer", params: FoldingRangeParams, ) -> Optional[Sequence[FoldingRange]]: - doc = ls.workspace.get_text_document(params.text_document.uri) - comment_start = -1 - folding_ranges = [] - for ( - token, - start_line, - start_offset, - end_line, - end_offset, - ) in _deb822_token_iter(tokenize_deb822_file(doc.lines)): - if token.is_comment: - if comment_start < 0: - comment_start = start_line - _info(f"Detected new comment: {start_line}") - elif comment_start > -1: - comment_start = -1 - folding_range = FoldingRange( - comment_start, - end_line, - kind=FoldingRangeKind.Comment, - ) - - folding_ranges.append(folding_range) - _info(f"Detected folding range: {folding_range}") - - return folding_ranges + return deb822_folding_ranges(ls, params, _DCTRL_FILE_METADATA) def _deb822_token_iter( @@ -760,38 +715,13 @@ def _lint_debian_control( return diagnostics -def _handle_semantic_tokens_full( +@lsp_semantic_tokens_full(_LANGUAGE_IDS) +def _semantic_tokens_full( ls: "LanguageServer", request: SemanticTokensParams, ) -> Optional[SemanticTokens]: - doc = ls.workspace.get_text_document(request.text_document.uri) - lines = doc.lines - deb822_file = parse_deb822_file( - lines, - accept_files_with_duplicated_fields=True, - accept_files_with_error_tokens=True, + return deb822_semantic_tokens_full( + ls, + request, + _DCTRL_FILE_METADATA, ) - tokens = [] - previous_line = 0 - keyword_token = 0 - no_modifiers = 0 - - for paragraph_no, paragraph in enumerate(deb822_file, start=1): - paragraph_position = paragraph.position_in_file() - for kvpair in paragraph.iter_parts_of_type(Deb822KeyValuePairElement): - field_position_without_comments = kvpair.position_in_parent().relative_to( - paragraph_position - ) - field_size = doc.position_codec.client_num_units(kvpair.field_name) - current_line = field_position_without_comments.line_position - line_delta = current_line - previous_line - previous_line = current_line - tokens.append(line_delta) # Line delta - tokens.append(0) # Token delta - tokens.append(field_size) # Token length - tokens.append(keyword_token) - tokens.append(no_modifiers) - - if not tokens: - return None - return SemanticTokens(tokens) diff --git a/src/debputy/lsp/lsp_debian_copyright.py b/src/debputy/lsp/lsp_debian_copyright.py index 052654a..f22bd0b 100644 --- a/src/debputy/lsp/lsp_debian_copyright.py +++ b/src/debputy/lsp/lsp_debian_copyright.py @@ -10,36 +10,16 @@ from typing import ( List, ) -from debputy.lsp.vendoring._deb822_repro import ( - parse_deb822_file, - Deb822FileElement, - Deb822ParagraphElement, -) -from debputy.lsp.vendoring._deb822_repro.parsing import ( - Deb822KeyValuePairElement, - LIST_SPACE_SEPARATED_INTERPRETATION, -) -from debputy.lsp.vendoring._deb822_repro.tokens import ( - Deb822Token, - tokenize_deb822_file, - Deb822FieldNameToken, -) from lsprotocol.types import ( DiagnosticSeverity, Range, Diagnostic, Position, - DidOpenTextDocumentParams, - DidChangeTextDocumentParams, - FoldingRangeKind, - FoldingRange, - FoldingRangeParams, CompletionItem, CompletionList, CompletionParams, TEXT_DOCUMENT_DID_OPEN, TEXT_DOCUMENT_DID_CHANGE, - TEXT_DOCUMENT_FOLDING_RANGE, TEXT_DOCUMENT_COMPLETION, TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL, DiagnosticRelatedInformation, @@ -53,10 +33,11 @@ from lsprotocol.types import ( TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, SemanticTokens, SemanticTokensParams, + FoldingRangeParams, + FoldingRange, ) from debputy.lsp.lsp_debian_control_reference_data import ( - FieldValueClass, _DEP5_HEADER_FIELDS, _DEP5_FILES_FIELDS, Deb822KnownField, @@ -68,8 +49,15 @@ from debputy.lsp.lsp_features import ( lsp_completer, lsp_hover, lsp_standard_handler, + lsp_folding_ranges, + lsp_semantic_tokens_full, +) +from debputy.lsp.lsp_generic_deb822 import ( + deb822_completer, + deb822_hover, + deb822_folding_ranges, + deb822_semantic_tokens_full, ) -from debputy.lsp.lsp_generic_deb822 import deb822_completer, deb822_hover from debputy.lsp.quickfixes import ( propose_remove_line_quick_fix, propose_correct_text_quick_fix, @@ -83,7 +71,20 @@ from debputy.lsp.text_util import ( detect_possible_typo, te_range_to_lsp, ) -from debputy.util import _info, _error +from debputy.lsp.vendoring._deb822_repro import ( + parse_deb822_file, + Deb822FileElement, + Deb822ParagraphElement, +) +from debputy.lsp.vendoring._deb822_repro.parsing import ( + Deb822KeyValuePairElement, + LIST_SPACE_SEPARATED_INTERPRETATION, +) +from debputy.lsp.vendoring._deb822_repro.tokens import ( + Deb822Token, + Deb822FieldNameToken, +) +from debputy.util import _error try: from debputy.lsp.vendoring._deb822_repro.locatable import ( @@ -109,32 +110,6 @@ _LANGUAGE_IDS = [ _DEP5_FILE_METADATA = Dep5FileMetadata() -SEMANTIC_TOKENS_LEGEND = SemanticTokensLegend( - token_types=["keyword"], - token_modifiers=[], -) - - -def register_dcpy_lsp(ls: "LanguageServer") -> None: - try: - from debian._deb822_repro.locatable import Locatable - except ImportError: - _error( - 'Sorry; this feature requires a newer version of python-debian (with "Locatable").' - ) - - ls.feature(TEXT_DOCUMENT_DID_OPEN)(_diagnostics_debian_copyright) - ls.feature(TEXT_DOCUMENT_DID_CHANGE)(_diagnostics_debian_copyright) - ls.feature(TEXT_DOCUMENT_FOLDING_RANGE)(_detect_folding_ranges_debian_copyright) - ls.feature(TEXT_DOCUMENT_COMPLETION)(_debian_copyright_completions) - ls.feature(TEXT_DOCUMENT_CODE_ACTION)(provide_standard_quickfixes_from_diagnostics) - ls.feature(TEXT_DOCUMENT_HOVER)(_debian_copyright_hover) - ls.feature(TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL)(on_save_trim_end_of_line_whitespace) - ls.feature(TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, SEMANTIC_TOKENS_LEGEND)( - _handle_semantic_tokens_full - ) - - lsp_standard_handler(_LANGUAGE_IDS, TEXT_DOCUMENT_CODE_ACTION) lsp_standard_handler(_LANGUAGE_IDS, TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL) @@ -155,36 +130,12 @@ def _debian_copyright_completions( return deb822_completer(ls, params, _DEP5_FILE_METADATA) -def _detect_folding_ranges_debian_copyright( +@lsp_folding_ranges(_LANGUAGE_IDS) +def _debian_copyright_folding_ranges( ls: "LanguageServer", params: FoldingRangeParams, ) -> Optional[Sequence[FoldingRange]]: - doc = ls.workspace.get_text_document(params.text_document.uri) - comment_start = -1 - folding_ranges = [] - for ( - token, - start_line, - start_offset, - end_line, - end_offset, - ) in _deb822_token_iter(tokenize_deb822_file(doc.lines)): - if token.is_comment: - if comment_start < 0: - comment_start = start_line - _info(f"Detected new comment: {start_line}") - elif comment_start > -1: - comment_start = -1 - folding_range = FoldingRange( - comment_start, - end_line, - kind=FoldingRangeKind.Comment, - ) - - folding_ranges.append(folding_range) - _info(f"Detected folding range: {folding_range}") - - return folding_ranges + return deb822_folding_ranges(ls, params, _DEP5_FILE_METADATA) def _deb822_token_iter( @@ -576,22 +527,6 @@ def _scan_for_syntax_errors_and_token_level_diagnostics( return first_error -def _diagnostics_debian_copyright( - ls: "LanguageServer", - params: Union[DidOpenTextDocumentParams, DidChangeTextDocumentParams], -) -> None: - doc = ls.workspace.get_text_document(params.text_document.uri) - _info(f"Opened document: {doc.path} ({doc.language_id})") - lines = doc.lines - position_codec: LintCapablePositionCodec = doc.position_codec - - diagnostics = _lint_debian_copyright(doc.uri, doc.path, lines, position_codec) - ls.publish_diagnostics( - doc.uri, - diagnostics, - ) - - @lint_diagnostics(_LANGUAGE_IDS) def _lint_debian_copyright( doc_reference: str, @@ -648,38 +583,13 @@ def _lint_debian_copyright( return diagnostics -def _handle_semantic_tokens_full( +@lsp_semantic_tokens_full(_LANGUAGE_IDS) +def _semantic_tokens_full( ls: "LanguageServer", request: SemanticTokensParams, ) -> Optional[SemanticTokens]: - doc = ls.workspace.get_text_document(request.text_document.uri) - lines = doc.lines - deb822_file = parse_deb822_file( - lines, - accept_files_with_duplicated_fields=True, - accept_files_with_error_tokens=True, + return deb822_semantic_tokens_full( + ls, + request, + _DEP5_FILE_METADATA, ) - tokens = [] - previous_line = 0 - keyword_token = 0 - no_modifiers = 0 - - for paragraph_no, paragraph in enumerate(deb822_file, start=1): - paragraph_position = paragraph.position_in_file() - for kvpair in paragraph.iter_parts_of_type(Deb822KeyValuePairElement): - field_position_without_comments = kvpair.position_in_parent().relative_to( - paragraph_position - ) - field_size = doc.position_codec.client_num_units(kvpair.field_name) - current_line = field_position_without_comments.line_position - line_delta = current_line - previous_line - previous_line = current_line - tokens.append(line_delta) # Line delta - tokens.append(0) # Token delta - tokens.append(field_size) # Token length - tokens.append(keyword_token) - tokens.append(no_modifiers) - - if not tokens: - return None - return SemanticTokens(tokens) diff --git a/src/debputy/lsp/lsp_debian_debputy_manifest.py b/src/debputy/lsp/lsp_debian_debputy_manifest.py index 2f9920e..97fffcc 100644 --- a/src/debputy/lsp/lsp_debian_debputy_manifest.py +++ b/src/debputy/lsp/lsp_debian_debputy_manifest.py @@ -45,6 +45,12 @@ _LANGUAGE_IDS = [ lsp_standard_handler(_LANGUAGE_IDS, TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL) +def is_valid_file(path: str) -> bool: + # For debian/debputy.manifest, the language ID is often set to makefile meaning we get random + # "non-debian/debputy.manifest" YAML files here. Skip those. + return path.endswith("debian/debputy.manifest") + + def _word_range_at_position( lines: List[str], line_no: int, @@ -69,10 +75,12 @@ def _word_range_at_position( @lint_diagnostics(_LANGUAGE_IDS) def _lint_debian_debputy_manifest( _doc_reference: str, - _path: str, + path: str, lines: List[str], position_codec: LintCapablePositionCodec, ) -> Optional[List[Diagnostic]]: + if not is_valid_file(path): + return None diagnostics = [] try: MANIFEST_YAML.load("".join(lines)) diff --git a/src/debputy/lsp/lsp_debian_rules.py b/src/debputy/lsp/lsp_debian_rules.py index 7f0e5fb..86b114c 100644 --- a/src/debputy/lsp/lsp_debian_rules.py +++ b/src/debputy/lsp/lsp_debian_rules.py @@ -15,8 +15,6 @@ from typing import ( from lsprotocol.types import ( CompletionItem, - DidOpenTextDocumentParams, - DidChangeTextDocumentParams, Diagnostic, Range, Position, @@ -27,6 +25,7 @@ from lsprotocol.types import ( TEXT_DOCUMENT_CODE_ACTION, ) +from debputy.debhelper_emulation import parse_drules_for_addons from debputy.lsp.lsp_features import ( lint_diagnostics, lsp_standard_handler, @@ -126,40 +125,26 @@ def _as_hook_targets(command_name: str) -> Iterable[str]: yield f"{prefix}{command_name}{suffix}" -def _diagnostics_debian_rules( - ls: "LanguageServer", - params: Union[DidOpenTextDocumentParams, DidChangeTextDocumentParams], -) -> None: - doc = ls.workspace.get_text_document(params.text_document.uri) - if not doc.path.endswith("debian/rules"): - return - lines = doc.lines - diagnostics = _lint_debian_rules( - doc.uri, - doc.path, - lines, - doc.position_codec, - ) - ls.publish_diagnostics( - doc.uri, - diagnostics, - ) - - lsp_standard_handler(_LANGUAGE_IDS, TEXT_DOCUMENT_CODE_ACTION) lsp_standard_handler(_LANGUAGE_IDS, TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL) +def is_valid_file(path: str) -> bool: + # For debian/rules, the language ID is often set to makefile meaning we get random "non-debian/rules" + # makefiles here. Skip those. + return path.endswith("debian/rules") + + @lint_diagnostics(_LANGUAGE_IDS) -def _lint_debian_rules_via_debputy_lsp( +def _lint_debian_rules( doc_reference: str, path: str, lines: List[str], position_codec: LintCapablePositionCodec, ) -> Optional[List[Diagnostic]]: - if not path.endswith("debian/rules"): + if not is_valid_file(path): return None - return _lint_debian_rules( + return _lint_debian_rules_impl( doc_reference, path, lines, @@ -245,7 +230,7 @@ def iter_make_lines( yield line_no, line -def _lint_debian_rules( +def _lint_debian_rules_impl( _doc_reference: str, path: str, lines: List[str], @@ -259,7 +244,8 @@ def _lint_debian_rules( make_error = _run_make_dryrun(source_root, lines) if make_error is not None: diagnostics.append(make_error) - all_dh_commands = _all_dh_commands(source_root) + + all_dh_commands = _all_dh_commands(source_root, lines) if all_dh_commands: all_hook_targets = {ht for c in all_dh_commands for ht in _as_hook_targets(c)} all_hook_targets.update(_KNOWN_TARGETS) @@ -330,10 +316,15 @@ def _lint_debian_rules( return diagnostics -def _all_dh_commands(source_root: str) -> Optional[Sequence[str]]: +def _all_dh_commands(source_root: str, lines: List[str]) -> Optional[Sequence[str]]: + drules_sequences = set() + parse_drules_for_addons(lines, drules_sequences) + cmd = ["dh_assistant", "list-commands", "--output-format=json"] + if drules_sequences: + cmd.append(f"--with={','.join(drules_sequences)}") try: output = subprocess.check_output( - ["dh_assistant", "list-commands", "--output-format=json"], + cmd, stderr=subprocess.DEVNULL, cwd=source_root, ) @@ -364,7 +355,7 @@ def _debian_rules_completions( params: CompletionParams, ) -> Optional[Union[CompletionList, Sequence[CompletionItem]]]: doc = ls.workspace.get_text_document(params.text_document.uri) - if not doc.path.endswith("debian/rules"): + if not is_valid_file(doc.path): return None lines = doc.lines server_position = doc.position_codec.position_from_client_units( @@ -378,7 +369,7 @@ def _debian_rules_completions( return None source_root = os.path.dirname(os.path.dirname(doc.path)) - all_commands = _all_dh_commands(source_root) + all_commands = _all_dh_commands(source_root, lines) items = [CompletionItem(ht) for c in all_commands for ht in _as_hook_targets(c)] return items diff --git a/src/debputy/lsp/lsp_dispatch.py b/src/debputy/lsp/lsp_dispatch.py index 41e9111..b7b744c 100644 --- a/src/debputy/lsp/lsp_dispatch.py +++ b/src/debputy/lsp/lsp_dispatch.py @@ -1,5 +1,15 @@ import asyncio -from typing import Dict, Sequence, Union, Optional +from typing import ( + Dict, + Sequence, + Union, + Optional, + Any, + TypeVar, + Callable, + Mapping, + List, +) from lsprotocol.types import ( DidOpenTextDocumentParams, @@ -11,6 +21,19 @@ from lsprotocol.types import ( CompletionItem, CompletionParams, TEXT_DOCUMENT_HOVER, + TEXT_DOCUMENT_FOLDING_RANGE, + FoldingRange, + FoldingRangeParams, + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, + SemanticTokensParams, + SemanticTokens, + Hover, + TEXT_DOCUMENT_CODE_ACTION, + Command, + CodeAction, + TextDocumentCodeActionRequest, + CodeActionParams, + SemanticTokensRegistrationOptions, ) from debputy import __version__ @@ -18,6 +41,9 @@ from debputy.lsp.lsp_features import ( DIAGNOSTIC_HANDLERS, COMPLETER_HANDLERS, HOVER_HANDLERS, + SEMANTIC_TOKENS_FULL_HANDLERS, + CODE_ACTION_HANDLERS, + SEMANTIC_TOKENS_LEGEND, ) from debputy.util import _info @@ -37,6 +63,10 @@ except ImportError: DEBPUTY_LANGUAGE_SERVER = Mock() +P = TypeVar("P") +R = TypeVar("R") + + def is_doc_at_version(uri: str, version: int) -> bool: dv = _DOCUMENT_VERSION_TABLE.get(uri) return dv == version @@ -88,22 +118,12 @@ def _completions( ls: "LanguageServer", params: CompletionParams, ) -> Optional[Union[CompletionList, Sequence[CompletionItem]]]: - doc_uri = params.text_document.uri - doc = ls.workspace.get_text_document(doc_uri) - - handler = COMPLETER_HANDLERS.get(doc.language_id) - if handler is None: - _info( - f"Complete request for document: {doc.path} ({doc.language_id}) - no handler" - ) - return - _info( - f"Complete request for document: {doc.path} ({doc.language_id}) - delegating to handler" - ) - - return handler( + return _dispatch_standard_handler( ls, + params.text_document.uri, params, + COMPLETER_HANDLERS, + "Complete request", ) @@ -111,18 +131,81 @@ def _completions( def _hover( ls: "LanguageServer", params: CompletionParams, -) -> Optional[Union[CompletionList, Sequence[CompletionItem]]]: - doc_uri = params.text_document.uri +) -> Optional[Hover]: + return _dispatch_standard_handler( + ls, + params.text_document.uri, + params, + HOVER_HANDLERS, + "Hover doc request", + ) + + +@DEBPUTY_LANGUAGE_SERVER.feature(TEXT_DOCUMENT_CODE_ACTION) +def _code_actions( + ls: "LanguageServer", + params: CodeActionParams, +) -> Optional[List[Union[Command, CodeAction]]]: + return _dispatch_standard_handler( + ls, + params.text_document.uri, + params, + CODE_ACTION_HANDLERS, + "Code action request", + ) + + +@DEBPUTY_LANGUAGE_SERVER.feature(TEXT_DOCUMENT_FOLDING_RANGE) +def _folding_ranges( + ls: "LanguageServer", + params: FoldingRangeParams, +) -> Optional[Sequence[FoldingRange]]: + return _dispatch_standard_handler( + ls, + params.text_document.uri, + params, + HOVER_HANDLERS, + "Folding range request", + ) + + +@DEBPUTY_LANGUAGE_SERVER.feature( + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL, + SemanticTokensRegistrationOptions( + SEMANTIC_TOKENS_LEGEND, + full=True, + ), +) +def _semantic_tokens_full( + ls: "LanguageServer", + params: SemanticTokensParams, +) -> Optional[SemanticTokens]: + return _dispatch_standard_handler( + ls, + params.text_document.uri, + params, + SEMANTIC_TOKENS_FULL_HANDLERS, + "Semantic tokens request", + ) + + +def _dispatch_standard_handler( + ls: "LanguageServer", + doc_uri: str, + params: P, + handler_table: Mapping[str, Callable[["LanguageServer", P], R]], + request_type: str, +) -> R: doc = ls.workspace.get_text_document(doc_uri) - handler = HOVER_HANDLERS.get(doc.language_id) + handler = handler_table.get(doc.language_id) if handler is None: _info( - f"Hover request for document: {doc.path} ({doc.language_id}) - no handler" + f"{request_type} for document: {doc.path} ({doc.language_id}) - no handler" ) return _info( - f"Hover request for document: {doc.path} ({doc.language_id}) - delegating to handler" + f"{request_type} for document: {doc.path} ({doc.language_id}) - delegating to handler" ) return handler( diff --git a/src/debputy/lsp/lsp_features.py b/src/debputy/lsp/lsp_features.py index b417dd3..8260675 100644 --- a/src/debputy/lsp/lsp_features.py +++ b/src/debputy/lsp/lsp_features.py @@ -8,6 +8,7 @@ from lsprotocol.types import ( DidChangeTextDocumentParams, Diagnostic, DidOpenTextDocumentParams, + SemanticTokensLegend, ) try: @@ -21,11 +22,20 @@ from debputy.lsp.text_util import on_save_trim_end_of_line_whitespace C = TypeVar("C", bound=Callable) +SEMANTIC_TOKENS_LEGEND = SemanticTokensLegend( + token_types=["keyword", "enumMember"], + token_modifiers=[], +) +SEMANTIC_TOKEN_TYPES_IDS = { + t: idx for idx, t in enumerate(SEMANTIC_TOKENS_LEGEND.token_types) +} DIAGNOSTIC_HANDLERS = {} COMPLETER_HANDLERS = {} HOVER_HANDLERS = {} CODE_ACTION_HANDLERS = {} +FOLDING_RANGE_HANDLERS = {} +SEMANTIC_TOKENS_FULL_HANDLERS = {} WILL_SAVE_WAIT_UNTIL_HANDLERS = {} _ALIAS_OF = {} @@ -111,6 +121,16 @@ def lsp_hover(file_formats: Union[str, Sequence[str]]) -> Callable[[C], C]: return _registering_wrapper(file_formats, HOVER_HANDLERS) +def lsp_folding_ranges(file_formats: Union[str, Sequence[str]]) -> Callable[[C], C]: + return _registering_wrapper(file_formats, FOLDING_RANGE_HANDLERS) + + +def lsp_semantic_tokens_full( + file_formats: Union[str, Sequence[str]] +) -> Callable[[C], C]: + return _registering_wrapper(file_formats, SEMANTIC_TOKENS_FULL_HANDLERS) + + def lsp_standard_handler(file_formats: Union[str, Sequence[str]], topic: str) -> None: res = _STANDARD_HANDLERS.get(topic) if res is None: @@ -171,6 +191,8 @@ def describe_lsp_features() -> None: ("code actions/quickfixes", CODE_ACTION_HANDLERS), ("completion suggestions", COMPLETER_HANDLERS), ("hover docs", HOVER_HANDLERS), + ("folding ranges", FOLDING_RANGE_HANDLERS), + ("semantic tokens", SEMANTIC_TOKENS_FULL_HANDLERS), ("on-save handler", WILL_SAVE_WAIT_UNTIL_HANDLERS), ] print("LSP language IDs and their features:") diff --git a/src/debputy/lsp/lsp_generic_deb822.py b/src/debputy/lsp/lsp_generic_deb822.py index 245f3de..7a1f96f 100644 --- a/src/debputy/lsp/lsp_generic_deb822.py +++ b/src/debputy/lsp/lsp_generic_deb822.py @@ -8,6 +8,8 @@ from typing import ( Any, Container, List, + Iterable, + Iterator, ) from lsprotocol.types import ( @@ -20,14 +22,27 @@ from lsprotocol.types import ( Hover, MarkupKind, HoverParams, + FoldingRangeParams, + FoldingRange, + FoldingRangeKind, + SemanticTokensParams, + SemanticTokens, ) from debputy.lsp.lsp_debian_control_reference_data import ( Deb822FileMetadata, Deb822KnownField, StanzaMetadata, + FieldValueClass, ) +from debputy.lsp.lsp_features import SEMANTIC_TOKEN_TYPES_IDS from debputy.lsp.text_util import normalize_dctrl_field_name +from debputy.lsp.vendoring._deb822_repro import parse_deb822_file +from debputy.lsp.vendoring._deb822_repro.parsing import ( + Deb822KeyValuePairElement, + LIST_SPACE_SEPARATED_INTERPRETATION, +) +from debputy.lsp.vendoring._deb822_repro.tokens import tokenize_deb822_file, Deb822Token from debputy.util import _info try: @@ -175,6 +190,152 @@ def deb822_hover( ) +def _deb822_token_iter( + tokens: Iterable[Deb822Token], +) -> Iterator[Tuple[Deb822Token, int, int, int, int, int]]: + line_no = 0 + line_offset = 0 + + for token in tokens: + start_line = line_no + start_line_offset = line_offset + + newlines = token.text.count("\n") + line_no += newlines + text_len = len(token.text) + if newlines: + if token.text.endswith("\n"): + line_offset = 0 + else: + # -2, one to remove the "\n" and one to get 0-offset + line_offset = text_len - token.text.rindex("\n") - 2 + else: + line_offset += text_len + + yield token, start_line, start_line_offset, line_no, line_offset + + +def deb822_folding_ranges( + ls: "LanguageServer", + params: FoldingRangeParams, + # Unused for now: might be relevant for supporting folding for some fields + _file_metadata: Deb822FileMetadata[Any], +) -> Optional[Sequence[FoldingRange]]: + doc = ls.workspace.get_text_document(params.text_document.uri) + comment_start = -1 + folding_ranges = [] + for ( + token, + start_line, + start_offset, + end_line, + end_offset, + ) in _deb822_token_iter(tokenize_deb822_file(doc.lines)): + if token.is_comment: + if comment_start < 0: + comment_start = start_line + elif comment_start > -1: + comment_start = -1 + folding_range = FoldingRange( + comment_start, + end_line, + kind=FoldingRangeKind.Comment, + ) + + folding_ranges.append(folding_range) + + return folding_ranges + + +def deb822_semantic_tokens_full( + ls: "LanguageServer", + request: SemanticTokensParams, + file_metadata: Deb822FileMetadata[Any], +) -> Optional[SemanticTokens]: + doc = ls.workspace.get_text_document(request.text_document.uri) + lines = doc.lines + deb822_file = parse_deb822_file( + lines, + accept_files_with_duplicated_fields=True, + accept_files_with_error_tokens=True, + ) + tokens = [] + previous_line = 0 + keyword_token_code = SEMANTIC_TOKEN_TYPES_IDS["keyword"] + known_value_token_code = SEMANTIC_TOKEN_TYPES_IDS["enumMember"] + no_modifiers = 0 + + # TODO: Add comment support; slightly complicated by how we parse the file. + + for stanza_idx, stanza in enumerate(deb822_file): + stanza_position = stanza.position_in_file() + stanza_metadata = file_metadata.classify_stanza(stanza, stanza_idx=stanza_idx) + for kvpair in stanza.iter_parts_of_type(Deb822KeyValuePairElement): + kvpair_pos = kvpair.position_in_parent().relative_to(stanza_position) + # These two happen to be the same; the indirection is to make it explicit that the two + # positions for different tokens are the same. + field_position_without_comments = kvpair_pos + field_size = doc.position_codec.client_num_units(kvpair.field_name) + current_line = field_position_without_comments.line_position + line_delta = current_line - previous_line + previous_line = current_line + tokens.append(line_delta) # Line delta + tokens.append(0) # Token column delta + tokens.append(field_size) # Token length + tokens.append(keyword_token_code) + tokens.append(no_modifiers) + + known_field: Optional[Deb822KnownField] = stanza_metadata.get( + kvpair.field_name + ) + if ( + known_field is None + or not known_field.known_values + or known_field.spellcheck_value + ): + continue + + if known_field.field_value_class not in ( + FieldValueClass.SINGLE_VALUE, + FieldValueClass.SPACE_SEPARATED_LIST, + ): + continue + value_element_pos = kvpair.value_element.position_in_parent().relative_to( + kvpair_pos + ) + + last_token_start_column = 0 + + for value_ref in kvpair.interpret_as( + LIST_SPACE_SEPARATED_INTERPRETATION + ).iter_value_references(): + if value_ref.value not in known_field.known_values: + continue + value_loc = value_ref.locatable + value_range_te = value_loc.range_in_parent().relative_to( + value_element_pos + ) + start_line = value_range_te.start_pos.line_position + line_delta = start_line - current_line + current_line = start_line + if line_delta: + last_token_start_column = 0 + + value_start_column = value_range_te.start_pos.cursor_position + column_delta = value_start_column - last_token_start_column + last_token_start_column = value_start_column + + tokens.append(line_delta) # Line delta + tokens.append(column_delta) # Token column delta + tokens.append(field_size) # Token length + tokens.append(known_value_token_code) + tokens.append(no_modifiers) + + if not tokens: + return None + return SemanticTokens(tokens) + + def _should_complete_field_with_value(cand: Deb822KnownField) -> bool: return cand.known_values is not None and ( len(cand.known_values) == 1 diff --git a/src/debputy/lsp/quickfixes.py b/src/debputy/lsp/quickfixes.py index d911961..7ae0324 100644 --- a/src/debputy/lsp/quickfixes.py +++ b/src/debputy/lsp/quickfixes.py @@ -172,6 +172,7 @@ def _correct_value_code_action( def provide_standard_quickfixes_from_diagnostics( + ls: "LanguageServer", code_action_params: CodeActionParams, ) -> Optional[List[Union[Command, CodeAction]]]: actions = [] diff --git a/src/debputy/lsp/spellchecking.py b/src/debputy/lsp/spellchecking.py index 69dd119..f9027af 100644 --- a/src/debputy/lsp/spellchecking.py +++ b/src/debputy/lsp/spellchecking.py @@ -45,7 +45,7 @@ _LOOKS_LIKE_PROGRAMMING_TERM = re.compile( # SCREAMING_SNAKE_CASE (environment variables plus -DVAR=B or $FOO) | [-$%&*_]{0,2}[A-Z][A-Z0-9]*(_[A-Z0-9]+)+(?:=\S+)? | \#[A-Z][A-Z0-9]*(_[A-Z0-9]+)+\# - # Subcommand names. Require at least two "-" to avoid skipping hypenated words + # Subcommand names. Require at least two "-" to avoid skipping hyphenated words | [a-z][a-z0-9]*(-[a-z0-9]+){2,} # Short args | -[a-z0-9]+ diff --git a/src/debputy/lsp/vendoring/_deb822_repro/__init__.py b/src/debputy/lsp/vendoring/_deb822_repro/__init__.py index 72fe6dc..cc2b1de 100644 --- a/src/debputy/lsp/vendoring/_deb822_repro/__init__.py +++ b/src/debputy/lsp/vendoring/_deb822_repro/__init__.py @@ -53,7 +53,7 @@ Compared to debian.deb822 ------------------------- The round-trip safe API is primarily useful when your program is editing files -and the file in question is (likely) to be hand-edited or formated directly by +and the file in question is (likely) to be hand-edited or formatted directly by human maintainers. This includes files like debian/control and the debian/copyright using the "DEP-5" format. diff --git a/src/debputy/lsp/vendoring/_deb822_repro/locatable.py b/src/debputy/lsp/vendoring/_deb822_repro/locatable.py index 90bfa1c..afebf14 100644 --- a/src/debputy/lsp/vendoring/_deb822_repro/locatable.py +++ b/src/debputy/lsp/vendoring/_deb822_repro/locatable.py @@ -270,7 +270,7 @@ class Range: :param base: The desired starting position :param sizes: All the ranges that combined makes up the size of the desired position. Note that order can affect the end result. Particularly - the end character offset gets reset everytime a size spans a line. + the end character offset gets reset every time a size spans a line. :returns: A range at the provided base position that has the size of the provided range. """ diff --git a/src/debputy/lsp/vendoring/_deb822_repro/parsing.py b/src/debputy/lsp/vendoring/_deb822_repro/parsing.py index 13e59b1..1a2da25 100644 --- a/src/debputy/lsp/vendoring/_deb822_repro/parsing.py +++ b/src/debputy/lsp/vendoring/_deb822_repro/parsing.py @@ -2076,7 +2076,7 @@ class Deb822ParagraphElement(Deb822Element, Deb822ParagraphToStrWrapperMixin, AB :param discard_comments_on_read: When getting a field value from the dict, this parameter decides how in-line comments are handled. When setting the value, inline comments are still allowed and will be retained. - However, keep in mind that this option makes getter and setter assymetric + However, keep in mind that this option makes getter and setter asymmetric as a "get" following a "set" with inline comments will omit the comments even if they are there (see the code example). :param auto_map_initial_line_whitespace: Special-case the first value line diff --git a/src/debputy/lsp/vendoring/_deb822_repro/tokens.py b/src/debputy/lsp/vendoring/_deb822_repro/tokens.py index 4e5fa16..5db991a 100644 --- a/src/debputy/lsp/vendoring/_deb822_repro/tokens.py +++ b/src/debputy/lsp/vendoring/_deb822_repro/tokens.py @@ -45,7 +45,7 @@ _RE_WHITESPACE_SEPARATED_WORD_LIST = re.compile( _RE_COMMA_SEPARATED_WORD_LIST = re.compile( r""" # This regex is slightly complicated by the fact that it should work with - # finditer and comsume the entire value. + # finditer and consume the entire value. # # To do this, we structure the regex so it always starts on a comma (except # for the first iteration, where we permit the absence of a comma) diff --git a/src/debputy/manifest_parser/declarative_parser.py b/src/debputy/manifest_parser/declarative_parser.py index 32e93fe..84e0230 100644 --- a/src/debputy/manifest_parser/declarative_parser.py +++ b/src/debputy/manifest_parser/declarative_parser.py @@ -720,7 +720,7 @@ class ParserGenerator: ``` While this is sufficient for programmers, it is a bit ridig for the packager writing the manifest. Therefore, - you can also provide a TypedDict descriping the input, enabling more flexibility: + you can also provide a TypedDict describing the input, enabling more flexibility: >>> class InstallDocsRule(DebputyParsedContent): ... sources: List[str] @@ -737,7 +737,7 @@ class ParserGenerator: In this case, the `sources` field can either come from a single `source` in the manifest (which must be a string) or `sources` (which must be a list of strings). The parser also ensures that only one of `source` or `sources` - is used to ensure the input is not ambigious. For the `into` parameter, the parser will accept it being a str + is used to ensure the input is not ambiguous. For the `into` parameter, the parser will accept it being a str or a list of strings. Regardless of how the input was provided, the parser will normalize the input such that both `sources` and `into` in the result is a list of strings. As an example, this parser can accept both the previous input but also the following input: diff --git a/src/debputy/plugin/api/impl.py b/src/debputy/plugin/api/impl.py index e25713f..8b75322 100644 --- a/src/debputy/plugin/api/impl.py +++ b/src/debputy/plugin/api/impl.py @@ -783,7 +783,7 @@ class DebputyPluginInitializerProvider(DebputyPluginInitializer): self._unloaders.append(_unload) - def plugable_object_parser( + def pluggable_object_parser( self, rule_type: str, rule_name: str, @@ -821,12 +821,12 @@ class DebputyPluginInitializerProvider(DebputyPluginInitializer): def _unload() -> None: raise PluginInitializationError( - "Cannot unload plugable_object_parser (not implemented)" + "Cannot unload pluggable_object_parser (not implemented)" ) self._unloaders.append(_unload) - def plugable_manifest_rule( + def pluggable_manifest_rule( self, rule_type: Union[TTP, str], rule_name: Union[str, List[str]], @@ -869,7 +869,7 @@ class DebputyPluginInitializerProvider(DebputyPluginInitializer): def _unload() -> None: raise PluginInitializationError( - "Cannot unload plugable_manifest_rule (not implemented)" + "Cannot unload pluggable_manifest_rule (not implemented)" ) self._unloaders.append(_unload) diff --git a/src/debputy/plugin/api/test_api/test_spec.py b/src/debputy/plugin/api/test_api/test_spec.py index b05f7ed..0c0c6bc 100644 --- a/src/debputy/plugin/api/test_api/test_spec.py +++ b/src/debputy/plugin/api/test_api/test_spec.py @@ -61,7 +61,7 @@ def build_virtual_file_system( ... ) True - Any string provided will be pased to `virtual_path` using all defaults for other parameters, making `str` + Any string provided will be passed to `virtual_path` using all defaults for other parameters, making `str` arguments a nice easy shorthand if you just want a path to exist, but do not really care about it otherwise (or `virtual_path_def` defaults happens to work for you). @@ -158,7 +158,7 @@ class RegisteredPackagerProvidedFile(metaclass=ABCMeta): installed_path: str """The mode that debputy will give these files when installed (unless overridden)""" default_mode: int - """The default priority assigned to files unless overriden (if priories are assigned at all)""" + """The default priority assigned to files unless overridden (if priories are assigned at all)""" default_priority: Optional[int] """The filename format to be used""" filename_format: Optional[str] diff --git a/src/debputy/plugin/debputy/binary_package_rules.py b/src/debputy/plugin/debputy/binary_package_rules.py index 04a0fa1..686d71a 100644 --- a/src/debputy/plugin/debputy/binary_package_rules.py +++ b/src/debputy/plugin/debputy/binary_package_rules.py @@ -59,7 +59,7 @@ ACCEPTABLE_CLEAN_ON_REMOVAL_IF_EXACT_MATCH_OR_SUBDIR_OF = frozenset( def register_binary_package_rules(api: DebputyPluginInitializerProvider) -> None: - api.plugable_manifest_rule( + api.pluggable_manifest_rule( OPARSER_PACKAGES, "binary-version", BinaryVersionParsedFormat, @@ -93,7 +93,7 @@ def register_binary_package_rules(api: DebputyPluginInitializerProvider) -> None ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( OPARSER_PACKAGES, "transformations", ListOfTransformationRulesFormat, @@ -131,7 +131,7 @@ def register_binary_package_rules(api: DebputyPluginInitializerProvider) -> None ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( OPARSER_PACKAGES, "conffile-management", ListOfDpkgMaintscriptHelperCommandFormat, @@ -139,7 +139,7 @@ def register_binary_package_rules(api: DebputyPluginInitializerProvider) -> None source_format=List[DpkgMaintscriptHelperCommand], ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( OPARSER_PACKAGES, "clean-after-removal", ListParsedFormat, @@ -203,7 +203,7 @@ def register_binary_package_rules(api: DebputyPluginInitializerProvider) -> None ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( OPARSER_PACKAGES, "installation-search-dirs", InstallationSearchDirsParsedFormat, diff --git a/src/debputy/plugin/debputy/manifest_root_rules.py b/src/debputy/plugin/debputy/manifest_root_rules.py index cc2b1d4..86a1c27 100644 --- a/src/debputy/plugin/debputy/manifest_root_rules.py +++ b/src/debputy/plugin/debputy/manifest_root_rules.py @@ -33,7 +33,7 @@ if TYPE_CHECKING: def register_manifest_root_rules(api: DebputyPluginInitializerProvider) -> None: # Registration order matters. Notably, definitions must come before anything that can # use definitions (variables), which is why it is second only to the manifest version. - api.plugable_manifest_rule( + api.pluggable_manifest_rule( OPARSER_MANIFEST_ROOT, MK_MANIFEST_VERSION, ManifestVersionFormat, @@ -56,13 +56,13 @@ def register_manifest_root_rules(api: DebputyPluginInitializerProvider) -> None: ), ), ) - api.plugable_object_parser( + api.pluggable_object_parser( OPARSER_MANIFEST_ROOT, MK_MANIFEST_DEFINITIONS, object_parser_key=OPARSER_MANIFEST_DEFINITIONS, on_end_parse_step=lambda _a, _b, _c, mp: mp._ensure_package_states_is_initialized(), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( OPARSER_MANIFEST_DEFINITIONS, MK_MANIFEST_VARIABLES, ManifestVariablesParsedFormat, @@ -105,7 +105,7 @@ def register_manifest_root_rules(api: DebputyPluginInitializerProvider) -> None: ), ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( OPARSER_MANIFEST_ROOT, MK_INSTALLATIONS, ListOfInstallRulesFormat, @@ -156,7 +156,7 @@ def register_manifest_root_rules(api: DebputyPluginInitializerProvider) -> None: ), ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( OPARSER_MANIFEST_ROOT, MK_PACKAGES, DictFormat, diff --git a/src/debputy/plugin/debputy/private_api.py b/src/debputy/plugin/debputy/private_api.py index 2db2b56..b9aa043 100644 --- a/src/debputy/plugin/debputy/private_api.py +++ b/src/debputy/plugin/debputy/private_api.py @@ -781,7 +781,7 @@ def register_special_ppfs(api: DebputyPluginInitializerProvider) -> None: def register_install_rules(api: DebputyPluginInitializerProvider) -> None: - api.plugable_manifest_rule( + api.pluggable_manifest_rule( InstallRule, MK_INSTALLATIONS_INSTALL, ParsedInstallRule, @@ -868,7 +868,7 @@ def register_install_rules(api: DebputyPluginInitializerProvider) -> None: reference_documentation_url=_manifest_format_doc("generic-install-install"), ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( InstallRule, [ MK_INSTALLATIONS_INSTALL_DOCS, @@ -977,7 +977,7 @@ def register_install_rules(api: DebputyPluginInitializerProvider) -> None: ), ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( InstallRule, [ MK_INSTALLATIONS_INSTALL_EXAMPLES, @@ -1059,7 +1059,7 @@ def register_install_rules(api: DebputyPluginInitializerProvider) -> None: ), ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( InstallRule, MK_INSTALLATIONS_INSTALL_MAN, ParsedInstallManpageRule, @@ -1166,7 +1166,7 @@ def register_install_rules(api: DebputyPluginInitializerProvider) -> None: ), ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( InstallRule, MK_INSTALLATIONS_DISCARD, ParsedInstallDiscardRule, @@ -1242,7 +1242,7 @@ def register_install_rules(api: DebputyPluginInitializerProvider) -> None: ), ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( InstallRule, MK_INSTALLATIONS_MULTI_DEST_INSTALL, ParsedMultiDestInstallRule, @@ -1329,7 +1329,7 @@ def register_install_rules(api: DebputyPluginInitializerProvider) -> None: def register_transformation_rules(api: DebputyPluginInitializerProvider) -> None: - api.plugable_manifest_rule( + api.pluggable_manifest_rule( TransformationRule, "move", TransformationMoveRuleSpec, @@ -1381,7 +1381,7 @@ def register_transformation_rules(api: DebputyPluginInitializerProvider) -> None ), ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( TransformationRule, "remove", TransformationRemoveRuleSpec, @@ -1451,7 +1451,7 @@ def register_transformation_rules(api: DebputyPluginInitializerProvider) -> None ), ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( TransformationRule, "create-symlink", CreateSymlinkRule, @@ -1537,7 +1537,7 @@ def register_transformation_rules(api: DebputyPluginInitializerProvider) -> None ), ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( TransformationRule, "path-metadata", PathManifestRule, @@ -1653,7 +1653,7 @@ def register_transformation_rules(api: DebputyPluginInitializerProvider) -> None ), ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( TransformationRule, "create-directories", EnsureDirectoryRule, @@ -1824,7 +1824,7 @@ def register_manifest_condition_rules(api: DebputyPluginInitializerProvider) -> ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( ManifestCondition, "not", MCNot, @@ -1868,7 +1868,7 @@ def register_manifest_condition_rules(api: DebputyPluginInitializerProvider) -> ), ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( ManifestCondition, ["any-of", "all-of"], MCAnyOfAllOf, @@ -1887,7 +1887,7 @@ def register_manifest_condition_rules(api: DebputyPluginInitializerProvider) -> ), ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( ManifestCondition, "arch-matches", MCArchMatches, @@ -1997,7 +1997,7 @@ def register_manifest_condition_rules(api: DebputyPluginInitializerProvider) -> ), ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( ManifestCondition, "source-context-arch-matches", MCArchMatches, @@ -2005,7 +2005,7 @@ def register_manifest_condition_rules(api: DebputyPluginInitializerProvider) -> source_format=str, inline_reference_documentation=context_arch_doc, ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( ManifestCondition, "package-context-arch-matches", MCArchMatches, @@ -2013,7 +2013,7 @@ def register_manifest_condition_rules(api: DebputyPluginInitializerProvider) -> source_format=str, inline_reference_documentation=context_arch_doc, ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( ManifestCondition, "build-profiles-matches", MCBuildProfileMatches, @@ -2043,7 +2043,7 @@ def register_manifest_condition_rules(api: DebputyPluginInitializerProvider) -> def register_dpkg_conffile_rules(api: DebputyPluginInitializerProvider) -> None: - api.plugable_manifest_rule( + api.pluggable_manifest_rule( DpkgMaintscriptHelperCommand, "remove", DpkgRemoveConffileRule, @@ -2051,7 +2051,7 @@ def register_dpkg_conffile_rules(api: DebputyPluginInitializerProvider) -> None: inline_reference_documentation=None, # TODO: write and add ) - api.plugable_manifest_rule( + api.pluggable_manifest_rule( DpkgMaintscriptHelperCommand, "rename", DpkgRenameConffileRule, diff --git a/src/debputy/transformation_rules.py b/src/debputy/transformation_rules.py index 8d9caae..fdf9528 100644 --- a/src/debputy/transformation_rules.py +++ b/src/debputy/transformation_rules.py @@ -217,7 +217,7 @@ class MoveTransformationRule(TransformationRule): self._error( f"Could not rename {self._match_rule.describe_match_short()} to {self._dest_path}" f" (from: {self._definition_source}). Multiple paths matched the pattern and the" - " destination was not a directory. Either correct the pattern to only match ony source" + " destination was not a directory. Either correct the pattern to only match only source" " OR define the destination to be a directory (E.g., add a trailing slash - example:" f' "{self._dest_path}/")' ) -- cgit v1.2.3