67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/about.toml:
--------------------------------------------------------------------------------
1 | accepted = [
2 | "Apache-2.0",
3 | "BSD-2-Clause",
4 | "BSD-3-Clause",
5 | "BSL-1.0",
6 | "CC0-1.0",
7 | "CDLA-Permissive-2.0",
8 | "GPL-3.0",
9 | "ISC",
10 | "LGPL-3.0",
11 | "MIT",
12 | "MPL-2.0",
13 | "OpenSSL",
14 | "Unicode-3.0",
15 | "Unicode-DFS-2016",
16 | ]
17 | ignore-dev-dependencies = true
18 | private.ignore = true
19 | workarounds = ["ring"]
20 |
--------------------------------------------------------------------------------
/clippy.toml:
--------------------------------------------------------------------------------
1 | allow-unwrap-in-tests = true
2 |
--------------------------------------------------------------------------------
/deny.toml:
--------------------------------------------------------------------------------
1 | # This template contains all of the possible sections and their default values
2 |
3 | # Note that all fields that take a lint level have these possible values:
4 | # * deny - An error will be produced and the check will fail
5 | # * warn - A warning will be produced, but the check will not fail
6 | # * allow - No warning or error will be produced, though in some cases a note
7 | # will be
8 |
9 | # The values provided in this template are the default values that will be used
10 | # when any section or field is not specified in your own configuration
11 |
12 | # Root options
13 |
14 | # The graph table configures how the dependency graph is constructed and thus
15 | # which crates the checks are performed against
16 | [graph]
17 | # If 1 or more target triples (and optionally, target_features) are specified,
18 | # only the specified targets will be checked when running `cargo deny check`.
19 | # This means, if a particular package is only ever used as a target specific
20 | # dependency, such as, for example, the `nix` crate only being used via the
21 | # `target_family = "unix"` configuration, that only having windows targets in
22 | # this list would mean the nix crate, as well as any of its exclusive
23 | # dependencies not shared by any other crates, would be ignored, as the target
24 | # list here is effectively saying which targets you are building for.
25 | targets = [
26 | # The triple can be any string, but only the target triples built in to
27 | # rustc (as of 1.40) can be checked against actual config expressions
28 | #"x86_64-unknown-linux-musl",
29 | # You can also specify which target_features you promise are enabled for a
30 | # particular target. target_features are currently not validated against
31 | # the actual valid features supported by the target architecture.
32 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
33 | ]
34 | # When creating the dependency graph used as the source of truth when checks are
35 | # executed, this field can be used to prune crates from the graph, removing them
36 | # from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
37 | # is pruned from the graph, all of its dependencies will also be pruned unless
38 | # they are connected to another crate in the graph that hasn't been pruned,
39 | # so it should be used with care. The identifiers are [Package ID Specifications]
40 | # (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
41 | #exclude = []
42 | # If true, metadata will be collected with `--all-features`. Note that this can't
43 | # be toggled off if true, if you want to conditionally enable `--all-features` it
44 | # is recommended to pass `--all-features` on the cmd line instead
45 | all-features = false
46 | # If true, metadata will be collected with `--no-default-features`. The same
47 | # caveat with `all-features` applies
48 | no-default-features = false
49 | # If set, these feature will be enabled when collecting metadata. If `--features`
50 | # is specified on the cmd line they will take precedence over this option.
51 | #features = []
52 |
53 | # The output table provides options for how/if diagnostics are outputted
54 | [output]
55 | # When outputting inclusion graphs in diagnostics that include features, this
56 | # option can be used to specify the depth at which feature edges will be added.
57 | # This option is included since the graphs can be quite large and the addition
58 | # of features from the crate(s) to all of the graph roots can be far too verbose.
59 | # This option can be overridden via `--feature-depth` on the cmd line
60 | feature-depth = 1
61 |
62 | # This section is considered when running `cargo deny check advisories`
63 | # More documentation for the advisories section can be found here:
64 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
65 | [advisories]
66 | # The path where the advisory databases are cloned/fetched into
67 | #db-path = "$CARGO_HOME/advisory-dbs"
68 | # The url(s) of the advisory databases to use
69 | #db-urls = ["https://github.com/rustsec/advisory-db"]
70 | # A list of advisory IDs to ignore. Note that ignored advisories will still
71 | # output a note when they are encountered.
72 | ignore = [
73 | #"RUSTSEC-0000-0000",
74 | #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
75 | #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
76 | #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
77 | { id = "RUSTSEC-2024-0436", reason = "paste is unmaintained, but no option is available, indirect dependency" },
78 | ]
79 | # If this is true, then cargo deny will use the git executable to fetch advisory database.
80 | # If this is false, then it uses a built-in git library.
81 | # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
82 | # See Git Authentication for more information about setting up git authentication.
83 | #git-fetch-with-cli = true
84 |
85 | # This section is considered when running `cargo deny check licenses`
86 | # More documentation for the licenses section can be found here:
87 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
88 | [licenses]
89 | # List of explicitly allowed licenses
90 | # See https://spdx.org/licenses/ for list of possible licenses
91 | # [possible values: any SPDX 3.11 short identifier (+ optional exception)].
92 | allow = [
93 | #"Apache-2.0 WITH LLVM-exception",
94 | "Apache-2.0",
95 | "BSD-3-Clause",
96 | "BSL-1.0",
97 | "CDLA-Permissive-2.0",
98 | "GPL-3.0",
99 | "ISC",
100 | "LGPL-3.0",
101 | "MIT",
102 | "MPL-2.0",
103 | "Unicode-3.0",
104 | #"Unicode-DFS-2016",
105 | ]
106 | # The confidence threshold for detecting a license from license text.
107 | # The higher the value, the more closely the license text must be to the
108 | # canonical license text of a valid SPDX license file.
109 | # [possible values: any between 0.0 and 1.0].
110 | confidence-threshold = 0.8
111 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses
112 | # aren't accepted for every possible crate as with the normal allow list
113 | exceptions = [
114 | # Each entry is the crate and version constraint, and its specific allow
115 | # list
116 | #{ allow = ["Zlib"], crate = "adler32" },
117 | { allow = ["OpenSSL"], crate = "ring" },
118 | ]
119 |
120 | # Some crates don't have (easily) machine readable licensing information,
121 | # adding a clarification entry for it allows you to manually specify the
122 | # licensing information
123 | [[licenses.clarify]]
124 | # The package spec the clarification applies to
125 | crate = "ring"
126 | # The SPDX expression for the license requirements of the crate
127 | expression = "MIT AND ISC AND OpenSSL"
128 | # One or more files in the crate's source used as the "source of truth" for
129 | # the license expression. If the contents match, the clarification will be used
130 | # when running the license check, otherwise the clarification will be ignored
131 | # and the crate will be checked normally, which may produce warnings or errors
132 | # depending on the rest of your configuration
133 | license-files = [
134 | # Each entry is a crate relative path, and the (opaque) hash of its contents
135 | { path = "LICENSE", hash = 0xbd0eed23 },
136 | ]
137 |
138 | [licenses.private]
139 | # If true, ignores workspace crates that aren't published, or are only
140 | # published to private registries.
141 | # To see how to mark a crate as unpublished (to the official registry),
142 | # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
143 | ignore = false
144 | # One or more private registries that you might publish crates to, if a crate
145 | # is only published to private registries, and ignore is true, the crate will
146 | # not have its license(s) checked
147 | registries = [
148 | #"https://sekretz.com/registry
149 | ]
150 |
151 | # This section is considered when running `cargo deny check bans`.
152 | # More documentation about the 'bans' section can be found here:
153 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
154 | [bans]
155 | # Lint level for when multiple versions of the same crate are detected
156 | multiple-versions = "warn"
157 | # Lint level for when a crate version requirement is `*`
158 | wildcards = "allow"
159 | # The graph highlighting used when creating dotgraphs for crates
160 | # with multiple versions
161 | # * lowest-version - The path to the lowest versioned duplicate is highlighted
162 | # * simplest-path - The path to the version with the fewest edges is highlighted
163 | # * all - Both lowest-version and simplest-path are used
164 | highlight = "all"
165 | # The default lint level for `default` features for crates that are members of
166 | # the workspace that is being checked. This can be overridden by allowing/denying
167 | # `default` on a crate-by-crate basis if desired.
168 | workspace-default-features = "allow"
169 | # The default lint level for `default` features for external crates that are not
170 | # members of the workspace. This can be overridden by allowing/denying `default`
171 | # on a crate-by-crate basis if desired.
172 | external-default-features = "allow"
173 | # List of crates that are allowed. Use with care!
174 | allow = [
175 | #"ansi_term@0.11.0",
176 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
177 | ]
178 | # List of crates to deny
179 | deny = [
180 | #"ansi_term@0.11.0",
181 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
182 | # Wrapper crates can optionally be specified to allow the crate when it
183 | # is a direct dependency of the otherwise banned crate
184 | #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
185 | ]
186 |
187 | # List of features to allow/deny
188 | # Each entry the name of a crate and a version range. If version is
189 | # not specified, all versions will be matched.
190 | #[[bans.features]]
191 | #crate = "reqwest"
192 | # Features to not allow
193 | #deny = ["json"]
194 | # Features to allow
195 | #allow = [
196 | # "rustls",
197 | # "__rustls",
198 | # "__tls",
199 | # "hyper-rustls",
200 | # "rustls",
201 | # "rustls-pemfile",
202 | # "rustls-tls-webpki-roots",
203 | # "tokio-rustls",
204 | # "webpki-roots",
205 | #]
206 | # If true, the allowed features must exactly match the enabled feature set. If
207 | # this is set there is no point setting `deny`
208 | #exact = true
209 |
210 | # Certain crates/versions that will be skipped when doing duplicate detection.
211 | skip = [
212 | #"ansi_term@0.11.0",
213 | #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
214 | ]
215 | # Similarly to `skip` allows you to skip certain crates during duplicate
216 | # detection. Unlike skip, it also includes the entire tree of transitive
217 | # dependencies starting at the specified crate, up to a certain depth, which is
218 | # by default infinite.
219 | skip-tree = [
220 | #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
221 | #{ crate = "ansi_term@0.11.0", depth = 20 },
222 | ]
223 |
224 | # This section is considered when running `cargo deny check sources`.
225 | # More documentation about the 'sources' section can be found here:
226 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
227 | [sources]
228 | # Lint level for what to happen when a crate from a crate registry that is not
229 | # in the allow list is encountered
230 | unknown-registry = "warn"
231 | # Lint level for what to happen when a crate from a git repository that is not
232 | # in the allow list is encountered
233 | unknown-git = "warn"
234 | # List of URLs for allowed crate registries. Defaults to the crates.io index
235 | # if not specified. If it is specified but empty, no registries are allowed.
236 | allow-registry = ["https://github.com/rust-lang/crates.io-index"]
237 | # List of URLs for allowed Git repositories
238 | allow-git = []
239 |
240 | [sources.allow-org]
241 | # 1 or more github.com organizations to allow git sources for
242 | #github = [""]
243 | # 1 or more gitlab.com organizations to allow git sources for
244 | #gitlab = [""]
245 | # 1 or more bitbucket.org organizations to allow git sources for
246 | #bitbucket = [""]
247 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | book
2 |
--------------------------------------------------------------------------------
/docs/book.toml:
--------------------------------------------------------------------------------
1 | [book]
2 | authors = ["Arvid Norlander"]
3 | language = "en"
4 | multilingual = false
5 | src = "src"
6 | title = "Chezmoi Modify Manager"
7 |
8 | [output.html]
9 | site-url = "/chezmoi_modify_manager/"
10 | git-repository-url = "https://github.com/VorpalBlade/chezmoi_modify_manager/tree/main"
11 | edit-url-template = "https://github.com/VorpalBlade/chezmoi_modify_manager/edit/main/docs/{path}"
12 |
--------------------------------------------------------------------------------
/docs/src/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | `chezmoi_modify_manager` is an addon for [chezmoi](https://www.chezmoi.io/)
4 | that deals with settings files that contain a mix of settings and state.
5 | So far handling INI-style files are supported.
6 |
7 | A typical example of this is KDE settings files. These contain (apart from
8 | settings) state like recently opened files and positions of windows and dialog
9 | boxes. Other programs (such as PrusaSlicer) also do the same thing.
10 |
11 | `chezmoi_modify_manager` allows you to ignore certain sections of those
12 | INI files when managing the configuration files with chezmoi.
13 |
14 | > Note! This documentation reflects the latest release and may not match older
15 | versions or newer in-development versions.
16 |
17 | ## Features
18 |
19 | * Ignore entire sections or specific keys in an INI style file.
20 | * Ignore a key in a section based on regular expressions.
21 | * Force set a value (useful together with templating).
22 | * Force remove a section, key or entries matching a regex (useful together with templating).
23 | * Apply a transformation to the value of a specified key. These are special
24 | operations that are built in and provide more complicated transformations.
25 | Some examples that this can do:
26 | * Look up a password in the platform keyring
27 | * Ignore the sorting order of a list style value (`key=a,b,c,d`)
28 | * etc.
29 | * Assisted adding/updating of files in your chezmoi source state.
30 | * *Optional* built in self-updater
31 |
--------------------------------------------------------------------------------
/docs/src/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | [Introduction](README.md)
4 |
5 | # User guide
6 |
7 | - [Installation & upgrades](./installation.md)
8 | - [Basic Usage](./basic_usage.md)
9 | - [Syntax of configuration files](./configuration_files.md)
10 | - [Transforms](./transforms.md)
11 | - [Examples](./examples/README.md)
12 | - [Ignores & transforms](./examples/basics.md)
13 | - [Advanced: set, remove, add:*](./examples/advanced.md)
14 | - [Migration](./migration/README.md)
15 | - [Migration from version 1.x to 2.x](./migration/migration_2.md)
16 | - [Migration from version 2.x to 3.x](./migration/migration_3.md)
17 | - [Troubleshooting](./troubleshooting.md)
18 | - [Limitations](./limitations.md)
19 |
20 | # Advanced topics
21 |
22 | - [How chezmoi_modify_manager finds the data file](./source_specification.md)
23 | - [Actions & directives](./actions.md)
24 | - [Algorithms](./algorithms.md)
25 |
26 | # Developer guide
27 |
28 | - [Packaging for Linux distros etc](./dev/packaging.md)
29 | - [Design decisions](./dev/design_decisions.md)
--------------------------------------------------------------------------------
/docs/src/actions.md:
--------------------------------------------------------------------------------
1 | # Actions & directives
2 |
3 | This is a high level overview of how chezmoi_modify_manager applies your config.
4 | For the details on specific directives see `chezmoi_modify_manager --help-syntax`.
5 |
6 | ## Glossary
7 |
8 | * Directive: Things like `source`, `ignore`, `transform`, `set`, `add:hide`, etc
9 | that you put in your config file. They are documented in the output of
10 | `chezmoi_modify_manager --help-syntax`.
11 | * Actions: The directives are internally translated into a ruleset of actions.
12 | These are very similar to the directives, but may not correspond 1:1. For example:
13 | * `set` becomes a special transform internally.
14 | * `source` doesn't enter the actions, it is only used to figure out what file to load.
15 | * etc.
16 |
17 | ## Contexts
18 |
19 | There are two different "contexts" for evaluating actions:
20 |
21 | * Merging: This is the normal algorithm, used during `chezmoi apply` (and `diff` etc)
22 | * Filtering: This is using when re-adding an existing file (`chezmoi_modify_manager -a`
23 | or `-s`).
24 |
25 | See [Algorithms](algorithms.md) for details of how these work, in this file we
26 | are only concerned with how the directives and rules matching works.
27 |
28 | These have separate directive to action translators. Not all directives apply to
29 | all contexts. Some examples:
30 |
31 | * `set` is unused when filtering
32 | * `add:hide` is unused when merging
33 | * `ignore` translates to the same as `add:remove` when filtering.
34 | * etc.
35 |
36 | ## Order of action matching
37 |
38 | Actions come in three flavours:
39 |
40 | 1. Section matches (always literal matches)
41 | 2. Literal section+key matches
42 | 3. Regular expression section+key matches
43 |
44 | Not every rule can exist in every variant. For example:
45 |
46 | * Merge section matches only support `ignore` and `remove`.
47 | * `set` will only ever exist as a literal section+key match
48 | * etc.
49 |
50 | Chezmoi_modify_manager uses a single regex to match both the section and key.
51 | This is done by constructing a combined string for these, using the 0-byte
52 | (`\0`) as a separator. For example a regex directive
53 | `ignore regex "Section|OtherSection" "SomePrefix.*"` is compiled down to
54 | `(?:Section|OtherSection)\0(?:SomePrefix.*)`. This can be visible if you attempt
55 | to use `^` or `$` (don't do that).
56 |
57 | The special string `` is used to match keys that appear before the
58 | first section (hopefully no one has an ini-file with a section with that name in it).
59 |
60 | When matching actions:
61 |
62 | 1. We first check if any section action applies. If so we are done. These are always literal matches.
63 | 2. Then we check if there is a literal section+key match. If so it applies and we are done.
64 | 3. Otherwise, we check if any regex action matches. If so we take the first result.
65 | This will be the same as first in source order in your config file.
66 |
67 | Additionally, chezmoi_modify_manager will warn if there are multiple regex matches
68 | that match. This can be disabled (per file) with a `no-warn-multiple-key-matches`
69 | directive, in case you want this behaviour.
70 |
--------------------------------------------------------------------------------
/docs/src/algorithms.md:
--------------------------------------------------------------------------------
1 | # Algorithms
2 |
3 | This documents a high level overview of the algorithms chezmoi_modify_manager
4 | uses for merging or filtering INI files.
5 |
6 | In general these algorithms are single-pass, processing one line at a time
7 | from the input INI file. This makes them quite fast in practice.
8 |
9 | The code for these are implemented in the [ini-merge] crate. The explanation
10 | here is intended for users, and as such leaves out a lot of gnarly details around
11 | for example: empty sections, sections with only comments in them, etc. If you are
12 | interested in that, go read the code.
13 |
14 | The actual INI parser is in the [ini-roundtrip] crate. A custom INI parser is used
15 | to ensure that writing it back out doesn't change formatting.
16 |
17 | # Filtering
18 |
19 | This is used when re-adding an existing file (`chezmoi_modify_manager -a` or `-s`).
20 | This is the simpler of the two algorithms.
21 |
22 | Relevant directives from your config for this algorithm:
23 | * `add:hide` (replaces the value with `HIDDEN` when re-adding)
24 | * `add:remove` (removes the line when re-adding)
25 | * `ignore` (removes the line when re-adding)
26 |
27 | (The reason there are two directives with the same effect is that they do
28 | different things when merging.)
29 |
30 | When a user passes `-s` or `-a` a bunch of things happen:
31 |
32 | 1. We figure out if the file is already managed or not. Depending on `-a` or `-s`
33 | we will then do different things. This is not the focus of this page though.
34 | 2. Assuming we decided that we should add the file and manage it using
35 | `chezmoi_modify_manager` (instead of plain `chezmoi`), and that the file was
36 | *already* managed by us before, we then need to filter:
37 | 1. Load the ruleset that the user wrote into an [Actions structure](actions.md).
38 | Currently, this does not take chezmoi templates into account (though this
39 | might change).
40 | 2. For each line in the file being filtered:
41 | * If it is a new section header, check [section actions](actions.md) to
42 | determine if it should be removed entirely, otherwise keep it.
43 | * If it is a comment or blank line keep it (unless the entire section is
44 | being removed)
45 | * If it is a key, check [actions](actions.md) to determine if it should be
46 | hidden, removed or kept.
47 |
48 | Note evaluation order of actions documented in [Actions](actions.md#order-of-action-matching),
49 | section matches take priority, then literal matches, then regex matches (in order).
50 |
51 | # Merging
52 |
53 | This is used for normal `chezmoi apply` (and `chezmoi diff` etc). This is a more
54 | complicated case: there are now three files involved.
55 |
56 | Relevant directives from your config for this algorithm:
57 | * `ignore` (keeps the system state and ignores whatever is in the source state)
58 | * `set` (sets to a specific key and value)
59 | * `remove` (entirely removes the match)
60 | * `transform` (applies a custom transform to the match, see `--help-transforms`,
61 | custom semantics apply to each)
62 |
63 | 1. Load the ruleset that the user wrote into an [Actions structure](actions.md).
64 | Chezmoi has already processed any templates for us.
65 | 2. Load the `.src.ini` file into a fast data structure for looking things up in it.
66 | 3. For each line in the system state (as provided by chezmoi on stdin):
67 | * If it is a comment or blank line, keep it (unless it is in a section
68 | that we are not outputting).
69 | * If it is a section header, check:
70 | * If the entire section is ignored, keep it as is from the system state.
71 | * If the section is being removed by `remove`, remove it.
72 | * If the section exists in the .src.ini, keep it.
73 | * If the section *doesn't* exist in the .src.ini, remove it.
74 | * (There is also some additional logic to deal with entirely empty
75 | sections etc, so we don't actually emit the section on stdout until we
76 | are sure later on, there is a concept of "pending lines" to implement that.)
77 | * If it is a key, find the first [action that applies](actions.md#order-of-action-matching)
78 | if any. Then:
79 | * If no action applies, take the value from the `.src.ini` file.
80 | * If no action applies and the line is not in the `.src.ini` file, remove
81 | the line.
82 | * If the action is to `ignore`, leave the system value as is.
83 | * If the action is to `remove`, remove it.
84 | * If the action is to `set`, set it.
85 | * If a transform applies, apply it (see each transform for more details).
86 | * Before we start a new section, check if there are any lines in the
87 | `.src.ini` that didn't exist in the system state (or any such `set`
88 | directives), if so emit them.
89 | * Before the end of the file, check for entire sections (or `set` directives
90 | in such sections) in the `.src.ini` that didn't exist in the system state,
91 | if so emit them.
92 |
93 | The newly emitted keys or sections from the last two bullet points will
94 | generally be weirdly formatted. The assumption is the program that owns this
95 | file will reformat it on next use.
96 |
97 | [ini-merge]: https://github.com/VorpalBlade/ini-merge
98 | [ini-roundtrip]: https://github.com/VorpalBlade/ini-roundtrip
99 |
--------------------------------------------------------------------------------
/docs/src/basic_usage.md:
--------------------------------------------------------------------------------
1 | # Basic Usage
2 |
3 | ## Theory of operation
4 |
5 | For each settings file you want to manage with `chezmoi_modify_manager` there
6 | will be two files in your chezmoi source directory:
7 |
8 | * `modify_` or `modify_.tmpl`, e.g. `modify_private_kdeglobals.tmpl` \
9 | This is the modify script/configuration file that calls `chezmoi_modify_manager`.
10 | It contains the directives describing what to ignore.
11 | * `.src.ini`, e.g. `private_kdeglobals.src.ini`\
12 | This is the source state of the INI file.
13 |
14 | The `modify_` script is responsible for generating the new state of the file
15 | given the current state in your home directory. The `modify_` script is set
16 | up to use `chezmoi_modify_manager` as an interpreter to do so.
17 | `chezmoi_modify_manager` will read the modify script to read configuration and
18 | the `.src.ini` file and by default will apply that file exactly (ignoring blank
19 | lines and comments).
20 |
21 | However, by giving additional directives to `chezmoi_modify_manager` in the
22 | `modify_` script you can tell it to ignore certain sections (see
23 | `chezmoi_modify_manager --help-syntax` for details). For example:
24 |
25 | ```bash
26 | ignore "KFileDialog Settings" "Show Inline Previews"
27 | ignore section "DirSelect Dialog"
28 | ```
29 |
30 | will tell it to ignore the key `Show Inline Previews` in the section
31 | `KFileDialog Settings` and the entire section `DirSelect Dialog`. More on
32 | this [below](#configuring-filters).
33 |
34 | ## Adding files
35 |
36 | > Always refer to `chezmoi_modify_manager --help` for the *most* up-to-date details
37 | that matches the version you are using.
38 |
39 | There are two modes to add files in:
40 |
41 | * `-s`/`--smart-add`: Smart re-add mode that re-adds files as managed `.src.ini`
42 | if they are already managed, otherwise adds with plain chezmoi.
43 | * `-a`/`--add`: This adds or converts from plain chezmoi to managed `.src.ini`.
44 |
45 | Here are some examples:
46 |
47 | ```bash
48 | # Add configs to be handled by chezmoi_modify_manager (or convert configs
49 | # managed by chezmoi to be managed by chezmoi_modify_manager).
50 | chezmoi_modify_manager --add ~/.config/kdeglobals ~/.config/kwinrc
51 |
52 | # Re-add config after changes in the live system.
53 | chezmoi_modify_manager --add ~/.config/kdeglobals
54 |
55 | # Don't remember if chezmoi_modify_manager handles the file or if it is raw chezmoi?
56 | # Use smart mode (-s/--smart-add) to update the file!
57 | chezmoi_modify_manager --smart-add ~/.config/PrusaSlicer/PrusaSlicer.ini
58 | ```
59 |
60 | In addition, you can control *re*adding behaviour with some settings in the
61 | `modify_`, to filter out entries while readding. This is covered
62 | in the [next chapter](configuration_files.md).
63 |
64 | ## Configuring filters
65 |
66 | The file `modify_` (or `modify_.tmpl` if you wish
67 | to use chezmoi templating) contain the control directives for that config
68 | file which controls the behaviour for `chezmoi apply` of those files (as well as
69 | when readding files from the system). The full details on this file are in the
70 | [next chapter](configuration_files.md), this section just covers the basics.
71 |
72 | A basic such file will have this structure:
73 |
74 | ```bash
75 | #!/usr/bin/env chezmoi_modify_manager
76 |
77 | source auto
78 |
79 | # This is a comment
80 | # The next line is a directive. Directives are delimited by newlines.
81 | ignore "SomeSection" "SomeKey"
82 |
83 | ignore section "An entire section that is ignored"
84 |
85 | ignore regex "Some Sections .*" "A key regex .*"
86 | ```
87 |
88 | This illustrates some basics:
89 |
90 | * The first line needs to be a `#!` that tells the OS that `chezmoi_modify_manager`
91 | should be the interpreter for this file. (This still works on Windows because
92 | `chezmoi` handles that internally as far as I understand, though I don't use
93 | Windows myself.)
94 | * The `source` directive tells `chezmoi_modify_manager` where to look for the
95 | `.src.ini` file. As of chezmoi 2.46.1 this can be auto-detected. If you use an
96 | older version, `chezmoi_modify_manager --add` will detect that and insert the
97 | appropriate template based line instead.
98 | * The `ignore` directive is the most important directive. It has two effects:
99 | * When running `chezmoi apply` it results in the matching entries from
100 | `.src.ini` being ignored, and the current system state is used instead.
101 | * When running `chezmoi_modify_manager --add` (or `--smart-add`) it results
102 | in not copying matching entries to the `.src.ini` to begin with.
103 |
104 | There are several other directives as well, here is a basic rundown of them,
105 | they are covered in more detail in the [next chapter](configuration_files.md). Here
106 | is a short summary:
107 |
108 | * `set`: Sets an entry to a specific value. Useful together with chezmoi templating.
109 | * `remove`: Remove a specific entry, also useful together with chezmoi templating.
110 | * `transform`: Apply a custom transformation to the value. Can be used to handle
111 | some hard to deal with corner cases, supported transforms are covered in a
112 | [later chapter](transforms.md).
113 | * `add:hide` & `add:remove`: Useful together with certain transforms to control
114 | re-adding behaviour.
115 | * `no-warn-multiple-key-matches`: If there are multiple regex rules that overlap
116 | a warning will be issued. You can use this directive to quieten those warnings
117 | if this is intentional. See [action evaluation order](actions.md#order-of-action-matching)
118 | for more information on this.
119 |
--------------------------------------------------------------------------------
/docs/src/configuration_files.md:
--------------------------------------------------------------------------------
1 | # Syntax of configuration files
2 |
3 | chezmoi_modify_manager uses basic configuration files to control how to
4 | merge INI files. These are the `modify_` files. They can
5 | also be templated with `chezmoi` by naming the file
6 | `modify_.tmpl` instead. The easiest way to get started is
7 | to use `-a` to add a file and generate a skeleton configuration file.
8 |
9 | ## Syntax
10 |
11 | The file consists of directives, one per line. Comments are supported by
12 | prefixing a line with #. Comments are only supported at the start of lines.
13 |
14 | > **Note!** If a key appears before the first section, use `` as the
15 | section.
16 |
17 | > **Note!** The modify script can itself be a chezmoi template (if it ends with
18 | `.tmpl`), which can be useful if you want to do host specific configuration using
19 | the `set` directive for example.\
20 | \
21 | This however will slow things down every so slightly as chezmoi has to run its
22 | templating engine on the file. Typically, this will be an overhead of about half
23 | a millisecond per templated modify script (measured on an AMD Ryzen 5 5600X).
24 |
25 | ## Directives
26 |
27 | ### source
28 |
29 | This directive is required. It specifies where to find the source file
30 | (i.e. the file in the dotfile repo). It should have the following format
31 | to support Chezmoi versions older than v2.46.1:
32 |
33 | ```bash
34 | source "{{ .chezmoi.sourceDir }}/{{ .chezmoi.sourceFile | trimSuffix ".tmpl" | replace "modify_" "" }}.src.ini"
35 | ```
36 |
37 | From Chezmoi v2.46.1 and forward the following also works instead:
38 |
39 | ```bash
40 | source auto
41 | ```
42 |
43 | ### ignore
44 |
45 | Ignore a certain line, always taking it from the target file (i.e. file in
46 | your home directory), instead of the source state. The following variants
47 | are supported:
48 |
49 | ```bash
50 | ignore section "my-section"
51 | ignore "my-section" "my-key"
52 | ignore regex "section.*regex" "key regex.*"
53 | ```
54 |
55 | * The first form ignores a whole section (exact literal match).
56 | * The second form ignores a specific key (exact literal match).
57 | * The third form uses a regex to ignore a specific key.
58 |
59 | Prefer the exact literal match variants where possible, they will be
60 | marginally faster.
61 |
62 | An additional effect is that lines that are missing in the source state
63 | will not be deleted if they are ignored.
64 |
65 | Finally, ignored lines will not be added back when using `--add` or
66 | `--smart-add`, in order to reduce git diffs.
67 |
68 | ### set
69 |
70 | Set an entry to a specific value. This is primarily useful together with
71 | chezmoi templates, allowing you to override a specific value for only some
72 | of your computers. The following variants are supported:
73 |
74 | ```bash
75 | set "section" "key" "value"
76 | set "section" "key" "value" separator="="
77 | ```
78 |
79 | By default, separator is `" = "`, which might not match what the program that
80 | the ini files belongs to uses.
81 |
82 | Notes:
83 |
84 | * Only exact literal matches are supported.
85 | * It works better if the line exists in the source & target state, otherwise
86 | it is likely the line will get formatted weirdly (which will often be
87 | changed by the program the INI file belongs to).
88 |
89 | ### remove
90 |
91 | Unconditionally remove everything matching the directive. This is primarily
92 | useful together with chezmoi templates, allowing you to remove a specific
93 | key or section for only some of your computers. The following variants are
94 | supported:
95 |
96 | ```bash
97 | remove section "my-section"
98 | remove "my-section" "my-key"
99 | remove regex "section.*regex" "key regex.*"
100 | ```
101 |
102 | (Matching works identically to ignore, see above for more details.)
103 |
104 | ### transform
105 |
106 | Some specific situations need more complicated merging that a simple
107 | ignore. For those situations you can use transforms. Supported variants
108 | are:
109 |
110 | ```bash
111 | transform "section" "key" transform-name arg1="value" arg2="value" ...
112 | transform regex "section-regex.*" "key-regex.*" transform-name arg1="value" ...
113 | ```
114 |
115 | (Matching works identically to ignore except matching entire sections is
116 | not supported. See above for more details.)
117 |
118 | For example, to treat `mykey` in `mysection` as an unsorted comma separated
119 | list, you could use:
120 |
121 | ```bash
122 | transform "mysection" "mykey" unsorted-list separator=","
123 | ```
124 |
125 | The full list of supported transforms, and how to use them can be listed
126 | using `--help-transforms`.
127 |
128 | ### add:remove & add:hide
129 |
130 | These two directives control the behaviour when using --add or --smart-add.
131 | In particular, these allow filtering lines that will be added back to the
132 | source state.
133 |
134 | `add:remove` will remove the matching lines entirely. The following forms are
135 | supported:
136 |
137 | ```bash
138 | add:remove section "section name"
139 | add:remove "section name" "key"
140 | add:remove regex "section-regex.*" "key-regex.*"
141 | ```
142 |
143 | (Matching works identically to ignore, see above for more details.)
144 |
145 | `add:hide` will instead keep the entries but replace the value associated with
146 | those keys. This is useful together with the keyring transform in particular,
147 | as the key needs to exist in the source or target state for it to trigger
148 | the replacement. The following forms are supported:
149 |
150 | ```bash
151 | add:hide section "section name"
152 | add:hide "section name" "key"
153 | add:hide regex "section-regex.*" "key-regex.*"
154 | ```
155 |
156 | (Matching works identically to ignore, see above for more details.)
157 |
158 | ### no-warn-multiple-key-matches
159 |
160 | This directive quietens warnings on multiple regular expressions matching the
161 | same section+key. While the warning is generally useful, sometimes you might
162 | actually "know what you are doing" and want to suppress it.
--------------------------------------------------------------------------------
/docs/src/dev/design_decisions.md:
--------------------------------------------------------------------------------
1 | # Design decisions
2 |
3 | This file documents motives some design decisions.
4 |
5 | ## Why did you implement a custom INI parser?
6 |
7 | I ended up writing my own INI parser for rust:
8 | [ini-roundtrip](https://github.com/VorpalBlade/ini-roundtrip). This had to
9 | be done because standard INI parsers don't support preserving the
10 | formatting. This is not acceptable when trying to minimise the diff. We
11 | want to not change the formatting applied by the program that writes the
12 | settings file. For example KDE writes `key=value` while PrusaSlicer writes
13 | `key = value`.
14 |
15 | It also does minimal parsing, meaning it can handle weird non-standard syntax
16 | such as `[Colors:Header][Inactive]` (a real example from `kdeglobals`).
17 |
18 | ## Why Rust?
19 |
20 | This code used to be written in Python, but each invocation of the command
21 | would take on the order of 95 ms. Per managed file. As I was getting up to
22 | around 20 managed INI files, this started to add up. The rewrite in Rust
23 | takes (on the same computer) 2 ms. This is a 46x speedup. On another (faster)
24 | computer I got a 63x speedup (54 ms vs 0.9 ms).
25 |
26 | ## Fast path
27 |
28 | The most time critical operation is to compute the new system state when chezmoi
29 | invokes us. This is the "fast path" in the code. All other operations such as
30 | `--add`, `--update` etc are less important from a performance perspective. This
31 | should be kept in mind when adding new features.
32 |
--------------------------------------------------------------------------------
/docs/src/dev/packaging.md:
--------------------------------------------------------------------------------
1 | # Packaging for Linux distros etc
2 |
3 | Do you want to package chezmoi_modify_manager in your favourite package manager?
4 | Awesome! Here are some helpful notes for you.
5 |
6 | * Please tell me about your package (file a github issue): I will link to any
7 | suitable package from the README (with a written caveat that I don't maintain
8 | them and thus cannot guarantee that they are up-to-date or safe to use). I
9 | maintain the AUR package myself as of writing this (thus there is no
10 | disclaimer there).
11 | * If you need some inspiration for how to build and install properly, you might
12 | want to take a look at my AUR
13 | [PKGBUILD](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=chezmoi_modify_manager).
14 | Arch Linux uses a relatively simply format (bash scripts), so it should be
15 | easy to decode and adapt.
16 |
17 | ## How to build
18 |
19 | * When building, please export the environment variable
20 | `CHEZMOI_MODIFY_MANAGER_BUILDER`. This will be reported in
21 | `chezmoi_modify_manager --doctor`, and can be very helpful in bug reports.
22 | If you don't set this, `--doctor` will report it as a warning (assuming that it
23 | is an unknown local build on someone's computer).
24 | * Please set it to a short and truthful value (such as "debian", "homebrew",
25 | "aur" etc) that identifies the package ecosystem. If your package build the
26 | latest and greatest git version, please add a suffix indicating so (e.g. "aur-git")
27 | * Especially don't claim to be "github-ci" or "github-release" as those are used
28 | for the official binary releases. That would be Not Cool!
29 | * Build a release build (`--release`). Rust is really quite slow in debug
30 | builds, but very speedy in release builds. The difference between debug and
31 | release builds is much larger than for C/C++.
32 | * Build with `--locked`: This ensures that the versions of dependencies are
33 | exactly the same as upstream. Otherwise, you might get newer supposedly
34 | compatible versions. Those may or may not work.
35 | * Build with the stable Rust toolchain: Nightly or beta is not needed and is
36 | just asking for potential issues.
37 | * You likely want to exclude the built-in self updater (that downloads from
38 | Github releases), as your package manager should be used instead. This is easy:
39 | pass `--no-default-features --features=keyring` to cargo build. This will also
40 | avoid vendoring C dependencies (in particular `libdbus`) and instead link them
41 | dynamically. If you don't want that, add the `vendored` feature as well
42 | (i.e. `--features=keyring,vendored`).
43 |
44 | And so we arrive at the final (reliable regardless of what environment the
45 | user might have) build command:
46 |
47 | ```bash
48 | export CHEZMOI_MODIFY_MANAGER_BUILDER=""
49 | export RUSTUP_TOOLCHAIN=stable
50 | export CARGO_TARGET_DIR=target
51 | cargo build --locked --release --no-default-features --features=keyring
52 | ```
53 |
54 | If you need to download dependencies first (as is best practise for some build
55 | systems) this gets split into two phases:
56 |
57 | ```bash
58 | # Download deps
59 | export RUSTUP_TOOLCHAIN=stable
60 | cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
61 |
62 | # Build
63 | export CHEZMOI_MODIFY_MANAGER_BUILDER=aur
64 | export RUSTUP_TOOLCHAIN=stable
65 | export CARGO_TARGET_DIR=target
66 | cargo build --frozen --release --no-default-features --features=keyring
67 | ```
68 |
69 | Note the change from `--locked` to `--frozen` in the second command here.
70 |
71 | ## What to install
72 |
73 | You should of course install the binary itself `chezmoi_modify_manager`. However,
74 | you might also want to install shell completion files relevant to your platform.
75 | These can be generated by executing the built binary with
76 | `--bpaf-complete-style-`. Here is the code from the AUR PKGBUILD
77 | to do the entire install:
78 |
79 | ```bash
80 | local _cmd_name="target/release/${pkgname}"
81 | install -Dm0755 -t "$pkgdir/usr/bin/" "$_cmd_name"
82 | mkdir -p "$pkgdir/usr/share/bash-completion/completions/"
83 | mkdir -p "$pkgdir/usr/share/zsh/site-functions/"
84 | mkdir -p "$pkgdir/usr/share/fish/vendor_completions.d/"
85 | "$_cmd_name" --bpaf-complete-style-zsh > "$pkgdir/usr/share/zsh/site-functions/_$pkgname"
86 | "$_cmd_name" --bpaf-complete-style-bash > "$pkgdir/usr/share/bash-completion/completions/$pkgname"
87 | "$_cmd_name" --bpaf-complete-style-fish > "$pkgdir/usr/share/fish/vendor_completions.d/${pkgname}.fish"
88 | # No support to install distro completions in elvish.
89 | # See https://github.com/elves/elvish/issues/1739
90 | #"$_cmd_name" --bpaf-complete-style-elvish
91 | ```
92 |
93 | For more info on supported shells, see the
94 | [bpaf documentation](https://docs.rs/bpaf/0.9.9/bpaf/_documentation/_2_howto/_1_completion/index.html),
95 | which is the library used by chezmoi_modify_manager to handle command line parsing.
96 |
--------------------------------------------------------------------------------
/docs/src/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | This chapter has examples of how to configure `chezmoi_modify_manager` for
4 | several different programs, as well as some general examples on more advanced
5 | topics.
6 |
--------------------------------------------------------------------------------
/docs/src/examples/advanced.md:
--------------------------------------------------------------------------------
1 | # Advanced examples: set, remove, add:*
2 |
3 | ## set/remove
4 |
5 | The `set` and `remove` directives are meant to be used together with templating
6 | in the modify scripts. For example, there might be a key binding in KDE you only
7 | want on computers were a specific program is installed. This could be accomplished
8 | by something like the following for `kglobalshortcutsrc`
9 |
10 | ```bash
11 | {{if lookPath "my-fancy-program"}}
12 | set "my-fancy-program.desktop" _k_friendly_name "My fancy program" separator="="
13 | set "my-fancy-program.desktop" _launch "Ctrl+Shift+Y,none,my-fancy-program" separator="="
14 | {{end}}
15 |
16 | # Make sure the lines aren't added back into the config for all systems
17 | # This should be outside the if statement
18 | add:remove "my-fancy-program.desktop" _k_friendly_name
19 | add:remove "my-fancy-program.desktop" _launch
20 | ```
21 |
22 | (In this case, note that you might need to manage the `.desktop` file with
23 | chezmoi as well. KDE normally creates these in `$HOME/.local/share/applications/`.)
24 |
25 | Similarly, `remove` can be used to remove entries, but be careful when readding
26 | the source files: If you blindly re-add the file on the computer where the lines
27 | are filtered out, they will get lost for all computers.
28 |
29 | ## add:remove/add:hide
30 |
31 | The directives `add:remove` and `add:hide` can be used to remove entries and
32 | hide values respectively when re-adding files from the system to the chezmoi
33 | source state.
34 |
35 | Some use cases for this are:
36 | * Use `add:hide` to prevent a password from being added back to the source state
37 | when you re-add a file with other changes. See the
38 | [konversationrc example](basics.md#konversationrc) for an example of this. By using
39 | `add:hide`, the line will still be present in the source file, but without its
40 | value. This ensures that the keyring transform is able to find it in the source
41 | state and do its work when checking out the file on a new system.
42 | * Use `add:remove` to prevent a line from entering the source state at all. This
43 | can be useful together with system specific configuration with the `set`
44 | directive:
45 | ```bash
46 | {{ if (.is_work) }}
47 | set "Default Applications" "x-scheme-handler/jetbrains" "jetbrains-toolbox.desktop" separator="="
48 | {{ end }}
49 | # Completely remove the line when adding back (regardless of which computer this is on).
50 | add:remove "Default Applications" "x-scheme-handler/jetbrains"
51 | ```
52 | This example for the `mimeapps.list` file will add a specific line only if
53 | `is_work` is true. The `add:remove` directive helps prevent that line from being
54 | added back to the source state by mistake (where it would be applied to other
55 | computers unintentionally).
56 |
57 | > **NOTE:** The `add:hide` and `add:remove` directives are processed as is
58 | without going through chezmoi's template engine when re-adding files. This means
59 | it won't matter if they are inside an if block, nor can you use template
60 | expressions in their arguments.
61 |
62 | > **NOTE:** `ignore` directives also result in an implicit `add:remove`. Again,
63 | it doesn't matter if it is inside an if block or not currently during adding of
64 | files, and any template expressions will not be expanded.
65 |
66 | Both of these limitations *may* change in the future.
67 |
--------------------------------------------------------------------------------
/docs/src/examples/basics.md:
--------------------------------------------------------------------------------
1 | # Examples: Ignores & transforms
2 |
3 | Here are some useful examples of flags for various settings files I have come across.
4 |
5 | ## KDE
6 |
7 | ### dolphinrc
8 | ```bash
9 | ignore section "MainWindow"
10 | ignore section "KPropertiesDialog"
11 | ignore "General" "ViewPropsTimestamp"
12 | ignore "Open-with settings" "History"
13 | ```
14 |
15 | ### kdeglobals
16 | ```bash
17 | ignore "General" "ColorSchemeHash"
18 | ignore "KFileDialog Settings" "Show hidden files"
19 | ignore "KFileDialog Settings" "Show Inline Previews"
20 | ignore section "DirSelect Dialog"
21 | ```
22 |
23 | ### kglobalshortcutsrc
24 | There are two issues in this configuration.
25 |
26 | First, ActivityManager switch-to-activity entries. There are multiple entries,
27 | making it a perfect fit for a regular expression. Note that this is not state
28 | per se. It does however seem to vary between computers, having different UUID
29 | values.
30 |
31 | Second, certain shortcut keys like flipping between two representations. A
32 | specialised transform has been added to handle this case. When this is needed
33 | you will see diffs like the following:
34 |
35 | ```diff
36 | -playmedia=none,,Play media playback
37 | +playmedia=none,none,Play media playback
38 | ```
39 |
40 | In summary, the following seems to work well:
41 |
42 | ```bash
43 | # The two regex below have overlapping matches, this is OK in this case so
44 | # turn off the warning for this file.
45 | no-warn-multiple-key-matches
46 |
47 | ignore regex "ActivityManager" "switch-to-activity-.*"
48 | transform regex ".*" ".*" kde-shortcut
49 | ```
50 |
51 | ### konversationrc
52 | Konversation has two relevant quirks:
53 |
54 | 1. It saves the password in the settings file (instead of using kwallet)
55 | 2. It resorts it alias list every time.
56 |
57 | ```bash
58 | ignore "ServerListDialog" "Size"
59 | transform "Aliases" "AliasList" unsorted-list separator=","
60 | transform "Identity 0" "Password" keyring service="konversation" user="konversation_id0"
61 | # Make sure the password isn't added back into the config file on re-add
62 | add:hide "Identity 0" "Password"
63 | ```
64 |
65 | To store the password for Identity 0 in your keyring of choice you can use:
66 |
67 | ```console
68 | $ chezmoi_modify_manager --keyring-set konversation konversation_id0
69 | [Enter your password at the prompt]
70 | ```
71 |
72 | ### kwinrc
73 | Similar to kglobalshortcutsrc there are computer specific UUIDs. In addition,
74 | the tiling configurations seem to be overwritten by KDE Plasma between computers.
75 |
76 | ```bash
77 | ignore regex "Desktops" "Id_.*"
78 | ignore regex "Tiling\\]\\[.*" ".*"
79 | ```
80 |
81 | ### plasmanotifyrc
82 |
83 | ```bash
84 | ignore section "DoNotDisturb"
85 | ```
86 |
87 | ### Trolltech.conf
88 |
89 | This is a Qt config, rather than a KDE config (strictly speaking) but since KDE
90 | uses Qt, it is sitll relevant.
91 |
92 | ```bash
93 | ignore "Qt" "filedialog"
94 | ```
95 |
96 | ## PrusaSlicer / SuperSlicer
97 |
98 | PrusaSlicer and the fork SuperSlicer also use INI style files:
99 |
100 | ### PrusaSlicer.ini / SuperSlicer.ini
101 |
102 | ```bash
103 | ignore "" "auto_toolbar_size"
104 | ignore "" "downloader_url_registered"
105 | ignore "" "freecad_path"
106 | ignore "" "last_output_path_removable"
107 | ignore "" "last_output_path"
108 | ignore "" "version_online_seen"
109 | ignore "" "version_online"
110 | ignore "" "version_system_info_sent"
111 | ignore "" "version"
112 | ignore "" "window_mainframe"
113 | ignore "font" "active_font"
114 | ignore "presets" "filament"
115 | ignore "presets" "print"
116 | ignore "presets" "printer"
117 | ignore "presets" "sla_material"
118 | ignore "presets" "sla_print"
119 | ignore regex "" "desktop_integration_.*"
120 | ignore regex "" "print_host_queue_dialog_.*"
121 | ignore regex "font:.*" ".*"
122 | ignore regex "presets" "filament_.*"
123 | ignore section "recent_projects"
124 | ignore section "recent"
125 | ```
126 |
127 | ### PrusaSlicerGcodeViewer.ini / SuperSlicerGcodeViewer.ini
128 |
129 | ```bash
130 | ignore "" "version"
131 | ignore "" "window_mainframe"
132 | ignore section "recent_projects"
133 | ```
134 |
135 | ### PrusaSlicer physical printer settings
136 |
137 | PrusaSlicer allows you to configure "physical printers" (with connection details
138 | to e.g. OctoPrint or PrusaLink). There will be one such config per physical printer
139 | you configured, located at `.config/PrusaSlicer/physical_printer/.ini`
140 |
141 | As these contain login details you probably want to put that in your keyring instead of
142 | in git. This works similarly to [konversation](#konversationrc).
143 |
144 | For example, you might use the following if you have a Prusa Mk3.9:
145 |
146 | ```bash
147 | transform "" "printhost_password" keyring service="chezmoi_modify_manager" user="prusa_mk39_password" separator=" = "
148 | transform "" "printhost_apikey" keyring service="chezmoi_modify_manager" user="prusa_mk39_apikey" separator=" = "
149 | add:hide "" "printhost_password"
150 | add:hide "" "printhost_apikey"
151 | ```
152 |
153 | To add your password and API key you would then use:
154 |
155 | ```console
156 | chezmoi_modify_manager --keyring-set chezmoi_modify_manager prusa_mk39_password
157 | Password: [Enter password]
158 | chezmoi_modify_manager --keyring-set chezmoi_modify_manager prusa_mk39_apikey
159 | Password: [Enter the API key]
160 | ```
161 |
162 | ## KeePassXC
163 |
164 | ### keepassxc.ini
165 |
166 | KeePassXC stores private and public keys for KeeShare in the config.
167 | You may not want to commit this to the repository.
168 |
169 | ```bash
170 | ignore "KeeShare" "Active"
171 | ignore "KeeShare" "Foreign"
172 | ignore "KeeShare" "Own"
173 | ```
174 |
175 | ## GTK-3.0/GTK-4.0
176 |
177 | ### settings.ini
178 |
179 | The file `~/.config/gtk-/settings.ini` has a DPI value in it that
180 | changes between computers. Thus, each of those setting files need the
181 | following:
182 |
183 | ```bash
184 | ignore "Settings" "gtk-xft-dpi"
185 | ```
186 |
--------------------------------------------------------------------------------
/docs/src/installation.md:
--------------------------------------------------------------------------------
1 | # Installation & upgrades
2 |
3 | It is assumed you already have [chezmoi](https://www.chezmoi.io/) set up
4 | and understand the basics of how it works.
5 |
6 | 1. To your root `.chezmoiignore` add: `**/*.src.ini`. These files should not be
7 | checked out into your target directory, but acts as the "source of truth" for
8 | the modify script.
9 | 2. Do *one* of these:
10 | * Recommended: Install `chezmoi_modify_manager` into your `$PATH`. This can be
11 | done by one of (in descending order of preference):
12 | * Using a distro package (if available for what you use)
13 | * Download the binary from the [releases on GitHub](https://github.com/VorpalBlade/chezmoi_modify_manager/releases) and install it somewhere into your `PATH`.
14 | * Install from [crates.io] using `cargo` (only do this if you know what you are doing).
15 | * Not recommended: Install `chezmoi_modify_manager` from the releases page
16 | into `/.utils/chezmoi_modify_manager--`
17 | where `` is typically `linux` and `` is typically `x86-64`. If
18 | you use another path, the template modify script that is added will be wrong.
19 | 3. Run `chezmoi_modify_manager --doctor` and make sure it reports no major issues
20 | with your installation.
21 |
22 |
23 | ## Tab completion
24 |
25 | Optionally you can install tab completion. The tab completion can be generated
26 | using the hidden command line flag `--bpaf-complete-style-SHELL_NAME`, (e.g.
27 | `--bpaf-complete-style-zsh`, `--bpaf-complete-style-bash`, ...). As this is
28 | handled internally by the command line parsing library we use, please see
29 | [their documentation](https://docs.rs/bpaf/0.9.12/bpaf/_documentation/_2_howto/_1_completion/index.html)
30 | for detailed instructions.
31 |
32 | > For the Arch Linux AUR package, the completions are already installed for you
33 | (except for elvish, which doesn't support a global install).
34 |
35 | ## Upgrading
36 |
37 | Depending on the installation method:
38 | * `chezmoi_modify_manager --upgrade`
39 | * With your package manager
40 | * For each OS and architecture, update the file `.utils/chezmoi_modify_manager--`.
41 | Note! For executables that you can run (i.e. the native one) you can still use `--upgrade`
42 | to do this.
43 |
44 | > **You** are in control of updates. Nothing will happen unless you pass
45 | `--upgrade`. Consider subscribing to be notified of new releases on the
46 | [GitHub repository]. This can be done via `Watch` -> `Custom` in the top
47 | right corner on that page after logging in to GitHub. Or just remember to
48 | check with `--upgrade` occasionally.
49 |
50 | [GitHub repository]: https://github.com/VorpalBlade/chezmoi_modify_manager
51 |
--------------------------------------------------------------------------------
/docs/src/limitations.md:
--------------------------------------------------------------------------------
1 | # Limitations
2 |
3 | Alas, no software (apart from perhaps a simple `Hello, world!`) is perfect.
4 | Here are some known limitations of `chezmoi_modify_manager`:
5 |
6 | * When a key exists in the `.src.ini` file but not in the target state it will
7 | be added to the end of the relevant section. This is not an issue as the
8 | program will usually just resort the file next time it writes out its
9 | settings.
10 | * `modify_` scripts bypass the check for "Did the file change in the target
11 | state" that chezmoi performs. This is essential for proper operation.
12 | However, it also means that you will not be asked about overwriting changes.
13 | Always look at `chezmoi diff` first! I do have some ideas on how to mitigate
14 | this in the future. See also [this chezmoi bug](https://github.com/twpayne/chezmoi/issues/2244)
15 | for a more detailed discussion on this.
16 |
--------------------------------------------------------------------------------
/docs/src/migration/README.md:
--------------------------------------------------------------------------------
1 | # Migration
2 |
3 | This chapter cover migrations between major versions of `chezmoi_modify_manager`.
4 |
--------------------------------------------------------------------------------
/docs/src/migration/migration_2.md:
--------------------------------------------------------------------------------
1 | # Migration from version 1.x to 2.x
2 |
3 | The new Rust code base has a superset of the features of the 1.x version, and it
4 | is also about 50x faster (in release builds, about 25x in debug builds).
5 |
6 | However, there is some work involved in migrating:
7 | * Different [installation](#installation) method.
8 | * The [syntax](#automatic-conversion-of-modify-scripts) has changed in the
9 | modify scripts.
10 | * Some changes to [transforms](#transforms) (in particular the
11 | [keyring](#keyring) transform has changed).
12 |
13 | In addition, the following differences are good to know about:
14 | * The separate shell script to help with adding files is gone, the functionality
15 | is now built into the main program (see `--help` output).
16 | * For binary installs from GitHub, you can now use a built-in self updater
17 | (`--upgrade`).
18 | * The regex syntax is different. Previously Python re module was used, now the
19 | [regex crate](https://docs.rs/regex/latest/regex/) for Rust is used. For most
20 | simple regular expressions there will be no relevant difference. However, some
21 | features (such as back references and look arounds) are not supported.
22 | * Platform support with precompiled binaries is somewhat limited (compared to
23 | everything that Python supports). This is due to what I can build & test and
24 | what GitHub CI supports. Pull requests that enable testing and building for
25 | more platforms are welcome however (if the *tests cannot be executed* on
26 | GitHub CI, I will not accept it however).
27 |
28 | ## Installation
29 |
30 | The methods of installation are different. No longer do you need (or should)
31 | add this repo as a submodule in your dotfiles repo. Remove that and instead
32 | see the [installation section](../installation.md) in the README.
33 |
34 | ## Modify scripts: Automatic conversion
35 |
36 | There is a [script](https://raw.githubusercontent.com/VorpalBlade/chezmoi_modify_manager/main/utils/conversion.sh)
37 | that can help if you have standard shaped files (i.e. as created by the old `chezmoi_ini_add`).
38 |
39 | However, it will not handle 100% of the conversion for transforms. The argument
40 | list format has changed, as have some of the argument names. See
41 | [below](#transforms) for more details.
42 |
43 | Also, special consideration needs to be taken for the [keyring](#keyring)
44 | transform.
45 |
46 | ## Modify scripts: Manual conversion
47 |
48 | The first line should now be used to invoke chezmoi_modify_manager. It should
49 | be one of:
50 |
51 | Use this if chezmoi_modify_manager is installed in PATH (recommended):
52 | ```bash
53 | #!/usr/bin/env chezmoi_modify_manager
54 | ```
55 |
56 | Use this if you keep chezmoi_modify_manager in your chezmoi source directory:
57 | ```bash
58 | #!{{ .chezmoi.sourceDir }}/.utils/chezmoi_modify_manager-{{ .chezmoi.os }}-{{ .chezmoi.arch }}
59 | ```
60 |
61 | In addition, the way to specify the source file has changed. The line to specify
62 | the source file would now typically look like:
63 |
64 | ```bash
65 | source "{{ .chezmoi.sourceDir }}/{{ .chezmoi.sourceFile | trimSuffix ".tmpl" | replace "modify_" "" }}.src.ini"
66 | ```
67 |
68 | Finally, you need to convert the actual ignores and transforms themselves:
69 |
70 | ```bash
71 |
72 | -ik key value -> ignore "key" "value"
73 |
74 | -is section-name -> ignore section "section-name"
75 |
76 | -ikr key-re value-re -> ignore regex "key-re" "value-re"
77 |
78 | # Note change of argument order for transforms, the transform name
79 | # now comes after the match.
80 | -tk transform_name key value '{ "arg1": "value1", "arg2": "value2" }'
81 | -> transform "key" "value" transform-name arg1="value1" arg2="value2"
82 |
83 | -tkr transform_name key value '{ "arg1": "value1", "arg2": "value2" }'
84 | -> transform regex "key" "value" transform-name arg1="value1" arg2="value2"
85 | ```
86 |
87 | ## Transforms
88 |
89 | Transform arguments have changed. Before they were a JSON object, now they
90 | are a series of `key="value"`.
91 |
92 | Apart from that, transform names have changed:
93 |
94 | * kde_shortcut -> kde-shortcut
95 | * unsorted_list -> unsorted-list
96 |
97 | Finally, the argument name has changed for keyring: `username` is now just `user`.
98 |
99 | ## Keyring
100 |
101 | As stated in the [previous section](#transforms), the argument names have
102 | changed.
103 |
104 | In addition, because the backend for talking to the platform secret store
105 | is different, there can be other incompatibilities. Known ones include:
106 |
107 | * On Linux, KDE KWallet is no longer supported. Only secret stores over
108 | DBus SecretService are supported. This means it will likely end up using
109 | GNOME's secret store (Seahorse) instead. See
110 | [the example for konversationrc](../examples/basics.md#konversationrc) for how to
111 | add the password, if you need to migrate.
112 |
113 | Other platforms are untested (since I don't have any of those), but I
114 | welcome any feedback to improve this documentation.
115 |
--------------------------------------------------------------------------------
/docs/src/migration/migration_3.md:
--------------------------------------------------------------------------------
1 | # Migration from version 2.x to 3.x
2 |
3 | ## Migrating from hook scripts
4 |
5 | In 2023 hook scripts were deprecated and then removed in early 2024 in version 3.0. They
6 | are now replaced by the `add:remove` and `add:hide` directives.
7 |
8 | For example, to make sure that a password isn't added back into the source
9 | state you might use something like this:
10 |
11 | ```bash
12 | transform "LoginSection" "Password" keyring service="myprogram" user="myuser"
13 | # Make sure the password isn't added back into the config file on re-add
14 | add:hide "LoginSection" "Password"
15 | ```
16 |
17 | This would pull the password from the OS keyring, but erase it when doing
18 | a re-add.
19 |
20 | The `add:remove` directive can be used to completely remove the entry instead.
21 | This can be useful together with `set` and system specific configuration:
22 |
23 | ```bash
24 | {{ if (.is_work) }}
25 | set "Default Applications" "x-scheme-handler/jetbrains" "jetbrains-toolbox.desktop" separator="="
26 | {{ end }}
27 | # Completely remove the line when adding back (regardless of which computer this is on).
28 | add:remove "Default Applications" "x-scheme-handler/jetbrains"
29 | ```
30 |
31 | This example for `mimeapps.list` would add an entry only when .is_work is true,
32 | but also make sure that the value isn't added back to the config file and thus
33 | prevents transferring it to other computers by mistake.
34 |
35 | > **NOTE:** The `add:hide` and `add:remove` directives are processed as is
36 | without going through chezmoi's template engine when re-adding files. This means
37 | it won't matter if they are inside an if block, nor can you use template
38 | expressions in their arguments.
39 |
40 | > **NOTE:** `ignore` directives also result in an implicit `add:remove`. Again,
41 | it doesn't matter if it is inside an if block or not currently during adding of
42 | files.
43 |
--------------------------------------------------------------------------------
/docs/src/source_specification.md:
--------------------------------------------------------------------------------
1 | # `source`: How chezmoi_modify_manager finds the data file
2 |
3 | ## Background
4 |
5 | chezmoi_modify_manager needs three inputs to work:
6 |
7 | * The modify script with directives (ignores, transforms, etc)
8 | * The state of the config file in your home directory
9 | * The source state of the config file.
10 |
11 | The first two are provided by chezmoi, no issues. But as far as chezmoi is
12 | concerned, the modify script itself is the source state. As such we need
13 | an alternative mechanism.
14 |
15 | ## Problem
16 |
17 | The obvious solution would be a path relative to the modify script. However,
18 | chezmoi always copies the modify script to a temporary directory before executing
19 | it, even if the modify script isn't templated. So this doesn't work. (It is however
20 | used internally in the test suite of chezmoi_modify_manager using
21 | `source auto-path`, which might be relevant if you are working on the
22 | chezmoi_modify_manager codebase itself.)
23 |
24 | Prior to chezmoi 2.46.1, we had to rely on making the modify script a template,
25 | as chezmoi didn't expose enough information to us (see
26 | [this chezmoi issue](https://github.com/twpayne/chezmoi/issues/2934) for more
27 | historical details). Basically we can make chezmoi find the source file for us
28 | using the following line:
29 |
30 | ```bash
31 | source "{{ .chezmoi.sourceDir }}/{{ .chezmoi.sourceFile | trimSuffix ".tmpl" | replace "modify_" "" }}.src.ini"
32 | ```
33 |
34 | Since chezmoi 2.46.1, chezmoi now provides us with two environment variables:
35 |
36 | * `CHEZMOI_SOURCE_DIR`: Path to the source directory root
37 | * `CHEZMOI_SOURCE_FILE`: Path to our modify script (relative the source directory root)
38 |
39 | With these two together we no longer need templating, and the following works:
40 |
41 | ```bash
42 | source auto
43 | ```
44 |
45 | ## What the code does
46 |
47 | Since chezmoi_modify_manager 3.1, it will auto-detect the version of chezmoi
48 | (based on executing `chezmoi --version`). This is used for:
49 |
50 | * The template that `--add` creates to either use the templated source string or
51 | the simpler `source auto`.
52 | * Interpreting the meaning of `--style=auto` (default value for style) to either
53 | create a templated modify script or a non-templated modify script.
54 |
55 | The main benefit of the simpler `source auto` is that if your modify script
56 | *doesn't need* to be a template for any other reason, it will speed up execution,
57 | as chezmoi no longer needs to run its template engine.
58 |
59 | ### Overriding auto detection
60 |
61 | Auto-detection has one downside though: What if you use multiple versions of
62 | chezmoi (such as an old version from Debian stable on some server but an up-to-date
63 | version on your personal computer). In that case you don't want to use the newer
64 | syntax for compatibility reasons.
65 |
66 | The workaround is to export an environment
67 | variable `CHEZMOI_MODIFY_MANAGER_ASSUME_CHEZMOI_VERSION` set to the oldest
68 | version that you use. E.g:
69 |
70 | ```bash
71 | CHEZMOI_MODIFY_MANAGER_ASSUME_CHEZMOI_VERSION=2.46.0
72 | ```
73 | This could be set in your `.bashrc`/`.zshrc`/`.profile` or similar file (the
74 | details of how to best set environment variables for a particular platform and
75 | shell is out of scope of this documentation).
76 |
--------------------------------------------------------------------------------
/docs/src/transforms.md:
--------------------------------------------------------------------------------
1 | # Transforms
2 |
3 | This is a list of supported transforms. These are used to support some special
4 | hard-to-handle cases. The general syntax is [documented elsewhere](configuration_files.md#transform),
5 | but in short:
6 |
7 | ```bash
8 | transform "section" "key" transform-name arg1="value" arg2="value" ...
9 | transform regex "section-regex.*" "key-regex.*" transform-name arg1="value" ...
10 | ```
11 |
12 | For example:
13 |
14 | ```bash
15 | transform "mysection" "mykey" unsorted-list separator=","
16 | ```
17 |
18 | Below is a list of supported transforms, but remember to check
19 | `chezmoi_modify_manager --help-transforms` for the most up-to-date list.
20 |
21 | ## unsorted-list
22 |
23 | Compare the value as an unsorted list.
24 | Useful because Konversation likes to reorder lists.
25 |
26 | Arguments:
27 |
28 | * `separator=","`: Separating character between list elements
29 |
30 | ## kde-shortcut
31 |
32 | Specialised transform to handle KDE changing certain global
33 | shortcuts back and forth between formats like:
34 |
35 | ```ini
36 | playmedia=none,,Play media playback
37 | playmedia=none,none,Play media playback
38 | ```
39 |
40 | No arguments.
41 |
42 | ## keyring
43 |
44 | Get the value for a key from the system keyring. Useful for passwords
45 | etc that you do not want in your dotfiles repo.
46 |
47 | Arguments:
48 |
49 | * `service="service-name"`: Service name to find entry in the keyring.
50 | * `user="user-name"`: Username to find entry in the keyring.
51 |
52 | You can add an entry to the secret store for your platform with:
53 |
54 | ```bash
55 | chezmoi_modify_manager --keyring-set service-name user-name
56 | ```
57 |
--------------------------------------------------------------------------------
/docs/src/troubleshooting.md:
--------------------------------------------------------------------------------
1 | # Troubleshooting
2 |
3 | The first step should be to run `chezmoi_modify_manager --doctor` and correct
4 | any issues reported. This will help identify some common issues:
5 |
6 | * chezmoi_modify_manager needs to be in `PATH`
7 | * `**/*.src.ini` needs to be ignored in the root `.chezmoiignore` file
8 | * Old chezmoi and/or using `CHEZMOI_MODIFY_MANAGER_ASSUME_CHEZMOI_VERSION`, see
9 | [this documentation](source_specification.md) for more details on when or
10 | when not to use this.
11 |
--------------------------------------------------------------------------------
/src/add.rs:
--------------------------------------------------------------------------------
1 | //! Support for adding files
2 |
3 | // Doc comments are used to generate --help, not to for rustdoc.
4 | #![allow(clippy::doc_markdown)]
5 |
6 | use crate::config;
7 | use crate::utils::Chezmoi;
8 | use crate::utils::ChezmoiVersion;
9 | use crate::utils::CHEZMOI_AUTO_SOURCE_VERSION;
10 | use anyhow::anyhow;
11 | use anyhow::Context;
12 | use camino::Utf8Path;
13 | use camino::Utf8PathBuf;
14 | use indoc::formatdoc;
15 | use ini_merge::filter::filter_ini;
16 | use std::fs::File;
17 | use std::io::Write;
18 | use strum::Display;
19 | use strum::EnumIter;
20 | use strum::EnumMessage;
21 | use strum::EnumString;
22 | use strum::IntoStaticStr;
23 |
24 | #[cfg(test)]
25 | mod tests;
26 |
27 | /// The style of calls to the executable
28 | #[derive(
29 | Debug, Eq, PartialEq, EnumString, Clone, Copy, EnumIter, EnumMessage, Display, IntoStaticStr,
30 | )]
31 | pub enum Style {
32 | /// Selects between path and path-tmpl based on detected chezmoi version
33 | #[strum(serialize = "auto")]
34 | Auto,
35 | /// chezmoi_modify_manager is searched for in PATH
36 | /// (modify_ script is not templated for best performance)
37 | #[strum(serialize = "path")]
38 | InPath,
39 | /// chezmoi_modify_manager is searched for in PATH
40 | /// (modify_ script is templated for your convenience)
41 | #[strum(serialize = "path-tmpl")]
42 | InPathTmpl,
43 | /// Program is in .utils of chezmoi source state
44 | /// (modify_ script is always templated)
45 | #[strum(serialize = "src")]
46 | InSrc,
47 | }
48 |
49 | /// The mode for adding
50 | #[derive(Debug, Clone, Copy)]
51 | pub(crate) enum Mode {
52 | Normal,
53 | Smart,
54 | }
55 |
56 | /// Template for newly created scripts
57 | const TEMPLATE: &str = indoc::indoc! {r#"
58 | #!(PATH)
59 |
60 | (SOURCE)
61 |
62 | # Add your ignores and transforms here
63 | #ignore section "my-section"
64 | #ignore "exact section name without brackets" "exact key name"
65 | #ignore regex "section.*" "key_prefix_.*"
66 | #transform "section" "key" transform_name read="the docs" for="more detail on transforms"
67 | "#};
68 |
69 | const SOURCE_NEW: &str = "source auto";
70 | const SOURCE_OLD: &str = indoc::indoc! {r#"
71 | # This is needed to figure out where the source file is on older Chezmoi versions.
72 | # See https://github.com/twpayne/chezmoi/issues/2934
73 | source "{{ .chezmoi.sourceDir }}/{{ .chezmoi.sourceFile | trimSuffix ".tmpl" | replace "modify_" "" }}.src.ini""#};
74 |
75 | /// Shebang line to use when command is in PATH
76 | const IN_PATH: &str = "/usr/bin/env chezmoi_modify_manager";
77 | /// Shebang line to use when command is in dotfile repo.
78 | const IN_SRC: &str =
79 | "{{ .chezmoi.sourceDir }}/.utils/chezmoi_modify_manager-{{ .chezmoi.os }}-{{ .chezmoi.arch }}";
80 |
81 | /// Format the template
82 | fn template(path: &str, version: &ChezmoiVersion) -> String {
83 | let result = TEMPLATE.replace("(PATH)", path);
84 | if version < &CHEZMOI_AUTO_SOURCE_VERSION {
85 | result.replace("(SOURCE)", SOURCE_OLD)
86 | } else {
87 | result.replace("(SOURCE)", SOURCE_NEW)
88 | }
89 | }
90 |
91 | /// Perform actual adding with a script
92 | fn add_with_script(
93 | chezmoi: &impl Chezmoi,
94 | src_path: Option,
95 | path: &Utf8Path,
96 | style: Style,
97 | status_out: &mut impl Write,
98 | ) -> anyhow::Result<()> {
99 | chezmoi.add(path)?;
100 | // If we don't already know the source path (newly added file), get it now
101 | let src_path = match src_path {
102 | Some(path) => path,
103 | None => chezmoi
104 | .source_path(path)?
105 | .context("chezmoi couldn't find added file")?,
106 | };
107 | let src_name = src_path.file_name().context("File has no filename")?;
108 | let data_path = src_path.with_file_name(format!("{src_name}.src.ini"));
109 | let script_path = match style {
110 | Style::Auto => panic!("Impossible: Auto should already have been mapped"),
111 | Style::InPath => src_path.with_file_name(format!("modify_{src_name}")),
112 | Style::InPathTmpl | Style::InSrc => {
113 | src_path.with_file_name(format!("modify_{src_name}.tmpl"))
114 | }
115 | };
116 | // Add while respecting filtering directives
117 | filtered_add(&data_path, &src_path, None, status_out)?;
118 |
119 | // Remove the temporary file that chezmoi added
120 | std::fs::remove_file(src_path)?;
121 |
122 | maybe_create_script(&script_path, style, status_out, &chezmoi.version()?)?;
123 | Ok(())
124 | }
125 |
126 | /// Add and handle filtering directives (add:remove, add:hide and ignore)
127 | ///
128 | /// * `target_path`: Path to write to
129 | /// * `src_path`: Path to actually read file data from
130 | /// * `script_path`: Path to modify script (if it exists)
131 | /// * `status_out`: Where to write status messages
132 | fn filtered_add(
133 | target_path: &Utf8Path,
134 | src_path: &Utf8Path,
135 | script_path: Option<&Utf8Path>,
136 | status_out: &mut impl Write,
137 | ) -> Result<(), anyhow::Error> {
138 | let file_contents =
139 | std::fs::read(src_path).context("Failed to load data from file we are adding")?;
140 |
141 | // If we are updating an existing script, run the contents through the filtering
142 | let mut file_contents = if let Some(sp) = script_path {
143 | _ = writeln!(
144 | status_out,
145 | "Has existing modify script, parsing to check for filtering..."
146 | );
147 | let config_data = std::fs::read_to_string(sp).context("Failed to load modify script")?;
148 | internal_filter(&config_data, &file_contents)?
149 | } else {
150 | file_contents
151 | };
152 |
153 | if !file_contents.ends_with(b"\n") {
154 | file_contents.push(b'\n');
155 | }
156 |
157 | _ = writeln!(status_out, "Writing out file data");
158 | std::fs::write(target_path, file_contents)?;
159 | Ok(())
160 | }
161 |
162 | /// Perform internal filtering using add:hide and add:remove (modern filtering)
163 | fn internal_filter(config_data: &str, contents: &[u8]) -> anyhow::Result> {
164 | let config = config::parse_for_add(config_data)?;
165 | let mut file = std::io::Cursor::new(contents);
166 | let result = filter_ini(&mut file, &config.mutations)?;
167 | let s: String = itertools::intersperse(result, "\n".into()).collect();
168 | Ok(s.as_bytes().into())
169 | }
170 |
171 | /// Create a modify script if one doesn't exist
172 | fn maybe_create_script(
173 | script_path: &Utf8Path,
174 | style: Style,
175 | status_out: &mut impl Write,
176 | version: &ChezmoiVersion,
177 | ) -> anyhow::Result<()> {
178 | if script_path.exists() {
179 | return Ok(());
180 | }
181 | let mut file = File::create(script_path)?;
182 | file.write_all(
183 | template(
184 | match style {
185 | Style::Auto => panic!("Impossible: Auto should already have been mapped"),
186 | Style::InPath => IN_PATH,
187 | Style::InPathTmpl => IN_PATH,
188 | Style::InSrc => IN_SRC,
189 | },
190 | version,
191 | )
192 | .as_bytes(),
193 | )?;
194 | _ = writeln!(status_out, "New script at {script_path}");
195 |
196 | Ok(())
197 | }
198 |
199 | /// Classifies the state of the file in chezmoi source state.
200 | #[derive(Debug)]
201 | enum ChezmoiState {
202 | NotInChezmoi,
203 | ExistingNormal {
204 | data_path: Utf8PathBuf,
205 | },
206 | ExistingManaged {
207 | script_path: Utf8PathBuf,
208 | data_path: Utf8PathBuf,
209 | },
210 | }
211 |
212 | /// Add a file
213 | pub(crate) fn add(
214 | chezmoi: &impl Chezmoi,
215 | mode: Mode,
216 | mut style: Style,
217 | path: &Utf8Path,
218 | status_out: &mut impl Write,
219 | ) -> anyhow::Result<()> {
220 | // Check for auto style
221 | if style == Style::Auto {
222 | style = if chezmoi.version()? < CHEZMOI_AUTO_SOURCE_VERSION {
223 | Style::InPathTmpl
224 | } else {
225 | Style::InPath
226 | }
227 | }
228 | // Start with a sanity check on the input file and environment
229 | sanity_check(path, style, chezmoi)?;
230 |
231 | // Let's check if the managed path exists
232 | let src_path = chezmoi.source_path(path)?;
233 |
234 | // Then lets classify the situation we are in
235 | let situation = classify_chezmoi_state(src_path)?;
236 |
237 | // Inform user of what we found
238 | match &situation {
239 | ChezmoiState::NotInChezmoi => {
240 | _ = writeln!(status_out, "State: New (to chezmoi) file");
241 | }
242 | ChezmoiState::ExistingNormal { .. } => {
243 | _ = writeln!(
244 | status_out,
245 | "State: Managed by chezmoi, but not a modify script."
246 | );
247 | }
248 | ChezmoiState::ExistingManaged { .. } => {
249 | _ = writeln!(
250 | status_out,
251 | "State: Managed by chezmoi and is a modify script."
252 | );
253 | }
254 | }
255 |
256 | // Finally decide on an action based on source state and the user selected mode.
257 | match (situation, mode) {
258 | (ChezmoiState::NotInChezmoi | ChezmoiState::ExistingNormal { .. }, Mode::Smart) => {
259 | _ = writeln!(
260 | status_out,
261 | "Action: Adding as plain chezmoi (since we are in smart mode)."
262 | );
263 | chezmoi.add(path)?;
264 | }
265 | (ChezmoiState::NotInChezmoi, Mode::Normal) => {
266 | _ = writeln!(
267 | status_out,
268 | "Action: Adding & setting up new modify_ script."
269 | );
270 | add_with_script(chezmoi, None, path, style, status_out)?;
271 | }
272 | (ChezmoiState::ExistingNormal { data_path }, Mode::Normal) => {
273 | _ = writeln!(
274 | status_out,
275 | "Action: Converting & setting up new modify_ script."
276 | );
277 | add_with_script(chezmoi, Some(data_path), path, style, status_out)?;
278 | }
279 | (
280 | ChezmoiState::ExistingManaged {
281 | script_path,
282 | data_path,
283 | },
284 | _,
285 | ) => {
286 | _ = writeln!(
287 | status_out,
288 | "Action: Updating existing .src.ini file for {script_path}."
289 | );
290 | filtered_add(
291 | data_path.as_ref(),
292 | path,
293 | Some(script_path.as_ref()),
294 | status_out,
295 | )?;
296 | }
297 | }
298 | Ok(())
299 | }
300 |
301 | /// Find out what the state of the file in chezmoi currently is.
302 | fn classify_chezmoi_state(src_path: Option) -> Result {
303 | let situation = match src_path {
304 | Some(existing_file) => {
305 | let src_filename = existing_file.file_name().context("No file name?")?;
306 | let is_mod_script = src_filename.starts_with("modify_");
307 | if is_mod_script {
308 | let src_dir = existing_file
309 | .parent()
310 | .context("Couldn't extract directory")?;
311 | let targeted_file = find_data_file(&existing_file, src_dir)?;
312 | ChezmoiState::ExistingManaged {
313 | script_path: existing_file,
314 | data_path: targeted_file,
315 | }
316 | } else {
317 | ChezmoiState::ExistingNormal {
318 | data_path: existing_file,
319 | }
320 | }
321 | }
322 | None => ChezmoiState::NotInChezmoi,
323 | };
324 | Ok(situation)
325 | }
326 |
327 | /// Perform preliminary environment sanity checks
328 | fn sanity_check(
329 | path: &Utf8Path,
330 | style: Style,
331 | chezmoi: &impl Chezmoi,
332 | ) -> Result<(), anyhow::Error> {
333 | if !path.is_file() {
334 | return Err(anyhow!("{} is not a regular file", path));
335 | }
336 | if Style::InPath == style && chezmoi.version()? < CHEZMOI_AUTO_SOURCE_VERSION {
337 | return Err(anyhow!(
338 | "To use \"--style path\" you need chezmoi {CHEZMOI_AUTO_SOURCE_VERSION} or newer"
339 | ));
340 | }
341 | match crate::doctor::hook_paths(chezmoi)?.as_slice() {
342 | [] => Ok(()),
343 | _ => {
344 | Err(anyhow!("Legacy hook script found, see chezmoi_modify_manager --doctor and please read https://github.com/VorpalBlade/chezmoi_modify_manager/blob/main/doc/migration_3.md"))
345 | }
346 | }
347 | }
348 |
349 | /// Given a modify script, find the associated .src.ini file
350 | fn find_data_file(
351 | modify_script: &Utf8Path,
352 | src_dir: &Utf8Path,
353 | ) -> Result {
354 | let data_file = modify_script
355 | .file_name()
356 | .context("Failed to get filename")?
357 | .strip_prefix("modify_")
358 | .and_then(|s| s.strip_suffix(".tmpl").or(Some(s)))
359 | .context("This should never happen")?
360 | .to_owned()
361 | + ".src.ini";
362 | let mut targeted_file: Utf8PathBuf = src_dir.into();
363 | targeted_file.push(data_file);
364 | if !targeted_file.exists() {
365 | let err_str = formatdoc!(
366 | r#"Found existing modify_ script but no associated .src.ini file (looked at {targeted_file}).
367 | Possible causes:
368 | * Did you change the "source" directive from the default value?
369 | * Remove the file by mistake?
370 |
371 | Either way: the automated adding code is not smart enough to handle this situation by itself."#
372 | );
373 | return Err(anyhow!(err_str));
374 | }
375 | Ok(targeted_file)
376 | }
377 |
--------------------------------------------------------------------------------
/src/add/tests.rs:
--------------------------------------------------------------------------------
1 | //! Two things are tested here:
2 | //! * New style filtering
3 | //! * Overall basic successful adding
4 | //!
5 | //! ## Full tests for adding
6 | //!
7 | //! There are several scenarios for adding files, all of these are tested
8 | //! separately.
9 | //!
10 | //! | Previous source state | Command | Expected state |
11 | //! | --------------------- | ------- | ------------------------------ |
12 | //! | Missing | Normal | chezmoi add and convert/create |
13 | //! | Missing | Smart | chezmoi add |
14 | //! | Existing (basic) | Normal | chezmoi add and convert/create |
15 | //! | Existing (basic) | Smart | chezmoi add |
16 | //! | Existing (modify_) | Normal | Update data file |
17 | //! | Existing (modify_) | Smart | Update data file |
18 |
19 | use super::internal_filter;
20 | use crate::utils::Chezmoi;
21 | use crate::utils::ChezmoiVersion;
22 | use crate::utils::CHEZMOI_AUTO_SOURCE_VERSION;
23 | use crate::Style;
24 | use camino::Utf8Path;
25 | use camino::Utf8PathBuf;
26 | use indoc::indoc;
27 | use pathdiff::diff_utf8_paths;
28 | use pretty_assertions::assert_eq;
29 | use tempfile::tempdir;
30 | use tempfile::TempDir;
31 |
32 | #[derive(Debug)]
33 | struct FilterTest {
34 | cfg: &'static str,
35 | input: &'static str,
36 | expected: &'static str,
37 | }
38 |
39 | const FILTER_TESTS: &[FilterTest] = &[
40 | FilterTest {
41 | cfg: indoc!(
42 | r#"
43 | source "{{ .chezmoi.sourceDir }}/{{ .chezmoi.sourceFile | trimSuffix ".tmpl" | replace "modify_" "" }}.src.ini"
44 |
45 | add:hide "a" "b"
46 | add:remove "a" "c"
47 | ignore "quux" "e"
48 | "#
49 | ),
50 | input: indoc!(
51 | r"
52 | [a]
53 | b=foo
54 | c=bar
55 | d=quux
56 |
57 | [quux]
58 | e=f
59 | g=h
60 | "
61 | ),
62 | expected: indoc!(
63 | r"
64 | [a]
65 | b=HIDDEN
66 | d=quux
67 |
68 | [quux]
69 | g=h
70 | "
71 | ),
72 | },
73 | FilterTest {
74 | cfg: indoc!(
75 | r#"
76 | source "{{ .chezmoi.sourceDir }}/{{ .chezmoi.sourceFile | trimSuffix ".tmpl" | replace "modify_" "" }}.src.ini"
77 |
78 | {{ if (chezmoi templating) }}
79 | set "a" "b" "c"
80 | {{ endif }}
81 | add:remove "a" "b"
82 | "#
83 | ),
84 | input: indoc!(
85 | r"
86 | [a]
87 | b=c
88 | d=e
89 | "
90 | ),
91 | expected: indoc!(
92 | r"
93 | [a]
94 | d=e
95 | "
96 | ),
97 | },
98 | ];
99 |
100 | #[test]
101 | fn check_filtering() {
102 | for test_case in FILTER_TESTS {
103 | let result = internal_filter(test_case.cfg, test_case.input.as_bytes());
104 | dbg!(&result);
105 | let result = result.unwrap();
106 | assert_eq!(
107 | String::from_utf8(result).unwrap().trim_end(),
108 | test_case.expected.trim_end()
109 | );
110 | }
111 | }
112 |
113 | /// Very simple dummy chezmoi implementation
114 | #[derive(Debug)]
115 | struct DummyChezmoi {
116 | // tmp_dir is a RAII guard
117 | #[allow(dead_code)]
118 | tmp_dir: TempDir,
119 | input_dir: Utf8PathBuf,
120 | src_dir: Utf8PathBuf,
121 | dummy_file: Utf8PathBuf,
122 | version: ChezmoiVersion,
123 | }
124 |
125 | impl DummyChezmoi {
126 | fn new() -> Self {
127 | let tmp_dir = tempdir().unwrap();
128 | let input_dir: Utf8PathBuf = tmp_dir.path().join("input").try_into().unwrap();
129 | let src_dir: Utf8PathBuf = tmp_dir.path().join("source").try_into().unwrap();
130 | let dummy_file: Utf8PathBuf = input_dir.join("dummy_file");
131 | std::fs::create_dir(input_dir.as_path()).unwrap();
132 | std::fs::create_dir(src_dir.as_path()).unwrap();
133 | std::fs::write(dummy_file.as_path(), "[a]\nb=c").unwrap();
134 | Self {
135 | tmp_dir,
136 | input_dir,
137 | src_dir,
138 | dummy_file,
139 | version: CHEZMOI_AUTO_SOURCE_VERSION,
140 | }
141 | }
142 |
143 | fn basic_source_path(&self, path: &Utf8Path) -> Utf8PathBuf {
144 | let rel_path = diff_utf8_paths(path, self.input_dir.as_path()).unwrap();
145 | self.src_dir.join(rel_path)
146 | }
147 |
148 | fn make_script_path(&self, file_name: &str, style: Style) -> Utf8PathBuf {
149 | match style {
150 | Style::Auto => todo!("Not implemented in test yet"),
151 | Style::InPath => self.src_dir.join(format!("modify_{file_name}")),
152 | Style::InPathTmpl | Style::InSrc => {
153 | self.src_dir.join(format!("modify_{file_name}.tmpl"))
154 | }
155 | }
156 | }
157 | }
158 |
159 | impl Chezmoi for DummyChezmoi {
160 | fn source_path(&self, path: &Utf8Path) -> anyhow::Result