mirror of
https://github.com/Llewellynvdm/starship.git
synced 2024-11-24 13:47:38 +00:00
feat(fossil_metrics): add fossil_metrics module (#4874)
* feat(fossil_metrics): add fossil_metrics module * Return early if not in a Fossil check-out * Add more tests for fossil_metrics * Move is in Fossil checkout check after module enabled check * Update type for new toml version * Update the config file schema * Rework parsing of fossil diff output * Fix Fossil check-out detection in subdirectories * Use regex to only match expected fossil diff output * Use shared ancestor scanning and fix detection on Windows * Add note on minimum Fossil version
This commit is contained in:
parent
91d9053aa4
commit
e867cda1eb
40
.github/config-schema.json
vendored
40
.github/config-schema.json
vendored
@ -520,6 +520,20 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"fossil_metrics": {
|
||||
"default": {
|
||||
"added_style": "bold green",
|
||||
"deleted_style": "bold red",
|
||||
"disabled": true,
|
||||
"format": "([+$added]($added_style) )([-$deleted]($deleted_style) )",
|
||||
"only_nonzero_diffs": true
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/FossilMetricsConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gcloud": {
|
||||
"default": {
|
||||
"detect_env_vars": [],
|
||||
@ -3068,6 +3082,32 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"FossilMetricsConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"format": {
|
||||
"default": "([+$added]($added_style) )([-$deleted]($deleted_style) )",
|
||||
"type": "string"
|
||||
},
|
||||
"added_style": {
|
||||
"default": "bold green",
|
||||
"type": "string"
|
||||
},
|
||||
"deleted_style": {
|
||||
"default": "bold red",
|
||||
"type": "string"
|
||||
},
|
||||
"only_nonzero_diffs": {
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"disabled": {
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"GcloudConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -266,6 +266,7 @@ $kubernetes\
|
||||
$directory\
|
||||
$vcsh\
|
||||
$fossil_branch\
|
||||
$fossil_metrics\
|
||||
$git_branch\
|
||||
$git_commit\
|
||||
$git_state\
|
||||
@ -1604,6 +1605,41 @@ truncation_length = 4
|
||||
truncation_symbol = ''
|
||||
```
|
||||
|
||||
## Fossil Metrics
|
||||
|
||||
The `fossil_metrics` module will show the number of added and deleted lines in the check-out in your current directory. At least v2.14 (2021-01-20) of Fossil is required.
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Default | Description |
|
||||
| -------------------- | ------------------------------------------------------------ | ------------------------------------- |
|
||||
| `format` | `'([+$added]($added_style) )([-$deleted]($deleted_style) )'` | The format for the module. |
|
||||
| `added_style` | `'bold green'` | The style for the added count. |
|
||||
| `deleted_style` | `'bold red'` | The style for the deleted count. |
|
||||
| `only_nonzero_diffs` | `true` | Render status only for changed items. |
|
||||
| `disabled` | `true` | Disables the `fossil_metrics` module. |
|
||||
|
||||
### Variables
|
||||
|
||||
| Variable | Example | Description |
|
||||
| --------------- | ------- | ------------------------------------------- |
|
||||
| added | `1` | The current number of added lines |
|
||||
| deleted | `2` | The current number of deleted lines |
|
||||
| added_style\* | | Mirrors the value of option `added_style` |
|
||||
| deleted_style\* | | Mirrors the value of option `deleted_style` |
|
||||
|
||||
*: This variable can only be used as a part of a style string
|
||||
|
||||
### Example
|
||||
|
||||
```toml
|
||||
# ~/.config/starship.toml
|
||||
|
||||
[fossil_metrics]
|
||||
added_style = 'bold blue'
|
||||
format = '[+$added]($added_style)/[-$deleted]($deleted_style) '
|
||||
```
|
||||
|
||||
## Google Cloud (`gcloud`)
|
||||
|
||||
The `gcloud` module shows the current configuration for [`gcloud`](https://cloud.google.com/sdk/gcloud) CLI.
|
||||
|
28
src/configs/fossil_metrics.rs
Normal file
28
src/configs/fossil_metrics.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[cfg_attr(
|
||||
feature = "config-schema",
|
||||
derive(schemars::JsonSchema),
|
||||
schemars(deny_unknown_fields)
|
||||
)]
|
||||
#[serde(default)]
|
||||
pub struct FossilMetricsConfig<'a> {
|
||||
pub format: &'a str,
|
||||
pub added_style: &'a str,
|
||||
pub deleted_style: &'a str,
|
||||
pub only_nonzero_diffs: bool,
|
||||
pub disabled: bool,
|
||||
}
|
||||
|
||||
impl<'a> Default for FossilMetricsConfig<'a> {
|
||||
fn default() -> Self {
|
||||
FossilMetricsConfig {
|
||||
format: "([+$added]($added_style) )([-$deleted]($deleted_style) )",
|
||||
added_style: "bold green",
|
||||
deleted_style: "bold red",
|
||||
only_nonzero_diffs: true,
|
||||
disabled: true,
|
||||
}
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ pub mod erlang;
|
||||
pub mod fennel;
|
||||
pub mod fill;
|
||||
pub mod fossil_branch;
|
||||
pub mod fossil_metrics;
|
||||
pub mod gcloud;
|
||||
pub mod git_branch;
|
||||
pub mod git_commit;
|
||||
@ -159,6 +160,8 @@ pub struct FullConfig<'a> {
|
||||
#[serde(borrow)]
|
||||
fossil_branch: fossil_branch::FossilBranchConfig<'a>,
|
||||
#[serde(borrow)]
|
||||
fossil_metrics: fossil_metrics::FossilMetricsConfig<'a>,
|
||||
#[serde(borrow)]
|
||||
gcloud: gcloud::GcloudConfig<'a>,
|
||||
#[serde(borrow)]
|
||||
git_branch: git_branch::GitBranchConfig<'a>,
|
||||
|
@ -39,6 +39,7 @@ pub const PROMPT_ORDER: &[&str] = &[
|
||||
"directory",
|
||||
"vcsh",
|
||||
"fossil_branch",
|
||||
"fossil_metrics",
|
||||
"git_branch",
|
||||
"git_commit",
|
||||
"git_state",
|
||||
|
@ -35,6 +35,7 @@ pub const ALL_MODULES: &[&str] = &[
|
||||
"fennel",
|
||||
"fill",
|
||||
"fossil_branch",
|
||||
"fossil_metrics",
|
||||
"gcloud",
|
||||
"git_branch",
|
||||
"git_commit",
|
||||
|
297
src/modules/fossil_metrics.rs
Normal file
297
src/modules/fossil_metrics.rs
Normal file
@ -0,0 +1,297 @@
|
||||
use regex::Regex;
|
||||
|
||||
use super::{Context, Module, ModuleConfig};
|
||||
|
||||
use crate::configs::fossil_metrics::FossilMetricsConfig;
|
||||
use crate::formatter::StringFormatter;
|
||||
|
||||
/// Creates a module with currently added/deleted lines in the Fossil check-out in the current
|
||||
/// directory.
|
||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
let mut module = context.new_module("fossil_metrics");
|
||||
let config = FossilMetricsConfig::try_load(module.config);
|
||||
|
||||
// As we default to disabled=true, we have to check here after loading our config module,
|
||||
// before it was only checking against whatever is in the config starship.toml
|
||||
if config.disabled {
|
||||
return None;
|
||||
};
|
||||
|
||||
let checkout_db = if cfg!(windows) {
|
||||
"_FOSSIL_"
|
||||
} else {
|
||||
".fslckout"
|
||||
};
|
||||
// See if we're in a check-out by scanning upwards for a directory containing the checkout_db file
|
||||
context
|
||||
.begin_ancestor_scan()
|
||||
.set_files(&[checkout_db])
|
||||
.scan()?;
|
||||
|
||||
// Read the total number of added and deleted lines from "fossil diff --numstat"
|
||||
let output = context.exec_cmd("fossil", &["diff", "--numstat"])?.stdout;
|
||||
let stats = FossilDiff::parse(&output, config.only_nonzero_diffs);
|
||||
|
||||
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
|
||||
formatter
|
||||
.map_style(|variable| match variable {
|
||||
"added_style" => Some(Ok(config.added_style)),
|
||||
"deleted_style" => Some(Ok(config.deleted_style)),
|
||||
_ => None,
|
||||
})
|
||||
.map(|variable| match variable {
|
||||
"added" => Some(Ok(stats.added)),
|
||||
"deleted" => Some(Ok(stats.deleted)),
|
||||
_ => None,
|
||||
})
|
||||
.parse(None, Some(context))
|
||||
});
|
||||
|
||||
module.set_segments(match parsed {
|
||||
Ok(segments) => segments,
|
||||
Err(error) => {
|
||||
log::warn!("Error in module `fossil_metrics`:\n{}", error);
|
||||
return None;
|
||||
}
|
||||
});
|
||||
|
||||
Some(module)
|
||||
}
|
||||
|
||||
/// Represents the parsed output from a Fossil diff with the --numstat option enabled.
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct FossilDiff<'a> {
|
||||
added: &'a str,
|
||||
deleted: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> FossilDiff<'a> {
|
||||
/// Parses the output of `fossil diff --numstat` as a `FossilDiff` struct.
|
||||
pub fn parse(diff_numstat: &'a str, only_nonzero_diffs: bool) -> Self {
|
||||
// Fossil formats the last line of the output as "%10d %10d TOTAL over %d changed files\n"
|
||||
// where the 1st and 2nd placeholders are the number of added and deleted lines respectively
|
||||
let re = Regex::new(r"^\s*(\d+)\s+(\d+) TOTAL over \d+ changed files$").unwrap();
|
||||
|
||||
let (added, deleted) = diff_numstat
|
||||
.lines()
|
||||
.last()
|
||||
.and_then(|s| re.captures(s))
|
||||
.and_then(|caps| {
|
||||
let added = match caps.get(1)?.as_str() {
|
||||
"0" if only_nonzero_diffs => "",
|
||||
s => s,
|
||||
};
|
||||
|
||||
let deleted = match caps.get(2)?.as_str() {
|
||||
"0" if only_nonzero_diffs => "",
|
||||
s => s,
|
||||
};
|
||||
|
||||
Some((added, deleted))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
Self { added, deleted }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use nu_ansi_term::{Color, Style};
|
||||
|
||||
use crate::test::{fixture_repo, FixtureProvider, ModuleRenderer};
|
||||
|
||||
use super::FossilDiff;
|
||||
|
||||
enum Expect<'a> {
|
||||
Empty,
|
||||
Added(Option<&'a str>),
|
||||
AddedStyle(Style),
|
||||
Deleted(Option<&'a str>),
|
||||
DeletedStyle(Style),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_nothing_on_empty_dir() -> io::Result<()> {
|
||||
let checkout_dir = tempfile::tempdir()?;
|
||||
|
||||
let actual = ModuleRenderer::new("fossil_metrics")
|
||||
.path(checkout_dir.path())
|
||||
.collect();
|
||||
let expected = None;
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
checkout_dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fossil_metrics_disabled_per_default() -> io::Result<()> {
|
||||
let tempdir = fixture_repo(FixtureProvider::Fossil)?;
|
||||
let checkout_dir = tempdir.path();
|
||||
expect_fossil_metrics_with_config(
|
||||
checkout_dir,
|
||||
Some(toml::toml! {
|
||||
// no "disabled=false" in config!
|
||||
[fossil_metrics]
|
||||
only_nonzero_diffs = false
|
||||
}),
|
||||
&[Expect::Empty],
|
||||
);
|
||||
tempdir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fossil_metrics_autodisabled() -> io::Result<()> {
|
||||
let tempdir = tempfile::tempdir()?;
|
||||
expect_fossil_metrics_with_config(tempdir.path(), None, &[Expect::Empty]);
|
||||
tempdir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fossil_metrics() -> io::Result<()> {
|
||||
let tempdir = fixture_repo(FixtureProvider::Fossil)?;
|
||||
let checkout_dir = tempdir.path();
|
||||
expect_fossil_metrics_with_config(
|
||||
checkout_dir,
|
||||
None,
|
||||
&[Expect::Added(Some("3")), Expect::Deleted(Some("2"))],
|
||||
);
|
||||
tempdir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fossil_metrics_subdir() -> io::Result<()> {
|
||||
let tempdir = fixture_repo(FixtureProvider::Fossil)?;
|
||||
let checkout_dir = tempdir.path();
|
||||
expect_fossil_metrics_with_config(
|
||||
&checkout_dir.join("subdir"),
|
||||
None,
|
||||
&[Expect::Added(Some("3")), Expect::Deleted(Some("2"))],
|
||||
);
|
||||
tempdir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fossil_metrics_configured() -> io::Result<()> {
|
||||
let tempdir = fixture_repo(FixtureProvider::Fossil)?;
|
||||
let checkout_dir = tempdir.path();
|
||||
expect_fossil_metrics_with_config(
|
||||
checkout_dir,
|
||||
Some(toml::toml! {
|
||||
[fossil_metrics]
|
||||
added_style = "underline blue"
|
||||
deleted_style = "underline purple"
|
||||
disabled = false
|
||||
}),
|
||||
&[
|
||||
Expect::Added(Some("3")),
|
||||
Expect::AddedStyle(Color::Blue.underline()),
|
||||
Expect::Deleted(Some("2")),
|
||||
Expect::DeletedStyle(Color::Purple.underline()),
|
||||
],
|
||||
);
|
||||
tempdir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_no_changes_discard_zeros() {
|
||||
let actual = FossilDiff::parse(" 0 0 TOTAL over 0 changed files\n", true);
|
||||
let expected = FossilDiff {
|
||||
added: "",
|
||||
deleted: "",
|
||||
};
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_no_changes_keep_zeros() {
|
||||
let actual = FossilDiff::parse(" 0 0 TOTAL over 0 changed files\n", false);
|
||||
let expected = FossilDiff {
|
||||
added: "0",
|
||||
deleted: "0",
|
||||
};
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_with_changes() {
|
||||
let actual = FossilDiff::parse(
|
||||
" 3 2 README.md\n 3 2 TOTAL over 1 changed files\n",
|
||||
true,
|
||||
);
|
||||
let expected = FossilDiff {
|
||||
added: "3",
|
||||
deleted: "2",
|
||||
};
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ignore_empty() {
|
||||
let actual = FossilDiff::parse("", true);
|
||||
let expected = FossilDiff {
|
||||
added: "",
|
||||
deleted: "",
|
||||
};
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
/// Tests output as produced by Fossil v2.3 to v2.14, i.e. without the summary line.
|
||||
#[test]
|
||||
fn parse_ignore_when_missing_total_line() {
|
||||
let actual = FossilDiff::parse(" 3 2 README.md\n", true);
|
||||
let expected = FossilDiff {
|
||||
added: "",
|
||||
deleted: "",
|
||||
};
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
fn expect_fossil_metrics_with_config(
|
||||
checkout_dir: &Path,
|
||||
config: Option<toml::Table>,
|
||||
expectations: &[Expect],
|
||||
) {
|
||||
let actual = ModuleRenderer::new("fossil_metrics")
|
||||
.path(checkout_dir.to_str().unwrap())
|
||||
.config(config.unwrap_or_else(|| {
|
||||
toml::toml! {
|
||||
[fossil_metrics]
|
||||
disabled = false
|
||||
}
|
||||
}))
|
||||
.collect();
|
||||
|
||||
let mut expect_added = Some("3");
|
||||
let mut expect_added_style = Color::Green.bold();
|
||||
let mut expect_deleted = Some("2");
|
||||
let mut expect_deleted_style = Color::Red.bold();
|
||||
|
||||
for expect in expectations {
|
||||
match expect {
|
||||
Expect::Empty => {
|
||||
assert_eq!(None, actual);
|
||||
return;
|
||||
}
|
||||
Expect::Added(added) => expect_added = *added,
|
||||
Expect::AddedStyle(style) => expect_added_style = *style,
|
||||
Expect::Deleted(deleted) => expect_deleted = *deleted,
|
||||
Expect::DeletedStyle(style) => expect_deleted_style = *style,
|
||||
}
|
||||
}
|
||||
|
||||
let expected = Some(format!(
|
||||
"{}{}",
|
||||
expect_added
|
||||
.map(|added| format!("{} ", expect_added_style.paint(format!("+{added}"))))
|
||||
.unwrap_or(String::from("")),
|
||||
expect_deleted
|
||||
.map(|deleted| format!("{} ", expect_deleted_style.paint(format!("-{deleted}"))))
|
||||
.unwrap_or(String::from("")),
|
||||
));
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ mod erlang;
|
||||
mod fennel;
|
||||
mod fill;
|
||||
mod fossil_branch;
|
||||
mod fossil_metrics;
|
||||
mod gcloud;
|
||||
mod git_branch;
|
||||
mod git_commit;
|
||||
@ -129,6 +130,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
|
||||
"fennel" => fennel::module(context),
|
||||
"fill" => fill::module(context),
|
||||
"fossil_branch" => fossil_branch::module(context),
|
||||
"fossil_metrics" => fossil_metrics::module(context),
|
||||
"gcloud" => gcloud::module(context),
|
||||
"git_branch" => git_branch::module(context),
|
||||
"git_commit" => git_commit::module(context),
|
||||
@ -244,6 +246,7 @@ pub fn description(module: &str) -> &'static str {
|
||||
"fennel" => "The currently installed version of Fennel",
|
||||
"fill" => "Fills the remaining space on the line with a pad string",
|
||||
"fossil_branch" => "The active branch of the check-out in your current directory",
|
||||
"fossil_metrics" => "The currently added/deleted lines in your check-out",
|
||||
"gcloud" => "The current GCP client configuration",
|
||||
"git_branch" => "The active branch of the repo in your current directory",
|
||||
"git_commit" => "The active commit (and tag if any) of the repo in your current directory",
|
||||
|
@ -257,6 +257,12 @@ Elixir 1.10 (compiled with Erlang/OTP 22)\n",
|
||||
stdout: String::default(),
|
||||
stderr: String::default(),
|
||||
}),
|
||||
"fossil diff --numstat" => Some(CommandOutput{
|
||||
stdout: String::from("\
|
||||
3 2 README.md
|
||||
3 2 TOTAL over 1 changed files"),
|
||||
stderr: String::default(),
|
||||
}),
|
||||
"fossil update topic-branch" => Some(CommandOutput{
|
||||
stdout: String::default(),
|
||||
stderr: String::default(),
|
||||
|
Loading…
Reference in New Issue
Block a user