mirror of
https://github.com/starship/starship.git
synced 2026-06-23 02:05:51 +07:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b05177713 | |||
| ff2d7d415f | |||
| 2f75c0ed34 | |||
| a765690f30 | |||
| cd75be40f3 | |||
| 1111230289 | |||
| b78f271fee | |||
| 682fa5f312 | |||
| 671f23140e | |||
| 2ae09e226a | |||
| 91efd25889 | |||
| 49cf2b016f | |||
| dc60c13b0d | |||
| fea4e5155a | |||
| f595899021 | |||
| 2b03c4e721 | |||
| 3dd8c14144 | |||
| 033f20b461 | |||
| da264890e3 | |||
| 57bb99bd0d | |||
| 9a17d3a4e2 | |||
| f28f7791a9 | |||
| 4197977efe | |||
| a496165316 | |||
| 166d7bb30a | |||
| 528332ee61 | |||
| cfd5e7619e | |||
| 26ce2ccf4c | |||
| 712210ecfe | |||
| 3df4a67591 | |||
| dc5c898dd1 | |||
| eb54ec12c8 | |||
| 831cffbb22 | |||
| dacc8e9408 | |||
| db39a6539d | |||
| 96c1f90eeb | |||
| 05ee20141e | |||
| 0d8ca602ad | |||
| 7499e283b3 | |||
| 54e861221c | |||
| 14a5f870af | |||
| e521f39078 | |||
| 17cf5317eb | |||
| 3ebbeae1a7 | |||
| 5162c4159b | |||
| b76e16c38c | |||
| 7d9cbeb5b7 | |||
| 41f2030db1 | |||
| e1d8dea865 | |||
| fe0296bc8f | |||
| ffe59d6787 | |||
| b85b7b9c52 | |||
| d455255e7b | |||
| d0e246802c | |||
| a54b62bfb1 | |||
| e5a1f9c33d | |||
| ed582dd038 | |||
| cd0b573326 | |||
| 10daba00c0 | |||
| c9bacab812 | |||
| 30c7f513d3 | |||
| bea2843088 | |||
| 6a2096608e | |||
| 387c2f6e28 |
+2
-2
@@ -25,9 +25,9 @@
|
||||
"target/"
|
||||
],
|
||||
"plugins": [
|
||||
"https://github.com/dprint/dprint-plugin-typescript/releases/download/0.95.15/plugin.wasm",
|
||||
"https://github.com/dprint/dprint-plugin-typescript/releases/download/0.96.1/plugin.wasm",
|
||||
"https://github.com/dprint/dprint-plugin-json/releases/download/0.21.3/plugin.wasm",
|
||||
"https://github.com/dprint/dprint-plugin-markdown/releases/download/0.21.1/plugin.wasm",
|
||||
"https://github.com/dprint/dprint-plugin-markdown/releases/download/0.22.1/plugin.wasm",
|
||||
"https://github.com/dprint/dprint-plugin-toml/releases/download/0.7.0/plugin.wasm"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -16,12 +16,27 @@ Closes #
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, tests ran to see how -->
|
||||
<!--- your change affects other areas of the code, etc. -->
|
||||
<!--- Most changes do not need to be tested on multiple operating systems. -->
|
||||
- [ ] I have tested using **MacOS**
|
||||
- [ ] I have tested using **Linux**
|
||||
- [ ] I have tested using **Windows**
|
||||
|
||||
#### AI-Assistance
|
||||
<!-- Per our AI Policy (AI_POLICY.md), all AI usage must be declared. -->
|
||||
Have you used AI-assistance to author this PR?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
If **yes**, describe the scope of assistance:
|
||||
<!--- Describe how you used AI-Assistance -->
|
||||
<!--- Disclosure is required if you have used AI-assistance -->
|
||||
<!--- For example: answering project questions, writing tests, implementation, or documentation -->
|
||||
N/A
|
||||
|
||||
#### Checklist:
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have updated the tests accordingly.
|
||||
- [ ] I understand and have read the code I contribute and can answer questions about it.
|
||||
|
||||
@@ -1076,9 +1076,7 @@
|
||||
"detect_files": [
|
||||
"pom.xml"
|
||||
],
|
||||
"detect_folders": [
|
||||
".mvn"
|
||||
]
|
||||
"detect_folders": []
|
||||
}
|
||||
},
|
||||
"memory_usage": {
|
||||
@@ -1211,7 +1209,10 @@
|
||||
".nvmrc",
|
||||
"!bunfig.toml",
|
||||
"!bun.lock",
|
||||
"!bun.lockb"
|
||||
"!bun.lockb",
|
||||
"!deno.json",
|
||||
"!deno.jsonc",
|
||||
"!deno.lock"
|
||||
],
|
||||
"detect_folders": [
|
||||
"node_modules"
|
||||
@@ -4846,9 +4847,7 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
".mvn"
|
||||
]
|
||||
"default": []
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -5203,7 +5202,10 @@
|
||||
".nvmrc",
|
||||
"!bunfig.toml",
|
||||
"!bun.lock",
|
||||
"!bun.lockb"
|
||||
"!bun.lockb",
|
||||
"!deno.json",
|
||||
"!deno.jsonc",
|
||||
"!deno.lock"
|
||||
]
|
||||
},
|
||||
"detect_folders": {
|
||||
|
||||
@@ -135,5 +135,16 @@
|
||||
],
|
||||
datasourceTemplate: 'crate',
|
||||
},
|
||||
{
|
||||
customType: 'regex',
|
||||
managerFilePatterns: [
|
||||
'/^(workflow-templates|\\.github\\/workflows)\\/[^/]+\\.ya?ml$/',
|
||||
],
|
||||
matchStrings: [
|
||||
'ziglang==(?<currentValue>\\S+)',
|
||||
],
|
||||
datasourceTemplate: 'pypi',
|
||||
depNameTemplate: 'ziglang',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- name: Docs | Format
|
||||
uses: dprint/check@9cb3a2b17a8e606d37aae341e49df3654933fc23 # v2.3
|
||||
|
||||
@@ -22,9 +22,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- name: Install | Taplo
|
||||
uses: taiki-e/install-action@7a4939c09608b2a1986b484eca1d16fd0db8ebef # v2.75.5
|
||||
uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
|
||||
with:
|
||||
tool: taplo-cli@0.10.0
|
||||
- name: Presets | Validate with schema
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref != 'i18n_master' }}
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Prevent File Change
|
||||
@@ -64,12 +64,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Setup | Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 24.15.0
|
||||
node-version: 24.16.0
|
||||
cache: 'npm'
|
||||
cache-dependency-path: docs/package-lock.json
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Lint | Run shellcheck
|
||||
run: shellcheck --severity=warning install/**/*.sh
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Setup | Install shfmt
|
||||
run: |
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Test | Piped execution with curl
|
||||
run: |
|
||||
|
||||
@@ -7,12 +7,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Setup | Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 24.15.0
|
||||
node-version: 24.16.0
|
||||
cache: 'npm'
|
||||
cache-dependency-path: docs/package-lock.json
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ name: Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
@@ -58,6 +58,7 @@ jobs:
|
||||
- target: riscv64gc-unknown-linux-musl
|
||||
os: ubuntu-latest
|
||||
name: starship-riscv64gc-unknown-linux-musl.tar.gz
|
||||
use_zigbuild: true
|
||||
|
||||
- target: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
@@ -92,7 +93,7 @@ jobs:
|
||||
RUSTFLAGS: ${{ matrix.rustflags || '' }}
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Setup | Rust
|
||||
run: rustup toolchain install stable --profile minimal --target ${{ matrix.target }} --no-self-update
|
||||
@@ -106,19 +107,33 @@ jobs:
|
||||
RUSTFLAGS: ""
|
||||
|
||||
- name: Setup | Install cross [Linux]
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
uses: taiki-e/install-action@7a4939c09608b2a1986b484eca1d16fd0db8ebef # v2.75.5
|
||||
if: matrix.os == 'ubuntu-latest' && !matrix.use_zigbuild
|
||||
uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
|
||||
with:
|
||||
tool: cross@0.2.5
|
||||
|
||||
- name: Setup | Install Zig [riscv64]
|
||||
if: matrix.use_zigbuild
|
||||
run: pip install ziglang==0.16.0
|
||||
|
||||
- name: Setup | Install cargo-zigbuild [riscv64]
|
||||
if: matrix.use_zigbuild
|
||||
uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
|
||||
with:
|
||||
tool: cargo-zigbuild
|
||||
|
||||
- name: Build | Build [Cargo]
|
||||
if: matrix.os != 'ubuntu-latest'
|
||||
run: cargo build --release --locked --target ${{ matrix.target }}
|
||||
|
||||
- name: Build | Build [Cross]
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
if: matrix.os == 'ubuntu-latest' && !matrix.use_zigbuild
|
||||
run: cross build --release --locked --target ${{ matrix.target }}
|
||||
|
||||
- name: Build | Build [Zigbuild]
|
||||
if: matrix.use_zigbuild
|
||||
run: cargo zigbuild --release --locked --target ${{ matrix.target }}
|
||||
|
||||
- name: Build | Installer [Windows]
|
||||
continue-on-error: true
|
||||
if: matrix.os == 'windows-latest'
|
||||
@@ -141,7 +156,7 @@ jobs:
|
||||
- name: Sign | Sign [Windows]
|
||||
continue-on-error: true
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: signpath/github-action-submit-signing-request@bc66d86b015a46e9c6d9700de73143a82f9570ff # v2.1
|
||||
uses: signpath/github-action-submit-signing-request@b9d91eadd323de506c0c81cf0c7fe7438f3360fd # v2.2
|
||||
with:
|
||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||
organization-id: '${{ vars.SIGNPATH_ORGANIZATION_ID }}'
|
||||
@@ -204,10 +219,10 @@ jobs:
|
||||
STARSHIP_VERSION: ${{ needs.release_please.outputs.tag_name }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
# Required to include the recently merged Crowdin PR
|
||||
ref: master
|
||||
ref: main
|
||||
|
||||
- name: Notarize | Set up secrets
|
||||
env:
|
||||
@@ -241,9 +256,9 @@ jobs:
|
||||
xcrun notarytool store-credentials "$KEYCHAIN_ENTRY" --team-id "$APPLEID_TEAMID" --apple-id "$APPLEID_USERNAME" --password "$APPLEID_PASSWORD" --keychain "$KEYCHAIN_PATH"
|
||||
|
||||
- name: Setup | Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 24.15.0
|
||||
node-version: 24.16.0
|
||||
|
||||
- name: Notarize | Build docs
|
||||
run: |
|
||||
@@ -317,13 +332,13 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Setup | Rust
|
||||
run: rustup toolchain install stable --profile minimal --no-self-update
|
||||
|
||||
- name: Build | Authenticate
|
||||
uses: rust-lang/crates-io-auth-action@b7e9a28eded4986ec6b1fa40eeee8f8f165559ec # v1.0.3
|
||||
uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe # v1.0.4
|
||||
id: auth
|
||||
|
||||
- name: Build | Publish
|
||||
@@ -375,7 +390,7 @@ jobs:
|
||||
if: ${{ needs.release_please.outputs.release_created == 'true' }}
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- name: Setup | Artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- run: pwsh ./install/windows/choco/update.ps1
|
||||
@@ -391,7 +406,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- name: Merge | Merge Crowdin PR
|
||||
run: gh pr merge i18n_master --squash --repo=starship/starship
|
||||
env:
|
||||
@@ -403,7 +418,7 @@ jobs:
|
||||
needs: merge_crowdin_pr
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- name: Trigger workflow dispatch
|
||||
run: gh workflow run publish-docs.yml
|
||||
env:
|
||||
|
||||
@@ -22,8 +22,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- name: Test | Security Audit
|
||||
uses: EmbarkStudios/cargo-deny-action@91bf2b620e09e18d6eb78b92e7861937469acedb # v2.0.17
|
||||
uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe # v2.0.20
|
||||
with:
|
||||
command: check ${{ matrix.checks }}
|
||||
|
||||
@@ -6,5 +6,5 @@ jobs:
|
||||
name: Spell Check with Typos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: crate-ci/typos@7c572958218557a3272c2d6719629443b5cc26fd # v1.45.2
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- uses: crate-ci/typos@37bb98842b0d8c4ffebdb75301a13db0267cef89 # v1.47.2
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Setup | Rust
|
||||
run: rustup toolchain install stable --profile minimal --component rustfmt --no-self-update
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Setup | Rust
|
||||
run: rustup toolchain install stable --profile minimal --component clippy --no-self-update
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Setup | Rust
|
||||
run: rustup toolchain install stable --profile minimal --no-self-update
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Setup | Rust
|
||||
run: rustup toolchain install stable --profile minimal --no-self-update
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Setup | Rust
|
||||
run: rustup toolchain install stable --profile minimal --no-self-update
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Setup | Rust
|
||||
run: rustup toolchain install stable --profile minimal --no-self-update
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
RUSTFLAGS: ${{ matrix.rustflags || '' }}
|
||||
steps:
|
||||
- name: Setup | Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
# Install all the required dependencies for testing
|
||||
- name: Setup | Rust
|
||||
@@ -162,7 +162,7 @@ jobs:
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@7a4939c09608b2a1986b484eca1d16fd0db8ebef # v2.75.5
|
||||
uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
|
||||
with:
|
||||
tool: cargo-llvm-cov@0.8.5
|
||||
|
||||
@@ -234,7 +234,7 @@ jobs:
|
||||
target/debug/starship-x86_64-pc-windows-msvc.msi
|
||||
|
||||
- name: Sign | Sign [Windows]
|
||||
uses: signpath/github-action-submit-signing-request@bc66d86b015a46e9c6d9700de73143a82f9570ff # v2.1
|
||||
uses: signpath/github-action-submit-signing-request@b9d91eadd323de506c0c81cf0c7fe7438f3360fd # v2.2
|
||||
continue-on-error: true
|
||||
if: matrix.os == 'windows-latest' && matrix.rust == 'stable' && github.event_name == 'push' && github.repository == 'starship/starship'
|
||||
with:
|
||||
@@ -247,7 +247,7 @@ jobs:
|
||||
output-artifact-directory: target/debug
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
if: github.repository == 'starship/starship'
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# AI Usage Policy
|
||||
|
||||
This policy supplements our [Contributing Guide](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
This project is maintained by volunteers.
|
||||
This policy exists to keep review burden reasonable.
|
||||
The policy is inspired by the [Ghostty](https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md) and [LLVM](https://llvm.org/docs/AIToolPolicy.html) AI policies.
|
||||
|
||||
Contributions that violate this policy may be closed without further notice.
|
||||
|
||||
## Mandatory Disclosure
|
||||
|
||||
Every Pull Request that utilizes AI-assisted tooling (including but not limited to Claude Code, Cursor, GitHub Copilot, ChatGPT, or local LLMs) must disclose its usage.
|
||||
|
||||
### PR Description
|
||||
|
||||
You must complete the **AI-Assistance** section in our Pull Request Template.
|
||||
|
||||
## Human-in-the-Loop
|
||||
|
||||
Contributors must fully understand all submitted contributions.
|
||||
|
||||
### Contributions
|
||||
|
||||
- You must be able to explain what your changes do and defend your implementation choices.
|
||||
- You are expected to have read and understood every line of code you submit.
|
||||
- If your response to a maintainer's question is an unedited copy-paste from an LLM, or if you cannot explain the mechanics of your PR, the PR will be closed.
|
||||
|
||||
### Issue Triage and Discussions
|
||||
|
||||
You are not allowed to reply to user issues or discussions with unverified or raw AI-generated information.
|
||||
|
||||
## "Good First Issue" Protections
|
||||
|
||||
You may not submit contributions to close a `🌱 good first issue` if they were authored with substantial AI assistance.
|
||||
These issues are intentionally triaged as learning opportunities for new developers navigating the codebase for the first time.
|
||||
|
||||
## Low-Effort Contributions & Prohibition of Autonomous Agents
|
||||
|
||||
- Contributions that are overly verbose, contain unsupported or hallucinated claims, or otherwise show the hallmarks of low-effort LLM usage may be closed without further notice.
|
||||
- Contributions via OpenClaw, or any other unsupervised autonomous agent operating in an automated loop, are strictly prohibited.
|
||||
+2
-2
@@ -273,10 +273,10 @@ Once setup is complete, you can refer to VitePress documentation on the actual i
|
||||
This is our preferred process for opening a PR on GitHub:
|
||||
|
||||
1. Fork this repository
|
||||
2. Create a branch off of `master` for your work: `git checkout -b my-feature-branch`
|
||||
2. Create a branch off of `main` for your work: `git checkout -b my-feature-branch`
|
||||
3. Make some changes, committing them along the way
|
||||
4. When your changes are ready for review, push your branch: `git push origin my-feature-branch`
|
||||
5. Create a pull request from your branch to `starship/master`
|
||||
5. Create a pull request from your branch to `starship/main`
|
||||
6. No need to assign the pull request to anyone, we'll review it when we can
|
||||
7. When the changes have been reviewed and approved, someone will squash and merge for you
|
||||
|
||||
|
||||
Generated
+378
-639
File diff suppressed because it is too large
Load Diff
+18
-18
@@ -35,53 +35,54 @@ config-schema = ["schemars"]
|
||||
notify = ["notify-rust"]
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.44", default-features = false, features = ["clock", "std", "wasmbind"] }
|
||||
clap = { version = "4.6.1", features = ["derive", "cargo", "unicode"] }
|
||||
clap_complete = "4.6.3"
|
||||
clap_complete = "4.6.5"
|
||||
clap_complete_nushell = "4.6.0"
|
||||
dirs = "6.0.0"
|
||||
dunce = "1.0.5"
|
||||
# default feature restriction addresses https://github.com/starship/starship/issues/4251
|
||||
gix = { version = "0.83.0", default-features = false, features = ["max-performance-safe", "revision", "zlib-rs", "status", "sha1"] }
|
||||
gix = { version = "0.84.0", default-features = false, features = ["max-performance-safe", "revision", "zlib-rs", "status", "sha1"] }
|
||||
indexmap = { version = "2.14.0", features = ["serde"] }
|
||||
jsonc-parser = { version = "0.32.3", features = ["serde"] }
|
||||
log = { version = "0.4.29", features = ["std"] }
|
||||
jiff = { version = "0.2.28", features = ["serde"] }
|
||||
jsonc-parser = { version = "0.32.4", features = ["serde"] }
|
||||
log = { version = "0.4.32", features = ["std"] }
|
||||
# notify-rust is optional (on by default) because the crate doesn't currently build for darwin with nix
|
||||
# see: https://github.com/NixOS/nixpkgs/issues/160876
|
||||
notify-rust = { version = "4.16.1", optional = true }
|
||||
notify-rust = { version = "4.18.0", optional = true }
|
||||
nu-ansi-term = "0.50.3"
|
||||
open = "5.3.4"
|
||||
open = "5.3.5"
|
||||
# update os module config and tests when upgrading os_info
|
||||
os_info = { version = "3.14.0", features = ["schemars"] }
|
||||
os_info = { version = "3.15.0", features = ["schemars"] }
|
||||
# for efficient shared state between `git_status` and `git_metrics`, allowing parallel printing. This is for poison-free locks.
|
||||
parking_lot = "0.12.5"
|
||||
path-slash = "0.2.1"
|
||||
pest = "2.8.6"
|
||||
pest_derive = "2.8.6"
|
||||
process_control = "5.2.0"
|
||||
quick-xml = "0.39.2"
|
||||
quick-xml = "0.40.1"
|
||||
rand = "0.10.1"
|
||||
rayon = "1.12.0"
|
||||
regex = { version = "1.12.3", default-features = false, features = ["perf", "std", "unicode-perl"] }
|
||||
regex = { version = "1.12.4", default-features = false, features = ["perf", "std", "unicode-perl"] }
|
||||
rust-ini = "0.21.3"
|
||||
semver = "1.0.28"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
serde_json = "1.0.150"
|
||||
sha1 = "0.11.0"
|
||||
shadow-rs = { version = "2.0.0", default-features = false, features = ["build"] }
|
||||
# battery is optional (on by default) because the crate doesn't currently build for Termux
|
||||
# see: https://github.com/svartalf/rust-battery/issues/33
|
||||
starship-battery = { version = "0.11.0", optional = true }
|
||||
strsim = "0.11.1"
|
||||
systemstat = "=0.2.6"
|
||||
systemstat = "=0.2.7"
|
||||
tempfile = "3.27.0"
|
||||
terminal_size = "0.4.4"
|
||||
toml = { version = "1.1.2", features = ["preserve_order"] }
|
||||
toml_edit = "0.25.11"
|
||||
unicode-segmentation = "1.13.2"
|
||||
toml_edit = "0.25.12"
|
||||
unicode-segmentation = "1.13.3"
|
||||
unicode-width = "0.2.2"
|
||||
urlencoding = "2.1.3"
|
||||
versions = "7.0.0"
|
||||
which = "8.0.2"
|
||||
which = "8.0.4"
|
||||
whoami = { version = "2.1.2", default-features = false, features = ["std", "wasi-wasite"] }
|
||||
yaml-rust2 = "0.11.0"
|
||||
|
||||
@@ -108,7 +109,7 @@ features = [
|
||||
]
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
nix = { version = "0.31.2", default-features = false, features = ["feature", "fs", "user"] }
|
||||
nix = { version = "0.31.3", default-features = false, features = ["feature", "fs", "user"] }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "2.0.0", default-features = false, features = ["build"] }
|
||||
@@ -118,8 +119,7 @@ dunce = "1.0.5"
|
||||
winres = "0.1.12"
|
||||
|
||||
[dev-dependencies]
|
||||
mockall = "0.14.0"
|
||||
tempfile = "3.27.0"
|
||||
mockall = "=0.14.0"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<p align="center">
|
||||
<img
|
||||
width="400"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/logo.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/logo.png"
|
||||
alt="Starship – Cross-shell prompt"
|
||||
/>
|
||||
</p>
|
||||
@@ -9,7 +9,7 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/starship/starship/actions"
|
||||
><img
|
||||
src="https://img.shields.io/github/actions/workflow/status/starship/starship/workflow.yml?branch=master&label=workflow&style=flat-square"
|
||||
src="https://img.shields.io/github/actions/workflow/status/starship/starship/workflow.yml?branch=main&label=workflow&style=flat-square"
|
||||
alt="GitHub Actions workflow status"
|
||||
/></a>
|
||||
<a href="https://crates.io/crates/starship"
|
||||
@@ -48,114 +48,125 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/starship/starship/blob/master/README.md"
|
||||
<a href="https://github.com/starship/starship/blob/main/README.md"
|
||||
><img
|
||||
height="20"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/flag-us.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/flag-us.png"
|
||||
alt="English"
|
||||
/></a>
|
||||
|
||||
<a
|
||||
href="https://github.com/starship/starship/blob/master/docs/de-DE/guide/README.md"
|
||||
href="https://github.com/starship/starship/blob/main/docs/de-DE/guide/README.md"
|
||||
><img
|
||||
height="20"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/flag-de.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/flag-de.png"
|
||||
alt="Deutsch"
|
||||
/></a>
|
||||
|
||||
<a
|
||||
href="https://github.com/starship/starship/blob/master/docs/es-ES/guide/README.md"
|
||||
href="https://github.com/starship/starship/blob/main/docs/es-ES/guide/README.md"
|
||||
><img
|
||||
height="20"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/flag-es.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/flag-es.png"
|
||||
alt="Español"
|
||||
/></a>
|
||||
|
||||
<a
|
||||
href="https://github.com/starship/starship/blob/master/docs/fr-FR/guide/README.md"
|
||||
href="https://github.com/starship/starship/blob/main/docs/fr-FR/guide/README.md"
|
||||
><img
|
||||
height="20"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/flag-fr.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/flag-fr.png"
|
||||
alt="Français"
|
||||
/></a>
|
||||
|
||||
<a
|
||||
href="https://github.com/starship/starship/blob/master/docs/id-ID/guide/README.md"
|
||||
href="https://github.com/starship/starship/blob/main/docs/id-ID/guide/README.md"
|
||||
><img
|
||||
height="20"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/flag-id.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/flag-id.png"
|
||||
alt="Bahasa Indonesia"
|
||||
/></a>
|
||||
|
||||
<a
|
||||
href="https://github.com/starship/starship/blob/master/docs/it-IT/guide/README.md"
|
||||
href="https://github.com/starship/starship/blob/main/docs/it-IT/guide/README.md"
|
||||
><img
|
||||
height="20"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/flag-it.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/flag-it.png"
|
||||
alt="Italiano"
|
||||
/></a>
|
||||
|
||||
<a
|
||||
href="https://github.com/starship/starship/blob/master/docs/ja-JP/guide/README.md"
|
||||
href="https://github.com/starship/starship/blob/main/docs/ja-JP/guide/README.md"
|
||||
><img
|
||||
height="20"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/flag-jp.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/flag-jp.png"
|
||||
alt="日本語"
|
||||
/></a>
|
||||
|
||||
<a
|
||||
href="https://github.com/starship/starship/blob/master/docs/pt-BR/guide/README.md"
|
||||
href="https://github.com/starship/starship/blob/main/docs/pt-BR/guide/README.md"
|
||||
><img
|
||||
height="20"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/flag-br.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/flag-br.png"
|
||||
alt="Português do Brasil"
|
||||
/></a>
|
||||
|
||||
<a
|
||||
href="https://github.com/starship/starship/blob/master/docs/ru-RU/guide/README.md"
|
||||
href="https://github.com/starship/starship/blob/main/docs/ru-RU/guide/README.md"
|
||||
><img
|
||||
height="20"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/flag-ru.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/flag-ru.png"
|
||||
alt="Русский"
|
||||
/></a>
|
||||
|
||||
<a
|
||||
href="https://github.com/starship/starship/blob/master/docs/uk-UA/guide/README.md"
|
||||
href="https://github.com/starship/starship/blob/main/docs/uk-UA/guide/README.md"
|
||||
><img
|
||||
height="20"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/flag-ua.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/flag-ua.png"
|
||||
alt="Українська"
|
||||
/></a>
|
||||
|
||||
<a
|
||||
href="https://github.com/starship/starship/blob/master/docs/vi-VN/guide/README.md"
|
||||
href="https://github.com/starship/starship/blob/main/docs/vi-VN/guide/README.md"
|
||||
><img
|
||||
height="20"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/flag-vn.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/flag-vn.png"
|
||||
alt="Tiếng Việt"
|
||||
/></a>
|
||||
|
||||
<a
|
||||
href="https://github.com/starship/starship/blob/master/docs/zh-CN/guide/README.md"
|
||||
href="https://github.com/starship/starship/blob/main/docs/zh-CN/guide/README.md"
|
||||
><img
|
||||
height="20"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/flag-cn.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/flag-cn.png"
|
||||
alt="简体中文"
|
||||
/></a>
|
||||
|
||||
<a
|
||||
href="https://github.com/starship/starship/blob/master/docs/zh-TW/guide/README.md"
|
||||
href="https://github.com/starship/starship/blob/main/docs/zh-TW/guide/README.md"
|
||||
><img
|
||||
height="20"
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/flag-tw.png"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/flag-tw.png"
|
||||
alt="繁體中文"
|
||||
/></a>
|
||||
</p>
|
||||
|
||||
<h1></h1>
|
||||
|
||||
> [!WARNING]
|
||||
> **The default branch has been renamed from `master` to `main`.**
|
||||
> If you have a local clone, update it by running:
|
||||
>
|
||||
> ```sh
|
||||
> git branch -m master main
|
||||
> git fetch origin
|
||||
> git branch -u origin/main main
|
||||
> git remote set-head origin -a
|
||||
> ```
|
||||
|
||||
<img
|
||||
src="https://raw.githubusercontent.com/starship/starship/master/media/demo.gif"
|
||||
src="https://raw.githubusercontent.com/starship/starship/main/media/demo.gif"
|
||||
alt="Starship with iTerm2 and the Snazzy theme"
|
||||
width="50%"
|
||||
align="right"
|
||||
@@ -414,7 +425,7 @@ We are always looking for contributors of **all skill levels**! If you're lookin
|
||||
|
||||
If you are fluent in a non-English language, we greatly appreciate any help keeping our docs translated and up-to-date in other languages. If you would like to help, translations can be contributed on the [Starship Crowdin](https://translate.starship.rs/).
|
||||
|
||||
If you are interested in helping contribute to starship, please take a look at our [Contributing Guide](https://github.com/starship/starship/blob/master/CONTRIBUTING.md). Also, feel free to drop into our [Discord server](https://discord.gg/8Jzqu3T) and say hi. 👋
|
||||
If you are interested in helping contribute to starship, please take a look at our [Contributing Guide](https://github.com/starship/starship/blob/main/CONTRIBUTING.md). Also, feel free to drop into our [Discord server](https://discord.gg/8Jzqu3T) and say hi. 👋
|
||||
|
||||
## 💭 Inspired By
|
||||
|
||||
@@ -443,13 +454,13 @@ This program will not transfer any information to other networked systems unless
|
||||
|
||||
<p align="center">
|
||||
<br>
|
||||
<img width="100" src="https://raw.githubusercontent.com/starship/starship/master/media/icon.png" alt="Starship rocket icon">
|
||||
<img width="100" src="https://raw.githubusercontent.com/starship/starship/main/media/icon.png" alt="Starship rocket icon">
|
||||
</p>
|
||||
|
||||
## 📝 License
|
||||
|
||||
Copyright © 2019-present, [Starship Contributors](https://github.com/starship/starship/graphs/contributors).<br>
|
||||
This project is [ISC](https://github.com/starship/starship/blob/master/LICENSE) licensed.
|
||||
This project is [ISC](https://github.com/starship/starship/blob/main/LICENSE) licensed.
|
||||
|
||||
[alpine linux packages]: https://pkgs.alpinelinux.org/packages?name=starship
|
||||
[arch linux extra]: https://archlinux.org/packages/extra/x86_64/starship
|
||||
|
||||
@@ -37,7 +37,7 @@ fn gen_presets_hook(mut file: &File) -> SdResult<()> {
|
||||
.and_then(|v| v.strip_suffix(".toml"))
|
||||
.expect("Failed to process filename");
|
||||
presets.push_str(format!("print::Preset(\"{name}\"),\n").as_str());
|
||||
match_arms.push_str(format!(r#""{name}" => include_bytes!(r"{full_path}"),"#).as_str());
|
||||
match_arms.push_str(format!(r#""{name}" => include_str!(r"{full_path}"),"#).as_str());
|
||||
}
|
||||
|
||||
writeln!(
|
||||
@@ -51,7 +51,7 @@ pub fn get_preset_list<'a>() -> &'a [print::Preset] {{
|
||||
]
|
||||
}}
|
||||
|
||||
pub fn get_preset_content(name: &str) -> &[u8] {{
|
||||
pub fn get_preset_content(name: &str) -> &str {{
|
||||
match name {{
|
||||
{match_arms}
|
||||
_ => unreachable!(),
|
||||
|
||||
@@ -315,6 +315,12 @@ Produces a prompt like the following:
|
||||
▶ starship on rprompt [!] is 📦 v0.57.0 via 🦀 v1.54.0 took 17s
|
||||
```
|
||||
|
||||
When using `zsh` (v5.0.5+), the shell adds a default trailing space to the right prompt. This can cause alignment issues specifically when using the Starship `$fill` module. To remove this gap, add the following to your `.zshrc`:
|
||||
|
||||
```zsh
|
||||
ZLE_RPROMPT_INDENT=0
|
||||
```
|
||||
|
||||
## Continuation Prompt
|
||||
|
||||
Some shells support a continuation prompt along with the normal prompt. This prompt is rendered instead of the normal prompt when the user has entered an incomplete statement (such as a single left parenthesis or quote).
|
||||
|
||||
+41
-24
@@ -1824,6 +1824,7 @@ format = '[+$added]($added_style)/[-$deleted]($deleted_style) '
|
||||
|
||||
The `gcloud` module shows the current configuration for [`gcloud`](https://cloud.google.com/sdk/gcloud) CLI.
|
||||
This is based on the `~/.config/gcloud/active_config` file and the `~/.config/gcloud/configurations/config_{CONFIG NAME}` file and the `CLOUDSDK_CONFIG` env var.
|
||||
The `CLOUDSDK_CORE_PROJECT` and `CLOUDSDK_COMPUTE_REGION` environment variables, when set, override the `project` and `region` values from the active configuration, mirroring the behavior of `gcloud` itself.
|
||||
|
||||
When the module is enabled it will always be active, unless `detect_env_vars` has
|
||||
been set in which case the module will only be active when one of the
|
||||
@@ -3343,12 +3344,13 @@ The module will be shown when inside a nix-shell environment.
|
||||
|
||||
### Variables
|
||||
|
||||
| Variable | Example | Description |
|
||||
| -------- | ------- | ------------------------------------ |
|
||||
| state | `pure` | The state of the nix-shell |
|
||||
| name | `lorri` | The name of the nix-shell |
|
||||
| symbol | | Mirrors the value of option `symbol` |
|
||||
| style\* | | Mirrors the value of option `style` |
|
||||
| Variable | Example | Description |
|
||||
| -------- | ------- | ----------------------------------------------------------------------------- |
|
||||
| state | `pure` | The state of the nix-shell |
|
||||
| name | `lorri` | The name of the nix-shell |
|
||||
| level | `1` | The depth level of the nix-shell (Only when using [Lix](https://lix.systems)) |
|
||||
| symbol | | Mirrors the value of option `symbol` |
|
||||
| style\* | | Mirrors the value of option `style` |
|
||||
|
||||
*: This variable can only be used as a part of a style string
|
||||
|
||||
@@ -3377,7 +3379,7 @@ By default the module will be shown if any of the following conditions are met:
|
||||
- The current directory contains a file with the `.js`, `.mjs` or `.cjs` extension
|
||||
- The current directory contains a file with the `.ts`, `.mts` or `.cts` extension
|
||||
|
||||
Additionally, the module will be hidden by default if the directory contains a `bunfig.toml`, `bun.lock`, or `bun.lockb` file, overriding the above conditions.
|
||||
Additionally, the module will be hidden by default if the directory contains a `deno.json`, `deno.jsonc`, `deno.lock`, `bunfig.toml`, `bun.lock`, or `bun.lockb` file, overriding the above conditions.
|
||||
|
||||
### Options
|
||||
|
||||
@@ -3856,7 +3858,8 @@ The `pijul_channel` module shows the active channel of the repo in your current
|
||||
|
||||
## Pixi
|
||||
|
||||
The `pixi` module shows the installed [pixi](https://pixi.sh) version as well as the activated environment, if `$PIXI_ENVIRONMENT_NAME` is set.
|
||||
The `pixi` module shows the installed [pixi](https://pixi.sh) version as well as the activated
|
||||
environment and project name, if `$PIXI_ENVIRONMENT_NAME` is set.
|
||||
|
||||
> [!TIP]
|
||||
> This does not suppress pixi's own prompt modifier, you may want to run `pixi config set shell.change-ps1 false`.
|
||||
@@ -3878,12 +3881,13 @@ The `pixi` module shows the installed [pixi](https://pixi.sh) version as well as
|
||||
|
||||
### Variables
|
||||
|
||||
| Variable | Example | Description |
|
||||
| ----------- | --------- | ------------------------------------ |
|
||||
| version | `v0.33.0` | The version of `pixi` |
|
||||
| environment | `py311` | The current pixi environment |
|
||||
| symbol | | Mirrors the value of option `symbol` |
|
||||
| style | | Mirrors the value of option `style` |
|
||||
| Variable | Example | Description |
|
||||
| ------------ | ------------ | ------------------------------------ |
|
||||
| version | `v0.33.0` | The version of `pixi` |
|
||||
| environment | `py311` | The current pixi environment |
|
||||
| project_name | `my-project` | The current pixi project name |
|
||||
| symbol | | Mirrors the value of option `symbol` |
|
||||
| style | | Mirrors the value of option `style` |
|
||||
|
||||
### Example
|
||||
|
||||
@@ -4810,7 +4814,7 @@ format = 'via [$symbol$workspace]($style) '
|
||||
## Time
|
||||
|
||||
The `time` module shows the current **local** time.
|
||||
The `format` configuration value is used by the [`chrono`](https://crates.io/crates/chrono) crate to control how the time is displayed. Take a look [at the chrono strftime docs](https://docs.rs/chrono/0.4.7/chrono/format/strftime/index.html) to see what options are available.
|
||||
The `format` configuration value is used by the [`jiff`](https://crates.io/crates/jiff) crate to control how the time is displayed. Take a look [at the jiff strftime docs](https://docs.rs/jiff/latest/jiff/fmt/strtime/index.html) to see what options are available.
|
||||
|
||||
> [!TIP]
|
||||
> This module is disabled by default.
|
||||
@@ -4818,15 +4822,15 @@ The `format` configuration value is used by the [`chrono`](https://crates.io/cra
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Default | Description |
|
||||
| ----------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| `format` | `'at [$time]($style) '` | The format string for the module. |
|
||||
| `use_12hr` | `false` | Enables 12 hour formatting |
|
||||
| `time_format` | see below | The [chrono format string](https://docs.rs/chrono/0.4.7/chrono/format/strftime/index.html) used to format the time. |
|
||||
| `style` | `'bold yellow'` | The style for the module time |
|
||||
| `utc_time_offset` | `'local'` | Sets the UTC offset to use. Range from -24 < x < 24. Allows floats to accommodate 30/45 minute timezone offsets. |
|
||||
| `disabled` | `true` | Disables the `time` module. |
|
||||
| `time_range` | `'-'` | Sets the time range during which the module will be shown. Times must be specified in 24-hours format |
|
||||
| Option | Default | Description |
|
||||
| ----------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `format` | `'at [$time]($style) '` | The format string for the module. |
|
||||
| `use_12hr` | `false` | Enables 12 hour formatting |
|
||||
| `time_format` | see below | The [jiff format string](https://docs.rs/jiff/latest/jiff/fmt/strtime/index.html) used to format the time. |
|
||||
| `style` | `'bold yellow'` | The style for the module time |
|
||||
| `utc_time_offset` | `'local'` | Sets the UTC offset to use. Either an IANA time zone name or a range from -24 < x < 24. Allows floats to accommodate 30/45 minute timezone offsets. |
|
||||
| `disabled` | `true` | Disables the `time` module. |
|
||||
| `time_range` | `'-'` | Sets the time range during which the module will be shown. Times must be specified in 24-hours format |
|
||||
|
||||
If `use_12hr` is `true`, then `time_format` defaults to `'%r'`. Otherwise, it defaults to `'%T'`.
|
||||
Manually setting `time_format` will override the `use_12hr` setting.
|
||||
@@ -4842,6 +4846,8 @@ Manually setting `time_format` will override the `use_12hr` setting.
|
||||
|
||||
### Example
|
||||
|
||||
#### With UTC offset
|
||||
|
||||
```toml
|
||||
# ~/.config/starship.toml
|
||||
|
||||
@@ -4853,6 +4859,17 @@ utc_time_offset = '-5'
|
||||
time_range = '10:00:00-14:00:00'
|
||||
```
|
||||
|
||||
#### With Timezone name
|
||||
|
||||
```toml
|
||||
# ~/.config/starship.toml
|
||||
|
||||
[time]
|
||||
disabled = false
|
||||
time_format = '%T'
|
||||
utc_time_offset = 'Europe/Berlin'
|
||||
```
|
||||
|
||||
## Typst
|
||||
|
||||
The `typst` module shows the current installed version of Typst used in a project.
|
||||
|
||||
Generated
+300
-300
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -5,6 +5,6 @@
|
||||
"preview": "vitepress preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitepress": "^1.6.4"
|
||||
"vitepress": "1.6.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ impl Default for MavenConfig<'_> {
|
||||
recursive: false,
|
||||
detect_extensions: vec![],
|
||||
detect_files: vec!["pom.xml"],
|
||||
detect_folders: vec![".mvn"],
|
||||
detect_folders: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ impl Default for NodejsConfig<'_> {
|
||||
"!bunfig.toml",
|
||||
"!bun.lock",
|
||||
"!bun.lockb",
|
||||
"!deno.json",
|
||||
"!deno.jsonc",
|
||||
"!deno.lock",
|
||||
],
|
||||
detect_folders: vec!["node_modules"],
|
||||
}
|
||||
|
||||
+11
-2
@@ -1,5 +1,13 @@
|
||||
use crate::config::Either;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Wrapper struct to enable serde serialization/deserialization for jiff::tz::TimeZone
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct TimezoneWrapper(
|
||||
#[serde(with = "jiff::fmt::serde::tz::required")] pub jiff::tz::TimeZone,
|
||||
);
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[cfg_attr(
|
||||
feature = "config-schema",
|
||||
@@ -14,7 +22,8 @@ pub struct TimeConfig<'a> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub time_format: Option<&'a str>,
|
||||
pub disabled: bool,
|
||||
pub utc_time_offset: &'a str,
|
||||
#[cfg_attr(feature = "config-schema", schemars(with = "String"))]
|
||||
pub utc_time_offset: Either<TimezoneWrapper, &'a str>,
|
||||
pub time_range: &'a str,
|
||||
}
|
||||
|
||||
@@ -26,7 +35,7 @@ impl Default for TimeConfig<'_> {
|
||||
use_12hr: false,
|
||||
time_format: None,
|
||||
disabled: true,
|
||||
utc_time_offset: "local",
|
||||
utc_time_offset: Either::Second("local"),
|
||||
time_range: "-",
|
||||
}
|
||||
}
|
||||
|
||||
+13
-9
@@ -8,8 +8,7 @@ use crate::config::StarshipConfig;
|
||||
use crate::configs::PROMPT_ORDER;
|
||||
use crate::context::Context;
|
||||
use crate::utils;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use toml_edit::DocumentMut;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
@@ -240,16 +239,17 @@ pub fn get_configuration_edit(context: &Context) -> DocumentMut {
|
||||
}
|
||||
|
||||
pub fn write_configuration(context: &Context, doc: &DocumentMut) {
|
||||
let config_path = context.get_config_path_os().unwrap_or_else(|| {
|
||||
let Some(config_path) = context.get_config_path_os() else {
|
||||
eprintln!("config path required to write configuration");
|
||||
process::exit(1);
|
||||
});
|
||||
};
|
||||
|
||||
let config_str = doc.to_string();
|
||||
let config_path = PathBuf::from(config_path);
|
||||
|
||||
File::create(config_path)
|
||||
.and_then(|mut file| file.write_all(config_str.as_ref()))
|
||||
.expect("Error writing starship config");
|
||||
if let Err(e) = crate::utils::write_file_atomic(config_path, doc.to_string(), true) {
|
||||
eprintln!("Unable to write configuration: {e}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn edit_configuration(
|
||||
@@ -313,7 +313,11 @@ fn get_editor_internal(visual: Option<String>, editor: Option<String>) -> String
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fs::create_dir, io, path::PathBuf};
|
||||
use std::{
|
||||
fs::{File, create_dir},
|
||||
io::{self, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use tempfile::TempDir;
|
||||
use toml_edit::Item;
|
||||
|
||||
+9
-1
@@ -107,6 +107,9 @@ enum Commands {
|
||||
/// Output the preset to a file instead of stdout
|
||||
#[clap(short, long, conflicts_with = "list")]
|
||||
output: Option<PathBuf>,
|
||||
/// Forcibly overwrite the output file if it already exists
|
||||
#[clap(short, long, requires = "output")]
|
||||
force: bool,
|
||||
/// List out all preset names
|
||||
#[clap(short, long)]
|
||||
list: bool,
|
||||
@@ -251,7 +254,12 @@ fn main() {
|
||||
print::module(&module_name, properties);
|
||||
}
|
||||
}
|
||||
Commands::Preset { name, list, output } => print::preset_command(name, output, list),
|
||||
Commands::Preset {
|
||||
name,
|
||||
list,
|
||||
output,
|
||||
force,
|
||||
} => print::preset_command(name, output, force, list),
|
||||
Commands::Config { name, value } => {
|
||||
let context = Context::default();
|
||||
if let Some(name) = name {
|
||||
|
||||
+20
-48
@@ -3,8 +3,8 @@ use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::DateTime;
|
||||
use ini::Ini;
|
||||
use jiff::{Timestamp, Zoned};
|
||||
use serde_json as json;
|
||||
use sha1::{Digest, Sha1};
|
||||
|
||||
@@ -149,7 +149,7 @@ fn get_credentials_duration(
|
||||
.find_map(|env_var| context.get_env(env_var))
|
||||
{
|
||||
// get expiration from environment variables
|
||||
chrono::DateTime::parse_from_rfc3339(&expiration_date).ok()
|
||||
expiration_date.parse::<Timestamp>().ok()
|
||||
} else if let Some(section) =
|
||||
get_creds(context, aws_creds).and_then(|creds| get_profile_creds(creds, aws_profile))
|
||||
{
|
||||
@@ -158,7 +158,7 @@ fn get_credentials_duration(
|
||||
expiration_keys
|
||||
.iter()
|
||||
.find_map(|expiration_key| section.get(expiration_key))
|
||||
.and_then(|expiration| DateTime::parse_from_rfc3339(expiration).ok())
|
||||
.and_then(|expiration| expiration.parse::<Timestamp>().ok())
|
||||
} else {
|
||||
// get expiration from cached SSO credentials
|
||||
let config = get_config(context, aws_config)?;
|
||||
@@ -172,10 +172,10 @@ fn get_credentials_duration(
|
||||
let sso_cred_json: json::Value =
|
||||
json::from_str(&crate::utils::read_file(&sso_cred_path).ok()?).ok()?;
|
||||
let expires_at = sso_cred_json.get("expiresAt")?.as_str();
|
||||
DateTime::parse_from_rfc3339(expires_at?).ok()
|
||||
expires_at?.parse::<Timestamp>().ok()
|
||||
}?;
|
||||
|
||||
Some(expiration_date.timestamp() - chrono::Local::now().timestamp())
|
||||
Some(expiration_date.as_second() - Zoned::now().timestamp().as_second())
|
||||
}
|
||||
|
||||
fn alias_name(name: Option<String>, aliases: &HashMap<String, &str>) -> Option<String> {
|
||||
@@ -332,6 +332,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test::ModuleRenderer;
|
||||
use nu_ansi_term::Color;
|
||||
use std::fs::{File, create_dir};
|
||||
@@ -736,21 +737,16 @@ credential_process = /opt/bin/awscreds-retriever
|
||||
|
||||
#[test]
|
||||
fn expiration_date_set() {
|
||||
use chrono::{DateTime, SecondsFormat, Utc};
|
||||
|
||||
let expiration_env_vars = ["AWS_SESSION_EXPIRATION", "AWS_CREDENTIAL_EXPIRATION"];
|
||||
for env_var in expiration_env_vars {
|
||||
let now_plus_half_hour: DateTime<Utc> =
|
||||
DateTime::from_timestamp(chrono::Local::now().timestamp() + 1800, 0).unwrap();
|
||||
let now_plus_half_hour =
|
||||
Timestamp::from_second(Zoned::now().timestamp().as_second() + 1800).unwrap();
|
||||
|
||||
let actual = ModuleRenderer::new("aws")
|
||||
.env("AWS_PROFILE", "astronauts")
|
||||
.env("AWS_REGION", "ap-northeast-2")
|
||||
.env("AWS_ACCESS_KEY_ID", "dummy")
|
||||
.env(
|
||||
env_var,
|
||||
now_plus_half_hour.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
)
|
||||
.env(env_var, now_plus_half_hour.to_string())
|
||||
.collect();
|
||||
|
||||
let possible_values = [
|
||||
@@ -772,16 +768,14 @@ credential_process = /opt/bin/awscreds-retriever
|
||||
|
||||
#[test]
|
||||
fn expiration_date_set_from_file() -> io::Result<()> {
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
let dir = tempfile::tempdir()?;
|
||||
let credentials_path = dir.path().join("credentials");
|
||||
let mut file = File::create(&credentials_path)?;
|
||||
|
||||
let now_plus_half_hour: DateTime<Utc> =
|
||||
DateTime::from_timestamp(chrono::Local::now().timestamp() + 1800, 0).unwrap();
|
||||
let now_plus_half_hour =
|
||||
Timestamp::from_second(Zoned::now().timestamp().as_second() + 1800).unwrap();
|
||||
|
||||
let expiration_date = now_plus_half_hour.to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
|
||||
let expiration_date = now_plus_half_hour.to_string();
|
||||
|
||||
let expiration_keys = ["expiration", "x_security_token_expires"];
|
||||
for key in expiration_keys {
|
||||
@@ -847,10 +841,7 @@ aws_secret_access_key=dummy
|
||||
|
||||
#[test]
|
||||
fn expiration_date_set_expired() {
|
||||
use chrono::{DateTime, SecondsFormat, Utc};
|
||||
|
||||
let now: DateTime<Utc> =
|
||||
DateTime::from_timestamp(chrono::Local::now().timestamp() - 1800, 0).unwrap();
|
||||
let now = Timestamp::from_second(Zoned::now().timestamp().as_second() - 1800).unwrap();
|
||||
|
||||
let symbol = "!!!";
|
||||
|
||||
@@ -862,10 +853,7 @@ aws_secret_access_key=dummy
|
||||
.env("AWS_PROFILE", "astronauts")
|
||||
.env("AWS_REGION", "ap-northeast-2")
|
||||
.env("AWS_ACCESS_KEY_ID", "dummy")
|
||||
.env(
|
||||
"AWS_SESSION_EXPIRATION",
|
||||
now.to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
)
|
||||
.env("AWS_SESSION_EXPIRATION", now.to_string())
|
||||
.collect();
|
||||
let expected = Some(format!(
|
||||
"on {}",
|
||||
@@ -1056,8 +1044,6 @@ credential_process = /opt/bin/awscreds-for-tests
|
||||
|
||||
#[test]
|
||||
fn sso_legacy_set() -> io::Result<()> {
|
||||
use chrono::{DateTime, SecondsFormat, Utc};
|
||||
|
||||
let (module_renderer, dir) = ModuleRenderer::new_with_home("aws")?;
|
||||
std::fs::create_dir_all(dir.path().join(".aws/sso/cache"))?;
|
||||
|
||||
@@ -1080,16 +1066,10 @@ sso_role_name = <AWS-ROLE-NAME>
|
||||
.join(".aws/sso/cache/a47a4e57aecc96b31b4f083543924bd6f828e65a.json"),
|
||||
)?;
|
||||
|
||||
let one_second_ago: DateTime<Utc> =
|
||||
DateTime::from_timestamp(chrono::Local::now().timestamp() - 1, 0).unwrap();
|
||||
let one_second_ago =
|
||||
Timestamp::from_second(Zoned::now().timestamp().as_second() - 1).unwrap();
|
||||
|
||||
file.write_all(
|
||||
format!(
|
||||
r#"{{"expiresAt": "{}"}}"#,
|
||||
one_second_ago.to_rfc3339_opts(SecondsFormat::Secs, true)
|
||||
)
|
||||
.as_bytes(),
|
||||
)?;
|
||||
file.write_all(format!(r#"{{"expiresAt": "{one_second_ago}"}}"#).as_bytes())?;
|
||||
file.sync_all()?;
|
||||
|
||||
let actual = module_renderer.collect();
|
||||
@@ -1104,8 +1084,6 @@ sso_role_name = <AWS-ROLE-NAME>
|
||||
|
||||
#[test]
|
||||
fn sso_set() -> io::Result<()> {
|
||||
use chrono::{DateTime, SecondsFormat, Utc};
|
||||
|
||||
let (module_renderer, dir) = ModuleRenderer::new_with_home("aws")?;
|
||||
std::fs::create_dir_all(dir.path().join(".aws/sso/cache"))?;
|
||||
|
||||
@@ -1134,16 +1112,10 @@ sso_registration_scopes = sso:account:access
|
||||
.join(".aws/sso/cache/7505d64a54e061b7acd54ccd58b49dc43500b635.json"),
|
||||
)?;
|
||||
|
||||
let one_second_ago: DateTime<Utc> =
|
||||
DateTime::from_timestamp(chrono::Local::now().timestamp() - 1, 0).unwrap();
|
||||
let one_second_ago =
|
||||
Timestamp::from_second(Zoned::now().timestamp().as_second() - 1).unwrap();
|
||||
|
||||
cache_file.write_all(
|
||||
format!(
|
||||
r#"{{"expiresAt": "{}"}}"#,
|
||||
one_second_ago.to_rfc3339_opts(SecondsFormat::Secs, true)
|
||||
)
|
||||
.as_bytes(),
|
||||
)?;
|
||||
cache_file.write_all(format!(r#"{{"expiresAt": "{one_second_ago}"}}"#).as_bytes())?;
|
||||
cache_file.sync_all()?;
|
||||
|
||||
let actual = module_renderer
|
||||
|
||||
+68
-4
@@ -116,10 +116,17 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
.and_then(|(_, domain)| domain)
|
||||
.map(Cow::Borrowed)
|
||||
.map(Ok),
|
||||
"region" => gcloud_context
|
||||
.get_region()
|
||||
.map(|region| config.region_aliases.get(region).copied().unwrap_or(region))
|
||||
.map(Cow::Borrowed)
|
||||
"region" => context
|
||||
.get_env("CLOUDSDK_COMPUTE_REGION")
|
||||
.map(Cow::Owned)
|
||||
.or_else(|| gcloud_context.get_region().map(Cow::Borrowed))
|
||||
.map(|region| {
|
||||
config
|
||||
.region_aliases
|
||||
.get(region.as_ref())
|
||||
.copied()
|
||||
.map_or(region, Cow::Borrowed)
|
||||
})
|
||||
.map(Ok),
|
||||
"project" => context
|
||||
.get_env("CLOUDSDK_CORE_PROJECT")
|
||||
@@ -451,6 +458,63 @@ project = very-long-project-name
|
||||
dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn region_set_in_env() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let active_config_path = dir.path().join("active_config");
|
||||
let mut active_config_file = File::create(active_config_path)?;
|
||||
active_config_file.write_all(b"default")?;
|
||||
|
||||
create_dir(dir.path().join("configurations"))?;
|
||||
let config_default_path = dir.path().join("configurations").join("config_default");
|
||||
let mut config_default_file = File::create(config_default_path)?;
|
||||
config_default_file.write_all(
|
||||
b"\
|
||||
[compute]
|
||||
region = us-central1
|
||||
",
|
||||
)?;
|
||||
|
||||
let actual = ModuleRenderer::new("gcloud")
|
||||
.env("CLOUDSDK_COMPUTE_REGION", "europe-west2")
|
||||
.env("CLOUDSDK_CONFIG", dir.path().to_string_lossy())
|
||||
.config(toml::toml! {
|
||||
[gcloud]
|
||||
format = "on [$symbol$region]($style) "
|
||||
})
|
||||
.collect();
|
||||
let expected = Some(format!(
|
||||
"on {} ",
|
||||
Color::Blue.bold().paint("☁️ europe-west2")
|
||||
));
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn region_set_in_env_with_alias() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let active_config_path = dir.path().join("active_config");
|
||||
let mut active_config_file = File::create(active_config_path)?;
|
||||
active_config_file.write_all(b"default")?;
|
||||
|
||||
let actual = ModuleRenderer::new("gcloud")
|
||||
.env("CLOUDSDK_COMPUTE_REGION", "europe-west2")
|
||||
.env("CLOUDSDK_CONFIG", dir.path().to_string_lossy())
|
||||
.config(toml::toml! {
|
||||
[gcloud]
|
||||
format = "on [$symbol$region]($style) "
|
||||
[gcloud.region_aliases]
|
||||
europe-west2 = "ew2"
|
||||
})
|
||||
.collect();
|
||||
let expected = Some(format!("on {} ", Color::Blue.bold().paint("☁️ ew2")));
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn region_not_set_with_display_region() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
|
||||
+50
-13
@@ -75,24 +75,16 @@ fn get_state_description<'a>(
|
||||
current: None,
|
||||
total: None,
|
||||
}),
|
||||
InProgress::ApplyMailbox => Some(StateDescription {
|
||||
label: config.am,
|
||||
current: None,
|
||||
total: None,
|
||||
}),
|
||||
InProgress::ApplyMailboxRebase => Some(StateDescription {
|
||||
label: config.am_or_rebase,
|
||||
current: None,
|
||||
total: None,
|
||||
}),
|
||||
InProgress::ApplyMailbox => Some(describe_rebase_apply(repo, config.am)),
|
||||
InProgress::ApplyMailboxRebase => Some(describe_rebase_apply(repo, config.am_or_rebase)),
|
||||
InProgress::Rebase | InProgress::RebaseInteractive => {
|
||||
Some(describe_rebase(repo, config.rebase))
|
||||
Some(describe_rebase_apply(repo, config.rebase))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use future gitoxide API to get the state of the rebase
|
||||
fn describe_rebase<'a>(repo: &'a Repo, rebase_config: &'a str) -> StateDescription<'a> {
|
||||
fn describe_rebase_apply<'a>(repo: &'a Repo, state_label: &'a str) -> StateDescription<'a> {
|
||||
/*
|
||||
* Sadly, libgit2 seems to have some issues with reading the state of
|
||||
* interactive rebases. So, instead, we'll poke a few of the .git files
|
||||
@@ -134,7 +126,7 @@ fn describe_rebase<'a>(repo: &'a Repo, rebase_config: &'a str) -> StateDescripti
|
||||
};
|
||||
|
||||
StateDescription {
|
||||
label: rebase_config,
|
||||
label: state_label,
|
||||
current,
|
||||
total,
|
||||
}
|
||||
@@ -186,6 +178,33 @@ mod tests {
|
||||
repo_dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shows_applying_mailbox_progress() -> io::Result<()> {
|
||||
let repo_dir = create_repo_with_conflict()?;
|
||||
let path = repo_dir.path();
|
||||
let patch_path = path.join("other-branch.patch");
|
||||
let patch =
|
||||
run_git_cmd_with_output(["format-patch", "-1", "other-branch", "--stdout"], path)?;
|
||||
write_file(
|
||||
&patch_path,
|
||||
String::from_utf8(patch.stdout)
|
||||
.map_err(|error| Error::new(ErrorKind::InvalidData, error))?,
|
||||
)?;
|
||||
|
||||
run_git_cmd(
|
||||
["am", patch_path.to_str().expect("Path was not UTF-8")],
|
||||
Some(path),
|
||||
false,
|
||||
)?;
|
||||
|
||||
let actual = ModuleRenderer::new("git_state").path(path).collect();
|
||||
|
||||
let expected = Some(format!("({}) ", Color::Yellow.bold().paint("AM 1/1")));
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
repo_dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shows_merging() -> io::Result<()> {
|
||||
let repo_dir = create_repo_with_conflict()?;
|
||||
@@ -274,6 +293,24 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn run_git_cmd_with_output<A, S>(args: A, dir: &Path) -> io::Result<std::process::Output>
|
||||
where
|
||||
A: IntoIterator<Item = S>,
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
let output = create_command("git")?
|
||||
.args(args)
|
||||
.current_dir(dir)
|
||||
.stderr(Stdio::null())
|
||||
.output()?;
|
||||
|
||||
if output.status.success() {
|
||||
Ok(output)
|
||||
} else {
|
||||
Err(Error::from(ErrorKind::Other))
|
||||
}
|
||||
}
|
||||
|
||||
fn create_repo_with_conflict() -> io::Result<tempfile::TempDir> {
|
||||
let repo_dir = tempfile::tempdir()?;
|
||||
let path = repo_dir.path();
|
||||
|
||||
+22
-8
@@ -12,18 +12,19 @@ use crate::{
|
||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
let mut module = context.new_module("maven");
|
||||
let config = MavenConfig::try_load(module.config);
|
||||
let is_maven_project = context
|
||||
.try_begin_scan()?
|
||||
.set_files(&config.detect_files)
|
||||
.set_extensions(&config.detect_extensions)
|
||||
.set_folders(&config.detect_folders)
|
||||
.is_match();
|
||||
let wrapper_properties = get_wrapper_properties_file(context, config.recursive);
|
||||
let is_maven_project = wrapper_properties.is_some()
|
||||
|| context
|
||||
.try_begin_scan()?
|
||||
.set_files(&config.detect_files)
|
||||
.set_extensions(&config.detect_extensions)
|
||||
.set_folders(&config.detect_folders)
|
||||
.is_match();
|
||||
|
||||
if !is_maven_project {
|
||||
return None;
|
||||
}
|
||||
|
||||
let wrapper_properties = get_wrapper_properties_file(context, config.recursive);
|
||||
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
|
||||
formatter
|
||||
.map_meta(|var, _| match var {
|
||||
@@ -123,6 +124,20 @@ mod tests {
|
||||
dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_with_maven_config_does_not_trigger_module() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let maven_config = dir.path().join(".mvn").join("maven.config");
|
||||
fs::create_dir_all(maven_config.parent().unwrap())?;
|
||||
File::create(maven_config)?.sync_all()?;
|
||||
|
||||
let actual = ModuleRenderer::new("maven").path(dir.path()).collect();
|
||||
|
||||
let expected = None;
|
||||
assert_eq!(expected, actual);
|
||||
dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_with_maven_wrapper_properties() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
@@ -132,7 +147,6 @@ mod tests {
|
||||
.join("wrapper")
|
||||
.join("maven-wrapper.properties");
|
||||
fs::create_dir_all(properties.parent().unwrap())?;
|
||||
File::create(dir.path().join("pom.xml"))?.sync_all()?;
|
||||
let mut file = File::create(properties)?;
|
||||
file.write_all(
|
||||
b"\
|
||||
|
||||
@@ -45,6 +45,7 @@ impl NixShellType {
|
||||
/// The module will use the `$IN_NIX_SHELL` and `$name` environment variable to
|
||||
/// determine if it's inside a nix-shell and the name of it.
|
||||
///
|
||||
///
|
||||
/// The following options are available:
|
||||
/// - `impure_msg` (string) // change the impure msg
|
||||
/// - `pure_msg` (string) // change the pure msg
|
||||
@@ -57,11 +58,12 @@ impl NixShellType {
|
||||
/// - impure // $name == "" in an impure nix-shell
|
||||
/// - unknown (name) // $name == "name" in an unknown nix-shell
|
||||
/// - unknown // $name == "" in an unknown nix-shell
|
||||
///
|
||||
/// When using Lix 2.95+, will also have $level from `$NIX_SHELL_LEVEL`
|
||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
let mut module = context.new_module("nix_shell");
|
||||
let config: NixShellConfig = NixShellConfig::try_load(module.config);
|
||||
|
||||
let shell_name = context.get_env("name");
|
||||
let shell_type = NixShellType::detect_shell_type(config.heuristic, context)?;
|
||||
let shell_type_format = match shell_type {
|
||||
NixShellType::Pure => config.pure_msg,
|
||||
@@ -81,7 +83,8 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
_ => None,
|
||||
})
|
||||
.map(|variable| match variable {
|
||||
"name" => shell_name.as_ref().map(Ok),
|
||||
"name" => context.get_env("name").map(Ok),
|
||||
"level" => context.get_env("NIX_SHELL_LEVEL").map(Ok),
|
||||
_ => None,
|
||||
})
|
||||
.parse(None, Some(context))
|
||||
@@ -226,4 +229,19 @@ mod tests {
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nix_shell_level() {
|
||||
let actual = ModuleRenderer::new("nix_shell")
|
||||
.env("IN_NIX_SHELL", "impure")
|
||||
.env("NIX_SHELL_LEVEL", "3")
|
||||
.config(toml::toml! {
|
||||
[nix_shell]
|
||||
format = "via [$symbol$state( \\($name\\)) $level]($style) "
|
||||
})
|
||||
.collect();
|
||||
let expected = Some(format!("via {} ", Color::Blue.bold().paint("❄️ impure 3")));
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
})
|
||||
.map(|variable| match variable {
|
||||
"environment" => pixi_environment_name.clone().map(Ok),
|
||||
"project_name" => context.get_env("PIXI_PROJECT_NAME").map(Ok),
|
||||
"version" => {
|
||||
let pixi_version = get_pixi_version(context, &config)?;
|
||||
VersionFormatter::format_module_version(
|
||||
@@ -125,6 +126,45 @@ mod tests {
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_name_set() {
|
||||
let actual = ModuleRenderer::new("pixi")
|
||||
.env("PIXI_ENVIRONMENT_NAME", "py312")
|
||||
.env("PIXI_PROJECT_NAME", "my-project")
|
||||
.config(toml::toml! {
|
||||
[pixi]
|
||||
format = "via [$symbol($version )(\\($environment\\) )(\\[$project_name\\] )]($style)"
|
||||
})
|
||||
.collect();
|
||||
|
||||
let expected = Some(format!(
|
||||
"via {}",
|
||||
Color::Yellow
|
||||
.bold()
|
||||
.paint("🧚 v0.33.0 (py312) [my-project] ")
|
||||
));
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_name_not_set() {
|
||||
let actual = ModuleRenderer::new("pixi")
|
||||
.env("PIXI_ENVIRONMENT_NAME", "py312")
|
||||
.config(toml::toml! {
|
||||
[pixi]
|
||||
format = "via [$symbol($version )(\\($environment\\) )(\\[$project_name\\] )]($style)"
|
||||
})
|
||||
.collect();
|
||||
|
||||
let expected = Some(format!(
|
||||
"via {}",
|
||||
Color::Yellow.bold().paint("🧚 v0.33.0 (py312) ")
|
||||
));
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_with_pixi_toml() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
|
||||
+258
-111
@@ -1,7 +1,12 @@
|
||||
use chrono::{DateTime, FixedOffset, Local, NaiveTime, Utc};
|
||||
use jiff::{
|
||||
Timestamp, Zoned,
|
||||
civil::Time,
|
||||
tz::{Offset, TimeZone},
|
||||
};
|
||||
|
||||
use super::{Context, Module, ModuleConfig};
|
||||
use crate::configs::time::TimeConfig;
|
||||
use crate::config::Either;
|
||||
use crate::configs::time::{TimeConfig, TimezoneWrapper};
|
||||
use crate::formatter::StringFormatter;
|
||||
|
||||
/// Outputs the current time
|
||||
@@ -17,7 +22,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
|
||||
// Hide prompt if current time is not inside time_range
|
||||
let (display_start, display_end) = parse_time_range(config.time_range);
|
||||
let time_now = Local::now().time();
|
||||
let time_now = Zoned::now().time();
|
||||
if !is_inside_time_range(time_now, display_start, display_end) {
|
||||
return None;
|
||||
}
|
||||
@@ -27,17 +32,26 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
|
||||
log::trace!("Timer module is enabled with format string: {time_format}");
|
||||
|
||||
let formatted_time_string = if config.utc_time_offset != "local" {
|
||||
create_offset_time_string(Utc::now(), config.utc_time_offset, time_format).unwrap_or_else(
|
||||
|_| {
|
||||
log::warn!(
|
||||
"Invalid utc_time_offset configuration provided! Falling back to \"local\"."
|
||||
);
|
||||
format_time(time_format, Local::now())
|
||||
},
|
||||
)
|
||||
} else {
|
||||
format_time(time_format, Local::now())
|
||||
let formatted_time_string = match &config.utc_time_offset {
|
||||
Either::First(TimezoneWrapper(tz)) => {
|
||||
// Use IANA timezone name
|
||||
let target_time = Timestamp::now().to_zoned(tz.clone());
|
||||
format_time_fixed_offset(time_format, target_time)
|
||||
}
|
||||
Either::Second("local") => {
|
||||
// Use local timezone
|
||||
format_time(time_format, Zoned::now())
|
||||
}
|
||||
Either::Second(utc_time_offset) => {
|
||||
// Use numeric offset
|
||||
create_offset_time_string(Timestamp::now(), utc_time_offset, time_format)
|
||||
.unwrap_or_else(|_| {
|
||||
log::warn!(
|
||||
"Invalid utc_time_offset configuration provided! Falling back to \"local\"."
|
||||
);
|
||||
format_time(time_format, Zoned::now())
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
|
||||
@@ -65,10 +79,10 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
}
|
||||
|
||||
fn create_offset_time_string(
|
||||
utc_time: DateTime<Utc>,
|
||||
utc_time: Timestamp,
|
||||
utc_time_offset_str: &str,
|
||||
time_format: &str,
|
||||
) -> Result<String, &'static str> {
|
||||
) -> Result<String, String> {
|
||||
// Using floats to allow 30/45 minute offsets: https://www.timeanddate.com/time/time-zones-interesting.html
|
||||
let utc_time_offset_in_hours = utc_time_offset_str.parse::<f32>().unwrap_or(
|
||||
// Passing out of range value to force falling back to "local"
|
||||
@@ -76,38 +90,34 @@ fn create_offset_time_string(
|
||||
);
|
||||
if utc_time_offset_in_hours < 24_f32 && utc_time_offset_in_hours > -24_f32 {
|
||||
let utc_offset_in_seconds: i32 = (utc_time_offset_in_hours * 3600_f32) as i32;
|
||||
let Some(timezone_offset) = FixedOffset::east_opt(utc_offset_in_seconds) else {
|
||||
return Err("Invalid offset");
|
||||
};
|
||||
let timezone_offset = Offset::from_seconds(utc_offset_in_seconds)
|
||||
.map_err(|err| format!("Invalid timezone offset: {err:?}"))?;
|
||||
let tz = TimeZone::fixed(timezone_offset);
|
||||
log::trace!("Target timezone offset is {timezone_offset}");
|
||||
|
||||
let target_time = utc_time.with_timezone(&timezone_offset);
|
||||
let target_time = utc_time.to_zoned(tz);
|
||||
log::trace!("Time in target timezone now is {target_time}");
|
||||
|
||||
Ok(format_time_fixed_offset(time_format, target_time))
|
||||
} else {
|
||||
Err("Invalid timezone offset.")
|
||||
Err("Invalid timezone offset.".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a given time into the given string. This function should be referentially
|
||||
/// transparent, which makes it easy to test (unlike anything involving the actual time)
|
||||
fn format_time(time_format: &str, local_time: DateTime<Local>) -> String {
|
||||
local_time.format(time_format).to_string()
|
||||
fn format_time(time_format: &str, local_time: Zoned) -> String {
|
||||
local_time.strftime(time_format).to_string()
|
||||
}
|
||||
|
||||
fn format_time_fixed_offset(time_format: &str, utc_time: DateTime<FixedOffset>) -> String {
|
||||
utc_time.format(time_format).to_string()
|
||||
fn format_time_fixed_offset(time_format: &str, zoned_time: Zoned) -> String {
|
||||
zoned_time.strftime(time_format).to_string()
|
||||
}
|
||||
|
||||
/// Returns true if `time_now` is between `time_start` and `time_end`.
|
||||
/// If one of these values is not given, then it is ignored.
|
||||
/// It also handles cases where `time_start` and `time_end` have a midnight in between
|
||||
fn is_inside_time_range(
|
||||
time_now: NaiveTime,
|
||||
time_start: Option<NaiveTime>,
|
||||
time_end: Option<NaiveTime>,
|
||||
) -> bool {
|
||||
fn is_inside_time_range(time_now: Time, time_start: Option<Time>, time_end: Option<Time>) -> bool {
|
||||
match (time_start, time_end) {
|
||||
(None, None) => true,
|
||||
(Some(i), None) => time_now > i,
|
||||
@@ -127,7 +137,7 @@ fn is_inside_time_range(
|
||||
///
|
||||
/// If one of the ranges is invalid or not provided, then the corresponding field in the output
|
||||
/// tuple is None
|
||||
fn parse_time_range(time_range: &str) -> (Option<NaiveTime>, Option<NaiveTime>) {
|
||||
fn parse_time_range(time_range: &str) -> (Option<Time>, Option<Time>) {
|
||||
let value = String::from(time_range);
|
||||
|
||||
// Check if there is exactly one hyphen, and fail otherwise
|
||||
@@ -140,8 +150,8 @@ fn parse_time_range(time_range: &str) -> (Option<NaiveTime>, Option<NaiveTime>)
|
||||
let end = &end[1..];
|
||||
|
||||
// Parse the ranges
|
||||
let start_time = NaiveTime::parse_from_str(start, "%H:%M:%S").ok();
|
||||
let end_time = NaiveTime::parse_from_str(end, "%H:%M:%S").ok();
|
||||
let start_time = start.parse::<Time>().ok();
|
||||
let end_time = end.parse::<Time>().ok();
|
||||
|
||||
(start_time, end_time)
|
||||
}
|
||||
@@ -152,140 +162,151 @@ tests become extra important */
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test::ModuleRenderer;
|
||||
use chrono::offset::TimeZone;
|
||||
use jiff::civil::date;
|
||||
|
||||
const FMT_12: &str = "%r";
|
||||
const FMT_24: &str = "%T";
|
||||
|
||||
#[test]
|
||||
fn test_midnight_12hr() {
|
||||
let time = Local.with_ymd_and_hms(2014, 7, 8, 0, 0, 0).unwrap();
|
||||
let time = date(2014, 7, 8)
|
||||
.at(0, 0, 0, 0)
|
||||
.to_zoned(TimeZone::system())
|
||||
.unwrap();
|
||||
let formatted = format_time(FMT_12, time);
|
||||
assert_eq!(formatted, "12:00:00 AM");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_midnight_24hr() {
|
||||
let time = Local.with_ymd_and_hms(2014, 7, 8, 0, 0, 0).unwrap();
|
||||
let time = date(2014, 7, 8)
|
||||
.at(0, 0, 0, 0)
|
||||
.to_zoned(TimeZone::system())
|
||||
.unwrap();
|
||||
let formatted = format_time(FMT_24, time);
|
||||
assert_eq!(formatted, "00:00:00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_noon_12hr() {
|
||||
let time = Local.with_ymd_and_hms(2014, 7, 8, 12, 0, 0).unwrap();
|
||||
let time = date(2014, 7, 8)
|
||||
.at(12, 0, 0, 0)
|
||||
.to_zoned(TimeZone::system())
|
||||
.unwrap();
|
||||
let formatted = format_time(FMT_12, time);
|
||||
assert_eq!(formatted, "12:00:00 PM");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_noon_24hr() {
|
||||
let time = Local.with_ymd_and_hms(2014, 7, 8, 12, 0, 0).unwrap();
|
||||
let time = date(2014, 7, 8)
|
||||
.at(12, 0, 0, 0)
|
||||
.to_zoned(TimeZone::system())
|
||||
.unwrap();
|
||||
let formatted = format_time(FMT_24, time);
|
||||
assert_eq!(formatted, "12:00:00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arbtime_12hr() {
|
||||
let time = Local.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap();
|
||||
let time = date(2014, 7, 8)
|
||||
.at(15, 36, 47, 0)
|
||||
.to_zoned(TimeZone::system())
|
||||
.unwrap();
|
||||
let formatted = format_time(FMT_12, time);
|
||||
assert_eq!(formatted, "03:36:47 PM");
|
||||
assert_eq!(formatted, "3:36:47 PM");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arbtime_24hr() {
|
||||
let time = Local.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap();
|
||||
let time = date(2014, 7, 8)
|
||||
.at(15, 36, 47, 0)
|
||||
.to_zoned(TimeZone::system())
|
||||
.unwrap();
|
||||
let formatted = format_time(FMT_24, time);
|
||||
assert_eq!(formatted, "15:36:47");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_with_paren() {
|
||||
let time = Local.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap();
|
||||
let time = date(2014, 7, 8)
|
||||
.at(15, 36, 47, 0)
|
||||
.to_zoned(TimeZone::system())
|
||||
.unwrap();
|
||||
let formatted = format_time("[%T]", time);
|
||||
assert_eq!(formatted, "[15:36:47]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_midnight_12hr_fixed_offset() {
|
||||
let timezone_offset = FixedOffset::east_opt(0).unwrap();
|
||||
let time = Utc
|
||||
.with_ymd_and_hms(2014, 7, 8, 0, 0, 0)
|
||||
.unwrap()
|
||||
.with_timezone(&timezone_offset);
|
||||
let timezone_offset = Offset::from_seconds(0).unwrap();
|
||||
let tz = TimeZone::fixed(timezone_offset);
|
||||
let time = date(2014, 7, 8).at(0, 0, 0, 0).to_zoned(tz).unwrap();
|
||||
let formatted = format_time_fixed_offset(FMT_12, time);
|
||||
assert_eq!(formatted, "12:00:00 AM");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_midnight_24hr_fixed_offset() {
|
||||
let timezone_offset = FixedOffset::east_opt(0).unwrap();
|
||||
let time = Utc
|
||||
.with_ymd_and_hms(2014, 7, 8, 0, 0, 0)
|
||||
.unwrap()
|
||||
.with_timezone(&timezone_offset);
|
||||
let timezone_offset = Offset::from_seconds(0).unwrap();
|
||||
let tz = TimeZone::fixed(timezone_offset);
|
||||
let time = date(2014, 7, 8).at(0, 0, 0, 0).to_zoned(tz).unwrap();
|
||||
let formatted = format_time_fixed_offset(FMT_24, time);
|
||||
assert_eq!(formatted, "00:00:00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_noon_12hr_fixed_offset() {
|
||||
let timezone_offset = FixedOffset::east_opt(0).unwrap();
|
||||
let time = Utc
|
||||
.with_ymd_and_hms(2014, 7, 8, 12, 0, 0)
|
||||
.unwrap()
|
||||
.with_timezone(&timezone_offset);
|
||||
let timezone_offset = Offset::from_seconds(0).unwrap();
|
||||
let tz = TimeZone::fixed(timezone_offset);
|
||||
let time = date(2014, 7, 8).at(12, 0, 0, 0).to_zoned(tz).unwrap();
|
||||
let formatted = format_time_fixed_offset(FMT_12, time);
|
||||
assert_eq!(formatted, "12:00:00 PM");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_noon_24hr_fixed_offset() {
|
||||
let timezone_offset = FixedOffset::east_opt(0).unwrap();
|
||||
let time = Utc
|
||||
.with_ymd_and_hms(2014, 7, 8, 12, 0, 0)
|
||||
.unwrap()
|
||||
.with_timezone(&timezone_offset);
|
||||
let timezone_offset = Offset::from_seconds(0).unwrap();
|
||||
let tz = TimeZone::fixed(timezone_offset);
|
||||
let time = date(2014, 7, 8).at(12, 0, 0, 0).to_zoned(tz).unwrap();
|
||||
let formatted = format_time_fixed_offset(FMT_24, time);
|
||||
assert_eq!(formatted, "12:00:00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arbtime_12hr_fixed_offset() {
|
||||
let timezone_offset = FixedOffset::east_opt(0).unwrap();
|
||||
let time = Utc
|
||||
.with_ymd_and_hms(2014, 7, 8, 15, 36, 47)
|
||||
.unwrap()
|
||||
.with_timezone(&timezone_offset);
|
||||
let timezone_offset = Offset::from_seconds(0).unwrap();
|
||||
let tz = TimeZone::fixed(timezone_offset);
|
||||
let time = date(2014, 7, 8).at(15, 36, 47, 0).to_zoned(tz).unwrap();
|
||||
let formatted = format_time_fixed_offset(FMT_12, time);
|
||||
assert_eq!(formatted, "03:36:47 PM");
|
||||
assert_eq!(formatted, "3:36:47 PM");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arbtime_24hr_fixed_offset() {
|
||||
let timezone_offset = FixedOffset::east_opt(0).unwrap();
|
||||
let time = Utc
|
||||
.with_ymd_and_hms(2014, 7, 8, 15, 36, 47)
|
||||
.unwrap()
|
||||
.with_timezone(&timezone_offset);
|
||||
let timezone_offset = Offset::from_seconds(0).unwrap();
|
||||
let tz = TimeZone::fixed(timezone_offset);
|
||||
let time = date(2014, 7, 8).at(15, 36, 47, 0).to_zoned(tz).unwrap();
|
||||
let formatted = format_time_fixed_offset(FMT_24, time);
|
||||
assert_eq!(formatted, "15:36:47");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_with_paren_fixed_offset() {
|
||||
let timezone_offset = FixedOffset::east_opt(0).unwrap();
|
||||
let time = Utc
|
||||
.with_ymd_and_hms(2014, 7, 8, 15, 36, 47)
|
||||
.unwrap()
|
||||
.with_timezone(&timezone_offset);
|
||||
let timezone_offset = Offset::from_seconds(0).unwrap();
|
||||
let tz = TimeZone::fixed(timezone_offset);
|
||||
let time = date(2014, 7, 8).at(15, 36, 47, 0).to_zoned(tz).unwrap();
|
||||
let formatted = format_time_fixed_offset("[%T]", time);
|
||||
assert_eq!(formatted, "[15:36:47]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_formatted_time_string_with_minus_3() {
|
||||
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap();
|
||||
let utc_time = date(2014, 7, 8)
|
||||
.at(15, 36, 47, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
let utc_time_offset_str = "-3";
|
||||
|
||||
let actual = create_offset_time_string(utc_time, utc_time_offset_str, FMT_12).unwrap();
|
||||
@@ -294,34 +315,50 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_create_formatted_time_string_with_plus_5() {
|
||||
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap();
|
||||
let utc_time = date(2014, 7, 8)
|
||||
.at(15, 36, 47, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
let utc_time_offset_str = "+5";
|
||||
|
||||
let actual = create_offset_time_string(utc_time, utc_time_offset_str, FMT_12).unwrap();
|
||||
assert_eq!(actual, "08:36:47 PM");
|
||||
assert_eq!(actual, "8:36:47 PM");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_formatted_time_string_with_plus_9_30() {
|
||||
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap();
|
||||
let utc_time = date(2014, 7, 8)
|
||||
.at(15, 36, 47, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
let utc_time_offset_str = "+9.5";
|
||||
|
||||
let actual = create_offset_time_string(utc_time, utc_time_offset_str, FMT_12).unwrap();
|
||||
assert_eq!(actual, "01:06:47 AM");
|
||||
assert_eq!(actual, "1:06:47 AM");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_formatted_time_string_with_plus_5_45() {
|
||||
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap();
|
||||
let utc_time = date(2014, 7, 8)
|
||||
.at(15, 36, 47, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
let utc_time_offset_str = "+5.75";
|
||||
|
||||
let actual = create_offset_time_string(utc_time, utc_time_offset_str, FMT_12).unwrap();
|
||||
assert_eq!(actual, "09:21:47 PM");
|
||||
assert_eq!(actual, "9:21:47 PM");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_formatted_time_string_with_plus_24() {
|
||||
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap();
|
||||
let utc_time = date(2014, 7, 8)
|
||||
.at(15, 36, 47, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
let utc_time_offset_str = "+24";
|
||||
|
||||
create_offset_time_string(utc_time, utc_time_offset_str, FMT_12)
|
||||
@@ -330,7 +367,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_create_formatted_time_string_with_minus_24() {
|
||||
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap();
|
||||
let utc_time = date(2014, 7, 8)
|
||||
.at(15, 36, 47, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
let utc_time_offset_str = "-24";
|
||||
|
||||
create_offset_time_string(utc_time, utc_time_offset_str, FMT_12)
|
||||
@@ -339,7 +380,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_create_formatted_time_string_with_plus_9001() {
|
||||
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap();
|
||||
let utc_time = date(2014, 7, 8)
|
||||
.at(15, 36, 47, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
let utc_time_offset_str = "+9001";
|
||||
|
||||
create_offset_time_string(utc_time, utc_time_offset_str, FMT_12)
|
||||
@@ -348,7 +393,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_create_formatted_time_string_with_minus_4242() {
|
||||
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap();
|
||||
let utc_time = date(2014, 7, 8)
|
||||
.at(15, 36, 47, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
let utc_time_offset_str = "-4242";
|
||||
|
||||
create_offset_time_string(utc_time, utc_time_offset_str, FMT_12)
|
||||
@@ -357,7 +406,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_create_formatted_time_string_with_invalid_string() {
|
||||
let utc_time: DateTime<Utc> = Utc.with_ymd_and_hms(2014, 7, 8, 15, 36, 47).unwrap();
|
||||
let utc_time = date(2014, 7, 8)
|
||||
.at(15, 36, 47, 0)
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
let utc_time_offset_str = "completely wrong config";
|
||||
|
||||
create_offset_time_string(utc_time, utc_time_offset_str, FMT_12)
|
||||
@@ -379,7 +432,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
parse_time_range(time_range),
|
||||
(Some(NaiveTime::from_hms_opt(10, 00, 00).unwrap()), None)
|
||||
(Some(Time::new(10, 0, 0, 0).unwrap()), None)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -389,7 +442,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
parse_time_range(time_range),
|
||||
(None, Some(NaiveTime::from_hms_opt(22, 00, 00).unwrap()))
|
||||
(None, Some(Time::new(22, 0, 0, 0).unwrap()))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -400,8 +453,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
parse_time_range(time_range),
|
||||
(
|
||||
Some(NaiveTime::from_hms_opt(10, 00, 00).unwrap()),
|
||||
Some(NaiveTime::from_hms_opt(16, 00, 00).unwrap())
|
||||
Some(Time::new(10, 0, 0, 0).unwrap()),
|
||||
Some(Time::new(16, 0, 0, 0).unwrap())
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -410,16 +463,16 @@ mod tests {
|
||||
fn test_is_inside_time_range_with_no_range() {
|
||||
let time_start = None;
|
||||
let time_end = None;
|
||||
let time_now = NaiveTime::from_hms_opt(10, 00, 00).unwrap();
|
||||
let time_now = Time::new(10, 0, 0, 0).unwrap();
|
||||
|
||||
assert!(is_inside_time_range(time_now, time_start, time_end));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_inside_time_range_with_start_range() {
|
||||
let time_start = Some(NaiveTime::from_hms_opt(10, 00, 00).unwrap());
|
||||
let time_now = NaiveTime::from_hms_opt(12, 00, 00).unwrap();
|
||||
let time_now2 = NaiveTime::from_hms_opt(8, 00, 00).unwrap();
|
||||
let time_start = Some(Time::new(10, 0, 0, 0).unwrap());
|
||||
let time_now = Time::new(12, 0, 0, 0).unwrap();
|
||||
let time_now2 = Time::new(8, 0, 0, 0).unwrap();
|
||||
|
||||
assert!(is_inside_time_range(time_now, time_start, None));
|
||||
assert!(!is_inside_time_range(time_now2, time_start, None));
|
||||
@@ -427,9 +480,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_is_inside_time_range_with_end_range() {
|
||||
let time_end = Some(NaiveTime::from_hms_opt(16, 00, 00).unwrap());
|
||||
let time_now = NaiveTime::from_hms_opt(15, 00, 00).unwrap();
|
||||
let time_now2 = NaiveTime::from_hms_opt(19, 00, 00).unwrap();
|
||||
let time_end = Some(Time::new(16, 0, 0, 0).unwrap());
|
||||
let time_now = Time::new(15, 0, 0, 0).unwrap();
|
||||
let time_now2 = Time::new(19, 0, 0, 0).unwrap();
|
||||
|
||||
assert!(is_inside_time_range(time_now, None, time_end));
|
||||
assert!(!is_inside_time_range(time_now2, None, time_end));
|
||||
@@ -437,11 +490,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_is_inside_time_range_with_complete_range() {
|
||||
let time_start = Some(NaiveTime::from_hms_opt(9, 00, 00).unwrap());
|
||||
let time_end = Some(NaiveTime::from_hms_opt(18, 00, 00).unwrap());
|
||||
let time_now = NaiveTime::from_hms_opt(3, 00, 00).unwrap();
|
||||
let time_now2 = NaiveTime::from_hms_opt(13, 00, 00).unwrap();
|
||||
let time_now3 = NaiveTime::from_hms_opt(20, 00, 00).unwrap();
|
||||
let time_start = Some(Time::new(9, 0, 0, 0).unwrap());
|
||||
let time_end = Some(Time::new(18, 0, 0, 0).unwrap());
|
||||
let time_now = Time::new(3, 0, 0, 0).unwrap();
|
||||
let time_now2 = Time::new(13, 0, 0, 0).unwrap();
|
||||
let time_now3 = Time::new(20, 0, 0, 0).unwrap();
|
||||
|
||||
assert!(!is_inside_time_range(time_now, time_start, time_end));
|
||||
assert!(is_inside_time_range(time_now2, time_start, time_end));
|
||||
@@ -450,11 +503,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_is_inside_time_range_with_complete_range_passing_midnight() {
|
||||
let time_start = Some(NaiveTime::from_hms_opt(19, 00, 00).unwrap());
|
||||
let time_end = Some(NaiveTime::from_hms_opt(12, 00, 00).unwrap());
|
||||
let time_now = NaiveTime::from_hms_opt(3, 00, 00).unwrap();
|
||||
let time_now2 = NaiveTime::from_hms_opt(13, 00, 00).unwrap();
|
||||
let time_now3 = NaiveTime::from_hms_opt(20, 00, 00).unwrap();
|
||||
let time_start = Some(Time::new(19, 0, 0, 0).unwrap());
|
||||
let time_end = Some(Time::new(12, 0, 0, 0).unwrap());
|
||||
let time_now = Time::new(3, 0, 0, 0).unwrap();
|
||||
let time_now2 = Time::new(13, 0, 0, 0).unwrap();
|
||||
let time_now3 = Time::new(20, 0, 0, 0).unwrap();
|
||||
|
||||
assert!(is_inside_time_range(time_now, time_start, time_end));
|
||||
assert!(!is_inside_time_range(time_now2, time_start, time_end));
|
||||
@@ -503,4 +556,98 @@ mod tests {
|
||||
assert!(actual.starts_with(&col_prefix));
|
||||
assert!(actual.ends_with(&col_suffix));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_check_invalid_tz() {
|
||||
let actual = ModuleRenderer::new("time")
|
||||
.config(toml::toml! {
|
||||
[time]
|
||||
disabled = false
|
||||
time_format = "%T"
|
||||
utc_time_offset = "invalid"
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert!(actual.is_some(), "Falls back to local time");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_tz() {
|
||||
use nu_ansi_term::Color;
|
||||
|
||||
let actual = ModuleRenderer::new("time")
|
||||
.config(toml::toml! {
|
||||
[time]
|
||||
disabled = false
|
||||
time_format = "%:z"
|
||||
utc_time_offset = "Asia/Kolkata"
|
||||
})
|
||||
.collect();
|
||||
|
||||
let expected = Some(format!("at {} ", Color::Yellow.bold().paint("+05:30")));
|
||||
|
||||
assert_eq!(actual, expected, "Uses timezone");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_offset() {
|
||||
use nu_ansi_term::Color;
|
||||
|
||||
let actual = ModuleRenderer::new("time")
|
||||
.config(toml::toml! {
|
||||
[time]
|
||||
disabled = false
|
||||
time_format = "%:z"
|
||||
utc_time_offset = "-1.75"
|
||||
})
|
||||
.collect();
|
||||
|
||||
let expected = Some(format!("at {} ", Color::Yellow.bold().paint("-01:45")));
|
||||
|
||||
assert_eq!(actual, expected, "Uses timezone offset");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_tz_abbreviation() {
|
||||
let actual = ModuleRenderer::new("time")
|
||||
.config(toml::toml! {
|
||||
[time]
|
||||
disabled = false
|
||||
time_format = "%Z"
|
||||
utc_time_offset = "America/New_York"
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Should output a timezone abbreviation like "EST" or "EDT"
|
||||
assert!(
|
||||
actual.is_some(),
|
||||
"Timezone abbreviation should be displayed"
|
||||
);
|
||||
let output = actual.unwrap();
|
||||
assert!(
|
||||
output.contains("EST") || output.contains("EDT"),
|
||||
"Should contain timezone abbreviation"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_tz_identifier() {
|
||||
use nu_ansi_term::Color;
|
||||
|
||||
let actual = ModuleRenderer::new("time")
|
||||
.config(toml::toml! {
|
||||
[time]
|
||||
disabled = false
|
||||
time_format = "%Q"
|
||||
utc_time_offset = "America/New_York"
|
||||
})
|
||||
.collect();
|
||||
|
||||
let expected = Some(format!(
|
||||
"at {} ",
|
||||
Color::Yellow.bold().paint("America/New_York")
|
||||
));
|
||||
|
||||
assert_eq!(actual, expected, "Uses IANA timezone identifier");
|
||||
}
|
||||
}
|
||||
|
||||
+30
-7
@@ -527,7 +527,7 @@ impl ValueEnum for Preset {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn preset_command(name: Option<Preset>, output: Option<PathBuf>, list: bool) {
|
||||
pub fn preset_command(name: Option<Preset>, output: Option<PathBuf>, force: bool, list: bool) {
|
||||
if list {
|
||||
println!("{}", preset_list());
|
||||
return;
|
||||
@@ -535,11 +535,11 @@ pub fn preset_command(name: Option<Preset>, output: Option<PathBuf>, list: bool)
|
||||
let variant = name.expect("name argument must be specified");
|
||||
let content = shadow::get_preset_content(variant.0);
|
||||
if let Some(output) = output {
|
||||
if let Err(err) = std::fs::write(output, content) {
|
||||
eprintln!("Error writing preset to file: {err}");
|
||||
if let Err(e) = crate::utils::write_file_atomic(&output, content, force) {
|
||||
eprintln!("Error writing preset to {output:?}: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else if let Err(err) = std::io::stdout().write_all(content) {
|
||||
} else if let Err(err) = std::io::stdout().write_all(content.as_bytes()) {
|
||||
eprintln!("Error writing preset to stdout: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
@@ -681,9 +681,9 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn preset_command_does_not_panic_on_correct_inputs() {
|
||||
preset_command(None, None, true);
|
||||
preset_command(None, None, false, true);
|
||||
for v in Preset::value_variants() {
|
||||
preset_command(Some(v.clone()), None, false);
|
||||
preset_command(Some(v.clone()), None, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,12 +691,35 @@ mod test {
|
||||
fn preset_command_output_to_file() -> std::io::Result<()> {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("preset.toml");
|
||||
preset_command(Some(Preset("nerd-font-symbols")), Some(path.clone()), false);
|
||||
preset_command(
|
||||
Some(Preset("nerd-font-symbols")),
|
||||
Some(path.clone()),
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
let actual = utils::read_file(&path)?;
|
||||
let expected = include_str!("../docs/public/presets/toml/nerd-font-symbols.toml");
|
||||
assert_eq!(actual, expected);
|
||||
dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preset_command_output_existing_file_force() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("preset.toml");
|
||||
utils::write_file(&path, "existing content")?;
|
||||
|
||||
preset_command(
|
||||
Some(Preset("nerd-font-symbols")),
|
||||
Some(path.clone()),
|
||||
true,
|
||||
false,
|
||||
);
|
||||
|
||||
let actual = utils::read_file(&path).unwrap();
|
||||
let expected = include_str!("../docs/public/presets/toml/nerd-font-symbols.toml");
|
||||
assert_eq!(actual, expected);
|
||||
dir.close()
|
||||
}
|
||||
|
||||
|
||||
+119
-5
@@ -5,8 +5,8 @@ pub mod statusline;
|
||||
use process_control::{ChildExt, Control};
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::read_to_string;
|
||||
use std::io::{Error, ErrorKind, Result};
|
||||
use std::fs;
|
||||
use std::io::{Error, ErrorKind, Result, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -44,7 +44,7 @@ pub fn context_path<S: AsRef<OsStr> + ?Sized>(context: &Context, s: &S) -> PathB
|
||||
pub fn read_file<P: AsRef<Path> + Debug>(file_name: P) -> Result<String> {
|
||||
log::trace!("Trying to read from {file_name:?}");
|
||||
|
||||
let result = read_to_string(file_name);
|
||||
let result = fs::read_to_string(file_name);
|
||||
|
||||
if result.is_err() {
|
||||
log::debug!("Error reading file: {result:?}");
|
||||
@@ -58,8 +58,6 @@ pub fn read_file<P: AsRef<Path> + Debug>(file_name: P) -> Result<String> {
|
||||
/// Write a string to a file
|
||||
#[cfg(test)]
|
||||
pub fn write_file<P: AsRef<Path>, S: AsRef<str>>(file_name: P, text: S) -> Result<()> {
|
||||
use std::io::Write;
|
||||
|
||||
let file_name = file_name.as_ref();
|
||||
let text = text.as_ref();
|
||||
|
||||
@@ -89,6 +87,73 @@ pub fn write_file<P: AsRef<Path>, S: AsRef<str>>(file_name: P, text: S) -> Resul
|
||||
file.sync_all()
|
||||
}
|
||||
|
||||
/// Write contents to a file by first writing to a temporary file
|
||||
/// and then move it to the target location in place
|
||||
/// Only overwrites existing files if `force` is true
|
||||
pub fn write_file_atomic<P: AsRef<Path>, S: AsRef<str>>(
|
||||
target_path: P,
|
||||
text: S,
|
||||
force: bool,
|
||||
) -> std::result::Result<(), String> {
|
||||
let target_path = target_path.as_ref();
|
||||
let text = text.as_ref();
|
||||
|
||||
log::trace!("Trying to write {text:?} to {target_path:?}");
|
||||
|
||||
#[cfg_attr(not(unix), allow(unused_mut))]
|
||||
let mut builder = tempfile::Builder::new();
|
||||
|
||||
// On Unix, the default permissions are too restrictive, so we need to relax them
|
||||
// This should be safe because we're creating a temporary file in the same directory
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
let permissions = target_path
|
||||
.metadata()
|
||||
.as_ref()
|
||||
.map(fs::Metadata::permissions)
|
||||
.unwrap_or_else(|_| {
|
||||
let all_read_write = 0o666;
|
||||
std::fs::Permissions::from_mode(all_read_write)
|
||||
});
|
||||
|
||||
builder.permissions(permissions);
|
||||
}
|
||||
|
||||
let Some(parent_dir) = target_path.parent() else {
|
||||
return Err(format!(
|
||||
"Unable to determine parent directory of {target_path:?}"
|
||||
));
|
||||
};
|
||||
|
||||
let mut temp_file = builder
|
||||
.tempfile_in(parent_dir)
|
||||
.map_err(|e| format!("Error creating temporary file: {}", e))?;
|
||||
|
||||
if let Err(err) = temp_file.write_all(text.as_bytes()) {
|
||||
return Err(format!("Error writing to temporary file: {}", err));
|
||||
}
|
||||
|
||||
let result = if force {
|
||||
temp_file.persist(target_path)
|
||||
} else {
|
||||
temp_file.persist_noclobber(target_path)
|
||||
};
|
||||
|
||||
result.map_err(|e| {
|
||||
if !force && e.error.kind() == ErrorKind::AlreadyExists {
|
||||
"Error saving file, use --force to overwrite existing configuration file".to_string()
|
||||
} else {
|
||||
format!("Error moving temporary file to target location: {e}")
|
||||
}
|
||||
})?;
|
||||
|
||||
log::trace!("File {target_path:?} written successfully");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads command output from stderr or stdout depending on to which stream program streamed it's output
|
||||
pub fn get_command_string_output(command: CommandOutput) -> String {
|
||||
if command.stdout.is_empty() {
|
||||
@@ -819,6 +884,8 @@ impl PathExt for Path {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tempfile::tempdir;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -1033,4 +1100,51 @@ mod tests {
|
||||
"080d09bd815e".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_file_atomic() -> Result<()> {
|
||||
// Create a temporary file for testing
|
||||
let tmp_dir = tempdir()?;
|
||||
let path = tmp_dir.path().join("test_config.toml");
|
||||
|
||||
let expected = "test data";
|
||||
write_file_atomic(&path, expected, false).unwrap();
|
||||
|
||||
let actual_data = read_file(&path)?;
|
||||
assert_eq!(actual_data, expected);
|
||||
|
||||
tmp_dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_file_atomic_already_exists() -> Result<()> {
|
||||
let tmp_dir = tempdir()?;
|
||||
let tmp_file_path = tmp_dir.path().join("test_config.toml");
|
||||
|
||||
write_file(&tmp_file_path, "existing data")?;
|
||||
|
||||
let err = write_file_atomic(&tmp_file_path, "should not contain this", false).unwrap_err();
|
||||
assert!(err.contains("--force"));
|
||||
|
||||
let actual_data = read_file(&tmp_file_path)?;
|
||||
assert_eq!(actual_data, "existing data");
|
||||
|
||||
tmp_dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_file_atomic_overwrite() -> Result<()> {
|
||||
let tmp_dir = tempdir()?;
|
||||
let path = tmp_dir.path().join("test_config.toml");
|
||||
|
||||
write_file(&path, "existing data")?;
|
||||
|
||||
let expected = "test data";
|
||||
write_file_atomic(&path, expected, true).unwrap();
|
||||
|
||||
let actual = read_file(&path)?;
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
tmp_dir.close()
|
||||
}
|
||||
}
|
||||
|
||||
+101
-2
@@ -1,6 +1,6 @@
|
||||
use crate::module::ALL_MODULES;
|
||||
use serde::de::{
|
||||
Deserializer, Error, IntoDeserializer, Visitor,
|
||||
self, Deserializer, Error, IntoDeserializer, Visitor,
|
||||
value::{Error as ValueError, MapDeserializer, SeqDeserializer},
|
||||
};
|
||||
use std::{cmp::Ordering, fmt};
|
||||
@@ -100,6 +100,20 @@ impl ValueDeserializer<'_> {
|
||||
_ => ValueError::custom(msg),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the fitting `de::Unexpected` type description for the given value.
|
||||
/// For use with `Error::invalid_type`.
|
||||
fn serde_unexpected(&self) -> de::Unexpected<'_> {
|
||||
match self.value {
|
||||
ValueRef::Boolean(b) => de::Unexpected::Bool(b),
|
||||
ValueRef::Integer(i) => de::Unexpected::Signed(i),
|
||||
ValueRef::Float(f) => de::Unexpected::Float(f),
|
||||
ValueRef::String(s) => de::Unexpected::Str(s),
|
||||
ValueRef::Array(_v) => de::Unexpected::Other("array"),
|
||||
ValueRef::Table(_v) => de::Unexpected::Other("table"),
|
||||
ValueRef::Datetime(_v) => de::Unexpected::Other("datetime"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> IntoDeserializer<'de> for ValueDeserializer<'de> {
|
||||
@@ -215,10 +229,29 @@ impl<'de> Deserializer<'de> for ValueDeserializer<'de> {
|
||||
visitor.visit_newtype_struct(self)
|
||||
}
|
||||
|
||||
fn deserialize_enum<V>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variants: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
match self.value {
|
||||
// de::Value::StrDeserializer implements de::EnumAccess, so we can just use it.
|
||||
ValueRef::String(s) => visitor.visit_enum(s.into_deserializer()),
|
||||
_ => Err(Self::Error::invalid_type(
|
||||
self.serde_unexpected(),
|
||||
&"string",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// Handle most deserialization cases by deferring to `deserialize_any`.
|
||||
serde::forward_to_deserialize_any! {
|
||||
bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string unit seq
|
||||
bytes byte_buf map unit_struct tuple_struct enum tuple identifier
|
||||
bytes byte_buf map unit_struct tuple_struct tuple identifier
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,6 +383,72 @@ mod test {
|
||||
assert_eq!(result.foo.0, "bar".to_owned());
|
||||
}
|
||||
|
||||
#[derive(Deserialize, PartialEq, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum SampleEnum {
|
||||
FirstItem,
|
||||
Second,
|
||||
ThirdItem,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_enum() {
|
||||
#[derive(Deserialize)]
|
||||
struct Sample {
|
||||
foo: SampleEnum,
|
||||
}
|
||||
|
||||
let value = toml::toml! {
|
||||
foo = "first_item"
|
||||
};
|
||||
|
||||
let deserializer = ValueDeserializer::new(&value);
|
||||
|
||||
let result = Sample::deserialize(deserializer).unwrap();
|
||||
assert_eq!(result.foo, SampleEnum::FirstItem);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_enum_unknown() {
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct Sample {
|
||||
foo: SampleEnum,
|
||||
}
|
||||
|
||||
let value = toml::toml! {
|
||||
foo = "unknown"
|
||||
};
|
||||
|
||||
let deserializer = ValueDeserializer::new(&value);
|
||||
|
||||
let result = Sample::deserialize(deserializer);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_enum_invalid_type() {
|
||||
#[derive(Deserialize, PartialEq, Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct Sample {
|
||||
foo: SampleEnum,
|
||||
}
|
||||
|
||||
let value = toml::toml! {
|
||||
foo = 1
|
||||
};
|
||||
|
||||
let deserializer = ValueDeserializer::new(&value);
|
||||
|
||||
let result = Sample::deserialize(deserializer);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(serde::de::Error::custom(
|
||||
"invalid type: integer `1`, expected string"
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_unknown() {
|
||||
let value = toml::toml! {
|
||||
|
||||
Reference in New Issue
Block a user