├── .github ├── dependabot.yml └── workflows │ ├── _typos.toml │ ├── release.yml │ ├── tests.yml │ └── typo-check.yaml ├── .gitignore ├── .nojekyll ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── docs ├── CNAME ├── assets │ ├── Home.74659134.js │ ├── app.2b904881.js │ ├── config_guide_01-default-behaviours.md.bcd25e88.js │ ├── config_guide_01-default-behaviours.md.bcd25e88.lean.js │ ├── config_guide_02-groups.md.54c6ed9d.js │ ├── config_guide_02-groups.md.54c6ed9d.lean.js │ ├── config_guide_03-syncing-methods.md.1ebfcfdd.js │ ├── config_guide_03-syncing-methods.md.1ebfcfdd.lean.js │ ├── config_guide_04-host-specific.md.3be46361.js │ ├── config_guide_04-host-specific.md.3be46361.lean.js │ ├── config_guide_05-priority.md.f64e8a7d.js │ ├── config_guide_05-priority.md.f64e8a7d.lean.js │ ├── config_guide_06-filename-manipulating.md.aba8fc76.js │ ├── config_guide_06-filename-manipulating.md.aba8fc76.lean.js │ ├── config_guide_07-templating.md.f7f33668.js │ ├── config_guide_07-templating.md.f7f33668.lean.js │ ├── config_guide_99-error-handling.md.e05b2975.js │ ├── config_guide_99-error-handling.md.e05b2975.lean.js │ ├── config_guide_index.md.a2153e28.js │ ├── config_guide_index.md.a2153e28.lean.js │ ├── config_key-references.md.df45381e.js │ ├── config_key-references.md.df45381e.lean.js │ ├── contributing.md.8dac4517.js │ ├── contributing.md.8dac4517.lean.js │ ├── features_01-host-specific.md.857720b4.js │ ├── features_01-host-specific.md.857720b4.lean.js │ ├── features_02-scope.md.bdd8e6d2.js │ ├── features_02-scope.md.bdd8e6d2.lean.js │ ├── features_03-filename-manipulating.md.4f4fb6b1.js │ ├── features_03-filename-manipulating.md.4f4fb6b1.lean.js │ ├── features_04-templating.md.adcf805f.js │ ├── features_04-templating.md.adcf805f.lean.js │ ├── features_index.md.27034f59.js │ ├── features_index.md.27034f59.lean.js │ ├── index.md.d0fcd5a8.js │ ├── index.md.d0fcd5a8.lean.js │ ├── installation.md.d7a4ab95.js │ ├── installation.md.d7a4ab95.lean.js │ ├── plugin-vue_export-helper.f07d1dea.js │ └── style.42fa774d.css ├── config │ ├── guide │ │ ├── 01-default-behaviours.html │ │ ├── 02-groups.html │ │ ├── 03-syncing-methods.html │ │ ├── 04-host-specific.html │ │ ├── 05-priority.html │ │ ├── 06-filename-manipulating.html │ │ ├── 07-templating.html │ │ ├── 99-error-handling.html │ │ └── index.html │ └── key-references.html ├── contributing.html ├── features │ ├── 01-host-specific.html │ ├── 02-scope.html │ ├── 03-filename-manipulating.html │ ├── 04-templating.html │ └── index.html ├── home-everywhere.png ├── index.html └── installation.html ├── dt-cli ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── dt-core ├── Cargo.toml ├── README.md └── src │ ├── config.rs │ ├── error.rs │ ├── item.rs │ ├── lib.rs │ ├── registry.rs │ ├── syncing.rs │ └── utils.rs ├── dt-server ├── Cargo.toml └── src │ └── main.rs ├── roadmap.md ├── rust-toolchain.toml └── rustfmt.toml /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/dt-core" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "cargo" 8 | directory: "/dt-cli" 9 | schedule: 10 | interval: "daily" 11 | - package-ecosystem: "cargo" 12 | directory: "/dt-server" 13 | schedule: 14 | interval: "daily" 15 | -------------------------------------------------------------------------------- /.github/workflows/_typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude = ["docs"] 3 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*.*.*' 5 | 6 | name: release 7 | 8 | jobs: 9 | generate-changelog: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | ref: ${{ github.ref }} 17 | - name: Get name of current tag 18 | id: get_tag_name 19 | run: echo "::set-output name=tag_name::${GITHUB_REF/refs\/tags\//}" 20 | - name: Generate changelog 21 | uses: orhun/git-cliff-action@v1 22 | id: git-cliff 23 | with: 24 | args: -vv --latest --strip header 25 | env: 26 | OUTPUT: CHANGELOG.md 27 | - name: Set release body 28 | id: changelog 29 | run: | 30 | if log=$(cat ${{ steps.git-cliff.outputs.changelog }}); then 31 | log="${log//'%'/'%25'}" 32 | log="${log//'~'/\\~}" 33 | log="${log//$'\n'/'%0A'}" 34 | log="${log//$'\r'/'%0D'}" 35 | echo "::set-output name=release_body::$log" 36 | else 37 | echo "::set-output name=release_body::log generation failed" 38 | fi 39 | - name: Upload built assets 40 | uses: svenstaro/upload-release-action@v2 41 | with: 42 | repo_token: ${{ secrets.GITHUB_TOKEN }} 43 | tag: ${{ steps.get_tag_name.outputs.tag_name }} 44 | release_name: ${{ steps.get_tag_name.outputs.tag_name }} 45 | body: ${{ steps.changelog.outputs.release_body }} 46 | file: LICENSE-* 47 | file_glob: true 48 | 49 | build-and-release: 50 | runs-on: ubuntu-latest 51 | strategy: 52 | matrix: 53 | build: [x86_64, armv7, aarch64] 54 | include: 55 | - build: x86_64 56 | toolchain: stable 57 | target: x86_64-unknown-linux-gnu 58 | cross: false 59 | strip_bin: strip 60 | - build: armv7 61 | toolchain: stable 62 | target: armv7-unknown-linux-gnueabihf 63 | cross: true 64 | cross_helpers: gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf 65 | strip_bin: arm-linux-gnueabihf-strip 66 | - build: aarch64 67 | toolchain: stable 68 | target: aarch64-unknown-linux-gnu 69 | cross: true 70 | cross_helpers: gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu 71 | strip_bin: aarch64-linux-gnu-strip 72 | steps: 73 | - name: Install Linker 74 | if: ${{ matrix.cross }} 75 | run: | 76 | sudo apt update 77 | sudo apt install ${{ matrix.cross_helpers }} 78 | - name: Checkout code 79 | uses: actions/checkout@v2 80 | with: 81 | fetch-depth: 0 82 | ref: ${{ github.ref }} 83 | - name: Setup rust toolchain 84 | uses: actions-rs/toolchain@v1 85 | with: 86 | toolchain: ${{ matrix.toolchain }} 87 | target: ${{ matrix.target }} 88 | #override: true 89 | - name: Compile dt-cli 90 | uses: actions-rs/cargo@v1 91 | with: 92 | command: build 93 | args: --release --all-features --locked --target ${{ matrix.target }} 94 | use-cross: ${{ matrix.cross }} 95 | - name: Fetch all tags 96 | run: git fetch origin +refs/tags/*:refs/tags/* 97 | - name: Get name of current tag 98 | id: get_tag_name 99 | run: echo "::set-output name=tag_name::${GITHUB_REF/refs\/tags\//}" 100 | - name: Strip debug symbols 101 | run: | 102 | ${{ matrix.strip_bin }} --strip-all target/${{ matrix.target }}/release/dt-cli 103 | - name: Give a name to built binary 104 | run: | 105 | mv target/${{ matrix.target }}/release/dt-cli dt-cli-${{ steps.get_tag_name.outputs.tag_name }}-${{ matrix.build }} 106 | - name: Upload built assets 107 | uses: svenstaro/upload-release-action@v2 108 | with: 109 | repo_token: ${{ secrets.GITHUB_TOKEN }} 110 | tag: ${{ github.ref }} 111 | release_name: ${{ steps.get_tag_name.outputs.tag_name }} 112 | file: dt-cli-${{ steps.get_tag_name.outputs.tag_name }}-${{ matrix.build }} 113 | 114 | # Author: Blurgy 115 | # Date: Oct 07 2021, 00:39 [CST] 116 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - "**" # All branches 5 | - "!docs" # Exclude branch "docs" 6 | 7 | name: tests 8 | 9 | jobs: 10 | unit-test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | build: [x86_64, armv7, aarch64] 15 | include: 16 | - build: x86_64 17 | toolchain: stable 18 | target: x86_64-unknown-linux-gnu 19 | cross: false 20 | strip_bin: strip 21 | - build: armv7 22 | toolchain: stable 23 | target: armv7-unknown-linux-gnueabihf 24 | cross: true 25 | cross_helpers: gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf 26 | strip_bin: arm-linux-gnueabihf-strip 27 | - build: aarch64 28 | toolchain: stable 29 | target: aarch64-unknown-linux-gnu 30 | cross: true 31 | cross_helpers: gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu 32 | strip_bin: aarch64-linux-gnu-strip 33 | steps: 34 | - name: Install Linker 35 | if: ${{ matrix.cross }} 36 | run: | 37 | sudo apt update 38 | sudo apt install ${{ matrix.cross_helpers }} 39 | - name: Checkout code 40 | uses: actions/checkout@v2 41 | with: 42 | fetch-depth: 0 43 | ref: ${{ github.ref }} 44 | - name: Setup rust toolchain 45 | uses: actions-rs/toolchain@v1 46 | with: 47 | toolchain: ${{ matrix.toolchain }} 48 | target: ${{ matrix.target }} 49 | #override: true 50 | - name: Run unit tests 51 | uses: actions-rs/cargo@v1 52 | with: 53 | command: test 54 | args: --all --all-features --locked --target ${{ matrix.target }} 55 | use-cross: ${{ matrix.cross }} 56 | 57 | # Author: Blurgy 58 | # Date: Oct 07 2021, 00:39 [CST] 59 | -------------------------------------------------------------------------------- /.github/workflows/typo-check.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - "**" # All branches 5 | 6 | jobs: 7 | run: 8 | name: Check typos 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout Actions Repository 12 | uses: actions/checkout@v2 13 | - name: Check typos in source code 14 | uses: crate-ci/typos@master 15 | with: 16 | config: ./.github/workflows/_typos.toml 17 | 18 | - name: Checkout Actions Repository 19 | uses: actions/checkout@v2 20 | with: 21 | ref: docs 22 | - name: Check typos in documentation 23 | uses: crate-ci/typos@master 24 | with: 25 | config: ./.github/workflows/_typos.toml 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | private.toml 3 | 4 | # Author: Blurgy 5 | # Date: Oct 01 2021, 15:29 [CST] 6 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blurgyy/dt/41ed4c97cac079b7492e2b5187a394f4b87ff6cc/.nojekyll -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | gy@blurgy.xyz. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing! There are many ways to contribute 4 | to [`dt`](https://github.com/blurgyy/dt). You can start with examining 5 | unchecked items in the 6 | [roadmap](https://github.com/blurgyy/dt/blob/main/roadmap.md), or discuss some 7 | features to be added to the roadmap. 8 | 9 | When contributing to this repository, please first discuss the change you wish 10 | to make via [issue](https://github.com/blurgyy/dt/issues), 11 | [email](mailto:gy@blurgy.xyz), or any other method with the owners of this 12 | repository before making a change. 13 | 14 | Please note we have a [code of 15 | conduct](https://github.com/blurgyy/dt/blob/main/CODE_OF_CONDUCT.md), please 16 | follow it in all your interactions with the project. 17 | 18 | ## Making your changes 19 | 20 | 1. Fork a copy of `dt` into your GitHub account and clone the forked 21 | repository to your development machine: 22 | ```shell 23 | $ git clone https://github.com/${your_account}/dt.git && cd dt 24 | ``` 25 | 2. Install latest stable version of [Rust](https://github.com/rust-lang/rust), 26 | then make your commits. Please follow the [conventional commit 27 | specification](https://www.conventionalcommits.org/) when writing your 28 | commit messages. 29 | 3. If features are added/updated, also add/update tests and documents and 30 | check if the tests are passed: 31 | ```shell 32 | $ cargo test 33 | ``` 34 | 4. Resolve errors and warnings given by `cargo-clippy`: 35 | ```shell 36 | $ cargo clippy 37 | ``` 38 | 5. Format your changes with `cargo-fmt`: 39 | ```shell 40 | $ cargo fmt -- --verbose 41 | ``` 42 | 6. Submit a [pull request](https://github.com/blurgyy/dt/pulls) and discuss 43 | the possible changes with the owner/maintainer, update your pull request if 44 | necessary. 45 | 46 | ## License 47 | 48 | By contributing, you agree that your contributions will be licenced under 49 | [MIT](http://opensource.org/licenses/MIT) 50 | or [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0) license. 51 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "dt-cli", 4 | "dt-core", 5 | "dt-server", 6 | ] 7 | 8 | [profile.release] 9 | strip = true 10 | opt-level = "z" 11 | lto = true 12 | codegen-units = 1 13 | panic = "abort" 14 | 15 | # Author: Blurgy 16 | # Date: Sep 17 2021, 21:19 [CST] 17 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Gaoyang Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DT 2 | 3 | [![release](https://github.com/blurgyy/dt/actions/workflows/release.yml/badge.svg)](https://github.com/blurgyy/dt/actions/workflows/release.yml) 4 | [![tests](https://github.com/blurgyy/dt/actions/workflows/tests.yml/badge.svg)](https://github.com/blurgyy/dt/actions/workflows/tests.yml) 5 | [![docs](https://github.com/blurgyy/dt/actions/workflows/docs.yml/badge.svg)](https://dt.cli.rs/) 6 | [![crates.io](https://img.shields.io/crates/v/dt-cli?style=flat&labelColor=1C2C2E&color=C96329&logo=Rust&logoColor=white)](https://crates.io/crates/dt-cli) 7 | 8 | `DT` allows you to sync/populate configuration files at will. It currently 9 | provides a CLI tool `dt-cli`. 10 | 11 | ## Usage 12 | 13 | The command line interface `dt-cli` accepts a path to the configuration file 14 | as an argument and performs the syncing process specified in the file. 15 | 16 | Configuration guides and detailed usages can be found in the 17 | [documentations](https://dt.cli.rs/). 18 | 19 | ### Example 20 | 21 | A simple working configuration file to sync all files from `~/dt/nvim` to 22 | `~/.config/nvim` that matches `*init.vim` can be written as: 23 | 24 | ```toml 25 | [[local]] 26 | name = "Neovim Configs" 27 | base = "~/dt/nvim" 28 | sources = ["*init.vim"] 29 | target = "~/.config/nvim" 30 | ``` 31 | 32 | :warning: **STOP HERE if you don't know what you are doing, or have not backed 33 | up existing files under `~/.config/nvim`.** 34 | 35 | Save above config to `~/.config/dt/cli.toml` and run 36 | 37 | ```shell 38 | $ dt-cli -c ~/.config/dt/cli.toml 39 | ``` 40 | 41 | to start syncing. Note the path in this example (`~/.config/dt/cli.toml`) is 42 | also the default path, so the below command (calling `dt-cli` with no argument) 43 | does the same thing as above: 44 | 45 | ```shell 46 | $ dt-cli 47 | ``` 48 | 49 | **Other command line flags & options** 50 | 51 | | Flags | Description | 52 | |---:|:---| 53 | | `-d\|--dry-run` | Shows changes to be made without actually syncing files. | 54 | | `-h\|--help` | Prints help information. | 55 | | `-q\|--quiet` | Decreases logging verbosity. | 56 | | `-v\|--verbose` | Increases logging verbosity. | 57 | | `-V\|--version` | Prints version information. | 58 | 59 | | Options | Description | 60 | |---:|:---| 61 | | `-c\|--config-path` `` | Specifies path to config file. | 62 | 63 | | Args | Description | 64 | |---:|:---| 65 | | `...` | Specifies name(s) of the group(s) to be processed | 66 | 67 | ## Install 68 | 69 | ### AUR 70 | 71 | `dt-cli` is in the [AUR](https://aur.archlinux.org/packages/dt-cli/), you can 72 | install it with your favorite package manager: 73 | 74 | ```shell 75 | $ paru -S dt-cli 76 | ``` 77 | 78 | ### Alternative ways 79 | 80 | Alternatively, you can: 81 | 82 | - Download latest [release](https://github.com/blurgyy/dt/releases/latest) 83 | from GitHub 84 | - Install from [crates.io](https://crates.io/crates/dt-cli/): 85 | 86 | ```shell 87 | $ cargo install dt-cli 88 | ``` 89 | 90 | - Build from source: 91 | 92 | ```shell 93 | $ git clone git@github.com:blurgyy/dt.git 94 | $ cd dt 95 | $ cargo test --release 96 | $ cargo install --path=dt-cli 97 | ``` 98 | 99 | ## Contributing 100 | 101 | There are numerous ways to help with this project. Let's [get 102 | started](https://github.com/blurgyy/dt/blob/main/CONTRIBUTING.md)! 103 | 104 | ## License 105 | 106 | Licensed under the the MIT license or 107 | Apache License, Version 2.0 , at 108 | your option. This file may not be copied, modified, or distributed except 109 | according to those terms. 110 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | dt.cli.rs 2 | -------------------------------------------------------------------------------- /docs/assets/Home.74659134.js: -------------------------------------------------------------------------------- 1 | import{u as y,w as F,N as x}from"./app.2b904881.js";import{_ as m,u as h,n as i,z as e,x as o,y as a,D as c,A as n,C as l,F as g,M as I,N as L,T as A,O as d,P as k}from"./plugin-vue_export-helper.f07d1dea.js";const C={key:0,class:"home-hero"},N={key:0,class:"figure"},b=["src","alt"],B={key:1,id:"main-title",class:"title"},w={key:2,class:"tagline"},D=h({name:"HomeHero",setup(p){const{site:s,frontmatter:t}=y(),_=i(()=>{const{heroImage:r,heroText:u,tagline:H,actionLink:$,actionText:T}=t.value;return r||u||H||$&&T}),v=i(()=>t.value.heroText||s.value.title),f=i(()=>t.value.tagline||s.value.description);return(r,u)=>e(_)?(o(),a("header",C,[e(t).heroImage?(o(),a("figure",N,[c("img",{class:"image",src:e(F)(e(t).heroImage),alt:e(t).heroAlt},null,8,b)])):n("",!0),e(v)?(o(),a("h1",B,l(e(v)),1)):n("",!0),e(f)?(o(),a("p",w,l(e(f)),1)):n("",!0),e(t).actionLink&&e(t).actionText?(o(),g(x,{key:3,item:{link:e(t).actionLink,text:e(t).actionText},class:"action"},null,8,["item"])):n("",!0),e(t).altActionLink&&e(t).altActionText?(o(),g(x,{key:4,item:{link:e(t).altActionLink,text:e(t).altActionText},class:"action alt"},null,8,["item"])):n("",!0)])):n("",!0)}});var V=m(D,[["__scopeId","data-v-0a64febb"]]);const S={key:0,class:"home-features"},z={class:"wrapper"},E={class:"container"},M={class:"features"},O={key:0,class:"title"},P={key:1,class:"details"},j=h({name:"HomeFeatures",setup(p){const{frontmatter:s}=y(),t=i(()=>s.value.features&&s.value.features.length>0),_=i(()=>s.value.features?s.value.features:[]);return(v,f)=>e(t)?(o(),a("div",S,[c("div",z,[c("div",E,[c("div",M,[(o(!0),a(I,null,L(e(_),(r,u)=>(o(),a("section",{key:u,class:"feature"},[r.title?(o(),a("h2",O,l(r.title),1)):n("",!0),r.details?(o(),a("p",P,l(r.details),1)):n("",!0)]))),128))])])])])):n("",!0)}});var q=m(j,[["__scopeId","data-v-d15ee26a"]]);const G={key:0,class:"footer"},J={class:"container"},K={class:"text"},Q=h({name:"HomeFooter",setup(p){const{frontmatter:s}=y();return(t,_)=>e(s).footer?(o(),a("footer",G,[c("div",J,[c("p",K,l(e(s).footer),1)])])):n("",!0)}});var R=m(Q,[["__scopeId","data-v-91c0f51a"]]);const U={class:"home","aria-labelledby":"main-title"},W={class:"home-content"},X=h({name:"Home",setup(p){return(s,t)=>{const _=A("Content");return o(),a("main",U,[d(V),k(s.$slots,"hero",{},void 0,!0),d(q),c("div",W,[d(_)]),k(s.$slots,"features",{},void 0,!0),d(R),k(s.$slots,"footer",{},void 0,!0)])}}});var ee=m(X,[["__scopeId","data-v-a43b121c"]]);export{ee as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/config_guide_01-default-behaviours.md.bcd25e88.js: -------------------------------------------------------------------------------- 1 | import{_ as n,y as a,x as s,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const f='{"title":"Defining Default Behaviours","description":"","frontmatter":{},"relativePath":"config/guide/01-default-behaviours.md","lastUpdated":1692639959838}',e={},o=t(`

Defining Default Behaviours

Note that when syncing our configuration files for Neovim in the basic config, dt-cli aborts on existing target files. When populating items to another machine, it's better to directly overwrite (assuming you know what you are doing) the target file, so the basic config is suboptimal. What we could do is to additionally define the default overwriting behaviours with a [global] section in the configuration:

\xA0
\xA0
\xA0
\xA0






[global]
 2 | allow_overwrite = true
 3 | 
 4 | 
 5 | [[local]]
 6 | name = "Neovim"
 7 | base = "~/dt/nvim"
 8 | sources = ["*init.vim"]
 9 | target = "~/.config/nvim"
10 | 

This time, with the added allow_overwrite = true, existence of target file no longer aborts the syncing process.

`,4),i=[o];function c(p,l,r,u,d,h){return s(),a("div",null,i)}var k=n(e,[["render",c]]);export{f as __pageData,k as default}; 11 | -------------------------------------------------------------------------------- /docs/assets/config_guide_01-default-behaviours.md.bcd25e88.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as n,y as a,x as s,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const f='{"title":"Defining Default Behaviours","description":"","frontmatter":{},"relativePath":"config/guide/01-default-behaviours.md","lastUpdated":1692639959838}',e={},o=t("",4),i=[o];function c(p,l,r,u,d,h){return s(),a("div",null,i)}var k=n(e,[["render",c]]);export{f as __pageData,k as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/config_guide_02-groups.md.54c6ed9d.js: -------------------------------------------------------------------------------- 1 | import{_ as s,y as n,x as a,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const h='{"title":"Groups","description":"","frontmatter":{},"headers":[{"level":2,"title":"Overriding Default Behaviours","slug":"overriding-default-behaviours"}],"relativePath":"config/guide/02-groups.md","lastUpdated":1692639959838}',e={},o=t(`

Groups

Syncing only one group is boring. We can also add more groups.

Assuming your configuration files for VSCode lies at ~/dt/VSCode/User/settings.json, the group for syncing this file can be:

[[local]]
 2 | name = "VSCode"
 3 | base = "~/dt/VSCode"
 4 | sources = ["User/settings.json"]
 5 | target = "~/.config/Code - OSS/"
 6 | 

Appending this VSCode group to our previous config file, we obtain:










\xA0
\xA0
\xA0
\xA0
\xA0

[global]
 7 | allow_overwrite = true
 8 | 
 9 | 
10 | [[local]]
11 | name = "Neovim"
12 | base = "~/dt/nvim"
13 | sources = ["*init.vim"]
14 | target = "~/.config/nvim"
15 | [[local]]
16 | name = "VSCode"
17 | base = "~/dt/VSCode"
18 | sources = ["User/settings.json"]
19 | target = "~/.config/Code - OSS/"
20 | 

After syncing with this config, the target item ~/.config/Code - OSS/User/settins.json will be a symlink of the staged item~/.local/share/dt/staging/VSCode/User/settings.json, where the staged item mirrors the content of the source item~/dt/VSCode/User/settings.json.

Overriding Default Behaviours

But what if we exceptionally want the VSCode group to not overwrite the target file if it already exists? No worries, here is the recipe of overriding a default behaviour for the VSCode group:






\xA0
\xA0

[[local]]
21 | name = "VSCode"
22 | base = "~/dt/VSCode"
23 | sources = ["User/settings.json"]
24 | target = "~/.config/Code - OSS/"
25 | 
26 | allow_overwrite = false
27 | 

A group's behaviour will precedent the default behaviour if explicitly specified. Listing all overridable configs and their default values here:

  • allow_overwrite: false
  • hostname_sep: "@@"
  • method: "Symlink"

References to those keys can be found at Key References.

TIP

So far we have not explained why does dt-cli sync files in such a (somewhat complex) manner. You might ask:

So what is staging, in particular?

Read on for a comprehensive explanation for this decision!

`,14),p=[o];function c(i,l,r,u,d,k){return a(),n("div",null,p)}var v=s(e,[["render",c]]);export{h as __pageData,v as default}; 28 | -------------------------------------------------------------------------------- /docs/assets/config_guide_02-groups.md.54c6ed9d.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as s,y as n,x as a,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const h='{"title":"Groups","description":"","frontmatter":{},"headers":[{"level":2,"title":"Overriding Default Behaviours","slug":"overriding-default-behaviours"}],"relativePath":"config/guide/02-groups.md","lastUpdated":1692639959838}',e={},o=t("",14),p=[o];function c(i,l,r,u,d,k){return a(),n("div",null,p)}var v=s(e,[["render",c]]);export{h as __pageData,v as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/config_guide_03-syncing-methods.md.1ebfcfdd.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as t,x as s,W as o}from"./plugin-vue_export-helper.f07d1dea.js";const g='{"title":"Syncing Methods","description":"","frontmatter":{},"headers":[{"level":2,"title":"Overview","slug":"overview"},{"level":3,"title":"Copy","slug":"copy"},{"level":3,"title":"Symlink","slug":"symlink"},{"level":2,"title":"Default Method","slug":"default-method"},{"level":2,"title":"Overriding","slug":"overriding"}],"relativePath":"config/guide/03-syncing-methods.md","lastUpdated":1692639959838}',a={},n=o(`

Syncing Methods

Until the last section, no comments has been given on the stage -> symlink steps. This section explains all the details a user wants to know about this process.

TIP

If you are interested in all the details of the process, I refer you to the implementation of dt_core::syncing::sync_corehere.

Overview

There are 2 available syncing methods: Copy and Symlink, where Symlink is the chosen default.

Copy

Directly copies source items defined in sources arrays to target.

First copies source items defined in sources arrays (this is called staging) to current group's staging directory (see global.staging and name), then symlinks the staged items to target.

Default Method

dt-cli chooses Symlink as the default behaviour. The added staging step:

  • Makes it possible to organize sources according to their group names, which Copy does not.

    TIP

    This means it allows human-readable directory structures, because groups are organized by your given names. You can also create a git repository at the staging root directory if you want,

  • Makes it possible to control permission of organized items from system-level sources which you shouldn't directly modify.
  • When the target and source are the same (by accident), Copy does not guarantee integrity of the source item, while Symlink preserves the file content in the staging directory.
  • Make all further symlinks point at most to the staged items.

    TIP

    This particularly helpful when you manage user-scope systemd services with symlinks. According to systemctl(1):

    Disables one or more units. This removes all symlinks to the unit files backing the specified units from the unit configuration directory, and hence undoes any changes made by enable or link. Note that this removes all symlinks to matching unit files, including manually created symlinks, and not just those actually created by enable or link.

    That said, when disabling services (with systemctl --user disable), systemctl removes all symlinks (including user-created ones!).

    With this added staging process, your source files will be well protected.

  • Protects original items if you want to make experimental changes.

Overriding

You can always override the default syncing method to Copy conveniently by adding method = "Copy" to the [global] section:

[global]
2 | method = "Copy"
3 | 

Or specify the syncing method for a given group similarly:

[[local]]
4 | method = "Copy"
5 | 
`,17),i=[n];function c(r,l,d,h,p,u){return s(),t("div",null,i)}var y=e(a,[["render",c]]);export{g as __pageData,y as default}; 6 | -------------------------------------------------------------------------------- /docs/assets/config_guide_03-syncing-methods.md.1ebfcfdd.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as t,x as s,W as o}from"./plugin-vue_export-helper.f07d1dea.js";const g='{"title":"Syncing Methods","description":"","frontmatter":{},"headers":[{"level":2,"title":"Overview","slug":"overview"},{"level":3,"title":"Copy","slug":"copy"},{"level":3,"title":"Symlink","slug":"symlink"},{"level":2,"title":"Default Method","slug":"default-method"},{"level":2,"title":"Overriding","slug":"overriding"}],"relativePath":"config/guide/03-syncing-methods.md","lastUpdated":1692639959838}',a={},n=o("",17),i=[n];function c(r,l,d,h,p,u){return s(),t("div",null,i)}var y=e(a,[["render",c]]);export{g as __pageData,y as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/config_guide_04-host-specific.md.3be46361.js: -------------------------------------------------------------------------------- 1 | import{_ as s,y as a,x as n,W as e}from"./plugin-vue_export-helper.f07d1dea.js";const k='{"title":"Host-specific Configuration","description":"","frontmatter":{},"headers":[{"level":2,"title":"Separator","slug":"separator"},{"level":2,"title":"Source Items","slug":"source-items"},{"level":3,"title":"base","slug":"base"},{"level":3,"title":"sources","slug":"sources"}],"relativePath":"config/guide/04-host-specific.md","lastUpdated":1692639959838}',t={},o=e(`

Host-specific Configuration

When you want to maintain multiple configurations for different machines, you will have to deal with host-specific syncing. This section describes how to use this feature of dt-cli properly.

Separator

First, you have to define a hostname_sep in your config file (or not, the default value @@ has a good chance at fitting your need), globally or per-group, for example, you want your hostname separator to be QwQ by default:

[global]
 2 | hostname_sep = "QwQ"
 3 | 

Or for a group only:

[[local]]
 4 | hostname_sep = "QwQ"
 5 | 

Source Items

Knowing what your hostname_sep is, you can now specify your source items.

dt-cli automatically deals with the logic for host-specific syncing, thus you should not contain a hostname suffix when specifying your sources.

base

For example, you want to sync some user-scope systemd services on your machines:

[[local]]
 6 | name = "SystemD-services"
 7 | base = "~/dt/systemd/user"
 8 | sources = ["*.service"]
 9 | target = "~/.config/systemd/user"
10 | 
11 | hostname_sep = "@@"
12 | 

Then, on one of your machines, whose hostname is elbert, for example, the above base will be automatically expanded to ~/dt/systemd/user@@elbert first, if the expanded base exists, dt-cli will uses the expanded version; If the expanded base does not exist, dt-cli will sync the original base when it exists.

sources

Another real-world example is when you are using the same terminal emulator on multiple machines, your workstation has a 8K ultra monitor, while your laptop at home only has a monitor sized 14 inches. You will not want to have the same font sizes on the two machines.

What you could do is to separately maintain two versions of config files for the terminal emulator. When your configs are maintained under the ~/dt directory, and you are using Alacritty (for example):

~/dt/
13 | \u251C\u2500\u2500 alacritty/
14 | \u2502   \u251C\u2500\u2500 alacritty.yml@@laptop
15 | \u2502   \u2514\u2500\u2500 alacritty.yml@@workstation
16 | \u251C\u2500\u2500 nvim/
17 | \u2502   \u251C\u2500\u2500 init.vim
18 | \u2502   \u2514\u2500\u2500 ...
19 | \u2514\u2500\u2500 ...
20 | 

You want to sync all stuff under the directory ~/dt to ~/.config, you can populate your config files safely with:

[[local]]
21 | name = "All-my-configs-including-for-terminal-emulator"
22 | base = "~/dt"
23 | sources = [
24 |   "*",
25 |   ".[!.]*",
26 |   "..?*",
27 | ]
28 | target = "~/.config"
29 | 

WARNING

dt-cli will panic (not a bug) if you use globbing patterns like .* or /path/to/something/.*, because .* also expands to the parent directory, which is almost never what you want.

The globbing patterns in the above sources array is the recommended way to glob all items under a given base.

Note that we did not specifically reference the alacritty directory anywhere in the above config, because dt-cli will recursively expand directories and automatically handle host-specific items in the expanded paths. You can also specify a source only, like below:

[[local]]
30 | name = "Alacritty"
31 | base = "~/.dt/alacritty"
32 | sources = ["alacritty.yml"]
33 | target = "~/.config/alacritty"
34 | 

WARNING

Do NOT include the host-specific part in the sources array (like alacritty.yml@@laptop or alacritty.yml@@workstation), see the Error Handling section for more details on this.

`,24),c=[o];function p(i,l,r,u,d,h){return n(),a("div",null,c)}var g=s(t,[["render",p]]);export{k as __pageData,g as default}; 35 | -------------------------------------------------------------------------------- /docs/assets/config_guide_04-host-specific.md.3be46361.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as s,y as a,x as n,W as e}from"./plugin-vue_export-helper.f07d1dea.js";const k='{"title":"Host-specific Configuration","description":"","frontmatter":{},"headers":[{"level":2,"title":"Separator","slug":"separator"},{"level":2,"title":"Source Items","slug":"source-items"},{"level":3,"title":"base","slug":"base"},{"level":3,"title":"sources","slug":"sources"}],"relativePath":"config/guide/04-host-specific.md","lastUpdated":1692639959838}',t={},o=e("",24),c=[o];function p(i,l,r,u,d,h){return n(),a("div",null,c)}var g=s(t,[["render",p]]);export{k as __pageData,g as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/config_guide_05-priority.md.f64e8a7d.js: -------------------------------------------------------------------------------- 1 | import{_ as n,y as s,x as a,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const h='{"title":"Scopes","description":"","frontmatter":{},"headers":[{"level":2,"title":"Examples","slug":"examples"},{"level":3,"title":"Dropin","slug":"dropin"},{"level":3,"title":"App","slug":"app"},{"level":3,"title":"General","slug":"general"}],"relativePath":"config/guide/05-priority.md","lastUpdated":1692639959838}',e={},o=t(`

Scopes

A group's scope decides the priority of its items. When multiple groups contain a same item, only the group with the highest priority will do sync that specific item. This mechanism minimizes total number of filesystem I/O operations, which makes dt-cli to appear faster, and achieves finer control over what to sync with dt-cli without having to picking out each application's config files from your dotfile library.

TIP

This feature is meant to be used with dt-cli's command-line argument, see the Background subsection of this feature's introduction for more details.

Examples

Dropin

On Arch Linux, package fontconfig provides a file /usr/share/fontconfig/conf.avail/10-sub-pixel-rgb.conf, which works for most monitors. A drop-in group can be defined as:

[[local]]
 2 | scope = "Dropin"
 3 | name = "fontconfig-system"
 4 | base = "/usr/share/fontconfig/conf.avail/"
 5 | sources = [
 6 |   # Pixel Alignment.  Test monitor's subpixel layout at
 7 |   # <http://www.lagom.nl/lcd-test/subpixel.php>, reference:
 8 |   # <https://wiki.archlinux.org/title/Font_configuration#Pixel_alignment>
 9 |   "10-sub-pixel-rgb.conf",
10 |   # Enable lcdfilter.  Reference:
11 |   # <https://forum.endeavouros.com/t/faq-bad-font-rendering-in-firefox-and-other-programs/13430/3>
12 |   "11-lcdfilter-default.conf",
13 | ]
14 | target = "~/.config/fontconfig/conf.d"
15 | 

App

For example, a group of GUI applications under the wayland protocol could be defined as:

[[local]]
16 | scope = "General"
17 | name = "gui"
18 | base = "/path/to/your/dotfiles/library/root"
19 | sources = [
20 |   ".gtkrc-2.0",
21 |   ".local/share/icons",
22 |   ".local/share/fcitx5",
23 |   ".config/sway",
24 |   ".config/swaylock",
25 |   ".config/waybar",
26 |   ".config/dunst",
27 |   ".config/gtk-*.0",
28 | ]
29 | target = "~"
30 | 

General

This scope is mostly used in the fallback groups, for example:

[[local]]
31 | scope = "General"
32 | name = "xdg_config_home"
33 | base = "/path/to/your/dotfiles/library/root/.config"
34 | sources = [
35 |   "*",
36 | ]
37 | target = "~/.config"
38 | [[local]]
39 | scope = "General"
40 | name = "misc"
41 | base = "/path/to/your/dotfiles/library/root"
42 | sources = [
43 |   ".[!.]*",
44 |   "..?*",
45 | ]
46 | target = "~"
47 | 
`,13),p=[o];function c(l,r,i,u,k,d){return a(),s("div",null,p)}var f=n(e,[["render",c]]);export{h as __pageData,f as default}; 48 | -------------------------------------------------------------------------------- /docs/assets/config_guide_05-priority.md.f64e8a7d.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as n,y as s,x as a,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const h='{"title":"Scopes","description":"","frontmatter":{},"headers":[{"level":2,"title":"Examples","slug":"examples"},{"level":3,"title":"Dropin","slug":"dropin"},{"level":3,"title":"App","slug":"app"},{"level":3,"title":"General","slug":"general"}],"relativePath":"config/guide/05-priority.md","lastUpdated":1692639959838}',e={},o=t("",13),p=[o];function c(l,r,i,u,k,d){return a(),s("div",null,p)}var f=n(e,[["render",c]]);export{h as __pageData,f as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/config_guide_06-filename-manipulating.md.aba8fc76.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as a,x as n,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const g='{"title":"Filename Manipulating","description":"","frontmatter":{},"headers":[{"level":2,"title":"Basics","slug":"basics"},{"level":2,"title":"Per-group Rules","slug":"per-group-rules"},{"level":2,"title":"Capturing Groups","slug":"capturing-groups-as-in-regular-expressions"},{"level":3,"title":"Example:","slug":"example-repeating-the-extension-of-an-item"}],"relativePath":"config/guide/06-filename-manipulating.md","lastUpdated":1692639959838}',s={},o=t(`

Filename Manipulating

To this point, our source items must be named as their destination requires, most "dotfiles" begin their names with a literal dot (.), which is sometimes annoying when managing them (like with git). In the meantime, for items that have names not feasible to be altered --- like items from your system directory (e.g. a wallpaper image which is provided as part of a system-wide installed package) --- according to the previous sections, there seem to be no good way to have them tracked by dt-cli.

Basics

To manipulate filename of items, dt-cli provides a configurable rename option in the config file. It is an array of renaming rules, each of them constitutes of a pattern and a substitution rule. A simple renaming rule to rename all items with a "dot-" prefix (like dot-config) to a "dotfile" (like .config, in this case) can be specified in the [global] section as:

[global]
 2 | rename = [
 3 |   [
 4 |     "^dot-",  # "pattern", must be a valid regular expression
 5 |     ".",      # "substitution rule"
 6 |   ],
 7 |   # Multiple renaming rules are applied sequentially, with the previous rule's
 8 |   # output being the input of the current rule.
 9 | ]
10 | 

WARNING

Note that only the path components that appear after a group's target will be altered by dt-cli. For example, with the above renaming rule added to your [global] section, a group with target set to /some/path/dot-target will have all its items populated to the exact path /some/path/dot-target, instead of /some/path/.target.

Per-group Rules

You might have guessed it: rename rules can also be specified on a per-group basis. The way this works is that dt-cli processes renaming rules in the rename array one by one, first global.rename, then the group's rename if any.

For example, to revert the above renaming operation for a single group, you can add a rule to this group:

[[local]]
11 | name = "Group from which items must have a 'dot-' prefix after syncing"
12 | # [...omitted...]
13 | rename = [
14 |   [
15 |     "^.",     # "pattern", matches prefixing dot
16 |     "dot-",   # "substitution rule", replace the matched string into "dot-"
17 |   ],
18 | ]
19 | 

Apparently, this rule "undo"s the renaming rule in the global section which we previously defined. Items that have names prefixed with dot- in this group will first be renamed to have a . prefix, then the . prefix is renamed back to dot-.

Capturing Groups (as in regular expressions)

Since this functionality is powered by the Rust crate regex, substitution rules are supported to the extent which this crate allows. A powerful capability it provides is defining capturing groups. Capturing groups can either be named or numbered, which allows arbitrary manipulation to be applied to any synced items.

Example: Repeating the Extension of an Item

To illustrate how capturing groups work, we try to have the destination items to repeat the extension name of their corresponding source items, via capturing groups. With numbered capturing group, this rule can be written as:

[global]
20 | rename = [
21 |   [
22 |     "(.*)\\\\.(\\\\w+)",
23 |     "\${1}.\${2}.\${2}",
24 |   ]
25 | ]
26 | 

Or, with named capturing group:

[global]
27 | rename = [
28 |   [
29 |     "(?P<actual_name>.*)\\\\.(?P<extension>\\\\w+)",
30 |     "\${actual_name}.\${extension}.\${extension}",
31 |   ]
32 | ]
33 | 

The outcomes of above two approaches are identical.

`,19),p=[o];function i(r,c,l,u,d,h){return n(),a("div",null,p)}var k=e(s,[["render",i]]);export{g as __pageData,k as default}; 34 | -------------------------------------------------------------------------------- /docs/assets/config_guide_06-filename-manipulating.md.aba8fc76.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as a,x as n,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const g='{"title":"Filename Manipulating","description":"","frontmatter":{},"headers":[{"level":2,"title":"Basics","slug":"basics"},{"level":2,"title":"Per-group Rules","slug":"per-group-rules"},{"level":2,"title":"Capturing Groups","slug":"capturing-groups-as-in-regular-expressions"},{"level":3,"title":"Example:","slug":"example-repeating-the-extension-of-an-item"}],"relativePath":"config/guide/06-filename-manipulating.md","lastUpdated":1692639959838}',s={},o=t("",19),p=[o];function i(r,c,l,u,d,h){return n(),a("div",null,p)}var k=e(s,[["render",i]]);export{g as __pageData,k as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/config_guide_07-templating.md.f7f33668.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as t,x as s,W as n}from"./plugin-vue_export-helper.f07d1dea.js";const k='{"title":"Templating","description":"","frontmatter":{},"headers":[{"level":2,"title":"Setting Values","slug":"setting-values"},{"level":2,"title":"Writing Templates","slug":"writing-templates"},{"level":2,"title":"Skipping Rendering","slug":"skipping-rendering"},{"level":2,"title":"Advanced Syntaxes","slug":"advanced-syntaxes"}],"relativePath":"config/guide/07-templating.md","lastUpdated":1692639959838}',a={},o=n("",24),r=[o];function l(p,c,i,d,u,h){return s(),t("div",null,r)}var f=e(a,[["render",l]]);export{k as __pageData,f as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/config_guide_99-error-handling.md.e05b2975.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as o,x as i,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const f='{"title":"Error Handling","description":"","frontmatter":{},"headers":[{"level":2,"title":"Config Validating","slug":"config-validating"},{"level":2,"title":"Sources Expanding","slug":"sources-expanding"},{"level":2,"title":"Syncing","slug":"syncing"}],"relativePath":"config/guide/99-error-handling.md","lastUpdated":1692639959838}',s={},n=t('

Error Handling

For an application that works with your daily configuration files, it's crucial that it handles error in an expectable manner.

dt-cli looks for possible errors in 3 stages throughout its running course, this section describes the checking in details.

Config Validating

Firstly, after a config file has been successfully loaded into memory, dt-cli validates each field of the config object. Specifically, the following cases are considered invalid:

  • Any group that has empty name/base/target
  • Any group name that contains / (group names are used for subdirectory names under staging directory, so slashes are not allowed)
  • Any group that has the same base and target
  • Any group whose base contains any occurrences of hostname_sep
  • Any group whose sources contains any item that contains any occurrences of hostname_sep
  • Any source item that:
    • begins with ../ (references the parent of base directory)
    • begins with ~ or / (path is absolute)
    • is .* (bad globbing pattern, it will expand to parent directory)
    • ends with /.* (bad globbing pattern)

TIP

Checking operations in this step do not touch the filesystem, but only match string patterns. This is for spotting obvious errors as fast as possible.

Sources Expanding

If the above validating step passed successfully, dt-cli begins to iterate through every group, recursively expand all sources according to their file hierarchy, the bases are also expanded to host-specific ones wherever possible. The following cases are considered invalid while expanding sources and base:

  • The group's base exists but is not a directory
  • The group's target exists and is not a directory
  • The group's target is non-existent but cannot be created
  • When any group uses the Symlink syncing method:
    • staging exists but iis not a directory
    • staging is non-existent but cannot be created

INFO

Non-existent base will not trigger an error but only a warning that complains about not matching anything.

INFO

Broken symlinks and item types other than file or directory are ignored and warned during expanding. These items will not cause errors.

TIP

Expanding operations in this step do not create or modify anything, but only query the filesystem to check for existences and permissions.

Syncing

Finally, if no error could be found, dt-cli carefully (and efficiently) syncs the expanded source items to the target directory. During this process, according to the values of allow_overwrite, different level of logging messages will show up when encountered with existing target items. Any other cases (e.g. a directory changes its permission to readonly for no reason) unhandled by the above 2 steps will cause dt-cli to panic.

TIP

If you think there's anything missing here, your contribution is welcome! Start by following the contributing guide.

',16),a=[n];function c(r,d,l,h,g,u){return i(),o("div",null,a)}var y=e(s,[["render",c]]);export{f as __pageData,y as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/config_guide_99-error-handling.md.e05b2975.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as o,x as i,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const f='{"title":"Error Handling","description":"","frontmatter":{},"headers":[{"level":2,"title":"Config Validating","slug":"config-validating"},{"level":2,"title":"Sources Expanding","slug":"sources-expanding"},{"level":2,"title":"Syncing","slug":"syncing"}],"relativePath":"config/guide/99-error-handling.md","lastUpdated":1692639959838}',s={},n=t("",16),a=[n];function c(r,d,l,h,g,u){return i(),o("div",null,a)}var y=e(s,[["render",c]]);export{f as __pageData,y as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/config_guide_index.md.a2153e28.js: -------------------------------------------------------------------------------- 1 | import{_ as t,y as e,x as s,W as a}from"./plugin-vue_export-helper.f07d1dea.js";const h='{"title":"Basics","description":"","frontmatter":{},"relativePath":"config/guide/index.md","lastUpdated":1692639959838}',o={},n=a(`

Basics

Configurations are composed with groups. A group represents a collection of files that share some properties. A local group is added to the configuration file by adding a [[local]] section.

Assuming your configuration files for Neovim reside in ~/dt/nvim, and all match the globbing pattern *init.vim, a minimal working example can then be configured as:

[[local]]
2 | name = "Neovim"
3 | base = "~/dt/nvim"
4 | sources = ["*init.vim"]
5 | target = "~/.config/nvim"
6 | 

This content causes dt-cli to perform the following steps:

  1. Create a "staging" directory at $XDG_DATA_HOME/dt/staging if the XDG_DATA_HOME environment variable is set, otherwise at ~/.local/share/dt/staging;
  2. Create the group's staging directory under the staging directory: [...]/dt/staging/Neovim;
  3. Find all items (recursively if an item is a directory) that matches glob ~/dt/nvim/*init.vim and store them back in the sources array;
  4. For each item in the updated sources array, first copy it to the group's staging directory (~/.local/share/dt/staging/Neovim), then symlink it to the target directory (~/.config/nvim), abort if a target file already exists.

TIP

Details of above steps are explained in the Syncing Methods section.

WARNING

Aborting on existing target files is probably not what you want. Read on for a better solution!

`,8),i=[n];function c(r,p,d,l,u,g){return s(),e("div",null,i)}var k=t(o,[["render",c]]);export{h as __pageData,k as default}; 7 | -------------------------------------------------------------------------------- /docs/assets/config_guide_index.md.a2153e28.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as t,y as e,x as s,W as a}from"./plugin-vue_export-helper.f07d1dea.js";const h='{"title":"Basics","description":"","frontmatter":{},"relativePath":"config/guide/index.md","lastUpdated":1692639959838}',o={},n=a("",8),i=[n];function c(r,p,d,l,u,g){return s(),e("div",null,i)}var k=t(o,[["render",c]]);export{h as __pageData,k as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/config_key-references.md.df45381e.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as a,x as o,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const k='{"title":"Key References","description":"","frontmatter":{},"headers":[{"level":2,"title":"Global","slug":"global"},{"level":3,"title":"staging","slug":"staging"},{"level":3,"title":"method","slug":"method"},{"level":3,"title":"allow_overwrite","slug":"allow-overwrite"},{"level":3,"title":"ignore_failure","slug":"ignore-failure"},{"level":3,"title":"renderable","slug":"renderable"},{"level":3,"title":"hostname_sep","slug":"hostname-sep"},{"level":3,"title":"rename","slug":"rename"},{"level":2,"title":"Local Groups","slug":"local-groups"},{"level":3,"title":"name","slug":"name"},{"level":3,"title":"scope","slug":"scope"},{"level":3,"title":"base","slug":"base"},{"level":3,"title":"sources","slug":"sources"},{"level":3,"title":"target","slug":"target"},{"level":3,"title":"ignored","slug":"ignored"},{"level":3,"title":"method","slug":"method-1"},{"level":3,"title":"allow_overwrite","slug":"allow-overwrite-1"},{"level":3,"title":"ignore_failure","slug":"ignore-failure-1"},{"level":3,"title":"renderable","slug":"renderable-1"},{"level":3,"title":"hostname_sep","slug":"hostname-sep-1"},{"level":3,"title":"rename","slug":"rename-1"}],"relativePath":"config/key-references.md","lastUpdated":1692639959838}',n={},s=t("",93),r=[s];function i(l,c,p,d,h,u){return o(),a("div",null,r)}var f=e(n,[["render",i]]);export{k as __pageData,f as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/contributing.md.8dac4517.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as t,x as a,W as r}from"./plugin-vue_export-helper.f07d1dea.js";const m='{"title":"Contributing","description":"","frontmatter":{},"headers":[{"level":2,"title":"Making your changes","slug":"making-your-changes"},{"level":2,"title":"License","slug":"license"}],"relativePath":"contributing.md","lastUpdated":1692639959838}',n={},o=r(`

Contributing

Thank you for your interest in contributing! There are many ways to contribute to dt. You can start with examining unchecked items in the roadmap, or discuss some features to be added to the roadmap.

When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.

Please note we have a code of conduct, please follow it in all your interactions with the project.

Making your changes

  1. Fork a copy of dt into your GitHub account and clone the forked repository to your development machine:
    $ git clone https://github.com/\${your_account}/dt.git && cd dt
    2 | 
  2. Install latest stable version of Rust, then make your commits. Please follow the conventional commit specification when writing your commit messages.
  3. If features are added/updated, also add/update tests and documents and check if the tests are passed:
    $ cargo test
    3 | 
  4. Resolve errors and warnings given by cargo-clippy:
    $ cargo clippy
    4 | 
  5. Format your changes with cargo-fmt:
    $ cargo fmt -- --verbose
    5 | 
  6. Submit a pull request and discuss the possible changes with the owner/maintainer, update your pull request if necessary.

License

By contributing, you agree that your contributions will be licenced under MIT or Apache 2.0 license.

`,8),s=[o];function i(c,l,d,h,p,u){return a(),t("div",null,s)}var b=e(n,[["render",i]]);export{m as __pageData,b as default}; 6 | -------------------------------------------------------------------------------- /docs/assets/contributing.md.8dac4517.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as t,x as a,W as r}from"./plugin-vue_export-helper.f07d1dea.js";const m='{"title":"Contributing","description":"","frontmatter":{},"headers":[{"level":2,"title":"Making your changes","slug":"making-your-changes"},{"level":2,"title":"License","slug":"license"}],"relativePath":"contributing.md","lastUpdated":1692639959838}',n={},o=r("",8),s=[o];function i(c,l,d,h,p,u){return a(),t("div",null,s)}var b=e(n,[["render",i]]);export{m as __pageData,b as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/features_01-host-specific.md.857720b4.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as t,x as o,W as s}from"./plugin-vue_export-helper.f07d1dea.js";const p='{"title":"Host-specific Syncing","description":"","frontmatter":{},"headers":[{"level":2,"title":"Background","slug":"background"},{"level":2,"title":"Hostname Suffix","slug":"hostname-suffix"}],"relativePath":"features/01-host-specific.md","lastUpdated":1692639959838}',i={},a=s('

Host-specific Syncing [Examples]

Background

With more servers there must also come more configuration files.

When you own more than one machine, you will eventually face the problem that one configuration file that works perfectly on one machine does not work well on another, be it due to their monitor sizes, network conditions, architectures, etc..

Hostname Suffix

What you want is to populate different configuration files for different machines. To allow multiple items with the same name name, dt-cli checks for an additional hostname suffix for every source item, and ignores those items which are meant for other hosts. dt-cli works with them quite intuitively. In short, it ignores items for other machines, and syncs items for current machine whenever possible.

INFO

Specifically, with hostname suffix defined, source items can be (virtually) categorized into 3 types:

  • Current: Items that are host-specific, and are for current machine only;
  • General: Items that are for all machines;
  • Other: Items that are host-specific, but are for some other machine.

dt-cli will sync items that are of type Current if they exist; if no Current item exists, dt-cli finds General items and sync them. Items of type Other are ignored for current machine.

A hostname suffix comprises of a hostname separator and a hostname:

  • Hostname separator: Defined in configuration file as hostname_sep, globally or per-group.
  • Hostname: Current machine's hostname.

Multiple Occurrences of hostname_sep

To eliminate ambiguity, the hostname separator should appear at most once in any of the source items. Multiple occurrences of the hostname separator will cause dt-cli to panic.

The default value (when not configured) for hostname_sep is @@. If a directory is marked as host-specific, all of its children will only be synced when the directory is for current machine.

',11),n=[a];function c(r,l,h,d,f,m){return o(),t("div",null,n)}var g=e(i,[["render",c]]);export{p as __pageData,g as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/features_01-host-specific.md.857720b4.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as t,x as o,W as s}from"./plugin-vue_export-helper.f07d1dea.js";const p='{"title":"Host-specific Syncing","description":"","frontmatter":{},"headers":[{"level":2,"title":"Background","slug":"background"},{"level":2,"title":"Hostname Suffix","slug":"hostname-suffix"}],"relativePath":"features/01-host-specific.md","lastUpdated":1692639959838}',i={},a=s("",11),n=[a];function c(r,l,h,d,f,m){return o(),t("div",null,n)}var g=e(i,[["render",c]]);export{p as __pageData,g as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/features_02-scope.md.bdd8e6d2.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as o,x as n,W as s}from"./plugin-vue_export-helper.f07d1dea.js";const f='{"title":"Priority Resolving","description":"","frontmatter":{},"headers":[{"level":2,"title":"Background","slug":"background"},{"level":2,"title":"Scope","slug":"scope"}],"relativePath":"features/02-scope.md","lastUpdated":1692639959838}',a={},t=s(`

Priority Resolving [Examples]

Background

Since dt-cli syncs your dotfiles on a per-group basis, you don't want to run through all of the groups when only a single file is modified in your dotfile library. For example, when you updated your shell init script, you might run the following command:

$ dt-cli shell
 2 |  INFO  dt_core::syncing > Local group: [shell]
 3 | 

Nevertheless, sometimes you have to run a full sync, which involves all of your defined groups in your config file. It may look like this:

$ dt-cli
 4 |  INFO  dt_core::syncing > Local group: [gdb]
 5 |  INFO  dt_core::syncing > Local group: [ssh]
 6 |  INFO  dt_core::syncing > Local group: [gpg]
 7 |  INFO  dt_core::syncing > Local group: [systemd]
 8 |  INFO  dt_core::syncing > Local group: [dt]
 9 |  INFO  dt_core::syncing > Local group: [nvim]
10 |  INFO  dt_core::syncing > Local group: [fontconfig]
11 |  INFO  dt_core::syncing > Local group: [shell]
12 |  INFO  dt_core::syncing > Local group: [gui]
13 |  INFO  dt_core::syncing > Local group: [xdg_config_home]
14 |  INFO  dt_core::syncing > Local group: [misc]
15 | 

Some groups may contain overlapping source items, in the above example, group xdg_config_home contains fontconfig and dt's base directories. It's neither friendly nor efficient for dt-cli to sync the same item twice or even more times: it's slow, and the final result depends on the order of the definitions of the groups.

Scope

dt-cli solves this problem by defining an extra attribute scope for each group.

A group's scope decides the priority of it being synced. There are 3 predefined scopes, namely Dropin, App and General. The names are pretty much self-explanatory:

  • General groups have the lowest priority. They are typically meant for the parent directories of your dotfile library.
  • Dropin groups have the highest priority. They are typically meant for those items that come from external sources as drop-in replacements, such as files from a system directory that is managed by your system's package manager.
  • App groups have medium priority. As the name implies, it is meant for some specific application, for example, a group containing your config files for GUI applications, or a group containing your shell/editor preferences/init scripts, etc..

INFO

A scope key in a group's definition is optional. When omitted, the default value is General.

TIP

Generally, a larger scope has a lower priority.

WARNING

If a file is included in multiple groups that have the same scope, it will only be synced by the first group appeared in your config file, later defined groups (with the same scope) won't repeatedly sync the file.

`,14),c=[t];function p(i,r,l,d,u,g){return n(),o("div",null,c)}var y=e(a,[["render",p]]);export{f as __pageData,y as default}; 16 | -------------------------------------------------------------------------------- /docs/assets/features_02-scope.md.bdd8e6d2.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as o,x as n,W as s}from"./plugin-vue_export-helper.f07d1dea.js";const f='{"title":"Priority Resolving","description":"","frontmatter":{},"headers":[{"level":2,"title":"Background","slug":"background"},{"level":2,"title":"Scope","slug":"scope"}],"relativePath":"features/02-scope.md","lastUpdated":1692639959838}',a={},t=s("",14),c=[t];function p(i,r,l,d,u,g){return n(),o("div",null,c)}var y=e(a,[["render",p]]);export{f as __pageData,y as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/features_03-filename-manipulating.md.4f4fb6b1.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as a,x as n,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const m='{"title":"Filename Manipulating","description":"","frontmatter":{},"headers":[{"level":2,"title":"Background","slug":"background"},{"level":2,"title":"rename","slug":"rename"}],"relativePath":"features/03-filename-manipulating.md","lastUpdated":1692639959838}',s={},o=t(`

Filename Manipulating [Examples]

Being able to have different names for the source items and their destination items arises as a vital need in some use cases. dt-cli offers this utility via a convenient and versatile way, with the power of regular expressions.

Background

This is not new. GNU Stow has an option --dotfiles, which explains this in man:stow(8):

--dotfiles:

Enable special handling for "dotfiles" (files or folders whose name begins with a period) in the package directory. If this option is enabled, Stow will add a preprocessing step for each file or folder whose name begins with "dot-", and replace the "dot-" prefix in the name by a period (.). This is useful when Stow is used to manage collections of dotfiles, to avoid having a package directory full of hidden files.

dt-cli's filename manipulating capability functions in a similar way, but in a much more powerful way: you can specify arbitrary renaming rules, thanks to the flexibility of regular expressions; you can also specify arbitrary number of renaming rules, multiple defined rules will apply to your items one-after-another.

rename

Renaming rules are defined in the rename array. A renaming rule is represented by a 2-tuple in the config file, where the first element is a regular expression pattern and the second element is a substitution rule. To achieve an identical behaviour as the --dotfiles option with GNU Stow, a renaming rule can be set in global section of the config file as:

[global]
 2 | rename = [
 3 |   [
 4 |     "^dot-",  # "pattern"
 5 |     ".",      # "substitution rule"
 6 |   ],
 7 |   # Multiple renaming rules are applied sequentially, with the previous rule's
 8 |   # output being the input of the current rule.
 9 | ]
10 | 

Since dt-cli's renaming capability is powered by regular expressions, it also supports regular expression features like capturing groups, which allows infinitely flexible filename manipulation. More examples can be found in the hands-on guide.

`,10),i=[o];function r(l,p,c,u,d,h){return n(),a("div",null,i)}var g=e(s,[["render",r]]);export{m as __pageData,g as default}; 11 | -------------------------------------------------------------------------------- /docs/assets/features_03-filename-manipulating.md.4f4fb6b1.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as a,x as n,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const m='{"title":"Filename Manipulating","description":"","frontmatter":{},"headers":[{"level":2,"title":"Background","slug":"background"},{"level":2,"title":"rename","slug":"rename"}],"relativePath":"features/03-filename-manipulating.md","lastUpdated":1692639959838}',s={},o=t("",10),i=[o];function r(l,p,c,u,d,h){return n(),a("div",null,i)}var g=e(s,[["render",r]]);export{m as __pageData,g as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/features_04-templating.md.adcf805f.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as a,x as n,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const m='{"title":"Templating","description":"","frontmatter":{},"headers":[{"level":2,"title":"Background","slug":"background"},{"level":2,"title":"Syntax","slug":"syntax"},{"level":3,"title":"Configuring","slug":"configuring"},{"level":3,"title":"Applying","slug":"applying"}],"relativePath":"features/04-templating.md","lastUpdated":1692639959838}',s={},o=t(`

Templating[Examples]

Background

As is always the case, there are quite a few applications that share a same set of properties. For example, we want to have uniform looks for Qt and GTK applications. Templating utility is developed under the DRY (Don't Repeat Yourself) principle, it allows to manage these shared properties in one place: change once, apply everywhere.

Syntax

Configuring

To manage shared properties, add a section [context] to dt-cli's config file. For example, to set a property named cursor-size for the gui group to value 24:

# ~/.config/dt/cli.toml
 2 | ...
 3 | [context]
 4 | gui.cursor-size = 24
 5 | ## Or, as TOML allows it:
 6 | #[context.gui]
 7 | #cursor-size = 24
 8 | ...
 9 | 

See the configuration guide for detailed usages.

Applying

dt-cli uses Rust's Handlebars crate to render templates. Handlebars is tested and widely used, according to its descriptions:

INFO

Handlebars-rust is the template engine that renders the official Rust website rust-lang.org.

For example, to apply a property named cursor-size to all source files under the gui group:

...
10 | gtk-cursor-theme-size={{{ gui.cursor-size }}}
11 | ...
12 | 

With context.gui.cursor-size being set to 24 (as in previous section), the above template (in a group with name gui) will be rendered as:

# ~/.config/gtk-3.0/settings.ini
13 | ...
14 | gtk-cursor-theme-size=24
15 | ...
16 | 

INFO

The time consumed while rendering can be quite noticeable if the template being rendered is huge. To skip rendering for a group, use the renderable = false option in the config file.

dt-cli also supports basic control flow syntaxes like looping and conditioning, and other helper directives that boosts the productiveness of the templating system. Check out the hands-on guide if interested!

`,17),i=[o];function r(c,p,l,d,u,g){return n(),a("div",null,i)}var f=e(s,[["render",r]]);export{m as __pageData,f as default}; 17 | -------------------------------------------------------------------------------- /docs/assets/features_04-templating.md.adcf805f.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as a,x as n,W as t}from"./plugin-vue_export-helper.f07d1dea.js";const m='{"title":"Templating","description":"","frontmatter":{},"headers":[{"level":2,"title":"Background","slug":"background"},{"level":2,"title":"Syntax","slug":"syntax"},{"level":3,"title":"Configuring","slug":"configuring"},{"level":3,"title":"Applying","slug":"applying"}],"relativePath":"features/04-templating.md","lastUpdated":1692639959838}',s={},o=t("",17),i=[o];function r(c,p,l,d,u,g){return n(),a("div",null,i)}var f=e(s,[["render",r]]);export{m as __pageData,f as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/features_index.md.27034f59.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as t,x as a,W as s}from"./plugin-vue_export-helper.f07d1dea.js";const m='{"title":"Features","description":"","frontmatter":{},"relativePath":"features/index.md","lastUpdated":1692639959838}',i={},o=s('

Features

INFO

This chapter and its subsequent sections illustrates why these features are desirable and how dt-cli solves them in an elegant way. For explicit configuration examples, please refer to the [Examples] links after each feature below.

dt-cli helps you to solve a slew of problems that are pains in the neck when managing dotfiles. To be specific, dt-cli supports:

',4),n=[o];function l(r,c,p,h,u,d){return a(),t("div",null,n)}var g=e(i,[["render",l]]);export{m as __pageData,g as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/features_index.md.27034f59.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as e,y as t,x as a,W as s}from"./plugin-vue_export-helper.f07d1dea.js";const m='{"title":"Features","description":"","frontmatter":{},"relativePath":"features/index.md","lastUpdated":1692639959838}',i={},o=s("",4),n=[o];function l(r,c,p,h,u,d){return a(),t("div",null,n)}var g=e(i,[["render",l]]);export{m as __pageData,g as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/index.md.d0fcd5a8.js: -------------------------------------------------------------------------------- 1 | import{_ as t,y as e,x as i,W as a}from"./plugin-vue_export-helper.f07d1dea.js";const u='{"title":"Overview","description":"","frontmatter":{},"headers":[{"level":2,"title":"Name","slug":"name"},{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Configuration","slug":"configuration"}],"relativePath":"index.md","lastUpdated":1692639959838}',d={},o=a(`

Overview

Name

dt-cli is a highly customizable dotfile manager.

Usage

dt-cli takes a configuration file and issues syncing process defined in the config file. Put your config file at ~/.config/dt/cli.toml and run

$ dt-cli -c ~/.config/dt/cli.toml
2 | 

to start syncing. Note the path in this example (~/.config/dt/cli.toml) is also the default path, so the below command (calling dt-cli with no argument) does the same thing as above:

$ dt-cli
3 | 

Other command line flags & options

FlagsDescription
-d|--dry-runShows changes to be made without actually syncing files.
-h|--helpPrints help information.
-q|--quietDecreases logging verbosity.
-v|--verboseIncreases logging verbosity.
-V|--versionPrints version information.
OptionsDescription
-c|--config-path <path>Specifies path to config file.
ArgsDescription
<group-name>...Specifies name(s) of the group(s) to be processed

Configuration

Create a configuration file by following the steps in the hands-on guide.

`,14),l=[o];function n(r,s,c,h,g,f){return i(),e("div",null,l)}var y=t(d,[["render",n]]);export{u as __pageData,y as default}; 4 | -------------------------------------------------------------------------------- /docs/assets/index.md.d0fcd5a8.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as t,y as e,x as i,W as a}from"./plugin-vue_export-helper.f07d1dea.js";const u='{"title":"Overview","description":"","frontmatter":{},"headers":[{"level":2,"title":"Name","slug":"name"},{"level":2,"title":"Usage","slug":"usage"},{"level":2,"title":"Configuration","slug":"configuration"}],"relativePath":"index.md","lastUpdated":1692639959838}',d={},o=a("",14),l=[o];function n(r,s,c,h,g,f){return i(),e("div",null,l)}var y=t(d,[["render",n]]);export{u as __pageData,y as default}; 2 | -------------------------------------------------------------------------------- /docs/assets/installation.md.d7a4ab95.js: -------------------------------------------------------------------------------- 1 | import{_ as a,y as e,x as t,W as l}from"./plugin-vue_export-helper.f07d1dea.js";const g='{"title":"Install","description":"","frontmatter":{},"headers":[{"level":2,"title":"AUR","slug":"aur"},{"level":2,"title":"Alternative ways","slug":"alternative-ways"}],"relativePath":"installation.md","lastUpdated":1692639959838}',n={},r=l(`

Install

AUR

dt-cli is in the AUR, you can install it with your favorite package manager:

$ paru -S dt-cli
2 | 

Alternative ways

Alternatively, you can:

  • Download latest release from GitHub

  • Install from crates.io:

    $ cargo install dt-cli
    3 | 
  • Build from source:

    $ git clone git@github.com:blurgyy/dt.git
    4 | $ cd dt
    5 | $ cargo test --release
    6 | $ cargo install --path=dt-cli
    7 | 
`,7),s=[r];function i(o,c,d,p,h,u){return t(),e("div",null,s)}var f=a(n,[["render",i]]);export{g as __pageData,f as default}; 8 | -------------------------------------------------------------------------------- /docs/assets/installation.md.d7a4ab95.lean.js: -------------------------------------------------------------------------------- 1 | import{_ as a,y as e,x as t,W as l}from"./plugin-vue_export-helper.f07d1dea.js";const g='{"title":"Install","description":"","frontmatter":{},"headers":[{"level":2,"title":"AUR","slug":"aur"},{"level":2,"title":"Alternative ways","slug":"alternative-ways"}],"relativePath":"installation.md","lastUpdated":1692639959838}',n={},r=l("",7),s=[r];function i(o,c,d,p,h,u){return t(),e("div",null,s)}var f=a(n,[["render",i]]);export{g as __pageData,f as default}; 2 | -------------------------------------------------------------------------------- /docs/config/guide/01-default-behaviours.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Defining Default Behaviours | dt-cli 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

Defining Default Behaviours

Note that when syncing our configuration files for Neovim in the basic config, dt-cli aborts on existing target files. When populating items to another machine, it's better to directly overwrite (assuming you know what you are doing) the target file, so the basic config is suboptimal. What we could do is to additionally define the default overwriting behaviours with a [global] section in the configuration:

 
 
 
 






[global]
25 | allow_overwrite = true
26 | 
27 | 
28 | [[local]]
29 | name = "Neovim"
30 | base = "~/dt/nvim"
31 | sources = ["*init.vim"]
32 | target = "~/.config/nvim"
33 | 

This time, with the added allow_overwrite = true, existence of target file no longer aborts the syncing process.

34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/config/guide/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Basics | dt-cli 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

Basics

Configurations are composed with groups. A group represents a collection of files that share some properties. A local group is added to the configuration file by adding a [[local]] section.

Assuming your configuration files for Neovim reside in ~/dt/nvim, and all match the globbing pattern *init.vim, a minimal working example can then be configured as:

[[local]]
25 | name = "Neovim"
26 | base = "~/dt/nvim"
27 | sources = ["*init.vim"]
28 | target = "~/.config/nvim"
29 | 

This content causes dt-cli to perform the following steps:

  1. Create a "staging" directory at $XDG_DATA_HOME/dt/staging if the XDG_DATA_HOME environment variable is set, otherwise at ~/.local/share/dt/staging;
  2. Create the group's staging directory under the staging directory: [...]/dt/staging/Neovim;
  3. Find all items (recursively if an item is a directory) that matches glob ~/dt/nvim/*init.vim and store them back in the sources array;
  4. For each item in the updated sources array, first copy it to the group's staging directory (~/.local/share/dt/staging/Neovim), then symlink it to the target directory (~/.config/nvim), abort if a target file already exists.

TIP

Details of above steps are explained in the Syncing Methods section.

WARNING

Aborting on existing target files is probably not what you want. Read on for a better solution!

30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /docs/contributing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Contributing | dt-cli 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

Contributing

Thank you for your interest in contributing! There are many ways to contribute to dt. You can start with examining unchecked items in the roadmap, or discuss some features to be added to the roadmap.

When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.

Please note we have a code of conduct, please follow it in all your interactions with the project.

Making your changes

  1. Fork a copy of dt into your GitHub account and clone the forked repository to your development machine:
    $ git clone https://github.com/${your_account}/dt.git && cd dt
    25 | 
  2. Install latest stable version of Rust, then make your commits. Please follow the conventional commit specification when writing your commit messages.
  3. If features are added/updated, also add/update tests and documents and check if the tests are passed:
    $ cargo test
    26 | 
  4. Resolve errors and warnings given by cargo-clippy:
    $ cargo clippy
    27 | 
  5. Format your changes with cargo-fmt:
    $ cargo fmt -- --verbose
    28 | 
  6. Submit a pull request and discuss the possible changes with the owner/maintainer, update your pull request if necessary.

License

By contributing, you agree that your contributions will be licenced under MIT or Apache 2.0 license.

29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/features/01-host-specific.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Host-specific Syncing | dt-cli 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

Host-specific Syncing [Examples]

Background

With more servers there must also come more configuration files.

When you own more than one machine, you will eventually face the problem that one configuration file that works perfectly on one machine does not work well on another, be it due to their monitor sizes, network conditions, architectures, etc..

Hostname Suffix

What you want is to populate different configuration files for different machines. To allow multiple items with the same name name, dt-cli checks for an additional hostname suffix for every source item, and ignores those items which are meant for other hosts. dt-cli works with them quite intuitively. In short, it ignores items for other machines, and syncs items for current machine whenever possible.

INFO

Specifically, with hostname suffix defined, source items can be (virtually) categorized into 3 types:

  • Current: Items that are host-specific, and are for current machine only;
  • General: Items that are for all machines;
  • Other: Items that are host-specific, but are for some other machine.

dt-cli will sync items that are of type Current if they exist; if no Current item exists, dt-cli finds General items and sync them. Items of type Other are ignored for current machine.

A hostname suffix comprises of a hostname separator and a hostname:

  • Hostname separator: Defined in configuration file as hostname_sep, globally or per-group.
  • Hostname: Current machine's hostname.

Multiple Occurrences of hostname_sep

To eliminate ambiguity, the hostname separator should appear at most once in any of the source items. Multiple occurrences of the hostname separator will cause dt-cli to panic.

The default value (when not configured) for hostname_sep is @@. If a directory is marked as host-specific, all of its children will only be synced when the directory is for current machine.

25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/features/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Features | dt-cli 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

Features

INFO

This chapter and its subsequent sections illustrates why these features are desirable and how dt-cli solves them in an elegant way. For explicit configuration examples, please refer to the [Examples] links after each feature below.

dt-cli helps you to solve a slew of problems that are pains in the neck when managing dotfiles. To be specific, dt-cli supports:

25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/home-everywhere.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blurgyy/dt/41ed4c97cac079b7492e2b5187a394f4b87ff6cc/docs/home-everywhere.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Overview | dt-cli 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

Overview

Name

dt-cli is a highly customizable dotfile manager.

Usage

dt-cli takes a configuration file and issues syncing process defined in the config file. Put your config file at ~/.config/dt/cli.toml and run

$ dt-cli -c ~/.config/dt/cli.toml
25 | 

to start syncing. Note the path in this example (~/.config/dt/cli.toml) is also the default path, so the below command (calling dt-cli with no argument) does the same thing as above:

$ dt-cli
26 | 

Other command line flags & options

FlagsDescription
-d|--dry-runShows changes to be made without actually syncing files.
-h|--helpPrints help information.
-q|--quietDecreases logging verbosity.
-v|--verboseIncreases logging verbosity.
-V|--versionPrints version information.
OptionsDescription
-c|--config-path <path>Specifies path to config file.
ArgsDescription
<group-name>...Specifies name(s) of the group(s) to be processed

Configuration

Create a configuration file by following the steps in the hands-on guide.

27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/installation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Install | dt-cli 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

Install

AUR

dt-cli is in the AUR, you can install it with your favorite package manager:

$ paru -S dt-cli
25 | 

Alternative ways

Alternatively, you can:

  • Download latest release from GitHub

  • Install from crates.io:

    $ cargo install dt-cli
    26 | 
  • Build from source:

    $ git clone git@github.com:blurgyy/dt.git
    27 | $ cd dt
    28 | $ cargo test --release
    29 | $ cargo install --path=dt-cli
    30 | 
31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /dt-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dt-cli" 3 | description = "$HOME, $HOME everywhere" 4 | version = "0.7.10" 5 | edition = "2021" 6 | authors = ["Gaoyang Zhang "] 7 | documentation = "https://dt.cli.rs/" 8 | license = "MIT OR Apache-2.0" 9 | repository = "https://github.com/blurgyy/dt" 10 | categories = ["command-line-utilities"] 11 | keywords = ["config", "dotfile", "manager"] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | dirs = "5.0.1" 17 | dt-core = { path = "../dt-core", version = "0.7.10" } 18 | log = "0.4.20" 19 | pretty_env_logger = "0.5.0" 20 | structopt = "0.3.26" 21 | 22 | [target.armv7-unknown-linux-gnueabihf] 23 | linker = "arm-linux-gnueabihf-gcc" 24 | 25 | [target.aarch64-unknown-linux-gnu] 26 | linker = "aarch64-linux-gnu-gcc" 27 | 28 | # Author: Blurgy 29 | # Date: Sep 17 2021, 21:32 [CST] 30 | -------------------------------------------------------------------------------- /dt-cli/README.md: -------------------------------------------------------------------------------- 1 | # DT 2 | 3 | [![release](https://github.com/blurgyy/dt/actions/workflows/release.yml/badge.svg)](https://github.com/blurgyy/dt/actions/workflows/release.yml) 4 | [![tests](https://github.com/blurgyy/dt/actions/workflows/tests.yml/badge.svg)](https://github.com/blurgyy/dt/actions/workflows/tests.yml) 5 | [![docs](https://github.com/blurgyy/dt/actions/workflows/docs.yml/badge.svg)](https://dt.cli.rs/) 6 | [![crates.io](https://img.shields.io/crates/v/dt-cli?style=flat&labelColor=1C2C2E&color=C96329&logo=Rust&logoColor=white)](https://crates.io/crates/dt-cli) 7 | 8 | `DT` allows you to sync/populate configuration files at will. It currently 9 | provides a CLI tool `dt-cli`. 10 | 11 | ## Usage 12 | 13 | The command line interface `dt-cli` accepts a path to the configuration file 14 | as an argument and performs the syncing process specified in the file. 15 | 16 | Configuration guides and detailed usages can be found in the 17 | [documentations](https://dt.cli.rs/). 18 | 19 | ### Example 20 | 21 | A simple working configuration file to sync all files from `~/dt/nvim` to 22 | `~/.config/nvim` that matches `*init.vim` can be written as: 23 | 24 | ```toml 25 | [[local]] 26 | name = "Neovim Configs" 27 | base = "~/dt/nvim" 28 | sources = ["*init.vim"] 29 | target = "~/.config/nvim" 30 | ``` 31 | 32 | :warning: **STOP HERE if you don't know what you are doing, or have not backed 33 | up existing files under `~/.config/nvim`.** 34 | 35 | Save above config to `~/.config/dt/cli.toml` and run 36 | 37 | ```shell 38 | $ dt-cli -c ~/.config/dt/cli.toml 39 | ``` 40 | 41 | to start syncing. Note the path in this example (`~/.config/dt/cli.toml`) is 42 | also the default path, so the below command (calling `dt-cli` with no argument) 43 | does the same thing as above: 44 | 45 | ```shell 46 | $ dt-cli 47 | ``` 48 | 49 | **Other command line flags & options** 50 | 51 | | Flags | Description | 52 | |---:|:---| 53 | | `-d\|--dry-run` | Shows changes to be made without actually syncing files. | 54 | | `-h\|--help` | Prints help information. | 55 | | `-q\|--quiet` | Decreases logging verbosity. | 56 | | `-v\|--verbose` | Increases logging verbosity. | 57 | | `-V\|--version` | Prints version information. | 58 | 59 | | Options | Description | 60 | |---:|:---| 61 | | `-c\|--config-path` `` | Specifies path to config file. | 62 | 63 | | Args | Description | 64 | |---:|:---| 65 | | `...` | Specifies name(s) of the group(s) to be processed | 66 | 67 | ## Install 68 | 69 | ### AUR 70 | 71 | `dt-cli` is in the [AUR](https://aur.archlinux.org/packages/dt-cli/), you can 72 | install it with your favorite package manager: 73 | 74 | ```shell 75 | $ paru -S dt-cli 76 | ``` 77 | 78 | ### Alternative ways 79 | 80 | Alternatively, you can: 81 | 82 | - Download latest [release](https://github.com/blurgyy/dt/releases/latest) 83 | from GitHub 84 | - Install from [crates.io](https://crates.io/crates/dt-cli/): 85 | 86 | ```shell 87 | $ cargo install dt-cli 88 | ``` 89 | 90 | - Build from source: 91 | 92 | ```shell 93 | $ git clone git@github.com:blurgyy/dt.git 94 | $ cd dt 95 | $ cargo test --release 96 | $ cargo install --path=dt-cli 97 | ``` 98 | 99 | ## Contributing 100 | 101 | There are numerous ways to help with this project. Let's [get 102 | started](https://github.com/blurgyy/dt/blob/main/CONTRIBUTING.md)! 103 | 104 | ## License 105 | 106 | Licensed under the the MIT license or 107 | Apache License, Version 2.0 , at 108 | your option. This file may not be copied, modified, or distributed except 109 | according to those terms. 110 | -------------------------------------------------------------------------------- /dt-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use structopt::StructOpt; 4 | 5 | use dt_core::{ 6 | config::DTConfig, 7 | error::{Error as AppError, Result}, 8 | syncing, 9 | utils::default_config_path, 10 | }; 11 | 12 | #[derive(StructOpt, Debug)] 13 | #[structopt( 14 | global_settings(&[structopt::clap::AppSettings::ColoredHelp]) 15 | )] 16 | struct Opt { 17 | /// Specifies path to config file 18 | #[structopt(short, long)] 19 | config_path: Option, 20 | 21 | ///Specifies name(s) of the group(s) to be processed" 22 | #[structopt(name = "group_name")] 23 | group_names: Vec, 24 | 25 | /// Shows changes to be made without actually syncing files 26 | #[structopt(short, long)] 27 | dry_run: bool, 28 | 29 | /// Increases logging verbosity 30 | #[structopt(short, long, parse(from_occurrences), conflicts_with = "quiet")] 31 | verbose: i8, 32 | 33 | /// Decreases logging verbosity 34 | #[structopt(short, long, parse(from_occurrences), conflicts_with = "verbose")] 35 | quiet: i8, 36 | } 37 | 38 | fn run() -> Result<()> { 39 | let opt = Opt::from_args(); 40 | setup(opt.verbose - opt.quiet + { opt.dry_run as i8 }); 41 | 42 | log::trace!("Parsed command line: {:#?}", &opt); 43 | 44 | let config_path = match opt.config_path { 45 | Some(p) => { 46 | log::debug!("Using config file '{}' (from command line)", p.display(),); 47 | p 48 | } 49 | None => default_config_path("DT_CLI_CONFIG_PATH", "DT_CONFIG_DIR", &["cli.toml"])?, 50 | }; 51 | 52 | let config = DTConfig::from_path(config_path)?; 53 | // Filter groups when appropriate 54 | let config = if opt.group_names.is_empty() { 55 | config 56 | } else { 57 | config.filter_names(opt.group_names) 58 | }; 59 | syncing::sync(config, opt.dry_run)?; 60 | Ok(()) 61 | } 62 | 63 | fn setup(verbosity: i8) { 64 | match verbosity { 65 | i8::MIN..=-2 => std::env::set_var("RUST_LOG", "error"), 66 | -1 => std::env::set_var("RUST_LOG", "warn"), 67 | 0 => std::env::set_var( 68 | "RUST_LOG", 69 | std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_owned()), 70 | ), 71 | 1 => std::env::set_var("RUST_LOG", "debug"), 72 | 2..=i8::MAX => std::env::set_var("RUST_LOG", "trace"), 73 | } 74 | pretty_env_logger::init(); 75 | } 76 | 77 | fn main() { 78 | if let Err(e) = run() { 79 | log::error!("{}", e); 80 | match e { 81 | AppError::ConfigError(_) => std::process::exit(1), 82 | AppError::IoError(_) => std::process::exit(2), 83 | AppError::ParseError(_) => std::process::exit(3), 84 | AppError::PathError(_) => std::process::exit(4), 85 | AppError::RenderingError(_) => std::process::exit(5), 86 | AppError::SyncingError(_) => std::process::exit(6), 87 | AppError::TemplatingError(_) => std::process::exit(7), 88 | 89 | #[allow(unreachable_patterns)] 90 | _ => std::process::exit(255), 91 | } 92 | } 93 | } 94 | 95 | // Author: Blurgy 96 | // Date: Sep 20 2021, 23:23 [CST] 97 | -------------------------------------------------------------------------------- /dt-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dt-core" 3 | description = "Core utilities used by dt-cli" 4 | version = "0.7.10" 5 | edition = "2021" 6 | authors = ["Gaoyang Zhang "] 7 | license = "MIT OR Apache-2.0" 8 | repository = "https://github.com/blurgyy/dt" 9 | categories = ["config"] 10 | keywords = ["syncing", "config"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | content_inspector = "0.2.4" 16 | dirs = "5.0.1" 17 | gethostname = "0.4.3" 18 | glob = "0.3.1" 19 | handlebars = "4.4.0" 20 | log = "0.4.20" 21 | path-clean = "1.0.1" 22 | regex = "1.9.5" 23 | serde = { version = "1.0.188", features = ["derive"] } 24 | serde_regex = "1.1.0" 25 | serde_tuple = "0.5.0" 26 | shellexpand = "3.1.0" 27 | sys-info = "0.9.1" 28 | toml = "0.8.1" 29 | url = { version = "2.4.1", features = ["serde"] } 30 | users = "0.11.0" 31 | 32 | [dev-dependencies] 33 | color-eyre = "0.6.2" 34 | pretty_assertions = "1.4.0" 35 | 36 | [target.armv7-unknown-linux-gnueabihf] 37 | linker = "arm-linux-gnueabihf-gcc" 38 | 39 | [target.aarch64-unknown-linux-gnu] 40 | linker = "aarch64-linux-gnu-gcc" 41 | 42 | # Author: Blurgy 43 | # Date: Sep 17 2021, 21:32 [CST] 44 | -------------------------------------------------------------------------------- /dt-core/README.md: -------------------------------------------------------------------------------- 1 | # DT 2 | 3 | [![release](https://github.com/blurgyy/dt/actions/workflows/release.yml/badge.svg)](https://github.com/blurgyy/dt/actions/workflows/release.yml) 4 | [![tests](https://github.com/blurgyy/dt/actions/workflows/tests.yml/badge.svg)](https://github.com/blurgyy/dt/actions/workflows/tests.yml) 5 | [![docs](https://github.com/blurgyy/dt/actions/workflows/docs.yml/badge.svg)](https://dt.cli.rs/) 6 | [![crates.io](https://img.shields.io/crates/v/dt-cli?style=flat&labelColor=1C2C2E&color=C96329&logo=Rust&logoColor=white)](https://crates.io/crates/dt-cli) 7 | 8 | `DT` allows you to sync/populate configuration files at will. It currently 9 | provides a CLI tool `dt-cli`. 10 | 11 | ## Usage 12 | 13 | The command line interface `dt-cli` accepts a path to the configuration file 14 | as an argument and performs the syncing process specified in the file. 15 | 16 | Configuration guides and detailed usages can be found in the 17 | [documentations](https://dt.cli.rs/). 18 | 19 | ### Example 20 | 21 | A simple working configuration file to sync all files from `~/dt/nvim` to 22 | `~/.config/nvim` that matches `*init.vim` can be written as: 23 | 24 | ```toml 25 | [[local]] 26 | name = "Neovim Configs" 27 | base = "~/dt/nvim" 28 | sources = ["*init.vim"] 29 | target = "~/.config/nvim" 30 | ``` 31 | 32 | :warning: **STOP HERE if you don't know what you are doing, or have not backed 33 | up existing files under `~/.config/nvim`.** 34 | 35 | Save above config to `~/.config/dt/cli.toml` and run 36 | 37 | ```shell 38 | $ dt-cli -c ~/.config/dt/cli.toml 39 | ``` 40 | 41 | to start syncing. Note the path in this example (`~/.config/dt/cli.toml`) is 42 | also the default path, so the below command (calling `dt-cli` with no argument) 43 | does the same thing as above: 44 | 45 | ```shell 46 | $ dt-cli 47 | ``` 48 | 49 | **Other command line flags & options** 50 | 51 | | Flags | Description | 52 | |---:|:---| 53 | | `-d\|--dry-run` | Shows changes to be made without actually syncing files. | 54 | | `-h\|--help` | Prints help information. | 55 | | `-q\|--quiet` | Decreases logging verbosity. | 56 | | `-v\|--verbose` | Increases logging verbosity. | 57 | | `-V\|--version` | Prints version information. | 58 | 59 | | Options | Description | 60 | |---:|:---| 61 | | `-c\|--config-path` `` | Specifies path to config file. | 62 | 63 | | Args | Description | 64 | |---:|:---| 65 | | `...` | Specifies name(s) of the group(s) to be processed | 66 | 67 | ## Install 68 | 69 | ### AUR 70 | 71 | `dt-cli` is in the [AUR](https://aur.archlinux.org/packages/dt-cli/), you can 72 | install it with your favorite package manager: 73 | 74 | ```shell 75 | $ paru -S dt-cli 76 | ``` 77 | 78 | ### Alternative ways 79 | 80 | Alternatively, you can: 81 | 82 | - Download latest [release](https://github.com/blurgyy/dt/releases/latest) 83 | from GitHub 84 | - Install from [crates.io](https://crates.io/crates/dt-cli/): 85 | 86 | ```shell 87 | $ cargo install dt-cli 88 | ``` 89 | 90 | - Build from source: 91 | 92 | ```shell 93 | $ git clone git@github.com:blurgyy/dt.git 94 | $ cd dt 95 | $ cargo test --release 96 | $ cargo install --path=dt-cli 97 | ``` 98 | 99 | ## Contributing 100 | 101 | There are numerous ways to help with this project. Let's [get 102 | started](https://github.com/blurgyy/dt/blob/main/CONTRIBUTING.md)! 103 | 104 | ## License 105 | 106 | Licensed under the the MIT license or 107 | Apache License, Version 2.0 , at 108 | your option. This file may not be copied, modified, or distributed except 109 | according to those terms. 110 | -------------------------------------------------------------------------------- /dt-core/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// Error definitions to use across the library. 4 | #[derive(Debug, PartialEq)] 5 | pub enum Error { 6 | /// Errors that occur when a config is deemed as invalid. 7 | ConfigError(String), 8 | /// Errors that occur during I/O operations. 9 | IoError(String), 10 | /// Errors that occur while parsing of structures fails. 11 | ParseError(String), 12 | /// Errors that occur while manipulating paths. 13 | PathError(String), 14 | /// Errors that occur while rendering templates. 15 | RenderingError(String), 16 | /// Errors that occur during syncing. 17 | SyncingError(String), 18 | /// Errors that occur while registering templates 19 | TemplatingError(String), 20 | } 21 | 22 | /// `Result` type to use across the library. 23 | pub type Result = std::result::Result; 24 | 25 | impl std::error::Error for Error {} 26 | 27 | impl fmt::Display for Error { 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 29 | match *self { 30 | Error::ConfigError(ref msg) => { 31 | write!(f, "Config Error: {}", msg) 32 | } 33 | Error::IoError(ref msg) => { 34 | write!(f, "IO Error: {}", msg) 35 | } 36 | Error::ParseError(ref msg) => { 37 | write!(f, "Parse Error: {}", msg) 38 | } 39 | Error::PathError(ref msg) => { 40 | write!(f, "Path Error: {}", msg) 41 | } 42 | Error::RenderingError(ref msg) => { 43 | write!(f, "Rendering Error: {}", msg) 44 | } 45 | Error::SyncingError(ref msg) => { 46 | write!(f, "Syncing Error: {}", msg) 47 | } 48 | Error::TemplatingError(ref msg) => { 49 | write!(f, "Templating Error: {}", msg) 50 | } 51 | } 52 | } 53 | } 54 | 55 | impl From for Error { 56 | fn from(err: std::io::Error) -> Self { 57 | Error::IoError(err.to_string()) 58 | } 59 | } 60 | impl From for Error { 61 | fn from(err: toml::de::Error) -> Self { 62 | Self::ParseError(err.to_string()) 63 | } 64 | } 65 | impl From for Error { 66 | fn from(err: std::path::StripPrefixError) -> Self { 67 | Self::PathError(err.to_string()) 68 | } 69 | } 70 | impl From for Error { 71 | fn from(err: glob::PatternError) -> Self { 72 | Self::PathError(err.to_string()) 73 | } 74 | } 75 | impl From for Error { 76 | fn from(err: handlebars::RenderError) -> Self { 77 | Self::RenderingError(err.to_string()) 78 | } 79 | } 80 | impl From for Error { 81 | fn from(err: std::str::Utf8Error) -> Self { 82 | Self::RenderingError(err.to_string()) 83 | } 84 | } 85 | impl From for Error { 86 | fn from(err: handlebars::TemplateError) -> Self { 87 | Self::TemplatingError(err.to_string()) 88 | } 89 | } 90 | 91 | // Author: Blurgy 92 | // Date: Oct 29 2021, 23:07 [CST] 93 | -------------------------------------------------------------------------------- /dt-core/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use crate::error::{Error as AppError, Result}; 4 | 5 | /// Gets config path from environment variables, or infer one. 6 | /// 7 | /// 1. If the environment variable indexed by `env_for_file`'s value is 8 | /// present, that environment variable's value is returned as the config file 9 | /// path. 10 | /// 11 | /// # Example 12 | /// 13 | /// ``` 14 | /// # use dt_core::utils::default_config_path; 15 | /// # use std::path::PathBuf; 16 | /// # use std::str::FromStr; 17 | /// std::env::set_var("DT_CLI_CONFIG_PATH", "/tmp/dt/configuration.toml"); 18 | /// assert_eq!( 19 | /// default_config_path::<&str>("DT_CLI_CONFIG_PATH", "", &[]), 20 | /// Ok(PathBuf::from_str("/tmp/dt/configuration.toml").unwrap()), 21 | /// ); 22 | /// ``` 23 | /// 24 | /// 2. Otherwise, if the environment variable indexed by `env_for_dir`'s value 25 | /// is present, that environment variable's value is considered the parent 26 | /// directory of the returned config path, filenames within `search_list` will 27 | /// be checked in order and the first existing file's path will be returned. 28 | /// If none of the `search_list` exists, a fallback filename `config.toml` 29 | /// will be used. 30 | /// 31 | /// # Example 32 | /// 33 | /// ``` 34 | /// # use dt_core::utils::default_config_path; 35 | /// # use std::path::PathBuf; 36 | /// # use std::str::FromStr; 37 | /// std::env::set_var("DT_CONFIG_DIR", "/tmp/d/t"); 38 | /// assert_eq!( 39 | /// default_config_path::<&str>( 40 | /// "some_non_existing_var", 41 | /// "DT_CONFIG_DIR", 42 | /// &[], 43 | /// ), 44 | /// Ok(PathBuf::from_str("/tmp/d/t/config.toml").unwrap()), 45 | /// ); 46 | /// ``` 47 | /// 48 | /// 3. When neither of `env_for_file`'s and `env_for_dir`'s corresponding 49 | /// environment variable exists, the parent directory of returned path is 50 | /// inferred as `$XDG_CONFIG_HOME/dt`, or `$HOME/.config/dt` if 51 | /// XDG_CONFIG_HOME is not set in the runtime environment. 52 | /// 53 | /// # Example 54 | /// 55 | /// ``` 56 | /// # use dt_core::utils::default_config_path; 57 | /// # use std::path::PathBuf; 58 | /// # use std::str::FromStr; 59 | /// std::env::set_var("XDG_CONFIG_HOME", "/tmp/confighome"); 60 | /// assert_eq!( 61 | /// default_config_path::<&str>( 62 | /// "some_non_existing_var", 63 | /// "some_other_non_existing_var", 64 | /// &[], 65 | /// ), 66 | /// Ok(PathBuf::from_str("/tmp/confighome/dt/config.toml").unwrap()), 67 | /// ); 68 | /// 69 | /// std::env::remove_var("XDG_CONFIG_HOME"); 70 | /// std::env::set_var("HOME", "/tmp/home"); 71 | /// assert_eq!( 72 | /// default_config_path::<&str>( 73 | /// "some_non_existing_var", 74 | /// "some_other_non_existing_var", 75 | /// &[], 76 | /// ), 77 | /// Ok(PathBuf::from_str("/tmp/home/.config/dt/config.toml").unwrap()), 78 | /// ); 79 | /// ``` 80 | pub fn default_config_path>( 81 | env_for_file: &str, 82 | env_for_dir: &str, 83 | search_list: &[P], 84 | ) -> Result { 85 | if let Ok(file_path) = std::env::var(env_for_file) { 86 | log::debug!( 87 | "Using config file '{}' (from environment variable `{}`)", 88 | file_path, 89 | env_for_file, 90 | ); 91 | Ok(file_path.into()) 92 | } else { 93 | let dir_path = match std::env::var(env_for_dir) { 94 | Ok(dir_path) => { 95 | log::debug!( 96 | "Using config directory '{}' (from environment variable `{}`)", 97 | dir_path, 98 | env_for_dir, 99 | ); 100 | dir_path.into() 101 | } 102 | _ => { 103 | if let Some(dir_path) = dirs::config_dir() { 104 | log::debug!("Using config directory '{}' (inferred)", dir_path.display(),); 105 | dir_path.join("dt") 106 | } else { 107 | return Err(AppError::ConfigError( 108 | "Could not infer directory to config file".to_owned(), 109 | )); 110 | } 111 | } 112 | }; 113 | let mut file_path = dir_path.join("config.toml"); 114 | for p in search_list { 115 | let candidate = dir_path.join(p); 116 | if candidate.exists() { 117 | file_path = candidate; 118 | break; 119 | } 120 | } 121 | log::debug!("Using config file '{}' (inferred)", file_path.display()); 122 | Ok(file_path) 123 | } 124 | } 125 | 126 | /// Gets the host-specific suffix, according to given [`hostname_sep`] and 127 | /// current machine's hostname. 128 | /// 129 | /// [`hostname_sep`]: crate::config::GlobalConfig::hostname_sep 130 | pub fn host_specific_suffix(hostname_sep: &str) -> String { 131 | hostname_sep.to_owned() 132 | + gethostname::gethostname() 133 | .to_str() 134 | .expect("Failed getting hostname") 135 | } 136 | 137 | #[cfg(test)] 138 | pub(crate) mod testing { 139 | use std::{ 140 | ffi::OsString, fs::Permissions, os::unix::prelude::PermissionsExt, path::PathBuf, 141 | str::FromStr, 142 | }; 143 | 144 | use color_eyre::Report; 145 | 146 | const TESTROOT: &str = "/tmp/dt-testing"; 147 | 148 | pub fn get_testroot(top_level: &str) -> PathBuf { 149 | PathBuf::from_str(TESTROOT).unwrap().join(top_level) 150 | } 151 | 152 | pub fn prepare_directory(abspath: PathBuf, mode: u32) -> std::result::Result { 153 | std::fs::create_dir_all(&abspath)?; 154 | std::fs::set_permissions(&abspath, Permissions::from_mode(mode))?; 155 | Ok(abspath) 156 | } 157 | 158 | pub fn prepare_file(abspath: PathBuf, mode: u32) -> std::result::Result { 159 | if let Some(parent) = abspath.parent() { 160 | std::fs::create_dir_all(parent)?; 161 | } 162 | std::fs::write( 163 | &abspath, 164 | "Created by: `dt_core::syncing::tests::prepare_file`\n", 165 | )?; 166 | std::fs::set_permissions(&abspath, Permissions::from_mode(mode))?; 167 | Ok(abspath) 168 | } 169 | 170 | pub fn gethostname() -> OsString { 171 | "r2d2".into() 172 | } 173 | 174 | pub fn get_current_uid() -> users::uid_t { 175 | 418 176 | } 177 | 178 | pub fn get_current_username() -> Option { 179 | Some("luke".into()) 180 | } 181 | 182 | pub fn linux_os_release() -> crate::error::Result { 183 | let info = sys_info::LinuxOSReleaseInfo { 184 | id: Some("dt".into()), 185 | id_like: Some("DotfileTemplater".into()), 186 | name: Some("dt".into()), 187 | pretty_name: Some("DT".into()), 188 | version: Some("latest".into()), 189 | version_id: Some("0.99.99".into()), 190 | version_codename: Some("dummy-version_codename".into()), 191 | ansi_color: Some("dummy-ansi_color".into()), 192 | logo: Some("Buzz Lightyear".into()), 193 | cpe_name: Some("dummy-cpe_name".into()), 194 | build_id: Some("#somethingsomething".into()), 195 | variant: Some("dummy-variant".into()), 196 | variant_id: Some("dummy-variant_id".into()), 197 | home_url: Some("https://github.com/blurgyy/dt/".into()), 198 | documentation_url: Some("https://dt.cli.rs/".into()), 199 | support_url: Some("https://github.com/blurgyy/dt/issues".into()), 200 | bug_report_url: Some("https://github.com/blurgyy/dt/issues".into()), 201 | privacy_policy_url: Some( 202 | "https://github.com/blurgyy/dt/blob/main/CODE_OF_CONDUCT.md".into(), 203 | ), 204 | }; 205 | Ok(info) 206 | } 207 | } 208 | 209 | // Author: Blurgy 210 | // Date: Oct 03 2021, 02:54 [CST] 211 | -------------------------------------------------------------------------------- /dt-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dt-server" 3 | description = "$HOME, $HOME everywhere" 4 | version = "0.1.0" 5 | edition = "2021" 6 | authors = ["Gaoyang Zhang "] 7 | documentation = "https://dt.cli.rs/" 8 | license = "MIT OR Apache-2.0" 9 | repository = "https://github.com/blurgyy/dt" 10 | categories = ["command-line-utilities"] 11 | keywords = ["dotfiles", "manager", "syncing", "config"] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | dirs = "5.0.1" 17 | dt-core = { path = "../dt-core", version = "0.7.10" } 18 | log = "0.4.20" 19 | pretty_env_logger = "0.5.0" 20 | structopt = "0.3.26" 21 | tokio = { version = "1.32.0", features = ["full"] } 22 | warp = "0.3.5" 23 | 24 | [target.armv7-unknown-linux-gnueabihf] 25 | linker = "arm-linux-gnueabihf-gcc" 26 | 27 | [target.aarch64-unknown-linux-gnu] 28 | linker = "aarch64-linux-gnu-gcc" 29 | 30 | # Author: Blurgy 31 | # Date: Oct 19 2021, 09:35 [CST] 32 | -------------------------------------------------------------------------------- /dt-server/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use dt_core::{ 4 | config::DTConfig, 5 | error::{Error as AppError, Result}, 6 | utils::default_config_path, 7 | }; 8 | use structopt::StructOpt; 9 | 10 | #[derive(StructOpt, Debug)] 11 | #[structopt( 12 | global_settings(&[structopt::clap::AppSettings::ColoredHelp]) 13 | )] 14 | struct Opt { 15 | /// Specifies path to config file 16 | #[structopt(short, long)] 17 | config_path: Option, 18 | 19 | /// Specifies a directory to serve static files from 20 | #[structopt(short, long)] 21 | static_dir: Option, 22 | 23 | ///Specifies the url prefix for served items 24 | #[structopt(short, long)] 25 | root: Option, 26 | 27 | /// Increases logging verbosity 28 | #[structopt(short, long, parse(from_occurrences), conflicts_with = "quiet")] 29 | verbose: i8, 30 | 31 | /// Decreases logging verbosity 32 | #[structopt(short, long, parse(from_occurrences), conflicts_with = "verbose")] 33 | quiet: i8, 34 | } 35 | 36 | async fn run() -> Result<()> { 37 | let opt = Opt::from_args(); 38 | setup(opt.verbose - opt.quiet); 39 | 40 | let config_path = match opt.config_path { 41 | Some(p) => { 42 | log::debug!("Using config file '{}' (from command line)", p.display(),); 43 | p 44 | } 45 | None => default_config_path("DT_SERVER_CONFIG_PATH", "DT_CONFIG_DIR", &["server.toml"])?, 46 | }; 47 | 48 | let config = DTConfig::from_path(config_path)?; 49 | Ok(()) 50 | } 51 | 52 | #[tokio::main] 53 | async fn main() { 54 | if let Err(e) = run().await { 55 | log::error!("{}", e); 56 | match e { 57 | #[allow(unreachable_patterns)] 58 | _ => std::process::exit(255), 59 | } 60 | } 61 | } 62 | 63 | fn setup(verbosity: i8) { 64 | match verbosity { 65 | i8::MIN..=-2 => std::env::set_var("RUST_LOG", "error"), 66 | -1 => std::env::set_var("RUST_LOG", "warn"), 67 | 0 => std::env::set_var( 68 | "RUST_LOG", 69 | std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_owned()), 70 | ), 71 | 1 => std::env::set_var("RUST_LOG", "debug"), 72 | 2..=i8::MAX => std::env::set_var("RUST_LOG", "trace"), 73 | } 74 | 75 | pretty_env_logger::init(); 76 | } 77 | 78 | // Author: Blurgy 79 | // Date: Oct 19 2021, 21:57 [CST] 80 | -------------------------------------------------------------------------------- /roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | ## Core 4 | 5 | - [x] Expand tilde and globs in source paths 6 | - [x] Add ignore patterns to LocalSyncConfig 7 | - [x] Add [global] section to config file, which defines global settings like 8 | staging directory, and default behaviours like whether to 9 | `copy`/`symlink` when syncing. 10 | - [x] Implement staging when `global.method` is `symlink` 11 | - [x] Make `local.basedir` mandatory (instead of optional) so as to preserve 12 | directory structure in the stating directory 13 | - [x] Do not expand tilde in sources, because sources are relative paths 14 | after making basedir mandatory 15 | - [x] Add `basedir` to LocalSyncConfig for easier configuring in sources 16 | - [ ] ~~Manage permission bits on a per-group basis~~ 17 | - [x] ~~Handle permission denied error when target file is created by current 18 | user but the `write` permission is not set~~ 19 | - [ ] ~~Set permissions after all syncing process~~ 20 | - [x] Keep target file modes and their corresponding source files identical 21 | - [x] Return error when `sources` contains `.*` or similar globs, because this 22 | glob also matches current directory (`.`) and parent directory (`..`) 23 | - [ ] Expand environment variables in `local.basedir`, `local.sources`, 24 | `local.target` and `staging` 25 | - [x] Handle non-existing source, give warnings when encountered 26 | - [ ] ~~Add `local.for` to separate groups for different machines w.r.t. their 27 | hostname (with `gethostname` crate)~~ 28 | - [ ] Add `global.ignored` as fallback ignoring list 29 | - [x] Add `local.name` for local groups as namespaces, to properly handle 30 | files from different groups having the same relative path to their 31 | basedir. 32 | - [ ] Let `ignored` array(s) match more intelligently 33 | - [x] Add `local.per_host` to check for per-host syncing items for groups that 34 | has this key set to `true` 35 | - [x] Make `local.per_host` default to `true`, or remove this from config 36 | - [x] Add group name to logging message 37 | - [x] Do not touch filesystem when parsing config for faster execution, only 38 | query filesystem when syncing 39 | - [ ] Warn about items without a source in the staging directory 40 | - [x] Add `global.hostname_sep` as default value, overridden by group configs 41 | - [x] Deny sources that start with "./" or "../" or similar 42 | - [x] Define group type (like one of "General", "App", "Dropin"), to define 43 | priority when syncing (priority order: Dropin > App > General), so that 44 | user won't have to carefully separate configurations of specific 45 | applications from its parent directory (like separating `~/.ssh` (which 46 | will be of type "App") from `~` (which will be of type "General"), or 47 | separating `~/.config/nvim` from `~/.config`) 48 | - [x] Recursively expand all sources in function `syncing::expand` 49 | - [x] Add README.md 50 | - [x] Do not remove host-specific suffix in staging directory 51 | - [ ] Warn when a group's `target` is inside another group's `basedir` 52 | - [ ] Set maximum recursion depth when expanding sources 53 | - [x] Deny empty `name`/`basedir`/`target` 54 | - [x] Better logging messages: 55 | - error: shows errors 56 | - warn: shows warnings 57 | - info: shows coarse step messages, like "syncing group [xxx]" 58 | - debug: shows how items are synced 59 | - trace: shows messages that contains multiple lines 60 | Paths in log messages should be single quoted. 61 | - [ ] Keep track of items that are synced last time, so that deletion of 62 | source items can also be propagated to local fs properly. 63 | - [ ] ?Make `target` an array 64 | - [ ] ?Expand globs in `target` to sync to multiple target directories (use 65 | case: `user.js` under Firefox user profile, which is named 66 | `xxxxxxxx.$profile_name` and can be globbed) 67 | - [x] Do not _remove_ existing target file when _overwriting_, because it 68 | causes some X compositor (like picom) to flash or fail to 69 | automatically load config file after overwriting 70 | - [ ] Add `global.include` array to allow including other config files 71 | - [x] Templating 72 | 73 | ## CLI 74 | 75 | - [x] Find config in `$XDG_CONFIG_HOME/dt/cli.toml` by default 76 | - [x] Add command line option to specify which group to sync via passing name 77 | of the group 78 | - [x] Change default config path to `$XDG_CONFIG_HOME/dt/config.toml` 79 | 80 | ## Server 81 | 82 | - [ ] Serve files with an HTTP server, grouped by their group names 83 | - [ ] Use the same config layout as `dt-cli` 84 | - [ ] Add `confidential` flag to `local` group to determine whether this group 85 | should be served in the HTTP server 86 | - [ ] Make URL prefix (like `/raw/`) configurable 87 | - [ ] Optionally serve static files at a given root 88 | - [ ] Encryption 89 | 90 | > Author: Blurgy 91 | > Date: Sep 29 2021, 00:18 [CST] 92 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | hard_tabs = false 3 | max_width = 100 4 | newline_style = "Unix" 5 | reorder_imports = true 6 | tab_spaces = 4 7 | use_field_init_shorthand = true 8 | 9 | # Unstable features 10 | unstable_features = true 11 | comment_width = 100 12 | wrap_comments = true 13 | 14 | # Author: Blurgy 15 | # Date: Jun 09 2021, 15:27 [CST] 16 | --------------------------------------------------------------------------------