Merge branch 'master' into conditional-style

This commit is contained in:
Filip Bachul 2022-04-26 21:36:51 +02:00
commit 54fa8a071c
18 changed files with 262 additions and 156 deletions

View File

@ -12,7 +12,7 @@
"disabled": false, "disabled": false,
"expiration_symbol": "X", "expiration_symbol": "X",
"force_display": false, "force_display": false,
"format": "on [$symbol($profile )(\\($region\\) )(\\[$duration\\])]($style)", "format": "on [$symbol($profile )(\\($region\\) )(\\[$duration\\] )]($style)",
"profile_aliases": {}, "profile_aliases": {},
"region_aliases": {}, "region_aliases": {},
"style": "bold yellow", "style": "bold yellow",
@ -468,7 +468,7 @@
"default": { "default": {
"always_show_remote": false, "always_show_remote": false,
"disabled": false, "disabled": false,
"format": "on [$symbol$branch]($style)(:[$remote]($style)) ", "format": "on [$symbol$branch(:$remote_branch)]($style) ",
"ignore_branches": [], "ignore_branches": [],
"only_attached": false, "only_attached": false,
"style": "bold purple", "style": "bold purple",
@ -1486,7 +1486,7 @@
"properties": { "properties": {
"format": { "format": {
"description": "The format for the module.", "description": "The format for the module.",
"default": "on [$symbol($profile )(\\($region\\) )(\\[$duration\\])]($style)", "default": "on [$symbol($profile )(\\($region\\) )(\\[$duration\\] )]($style)",
"type": "string" "type": "string"
}, },
"symbol": { "symbol": {
@ -2618,7 +2618,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"format": { "format": {
"default": "on [$symbol$branch]($style)(:[$remote]($style)) ", "default": "on [$symbol$branch(:$remote_branch)]($style) ",
"type": "string" "type": "string"
}, },
"symbol": { "symbol": {

View File

@ -284,7 +284,7 @@ jobs:
continue-on-error: true continue-on-error: true
steps: steps:
- name: Merge | Merge Crowdin PR - name: Merge | Merge Crowdin PR
run: gh pr merge --squash i18n_master run: gh pr merge i18n_master --squash --repo=starship/starship
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,5 +1,23 @@
# Changelog # 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) ### [1.6.2](https://github.com/starship/starship/compare/v1.6.1...v1.6.2) (2022-04-15)

48
Cargo.lock generated
View File

@ -272,9 +272,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "3.1.10" version = "3.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3124f3f75ce09e22d1410043e1e24f2ecc44fad3afe4f08408f1f7663d68da2b" checksum = "7c167e37342afc5f33fd87bbc870cedd020d2a6dffa05d45ccd9241fbdd146db"
dependencies = [ dependencies = [
"atty", "atty",
"bitflags", "bitflags",
@ -1055,6 +1055,17 @@ dependencies = [
"memoffset", "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]] [[package]]
name = "nom" name = "nom"
version = "5.1.2" version = "5.1.2"
@ -1898,7 +1909,7 @@ dependencies = [
[[package]] [[package]]
name = "starship" name = "starship"
version = "1.6.2" version = "1.6.3"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"byte-unit", "byte-unit",
@ -1914,7 +1925,7 @@ dependencies = [
"local_ipaddress", "local_ipaddress",
"log", "log",
"mockall", "mockall",
"nix", "nix 0.24.1",
"notify-rust", "notify-rust",
"once_cell", "once_cell",
"open", "open",
@ -1947,7 +1958,7 @@ dependencies = [
"urlencoding", "urlencoding",
"versions", "versions",
"which", "which",
"winapi", "windows 0.35.0",
"winres", "winres",
"yaml-rust", "yaml-rust",
] ]
@ -1963,7 +1974,7 @@ dependencies = [
"lazycell", "lazycell",
"libc", "libc",
"mach", "mach",
"nix", "nix 0.23.1",
"num-traits", "num-traits",
"uom", "uom",
"winapi", "winapi",
@ -2310,9 +2321,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]] [[package]]
name = "versions" name = "versions"
version = "4.0.0" version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5276c151793dde1cc57e08123f36f96e662a9f2532060c677612bf0e2c604d4" checksum = "ee97e1d97bd593fb513912a07691b742361b3dd64ad56f2c694ea2dbfe0665d3"
dependencies = [ dependencies = [
"itertools", "itertools",
"nom 7.1.1", "nom 7.1.1",
@ -2320,9 +2331,9 @@ dependencies = [
[[package]] [[package]]
name = "vtparse" name = "vtparse"
version = "0.6.0" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f41c9314c4dde1f43dd0c46c67bb5ae73850ce11eebaf7d8b912e178bda5401" checksum = "36ce903972602c84dd48f488cdce39edcba03a93b7ca67b146ae862568f48c5c"
dependencies = [ dependencies = [
"utf8parse", "utf8parse",
] ]
@ -2408,6 +2419,19 @@ dependencies = [
"windows_x86_64_msvc 0.24.0", "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]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.35.0" version = "0.35.0"
@ -2491,7 +2515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "007a0353840b23e0c6dc73e5b962ff58ed7f6bc9ceff3ce7fe6fbad8d496edf4" checksum = "007a0353840b23e0c6dc73e5b962ff58ed7f6bc9ceff3ce7fe6fbad8d496edf4"
dependencies = [ dependencies = [
"strum", "strum",
"windows", "windows 0.24.0",
"xml-rs", "xml-rs",
] ]
@ -2533,7 +2557,7 @@ dependencies = [
"futures-util", "futures-util",
"hex", "hex",
"lazy_static", "lazy_static",
"nix", "nix 0.23.1",
"once_cell", "once_cell",
"ordered-stream", "ordered-stream",
"rand 0.8.5", "rand 0.8.5",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "starship" name = "starship"
version = "1.6.2" version = "1.6.3"
authors = ["Starship Contributors"] authors = ["Starship Contributors"]
build = "build.rs" build = "build.rs"
categories = ["command-line-utilities"] categories = ["command-line-utilities"]
@ -39,7 +39,7 @@ notify = ["notify-rust"]
ansi_term = "0.12.1" ansi_term = "0.12.1"
byte-unit = "4.0.14" byte-unit = "4.0.14"
chrono = "0.4.19" 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" clap_complete = "3.1.2"
dirs-next = "2.0.0" dirs-next = "2.0.0"
dunce = "1.0.2" dunce = "1.0.2"
@ -78,7 +78,7 @@ toml_edit = "0.14.2"
unicode-segmentation = "1.9.0" unicode-segmentation = "1.9.0"
unicode-width = "0.1.9" unicode-width = "0.1.9"
urlencoding = "2.1.0" urlencoding = "2.1.0"
versions = "4.0.0" versions = "4.1.0"
which = "4.2.5" which = "4.2.5"
yaml-rust = "0.4.5" yaml-rust = "0.4.5"
@ -92,11 +92,20 @@ optional = true
features = ["preserve_order", "indexmap"] features = ["preserve_order", "indexmap"]
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9", features = ["winuser", "securitybaseapi", "processthreadsapi", "handleapi", "impl-default"] }
deelevate = "0.2.0" 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] [target.'cfg(not(windows))'.dependencies]
nix = "0.23.1" nix = { version = "0.24.1", default-features = false, features = ["feature", "fs", "user"] }
[build-dependencies] [build-dependencies]
shadow-rs = "0.11.0" shadow-rs = "0.11.0"

View File

@ -111,7 +111,9 @@ description: Starship is the minimal, blazing fast, and extremely customizable p
#### Elvish #### Elvish
::: warning ::: warning
Only elvish v0.18 or higher is supported. Only elvish v0.18 or higher is supported.
::: :::
Add the following to the end of `~/.elvish/rc.elv`: 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 #### Nushell
::: warning ::: warning
This will change in the future. This will change in the future.
Only Nushell v0.60+ is supported. Only Nushell v0.60+ is supported.
::: :::
Run the following: Run the following:
```sh ```sh
mkdir ~/.cache/starship mkdir ~/.cache/starship

View File

@ -307,8 +307,8 @@ date is read from the `AWSUME_EXPIRATION` env var.
### Options ### Options
| Option | Default | Description | | Option | Default | Description |
| ------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | ------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `format` | `'on [$symbol($profile )(\($region\) )(\[$duration\])]($style)'` | The format for the module. | | `format` | `'on [$symbol($profile )(\($region\) )(\[$duration\] )]($style)'` | The format for the module. |
| `symbol` | `"☁️ "` | The symbol used before displaying the current AWS profile. | | `symbol` | `"☁️ "` | The symbol used before displaying the current AWS profile. |
| `region_aliases` | | Table of region aliases to display in addition to the AWS name. | | `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. | | `profile_aliases` | | Table of profile aliases to display in addition to the AWS name. |
@ -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 - The `variable` configuration option is not defined, but the `default` configuration option is
::: tip ::: tip
Multiple environmental variables can be displayed by using a `.`. (see example) 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. If the `variable` configuration option is not set, the module will display value of variable under the name of text after the `.` character.
@ -1419,9 +1420,9 @@ The `git_branch` module shows the active branch of the repo in your current dire
### Options ### Options
| Option | Default | Description | | Option | Default | Description |
| -------------------- | -------------------------------- | ---------------------------------------------------------------------------------------- | | -------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `always_show_remote` | `false` | Shows the remote tracking branch name, even if it is equal to the local branch name. | | `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. | | `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. | | `symbol` | `" "` | A format string representing the symbol of git branch. |
| `style` | `"bold purple"` | The style for the module. | | `style` | `"bold purple"` | The style for the module. |
| `truncation_length` | `2^63 - 1` | Truncates a git branch to `N` graphemes. | | `truncation_length` | `2^63 - 1` | Truncates a git branch to `N` graphemes. |

View File

@ -50,7 +50,7 @@ pub struct AwsConfig<'a> {
impl<'a> Default for AwsConfig<'a> { impl<'a> Default for AwsConfig<'a> {
fn default() -> Self { fn default() -> Self {
AwsConfig { AwsConfig {
format: "on [$symbol($profile )(\\($region\\) )(\\[$duration\\])]($style)", format: "on [$symbol($profile )(\\($region\\) )(\\[$duration\\] )]($style)",
symbol: "☁️ ", symbol: "☁️ ",
style: "bold yellow", style: "bold yellow",
disabled: false, disabled: false,

View File

@ -18,7 +18,7 @@ pub struct GitBranchConfig<'a> {
impl<'a> Default for GitBranchConfig<'a> { impl<'a> Default for GitBranchConfig<'a> {
fn default() -> Self { fn default() -> Self {
GitBranchConfig { GitBranchConfig {
format: "on [$symbol$branch]($style)(:[$remote]($style)) ", format: "on [$symbol$branch(:$remote_branch)]($style) ",
symbol: "", symbol: "",
style: "bold purple", style: "bold purple",
truncation_length: std::i64::MAX, truncation_length: std::i64::MAX,

View File

@ -1,7 +1,7 @@
use crate::config::{ModuleConfig, StarshipConfig}; use crate::config::{ModuleConfig, StarshipConfig};
use crate::configs::StarshipRootConfig; use crate::configs::StarshipRootConfig;
use crate::module::Module; 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::modules;
use crate::utils::{self, home_dir}; use crate::utils::{self, home_dir};
@ -342,6 +342,18 @@ impl<'a> Context<'a> {
.iter() .iter()
.find_map(|attempt| self.exec_cmd(attempt[0], &attempt[1..])) .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)] #[derive(Debug)]

View File

@ -633,7 +633,7 @@ credential_process = /opt/bin/awscreds-retriever
"on {}", "on {}",
Color::Yellow Color::Yellow
.bold() .bold()
.paint("☁️ astronauts (ap-northeast-2) [30m]") .paint("☁️ astronauts (ap-northeast-2) [30m] ")
)); ));
assert_eq!(expected, actual); assert_eq!(expected, actual);
@ -679,7 +679,7 @@ expiration={}
// on shared runners may delay it. Allow for up to 2 seconds of delay. // on shared runners may delay it. Allow for up to 2 seconds of delay.
let possible_values = ["30m", "29m59s", "29m58s"]; let possible_values = ["30m", "29m59s", "29m58s"];
let possible_values = possible_values.map(|duration| { 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!( Some(format!(
"on {}", "on {}",
Color::Yellow.bold().paint(segment_colored) Color::Yellow.bold().paint(segment_colored)
@ -736,7 +736,7 @@ expiration={}
"on {}", "on {}",
Color::Yellow Color::Yellow
.bold() .bold()
.paint(format!("☁️ astronauts (ap-northeast-2) [{}]", symbol)) .paint(format!("☁️ astronauts (ap-northeast-2) [{}] ", symbol))
)); ));
assert_eq!(expected, actual); assert_eq!(expected, actual);

View File

@ -396,6 +396,42 @@ mod tests {
repo_dir.close() 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` // 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 // 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` // git2 does not care about our mocking and when we set the real `GIT_DIR`

View File

@ -372,7 +372,12 @@ fn git_status_wsl(context: &Context, conf: &GitStatusConfig) -> Option<String> {
// Ensure this is WSL // Ensure this is WSL
// This is lowercase in WSL1 and uppercase in WSL2, just skip the first letter // 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; return None;
} }

View File

@ -2,7 +2,6 @@ use super::{Context, Module, ModuleConfig};
use crate::configs::haskell::HaskellConfig; use crate::configs::haskell::HaskellConfig;
use crate::formatter::StringFormatter; use crate::formatter::StringFormatter;
use crate::utils;
/// Creates a module with the current Haskell version /// Creates a module with the current Haskell version
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { 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) { if !is_stack_project(context) {
return None; 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 yaml = yaml_rust::YamlLoader::load_from_str(&file_contents).ok()?;
let version = yaml.first()?["resolver"] let version = yaml.first()?["resolver"]
.as_str() .as_str()

View File

@ -2,7 +2,6 @@ use super::{Context, Module, ModuleConfig};
use crate::configs::nodejs::NodejsConfig; use crate::configs::nodejs::NodejsConfig;
use crate::formatter::{StringFormatter, VersionFormatter}; use crate::formatter::{StringFormatter, VersionFormatter};
use crate::utils;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
@ -10,7 +9,6 @@ use semver::Version;
use semver::VersionReq; use semver::VersionReq;
use serde_json as json; use serde_json as json;
use std::ops::Deref; use std::ops::Deref;
use std::path::Path;
/// Creates a module with the current Node.js version /// Creates a module with the current Node.js version
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { 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 { .map_style(|variable| match variable {
"style" => { "style" => {
let engines_version = get_engines_version(&context.current_dir); let engines_version = get_engines_version(context);
let in_engines_range = let in_engines_range =
check_engines_version(nodejs_version.deref().as_ref()?, engines_version); check_engines_version(nodejs_version.deref().as_ref()?, engines_version);
if in_engines_range { if in_engines_range {
@ -87,8 +85,8 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
Some(module) Some(module)
} }
fn get_engines_version(base_dir: &Path) -> Option<String> { fn get_engines_version(context: &Context) -> Option<String> {
let json_str = utils::read_file(base_dir.join("package.json")).ok()?; let json_str = context.read_file_from_pwd("package.json")?;
let package_json: json::Value = json::from_str(&json_str).ok()?; let package_json: json::Value = json::from_str(&json_str).ok()?;
let raw_version = package_json.get("engines")?.get("node")?.as_str()?; let raw_version = package_json.get("engines")?.get("node")?.as_str()?;
Some(raw_version.to_string()) Some(raw_version.to_string())

View File

@ -1,7 +1,6 @@
use super::{Context, Module, ModuleConfig}; use super::{Context, Module, ModuleConfig};
use crate::configs::package::PackageConfig; use crate::configs::package::PackageConfig;
use crate::formatter::{StringFormatter, VersionFormatter}; use crate::formatter::{StringFormatter, VersionFormatter};
use crate::utils;
use ini::Ini; use ini::Ini;
use quick_xml::events::Event as QXEvent; 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> { 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()?; let package_json: json::Value = json::from_str(&file_contents).ok()?;
if !config.display_private 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> { 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 poetry_toml: toml::Value = toml::from_str(&file_contents).ok()?;
let raw_version = poetry_toml let raw_version = poetry_toml
.get("tool")? .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> { 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 ini = Ini::load_from_str(&file_contents).ok()?;
let raw_version = ini.get_from(Some("metadata"), "version")?; 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> { 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 re = Regex::new(r#"(?m)^version ['"](?P<version>[^'"]+)['"]$"#).unwrap();
let caps = re.captures(&file_contents)?; 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> { 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 composer_json: json::Value = json::from_str(&file_contents).ok()?;
let raw_version = composer_json.get("version")?.as_str()?; 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> { 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 project_toml: toml::Value = toml::from_str(&file_contents).ok()?;
let raw_version = project_toml.get("version")?.as_str()?; 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> { 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 yaml = yaml_rust::YamlLoader::load_from_str(&file_contents).ok()?;
let version = yaml.first()?["version"].as_str()?; 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> { 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 re = Regex::new(r#"(?m)version: "(?P<version>[^"]+)""#).unwrap();
let caps = re.captures(&file_contents)?; 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> { fn get_maven_version(context: &Context, config: &PackageConfig) -> Option<String> {
let pom_file = utils::read_file(context.current_dir.join("pom.xml")).ok()?; let file_contents = context.read_file_from_pwd("pom.xml")?;
let mut reader = QXReader::from_str(&pom_file); let mut reader = QXReader::from_str(&file_contents);
reader.trim_text(true); reader.trim_text(true);
let mut buf = vec![]; 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> { fn get_meson_version(context: &Context, config: &PackageConfig) -> Option<String> {
let file_contents = utils::read_file(context.current_dir.join("meson.build")) let file_contents = context
.ok()? .read_file_from_pwd("meson.build")?
.split_ascii_whitespace() .split_ascii_whitespace()
.collect::<String>(); .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> { 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 re = Regex::new(r"(?m)^\s*version\s*:\s*'(?P<version>[^']+)'").unwrap();
let caps = re.captures(&file_contents)?; let caps = re.captures(&file_contents)?;
format_version(&caps["version"], config.version_format) format_version(&caps["version"], config.version_format)
} }
fn get_vpkg_version(context: &Context, config: &PackageConfig) -> Option<String> { 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 vpkg_json: json::Value = json::from_str(&file_contents).ok()?;
let raw_version = vpkg_json.get("version")?.as_str()?; 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> { 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 re = Regex::new(r"(?m)^(.*/)*\s*version\s*:=\s*.(?P<version>[\d\.]+)").unwrap();
let caps = re.captures(&file_contents)?; let caps = re.captures(&file_contents)?;
format_version(&caps["version"], config.version_format) format_version(&caps["version"], config.version_format)
} }
fn get_cargo_version(context: &Context, config: &PackageConfig) -> Option<String> { 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 cargo_toml: toml::Value = toml::from_str(&file_contents).ok()?;
let raw_version = cargo_toml.get("package")?.get("version")?.as_str()?; 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> { 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 data = yaml_rust::YamlLoader::load_from_str(&file_contents).ok()?;
let raw_version = data.first()?["version"].as_str()?; 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> { 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 data = yaml_rust::YamlLoader::load_from_str(&file_contents).ok()?;
let raw_version = data.first()?["version"].as_str()?; let raw_version = data.first()?["version"].as_str()?;

View File

@ -13,8 +13,9 @@ use std::path::Path;
/// 2a) (not implemented on macOS) one of the supplementary groups of the current user is the /// 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 /// directory group owner and whether it has write access
/// 3) 'others' part of the access mask has the 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> { pub fn is_write_allowed(folder_path: &Path) -> Result<bool, String> {
let meta = fs::metadata(folder_path).map_err(|_| "Unable to stat() directory")?; let meta =
fs::metadata(folder_path).map_err(|e| format!("Unable to stat() directory: {e:?}"))?;
let perms = meta.permissions().mode(); let perms = meta.permissions().mode();
let euid = Uid::effective(); let euid = Uid::effective();
@ -54,9 +55,9 @@ mod tests {
#[ignore] #[ignore]
fn read_only_test() { fn read_only_test() {
assert_eq!(is_write_allowed(Path::new("/etc")), Ok(false)); assert_eq!(is_write_allowed(Path::new("/etc")), Ok(false));
assert_eq!( assert!(match is_write_allowed(Path::new("/i_dont_exist")) {
is_write_allowed(Path::new("/i_dont_exist")), Ok(_) => false,
Err("Unable to stat() directory") Err(e) => e.starts_with("Unable to stat() directory"),
); });
} }
} }

View File

@ -1,29 +1,32 @@
extern crate winapi; use std::{ffi::c_void, mem, os::windows::ffi::OsStrExt, path::Path};
use std::mem; use windows::{
use std::os::windows::ffi::OsStrExt; core::PCWSTR,
use std::path::Path; Win32::{
Foundation::{CloseHandle, ERROR_INSUFFICIENT_BUFFER, HANDLE},
use winapi::shared::minwindef::{BOOL, DWORD}; Security::{
use winapi::um::handleapi; AccessCheck, DuplicateToken, GetFileSecurityW, MapGenericMask, SecurityImpersonation,
use winapi::um::processthreadsapi; DACL_SECURITY_INFORMATION, GENERIC_MAPPING, GROUP_SECURITY_INFORMATION,
use winapi::um::securitybaseapi; OWNER_SECURITY_INFORMATION, PRIVILEGE_SET, PSECURITY_DESCRIPTOR, TOKEN_DUPLICATE,
use winapi::um::winnt::{ TOKEN_IMPERSONATE, TOKEN_QUERY, TOKEN_READ_CONTROL,
SecurityImpersonation, BOOLEAN, DACL_SECURITY_INFORMATION, FILE_ALL_ACCESS, },
FILE_GENERIC_EXECUTE, FILE_GENERIC_READ, FILE_GENERIC_WRITE, GENERIC_MAPPING, Storage::FileSystem::{
GROUP_SECURITY_INFORMATION, HANDLE, LPCWSTR, OWNER_SECURITY_INFORMATION, PRIVILEGE_SET, FILE_ALL_ACCESS, FILE_GENERIC_EXECUTE, FILE_GENERIC_READ, FILE_GENERIC_WRITE,
STANDARD_RIGHTS_READ, TOKEN_DUPLICATE, TOKEN_IMPERSONATE, TOKEN_QUERY, },
System::Threading::{GetCurrentProcess, OpenProcessToken},
UI::Shell::PathIsNetworkPathW,
},
}; };
/// Checks if the current user has write access right to the `folder_path` /// 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 /// First, the function extracts DACL from the given directory and then calls `AccessCheck` against
/// the current process access token and directory's security descriptor. /// the current process access token and directory's security descriptor.
/// Does not work for network drives and always returns true /// Does not work for network drives and always returns true
pub fn is_write_allowed(folder_path: &Path) -> std::result::Result<bool, &'static str> { pub fn is_write_allowed(folder_path: &Path) -> std::result::Result<bool, String> {
let folder_name: Vec<u16> = folder_path.as_os_str().encode_wide().chain([0]).collect(); 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!( log::info!(
"Directory '{:?}' is a network drive, unable to check write permissions. See #1506 for details", "Directory '{:?}' is a network drive, unable to check write permissions. See #1506 for details",
folder_path folder_path
@ -31,79 +34,84 @@ pub fn is_write_allowed(folder_path: &Path) -> std::result::Result<bool, &'stati
return Ok(true); return Ok(true);
} }
let mut length: DWORD = 0; let mut length = 0;
let rc = unsafe { let rc = unsafe {
securitybaseapi::GetFileSecurityW( GetFileSecurityW(
folder_name.as_ptr(), wpath,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION).0,
std::ptr::null_mut(), PSECURITY_DESCRIPTOR::default(),
0, 0,
&mut length, &mut length,
) )
}; };
if rc != 0 {
return Err( // expect ERROR_INSUFFICIENT_BUFFER
"GetFileSecurityW returned non-zero when asked for the security descriptor size", 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 { let rc = unsafe {
securitybaseapi::GetFileSecurityW( GetFileSecurityW(
folder_name.as_ptr(), wpath,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION).0,
buf.as_mut_ptr().cast::<std::ffi::c_void>(), psecurity_descriptor,
length, length,
&mut length, &mut length,
) )
}; };
if rc != 1 { if let Err(e) = rc.ok() {
return Err("GetFileSecurityW failed to retrieve the security descriptor"); 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 { let rc = unsafe {
processthreadsapi::OpenProcessToken( OpenProcessToken(
processthreadsapi::GetCurrentProcess(), GetCurrentProcess(),
TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ, TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_READ_CONTROL,
&mut token, &mut token,
) )
}; };
if rc != 1 { if let Err(e) = rc.ok() {
return Err("OpenProcessToken failed to retrieve current process' security token"); return Err(format!(
"OpenProcessToken failed to retrieve current process' security token: {e:?}"
));
} }
let mut impersonated_token: HANDLE = 0 as HANDLE; let mut impersonated_token = HANDLE::default();
let rc = unsafe { let rc = unsafe { DuplicateToken(token, SecurityImpersonation, &mut impersonated_token) };
securitybaseapi::DuplicateToken(token, SecurityImpersonation, &mut impersonated_token)
}; if let Err(e) = rc.ok() {
if rc != 1 { unsafe { CloseHandle(token) };
unsafe { handleapi::CloseHandle(token) }; return Err(format!("DuplicateToken failed: {e:?}"));
return Err("DuplicateToken failed");
} }
let mut mapping: GENERIC_MAPPING = GENERIC_MAPPING { let mapping = GENERIC_MAPPING {
GenericRead: FILE_GENERIC_READ, GenericRead: FILE_GENERIC_READ.0,
GenericWrite: FILE_GENERIC_WRITE, GenericWrite: FILE_GENERIC_WRITE.0,
GenericExecute: FILE_GENERIC_EXECUTE, GenericExecute: FILE_GENERIC_EXECUTE.0,
GenericAll: FILE_ALL_ACCESS, GenericAll: FILE_ALL_ACCESS.0,
}; };
let mut priviledges: PRIVILEGE_SET = PRIVILEGE_SET::default(); let mut priviledges: PRIVILEGE_SET = PRIVILEGE_SET::default();
let mut priv_size = mem::size_of::<PRIVILEGE_SET>() as DWORD; let mut priv_size = mem::size_of::<PRIVILEGE_SET>() as _;
let mut granted_access: DWORD = 0; let mut granted_access = 0;
let mut access_rights: DWORD = FILE_GENERIC_WRITE; let mut access_rights = FILE_GENERIC_WRITE;
let mut result: BOOL = 0; let mut result = 0;
unsafe { securitybaseapi::MapGenericMask(&mut access_rights, &mut mapping) }; unsafe { MapGenericMask(&mut access_rights.0, &mapping) };
let rc = unsafe { let rc = unsafe {
securitybaseapi::AccessCheck( AccessCheck(
buf.as_mut_ptr().cast::<std::ffi::c_void>(), psecurity_descriptor,
impersonated_token, impersonated_token,
access_rights, access_rights.0,
&mut mapping, &mapping,
&mut priviledges, &mut priviledges,
&mut priv_size, &mut priv_size,
&mut granted_access, &mut granted_access,
@ -111,22 +119,13 @@ pub fn is_write_allowed(folder_path: &Path) -> std::result::Result<bool, &'stati
) )
}; };
unsafe { unsafe {
handleapi::CloseHandle(impersonated_token); CloseHandle(impersonated_token);
handleapi::CloseHandle(token); CloseHandle(token);
} }
if rc != 1 { if let Err(e) = rc.ok() {
return Err("AccessCheck failed"); return Err(format!("AccessCheck failed: {e:?}"));
} }
Ok(result != 0) 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 }
}