├── .chglog ├── CHANGELOG.tpl.md └── config.yml ├── .ci └── upd-pkgbuild.sh ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── support_request.md ├── dependabot.yml └── workflows │ ├── build.yml │ ├── lint.yml │ ├── mdbook-release.yml │ ├── mdbook.yml │ └── release.yml ├── .gitignore ├── .yamlfmt ├── Cargo.lock ├── Cargo.toml ├── Cross.toml ├── LICENSE.md ├── README.md ├── about.hbs ├── about.toml ├── clippy.toml ├── deny.toml ├── docs ├── .gitignore ├── book.toml └── src │ ├── README.md │ ├── SUMMARY.md │ ├── actions.md │ ├── algorithms.md │ ├── basic_usage.md │ ├── configuration_files.md │ ├── dev │ ├── design_decisions.md │ └── packaging.md │ ├── examples │ ├── README.md │ ├── advanced.md │ └── basics.md │ ├── installation.md │ ├── limitations.md │ ├── migration │ ├── README.md │ ├── migration_2.md │ └── migration_3.md │ ├── source_specification.md │ ├── transforms.md │ └── troubleshooting.md ├── src ├── add.rs ├── add │ └── tests.rs ├── arguments.rs ├── config.rs ├── config │ └── parser.rs ├── doctor.rs ├── lib.rs ├── main.rs ├── transforms.rs ├── update.rs └── utils.rs ├── tests ├── data │ ├── .gitattributes │ ├── basics.expected.ini │ ├── basics.src.ini │ ├── basics.sys.ini │ ├── basics.tmpl │ ├── key_only.expected.ini │ ├── key_only.src.ini │ ├── key_only.sys.ini │ ├── key_only.tmpl │ ├── regex.expected.ini │ ├── regex.src.ini │ ├── regex.sys.ini │ ├── regex.tmpl │ ├── set2.expected.ini │ ├── set2.src.ini │ ├── set2.sys.ini │ ├── set2.tmpl │ ├── set_remove.expected.ini │ ├── set_remove.src.ini │ ├── set_remove.sys.ini │ ├── set_remove.tmpl │ ├── transform-remove.expected.ini │ ├── transform-remove.src.ini │ ├── transform-remove.sys.ini │ ├── transform-remove.tmpl │ ├── transform.expected.ini │ ├── transform.src.ini │ ├── transform.sys.ini │ └── transform.tmpl └── integration │ ├── data_driven.rs │ └── main.rs └── utils └── conversion.sh /.chglog/CHANGELOG.tpl.md: -------------------------------------------------------------------------------- 1 | {{ range .Versions }} 2 | 3 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }}) 4 | 5 | {{ range .CommitGroups -}} 6 | ### {{ .Title }} 7 | 8 | {{ range .Commits -}} 9 | * {{ .Subject }} 10 | {{ end }} 11 | {{ end -}} 12 | 13 | {{- if .RevertCommits -}} 14 | ### Reverts 15 | 16 | {{ range .RevertCommits -}} 17 | * {{ .Revert.Header }} 18 | {{ end }} 19 | {{ end -}} 20 | 21 | {{- if .NoteGroups -}} 22 | {{ range .NoteGroups -}} 23 | ### {{ .Title }} 24 | 25 | {{ range .Notes }} 26 | {{ .Body }} 27 | {{ end }} 28 | {{ end -}} 29 | {{ end -}} 30 | {{ end -}} -------------------------------------------------------------------------------- /.chglog/config.yml: -------------------------------------------------------------------------------- 1 | style: github 2 | template: CHANGELOG.tpl.md 3 | info: 4 | title: CHANGELOG 5 | repository_url: https://github.com/VorpalBlade/chezmoi_modify_manager 6 | options: 7 | commits: 8 | filters: 9 | Type: 10 | - docs 11 | - feat 12 | - fix 13 | - perf 14 | - refactor 15 | - tests 16 | commit_groups: 17 | sort_by: Custom 18 | title_order: 19 | - feat 20 | - fix 21 | - perf 22 | - docs 23 | - refactor 24 | - tests 25 | title_maps: 26 | docs: Documentation 27 | feat: Features 28 | fix: Bug Fixes 29 | perf: Performance Improvements 30 | refactor: Code Refactoring 31 | tests: Unit & integration tests 32 | header: 33 | pattern: "^(\\w*)\\:\\s(.*)$" 34 | pattern_maps: 35 | - Type 36 | - Subject 37 | notes: 38 | keywords: 39 | - BREAKING CHANGE 40 | -------------------------------------------------------------------------------- /.ci/upd-pkgbuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | sed -i '/^_pkgver/s/=.*$/='${1#refs/tags/v}'/' "$2" 4 | sed -i '/^pkgrel/s/=.*$/=1/' "$2" 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report for an observed bug 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the bug 11 | A clear and concise description of what the bug is. 12 | 13 | ## To Reproduce 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | ## Expected behavior 21 | A clear and concise description of what you expected to happen. 22 | 23 | ## Actual behavior 24 | A clear and concise description of what actually happens instead. 25 | 26 | ## Environment (please complete the following information): 27 | - chezmoi version (or commit if built from git): [e.g. 1.2.3] 28 | - chezmoi_modify_manager version (or commit if built from git) ID: [e.g. v2.0] 29 | 30 | ## Output of `chezmoi_modify_manager --doctor` 31 | 32 |
33 | 34 | ```console 35 | $ chezmoi_modify_manager --doctor 36 | ``` 37 | 38 |
39 | 40 | ## Additional context 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Is your feature request related to a problem? Please describe. 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | ## Describe the solution you'd like 14 | A clear and concise description of what you want to happen. 15 | 16 | ## Describe alternatives you've considered 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | ## Additional context 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support request 3 | about: Need help? Confused about something? 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## What exactly are you trying to do? 11 | 12 | Describe in as much detail as possible. 13 | 14 | ## What have you tried so far? 15 | 16 | Describe what you have tried so far. 17 | 18 | ## Where else have you checked for solutions? 19 | 20 | * [ ] I have read the [README](https://github.com/VorpalBlade/chezmoi_modify_manager/blob/main/README.md) 21 | * [ ] I have read the output of `chezmoi_modify_manager --help-syntax` and 22 | `chezmoi_modify_manager --help-transforms` (if relevant to my question). 23 | * [ ] I have checked that `chezmoi_modify_manager --doctor` doesn't list any 24 | issues that need to be resolved (or I need help resolving those issues). 25 | * [ ] Other, please give details. 26 | 27 | ## Output of any commands you've tried 28 | 29 | E.g.: 30 | 31 | ```console 32 | $ chezmoi --verbose $COMMAND 33 | $ chezmoi_modify_manager $COMMAND 34 | ``` 35 | 36 | ## Environment (please complete the following information): 37 | - chezmoi version (or commit if built from git): [e.g. 1.2.3] 38 | - chezmoi_modify_manager version (or commit if built from git) ID: [e.g. v2.0] 39 | 40 | ## Output of `chezmoi_modify_manager --doctor` 41 | 42 |
43 | 44 | ```console 45 | $ chezmoi_modify_manager --doctor 46 | ``` 47 | 48 |
49 | 50 | ## Additional context 51 | Add any other context about the problem here. 52 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | push: 8 | branches: ["main"] 9 | pull_request: 10 | branches: ["main"] 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 14 | cancel-in-progress: true 15 | 16 | env: 17 | CARGO_INCREMENTAL: 0 18 | CARGO_PROFILE_DEV_DEBUG: 0 19 | CARGO_TERM_COLOR: always 20 | CHEZMOI_MODIFY_MANAGER_BUILDER: github-ci 21 | RUST_BACKTRACE: 1 22 | RUSTFLAGS: "-D warnings" 23 | RUSTUP_MAX_RETRIES: 10 24 | 25 | jobs: 26 | modern-default: 27 | # Test modern compilers on standard platforms on Linux. 28 | name: "Test: ${{ matrix.target }}, Rust ${{ matrix.rust }} (default configuration)" 29 | runs-on: ubuntu-latest 30 | env: 31 | CARGO_PROFILE_DEV_DEBUG: 1 32 | CARGO_PROFILE_DEV_SPLIT_DEBUGINFO: ${{ matrix.debug_info }} 33 | RUSTFLAGS: -D warnings -Clink-arg=-Wl,--compress-debug-sections=zlib 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | target: 38 | - aarch64-unknown-linux-gnu 39 | - aarch64-unknown-linux-musl 40 | - armv7-unknown-linux-gnueabihf 41 | - armv7-unknown-linux-musleabihf 42 | - i686-unknown-linux-gnu 43 | - i686-unknown-linux-musl 44 | # Unfortunately, As of 1.76 nightly rust uses calls not supported by 45 | # wine, so we can't run the cross-tests any more 46 | #- x86_64-pc-windows-gnu 47 | - x86_64-unknown-linux-gnu 48 | - x86_64-unknown-linux-musl 49 | # Some lower priority targets too (not currently built as releases) 50 | - powerpc-unknown-linux-gnu 51 | - powerpc64-unknown-linux-gnu 52 | rust: 53 | - stable 54 | - nightly 55 | debug_info: 56 | - packed 57 | include: 58 | # RISCV doesn't work with split debug info (see rust-lang/rust#110224) 59 | - target: riscv64gc-unknown-linux-gnu 60 | rust: stable 61 | debug_info: off 62 | - target: riscv64gc-unknown-linux-gnu 63 | rust: nightly 64 | debug_info: off 65 | steps: 66 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 67 | with: 68 | persist-credentials: false 69 | - name: Environment info 70 | run: | 71 | echo "rustup --version:"; rustup --version 72 | echo "rustup show:"; rustup show 73 | - name: Install Rust 74 | run: rustup install --profile minimal ${{ matrix.rust }} && rustup default ${{ matrix.rust }} 75 | - name: Install cross 76 | uses: taiki-e/install-action@6c6479b49816fcc0975a31af977bdc1f847c2920 # v2.52.1 77 | with: 78 | tool: cross@0.2.5 79 | - name: Cache builds 80 | uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 81 | with: 82 | key: ${{ matrix.target }}-${{ matrix.rust }} 83 | - name: Cross compile 84 | run: cross test --no-run --locked --target ${{ matrix.target }} --verbose 85 | - name: Cross test 86 | run: cross test --locked --target ${{ matrix.target }} --verbose 87 | - name: Compress binary 88 | if: matrix.rust == 'stable' && matrix.target != 'x86_64-pc-windows-gnu' 89 | run: | 90 | mkdir chezmoi_modify_manager 91 | cp target/${{ matrix.target }}/debug/chezmoi_modify_manager chezmoi_modify_manager/ 92 | if [[ -f target/${{ matrix.target }}/debug/chezmoi_modify_manager.dwp ]]; then 93 | # No split debug info for RISCV 94 | cp target/${{ matrix.target }}/debug/chezmoi_modify_manager.dwp chezmoi_modify_manager/ 95 | fi 96 | tar cf chezmoi_modify_manager.tar chezmoi_modify_manager 97 | zstd -T0 -6 chezmoi_modify_manager.tar 98 | - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 99 | if: matrix.rust == 'stable' && matrix.target != 'x86_64-pc-windows-gnu' 100 | with: 101 | name: chezmoi_modify_manager_${{ matrix.target }}.zst 102 | path: chezmoi_modify_manager.tar.zst 103 | retention-days: 7 104 | - name: Clean up temporary items 105 | run: | 106 | rm -rf chezmoi_modify_manager chezmoi_modify_manager.tar chezmoi_modify_manager.tar.zst 107 | 108 | configurations: 109 | # Test non-standard configurations, MSRV and Rust versions 110 | name: "Test: \"${{ matrix.features }}\" (Linux), Rust ${{ matrix.rust }}" 111 | runs-on: ubuntu-latest 112 | strategy: 113 | fail-fast: false 114 | matrix: 115 | features: 116 | - --no-default-features 117 | - --no-default-features --features=updater-tls-rusttls 118 | - --no-default-features --features=keyring 119 | rust: 120 | - 1.82.0 121 | - stable 122 | steps: 123 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 124 | with: 125 | persist-credentials: false 126 | - name: Install Rust 127 | run: rustup install --profile minimal ${{ matrix.rust }} && rustup default ${{ matrix.rust }} 128 | - name: Install libdbus 129 | if: matrix.features == '--no-default-features --features=keyring' 130 | run: sudo apt-get install -y libdbus-1-dev pkg-config 131 | - name: Cache builds 132 | uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 133 | with: 134 | save-if: ${{ matrix.features == '--no-default-features --features=updater-tls-native-vendored' }} 135 | - name: Compile 136 | run: cargo test --locked ${{ matrix.features }} --verbose --no-run 137 | - name: Test 138 | run: cargo test --locked ${{ matrix.features }} --verbose 139 | - name: Test updater 140 | if: matrix.features == '--no-default-features --features=updater-tls-rusttls' 141 | run: | 142 | echo n | cargo run --locked ${{ matrix.features }} -- --upgrade 143 | - name: Test no updater exit code 144 | if: matrix.features != '--no-default-features --features=updater-tls-rusttls' 145 | run: | 146 | # Because GitHub Actions exits on failure we need to do this 147 | bash -c "cargo run --locked ${{ matrix.features }} -- --upgrade; if [[ \$? -eq 0 ]]; then exit 1; fi" 148 | 149 | exotic-os: 150 | # Test native builds on non-Linux platforms 151 | name: "Test: ${{ matrix.target }} on ${{ matrix.os }} with ${{ matrix.rust }}" 152 | runs-on: ${{ matrix.os }} 153 | env: 154 | CARGO_PROFILE_DEV_DEBUG: 1 155 | CARGO_PROFILE_DEV_SPLIT_DEBUGINFO: "packed" 156 | strategy: 157 | fail-fast: false 158 | matrix: 159 | include: 160 | - target: x86_64-pc-windows-msvc 161 | os: windows-latest 162 | suffix: .exe 163 | debug-suffix: .pdb 164 | rust: stable 165 | - target: x86_64-pc-windows-msvc 166 | os: windows-latest 167 | suffix: .exe 168 | debug-suffix: .pdb 169 | rust: nightly 170 | - target: x86_64-pc-windows-gnu 171 | os: windows-latest 172 | suffix: .exe 173 | debug-suffix: .pdb 174 | rust: stable 175 | - target: x86_64-pc-windows-gnu 176 | os: windows-latest 177 | suffix: .exe 178 | debug-suffix: .pdb 179 | rust: nightly 180 | - target: x86_64-apple-darwin 181 | os: macos-latest 182 | suffix: 183 | debug-suffix: .dSYM 184 | rust: stable 185 | - target: aarch64-apple-darwin 186 | os: macos-latest 187 | suffix: 188 | debug-suffix: .dSYM 189 | rust: stable 190 | - target: x86_64-apple-darwin 191 | os: macos-latest 192 | suffix: 193 | debug-suffix: .dSYM 194 | rust: nightly 195 | steps: 196 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 197 | with: 198 | persist-credentials: false 199 | - name: Environment info 200 | run: | 201 | echo "rustup --version:"; rustup --version 202 | echo "rustup show:"; rustup show 203 | - name: Install Rust 204 | run: rustup install --no-self-update --profile minimal ${{ matrix.rust }} && rustup default ${{ matrix.rust }} && rustup target add ${{ matrix.target }} 205 | - name: Cache builds 206 | uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 207 | with: 208 | key: ${{ matrix.os }}-${{ matrix.target }}-${{ matrix.rust }} 209 | - name: Compile 210 | run: cargo test --no-run --locked --target ${{ matrix.target }} --verbose 211 | - name: Test 212 | run: cargo test --locked --target ${{ matrix.target }} --verbose 213 | - name: Compress binary 214 | if: matrix.rust == 'stable' && matrix.target != 'x86_64-pc-windows-gnu' 215 | run: | 216 | mkdir chezmoi_modify_manager 217 | cp target/${{ matrix.target }}/debug/chezmoi_modify_manager${{ matrix.suffix }} chezmoi_modify_manager/ 218 | cp -r target/${{ matrix.target }}/debug/chezmoi_modify_manager${{ matrix.debug-suffix }} chezmoi_modify_manager/ 219 | tar cf chezmoi_modify_manager.tar chezmoi_modify_manager 220 | zstd -T0 -6 chezmoi_modify_manager.tar 221 | - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 222 | if: matrix.rust == 'stable' && matrix.target != 'x86_64-pc-windows-gnu' 223 | with: 224 | name: chezmoi_modify_manager_${{ matrix.target }}.zst 225 | path: chezmoi_modify_manager.tar.zst 226 | retention-days: 7 227 | - name: Clean up temporary items 228 | if: matrix.os != 'windows-latest' 229 | # I cannot get this cleanup to work on Windows. The cache just gets to include uneeded things 230 | run: |- 231 | rm -rf chezmoi_modify_manager chezmoi_modify_manager.tar chezmoi_modify_manager.tar.zst 232 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: ["main"] 9 | schedule: 10 | - cron: '41 20 * * 1' 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | rust-clippy-analyze: 18 | name: Run rust-clippy analyzing 19 | runs-on: ubuntu-latest 20 | permissions: 21 | contents: read 22 | security-events: write 23 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 27 | with: 28 | persist-credentials: false 29 | 30 | - name: Setup Rust 31 | run: rustup update stable && rustup default stable && rustup component add clippy 32 | 33 | - name: Get cargo-binstall 34 | run: | 35 | curl -L https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -zxf - && mv cargo-binstall $HOME/.cargo/bin/ 36 | 37 | - name: Install required cargo 38 | run: cargo binstall --no-confirm --no-symlinks clippy-sarif sarif-fmt 39 | 40 | - name: Cache builds 41 | uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8 42 | 43 | - name: Run rust-clippy 44 | run: cargo clippy --all-features --all-targets --message-format=json -- -D warnings | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt 45 | continue-on-error: true 46 | 47 | - name: Upload analysis results to GitHub 48 | uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 49 | with: 50 | sarif_file: rust-clippy-results.sarif 51 | wait-for-processing: true 52 | 53 | - name: Report status 54 | run: cargo clippy --all-features --all-targets -- -D warnings 55 | 56 | rustfmt: 57 | name: Rustfmt 58 | runs-on: ubuntu-latest 59 | permissions: 60 | contents: read 61 | steps: 62 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 63 | with: 64 | persist-credentials: false 65 | - name: Install Rust 66 | run: rustup install --profile minimal stable && rustup default stable && rustup component add rustfmt 67 | - run: cargo fmt -- --check 68 | 69 | cargo-deny: 70 | runs-on: ubuntu-22.04 71 | permissions: 72 | contents: read 73 | steps: 74 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 75 | with: 76 | persist-credentials: false 77 | 78 | - name: Setup Rust 79 | run: rustup update stable && rustup default stable && rustup component add clippy 80 | 81 | - name: Get cargo-binstall 82 | run: | 83 | curl -L https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -zxf - && mv cargo-binstall $HOME/.cargo/bin/ 84 | 85 | - name: Install required cargo addons 86 | run: cargo binstall --no-confirm --no-symlinks cargo-deny 87 | 88 | - run: cargo deny check 89 | 90 | cargo-about: 91 | runs-on: ubuntu-22.04 92 | permissions: 93 | contents: read 94 | steps: 95 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 96 | with: 97 | persist-credentials: false 98 | 99 | - name: Setup Rust 100 | run: rustup update stable && rustup default stable && rustup component add clippy 101 | 102 | - name: Get cargo-binstall 103 | run: | 104 | curl -L https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -zxf - && mv cargo-binstall $HOME/.cargo/bin/ 105 | 106 | - name: Install required cargo addons 107 | run: cargo binstall --no-confirm --no-symlinks cargo-about 108 | 109 | - run: mkdir target && cargo about generate about.hbs > target/license.html 110 | -------------------------------------------------------------------------------- /.github/workflows/mdbook-release.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Mdbook 2 | 3 | on: 4 | push: 5 | tags: 6 | - v[0-9]+.* 7 | workflow_dispatch: 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | deploy: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: write # To push a branch 18 | pages: write # To push to a GitHub Pages site 19 | id-token: write # To update the deployment status 20 | steps: 21 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 22 | with: 23 | persist-credentials: false 24 | fetch-depth: 0 25 | - name: Install latest mdbook 26 | run: | 27 | tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name') 28 | url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz" 29 | mkdir mdbook 30 | curl -sSL $url | tar -xz --directory=./mdbook 31 | echo `pwd`/mdbook >> $GITHUB_PATH 32 | - name: Build Book 33 | run: | 34 | # This assumes your book is in the root of your repository. 35 | # Just add a `cd` here if you need to change to another directory. 36 | cd docs && mdbook build 37 | - name: Setup Pages 38 | uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 39 | - name: Upload artifact 40 | uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 41 | with: 42 | # Upload book directory 43 | path: 'docs/book' 44 | - name: Deploy to GitHub Pages 45 | id: deployment 46 | uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 47 | -------------------------------------------------------------------------------- /.github/workflows/mdbook.yml: -------------------------------------------------------------------------------- 1 | name: Mdbook 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | test: 15 | name: Test 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | steps: 20 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 21 | with: 22 | persist-credentials: false 23 | - name: Install Rust 24 | run: | 25 | rustup set profile minimal 26 | rustup toolchain install stable 27 | rustup default stable 28 | - name: Install latest mdbook 29 | run: | 30 | tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name') 31 | url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz" 32 | mkdir bin 33 | curl -sSL $url | tar -xz --directory=bin 34 | echo "$(pwd)/bin" >> $GITHUB_PATH 35 | - name: Run tests 36 | run: cd docs && mdbook test 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v[0-9]+.* 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 10 | cancel-in-progress: true 11 | 12 | env: 13 | CARGO_INCREMENTAL: 0 14 | CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 1 15 | CARGO_PROFILE_RELEASE_LTO: true 16 | CARGO_PROFILE_RELEASE_OPT_LEVEL: "s" 17 | CARGO_TERM_COLOR: always 18 | CHEZMOI_MODIFY_MANAGER_BUILDER: github-release 19 | RUST_BACKTRACE: 1 20 | RUSTFLAGS: "-D warnings" 21 | RUSTUP_MAX_RETRIES: 10 22 | 23 | jobs: 24 | create-release: 25 | runs-on: ubuntu-latest 26 | permissions: 27 | contents: write 28 | id-token: write 29 | steps: 30 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 31 | with: 32 | persist-credentials: false 33 | - uses: taiki-e/create-gh-release-action@26b80501670402f1999aff4b934e1574ef2d3705 # v1.9.1 34 | with: 35 | draft: true 36 | # (Required) GitHub token for creating GitHub Releases. 37 | token: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | cargo-about: 40 | needs: create-release 41 | runs-on: ubuntu-latest 42 | permissions: 43 | contents: write 44 | id-token: write 45 | steps: 46 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 47 | with: 48 | persist-credentials: false 49 | - name: Setup Rust 50 | run: rustup update stable && rustup default stable && rustup component add clippy 51 | - name: Get cargo-binstall 52 | run: | 53 | curl -L https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -zxf - && mv cargo-binstall $HOME/.cargo/bin/ 54 | - name: Install required cargo addons 55 | run: cargo binstall --no-confirm --no-symlinks cargo-about 56 | - run: mkdir target && cargo about generate about.hbs > target/licenses.html 57 | - name: Upload licenses.html 58 | run: GITHUB_TOKEN="${token}" gh release upload "${tag#refs/tags/}" target/licenses.html 59 | env: 60 | token: ${{ secrets.GITHUB_TOKEN }} 61 | tag: ${{ github.ref }} 62 | 63 | upload-assets: 64 | needs: create-release 65 | permissions: 66 | attestations: write 67 | contents: write 68 | id-token: write # Needed for attestations 69 | strategy: 70 | matrix: 71 | include: 72 | - target: aarch64-unknown-linux-gnu 73 | os: ubuntu-latest 74 | rustflags: -Clink-arg=-Wl,--compress-debug-sections=zlib 75 | - target: aarch64-unknown-linux-musl 76 | os: ubuntu-latest 77 | rustflags: -Clink-arg=-Wl,--compress-debug-sections=zlib 78 | - target: armv7-unknown-linux-gnueabihf 79 | os: ubuntu-latest 80 | rustflags: -Clink-arg=-Wl,--compress-debug-sections=zlib 81 | - target: armv7-unknown-linux-musleabihf 82 | os: ubuntu-latest 83 | rustflags: -Clink-arg=-Wl,--compress-debug-sections=zlib 84 | - target: i686-unknown-linux-gnu 85 | os: ubuntu-latest 86 | rustflags: -Clink-arg=-Wl,--compress-debug-sections=zlib 87 | - target: i686-unknown-linux-musl 88 | os: ubuntu-latest 89 | rustflags: -Clink-arg=-Wl,--compress-debug-sections=zlib 90 | - target: x86_64-unknown-linux-gnu 91 | os: ubuntu-latest 92 | rustflags: -Clink-arg=-Wl,--compress-debug-sections=zlib 93 | - target: x86_64-unknown-linux-musl 94 | os: ubuntu-latest 95 | rustflags: -Clink-arg=-Wl,--compress-debug-sections=zlib 96 | - target: x86_64-pc-windows-msvc 97 | os: windows-latest 98 | rustflags: 99 | - target: x86_64-apple-darwin 100 | os: macos-latest 101 | rustflags: 102 | - target: aarch64-apple-darwin 103 | os: macos-latest 104 | rustflags: 105 | runs-on: ${{ matrix.os }} 106 | steps: 107 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 108 | with: 109 | persist-credentials: false 110 | - uses: taiki-e/upload-rust-binary-action@db101489b509ad1c7acce163e118eb36a1650f98 # v1.26.0 111 | id: upload-rust-binary-action 112 | with: 113 | # (required) Comma-separated list of binary names (non-extension portion of filename) to build and upload. 114 | # Note that glob pattern is not supported yet. 115 | bin: chezmoi_modify_manager 116 | # (optional) Target triple, default is host triple. 117 | target: ${{ matrix.target }} 118 | # Include version number. 119 | archive: $bin-$tag-$target 120 | # (required) GitHub token for uploading assets to GitHub Releases. 121 | token: ${{ secrets.GITHUB_TOKEN }} 122 | env: 123 | RUSTFLAGS: ${{ matrix.rustflags }} 124 | - name: Generate artifact attestation 125 | uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0 126 | with: 127 | subject-path: "${{ steps.upload-rust-binary-action.outputs.archive }}.*" 128 | 129 | upload-crates-io: 130 | runs-on: ubuntu-latest 131 | permissions: 132 | contents: read 133 | steps: 134 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 135 | with: 136 | persist-credentials: false 137 | - run: cargo publish --token ${CRATES_TOKEN} 138 | env: 139 | CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} 140 | 141 | upload-aur: 142 | needs: 143 | - upload-crates-io 144 | runs-on: ubuntu-latest 145 | permissions: 146 | contents: read 147 | steps: 148 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 149 | with: 150 | persist-credentials: false 151 | - name: Get AUR repo 152 | run: git clone https://aur.archlinux.org/chezmoi_modify_manager.git aur 153 | - name: Update PKGBUILD 154 | run: .ci/upd-pkgbuild.sh "${RELEASE_TAG}" aur/PKGBUILD 155 | env: 156 | RELEASE_TAG: ${{ github.ref }} 157 | - name: Publish AUR package 158 | uses: KSXGitHub/github-actions-deploy-aur@2ac5a4c1d7035885d46b10e3193393be8460b6f1 # v4.1.1 159 | with: 160 | pkgname: chezmoi_modify_manager 161 | pkgbuild: aur/PKGBUILD 162 | updpkgsums: true 163 | commit_username: ${{ secrets.AUR_USERNAME }} 164 | commit_email: ${{ secrets.AUR_EMAIL }} 165 | ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} 166 | commit_message: New upstream release (automatic update from GitHub Actions) 167 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.yamlfmt: -------------------------------------------------------------------------------- 1 | continue_on_error: false 2 | doublestar: false 3 | exclude: [] 4 | extensions: 5 | - yaml 6 | - yml 7 | gitignore_excludes: false 8 | gitignore_path: .gitignore 9 | include: [] 10 | line_ending: lf 11 | output_format: default 12 | regex_exclude: [] 13 | formatter: 14 | disallow_anchors: false 15 | drop_merge_tag: false 16 | eof_newline: true 17 | include_document_start: false 18 | indent: 2 19 | indentless_arrays: false 20 | line_ending: lf 21 | max_line_length: 0 22 | pad_line_comments: 1 23 | retain_line_breaks: false 24 | retain_line_breaks_single: true 25 | scan_folded_as_literal: false 26 | trim_trailing_whitespace: true 27 | type: basic 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Arvid Norlander"] 3 | categories = ["config", "command-line-utilities"] 4 | description = "Chezmoi addon to patch ini files with mixed settings and state (experimental rust branch)" 5 | edition = "2021" 6 | exclude = ["/.github/", "/.ci/", "/.chglog/"] 7 | keywords = ["ini", "config", "configuration", "chezmoi"] 8 | license = "GPL-3.0-only" 9 | name = "chezmoi_modify_manager" 10 | repository = "https://github.com/VorpalBlade/chezmoi_modify_manager" 11 | rust-version = "1.82.0" 12 | version = "3.5.3" 13 | 14 | [features] 15 | default = ["updater-tls-rusttls", "keyring", "vendored"] 16 | # Support for keyring transform 17 | keyring = ["ini-merge/keyring"] 18 | # Built in updater, distro packages probably wants to disable this. Uses rustls for encryption. 19 | updater-tls-rusttls = ["dep:self_update"] 20 | # Vendor C/C++ dependencies and link them statically 21 | vendored = ["ini-merge/vendored"] 22 | 23 | [target.'cfg(windows)'.dependencies] 24 | self_update = { version = "0.42.0", optional = true, default-features = false, features = [ 25 | "archive-zip", 26 | "compression-zip-deflate", 27 | "rustls", 28 | ] } 29 | 30 | [target.'cfg(unix)'.dependencies] 31 | self_update = { version = "0.42.0", optional = true, default-features = false, features = [ 32 | "archive-tar", 33 | "compression-flate2", 34 | "rustls", 35 | ] } 36 | 37 | [dependencies] 38 | anstream = { version = "0.6.18", default-features = false, features = [ 39 | "auto", 40 | "wincon", 41 | ] } 42 | anstyle = { version = "1.0.10", default-features = false } 43 | anyhow = { version = "1.0.98", features = [ 44 | "std", 45 | "backtrace", 46 | ], default-features = false } 47 | bpaf = { version = "0.9", features = [ 48 | "autocomplete", 49 | "derive", 50 | ], default-features = false } 51 | camino = { version = "1.1.9", default-features = false } 52 | duct = { version = "1.0.0", default-features = false } 53 | env_logger = { version = "0.11.8", default-features = false } 54 | glob = { version = "0.3.2", default-features = false } 55 | indoc = { version = "2.0.6", default-features = false } 56 | ini-merge = { version = "0.6.2", default-features = false } 57 | itertools = { version = "0.14.0", default-features = false } 58 | log = { version = "0.4.27", default-features = false } 59 | medic = { version = "0.3.3" } 60 | regex = "1.11.1" 61 | rpassword = "7.4.0" 62 | strum = { version = "0.27.1", features = [ 63 | "derive", 64 | "std", 65 | ], default-features = false } 66 | thiserror = { version = "2.0.12", default-features = false } 67 | which = { version = "7.0.3", default-features = false } 68 | winnow = { version = "0.7.10", default-features = false, features = [ 69 | "simd", 70 | "std", 71 | ] } 72 | 73 | [dev-dependencies] 74 | pathdiff = { version = "0.2.3", features = [ 75 | "camino", 76 | ], default-features = false } 77 | pretty_assertions = { version = "1.4.1", default-features = false, features = [ 78 | "std", 79 | ] } 80 | tempfile = { version = "3.20.0", default-features = false } 81 | 82 | [lints.rust] 83 | elided_lifetimes_in_paths = "warn" 84 | keyword_idents = "warn" 85 | macro_use_extern_crate = "warn" 86 | meta_variable_misuse = "warn" 87 | redundant_lifetimes = "warn" 88 | rust_2018_idioms = "warn" 89 | trivial_casts = "warn" 90 | trivial_numeric_casts = "warn" 91 | unit_bindings = "warn" 92 | unreachable_pub = "warn" 93 | unused_qualifications = "warn" 94 | variant_size_differences = "warn" 95 | 96 | [lints.clippy] 97 | assigning_clones = "warn" 98 | cast_lossless = "warn" 99 | cloned_instead_of_copied = "warn" 100 | derive_partial_eq_without_eq = "warn" 101 | doc_markdown = "warn" 102 | equatable_if_let = "warn" 103 | explicit_iter_loop = "warn" 104 | flat_map_option = "warn" 105 | format_push_string = "warn" 106 | ignored_unit_patterns = "warn" 107 | manual_assert = "warn" 108 | manual_let_else = "warn" 109 | manual_string_new = "warn" 110 | needless_pass_by_value = "warn" 111 | or_fun_call = "warn" 112 | ptr_as_ptr = "warn" 113 | redundant_clone = "warn" 114 | redundant_closure_for_method_calls = "warn" 115 | redundant_else = "warn" 116 | semicolon_if_nothing_returned = "warn" 117 | type_repetition_in_bounds = "warn" 118 | undocumented_unsafe_blocks = "warn" 119 | uninlined_format_args = "warn" 120 | unnecessary_box_returns = "warn" 121 | unnecessary_safety_doc = "warn" 122 | unnested_or_patterns = "warn" 123 | unwrap_used = "warn" 124 | use_self = "warn" 125 | wildcard_imports = "warn" 126 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [build.env] 2 | passthrough = ["CHEZMOI_MODIFY_MANAGER_BUILDER"] 3 | 4 | [target.x86_64-unknown-linux-gnu] 5 | # Make it possible to compile ring 6 | image = "ghcr.io/cross-rs/x86_64-unknown-linux-gnu:main" 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modify script helper addon for chezmoi 2 | 3 | [ [User Manual] ] [ [lib.rs] ] [ [crates.io] ] [ [AUR] ] 4 | 5 | 6 | ## News 7 | 8 | * If you are upgrading across major releases see the [migration guides] 9 | 10 | --- 11 | 12 | Addon for [chezmoi](https://www.chezmoi.io/) that deals with settings files that 13 | contain a mix of settings and state. So far handling INI-style files are 14 | supported. 15 | 16 | A typical example of this is KDE settings files. These contain (apart from 17 | settings) state like recently opened files and positions of windows and dialog 18 | boxes. Other programs (such as PrusaSlicer) also do the same thing. 19 | 20 | The program in this repository allows you to ignore certain sections of those 21 | INI files when managing the configuration files with chezmoi. 22 | 23 | ## Documentation 24 | 25 | See the [user manual] for the full documentation on how to use 26 | `chezmoi_modify_manager`. 27 | 28 | ## Supported features 29 | 30 | #### Feature: Merging & filtering INI files 31 | 32 | This is the main mode and reason for the existance of this tool. 33 | 34 | `chezmoi_modify_manager` allows you to: 35 | 36 | * Ignore entire sections or specific keys in an INI style file. 37 | * Ignore a key in a section based on regular expressions. 38 | * Force set a value (useful together with templating). 39 | * Force remove a section, key or entries matching a regex (useful together with templating). 40 | * Apply a transformation to the value of a specified key. These are special 41 | operations that are built in and provide more complicated transformations. 42 | Some examples that this can do: 43 | * Look up a password in the platform keyring 44 | * Ignore the sorting order of a list style value (`key=a,b,c,d`) 45 | * etc. 46 | 47 | For detailed usage instructions see the [user manual]. 48 | 49 | #### Feature: Assisted adding to the chezmoi source state 50 | 51 | The command can also be used to add files (see `chezmoi_modify_manager --help` for details): 52 | 53 | * Smart re-add mode (re-add files as managed `.src.ini` if they are already 54 | managed, otherwise add with plain chezmoi). 55 | * Conversion mode (convert from plain chezmoi to managed to `.src.ini`). 56 | 57 | `chezmoi_modify_manager` also allows filtering the added files when re-adding 58 | them after they changed: 59 | 60 | * Any ignored keys will be removed (since we always use the system version of 61 | these, this reduces churn and the diff size in git). 62 | * The value can be hidden (`add:hide` directive), useful in case of passwords 63 | that comes from keyrings. 64 | * Or they can be removed entirely using the `add:remove` directive (useful in 65 | combination with `set` and a templated modify script). 66 | 67 | For detailed usage instructions see the [user manual]. 68 | 69 | ## Platform support and requirements 70 | 71 | The binary is self-contained with no non-optional system dependencies apart 72 | from the platform provided basic libraries (typically libc & libm on Linux). 73 | 74 | Requirements to build (if there is no native binary for your platform): 75 | 76 | * Rust 1.82.0 or newer 77 | * A C compiler and associated toolchain (linker, headers, libraries, etc).\ 78 | This is needed as some dependencies may include some C code. 79 | 80 | Platforms: 81 | 82 | | Platform | Architecture | Continuous Integration | Tested manually | 83 | |------------------|--------------|------------------------|---------------------| 84 | | Linux with Glibc | All major | Yes | Yes (x86-64, ARM64) | 85 | | Linux with Musl | All major | Yes | Yes (x86-64) | 86 | | Windows | x86-64 | Yes | No | 87 | | MacOS | x86-64 | Yes | No | 88 | 89 | The above table is limited to what I myself have access to (and use) as well as 90 | what works in GitHub CI. Other Unixes are likely to work, if 91 | [Rust has support](https://doc.rust-lang.org/stable/rustc/platform-support.html). 92 | 93 | ## Minimum Supported Rust Version (MSRV) policy 94 | 95 | The current Minimum Supported Rust Version (MSRV) is documented in the previous 96 | [section](#platform-support-and-requirements). The MSRV may be bumped as needed. 97 | It is guaranteed that `chezmoi_modify_manager` will at least build on the current 98 | and previous stable Rust release. An MSRV change is not considered a breaking 99 | change and as such may change even in a patch version. 100 | 101 | [AUR]: https://aur.archlinux.org/packages/chezmoi_modify_manager 102 | [crates.io]: https://crates.io/crates/chezmoi_modify_manager 103 | [lib.rs]: https://lib.rs/crates/chezmoi_modify_manager 104 | [user manual]: https://vorpalblade.github.io/chezmoi_modify_manager 105 | [migration guides]: https://vorpalblade.github.io/chezmoi_modify_manager/migration -------------------------------------------------------------------------------- /about.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 36 | 37 | 38 | 39 |
40 |
41 |

Third Party Licenses

42 |

This page lists the licenses of the projects used in chezmoi_modify_manager.

43 |
44 | 45 |

Overview of licenses:

46 | 51 | 52 |

All license text:

53 | 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> { 161 | let normal_path = self.basic_source_path(path); 162 | let script_path_tmpl = 163 | normal_path.with_file_name(format!("modify_{}.tmpl", normal_path.file_name().unwrap())); 164 | let script_path_plain = 165 | normal_path.with_file_name(format!("modify_{}", normal_path.file_name().unwrap())); 166 | if script_path_tmpl.exists() { 167 | Ok(Some(script_path_tmpl)) 168 | } else if script_path_plain.exists() { 169 | Ok(Some(script_path_plain)) 170 | } else if normal_path.exists() { 171 | Ok(Some(normal_path)) 172 | } else { 173 | Ok(None) 174 | } 175 | } 176 | 177 | fn source_root(&self) -> anyhow::Result> { 178 | Ok(Some(self.src_dir.clone())) 179 | } 180 | 181 | fn add(&self, path: &Utf8Path) -> anyhow::Result<()> { 182 | let expected_path = self.basic_source_path(path); 183 | std::fs::copy(path, expected_path).unwrap(); 184 | Ok(()) 185 | } 186 | 187 | fn version(&self) -> anyhow::Result { 188 | Ok(self.version) 189 | } 190 | } 191 | 192 | fn assert_default_script(chezmoi: &DummyChezmoi, style: Style) { 193 | let file_data = std::fs::read(chezmoi.src_dir.join("dummy_file.src.ini")).unwrap(); 194 | assert_eq!(file_data.strip_suffix(b"\n").unwrap(), b"[a]\nb=c"); 195 | 196 | let file_data = std::fs::read(chezmoi.make_script_path("dummy_file", style)).unwrap(); 197 | let file_data = String::from_utf8(file_data).unwrap(); 198 | assert!(file_data.starts_with("#!/usr/bin/env chezmoi_modify_manager\n")); 199 | 200 | // No dummy basic file should exist 201 | assert!(!chezmoi.src_dir.join("dummy_file").try_exists().unwrap()); 202 | } 203 | 204 | fn assert_unchanged_script(chezmoi: &DummyChezmoi, style: Style) { 205 | let file_data = std::fs::read(chezmoi.src_dir.join("dummy_file.src.ini")).unwrap(); 206 | assert_eq!(file_data.strip_suffix(b"\n").unwrap(), b"[a]\nb=c"); 207 | 208 | let file_data = std::fs::read(chezmoi.make_script_path("dummy_file", style)).unwrap(); 209 | let file_data = String::from_utf8(file_data).unwrap(); 210 | assert!(file_data.starts_with("#!/usr/bin/env chezmoi_modify_manager\n#UNTOUCHED\nsource auto")); 211 | 212 | // No dummy basic file should exist 213 | assert!(!chezmoi.src_dir.join("dummy_file").try_exists().unwrap()); 214 | } 215 | 216 | fn assert_default_basic(chezmoi: &DummyChezmoi) { 217 | let file_data = std::fs::read(chezmoi.src_dir.join("dummy_file")).unwrap(); 218 | assert_eq!(file_data, b"[a]\nb=c"); 219 | 220 | // No modify script should exist 221 | assert!(!chezmoi 222 | .src_dir 223 | .join("dummy_file.src.ini") 224 | .try_exists() 225 | .unwrap()); 226 | assert!(!chezmoi 227 | .src_dir 228 | .join("modify_dummy_file") 229 | .try_exists() 230 | .unwrap()); 231 | assert!(!chezmoi 232 | .src_dir 233 | .join("modify_dummy_file.tmpl") 234 | .try_exists() 235 | .unwrap()); 236 | } 237 | 238 | fn assert_nothing_added(chezmoi: &DummyChezmoi) { 239 | // No files added 240 | assert!(!chezmoi.src_dir.join("dummy_file").try_exists().unwrap()); 241 | assert!(!chezmoi 242 | .src_dir 243 | .join("dummy_file.src.ini") 244 | .try_exists() 245 | .unwrap()); 246 | assert!(!chezmoi 247 | .src_dir 248 | .join("modify_dummy_file") 249 | .try_exists() 250 | .unwrap()); 251 | assert!(!chezmoi 252 | .src_dir 253 | .join("modify_dummy_file.tmpl") 254 | .try_exists() 255 | .unwrap()); 256 | } 257 | 258 | mod versions { 259 | use super::assert_nothing_added; 260 | use super::DummyChezmoi; 261 | use crate::add::add; 262 | use crate::add::Mode; 263 | use crate::Style; 264 | 265 | #[test] 266 | fn check_error_on_old_chezmoi() { 267 | // Check that --style path errors on old chezmoi 268 | let mut chezmoi = DummyChezmoi::new(); 269 | chezmoi.version.1 -= 1; 270 | let chezmoi = chezmoi; 271 | 272 | let mut stdout: Vec = vec![]; 273 | 274 | let error = add( 275 | &chezmoi, 276 | Mode::Normal, 277 | Style::InPath, 278 | chezmoi.dummy_file.as_path(), 279 | &mut stdout, 280 | ); 281 | assert!(error.is_err()); 282 | 283 | assert_nothing_added(&chezmoi); 284 | } 285 | } 286 | 287 | mod path_tmpl { 288 | use super::assert_default_basic; 289 | use super::assert_default_script; 290 | use super::assert_unchanged_script; 291 | use super::DummyChezmoi; 292 | use crate::add::add; 293 | use crate::add::Mode; 294 | use crate::Style; 295 | 296 | #[test] 297 | fn check_add_normal_missing() { 298 | let chezmoi = DummyChezmoi::new(); 299 | let mut stdout: Vec = vec![]; 300 | 301 | add( 302 | &chezmoi, 303 | Mode::Normal, 304 | Style::InPathTmpl, 305 | chezmoi.dummy_file.as_path(), 306 | &mut stdout, 307 | ) 308 | .unwrap(); 309 | 310 | assert_default_script(&chezmoi, Style::InPathTmpl); 311 | } 312 | 313 | #[test] 314 | fn check_add_smart_missing() { 315 | let chezmoi = DummyChezmoi::new(); 316 | let mut stdout: Vec = vec![]; 317 | 318 | add( 319 | &chezmoi, 320 | Mode::Smart, 321 | Style::InPathTmpl, 322 | chezmoi.dummy_file.as_path(), 323 | &mut stdout, 324 | ) 325 | .unwrap(); 326 | 327 | assert_default_basic(&chezmoi); 328 | } 329 | 330 | #[test] 331 | fn check_add_normal_basic() { 332 | let chezmoi = DummyChezmoi::new(); 333 | let mut stdout: Vec = vec![]; 334 | 335 | std::fs::write(chezmoi.src_dir.join("dummy_file"), "old_contents").unwrap(); 336 | 337 | add( 338 | &chezmoi, 339 | Mode::Normal, 340 | Style::InPathTmpl, 341 | chezmoi.dummy_file.as_path(), 342 | &mut stdout, 343 | ) 344 | .unwrap(); 345 | 346 | assert_default_script(&chezmoi, Style::InPathTmpl); 347 | } 348 | 349 | #[test] 350 | fn check_add_smart_basic() { 351 | let chezmoi = DummyChezmoi::new(); 352 | let mut stdout: Vec = vec![]; 353 | 354 | std::fs::write(chezmoi.src_dir.join("dummy_file"), "old_contents").unwrap(); 355 | 356 | add( 357 | &chezmoi, 358 | Mode::Smart, 359 | Style::InPathTmpl, 360 | chezmoi.dummy_file.as_path(), 361 | &mut stdout, 362 | ) 363 | .unwrap(); 364 | 365 | assert_default_basic(&chezmoi); 366 | } 367 | 368 | #[test] 369 | fn check_add_normal_script() { 370 | let chezmoi = DummyChezmoi::new(); 371 | let mut stdout: Vec = vec![]; 372 | 373 | std::fs::write(chezmoi.src_dir.join("dummy_file.src.ini"), "old_contents").unwrap(); 374 | std::fs::write( 375 | chezmoi.src_dir.join("modify_dummy_file.tmpl"), 376 | "#!/usr/bin/env chezmoi_modify_manager\n#UNTOUCHED\nsource auto", 377 | ) 378 | .unwrap(); 379 | 380 | add( 381 | &chezmoi, 382 | Mode::Normal, 383 | Style::InPathTmpl, 384 | chezmoi.dummy_file.as_path(), 385 | &mut stdout, 386 | ) 387 | .unwrap(); 388 | 389 | assert_unchanged_script(&chezmoi, Style::InPathTmpl); 390 | } 391 | 392 | #[test] 393 | fn check_add_smart_script() { 394 | let chezmoi = DummyChezmoi::new(); 395 | let mut stdout: Vec = vec![]; 396 | 397 | std::fs::write(chezmoi.src_dir.join("dummy_file.src.ini"), "old_contents").unwrap(); 398 | std::fs::write( 399 | chezmoi.src_dir.join("modify_dummy_file.tmpl"), 400 | "#!/usr/bin/env chezmoi_modify_manager\n#UNTOUCHED\nsource auto", 401 | ) 402 | .unwrap(); 403 | 404 | add( 405 | &chezmoi, 406 | Mode::Smart, 407 | Style::InPathTmpl, 408 | chezmoi.dummy_file.as_path(), 409 | &mut stdout, 410 | ) 411 | .unwrap(); 412 | 413 | assert_unchanged_script(&chezmoi, Style::InPathTmpl); 414 | } 415 | } 416 | 417 | mod path { 418 | use super::assert_default_basic; 419 | use super::assert_default_script; 420 | use super::assert_unchanged_script; 421 | use super::DummyChezmoi; 422 | use crate::add::add; 423 | use crate::add::Mode; 424 | use crate::Style; 425 | 426 | #[test] 427 | fn check_add_normal_missing() { 428 | let chezmoi = DummyChezmoi::new(); 429 | let mut stdout: Vec = vec![]; 430 | 431 | add( 432 | &chezmoi, 433 | Mode::Normal, 434 | Style::InPath, 435 | chezmoi.dummy_file.as_path(), 436 | &mut stdout, 437 | ) 438 | .unwrap(); 439 | 440 | assert_default_script(&chezmoi, Style::InPath); 441 | } 442 | 443 | #[test] 444 | fn check_add_smart_missing() { 445 | let chezmoi = DummyChezmoi::new(); 446 | let mut stdout: Vec = vec![]; 447 | 448 | add( 449 | &chezmoi, 450 | Mode::Smart, 451 | Style::InPath, 452 | chezmoi.dummy_file.as_path(), 453 | &mut stdout, 454 | ) 455 | .unwrap(); 456 | 457 | assert_default_basic(&chezmoi); 458 | } 459 | 460 | #[test] 461 | fn check_add_normal_basic() { 462 | let chezmoi = DummyChezmoi::new(); 463 | let mut stdout: Vec = vec![]; 464 | 465 | std::fs::write(chezmoi.src_dir.join("dummy_file"), "old_contents").unwrap(); 466 | 467 | add( 468 | &chezmoi, 469 | Mode::Normal, 470 | Style::InPath, 471 | chezmoi.dummy_file.as_path(), 472 | &mut stdout, 473 | ) 474 | .unwrap(); 475 | 476 | assert_default_script(&chezmoi, Style::InPath); 477 | } 478 | 479 | #[test] 480 | fn check_add_smart_basic() { 481 | let chezmoi = DummyChezmoi::new(); 482 | let mut stdout: Vec = vec![]; 483 | 484 | std::fs::write(chezmoi.src_dir.join("dummy_file"), "old_contents").unwrap(); 485 | 486 | add( 487 | &chezmoi, 488 | Mode::Smart, 489 | Style::InPath, 490 | chezmoi.dummy_file.as_path(), 491 | &mut stdout, 492 | ) 493 | .unwrap(); 494 | 495 | assert_default_basic(&chezmoi); 496 | } 497 | 498 | #[test] 499 | fn check_add_normal_script() { 500 | let chezmoi = DummyChezmoi::new(); 501 | let mut stdout: Vec = vec![]; 502 | 503 | std::fs::write(chezmoi.src_dir.join("dummy_file.src.ini"), "old_contents").unwrap(); 504 | std::fs::write( 505 | chezmoi.src_dir.join("modify_dummy_file"), 506 | "#!/usr/bin/env chezmoi_modify_manager\n#UNTOUCHED\nsource auto", 507 | ) 508 | .unwrap(); 509 | 510 | add( 511 | &chezmoi, 512 | Mode::Normal, 513 | Style::InPath, 514 | chezmoi.dummy_file.as_path(), 515 | &mut stdout, 516 | ) 517 | .unwrap(); 518 | 519 | assert_unchanged_script(&chezmoi, Style::InPath); 520 | } 521 | 522 | #[test] 523 | fn check_add_smart_script() { 524 | let chezmoi = DummyChezmoi::new(); 525 | let mut stdout: Vec = vec![]; 526 | 527 | std::fs::write(chezmoi.src_dir.join("dummy_file.src.ini"), "old_contents").unwrap(); 528 | std::fs::write( 529 | chezmoi.src_dir.join("modify_dummy_file"), 530 | "#!/usr/bin/env chezmoi_modify_manager\n#UNTOUCHED\nsource auto", 531 | ) 532 | .unwrap(); 533 | 534 | add( 535 | &chezmoi, 536 | Mode::Smart, 537 | Style::InPath, 538 | chezmoi.dummy_file.as_path(), 539 | &mut stdout, 540 | ) 541 | .unwrap(); 542 | 543 | dbg!(String::from_utf8_lossy(&stdout)); 544 | 545 | assert_unchanged_script(&chezmoi, Style::InPath); 546 | } 547 | } 548 | -------------------------------------------------------------------------------- /src/arguments.rs: -------------------------------------------------------------------------------- 1 | //! Command line argument parser 2 | 3 | // Doc comments are used to generate --help, not to for rustdoc. 4 | #![allow(clippy::doc_markdown)] 5 | 6 | use crate::add::Style; 7 | use bpaf::short; 8 | use bpaf::Bpaf; 9 | use bpaf::Parser; 10 | use bpaf::ShellComp; 11 | use camino::Utf8PathBuf; 12 | use itertools::Itertools; 13 | use strum::EnumMessage; 14 | use strum::IntoEnumIterator; 15 | 16 | /// Parser for `--style` 17 | fn style() -> impl Parser