├── .cargo └── config ├── .gitattributes ├── .github ├── FUNDING.yml ├── chglog │ ├── CHANGELOG.tpl.md │ ├── RELEASE.tpl.md │ ├── changelog.yml │ └── release.yml ├── dependabot.yml └── workflows │ ├── audit.yml │ ├── deploy.yml │ └── rust.yml ├── .gitignore ├── .glitterrc ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── salt.png └── salt.svg ├── install.sh ├── rustfmt.toml └── src ├── args.rs ├── cli.rs ├── config.rs ├── get_and_parse.rs ├── macros.rs ├── main.rs ├── node.rs ├── run.rs └── task.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | lint = "clippy --all-targets --all-features" -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /install.sh linguist-documentation=true 2 | vscode-salt/src/**/*.{ts,js,svelte,css} linguist-documentation=true -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Milo123459 -------------------------------------------------------------------------------- /.github/chglog/CHANGELOG.tpl.md: -------------------------------------------------------------------------------- 1 | {{ if .Versions -}} 2 | 3 | ## [Unreleased] 4 | 5 | {{ if .Unreleased.CommitGroups -}} 6 | {{ range .Unreleased.CommitGroups -}} 7 | ### {{ .Title }} 8 | {{ range .Commits -}} 9 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} 10 | {{ end }} 11 | {{ end -}} 12 | {{ end -}} 13 | {{ end -}} 14 | 15 | {{ range .Versions }} 16 | 17 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }} 18 | {{ range .CommitGroups -}} 19 | ### {{ .Title }} 20 | {{ range .Commits -}} 21 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} 22 | {{ end }} 23 | {{ end -}} 24 | 25 | {{- if .RevertCommits -}} 26 | ### Reverts 27 | {{ range .RevertCommits -}} 28 | - {{ .Revert.Header }} 29 | {{ end }} 30 | {{ end -}} 31 | 32 | {{- if .NoteGroups -}} 33 | {{ range .NoteGroups -}} 34 | ### {{ .Title }} 35 | {{ range .Notes }} 36 | {{ .Body }} 37 | {{ end }} 38 | {{ end -}} 39 | {{ end -}} 40 | {{ end -}} 41 | 42 | {{- if .Versions }} 43 | [Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD 44 | {{ range .Versions -}} 45 | {{ if .Tag.Previous -}} 46 | [{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }} 47 | {{ end -}} 48 | {{ end -}} 49 | {{ end -}} -------------------------------------------------------------------------------- /.github/chglog/RELEASE.tpl.md: -------------------------------------------------------------------------------- 1 | {{ range .Versions }} 2 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }}) 3 | 4 | {{ range .CommitGroups -}} 5 | ### {{ .Title }} 6 | 7 | {{ range .Commits -}} 8 | * {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} 9 | {{ end }} 10 | {{ end -}} 11 | 12 | {{- if .RevertCommits -}} 13 | ### Reverts 14 | 15 | {{ range .RevertCommits -}} 16 | * {{ .Revert.Header }} 17 | {{ end }} 18 | {{ end -}} 19 | 20 | {{- if .NoteGroups -}} 21 | {{ range .NoteGroups -}} 22 | ### {{ .Title }} 23 | 24 | {{ range .Notes }} 25 | {{ .Body }} 26 | {{ end }} 27 | {{ end -}} 28 | {{ end -}} 29 | {{ end -}} -------------------------------------------------------------------------------- /.github/chglog/changelog.yml: -------------------------------------------------------------------------------- 1 | style: github 2 | template: CHANGELOG.tpl.md 3 | info: 4 | title: CHANGELOG 5 | repository_url: https://github.com/Milo123459/salt 6 | options: 7 | commits: 8 | commit_groups: 9 | title_maps: 10 | feat: Features 11 | fix: Bug Fixes 12 | perf: Performance Improvements 13 | refactor: Code Refactoring 14 | ci: Continuous Integration 15 | header: 16 | pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$" 17 | pattern_maps: 18 | - Type 19 | - Scope 20 | - Subject 21 | notes: 22 | keywords: 23 | - BREAKING CHANGE -------------------------------------------------------------------------------- /.github/chglog/release.yml: -------------------------------------------------------------------------------- 1 | style: github 2 | template: RELEASE.tpl.md 3 | info: 4 | repository_url: https://github.com/Milo123459/salt 5 | options: 6 | commits: 7 | commit_groups: 8 | title_maps: 9 | feat: Features 10 | fix: Bug Fixes 11 | perf: Performance Improvements 12 | refactor: Code Refactoring 13 | ci: Continuous Integration 14 | header: 15 | pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$" 16 | pattern_maps: 17 | - Type 18 | - Scope 19 | - Subject 20 | notes: 21 | keywords: 22 | - BREAKING CHANGE -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "08:00" 8 | open-pull-requests-limit: 10 -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | pull_request: 4 | paths: 5 | - '**/Cargo.toml' 6 | - '**/Cargo.lock' 7 | push: 8 | paths: 9 | - '**/Cargo.toml' 10 | - '**/Cargo.lock' 11 | schedule: 12 | - cron: '0 0 * * *' 13 | 14 | jobs: 15 | security_audit: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v1 19 | - uses: actions-rs/audit-check@v1 20 | with: 21 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | workflow_dispatch: 4 | push: 5 | tags: 6 | - "v*" 7 | # check out starship/starship - inspired by their workflow 8 | jobs: 9 | github_build: 10 | name: Build release binaries 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | include: 15 | - target: x86_64-unknown-linux-gnu 16 | os: ubuntu-latest 17 | name: salt-x86_64-unknown-linux-gnu.tar.gz 18 | 19 | - target: x86_64-unknown-linux-musl 20 | os: ubuntu-latest 21 | name: salt-x86_64-unknown-linux-musl.tar.gz 22 | 23 | - target: i686-unknown-linux-musl 24 | os: ubuntu-latest 25 | name: salt-i686-unknown-linux-musl.tar.gz 26 | 27 | - target: x86_64-apple-darwin 28 | os: macOS-latest 29 | name: salt-x86_64-apple-darwin.tar.gz 30 | 31 | - target: aarch64-apple-darwin 32 | os: macOS-latest 33 | name: salt-aarch64-apple-darwin.tar.gz 34 | 35 | - target: x86_64-pc-windows-msvc 36 | os: windows-latest 37 | name: salt-x86_64-pc-windows-msvc.zip 38 | 39 | - target: i686-pc-windows-msvc 40 | os: windows-latest 41 | name: salt-i686-pc-windows-msvc.zip 42 | 43 | - target: aarch64-pc-windows-msvc 44 | os: windows-latest 45 | name: salt-aarch64-pc-windows-msvc.zip 46 | 47 | runs-on: ${{ matrix.os }} 48 | continue-on-error: true 49 | steps: 50 | - name: Setup | Checkout 51 | uses: actions/checkout@v2 52 | - name: Setup | Install llvm and build-essential, cmake, cmake-doc, snapd and ninja-build 53 | if: ${{ matrix.os == 'ubuntu-latest' }} 54 | run: sudo apt install llvm build-essential cmake cmake-doc ninja-build snapd 55 | - name: Setup | Instal CC-Tool 56 | if: ${{ matrix.os == 'ubuntu-latest' }} 57 | run: sudo snap install cc-tool --edge --devmode 58 | - name: Setup | Rust 59 | uses: actions-rs/toolchain@v1 60 | with: 61 | toolchain: nightly 62 | override: true 63 | profile: minimal 64 | target: ${{ matrix.target }} 65 | - name: Build | Build 66 | uses: actions-rs/cargo@v1 67 | # TODO: Remove this once it's the default 68 | env: 69 | SDKROOT: /Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk 70 | with: 71 | command: build 72 | args: --release --target ${{ matrix.target }} 73 | 74 | - name: Post Build | Prepare artifacts [Windows] 75 | if: matrix.os == 'windows-latest' 76 | run: | 77 | cd target/${{ matrix.target }}/release 78 | strip salt.exe 79 | 7z a ../../../${{ matrix.name }} salt.exe 80 | cd - 81 | - name: Post Build | Prepare artifacts [-nix] 82 | if: matrix.os != 'windows-latest' 83 | run: | 84 | cd target/${{ matrix.target }}/release 85 | # TODO: investigate better cross platform stripping 86 | strip salt || true 87 | tar czvf ../../../${{ matrix.name }} salt 88 | cd - 89 | - name: Deploy | Upload artifacts 90 | uses: actions/upload-artifact@v2 91 | with: 92 | name: ${{ matrix.name }} 93 | path: ${{ matrix.name }} 94 | 95 | # Create GitHub release with Rust build targets and release notes 96 | github_release: 97 | name: Create GitHub Release 98 | needs: github_build 99 | runs-on: ubuntu-latest 100 | steps: 101 | - name: Setup | Checkout 102 | uses: actions/checkout@v2 103 | with: 104 | fetch-depth: 0 105 | 106 | - name: Setup | Go 107 | uses: actions/setup-go@v2 108 | with: 109 | go-version: "^1.15.7" 110 | 111 | - name: Setup | Artifacts 112 | uses: actions/download-artifact@v2 113 | 114 | - name: Setup | Checksums 115 | run: for file in salt-*/salt-*; do openssl dgst -sha256 -r "$file" | awk '{print $1}' > "${file}.sha256"; done 116 | 117 | - name: Setup | Release notes 118 | run: | 119 | GO111MODULE=on go get github.com/git-chglog/git-chglog/cmd/git-chglog@0.9.1 120 | git-chglog -c .github/chglog/release.yml $(git describe --tags) > RELEASE.md 121 | - name: Build | Publish 122 | uses: softprops/action-gh-release@v1 123 | with: 124 | files: salt-*/salt-* 125 | body_path: RELEASE.md 126 | env: 127 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Main workflow 2 | on: 3 | push: 4 | paths-ignore: 5 | - "docs/**" 6 | - "**.md" 7 | - "**.ts" 8 | - "**.css" 9 | - "**.svelte" 10 | pull_request: 11 | paths-ignore: 12 | - "docs/**" 13 | - "**.md" 14 | - "**.ts" 15 | - "**.css" 16 | - "**.svelte" 17 | 18 | jobs: 19 | # Run the `rustfmt` code formatter 20 | rustfmt: 21 | name: Rustfmt [Formatter] 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Setup | Checkout 25 | uses: actions/checkout@v2 26 | 27 | - name: Setup | Rust 28 | uses: actions-rs/toolchain@v1 29 | with: 30 | toolchain: stable 31 | override: true 32 | profile: minimal 33 | components: rustfmt 34 | 35 | - name: Setup | libdbus (ubuntu) 36 | run: sudo apt-get install libdbus-1-dev 37 | 38 | - name: Build | Format 39 | run: cargo fmt --all -- --check 40 | 41 | # Run the `clippy` linting tool 42 | clippy: 43 | name: Clippy [Linter] 44 | strategy: 45 | matrix: 46 | os: [ubuntu-latest] 47 | runs-on: ${{ matrix.os }} 48 | steps: 49 | - name: Setup | Checkout 50 | uses: actions/checkout@v2 51 | 52 | # Cache files between builds 53 | # - name: Setup | Cache Cargo 54 | # uses: actions/cache@v2 55 | # with: 56 | # path: | 57 | # ~/.cargo/registry 58 | # ~/.cargo/git 59 | # target 60 | # key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 61 | 62 | - name: Setup | libdbus (ubuntu) 63 | if: matrix.os == 'ubuntu-latest' 64 | run: sudo apt-get install libdbus-1-dev 65 | 66 | - name: Setup | Rust 67 | uses: actions-rs/toolchain@v1 68 | with: 69 | toolchain: stable 70 | override: true 71 | profile: minimal 72 | components: clippy 73 | 74 | - name: Build | Lint 75 | uses: actions-rs/cargo@v1 76 | with: 77 | command: clippy 78 | args: --workspace --locked --all-targets --all-features 79 | 80 | # Ensure that the project could be successfully compiled 81 | cargo_check: 82 | name: Compile 83 | runs-on: ubuntu-latest 84 | steps: 85 | - name: Setup | Checkout 86 | uses: actions/checkout@v2 87 | 88 | # Cache files between builds 89 | # - name: Setup | Cache Cargo 90 | # uses: actions/cache@v2 91 | # with: 92 | # path: | 93 | # ~/.cargo/registry 94 | # ~/.cargo/git 95 | # target 96 | # key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 97 | 98 | - name: Setup | Rust 99 | uses: actions-rs/toolchain@v1 100 | with: 101 | toolchain: stable 102 | profile: minimal 103 | override: true 104 | 105 | - name: Build | Check 106 | run: cargo check --workspace --locked 107 | 108 | # Run tests on Linux, macOS, and Windows 109 | # On both Rust stable and Rust nightly 110 | test: 111 | name: Test Suite 112 | runs-on: ${{ matrix.os }} 113 | needs: cargo_check # First check then run expansive tests 114 | strategy: 115 | fail-fast: false 116 | matrix: 117 | os: [ubuntu-latest, windows-latest] 118 | rust: [stable, nightly] 119 | steps: 120 | - name: Setup | Checkout 121 | uses: actions/checkout@v2 122 | 123 | - name: Setup | libdbus (ubuntu) 124 | if: matrix.os == 'ubuntu-latest' 125 | run: sudo apt-get install libdbus-1-dev 126 | 127 | # Cache files between builds 128 | # - name: Setup | Cache Cargo 129 | # uses: actions/cache@v2 130 | # with: 131 | # path: | 132 | # ~/.cargo/registry 133 | # ~/.cargo/git 134 | # target 135 | # key: ${{ runner.os }}-${{ matrix.rust }}-cargo-${{ hashFiles('**/Cargo.lock') }} 136 | 137 | # Install all the required dependencies for testing 138 | - name: Setup | Rust 139 | uses: actions-rs/toolchain@v1 140 | with: 141 | toolchain: ${{ matrix.rust }} 142 | profile: minimal 143 | override: true 144 | 145 | # Run the ignored tests that expect the above setup 146 | - name: Build | Test 147 | run: cargo test --workspace --locked --all-features -- -Z unstable-options --include-ignored -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.glitterrc: -------------------------------------------------------------------------------- 1 | { 2 | "commit_message": "$1: $2: $3+", 3 | "commit_message_arguments": [ 4 | { 5 | "argument": 1, 6 | "case": "lower", 7 | "type_enums": [ 8 | "fix", 9 | "feat", 10 | "chore", 11 | "refactor", 12 | "docs", 13 | "void", 14 | "deps", 15 | "ci" 16 | ] 17 | } 18 | ], 19 | "custom_tasks": [ 20 | { 21 | "name": "fmt", 22 | "execute": [ 23 | "cargo fmt" 24 | ] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ansi_term" 7 | version = "0.11.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 10 | dependencies = [ 11 | "winapi", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.45" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7" 19 | 20 | [[package]] 21 | name = "atty" 22 | version = "0.2.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 25 | dependencies = [ 26 | "hermit-abi", 27 | "libc", 28 | "winapi", 29 | ] 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.2.1" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 36 | 37 | [[package]] 38 | name = "cfg-if" 39 | version = "1.0.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 42 | 43 | [[package]] 44 | name = "clap" 45 | version = "2.33.3" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 48 | dependencies = [ 49 | "ansi_term", 50 | "atty", 51 | "bitflags", 52 | "strsim", 53 | "textwrap", 54 | "unicode-width", 55 | "vec_map", 56 | ] 57 | 58 | [[package]] 59 | name = "colored" 60 | version = "2.0.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 63 | dependencies = [ 64 | "atty", 65 | "lazy_static", 66 | "winapi", 67 | ] 68 | 69 | [[package]] 70 | name = "dirs-next" 71 | version = "2.0.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 74 | dependencies = [ 75 | "cfg-if", 76 | "dirs-sys-next", 77 | ] 78 | 79 | [[package]] 80 | name = "dirs-sys-next" 81 | version = "0.1.2" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 84 | dependencies = [ 85 | "libc", 86 | "redox_users", 87 | "winapi", 88 | ] 89 | 90 | [[package]] 91 | name = "getrandom" 92 | version = "0.2.3" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 95 | dependencies = [ 96 | "cfg-if", 97 | "libc", 98 | "wasi", 99 | ] 100 | 101 | [[package]] 102 | name = "heck" 103 | version = "0.3.3" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 106 | dependencies = [ 107 | "unicode-segmentation", 108 | ] 109 | 110 | [[package]] 111 | name = "hermit-abi" 112 | version = "0.1.19" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 115 | dependencies = [ 116 | "libc", 117 | ] 118 | 119 | [[package]] 120 | name = "itoa" 121 | version = "0.4.7" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 124 | 125 | [[package]] 126 | name = "lazy_static" 127 | version = "1.4.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 130 | 131 | [[package]] 132 | name = "libc" 133 | version = "0.2.98" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" 136 | 137 | [[package]] 138 | name = "proc-macro-error" 139 | version = "1.0.4" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 142 | dependencies = [ 143 | "proc-macro-error-attr", 144 | "proc-macro2", 145 | "quote", 146 | "syn", 147 | "version_check", 148 | ] 149 | 150 | [[package]] 151 | name = "proc-macro-error-attr" 152 | version = "1.0.4" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 155 | dependencies = [ 156 | "proc-macro2", 157 | "quote", 158 | "version_check", 159 | ] 160 | 161 | [[package]] 162 | name = "proc-macro2" 163 | version = "1.0.28" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" 166 | dependencies = [ 167 | "unicode-xid", 168 | ] 169 | 170 | [[package]] 171 | name = "quote" 172 | version = "1.0.9" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 175 | dependencies = [ 176 | "proc-macro2", 177 | ] 178 | 179 | [[package]] 180 | name = "redox_syscall" 181 | version = "0.2.9" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" 184 | dependencies = [ 185 | "bitflags", 186 | ] 187 | 188 | [[package]] 189 | name = "redox_users" 190 | version = "0.4.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" 193 | dependencies = [ 194 | "getrandom", 195 | "redox_syscall", 196 | ] 197 | 198 | [[package]] 199 | name = "ryu" 200 | version = "1.0.5" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 203 | 204 | [[package]] 205 | name = "salt" 206 | version = "0.2.3" 207 | dependencies = [ 208 | "anyhow", 209 | "colored", 210 | "dirs-next", 211 | "serde", 212 | "serde_json", 213 | "structopt", 214 | ] 215 | 216 | [[package]] 217 | name = "serde" 218 | version = "1.0.130" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 221 | dependencies = [ 222 | "serde_derive", 223 | ] 224 | 225 | [[package]] 226 | name = "serde_derive" 227 | version = "1.0.130" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" 230 | dependencies = [ 231 | "proc-macro2", 232 | "quote", 233 | "syn", 234 | ] 235 | 236 | [[package]] 237 | name = "serde_json" 238 | version = "1.0.70" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3" 241 | dependencies = [ 242 | "itoa", 243 | "ryu", 244 | "serde", 245 | ] 246 | 247 | [[package]] 248 | name = "strsim" 249 | version = "0.8.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 252 | 253 | [[package]] 254 | name = "structopt" 255 | version = "0.3.25" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" 258 | dependencies = [ 259 | "clap", 260 | "lazy_static", 261 | "structopt-derive", 262 | ] 263 | 264 | [[package]] 265 | name = "structopt-derive" 266 | version = "0.4.18" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 269 | dependencies = [ 270 | "heck", 271 | "proc-macro-error", 272 | "proc-macro2", 273 | "quote", 274 | "syn", 275 | ] 276 | 277 | [[package]] 278 | name = "syn" 279 | version = "1.0.74" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" 282 | dependencies = [ 283 | "proc-macro2", 284 | "quote", 285 | "unicode-xid", 286 | ] 287 | 288 | [[package]] 289 | name = "textwrap" 290 | version = "0.11.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 293 | dependencies = [ 294 | "unicode-width", 295 | ] 296 | 297 | [[package]] 298 | name = "unicode-segmentation" 299 | version = "1.8.0" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 302 | 303 | [[package]] 304 | name = "unicode-width" 305 | version = "0.1.8" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 308 | 309 | [[package]] 310 | name = "unicode-xid" 311 | version = "0.2.2" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 314 | 315 | [[package]] 316 | name = "vec_map" 317 | version = "0.8.2" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 320 | 321 | [[package]] 322 | name = "version_check" 323 | version = "0.9.3" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 326 | 327 | [[package]] 328 | name = "wasi" 329 | version = "0.10.2+wasi-snapshot-preview1" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 332 | 333 | [[package]] 334 | name = "winapi" 335 | version = "0.3.9" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 338 | dependencies = [ 339 | "winapi-i686-pc-windows-gnu", 340 | "winapi-x86_64-pc-windows-gnu", 341 | ] 342 | 343 | [[package]] 344 | name = "winapi-i686-pc-windows-gnu" 345 | version = "0.4.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 348 | 349 | [[package]] 350 | name = "winapi-x86_64-pc-windows-gnu" 351 | version = "0.4.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 354 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "salt" 3 | version = "0.2.3" 4 | authors = ["Milo123459"] 5 | edition = "2018" 6 | include = ["src/**/*", "LICENSE", "README.md"] 7 | license = "MIT" 8 | documentation = "https://github.com/Milo123459/salt" 9 | homepage = "https://github.com/Milo123459/salt" 10 | repository = "https://github.com/Milo123459/salt" 11 | description = "Task management for the CLI" 12 | 13 | [dependencies] 14 | serde_json = "1.0.70" 15 | serde = { version = "1.0.130", features = ["derive"] } 16 | structopt = "0.3.25" 17 | colored = "2.0.0" 18 | dirs-next = "2.0.0" 19 | anyhow = "1.0.45" 20 | 21 | [[bin]] 22 | name = "salt" 23 | 24 | [profile.release] 25 | lto = "fat" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-Present 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

❯ Salt

5 |

6 | Fast and simple task management from the CLI. 7 |

8 |

9 |

10 | 11 | 12 | 13 | 14 | 15 |

16 |

17 |

18 |

19 | DEV Post | YouTube Video | VSCode Extension 20 |

21 |

22 |

23 | 24 | ## Install 25 | 26 | Windows: [Scoop](https://scoop.sh) 27 | 28 | ``` 29 | scoop bucket add cone https://github.com/Milo123459/cone 30 | scoop install cone/salt 31 | ``` 32 | 33 | MacOS: [Brew](https://brew.sh) 34 | 35 | ``` 36 | brew tap eternalmoon1234/brew 37 | brew install salt 38 | ``` 39 | 40 | Linux: 41 | 42 | ``` 43 | curl -fsSL https://raw.githubusercontent.com/Milo123459/salt/master/install.sh | bash 44 | ``` 45 | 46 | Litreally anything (Rust needed): 47 | 48 | ``` 49 | cargo install salt 50 | ``` 51 | 52 | ## Concepts 53 | 54 | You have a node, and a task. 55 | 56 | Think of it like a tree with apples. The tree is the node, the apples are the tasks. They just live under specific trees. 57 | 58 | *just for context, apple = task, tree = node`* 59 | 60 | ## Future ideas 61 | 62 | - Stats (ie, which node you use the most) 63 | - GitHub issue sync 64 | - Sync across multiple devices 65 | 66 | ## How to use 67 | 68 | Pretty simple: 69 | 70 | Run `salt action` to see all commands. 71 | 72 | Or, run `salt node action` to see all node sub-commands. 73 | -------------------------------------------------------------------------------- /assets/salt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Milo123459/salt/55953b5a6cdf2335bb88c905a94adc42c7613c4d/assets/salt.png -------------------------------------------------------------------------------- /assets/salt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Adapted from starships install file 4 | # shellcheck disable=SC2039 5 | 6 | help_text="# Options 7 | # 8 | # -V, --verbose 9 | # Enable verbose output for the installer 10 | # 11 | # -f, -y, --force, --yes 12 | # Skip the confirmation prompt during installation 13 | # 14 | # -p, --platform 15 | # Override the platform identified by the installer 16 | # 17 | # -b, --bin-dir 18 | # Override the bin installation directory 19 | # 20 | # -a, --arch 21 | # Override the architecture identified by the installer 22 | # 23 | # -B, --base-url 24 | # Override the base URL used for downloading releases 25 | # -r, --remove 26 | # Uninstall salt 27 | # -h, --help 28 | # Get some help 29 | # 30 | " 31 | 32 | set -eu 33 | printf '\n' 34 | 35 | BOLD="$(tput bold 2>/dev/null || printf '')" 36 | GREY="$(tput setaf 0 2>/dev/null || printf '')" 37 | UNDERLINE="$(tput smul 2>/dev/null || printf '')" 38 | RED="$(tput setaf 1 2>/dev/null || printf '')" 39 | GREEN="$(tput setaf 2 2>/dev/null || printf '')" 40 | YELLOW="$(tput setaf 3 2>/dev/null || printf '')" 41 | BLUE="$(tput setaf 4 2>/dev/null || printf '')" 42 | MAGENTA="$(tput setaf 5 2>/dev/null || printf '')" 43 | NO_COLOR="$(tput sgr0 2>/dev/null || printf '')" 44 | 45 | SUPPORTED_TARGETS="x86_64-unknown-linux-gnu x86_64-unknown-linux-musl \ 46 | i686-unknown-linux-musl aarch64-unknown-linux-musl \ 47 | arm-unknown-linux-musleabihf x86_64-apple-darwin \ 48 | aarch64-apple-darwin x86_64-pc-windows-msvc \ 49 | i686-pc-windows-msvc aarch64-pc-windows-msvc \ 50 | x86_64-unknown-freebsd" 51 | 52 | info() { 53 | printf '%s\n' "${BOLD}${GREY}>${NO_COLOR} $*" 54 | } 55 | 56 | warn() { 57 | printf '%s\n' "${YELLOW}! $*${NO_COLOR}" 58 | } 59 | 60 | error() { 61 | printf '%s\n' "${RED}x $*${NO_COLOR}" >&2 62 | } 63 | 64 | completed() { 65 | printf '%s\n' "${GREEN}✓${NO_COLOR} $*" 66 | } 67 | 68 | has() { 69 | command -v "$1" 1>/dev/null 2>&1 70 | } 71 | 72 | # Gets path to a temporary file, even if 73 | get_tmpfile() { 74 | local suffix 75 | suffix="$1" 76 | if has mktemp; then 77 | printf "%s%s.%s.%s" "$(mktemp)" "-salt" "${RANDOM}" "${suffix}" 78 | else 79 | # No really good options here--let's pick a default + hope 80 | printf "/tmp/salt.%s" "${suffix}" 81 | fi 82 | } 83 | 84 | # Test if a location is writeable by trying to write to it. Windows does not let 85 | # you test writeability other than by writing: https://stackoverflow.com/q/1999988 86 | test_writeable() { 87 | local path 88 | path="${1:-}/test.txt" 89 | if touch "${path}" 2>/dev/null; then 90 | rm "${path}" 91 | return 0 92 | else 93 | return 1 94 | fi 95 | } 96 | 97 | download() { 98 | file="$1" 99 | url="$2" 100 | touch "$file" 101 | printf "%s" "$file" 102 | 103 | if has curl; then 104 | cmd="curl --fail --silent --location --output $file $url" 105 | elif has wget; then 106 | cmd="wget --quiet --output-document=$file $url" 107 | elif has fetch; then 108 | cmd="fetch --quiet --output=$file $url" 109 | else 110 | error "No HTTP download program (curl, wget, fetch) found, exiting…" 111 | return 1 112 | fi 113 | 114 | $cmd && return 0 || rc=$? 115 | 116 | error "Command failed (exit code $rc): ${BLUE}${cmd}${NO_COLOR}" 117 | printf "\n" >&2 118 | info "This is likely due to salt not yet supporting your configuration." 119 | info "If you would like to see a build for your configuration," 120 | info "please create an issue requesting a build for ${MAGENTA}${TARGET}${NO_COLOR}:" 121 | info "${BOLD}${UNDERLINE}https://github.com/Milo123459/salt/issues/new/${NO_COLOR}" 122 | return $rc 123 | } 124 | 125 | unpack() { 126 | local archive=$1 127 | local bin_dir=$2 128 | local sudo=${3-} 129 | 130 | case "$archive" in 131 | *.tar.gz) 132 | flags=$(test -n "${VERBOSE-}" && echo "-v" || echo "") 133 | ${sudo} tar "${flags}" -xzf "${archive}" -C "${bin_dir}" 134 | return 0 135 | ;; 136 | *.zip) 137 | flags=$(test -z "${VERBOSE-}" && echo "-qq" || echo "") 138 | UNZIP="${flags}" ${sudo} unzip "${archive}" -d "${bin_dir}" 139 | return 0 140 | ;; 141 | esac 142 | 143 | error "Unknown package extension." 144 | printf "\n" 145 | info "This almost certainly results from a bug in this script--please file a" 146 | info "bug report at https://github.com/Milo123459/salt/issues" 147 | return 1 148 | } 149 | 150 | elevate_priv() { 151 | if ! has sudo; then 152 | error 'Could not find the command "sudo", needed to get permissions for install.' 153 | info "If you are on Windows, please run your shell as an administrator, then" 154 | info "rerun this script. Otherwise, please run this script as root, or install" 155 | info "sudo." 156 | exit 1 157 | fi 158 | if ! sudo -v; then 159 | error "Superuser not granted, aborting installation" 160 | exit 1 161 | fi 162 | } 163 | 164 | install() { 165 | local msg 166 | local sudo 167 | local archive 168 | local ext="$1" 169 | 170 | if test_writeable "${BIN_DIR}"; then 171 | sudo="" 172 | msg="Installing salt, please wait…" 173 | else 174 | warn "Escalated permissions are required to install to ${BIN_DIR}" 175 | elevate_priv 176 | sudo="sudo" 177 | msg="Installing salt as root, please wait…" 178 | fi 179 | info "$msg" 180 | 181 | archive=$(get_tmpfile "$ext") 182 | 183 | # download to the temp file 184 | download "${archive}" "${URL}" 185 | 186 | # unpack the temp file to the bin dir, using sudo if required 187 | unpack "${archive}" "${BIN_DIR}" "${sudo}" 188 | 189 | # remove tempfile 190 | 191 | rm "${archive}" 192 | } 193 | 194 | # Currently supporting: 195 | # - win (Git Bash) 196 | # - darwin 197 | # - linux 198 | # - linux_musl (Alpine) 199 | # - freebsd 200 | detect_platform() { 201 | local platform 202 | platform="$(uname -s | tr '[:upper:]' '[:lower:]')" 203 | 204 | case "${platform}" in 205 | msys_nt*) platform="pc-windows-msvc" ;; 206 | cygwin_nt*) platform="pc-windows-msvc";; 207 | # mingw is Git-Bash 208 | mingw*) platform="pc-windows-msvc" ;; 209 | # use the statically compiled musl bins on linux to avoid linking issues. 210 | linux) platform="unknown-linux-musl" ;; 211 | darwin) platform="apple-darwin" ;; 212 | freebsd) platform="unknown-freebsd" ;; 213 | esac 214 | 215 | printf '%s' "${platform}" 216 | } 217 | 218 | # Currently supporting: 219 | # - x86_64 220 | # - i386 221 | detect_arch() { 222 | local arch 223 | arch="$(uname -m | tr '[:upper:]' '[:lower:]')" 224 | 225 | case "${arch}" in 226 | amd64) arch="x86_64" ;; 227 | armv*) arch="arm" ;; 228 | arm64) arch="aarch64" ;; 229 | esac 230 | 231 | # `uname -m` in some cases mis-reports 32-bit OS as 64-bit, so double check 232 | if [ "${arch}" = "x86_64" ] && [ "$(getconf LONG_BIT)" -eq 32 ]; then 233 | arch=i686 234 | elif [ "${arch}" = "aarch64" ] && [ "$(getconf LONG_BIT)" -eq 32 ]; then 235 | arch=arm 236 | fi 237 | 238 | printf '%s' "${arch}" 239 | } 240 | 241 | detect_target() { 242 | local arch="$1" 243 | local platform="$2" 244 | local target="$arch-$platform" 245 | 246 | if [ "${target}" = "arm-unknown-linux-musl" ]; then 247 | target="${target}eabihf" 248 | fi 249 | 250 | printf '%s' "${target}" 251 | } 252 | 253 | 254 | confirm() { 255 | if [ -z "${FORCE-}" ]; then 256 | printf "%s " "${MAGENTA}?${NO_COLOR} $* ${BOLD}[y/N]${NO_COLOR}" 257 | set +e 258 | read -r yn &2 318 | info "If you would like to see a build for your configuration," 319 | info "please create an issue requesting a build for ${MAGENTA}${target}${NO_COLOR}:" 320 | info "${BOLD}${UNDERLINE}https://github.com/Milo123459/salt/issues/new/${NO_COLOR}" 321 | printf "\n" 322 | exit 1 323 | fi 324 | } 325 | UNINSTALL=0 326 | HELP=0 327 | CARGOTOML="$(curl -fsSL https://raw.githubusercontent.com/Milo123459/salt/master/Cargo.toml)" 328 | ALL_VERSIONS="$(sed -n 's/.*version = "\([^"]*\)".*/\1/p' <<< "$CARGOTOML")" 329 | IFS=$'\n' read -r -a VERSION <<< "$ALL_VERSIONS" 330 | # defaults 331 | if [ -z "${PLATFORM-}" ]; then 332 | PLATFORM="$(detect_platform)" 333 | fi 334 | 335 | if [ -z "${BIN_DIR-}" ]; then 336 | BIN_DIR=/usr/local/bin 337 | fi 338 | 339 | if [ -z "${ARCH-}" ]; then 340 | ARCH="$(detect_arch)" 341 | fi 342 | 343 | if [ -z "${BASE_URL-}" ]; then 344 | BASE_URL="https://github.com/Milo123459/salt/releases" 345 | fi 346 | 347 | # parse argv variables 348 | while [ "$#" -gt 0 ]; do 349 | case "$1" in 350 | -p | --platform) 351 | PLATFORM="$2" 352 | shift 2 353 | ;; 354 | -b | --bin-dir) 355 | BIN_DIR="$2" 356 | shift 2 357 | ;; 358 | -a | --arch) 359 | ARCH="$2" 360 | shift 2 361 | ;; 362 | -B | --base-url) 363 | BASE_URL="$2" 364 | shift 2 365 | ;; 366 | 367 | -V | --verbose) 368 | VERBOSE=1 369 | shift 1 370 | ;; 371 | -f | -y | --force | --yes) 372 | FORCE=1 373 | shift 1 374 | ;; 375 | -r | --remove | --uninstall) 376 | UNINSTALL=1 377 | shift 1 378 | ;; 379 | -h | --help) 380 | HELP=1 381 | shift 1 382 | ;; 383 | -p=* | --platform=*) 384 | PLATFORM="${1#*=}" 385 | shift 1 386 | ;; 387 | -b=* | --bin-dir=*) 388 | BIN_DIR="${1#*=}" 389 | shift 1 390 | ;; 391 | -a=* | --arch=*) 392 | ARCH="${1#*=}" 393 | shift 1 394 | ;; 395 | -B=* | --base-url=*) 396 | BASE_URL="${1#*=}" 397 | shift 1 398 | ;; 399 | -V=* | --verbose=*) 400 | VERBOSE="${1#*=}" 401 | shift 1 402 | ;; 403 | -f=* | -y=* | --force=* | --yes=*) 404 | FORCE="${1#*=}" 405 | shift 1 406 | ;; 407 | 408 | *) 409 | error "Unknown option: $1" 410 | exit 1 411 | ;; 412 | esac 413 | done 414 | if [ $UNINSTALL == 1 ]; then 415 | confirm "Are you sure you want to uninstall salt?" 416 | 417 | msg="" 418 | sudo="" 419 | 420 | info "REMOVING salt" 421 | 422 | if test_writeable "$(dirname "$(which salt)")"; then 423 | sudo="" 424 | msg="Removing salt, please wait…" 425 | else 426 | warn "Escalated permissions are required to install to ${BIN_DIR}" 427 | elevate_priv 428 | sudo="sudo" 429 | msg="Removing salt as root, please wait…" 430 | fi 431 | 432 | info "$msg" 433 | ${sudo} rm "$(which salt)" 434 | ${sudo} rm /tmp/salt 435 | 436 | info "Removed salt" 437 | exit 0 438 | 439 | fi 440 | if [ $HELP == 1 ]; then 441 | echo "${help_text}" 442 | exit 0 443 | fi 444 | TARGET="$(detect_target "${ARCH}" "${PLATFORM}")" 445 | 446 | is_build_available "${ARCH}" "${PLATFORM}" "${TARGET}" 447 | 448 | printf " %s\n" "${UNDERLINE}Configuration${NO_COLOR}" 449 | info "${BOLD}Bin directory${NO_COLOR}: ${GREEN}${BIN_DIR}${NO_COLOR}" 450 | info "${BOLD}Platform${NO_COLOR}: ${GREEN}${PLATFORM}${NO_COLOR}" 451 | info "${BOLD}Arch${NO_COLOR}: ${GREEN}${ARCH}${NO_COLOR}" 452 | info "${BOLD}Version${NO_COLOR}: ${GREEN}${VERSION[0]}${NO_COLOR}" 453 | 454 | # non-empty VERBOSE enables verbose untarring 455 | if [ -n "${VERBOSE-}" ]; then 456 | VERBOSE=v 457 | info "${BOLD}Verbose${NO_COLOR}: yes" 458 | else 459 | VERBOSE= 460 | fi 461 | 462 | printf '\n' 463 | 464 | EXT=tar.gz 465 | if [ "${PLATFORM}" = "pc-windows-msvc" ]; then 466 | EXT=zip 467 | fi 468 | 469 | URL="${BASE_URL}/latest/download/salt-${TARGET}.${EXT}" 470 | info "Tarball URL: ${UNDERLINE}${BLUE}${URL}${NO_COLOR}" 471 | confirm "Install salt ${GREEN}${VERSION[0]}${NO_COLOR} to ${BOLD}${GREEN}${BIN_DIR}${NO_COLOR}?" 472 | check_bin_dir "${BIN_DIR}" 473 | 474 | install "${EXT}" 475 | completed "salt installed" -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | reorder_modules = true 3 | use_field_init_shorthand = true 4 | hard_tabs = true -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use structopt::StructOpt; 3 | 4 | #[derive(Serialize, Deserialize, Debug, StructOpt, PartialEq, Clone)] 5 | pub struct Arguments { 6 | /// type of action. run the `action` / `actions` action to see available actions. 7 | pub action: String, 8 | 9 | /// arguments to action 10 | pub arguments: Vec, 11 | 12 | /// todo node name 13 | #[structopt(long, short, default_value = "Default")] 14 | pub node: String, 15 | 16 | /// display done tasks (only applies on certain commands) 17 | #[structopt(long, short)] 18 | pub(crate) checked: Option>, 19 | } 20 | 21 | // for the usage of --checked 22 | impl Arguments { 23 | pub fn checked(&self) -> bool { 24 | match self.checked { 25 | None => false, 26 | Some(None) => true, 27 | Some(Some(a)) => a, 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::args; 2 | use crate::config; 3 | use crate::get_and_parse; 4 | use crate::match_patterns; 5 | use crate::node; 6 | use crate::task; 7 | use colored::*; 8 | use std::io::Error; 9 | use std::str::FromStr; 10 | 11 | pub fn action(input: Vec<&str>) -> anyhow::Result<()> { 12 | // this will sanitize the vec in a sense 13 | // the input has \" \" around the value we want so we remove it 14 | // we also filter out _ from the vec 15 | let actions = input 16 | .into_iter() 17 | .filter_map(|x| x.strip_prefix('"')?.strip_suffix('"')) 18 | .collect::>(); 19 | // log a nice message displaying all the actions 20 | println!( 21 | "Actions available:\n{}", 22 | actions.join(", ").underline().bold() 23 | ); 24 | Ok(()) 25 | } 26 | 27 | pub fn node_sub_command( 28 | config: config::SaltFile, 29 | args: args::Arguments, 30 | supplied_node: String, 31 | checked: bool, 32 | ) -> anyhow::Result<()> { 33 | if args.arguments.first().is_some() { 34 | match_patterns! { &*args.arguments.first().unwrap().to_lowercase(), patterns, 35 | "help" => action(patterns)?, 36 | "new" => { 37 | if config.nodes.iter().any(|i| i.name.to_lowercase() == supplied_node.to_lowercase()) { 38 | return Err(anyhow::Error::new(Error::new( 39 | std::io::ErrorKind::InvalidInput, 40 | format!("You can't create a node that already exists (supplied node was `{}` - please use --node to specify the node name)", supplied_node) 41 | ))) 42 | }; 43 | let mut new_config = config; 44 | new_config.nodes.push(node::Node { 45 | name: supplied_node.to_owned(), 46 | tasks: vec![], 47 | next_id: 1 48 | }); 49 | get_and_parse::write(new_config)?; 50 | println!("{} `{}`", "Created new node".bold().to_string(), &supplied_node.red().to_string()) 51 | }, 52 | "list" => { 53 | let nodes = config.nodes.into_iter().map(|x| x.name).collect::>(); 54 | println!("Available nodes:\n{}", nodes.join(", ").underline().bold()) 55 | }, 56 | "tasks" => { 57 | let possible_node = node::get_node(&supplied_node)?; 58 | println!("{}", node::display_node(possible_node, checked)); 59 | }, 60 | "convert" => { 61 | if args.arguments.get(1).is_some() { 62 | 63 | match_patterns! { &*args.arguments.get(1).unwrap().to_lowercase(), patterns_for_convert, 64 | "help" => action(patterns_for_convert)?, 65 | "md" => { 66 | let possible_node = node::get_node(&supplied_node)?; 67 | let mut markdown: Vec = vec![]; 68 | markdown.push(format!("**{}**\n", possible_node.name)); 69 | for task in possible_node.tasks { 70 | markdown.push(format!("- [{}] **{}** ({})", if task.checked { "x" } else { " " }, task.task, task.id)); 71 | } 72 | println!("{}", markdown.join("\n")) 73 | }, 74 | _ => return Err(anyhow::Error::new(Error::new( 75 | std::io::ErrorKind::InvalidInput, 76 | "Invalid sub-action. Try the command `convert help`", 77 | ))) 78 | } 79 | } else { 80 | println!("Invalid sub-action. Try the command `convert help`") 81 | } 82 | }, 83 | _ => return Err(anyhow::Error::new(Error::new( 84 | std::io::ErrorKind::InvalidInput, 85 | "Invalid sub-action. Try the command `node help`", 86 | ))) 87 | } 88 | } else { 89 | println!("Try `node help`") 90 | } 91 | Ok(()) 92 | } 93 | 94 | pub fn add( 95 | config: config::SaltFile, 96 | args: args::Arguments, 97 | supplied_node: String, 98 | ) -> anyhow::Result<()> { 99 | if args.arguments.first().is_some() { 100 | let task = args.arguments.join(" "); 101 | let other_task = &task.clone(); 102 | let mut possible_node = node::get_node(&supplied_node)?; 103 | let mut new_config = config; 104 | possible_node.tasks.push(task::Task { 105 | id: possible_node.next_id, 106 | task, 107 | checked: false, 108 | }); 109 | 110 | possible_node.next_id = &possible_node.next_id + 1; 111 | for node_in_config in &mut new_config.nodes { 112 | if *node_in_config.name.to_lowercase() == supplied_node.to_lowercase() { 113 | *node_in_config = (&possible_node).to_owned(); 114 | } 115 | } 116 | 117 | get_and_parse::write(new_config)?; 118 | 119 | println!( 120 | "Created task `{}` in node `{}`", 121 | other_task, 122 | supplied_node.red() 123 | ); 124 | } else { 125 | println!("Please specify a task description!") 126 | } 127 | Ok(()) 128 | } 129 | 130 | pub fn check( 131 | config: config::SaltFile, 132 | args: args::Arguments, 133 | supplied_node: String, 134 | ) -> anyhow::Result<()> { 135 | if args.arguments.first().is_some() { 136 | let mut possible_node = node::get_node(&supplied_node)?; 137 | let mut new_config = config; 138 | let mut status = false; 139 | for task_node in &mut possible_node.tasks { 140 | if task_node.id.to_string() == *args.arguments.first().unwrap() { 141 | status = !task_node.checked; 142 | task_node.checked = status; 143 | *task_node = task_node.to_owned(); 144 | } 145 | } 146 | for node_in_config in &mut new_config.nodes { 147 | if *node_in_config.name.to_lowercase() == supplied_node.to_lowercase() { 148 | *node_in_config = (&possible_node).to_owned(); 149 | } 150 | } 151 | get_and_parse::write(new_config)?; 152 | println!( 153 | "Task with ID of `{}` was {}", 154 | args.arguments.first().unwrap().to_string().red(), 155 | if status { "checked" } else { "unchecked" } 156 | ) 157 | } else { 158 | println!("Please specify the task ID") 159 | } 160 | Ok(()) 161 | } 162 | 163 | pub fn tasks(checked: bool, supplied_node: String) -> anyhow::Result<()> { 164 | let possible_node = node::get_node(&supplied_node)?; 165 | println!("{}", node::display_node(possible_node, checked)); 166 | Ok(()) 167 | } 168 | 169 | pub fn edit( 170 | config: config::SaltFile, 171 | args: args::Arguments, 172 | supplied_node: String, 173 | ) -> anyhow::Result<()> { 174 | if args.arguments.first().is_some() && args.arguments.get(1).is_some() { 175 | let joined_args = args.arguments.join(" "); 176 | let parsed_arguments = joined_args.split_once(" ").unwrap(); 177 | let id = FromStr::from_str(parsed_arguments.0)?; 178 | let new_description = parsed_arguments.1; 179 | let mut possible_node = node::get_node(&supplied_node)?; 180 | let mut new_config = config; 181 | if task::task_exists(possible_node.clone(), id).unwrap() { 182 | if let Some(task) = possible_node.tasks.iter_mut().find(|t| t.id == id) { 183 | task.task = String::from(new_description) 184 | } 185 | 186 | for node_in_config in &mut new_config.nodes { 187 | if *node_in_config.name.to_lowercase() == supplied_node.to_lowercase() { 188 | *node_in_config = (&possible_node).to_owned(); 189 | } 190 | } 191 | 192 | get_and_parse::write(new_config)?; 193 | 194 | println!( 195 | "Edited task with ID `{}` in node `{}`", 196 | id, 197 | supplied_node.red() 198 | ); 199 | } else { 200 | println!("Couldn't find task.") 201 | } 202 | } else { 203 | println!("Please specify a task id, and the new description of the task.") 204 | } 205 | Ok(()) 206 | } 207 | 208 | pub fn all(config: config::SaltFile, checked: bool) -> anyhow::Result<()> { 209 | for supplied_node in config.nodes { 210 | println!("{}", node::display_node(supplied_node, checked)); 211 | } 212 | Ok(()) 213 | } 214 | 215 | pub fn match_cmds(args: args::Arguments, config: config::SaltFile) -> anyhow::Result<()> { 216 | let cmd = &args.action; 217 | let supplied_node = args.clone().node; 218 | let checked = args.clone().checked(); 219 | match_patterns! { &*cmd.to_lowercase(), patterns, 220 | "action" => action(patterns)?, 221 | "actions" => action(patterns)?, 222 | "node" => node_sub_command(config, args, supplied_node, checked)?, 223 | "add" => add(config, args, supplied_node)?, 224 | "check" => check(config, args, supplied_node)?, 225 | "uncheck" => check(config, args, supplied_node)?, 226 | "done" => check(config, args, supplied_node)?, 227 | "tasks" => tasks(checked, supplied_node)?, 228 | "all" => all(config, checked)?, 229 | "edit" => edit(config, args, supplied_node)?, 230 | _ => return Err(anyhow::Error::new(Error::new( 231 | std::io::ErrorKind::InvalidInput, 232 | "Invalid action. Try the command `action`", 233 | ))) 234 | }; 235 | Ok(()) 236 | } 237 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::node; 2 | use serde::{Deserialize, Serialize}; 3 | #[derive(Debug, Deserialize, Serialize, Clone)] 4 | pub struct SaltFile { 5 | pub nodes: Vec, 6 | } 7 | -------------------------------------------------------------------------------- /src/get_and_parse.rs: -------------------------------------------------------------------------------- 1 | use crate::config::SaltFile; 2 | use crate::node::Node; 3 | use anyhow::Context; 4 | use dirs_next::home_dir; 5 | use std::fs; 6 | use std::io::prelude::*; 7 | use std::path::{Path, PathBuf}; 8 | 9 | pub fn parse() -> anyhow::Result { 10 | let dir = folder_path(); 11 | fs::create_dir_all(dir)?; 12 | let file = file_path(); 13 | let does_exist = Path::new(file.as_path()).exists(); 14 | if does_exist { 15 | read(file) 16 | } else { 17 | let config = SaltFile { 18 | nodes: vec![Node { 19 | name: "Default".to_owned(), 20 | tasks: vec![], 21 | next_id: 1, 22 | }], 23 | }; 24 | write(config) 25 | } 26 | } 27 | 28 | pub fn folder_path() -> PathBuf { 29 | let mut dir = PathBuf::new(); 30 | dir.push(home_dir().unwrap()); 31 | dir.push(".salt"); 32 | dir 33 | } 34 | 35 | pub fn file_path() -> PathBuf { 36 | let mut file = folder_path(); 37 | file.push("salt.json"); 38 | file 39 | } 40 | 41 | pub fn write(config: SaltFile) -> anyhow::Result { 42 | let dir = folder_path(); 43 | fs::create_dir_all(dir)?; 44 | let file = file_path(); 45 | let json = serde_json::to_string(&config).unwrap(); 46 | let mut physical_file = fs::File::create(file.as_path())?; 47 | physical_file.write_all(json.as_bytes())?; 48 | Ok(config) 49 | } 50 | 51 | pub fn read(path: PathBuf) -> anyhow::Result { 52 | let file = fs::File::open(path.as_path()); 53 | match serde_json::from_reader(file.unwrap()) { 54 | Ok(json) => Ok(json), 55 | Err(err) => Err(anyhow::Error::new(err)) 56 | .with_context(|| "Error parsing saltfile. Try deleting the file."), 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | // this is a macro that will return the patterns in match's 2 | #[macro_export] 3 | macro_rules! match_patterns { 4 | ($val:expr, $patterns_ident:ident, $($p:pat => $e:expr),*) => { 5 | let $patterns_ident = vec![$(stringify!($p)),*]; 6 | match $val { 7 | $($p => $e),* 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod args; 2 | mod cli; 3 | mod config; 4 | mod get_and_parse; 5 | mod macros; 6 | mod node; 7 | mod run; 8 | mod task; 9 | pub use anyhow::Context; 10 | pub use structopt::StructOpt; 11 | 12 | fn main() -> anyhow::Result<()> { 13 | run::run(args::Arguments::from_args())?; 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /src/node.rs: -------------------------------------------------------------------------------- 1 | use crate::get_and_parse; 2 | use crate::task; 3 | use colored::*; 4 | use serde::{Deserialize, Serialize}; 5 | use std::io::Error; 6 | 7 | #[derive(Debug, Deserialize, Serialize, Clone)] 8 | pub struct Node { 9 | pub name: String, 10 | pub tasks: Vec, 11 | pub next_id: i32, 12 | } 13 | 14 | pub fn get_node(name: &str) -> anyhow::Result { 15 | let nodes = get_and_parse::read(get_and_parse::file_path())?; 16 | let node = nodes 17 | .nodes 18 | .into_iter() 19 | .find(|node| node.name.to_lowercase() == name.to_lowercase()); 20 | if let Some(n) = node { 21 | Ok(n) 22 | } else { 23 | Err(anyhow::Error::new(Error::new( 24 | std::io::ErrorKind::InvalidInput, 25 | format!("No node with name `{}` was found", name), 26 | ))) 27 | } 28 | } 29 | 30 | pub fn display_node(node: Node, show_hidden: bool) -> String { 31 | let name = node.name.truecolor(246, 105, 194).bold().to_string(); 32 | let tasks_size = format!( 33 | "{}{}{}", 34 | "[".yellow().to_string(), 35 | node.tasks 36 | .iter() 37 | .filter(|task| if show_hidden { true } else { !task.checked }) 38 | .count() 39 | .to_string() 40 | .blue() 41 | .to_string(), 42 | "]".yellow().to_string() 43 | ); 44 | let tasks = node 45 | .tasks 46 | .iter() 47 | .filter(|task| if show_hidden { true } else { !task.checked }) 48 | .map(|task| task::display_task(task)) 49 | .collect::>() 50 | .join("\n └─> "); 51 | format!( 52 | "{} {}\n {}\n └─> {}", 53 | name, 54 | tasks_size, 55 | "Tasks".bold().truecolor(113, 247, 159).to_string(), 56 | tasks 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /src/run.rs: -------------------------------------------------------------------------------- 1 | use crate::args; 2 | use crate::cli; 3 | use crate::get_and_parse; 4 | 5 | pub fn run(args: args::Arguments) -> anyhow::Result<()> { 6 | let config = get_and_parse::parse().unwrap(); 7 | cli::match_cmds(args, config)?; 8 | Ok(()) 9 | } 10 | -------------------------------------------------------------------------------- /src/task.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use colored::*; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::node::Node; 6 | 7 | #[derive(Debug, Deserialize, Serialize, Clone)] 8 | pub struct Task { 9 | pub checked: bool, 10 | pub task: String, 11 | pub id: i32, 12 | } 13 | 14 | pub fn task_exists(node: Node, id: i32) -> anyhow::Result { 15 | let task = node.tasks.iter().find(|task| task.id == id); 16 | if task.is_some() { 17 | Ok(true) 18 | } else { 19 | Ok(false) 20 | } 21 | } 22 | 23 | pub fn display_task(task: &Task) -> String { 24 | let mut status = "[]".yellow().to_string(); 25 | let id = format!( 26 | "{}{}{}", 27 | "(".blue().to_string(), 28 | task.id.to_string().red().to_string(), 29 | ")".blue().to_string() 30 | ); 31 | if task.checked { 32 | status = format!( 33 | "{}{}{}", 34 | "[".yellow().to_string(), 35 | "x".blue().to_string(), 36 | "]".yellow().to_string() 37 | ); 38 | }; 39 | format!("{} {} {} ", status, task.task, id) 40 | } 41 | --------------------------------------------------------------------------------