mirror of
https://github.com/Llewellynvdm/starship.git
synced 2024-11-11 07:40:57 +00:00
feat: Add dotnet module (#416)
Adds a .NET module, which preferentially parses local/git files to get the .NET version.
This commit is contained in:
parent
f14392b5ea
commit
6621e4c859
9
.github/workflows/continuous-integration.yml
vendored
9
.github/workflows/continuous-integration.yml
vendored
@ -71,6 +71,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: "3.6.9"
|
python-version: "3.6.9"
|
||||||
|
|
||||||
|
# Install dotnet at a fixed version
|
||||||
|
- uses: actions/setup-dotnet@master
|
||||||
|
with:
|
||||||
|
dotnet-version: "2.2.402"
|
||||||
|
|
||||||
# Run the ignored tests that expect the above setup
|
# Run the ignored tests that expect the above setup
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@master
|
||||||
- name: Run all tests
|
- name: Run all tests
|
||||||
@ -88,6 +93,8 @@ jobs:
|
|||||||
- name: Fix file permissions
|
- name: Fix file permissions
|
||||||
run: chmod -R a+w .
|
run: chmod -R a+w .
|
||||||
- name: Build the Docker image
|
- name: Build the Docker image
|
||||||
run: docker build -f tests/Dockerfile --tag starshipcommand/starship-test --cache-from starshipcommand/starship-test .
|
run:
|
||||||
|
docker build -f tests/Dockerfile --tag starshipcommand/starship-test --cache-from
|
||||||
|
starshipcommand/starship-test .
|
||||||
- name: Run tests in Docker
|
- name: Run tests in Docker
|
||||||
run: docker run --rm -v $(pwd):/src/starship starshipcommand/starship-test
|
run: docker run --rm -v $(pwd):/src/starship starshipcommand/starship-test
|
||||||
|
@ -91,12 +91,13 @@ prompt_order = [
|
|||||||
"git_state",
|
"git_state",
|
||||||
"git_status",
|
"git_status",
|
||||||
"package",
|
"package",
|
||||||
"nodejs",
|
"dotnet",
|
||||||
"ruby",
|
|
||||||
"rust",
|
|
||||||
"python",
|
|
||||||
"golang",
|
"golang",
|
||||||
"java",
|
"java",
|
||||||
|
"nodejs",
|
||||||
|
"python",
|
||||||
|
"ruby",
|
||||||
|
"rust",
|
||||||
"nix_shell",
|
"nix_shell",
|
||||||
"memory_usage",
|
"memory_usage",
|
||||||
"aws",
|
"aws",
|
||||||
@ -317,6 +318,41 @@ it would have been `nixpkgs/pkgs`.
|
|||||||
truncation_length = 8
|
truncation_length = 8
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Dotnet
|
||||||
|
|
||||||
|
The `dotnet` module shows the relevant version of the .NET Core SDK for the current directory. If
|
||||||
|
the SDK has been pinned in the current directory, the pinned version is shown. Otherwise the module
|
||||||
|
shows the latest installed version of the SDK.
|
||||||
|
|
||||||
|
This module will only be shown in your prompt when one of the following files are present in the
|
||||||
|
current directory: `global.json`, `project.json`, `*.sln`, `*.csproj`, `*.fsproj`, `*.xproj`. You'll
|
||||||
|
also need the .NET Core command-line tools installed in order to use it correctly.
|
||||||
|
|
||||||
|
Internally, this module uses its own mechanism for version detection. Typically it is twice as fast
|
||||||
|
as running `dotnet --version`, but it may show an incorrect version if your .NET project has an
|
||||||
|
unusual directory layout. If accuracy is more important than speed, you can disable the mechanism by
|
||||||
|
setting `heuristic = false` in the module options.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
| ----------- | ------------- | -------------------------------------------------------- |
|
||||||
|
| `symbol` | `"•NET "` | The symbol used before displaying the version of dotnet. |
|
||||||
|
| `style` | `"bold blue"` | The style for the module. |
|
||||||
|
| `heuristic` | `true` | Use faster version detection to keep starship snappy. |
|
||||||
|
| `disabled` | `false` | Disables the `dotnet` module. |
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# ~/.config/starship.toml
|
||||||
|
|
||||||
|
[dotnet]
|
||||||
|
symbol = "🥅 "
|
||||||
|
style = "green"
|
||||||
|
heuristic = false
|
||||||
|
```
|
||||||
|
|
||||||
## Environment Variable
|
## Environment Variable
|
||||||
|
|
||||||
The `env_var` module displays the current value of a selected environment variable.
|
The `env_var` module displays the current value of a selected environment variable.
|
||||||
|
31
starship/src/configs/dotnet.rs
Normal file
31
starship/src/configs/dotnet.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig};
|
||||||
|
|
||||||
|
use ansi_term::{Color, Style};
|
||||||
|
use starship_module_config_derive::ModuleConfig;
|
||||||
|
|
||||||
|
#[derive(Clone, ModuleConfig)]
|
||||||
|
pub struct DotnetConfig<'a> {
|
||||||
|
pub symbol: SegmentConfig<'a>,
|
||||||
|
pub version: SegmentConfig<'a>,
|
||||||
|
pub style: Style,
|
||||||
|
pub heuristic: bool,
|
||||||
|
pub disabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RootModuleConfig<'a> for DotnetConfig<'a> {
|
||||||
|
fn new() -> Self {
|
||||||
|
DotnetConfig {
|
||||||
|
symbol: SegmentConfig {
|
||||||
|
value: "•NET ",
|
||||||
|
style: None,
|
||||||
|
},
|
||||||
|
version: SegmentConfig {
|
||||||
|
value: "",
|
||||||
|
style: None,
|
||||||
|
},
|
||||||
|
style: Color::Blue.bold(),
|
||||||
|
heuristic: true,
|
||||||
|
disabled: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
pub mod battery;
|
pub mod battery;
|
||||||
|
pub mod dotnet;
|
||||||
pub mod rust;
|
pub mod rust;
|
||||||
|
|
||||||
use crate::config::{ModuleConfig, RootModuleConfig};
|
use crate::config::{ModuleConfig, RootModuleConfig};
|
||||||
@ -26,12 +27,16 @@ impl<'a> RootModuleConfig<'a> for StarshipRootConfig<'a> {
|
|||||||
"git_state",
|
"git_state",
|
||||||
"git_status",
|
"git_status",
|
||||||
"package",
|
"package",
|
||||||
"nodejs",
|
// ↓ Toolchain version modules ↓
|
||||||
"ruby",
|
// (Let's keep these sorted alphabetically)
|
||||||
"rust",
|
"dotnet",
|
||||||
"python",
|
|
||||||
"golang",
|
"golang",
|
||||||
"java",
|
"java",
|
||||||
|
"nodejs",
|
||||||
|
"python",
|
||||||
|
"ruby",
|
||||||
|
"rust",
|
||||||
|
// ↑ Toolchain version modules ↑
|
||||||
"nix_shell",
|
"nix_shell",
|
||||||
"memory_usage",
|
"memory_usage",
|
||||||
"aws",
|
"aws",
|
||||||
|
@ -5,6 +5,8 @@ use ansi_term::{ANSIString, ANSIStrings};
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
// List of all modules
|
// List of all modules
|
||||||
|
// Keep these ordered alphabetically.
|
||||||
|
// Default ordering is handled in configs/mod.rs
|
||||||
pub const ALL_MODULES: &[&str] = &[
|
pub const ALL_MODULES: &[&str] = &[
|
||||||
"aws",
|
"aws",
|
||||||
#[cfg(feature = "battery")]
|
#[cfg(feature = "battery")]
|
||||||
@ -12,6 +14,7 @@ pub const ALL_MODULES: &[&str] = &[
|
|||||||
"character",
|
"character",
|
||||||
"cmd_duration",
|
"cmd_duration",
|
||||||
"directory",
|
"directory",
|
||||||
|
"dotnet",
|
||||||
"env_var",
|
"env_var",
|
||||||
"git_branch",
|
"git_branch",
|
||||||
"git_state",
|
"git_state",
|
||||||
|
@ -122,7 +122,7 @@ fn contract_path(full_path: &Path, top_level_path: &Path, top_level_replacement:
|
|||||||
/// On non-Windows OS, does nothing
|
/// On non-Windows OS, does nothing
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn replace_c_dir(path: String) -> String {
|
fn replace_c_dir(path: String) -> String {
|
||||||
return path.replace("C:/", "/c");
|
path.replace("C:/", "/c")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replaces "C://" with "/c/" within a Windows path
|
/// Replaces "C://" with "/c/" within a Windows path
|
||||||
|
314
starship/src/modules/dotnet.rs
Normal file
314
starship/src/modules/dotnet.rs
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::iter::Iterator;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
use super::{Context, Module};
|
||||||
|
use crate::config::RootModuleConfig;
|
||||||
|
use crate::configs::dotnet::DotnetConfig;
|
||||||
|
|
||||||
|
type JValue = serde_json::Value;
|
||||||
|
|
||||||
|
const GLOBAL_JSON_FILE: &str = "global.json";
|
||||||
|
const PROJECT_JSON_FILE: &str = "project.json";
|
||||||
|
|
||||||
|
/// A module which shows the latest (or pinned) version of the dotnet SDK
|
||||||
|
///
|
||||||
|
/// Will display if any of the following files are present in
|
||||||
|
/// the current directory:
|
||||||
|
/// global.json, project.json, *.sln, *.csproj, *.fsproj, *.xproj
|
||||||
|
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||||
|
let dotnet_files = get_local_dotnet_files(context).ok()?;
|
||||||
|
if dotnet_files.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut module = context.new_module("dotnet");
|
||||||
|
let config = DotnetConfig::try_load(module.config);
|
||||||
|
|
||||||
|
// Internally, this module uses its own mechanism for version detection.
|
||||||
|
// Typically it is twice as fast as running `dotnet --version`.
|
||||||
|
let enable_heuristic = config.heuristic;
|
||||||
|
let version = if enable_heuristic {
|
||||||
|
let repo_root = context
|
||||||
|
.get_repo()
|
||||||
|
.ok()
|
||||||
|
.and_then(|r| r.root.as_ref().map(PathBuf::as_path));
|
||||||
|
estimate_dotnet_version(&dotnet_files, &context.current_dir, repo_root)?
|
||||||
|
} else {
|
||||||
|
get_version_from_cli()?
|
||||||
|
};
|
||||||
|
|
||||||
|
module.set_style(config.style);
|
||||||
|
module.create_segment("symbol", &config.symbol);
|
||||||
|
module.create_segment("version", &config.version.with_value(&version.0));
|
||||||
|
|
||||||
|
Some(module)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn estimate_dotnet_version<'a>(
|
||||||
|
files: &[DotNetFile<'a>],
|
||||||
|
current_dir: &Path,
|
||||||
|
repo_root: Option<&Path>,
|
||||||
|
) -> Option<Version> {
|
||||||
|
let get_file_of_type = |t: FileType| files.iter().find(|f| f.file_type == t);
|
||||||
|
|
||||||
|
// It's important to check for a global.json or a solution file first,
|
||||||
|
// but otherwise we can take any relevant file. We'll take whichever is first.
|
||||||
|
let relevant_file = get_file_of_type(FileType::GlobalJson)
|
||||||
|
.or_else(|| get_file_of_type(FileType::SolutionFile))
|
||||||
|
.or_else(|| files.iter().next())?;
|
||||||
|
|
||||||
|
match relevant_file.file_type {
|
||||||
|
FileType::GlobalJson => {
|
||||||
|
get_pinned_sdk_version_from_file(relevant_file.path).or_else(get_latest_sdk_from_cli)
|
||||||
|
}
|
||||||
|
FileType::SolutionFile => {
|
||||||
|
// With this heuristic, we'll assume that a "global.json" won't
|
||||||
|
// be found in any directory above the solution file.
|
||||||
|
get_latest_sdk_from_cli()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// If we see a dotnet project, we'll check a small number of neighboring
|
||||||
|
// directories to see if we can find a global.json. Otherwise, assume the
|
||||||
|
// latest SDK is in use.
|
||||||
|
try_find_nearby_global_json(current_dir, repo_root).or_else(get_latest_sdk_from_cli)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Looks for a `global.json` which may exist in one of the parent directories of the current path.
|
||||||
|
/// If there is one present, and it contains valid version pinning information, then return that version.
|
||||||
|
///
|
||||||
|
/// The following places are scanned:
|
||||||
|
/// - The parent of the current directory
|
||||||
|
/// (Unless there is a git repository, and the parent is above the root of that repository)
|
||||||
|
/// - The root of the git repository
|
||||||
|
/// (If there is one)
|
||||||
|
fn try_find_nearby_global_json(current_dir: &Path, repo_root: Option<&Path>) -> Option<Version> {
|
||||||
|
let current_dir_is_repo_root = repo_root.map(|r| r == current_dir).unwrap_or(false);
|
||||||
|
let parent_dir = if current_dir_is_repo_root {
|
||||||
|
// Don't scan the parent directory if it's above the root of a git repository
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
current_dir.parent()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check the parent directory, or otherwise the repository root, for a global.json
|
||||||
|
let mut check_dirs = parent_dir
|
||||||
|
.iter()
|
||||||
|
.chain(repo_root.iter())
|
||||||
|
.copied() // Copies the reference, not the Path itself
|
||||||
|
.collect::<Vec<&Path>>();
|
||||||
|
|
||||||
|
// The parent directory and repository root may be the same directory,
|
||||||
|
// so avoid checking it twice.
|
||||||
|
check_dirs.dedup();
|
||||||
|
|
||||||
|
check_dirs
|
||||||
|
.iter()
|
||||||
|
// repo_root may be the same as the current directory. We don't need to scan it again.
|
||||||
|
.filter(|&&d| d != current_dir)
|
||||||
|
.filter_map(|d| check_directory_for_global_json(d))
|
||||||
|
// This will lazily evaluate the first directory with a global.json
|
||||||
|
.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_directory_for_global_json(path: &Path) -> Option<Version> {
|
||||||
|
let global_json_path = path.join(GLOBAL_JSON_FILE);
|
||||||
|
log::debug!(
|
||||||
|
"Checking if global.json exists at: {}",
|
||||||
|
&global_json_path.display()
|
||||||
|
);
|
||||||
|
if global_json_path.exists() {
|
||||||
|
get_pinned_sdk_version_from_file(&global_json_path)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pinned_sdk_version_from_file(path: &Path) -> Option<Version> {
|
||||||
|
let json_text = crate::utils::read_file(path).ok()?;
|
||||||
|
log::debug!(
|
||||||
|
"Checking if .NET SDK version is pinned in: {}",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
|
get_pinned_sdk_version(&json_text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pinned_sdk_version(json: &str) -> Option<Version> {
|
||||||
|
let parsed_json: JValue = serde_json::from_str(json).ok()?;
|
||||||
|
|
||||||
|
match parsed_json {
|
||||||
|
JValue::Object(root) => {
|
||||||
|
let sdk = root.get("sdk")?;
|
||||||
|
match sdk {
|
||||||
|
JValue::Object(sdk) => {
|
||||||
|
let version = sdk.get("version")?;
|
||||||
|
match version {
|
||||||
|
JValue::String(version_string) => {
|
||||||
|
let mut buffer = String::with_capacity(version_string.len() + 1);
|
||||||
|
buffer.push('v');
|
||||||
|
buffer.push_str(version_string);
|
||||||
|
Some(Version(buffer))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_local_dotnet_files<'a>(context: &'a Context) -> Result<Vec<DotNetFile<'a>>, std::io::Error> {
|
||||||
|
Ok(context
|
||||||
|
.get_dir_files()?
|
||||||
|
.iter()
|
||||||
|
.filter_map(|p| {
|
||||||
|
get_dotnet_file_type(p).map(|t| DotNetFile {
|
||||||
|
path: p.as_ref(),
|
||||||
|
file_type: t,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_dotnet_file_type(path: &Path) -> Option<FileType> {
|
||||||
|
let file_name_lower = map_str_to_lower(path.file_name());
|
||||||
|
|
||||||
|
match file_name_lower.as_ref().map(|f| f.as_ref()) {
|
||||||
|
Some(GLOBAL_JSON_FILE) => return Some(FileType::GlobalJson),
|
||||||
|
Some(PROJECT_JSON_FILE) => return Some(FileType::ProjectJson),
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
let extension_lower = map_str_to_lower(path.extension());
|
||||||
|
|
||||||
|
match extension_lower.as_ref().map(|f| f.as_ref()) {
|
||||||
|
Some("sln") => return Some(FileType::SolutionFile),
|
||||||
|
Some("csproj") | Some("fsproj") | Some("xproj") => return Some(FileType::ProjectFile),
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_str_to_lower(value: Option<&OsStr>) -> Option<String> {
|
||||||
|
Some(value?.to_str()?.to_ascii_lowercase())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_version_from_cli() -> Option<Version> {
|
||||||
|
let version_output = match Command::new("dotnet").arg("--version").output() {
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Failed to execute `dotnet --version`. {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let version = str::from_utf8(version_output.stdout.as_slice())
|
||||||
|
.ok()?
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
let mut buffer = String::with_capacity(version.len() + 1);
|
||||||
|
buffer.push('v');
|
||||||
|
buffer.push_str(version);
|
||||||
|
|
||||||
|
Some(Version(buffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_latest_sdk_from_cli() -> Option<Version> {
|
||||||
|
let mut cmd = Command::new("dotnet");
|
||||||
|
cmd.arg("--list-sdks")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.stdin(Stdio::null());
|
||||||
|
|
||||||
|
let exit_code = match cmd.status() {
|
||||||
|
Ok(status) => status,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Failed to execute `dotnet --list-sdks`. {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if exit_code.success() {
|
||||||
|
let sdks_output = cmd.output().ok()?;
|
||||||
|
fn parse_failed<T>() -> Option<T> {
|
||||||
|
log::warn!("Unable to parse the output from `dotnet --list-sdks`.");
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let latest_sdk = str::from_utf8(sdks_output.stdout.as_slice())
|
||||||
|
.ok()?
|
||||||
|
.lines()
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|l| !l.is_empty())
|
||||||
|
.last()
|
||||||
|
.or_else(parse_failed)?;
|
||||||
|
let take_until = latest_sdk.find('[').or_else(parse_failed)? - 1;
|
||||||
|
if take_until > 1 {
|
||||||
|
let version = &latest_sdk[..take_until];
|
||||||
|
let mut buffer = String::with_capacity(version.len() + 1);
|
||||||
|
buffer.push('v');
|
||||||
|
buffer.push_str(version);
|
||||||
|
Some(Version(buffer))
|
||||||
|
} else {
|
||||||
|
parse_failed()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Older versions of the dotnet cli do not support the --list-sdks command
|
||||||
|
// So, if the status code indicates failure, fall back to `dotnet --version`
|
||||||
|
log::warn!(
|
||||||
|
"Received a non-success exit code from `dotnet --list-sdks`. \
|
||||||
|
Falling back to `dotnet --version`.",
|
||||||
|
);
|
||||||
|
get_version_from_cli()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DotNetFile<'a> {
|
||||||
|
path: &'a Path,
|
||||||
|
file_type: FileType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum FileType {
|
||||||
|
ProjectJson,
|
||||||
|
ProjectFile,
|
||||||
|
GlobalJson,
|
||||||
|
SolutionFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Version(String);
|
||||||
|
|
||||||
|
impl Deref for Version {
|
||||||
|
type Target = String;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_parse_version_from_global_json() {
|
||||||
|
let json_text = r#"
|
||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "1.2.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let version = get_pinned_sdk_version(json_text).unwrap();
|
||||||
|
assert_eq!("v1.2.3", version.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_ignore_empty_global_json() {
|
||||||
|
let json_text = "{}";
|
||||||
|
|
||||||
|
let version = get_pinned_sdk_version(json_text);
|
||||||
|
assert!(version.is_none());
|
||||||
|
}
|
@ -3,6 +3,7 @@ mod aws;
|
|||||||
mod character;
|
mod character;
|
||||||
mod cmd_duration;
|
mod cmd_duration;
|
||||||
mod directory;
|
mod directory;
|
||||||
|
mod dotnet;
|
||||||
mod env_var;
|
mod env_var;
|
||||||
mod git_branch;
|
mod git_branch;
|
||||||
mod git_state;
|
mod git_state;
|
||||||
@ -31,32 +32,34 @@ use crate::module::Module;
|
|||||||
|
|
||||||
pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
|
pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
|
||||||
match module {
|
match module {
|
||||||
|
// Keep these ordered alphabetically.
|
||||||
|
// Default ordering is handled in configs/mod.rs
|
||||||
"aws" => aws::module(context),
|
"aws" => aws::module(context),
|
||||||
|
#[cfg(feature = "battery")]
|
||||||
|
"battery" => battery::module(context),
|
||||||
"directory" => directory::module(context),
|
"directory" => directory::module(context),
|
||||||
"env_var" => env_var::module(context),
|
|
||||||
"character" => character::module(context),
|
"character" => character::module(context),
|
||||||
"nodejs" => nodejs::module(context),
|
"cmd_duration" => cmd_duration::module(context),
|
||||||
"rust" => rust::module(context),
|
"dotnet" => dotnet::module(context),
|
||||||
"python" => python::module(context),
|
"env_var" => env_var::module(context),
|
||||||
"ruby" => ruby::module(context),
|
|
||||||
"golang" => golang::module(context),
|
|
||||||
"line_break" => line_break::module(context),
|
|
||||||
"package" => package::module(context),
|
|
||||||
"git_branch" => git_branch::module(context),
|
"git_branch" => git_branch::module(context),
|
||||||
"git_state" => git_state::module(context),
|
"git_state" => git_state::module(context),
|
||||||
"git_status" => git_status::module(context),
|
"git_status" => git_status::module(context),
|
||||||
"kubernetes" => kubernetes::module(context),
|
"golang" => golang::module(context),
|
||||||
"username" => username::module(context),
|
"hostname" => hostname::module(context),
|
||||||
#[cfg(feature = "battery")]
|
|
||||||
"battery" => battery::module(context),
|
|
||||||
"cmd_duration" => cmd_duration::module(context),
|
|
||||||
"java" => java::module(context),
|
"java" => java::module(context),
|
||||||
"jobs" => jobs::module(context),
|
"jobs" => jobs::module(context),
|
||||||
"nix_shell" => nix_shell::module(context),
|
"kubernetes" => kubernetes::module(context),
|
||||||
"hostname" => hostname::module(context),
|
"line_break" => line_break::module(context),
|
||||||
"time" => time::module(context),
|
|
||||||
"memory_usage" => memory_usage::module(context),
|
"memory_usage" => memory_usage::module(context),
|
||||||
|
"nix_shell" => nix_shell::module(context),
|
||||||
|
"nodejs" => nodejs::module(context),
|
||||||
|
"package" => package::module(context),
|
||||||
|
"python" => python::module(context),
|
||||||
|
"ruby" => ruby::module(context),
|
||||||
|
"rust" => rust::module(context),
|
||||||
|
"time" => time::module(context),
|
||||||
|
"username" => username::module(context),
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!("Error: Unknown module {}. Use starship module --list to list out all supported modules.", module);
|
eprintln!("Error: Unknown module {}. Use starship module --list to list out all supported modules.", module);
|
||||||
None
|
None
|
||||||
|
@ -48,6 +48,19 @@ RUN curl https://pyenv.run | bash \
|
|||||||
# Check that Python was correctly installed
|
# Check that Python was correctly installed
|
||||||
RUN python --version
|
RUN python --version
|
||||||
|
|
||||||
|
# Install Dotnet
|
||||||
|
ENV DOTNET_HOME /home/nonroot/dotnet
|
||||||
|
ENV DOTNET_SDK_VERSION 2.2.402
|
||||||
|
|
||||||
|
RUN mkdir -p "$DOTNET_HOME" \
|
||||||
|
&& dotnet_download="$DOTNET_HOME/../dotnet.tar.gz" \
|
||||||
|
&& curl -SL --output "$dotnet_download" https://dotnetcli.blob.core.windows.net/dotnet/Sdk/$DOTNET_SDK_VERSION/dotnet-sdk-$DOTNET_SDK_VERSION-linux-x64.tar.gz \
|
||||||
|
&& tar -zxf "$dotnet_download" -C "$DOTNET_HOME" \
|
||||||
|
&& rm "$dotnet_download"
|
||||||
|
|
||||||
|
ENV PATH $DOTNET_HOME:$PATH
|
||||||
|
RUN dotnet help
|
||||||
|
|
||||||
# Create blank project
|
# Create blank project
|
||||||
RUN USER=nonroot cargo new --bin /src/starship
|
RUN USER=nonroot cargo new --bin /src/starship
|
||||||
WORKDIR /src/starship
|
WORKDIR /src/starship
|
||||||
|
146
tests/testsuite/dotnet.rs
Normal file
146
tests/testsuite/dotnet.rs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
use super::common;
|
||||||
|
use std::fs::{DirBuilder, OpenOptions};
|
||||||
|
use std::io::{self, Error, ErrorKind, Write};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn shows_nothing_in_directory_with_zero_relevant_files() -> io::Result<()> {
|
||||||
|
let workspace = create_workspace(false)?;
|
||||||
|
expect_output(&workspace, ".", None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn shows_latest_in_directory_with_solution() -> io::Result<()> {
|
||||||
|
let workspace = create_workspace(false)?;
|
||||||
|
touch_path(&workspace, "solution.sln", None)?;
|
||||||
|
expect_output(&workspace, ".", Some("•NET v2.2.402"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn shows_latest_in_directory_with_csproj() -> io::Result<()> {
|
||||||
|
let workspace = create_workspace(false)?;
|
||||||
|
touch_path(&workspace, "project.csproj", None)?;
|
||||||
|
expect_output(&workspace, ".", Some("•NET v2.2.402"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn shows_latest_in_directory_with_fsproj() -> io::Result<()> {
|
||||||
|
let workspace = create_workspace(false)?;
|
||||||
|
touch_path(&workspace, "project.fsproj", None)?;
|
||||||
|
expect_output(&workspace, ".", Some("•NET v2.2.402"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn shows_latest_in_directory_with_xproj() -> io::Result<()> {
|
||||||
|
let workspace = create_workspace(false)?;
|
||||||
|
touch_path(&workspace, "project.xproj", None)?;
|
||||||
|
expect_output(&workspace, ".", Some("•NET v2.2.402"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn shows_latest_in_directory_with_project_json() -> io::Result<()> {
|
||||||
|
let workspace = create_workspace(false)?;
|
||||||
|
touch_path(&workspace, "project.json", None)?;
|
||||||
|
expect_output(&workspace, ".", Some("•NET v2.2.402"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn shows_pinned_in_directory_with_global_json() -> io::Result<()> {
|
||||||
|
let workspace = create_workspace(false)?;
|
||||||
|
let global_json = make_pinned_sdk_json("1.2.3");
|
||||||
|
touch_path(&workspace, "global.json", Some(&global_json))?;
|
||||||
|
expect_output(&workspace, ".", Some("•NET v1.2.3"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn shows_pinned_in_project_below_root_with_global_json() -> io::Result<()> {
|
||||||
|
let workspace = create_workspace(false)?;
|
||||||
|
let global_json = make_pinned_sdk_json("1.2.3");
|
||||||
|
touch_path(&workspace, "global.json", Some(&global_json))?;
|
||||||
|
touch_path(&workspace, "project/project.csproj", None)?;
|
||||||
|
expect_output(&workspace, "project", Some("•NET v1.2.3"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn shows_pinned_in_deeply_nested_project_within_repository() -> io::Result<()> {
|
||||||
|
let workspace = create_workspace(true)?;
|
||||||
|
let global_json = make_pinned_sdk_json("1.2.3");
|
||||||
|
touch_path(&workspace, "global.json", Some(&global_json))?;
|
||||||
|
touch_path(&workspace, "deep/path/to/project/project.csproj", None)?;
|
||||||
|
expect_output(&workspace, "deep/path/to/project", Some("•NET v1.2.3"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_workspace(is_repo: bool) -> io::Result<TempDir> {
|
||||||
|
let repo_dir = common::new_tempdir()?;
|
||||||
|
|
||||||
|
if is_repo {
|
||||||
|
let mut command = Command::new("git");
|
||||||
|
command
|
||||||
|
.args(&["init", "--quiet"])
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.current_dir(repo_dir.path());
|
||||||
|
|
||||||
|
if !command.status()?.success() {
|
||||||
|
return Err(Error::from(ErrorKind::Other));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(repo_dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn touch_path(workspace: &TempDir, relative_path: &str, contents: Option<&str>) -> io::Result<()> {
|
||||||
|
let path = workspace.path().join(relative_path);
|
||||||
|
|
||||||
|
DirBuilder::new().recursive(true).create(
|
||||||
|
path.parent()
|
||||||
|
.expect("Expected relative_path to be a file in a directory"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(&path)?;
|
||||||
|
write!(file, "{}", contents.unwrap_or(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_pinned_sdk_json(version: &str) -> String {
|
||||||
|
let json_text = r#"
|
||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "INSERT_VERSION"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
json_text.replace("INSERT_VERSION", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expect_output(workspace: &TempDir, run_from: &str, contains: Option<&str>) -> io::Result<()> {
|
||||||
|
let run_path = workspace.path().join(run_from);
|
||||||
|
let output = common::render_module("dotnet")
|
||||||
|
.current_dir(run_path)
|
||||||
|
.output()?;
|
||||||
|
let text = String::from_utf8(output.stdout).unwrap();
|
||||||
|
|
||||||
|
// This can be helpful for debugging
|
||||||
|
eprintln!("The dotnet module showed: {}", text);
|
||||||
|
|
||||||
|
match contains {
|
||||||
|
Some(contains) => assert!(text.contains(contains)),
|
||||||
|
None => assert!(text.is_empty()),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -4,6 +4,7 @@ mod cmd_duration;
|
|||||||
mod common;
|
mod common;
|
||||||
mod configuration;
|
mod configuration;
|
||||||
mod directory;
|
mod directory;
|
||||||
|
mod dotnet;
|
||||||
mod env_var;
|
mod env_var;
|
||||||
mod git_branch;
|
mod git_branch;
|
||||||
mod git_state;
|
mod git_state;
|
||||||
|
Loading…
Reference in New Issue
Block a user