mirror of
https://github.com/starship/starship.git
synced 2025-03-04 02:01:45 +01:00
Merge branch 'master' into conditional-style
This commit is contained in:
commit
54fa8a071c
8
.github/config-schema.json
vendored
8
.github/config-schema.json
vendored
@ -12,7 +12,7 @@
|
||||
"disabled": false,
|
||||
"expiration_symbol": "X",
|
||||
"force_display": false,
|
||||
"format": "on [$symbol($profile )(\\($region\\) )(\\[$duration\\])]($style)",
|
||||
"format": "on [$symbol($profile )(\\($region\\) )(\\[$duration\\] )]($style)",
|
||||
"profile_aliases": {},
|
||||
"region_aliases": {},
|
||||
"style": "bold yellow",
|
||||
@ -468,7 +468,7 @@
|
||||
"default": {
|
||||
"always_show_remote": false,
|
||||
"disabled": false,
|
||||
"format": "on [$symbol$branch]($style)(:[$remote]($style)) ",
|
||||
"format": "on [$symbol$branch(:$remote_branch)]($style) ",
|
||||
"ignore_branches": [],
|
||||
"only_attached": false,
|
||||
"style": "bold purple",
|
||||
@ -1486,7 +1486,7 @@
|
||||
"properties": {
|
||||
"format": {
|
||||
"description": "The format for the module.",
|
||||
"default": "on [$symbol($profile )(\\($region\\) )(\\[$duration\\])]($style)",
|
||||
"default": "on [$symbol($profile )(\\($region\\) )(\\[$duration\\] )]($style)",
|
||||
"type": "string"
|
||||
},
|
||||
"symbol": {
|
||||
@ -2618,7 +2618,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"format": {
|
||||
"default": "on [$symbol$branch]($style)(:[$remote]($style)) ",
|
||||
"default": "on [$symbol$branch(:$remote_branch)]($style) ",
|
||||
"type": "string"
|
||||
},
|
||||
"symbol": {
|
||||
|
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@ -284,7 +284,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Merge | Merge Crowdin PR
|
||||
run: gh pr merge --squash i18n_master
|
||||
run: gh pr merge i18n_master --squash --repo=starship/starship
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
18
CHANGELOG.md
18
CHANGELOG.md
@ -1,5 +1,23 @@
|
||||
# Changelog
|
||||
|
||||
### [1.6.3](https://github.com/starship/starship/compare/v1.6.2...v1.6.3) (2022-04-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **git_branch:** correct variable name for remote branch ([#3897](https://github.com/starship/starship/issues/3897)) ([bd7957f](https://github.com/starship/starship/commit/bd7957f01c7fa2b14f068e4130f1aedea61f4a76))
|
||||
* **schema:** move config-schema into docs folder ([#3878](https://github.com/starship/starship/issues/3878)) ([094f982](https://github.com/starship/starship/commit/094f982df184eecd85ea2832b3bf638629118c10))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **package:** only try to read files that exist ([#3904](https://github.com/starship/starship/issues/3904)) ([2a650bf](https://github.com/starship/starship/commit/2a650bfd140d561f955705cae124fb254ec549a1))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* **schema:** move config-schema back into .github folder ([#3886](https://github.com/starship/starship/issues/3886)) ([9b2ce42](https://github.com/starship/starship/commit/9b2ce4240c602df368f966996d870ef9197e65ac))
|
||||
|
||||
### [1.6.2](https://github.com/starship/starship/compare/v1.6.1...v1.6.2) (2022-04-15)
|
||||
|
||||
|
||||
|
48
Cargo.lock
generated
48
Cargo.lock
generated
@ -272,9 +272,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.1.10"
|
||||
version = "3.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3124f3f75ce09e22d1410043e1e24f2ecc44fad3afe4f08408f1f7663d68da2b"
|
||||
checksum = "7c167e37342afc5f33fd87bbc870cedd020d2a6dffa05d45ccd9241fbdd146db"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
@ -1055,6 +1055,17 @@ dependencies = [
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.2"
|
||||
@ -1898,7 +1909,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "starship"
|
||||
version = "1.6.2"
|
||||
version = "1.6.3"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"byte-unit",
|
||||
@ -1914,7 +1925,7 @@ dependencies = [
|
||||
"local_ipaddress",
|
||||
"log",
|
||||
"mockall",
|
||||
"nix",
|
||||
"nix 0.24.1",
|
||||
"notify-rust",
|
||||
"once_cell",
|
||||
"open",
|
||||
@ -1947,7 +1958,7 @@ dependencies = [
|
||||
"urlencoding",
|
||||
"versions",
|
||||
"which",
|
||||
"winapi",
|
||||
"windows 0.35.0",
|
||||
"winres",
|
||||
"yaml-rust",
|
||||
]
|
||||
@ -1963,7 +1974,7 @@ dependencies = [
|
||||
"lazycell",
|
||||
"libc",
|
||||
"mach",
|
||||
"nix",
|
||||
"nix 0.23.1",
|
||||
"num-traits",
|
||||
"uom",
|
||||
"winapi",
|
||||
@ -2310,9 +2321,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "versions"
|
||||
version = "4.0.0"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5276c151793dde1cc57e08123f36f96e662a9f2532060c677612bf0e2c604d4"
|
||||
checksum = "ee97e1d97bd593fb513912a07691b742361b3dd64ad56f2c694ea2dbfe0665d3"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"nom 7.1.1",
|
||||
@ -2320,9 +2331,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vtparse"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f41c9314c4dde1f43dd0c46c67bb5ae73850ce11eebaf7d8b912e178bda5401"
|
||||
checksum = "36ce903972602c84dd48f488cdce39edcba03a93b7ca67b146ae862568f48c5c"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
@ -2408,6 +2419,19 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.24.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08746b4b7ac95f708b3cccceb97b7f9a21a8916dd47fc99b0e6aaf7208f26fd7"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu 0.35.0",
|
||||
"windows_i686_msvc 0.35.0",
|
||||
"windows_x86_64_gnu 0.35.0",
|
||||
"windows_x86_64_msvc 0.35.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.35.0"
|
||||
@ -2491,7 +2515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "007a0353840b23e0c6dc73e5b962ff58ed7f6bc9ceff3ce7fe6fbad8d496edf4"
|
||||
dependencies = [
|
||||
"strum",
|
||||
"windows",
|
||||
"windows 0.24.0",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
@ -2533,7 +2557,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
"nix",
|
||||
"nix 0.23.1",
|
||||
"once_cell",
|
||||
"ordered-stream",
|
||||
"rand 0.8.5",
|
||||
|
19
Cargo.toml
19
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "starship"
|
||||
version = "1.6.2"
|
||||
version = "1.6.3"
|
||||
authors = ["Starship Contributors"]
|
||||
build = "build.rs"
|
||||
categories = ["command-line-utilities"]
|
||||
@ -39,7 +39,7 @@ notify = ["notify-rust"]
|
||||
ansi_term = "0.12.1"
|
||||
byte-unit = "4.0.14"
|
||||
chrono = "0.4.19"
|
||||
clap = { version = "3.1.10", features = ["derive", "cargo", "unicode"] }
|
||||
clap = { version = "3.1.12", features = ["derive", "cargo", "unicode"] }
|
||||
clap_complete = "3.1.2"
|
||||
dirs-next = "2.0.0"
|
||||
dunce = "1.0.2"
|
||||
@ -78,7 +78,7 @@ toml_edit = "0.14.2"
|
||||
unicode-segmentation = "1.9.0"
|
||||
unicode-width = "0.1.9"
|
||||
urlencoding = "2.1.0"
|
||||
versions = "4.0.0"
|
||||
versions = "4.1.0"
|
||||
which = "4.2.5"
|
||||
yaml-rust = "0.4.5"
|
||||
|
||||
@ -92,11 +92,20 @@ optional = true
|
||||
features = ["preserve_order", "indexmap"]
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.9", features = ["winuser", "securitybaseapi", "processthreadsapi", "handleapi", "impl-default"] }
|
||||
deelevate = "0.2.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
version = "0.35.0"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_Security",
|
||||
"Win32_System_Threading",
|
||||
"Win32_Storage_FileSystem",
|
||||
]
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
nix = "0.23.1"
|
||||
nix = { version = "0.24.1", default-features = false, features = ["feature", "fs", "user"] }
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = "0.11.0"
|
||||
|
@ -111,7 +111,9 @@ description: Starship is the minimal, blazing fast, and extremely customizable p
|
||||
#### Elvish
|
||||
|
||||
::: warning
|
||||
|
||||
Only elvish v0.18 or higher is supported.
|
||||
|
||||
:::
|
||||
|
||||
Add the following to the end of `~/.elvish/rc.elv`:
|
||||
@ -135,9 +137,12 @@ description: Starship is the minimal, blazing fast, and extremely customizable p
|
||||
#### Nushell
|
||||
|
||||
::: warning
|
||||
|
||||
This will change in the future.
|
||||
Only Nushell v0.60+ is supported.
|
||||
|
||||
:::
|
||||
|
||||
Run the following:
|
||||
```sh
|
||||
mkdir ~/.cache/starship
|
||||
|
@ -306,16 +306,16 @@ date is read from the `AWSUME_EXPIRATION` env var.
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Default | Description |
|
||||
| ------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| `format` | `'on [$symbol($profile )(\($region\) )(\[$duration\])]($style)'` | The format for the module. |
|
||||
| `symbol` | `"☁️ "` | The symbol used before displaying the current AWS profile. |
|
||||
| `region_aliases` | | Table of region aliases to display in addition to the AWS name. |
|
||||
| `profile_aliases` | | Table of profile aliases to display in addition to the AWS name. |
|
||||
| `style` | `"bold yellow"` | The style for the module. |
|
||||
| `expiration_symbol` | `X` | The symbol displayed when the temporary credentials have expired. |
|
||||
| `disabled` | `false` | Disables the `AWS` module. |
|
||||
| `force_display` | `false` | If `true` displays info even if `credentials`, `credential_process` or `sso_start_url` have not been setup. |
|
||||
| Option | Default | Description |
|
||||
| ------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||
| `format` | `'on [$symbol($profile )(\($region\) )(\[$duration\] )]($style)'` | The format for the module. |
|
||||
| `symbol` | `"☁️ "` | The symbol used before displaying the current AWS profile. |
|
||||
| `region_aliases` | | Table of region aliases to display in addition to the AWS name. |
|
||||
| `profile_aliases` | | Table of profile aliases to display in addition to the AWS name. |
|
||||
| `style` | `"bold yellow"` | The style for the module. |
|
||||
| `expiration_symbol` | `X` | The symbol displayed when the temporary credentials have expired. |
|
||||
| `disabled` | `false` | Disables the `AWS` module. |
|
||||
| `force_display` | `false` | If `true` displays info even if `credentials`, `credential_process` or `sso_start_url` have not been setup. |
|
||||
|
||||
### Variables
|
||||
|
||||
@ -1211,6 +1211,7 @@ The module will be shown only if any of the following conditions are met:
|
||||
- The `variable` configuration option is not defined, but the `default` configuration option is
|
||||
|
||||
::: tip
|
||||
|
||||
Multiple environmental variables can be displayed by using a `.`. (see example)
|
||||
If the `variable` configuration option is not set, the module will display value of variable under the name of text after the `.` character.
|
||||
|
||||
@ -1418,17 +1419,17 @@ The `git_branch` module shows the active branch of the repo in your current dire
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Default | Description |
|
||||
| -------------------- | -------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| `always_show_remote` | `false` | Shows the remote tracking branch name, even if it is equal to the local branch name. |
|
||||
| `format` | `"on [$symbol$branch]($style) "` | The format for the module. Use `"$branch"` to refer to the current branch name. |
|
||||
| `symbol` | `" "` | A format string representing the symbol of git branch. |
|
||||
| `style` | `"bold purple"` | The style for the module. |
|
||||
| `truncation_length` | `2^63 - 1` | Truncates a git branch to `N` graphemes. |
|
||||
| `truncation_symbol` | `"…"` | The symbol used to indicate a branch name was truncated. You can use `""` for no symbol. |
|
||||
| `only_attached` | `false` | Only show the branch name when not in a detached `HEAD` state. |
|
||||
| `ignore_branches` | `[]` | A list of names to avoid displaying. Useful for "master" or "main". |
|
||||
| `disabled` | `false` | Disables the `git_branch` module. |
|
||||
| Option | Default | Description |
|
||||
| -------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| `always_show_remote` | `false` | Shows the remote tracking branch name, even if it is equal to the local branch name. |
|
||||
| `format` | `"on [$symbol$branch(:$remote_branch)]($style) "` | The format for the module. Use `"$branch"` to refer to the current branch name. |
|
||||
| `symbol` | `" "` | A format string representing the symbol of git branch. |
|
||||
| `style` | `"bold purple"` | The style for the module. |
|
||||
| `truncation_length` | `2^63 - 1` | Truncates a git branch to `N` graphemes. |
|
||||
| `truncation_symbol` | `"…"` | The symbol used to indicate a branch name was truncated. You can use `""` for no symbol. |
|
||||
| `only_attached` | `false` | Only show the branch name when not in a detached `HEAD` state. |
|
||||
| `ignore_branches` | `[]` | A list of names to avoid displaying. Useful for "master" or "main". |
|
||||
| `disabled` | `false` | Disables the `git_branch` module. |
|
||||
|
||||
### Variables
|
||||
|
||||
|
@ -50,7 +50,7 @@ pub struct AwsConfig<'a> {
|
||||
impl<'a> Default for AwsConfig<'a> {
|
||||
fn default() -> Self {
|
||||
AwsConfig {
|
||||
format: "on [$symbol($profile )(\\($region\\) )(\\[$duration\\])]($style)",
|
||||
format: "on [$symbol($profile )(\\($region\\) )(\\[$duration\\] )]($style)",
|
||||
symbol: "☁️ ",
|
||||
style: "bold yellow",
|
||||
disabled: false,
|
||||
|
@ -18,7 +18,7 @@ pub struct GitBranchConfig<'a> {
|
||||
impl<'a> Default for GitBranchConfig<'a> {
|
||||
fn default() -> Self {
|
||||
GitBranchConfig {
|
||||
format: "on [$symbol$branch]($style)(:[$remote]($style)) ",
|
||||
format: "on [$symbol$branch(:$remote_branch)]($style) ",
|
||||
symbol: " ",
|
||||
style: "bold purple",
|
||||
truncation_length: std::i64::MAX,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::config::{ModuleConfig, StarshipConfig};
|
||||
use crate::configs::StarshipRootConfig;
|
||||
use crate::module::Module;
|
||||
use crate::utils::{create_command, exec_timeout, CommandOutput};
|
||||
use crate::utils::{create_command, exec_timeout, read_file, CommandOutput};
|
||||
|
||||
use crate::modules;
|
||||
use crate::utils::{self, home_dir};
|
||||
@ -342,6 +342,18 @@ impl<'a> Context<'a> {
|
||||
.iter()
|
||||
.find_map(|attempt| self.exec_cmd(attempt[0], &attempt[1..]))
|
||||
}
|
||||
|
||||
/// Returns the string contents of a file from the current working directory
|
||||
pub fn read_file_from_pwd(&self, file_name: &str) -> Option<String> {
|
||||
if !self.try_begin_scan()?.set_files(&[file_name]).is_match() {
|
||||
log::debug!(
|
||||
"Not attempting to read {file_name} because, it was not found during scan."
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
read_file(self.current_dir.join(file_name)).ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -633,7 +633,7 @@ credential_process = /opt/bin/awscreds-retriever
|
||||
"on {}",
|
||||
Color::Yellow
|
||||
.bold()
|
||||
.paint("☁️ astronauts (ap-northeast-2) [30m]")
|
||||
.paint("☁️ astronauts (ap-northeast-2) [30m] ")
|
||||
));
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
@ -679,7 +679,7 @@ expiration={}
|
||||
// on shared runners may delay it. Allow for up to 2 seconds of delay.
|
||||
let possible_values = ["30m", "29m59s", "29m58s"];
|
||||
let possible_values = possible_values.map(|duration| {
|
||||
let segment_colored = format!("☁️ astronauts (ap-northeast-2) [{}]", duration);
|
||||
let segment_colored = format!("☁️ astronauts (ap-northeast-2) [{}] ", duration);
|
||||
Some(format!(
|
||||
"on {}",
|
||||
Color::Yellow.bold().paint(segment_colored)
|
||||
@ -736,7 +736,7 @@ expiration={}
|
||||
"on {}",
|
||||
Color::Yellow
|
||||
.bold()
|
||||
.paint(format!("☁️ astronauts (ap-northeast-2) [{}]", symbol))
|
||||
.paint(format!("☁️ astronauts (ap-northeast-2) [{}] ", symbol))
|
||||
));
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
|
@ -396,6 +396,42 @@ mod tests {
|
||||
repo_dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remote() -> io::Result<()> {
|
||||
let remote_dir = fixture_repo(FixtureProvider::Git)?;
|
||||
let repo_dir = fixture_repo(FixtureProvider::Git)?;
|
||||
|
||||
create_command("git")?
|
||||
.args(&["checkout", "-b", "test_branch"])
|
||||
.current_dir(repo_dir.path())
|
||||
.output()?;
|
||||
|
||||
create_command("git")?
|
||||
.args(&["remote", "add", "--fetch", "remote_repo"])
|
||||
.arg(remote_dir.path())
|
||||
.current_dir(repo_dir.path())
|
||||
.output()?;
|
||||
|
||||
create_command("git")?
|
||||
.args(&["branch", "--set-upstream-to", "remote_repo/master"])
|
||||
.current_dir(repo_dir.path())
|
||||
.output()?;
|
||||
|
||||
let actual = ModuleRenderer::new("git_branch")
|
||||
.path(&repo_dir.path())
|
||||
.config(toml::toml! {
|
||||
[git_branch]
|
||||
format = "$branch(:$remote_name/$remote_branch)"
|
||||
})
|
||||
.collect();
|
||||
|
||||
let expected = Some("test_branch:remote_repo/master");
|
||||
|
||||
assert_eq!(expected, actual.as_deref());
|
||||
repo_dir.close()?;
|
||||
remote_dir.close()
|
||||
}
|
||||
|
||||
// This test is not possible until we switch to `git status --porcelain`
|
||||
// where we can mock the env for the specific git process. This is because
|
||||
// git2 does not care about our mocking and when we set the real `GIT_DIR`
|
||||
|
@ -372,7 +372,12 @@ fn git_status_wsl(context: &Context, conf: &GitStatusConfig) -> Option<String> {
|
||||
|
||||
// Ensure this is WSL
|
||||
// This is lowercase in WSL1 and uppercase in WSL2, just skip the first letter
|
||||
if !uname().release().contains("icrosoft") {
|
||||
if !uname()
|
||||
.ok()?
|
||||
.release()
|
||||
.to_string_lossy()
|
||||
.contains("icrosoft")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ use super::{Context, Module, ModuleConfig};
|
||||
|
||||
use crate::configs::haskell::HaskellConfig;
|
||||
use crate::formatter::StringFormatter;
|
||||
use crate::utils;
|
||||
|
||||
/// Creates a module with the current Haskell version
|
||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
@ -64,7 +63,7 @@ fn get_snapshot(context: &Context) -> Option<String> {
|
||||
if !is_stack_project(context) {
|
||||
return None;
|
||||
}
|
||||
let file_contents = utils::read_file(context.current_dir.join("stack.yaml")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("stack.yaml")?;
|
||||
let yaml = yaml_rust::YamlLoader::load_from_str(&file_contents).ok()?;
|
||||
let version = yaml.first()?["resolver"]
|
||||
.as_str()
|
||||
|
@ -2,7 +2,6 @@ use super::{Context, Module, ModuleConfig};
|
||||
|
||||
use crate::configs::nodejs::NodejsConfig;
|
||||
use crate::formatter::{StringFormatter, VersionFormatter};
|
||||
use crate::utils;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
@ -10,7 +9,6 @@ use semver::Version;
|
||||
use semver::VersionReq;
|
||||
use serde_json as json;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
||||
/// Creates a module with the current Node.js version
|
||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
@ -45,7 +43,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
})
|
||||
.map_style(|variable| match variable {
|
||||
"style" => {
|
||||
let engines_version = get_engines_version(&context.current_dir);
|
||||
let engines_version = get_engines_version(context);
|
||||
let in_engines_range =
|
||||
check_engines_version(nodejs_version.deref().as_ref()?, engines_version);
|
||||
if in_engines_range {
|
||||
@ -87,8 +85,8 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
Some(module)
|
||||
}
|
||||
|
||||
fn get_engines_version(base_dir: &Path) -> Option<String> {
|
||||
let json_str = utils::read_file(base_dir.join("package.json")).ok()?;
|
||||
fn get_engines_version(context: &Context) -> Option<String> {
|
||||
let json_str = context.read_file_from_pwd("package.json")?;
|
||||
let package_json: json::Value = json::from_str(&json_str).ok()?;
|
||||
let raw_version = package_json.get("engines")?.get("node")?.as_str()?;
|
||||
Some(raw_version.to_string())
|
||||
|
@ -1,7 +1,6 @@
|
||||
use super::{Context, Module, ModuleConfig};
|
||||
use crate::configs::package::PackageConfig;
|
||||
use crate::formatter::{StringFormatter, VersionFormatter};
|
||||
use crate::utils;
|
||||
|
||||
use ini::Ini;
|
||||
use quick_xml::events::Event as QXEvent;
|
||||
@ -44,7 +43,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
}
|
||||
|
||||
fn get_node_package_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(&context.current_dir.join("package.json")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("package.json")?;
|
||||
let package_json: json::Value = json::from_str(&file_contents).ok()?;
|
||||
|
||||
if !config.display_private
|
||||
@ -68,7 +67,7 @@ fn get_node_package_version(context: &Context, config: &PackageConfig) -> Option
|
||||
}
|
||||
|
||||
fn get_poetry_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(&context.current_dir.join("pyproject.toml")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("pyproject.toml")?;
|
||||
let poetry_toml: toml::Value = toml::from_str(&file_contents).ok()?;
|
||||
let raw_version = poetry_toml
|
||||
.get("tool")?
|
||||
@ -80,7 +79,7 @@ fn get_poetry_version(context: &Context, config: &PackageConfig) -> Option<Strin
|
||||
}
|
||||
|
||||
fn get_setup_cfg_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(context.current_dir.join("setup.cfg")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("setup.cfg")?;
|
||||
let ini = Ini::load_from_str(&file_contents).ok()?;
|
||||
let raw_version = ini.get_from(Some("metadata"), "version")?;
|
||||
|
||||
@ -92,7 +91,7 @@ fn get_setup_cfg_version(context: &Context, config: &PackageConfig) -> Option<St
|
||||
}
|
||||
|
||||
fn get_gradle_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(context.current_dir.join("build.gradle")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("build.gradle")?;
|
||||
let re = Regex::new(r#"(?m)^version ['"](?P<version>[^'"]+)['"]$"#).unwrap();
|
||||
let caps = re.captures(&file_contents)?;
|
||||
|
||||
@ -100,7 +99,7 @@ fn get_gradle_version(context: &Context, config: &PackageConfig) -> Option<Strin
|
||||
}
|
||||
|
||||
fn get_composer_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(context.current_dir.join("composer.json")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("composer.json")?;
|
||||
let composer_json: json::Value = json::from_str(&file_contents).ok()?;
|
||||
let raw_version = composer_json.get("version")?.as_str()?;
|
||||
|
||||
@ -108,7 +107,7 @@ fn get_composer_version(context: &Context, config: &PackageConfig) -> Option<Str
|
||||
}
|
||||
|
||||
fn get_julia_project_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(context.current_dir.join("Project.toml")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("Project.toml")?;
|
||||
let project_toml: toml::Value = toml::from_str(&file_contents).ok()?;
|
||||
let raw_version = project_toml.get("version")?.as_str()?;
|
||||
|
||||
@ -116,7 +115,7 @@ fn get_julia_project_version(context: &Context, config: &PackageConfig) -> Optio
|
||||
}
|
||||
|
||||
fn get_helm_package_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(context.current_dir.join("Chart.yaml")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("Chart.yaml")?;
|
||||
let yaml = yaml_rust::YamlLoader::load_from_str(&file_contents).ok()?;
|
||||
let version = yaml.first()?["version"].as_str()?;
|
||||
|
||||
@ -124,7 +123,7 @@ fn get_helm_package_version(context: &Context, config: &PackageConfig) -> Option
|
||||
}
|
||||
|
||||
fn get_mix_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(context.current_dir.join("mix.exs")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("mix.exs")?;
|
||||
let re = Regex::new(r#"(?m)version: "(?P<version>[^"]+)""#).unwrap();
|
||||
let caps = re.captures(&file_contents)?;
|
||||
|
||||
@ -132,8 +131,8 @@ fn get_mix_version(context: &Context, config: &PackageConfig) -> Option<String>
|
||||
}
|
||||
|
||||
fn get_maven_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let pom_file = utils::read_file(context.current_dir.join("pom.xml")).ok()?;
|
||||
let mut reader = QXReader::from_str(&pom_file);
|
||||
let file_contents = context.read_file_from_pwd("pom.xml")?;
|
||||
let mut reader = QXReader::from_str(&file_contents);
|
||||
reader.trim_text(true);
|
||||
|
||||
let mut buf = vec![];
|
||||
@ -171,8 +170,8 @@ fn get_maven_version(context: &Context, config: &PackageConfig) -> Option<String
|
||||
}
|
||||
|
||||
fn get_meson_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(context.current_dir.join("meson.build"))
|
||||
.ok()?
|
||||
let file_contents = context
|
||||
.read_file_from_pwd("meson.build")?
|
||||
.split_ascii_whitespace()
|
||||
.collect::<String>();
|
||||
|
||||
@ -183,14 +182,14 @@ fn get_meson_version(context: &Context, config: &PackageConfig) -> Option<String
|
||||
}
|
||||
|
||||
fn get_vmod_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(context.current_dir.join("v.mod")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("v.mod")?;
|
||||
let re = Regex::new(r"(?m)^\s*version\s*:\s*'(?P<version>[^']+)'").unwrap();
|
||||
let caps = re.captures(&file_contents)?;
|
||||
format_version(&caps["version"], config.version_format)
|
||||
}
|
||||
|
||||
fn get_vpkg_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(context.current_dir.join("vpkg.json")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("vpkg.json")?;
|
||||
let vpkg_json: json::Value = json::from_str(&file_contents).ok()?;
|
||||
let raw_version = vpkg_json.get("version")?.as_str()?;
|
||||
|
||||
@ -198,14 +197,14 @@ fn get_vpkg_version(context: &Context, config: &PackageConfig) -> Option<String>
|
||||
}
|
||||
|
||||
fn get_sbt_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(context.current_dir.join("build.sbt")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("build.sbt")?;
|
||||
let re = Regex::new(r"(?m)^(.*/)*\s*version\s*:=\s*.(?P<version>[\d\.]+)").unwrap();
|
||||
let caps = re.captures(&file_contents)?;
|
||||
format_version(&caps["version"], config.version_format)
|
||||
}
|
||||
|
||||
fn get_cargo_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(&context.current_dir.join("Cargo.toml")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("Cargo.toml")?;
|
||||
|
||||
let cargo_toml: toml::Value = toml::from_str(&file_contents).ok()?;
|
||||
let raw_version = cargo_toml.get("package")?.get("version")?.as_str()?;
|
||||
@ -231,7 +230,7 @@ fn get_nimble_version(context: &Context, config: &PackageConfig) -> Option<Strin
|
||||
}
|
||||
|
||||
fn get_shard_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(&context.current_dir.join("shard.yml")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("shard.yml")?;
|
||||
|
||||
let data = yaml_rust::YamlLoader::load_from_str(&file_contents).ok()?;
|
||||
let raw_version = data.first()?["version"].as_str()?;
|
||||
@ -240,7 +239,7 @@ fn get_shard_version(context: &Context, config: &PackageConfig) -> Option<String
|
||||
}
|
||||
|
||||
fn get_dart_pub_version(context: &Context, config: &PackageConfig) -> Option<String> {
|
||||
let file_contents = utils::read_file(&context.current_dir.join("pubspec.yaml")).ok()?;
|
||||
let file_contents = context.read_file_from_pwd("pubspec.yaml")?;
|
||||
|
||||
let data = yaml_rust::YamlLoader::load_from_str(&file_contents).ok()?;
|
||||
let raw_version = data.first()?["version"].as_str()?;
|
||||
|
@ -13,8 +13,9 @@ use std::path::Path;
|
||||
/// 2a) (not implemented on macOS) one of the supplementary groups of the current user is the
|
||||
/// directory group owner and whether it has write access
|
||||
/// 3) 'others' part of the access mask has the write access
|
||||
pub fn is_write_allowed(folder_path: &Path) -> Result<bool, &'static str> {
|
||||
let meta = fs::metadata(folder_path).map_err(|_| "Unable to stat() directory")?;
|
||||
pub fn is_write_allowed(folder_path: &Path) -> Result<bool, String> {
|
||||
let meta =
|
||||
fs::metadata(folder_path).map_err(|e| format!("Unable to stat() directory: {e:?}"))?;
|
||||
let perms = meta.permissions().mode();
|
||||
|
||||
let euid = Uid::effective();
|
||||
@ -54,9 +55,9 @@ mod tests {
|
||||
#[ignore]
|
||||
fn read_only_test() {
|
||||
assert_eq!(is_write_allowed(Path::new("/etc")), Ok(false));
|
||||
assert_eq!(
|
||||
is_write_allowed(Path::new("/i_dont_exist")),
|
||||
Err("Unable to stat() directory")
|
||||
);
|
||||
assert!(match is_write_allowed(Path::new("/i_dont_exist")) {
|
||||
Ok(_) => false,
|
||||
Err(e) => e.starts_with("Unable to stat() directory"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,32 @@
|
||||
extern crate winapi;
|
||||
use std::{ffi::c_void, mem, os::windows::ffi::OsStrExt, path::Path};
|
||||
|
||||
use std::mem;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
use winapi::shared::minwindef::{BOOL, DWORD};
|
||||
use winapi::um::handleapi;
|
||||
use winapi::um::processthreadsapi;
|
||||
use winapi::um::securitybaseapi;
|
||||
use winapi::um::winnt::{
|
||||
SecurityImpersonation, BOOLEAN, DACL_SECURITY_INFORMATION, FILE_ALL_ACCESS,
|
||||
FILE_GENERIC_EXECUTE, FILE_GENERIC_READ, FILE_GENERIC_WRITE, GENERIC_MAPPING,
|
||||
GROUP_SECURITY_INFORMATION, HANDLE, LPCWSTR, OWNER_SECURITY_INFORMATION, PRIVILEGE_SET,
|
||||
STANDARD_RIGHTS_READ, TOKEN_DUPLICATE, TOKEN_IMPERSONATE, TOKEN_QUERY,
|
||||
use windows::{
|
||||
core::PCWSTR,
|
||||
Win32::{
|
||||
Foundation::{CloseHandle, ERROR_INSUFFICIENT_BUFFER, HANDLE},
|
||||
Security::{
|
||||
AccessCheck, DuplicateToken, GetFileSecurityW, MapGenericMask, SecurityImpersonation,
|
||||
DACL_SECURITY_INFORMATION, GENERIC_MAPPING, GROUP_SECURITY_INFORMATION,
|
||||
OWNER_SECURITY_INFORMATION, PRIVILEGE_SET, PSECURITY_DESCRIPTOR, TOKEN_DUPLICATE,
|
||||
TOKEN_IMPERSONATE, TOKEN_QUERY, TOKEN_READ_CONTROL,
|
||||
},
|
||||
Storage::FileSystem::{
|
||||
FILE_ALL_ACCESS, FILE_GENERIC_EXECUTE, FILE_GENERIC_READ, FILE_GENERIC_WRITE,
|
||||
},
|
||||
System::Threading::{GetCurrentProcess, OpenProcessToken},
|
||||
UI::Shell::PathIsNetworkPathW,
|
||||
},
|
||||
};
|
||||
|
||||
/// Checks if the current user has write access right to the `folder_path`
|
||||
///
|
||||
/// First, the function extracts DACL from the given directory and then calls `AccessCheck` against
|
||||
/// the current process access token and directory's security descriptor.
|
||||
/// Does not work for network drives and always returns true
|
||||
pub fn is_write_allowed(folder_path: &Path) -> std::result::Result<bool, &'static str> {
|
||||
let folder_name: Vec<u16> = folder_path.as_os_str().encode_wide().chain([0]).collect();
|
||||
pub fn is_write_allowed(folder_path: &Path) -> std::result::Result<bool, String> {
|
||||
let wpath_vec: Vec<u16> = folder_path.as_os_str().encode_wide().chain([0]).collect();
|
||||
let wpath = PCWSTR(wpath_vec.as_ptr());
|
||||
|
||||
if is_network_path(&folder_name) {
|
||||
if unsafe { PathIsNetworkPathW(wpath) }.as_bool() {
|
||||
log::info!(
|
||||
"Directory '{:?}' is a network drive, unable to check write permissions. See #1506 for details",
|
||||
folder_path
|
||||
@ -31,79 +34,84 @@ pub fn is_write_allowed(folder_path: &Path) -> std::result::Result<bool, &'stati
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let mut length: DWORD = 0;
|
||||
let mut length = 0;
|
||||
|
||||
let rc = unsafe {
|
||||
securitybaseapi::GetFileSecurityW(
|
||||
folder_name.as_ptr(),
|
||||
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
|
||||
std::ptr::null_mut(),
|
||||
GetFileSecurityW(
|
||||
wpath,
|
||||
(OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION).0,
|
||||
PSECURITY_DESCRIPTOR::default(),
|
||||
0,
|
||||
&mut length,
|
||||
)
|
||||
};
|
||||
if rc != 0 {
|
||||
return Err(
|
||||
"GetFileSecurityW returned non-zero when asked for the security descriptor size",
|
||||
);
|
||||
|
||||
// expect ERROR_INSUFFICIENT_BUFFER
|
||||
match rc.ok() {
|
||||
Err(e) if e.win32_error() == Some(ERROR_INSUFFICIENT_BUFFER) => (),
|
||||
result => return Err(format!("GetFileSecurityW returned unexpected return value when asked for the security descriptor size: {:?}", result)),
|
||||
}
|
||||
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(length as usize);
|
||||
let mut buf = vec![0u8; length as usize];
|
||||
let psecurity_descriptor = PSECURITY_DESCRIPTOR(buf.as_mut_ptr() as *mut c_void);
|
||||
|
||||
let rc = unsafe {
|
||||
securitybaseapi::GetFileSecurityW(
|
||||
folder_name.as_ptr(),
|
||||
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
|
||||
buf.as_mut_ptr().cast::<std::ffi::c_void>(),
|
||||
GetFileSecurityW(
|
||||
wpath,
|
||||
(OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION).0,
|
||||
psecurity_descriptor,
|
||||
length,
|
||||
&mut length,
|
||||
)
|
||||
};
|
||||
|
||||
if rc != 1 {
|
||||
return Err("GetFileSecurityW failed to retrieve the security descriptor");
|
||||
if let Err(e) = rc.ok() {
|
||||
return Err(format!(
|
||||
"GetFileSecurityW failed to retrieve the security descriptor: {e:?}"
|
||||
));
|
||||
}
|
||||
|
||||
let mut token: HANDLE = 0 as HANDLE;
|
||||
let mut token = HANDLE::default();
|
||||
let rc = unsafe {
|
||||
processthreadsapi::OpenProcessToken(
|
||||
processthreadsapi::GetCurrentProcess(),
|
||||
TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ,
|
||||
OpenProcessToken(
|
||||
GetCurrentProcess(),
|
||||
TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_READ_CONTROL,
|
||||
&mut token,
|
||||
)
|
||||
};
|
||||
if rc != 1 {
|
||||
return Err("OpenProcessToken failed to retrieve current process' security token");
|
||||
if let Err(e) = rc.ok() {
|
||||
return Err(format!(
|
||||
"OpenProcessToken failed to retrieve current process' security token: {e:?}"
|
||||
));
|
||||
}
|
||||
|
||||
let mut impersonated_token: HANDLE = 0 as HANDLE;
|
||||
let rc = unsafe {
|
||||
securitybaseapi::DuplicateToken(token, SecurityImpersonation, &mut impersonated_token)
|
||||
};
|
||||
if rc != 1 {
|
||||
unsafe { handleapi::CloseHandle(token) };
|
||||
return Err("DuplicateToken failed");
|
||||
let mut impersonated_token = HANDLE::default();
|
||||
let rc = unsafe { DuplicateToken(token, SecurityImpersonation, &mut impersonated_token) };
|
||||
|
||||
if let Err(e) = rc.ok() {
|
||||
unsafe { CloseHandle(token) };
|
||||
return Err(format!("DuplicateToken failed: {e:?}"));
|
||||
}
|
||||
|
||||
let mut mapping: GENERIC_MAPPING = GENERIC_MAPPING {
|
||||
GenericRead: FILE_GENERIC_READ,
|
||||
GenericWrite: FILE_GENERIC_WRITE,
|
||||
GenericExecute: FILE_GENERIC_EXECUTE,
|
||||
GenericAll: FILE_ALL_ACCESS,
|
||||
let mapping = GENERIC_MAPPING {
|
||||
GenericRead: FILE_GENERIC_READ.0,
|
||||
GenericWrite: FILE_GENERIC_WRITE.0,
|
||||
GenericExecute: FILE_GENERIC_EXECUTE.0,
|
||||
GenericAll: FILE_ALL_ACCESS.0,
|
||||
};
|
||||
|
||||
let mut priviledges: PRIVILEGE_SET = PRIVILEGE_SET::default();
|
||||
let mut priv_size = mem::size_of::<PRIVILEGE_SET>() as DWORD;
|
||||
let mut granted_access: DWORD = 0;
|
||||
let mut access_rights: DWORD = FILE_GENERIC_WRITE;
|
||||
let mut result: BOOL = 0;
|
||||
unsafe { securitybaseapi::MapGenericMask(&mut access_rights, &mut mapping) };
|
||||
let mut priv_size = mem::size_of::<PRIVILEGE_SET>() as _;
|
||||
let mut granted_access = 0;
|
||||
let mut access_rights = FILE_GENERIC_WRITE;
|
||||
let mut result = 0;
|
||||
unsafe { MapGenericMask(&mut access_rights.0, &mapping) };
|
||||
let rc = unsafe {
|
||||
securitybaseapi::AccessCheck(
|
||||
buf.as_mut_ptr().cast::<std::ffi::c_void>(),
|
||||
AccessCheck(
|
||||
psecurity_descriptor,
|
||||
impersonated_token,
|
||||
access_rights,
|
||||
&mut mapping,
|
||||
access_rights.0,
|
||||
&mapping,
|
||||
&mut priviledges,
|
||||
&mut priv_size,
|
||||
&mut granted_access,
|
||||
@ -111,22 +119,13 @@ pub fn is_write_allowed(folder_path: &Path) -> std::result::Result<bool, &'stati
|
||||
)
|
||||
};
|
||||
unsafe {
|
||||
handleapi::CloseHandle(impersonated_token);
|
||||
handleapi::CloseHandle(token);
|
||||
CloseHandle(impersonated_token);
|
||||
CloseHandle(token);
|
||||
}
|
||||
|
||||
if rc != 1 {
|
||||
return Err("AccessCheck failed");
|
||||
if let Err(e) = rc.ok() {
|
||||
return Err(format!("AccessCheck failed: {e:?}"));
|
||||
}
|
||||
|
||||
Ok(result != 0)
|
||||
}
|
||||
|
||||
#[link(name = "Shlwapi")]
|
||||
extern "system" {
|
||||
fn PathIsNetworkPathW(pszPath: LPCWSTR) -> BOOLEAN;
|
||||
}
|
||||
|
||||
fn is_network_path(folder_path: &[u16]) -> bool {
|
||||
unsafe { PathIsNetworkPathW(folder_path.as_ptr()) == 1 }
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user