feat: Add the hg_branch module (#569)

This commit is contained in:
Luca Greco 2019-12-02 23:37:18 +01:00 committed by Matan Kushner
parent 33df8ac063
commit 337f213753
12 changed files with 383 additions and 0 deletions

View File

@ -124,6 +124,17 @@ jobs:
with: with:
dotnet-version: "2.2.402" dotnet-version: "2.2.402"
# Install Mercurial (pre-installed on linux, installed from pip on macos
# and from choco on windows),
- name: Install Mercurial (macos)
if: matrix.os == 'macOS-latest'
env:
HGPYTHON3: 1
run: pip install mercurial
- name: Install Mercurial (windows)
if: matrix.os == 'windows-latest'
run: choco install hg
# Run the ignored tests that expect the above setup # Run the ignored tests that expect the above setup
- name: Run all tests - name: Run all tests
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1

View File

@ -93,6 +93,7 @@ prompt_order = [
"git_branch", "git_branch",
"git_state", "git_state",
"git_status", "git_status",
"hg_branch",
"package", "package",
"dotnet", "dotnet",
"golang", "golang",
@ -559,6 +560,31 @@ The module will be shown if any of the following conditions are met:
symbol = "🏎💨 " symbol = "🏎💨 "
``` ```
## Mercurial Branch
The `hg_branch` module shows the active branch of the repo in your current directory.
### Options
| Variable | Default | Description |
| ------------------- | --------------- | -------------------------------------------------------------------------------------------- |
| `symbol` | `" "` | The symbol used before the hg bookmark or branch name of the repo in your current directory. |
| `truncation_length` | `2^63 - 1` | Truncates the hg branch name to X graphemes |
| `truncation_symbol` | `"…"` | The symbol used to indicate a branch name was truncated. |
| `style` | `"bold purple"` | The style for the module. |
| `disabled` | `true` | Disables the `hg_branch` module. |
### Example
```toml
# ~/.config/starship.toml
[hg_branch]
symbol = "🌱 "
truncation_length = 4
truncation_symbol = ""
```
## Hostname ## Hostname
The `hostname` module shows the system hostname. The `hostname` module shows the system hostname.

27
src/configs/hg_branch.rs Normal file
View File

@ -0,0 +1,27 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)]
pub struct HgBranchConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub truncation_length: i64,
pub truncation_symbol: &'a str,
pub branch_name: SegmentConfig<'a>,
pub style: Style,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for HgBranchConfig<'a> {
fn new() -> Self {
HgBranchConfig {
symbol: SegmentConfig::new(""),
truncation_length: std::i64::MAX,
truncation_symbol: "",
branch_name: SegmentConfig::default(),
style: Color::Purple.bold(),
disabled: true,
}
}
}

View File

@ -10,6 +10,7 @@ pub mod git_branch;
pub mod git_state; pub mod git_state;
pub mod git_status; pub mod git_status;
pub mod go; pub mod go;
pub mod hg_branch;
pub mod hostname; pub mod hostname;
pub mod java; pub mod java;
pub mod jobs; pub mod jobs;

View File

@ -24,6 +24,7 @@ impl<'a> RootModuleConfig<'a> for StarshipRootConfig<'a> {
"git_branch", "git_branch",
"git_state", "git_state",
"git_status", "git_status",
"hg_branch",
"package", "package",
// ↓ Toolchain version modules ↓ // ↓ Toolchain version modules ↓
// (Let's keep these sorted alphabetically) // (Let's keep these sorted alphabetically)

View File

@ -21,6 +21,7 @@ pub const ALL_MODULES: &[&str] = &[
"git_state", "git_state",
"git_status", "git_status",
"golang", "golang",
"hg_branch",
"hostname", "hostname",
"java", "java",
"jobs", "jobs",

89
src/modules/hg_branch.rs Normal file
View File

@ -0,0 +1,89 @@
use std::process::Command;
use unicode_segmentation::UnicodeSegmentation;
use super::{Context, Module, RootModuleConfig};
use crate::configs::hg_branch::HgBranchConfig;
/// Creates a module with the Hg bookmark or branch in the current directory
///
/// Will display the bookmark or branch name if the current directory is an hg repo
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let is_hg_repo = context
.try_begin_scan()?
.set_files(&[".hgignore"])
.set_folders(&[".hg"])
.is_match();
if !is_hg_repo {
return None;
}
let mut module = context.new_module("hg_branch");
let config = HgBranchConfig::try_load(module.config);
module.set_style(config.style);
module.get_prefix().set_value("on ");
let truncation_symbol = get_graphemes(config.truncation_symbol, 1);
module.create_segment("symbol", &config.symbol);
// TODO: Once error handling is implemented, warn the user if their config
// truncation length is nonsensical
let len = if config.truncation_length <= 0 {
log::warn!(
"\"truncation_length\" should be a positive value, found {}",
config.truncation_length
);
std::usize::MAX
} else {
config.truncation_length as usize
};
let get_branch_name = |tmpl| get_hg_log_template(tmpl, context);
let branch_name = get_branch_name("{activebookmark}")
.or_else(|| get_branch_name("{branch}"))
.unwrap_or_else(|| "(no branch)".to_string());
let truncated_graphemes = get_graphemes(&branch_name, len);
// The truncation symbol should only be added if we truncated
let truncated_and_symbol = if len < graphemes_len(&branch_name) {
truncated_graphemes + &truncation_symbol
} else {
truncated_graphemes
};
module.create_segment(
"name",
&config.branch_name.with_value(&truncated_and_symbol),
);
Some(module)
}
fn get_hg_log_template(hgtmpl: &str, ctx: &Context) -> Option<String> {
let output = Command::new("hg")
.args(&["log", "-r", ".", "--template", hgtmpl])
.current_dir(&ctx.current_dir)
.output()
.ok()
.and_then(|output| String::from_utf8(output.stdout).ok())?;
if output.is_empty() {
None
} else {
Some(output)
}
}
fn get_graphemes(text: &str, length: usize) -> String {
UnicodeSegmentation::graphemes(text, true)
.take(length)
.collect::<Vec<&str>>()
.concat()
}
fn graphemes_len(text: &str) -> usize {
UnicodeSegmentation::graphemes(&text[..], true).count()
}

View File

@ -10,6 +10,7 @@ mod git_branch;
mod git_state; mod git_state;
mod git_status; mod git_status;
mod golang; mod golang;
mod hg_branch;
mod hostname; mod hostname;
mod java; mod java;
mod jobs; mod jobs;
@ -50,6 +51,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
"git_state" => git_state::module(context), "git_state" => git_state::module(context),
"git_status" => git_status::module(context), "git_status" => git_status::module(context),
"golang" => golang::module(context), "golang" => golang::module(context),
"hg_branch" => hg_branch::module(context),
"hostname" => hostname::module(context), "hostname" => hostname::module(context),
"java" => java::module(context), "java" => java::module(context),
"jobs" => jobs::module(context), "jobs" => jobs::module(context),

View File

@ -61,6 +61,11 @@ RUN mkdir -p "$DOTNET_HOME" \
ENV PATH $DOTNET_HOME:$PATH ENV PATH $DOTNET_HOME:$PATH
RUN dotnet help RUN dotnet help
# Install Mercurial
RUN HGPYTHON3=1 pip install mercurial
# Check that Mercurial was correctly installed
RUN hg --version
# 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

BIN
tests/fixtures/hg-repo.bundle vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,219 @@
use ansi_term::{Color, Style};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::{env, io};
use tempfile;
use crate::common::{self, TestCommand};
enum Expect<'a> {
BranchName(&'a str),
Empty,
NoTruncation,
Symbol(&'a str),
Style(Style),
TruncationSymbol(&'a str),
}
#[test]
#[ignore]
fn test_hg_get_branch_fails() -> io::Result<()> {
let tempdir = tempfile::tempdir()?;
// Create a fake corrupted mercurial repo.
let hgdir = tempdir.path().join(".hg");
fs::create_dir(&hgdir)?;
fs::write(&hgdir.join("requires"), "fake-corrupted-repo")?;
expect_hg_branch_with_config(
tempdir.path(),
"",
&[Expect::BranchName(&"(no branch)"), Expect::NoTruncation],
)
}
#[test]
#[ignore]
fn test_hg_get_branch_autodisabled() -> io::Result<()> {
let tempdir = tempfile::tempdir()?;
expect_hg_branch_with_config(tempdir.path(), "", &[Expect::Empty])
}
#[test]
#[ignore]
fn test_hg_bookmark() -> io::Result<()> {
let tempdir = tempfile::tempdir()?;
let repo_dir = create_fixture_hgrepo(&tempdir)?;
run_hg(&["bookmark", "bookmark-101"], &repo_dir)?;
expect_hg_branch_with_config(
&repo_dir,
"",
&[Expect::BranchName(&"bookmark-101"), Expect::NoTruncation],
)
}
#[test]
#[ignore]
fn test_default_truncation_symbol() -> io::Result<()> {
let tempdir = tempfile::tempdir()?;
let repo_dir = create_fixture_hgrepo(&tempdir)?;
run_hg(&["branch", "-f", "branch-name-101"], &repo_dir)?;
run_hg(
&[
"commit",
"-m",
"empty commit 101",
"-u",
"fake user <fake@user>",
],
&repo_dir,
)?;
expect_hg_branch_with_config(
&repo_dir,
"truncation_length = 14",
&[Expect::BranchName(&"branch-name-10")],
)
}
#[test]
#[ignore]
fn test_configured_symbols() -> io::Result<()> {
let tempdir = tempfile::tempdir()?;
let repo_dir = create_fixture_hgrepo(&tempdir)?;
run_hg(&["branch", "-f", "branch-name-121"], &repo_dir)?;
run_hg(
&[
"commit",
"-m",
"empty commit 121",
"-u",
"fake user <fake@user>",
],
&repo_dir,
)?;
expect_hg_branch_with_config(
&repo_dir,
r#"
symbol = "B "
truncation_length = 14
truncation_symbol = "%"
"#,
&[
Expect::BranchName(&"branch-name-12"),
Expect::Symbol(&"B"),
Expect::TruncationSymbol(&"%"),
],
)
}
#[test]
#[ignore]
fn test_configured_style() -> io::Result<()> {
let tempdir = tempfile::tempdir()?;
let repo_dir = create_fixture_hgrepo(&tempdir)?;
run_hg(&["branch", "-f", "branch-name-131"], &repo_dir)?;
run_hg(
&[
"commit",
"-m",
"empty commit 131",
"-u",
"fake user <fake@user>",
],
&repo_dir,
)?;
expect_hg_branch_with_config(
&repo_dir,
r#"
style = "underline blue"
"#,
&[
Expect::BranchName(&"branch-name-131"),
Expect::Style(Color::Blue.underline()),
Expect::TruncationSymbol(&""),
],
)
}
fn expect_hg_branch_with_config(
repo_dir: &Path,
config_options: &str,
expectations: &[Expect],
) -> io::Result<()> {
let output = common::render_module("hg_branch")
.use_config(toml::from_str(&format!(
r#"
[hg_branch]
{}
"#,
config_options
))?)
.arg("--path")
.arg(repo_dir.to_str().unwrap())
.output()?;
let actual = String::from_utf8(output.stdout).unwrap();
let mut expect_branch_name = "(no branch)";
let mut expect_style = Color::Purple.bold();
let mut expect_symbol = "\u{e0a0}";
let mut expect_truncation_symbol = "";
for expect in expectations {
match expect {
Expect::Empty => {
assert_eq!("", actual);
return Ok(());
}
Expect::Symbol(symbol) => {
expect_symbol = symbol;
}
Expect::TruncationSymbol(truncation_symbol) => {
expect_truncation_symbol = truncation_symbol;
}
Expect::NoTruncation => {
expect_truncation_symbol = "";
}
Expect::BranchName(branch_name) => {
expect_branch_name = branch_name;
}
Expect::Style(style) => expect_style = *style,
}
}
let expected = format!(
"on {} ",
expect_style.paint(format!(
"{} {}{}",
expect_symbol, expect_branch_name, expect_truncation_symbol
)),
);
assert_eq!(expected, actual);
Ok(())
}
pub fn create_fixture_hgrepo(tempdir: &tempfile::TempDir) -> io::Result<PathBuf> {
let repo_path = tempdir.path().join("hg-repo");
let fixture_path = env::current_dir()?.join("tests/fixtures/hg-repo.bundle");
run_hg(
&[
"clone",
fixture_path.to_str().unwrap(),
repo_path.to_str().unwrap(),
],
&tempdir.path(),
)?;
Ok(repo_path)
}
fn run_hg(args: &[&str], repo_dir: &Path) -> io::Result<()> {
Command::new("hg")
.args(args)
.current_dir(&repo_dir)
.output()?;
Ok(())
}

View File

@ -11,6 +11,7 @@ mod git_branch;
mod git_state; mod git_state;
mod git_status; mod git_status;
mod golang; mod golang;
mod hg_branch;
mod hostname; mod hostname;
mod jobs; mod jobs;
mod line_break; mod line_break;