starship/src/main.rs

311 lines
12 KiB
Rust
Raw Normal View History

#![warn(clippy::disallowed_method)]
use clap::crate_authors;
use std::io;
use std::time::SystemTime;
use clap::{App, AppSettings, Arg, Shell, SubCommand};
use rand::distributions::Alphanumeric;
use rand::Rng;
use starship::module::ALL_MODULES;
use starship::*;
2019-04-02 04:45:49 +00:00
2019-04-02 03:23:03 +00:00
fn main() {
// Configure the current terminal on windows to support ANSI escape sequences.
#[cfg(windows)]
let _ = ansi_term::enable_ansi_support();
logger::init();
2019-05-14 04:43:11 +00:00
let status_code_arg = Arg::with_name("status_code")
.short("s")
.long("status")
.value_name("STATUS_CODE")
.help("The status code of the previously run command")
.takes_value(true);
let pipestatus_arg = Arg::with_name("pipestatus")
.long("pipestatus")
.value_name("PIPESTATUS")
.help("Status codes from a command pipeline")
.long_help("Bash and Zsh supports returning codes for each process in a pipeline.")
.multiple(true);
let terminal_width_arg = Arg::with_name("terminal_width")
.short("w")
.long("terminal-width")
.value_name("TERMINAL_WIDTH")
.help("The width of the current interactive terminal.")
.takes_value(true);
let path_arg = Arg::with_name("path")
.short("p")
.long("path")
.value_name("PATH")
refactor(directory): Introduce `logical-path` argument which allows a shell to explicitly specify both a logical and physical filesystem path (#2104) * refactor(directory): Introduce `logical-path` argument which allows a shell to explicitly specify both a logical and physical filesystem path Fix `directory::module` to consume both path and logical-path (if provided). The "logical" path is preferred when rendering the "display path", while the "physical" path is used to resolve the "read only" flag. Repo- and home-directory contraction behavior is maintained, based on the logical path if it is set, or the physical path if it is not. The custom "get_current_dir" logic has been removed entirely, and the `directory` module now relies on `context.current_dir` / `context.logical_dir` entirely. Changes have been made to `init/starship.ps1` to work with this new flag: - Calculate and pass "physical" and "logical" paths explicitly (as other shells do not pass `--logical-path` that they fall back to rendering the physical path) - Moved the "powershell provider prefix" cleanup code to the PowerShell script - this code _should_ now support any kind of powershell path prefix. * fix(powershell): Fix an issue with trailing backslashes on file paths causing command line parsing issues. This is a bit of a footgun! The work-around chosen is to append a trailing space when a path string ends with a backslash, and then trim any extra whitespace away in the Context constructor. Other alternatives considered and rejected: 1. Always trim trailing backslashes as the filesystem generally doesn't need them. 2. Escape trailing backslashes with another backslash. This proved complex as PS only quotes string args when the string includes some whitespace, and other backslashes within the string apparently don't need to be escaped. * fix(powershell): Use Invoke-Native pattern for safely invoking native executables with strings which may contain characters which need to be escaped carefully. * fix(context): Remove superfluous argument trims These were in place to clean up extra whitespace sometimes injected by starship.ps1::prompt, and are no longer required with the new Invoke-Native helper in place. * refactor(directory): Clean up the semantics of `logical_dir` defaulting it to `current_dir` but overridable by the `--logical-dir` flag. - Restore `use_logical_path` config flag. - Always attempt to contract repo paths from the `current_dir`. * fix(directory) :Use logical_dir for contracting the home directory This keeps the two calls to contract_path in sync. * fix(directory): Remove test script * refactor(directory): Convert current_dir to canonical filesystem path when use_logical_path = false - This requires some clean-up to remove the extended-path prefix on Windows - The configured logical_dir is ignored entirely in this mode - we calculate a new logical_dir by cleaning up the physical_dir path for display. - Test coverage * fix(directory): Use AsRef style for passing Path arguments * fix(directory): Strip the windows extended-path prefix from the display string later in the render process * fix(docs): Update docs/config/README.md for use_logical_path * refactor(context): Populate `current_dir` from `--path` or `std::env::current_dir`, populate `logical_dir` from `--logical-path` or the `PWD` env var - `current_dir` is always canonicalized - On Windows, `current_dir` will have an extended-path prefix - `logical_dir` is now always set - `directory::module` now just selects between `current_dir` and `logical_dir` when picking which path to render - Test coverage * fix(directory): Fix path comparison operations in directory to ignore differences between path prefixes - Added PathExt extension trait which adds `normalised_equals`, `normalised_starts_with` and `without_prefix` * fix(path): Add test coverage for PathExt on *nix * fix(directory): Test coverage for `contract_repo_path`, `contract_path` with variations of verbatim and non-verbatim paths * fix(directory): Update path-slash to latest This fixes the issue with the trailing character of some Windows paths being truncated, e.g. `\\server\share` and `C:` * fix(powershell): Improve UTF8 output handling, argument encoding - Use `ProcessStartInfo` to launch native executable, replacing manual UTF8 output encoding handling - If we detect we're on PWSH6+ use the new `System.Diagnostics.ProcessStartInfo.ArgumentList` parameter, otherwise manually escape the argument string - Move `Get-Cwd` and `Invoke-Native` into the prompt function scope so that they don't leak into the user's shell scope * fix(path): Make PathExt methods no-ops on *nix * fix(path): Cargo fmt * fix(powershell): Remove typo ';'. Fix variable assignment lint.
2021-02-08 14:14:59 +00:00
.help("The path that the prompt should render for.")
.takes_value(true);
let logical_path_arg = Arg::with_name("logical_path")
.short("P")
.long("logical-path")
.value_name("LOGICAL_PATH")
.help(concat!(
"The logical path that the prompt should render for. ",
"This path should be a virtual/logical representation of the PATH argument."
))
.takes_value(true);
let shell_arg = Arg::with_name("shell")
.value_name("SHELL")
.help(
"The name of the currently running shell\nCurrently supported options: bash, zsh, fish, powershell, ion, elvish, tcsh, nu, xonsh",
)
.required(true);
let cmd_duration_arg = Arg::with_name("cmd_duration")
.short("d")
.long("cmd-duration")
.value_name("CMD_DURATION")
.help("The execution duration of the last command, in milliseconds")
.takes_value(true);
let keymap_arg = Arg::with_name("keymap")
.short("k")
.long("keymap")
.value_name("KEYMAP")
// fish/zsh only
.help("The keymap of fish/zsh")
.takes_value(true);
let jobs_arg = Arg::with_name("jobs")
.short("j")
.long("jobs")
.value_name("JOBS")
.help("The number of currently running jobs")
.takes_value(true);
let init_scripts_arg = Arg::with_name("print_full_init")
.long("print-full-init")
.help("Print the main initialization script (as opposed to the init stub)");
let long_version = crate::shadow::clap_version();
let mut app =
App::new("starship")
.about("The cross-shell prompt for astronauts. ☄🌌️")
// pull the version number from Cargo.toml
.version(shadow::PKG_VERSION)
.long_version(long_version.as_str())
// pull the authors from Cargo.toml
.author(crate_authors!())
.after_help("https://github.com/starship/starship")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name("init")
.about("Prints the shell function used to execute starship")
.arg(&shell_arg)
.arg(&init_scripts_arg),
)
.subcommand(
SubCommand::with_name("prompt")
.about("Prints the full starship prompt")
.arg(
Arg::with_name("right")
.long("right")
.help("Print the right prompt (instead of the standard left prompt)"),
)
.arg(&status_code_arg)
.arg(&pipestatus_arg)
.arg(&terminal_width_arg)
.arg(&path_arg)
.arg(&logical_path_arg)
.arg(&cmd_duration_arg)
.arg(&keymap_arg)
.arg(&jobs_arg),
)
.subcommand(
SubCommand::with_name("module")
.about("Prints a specific prompt module")
.arg(
Arg::with_name("name")
.help("The name of the module to be printed")
.required(true)
.required_unless("list"),
)
.arg(
Arg::with_name("list")
.short("l")
.long("list")
.help("List out all supported modules"),
)
.arg(&status_code_arg)
.arg(&pipestatus_arg)
.arg(&terminal_width_arg)
.arg(&path_arg)
.arg(&logical_path_arg)
.arg(&cmd_duration_arg)
.arg(&keymap_arg)
.arg(&jobs_arg),
)
.subcommand(
SubCommand::with_name("config")
.alias("configure")
.about("Edit the starship configuration")
.arg(
Arg::with_name("name")
.help("Configuration key to edit")
.required(false)
.requires("value"),
)
.arg(Arg::with_name("value").help("Value to place into that key")),
)
.subcommand(
SubCommand::with_name("print-config")
.about("Prints the computed starship configuration")
.arg(
Arg::with_name("default")
.short("d")
.long("default")
.help("Print the default instead of the computed config")
.takes_value(false),
),
)
.subcommand(
SubCommand::with_name("toggle")
.about("Toggle a given starship module")
.arg(
Arg::with_name("name")
.help("The name of the module to be toggled")
.required(true),
)
.arg(
Arg::with_name("key")
.help("The key of the config to be toggled")
.required(false)
.required_unless("name"),
),
)
.subcommand(SubCommand::with_name("bug-report").about(
"Create a pre-populated GitHub issue with information about your configuration",
))
.subcommand(
SubCommand::with_name("time")
.about("Prints time in milliseconds")
.settings(&[AppSettings::Hidden]),
)
.subcommand(
SubCommand::with_name("explain")
.about("Explains the currently showing modules")
.arg(&status_code_arg)
.arg(&pipestatus_arg)
.arg(&terminal_width_arg)
.arg(&path_arg)
.arg(&logical_path_arg)
.arg(&cmd_duration_arg)
.arg(&keymap_arg)
.arg(&jobs_arg),
)
.subcommand(
SubCommand::with_name("timings")
.about("Prints timings of all active modules")
.arg(&status_code_arg)
.arg(&pipestatus_arg)
.arg(&terminal_width_arg)
.arg(&path_arg)
.arg(&logical_path_arg)
.arg(&cmd_duration_arg)
.arg(&keymap_arg)
.arg(&jobs_arg),
)
.subcommand(
SubCommand::with_name("completions")
.about("Generate starship shell completions for your shell to stdout")
.arg(
Arg::with_name("shell")
.takes_value(true)
.possible_values(&Shell::variants())
.help("the shell to generate completions for")
.value_name("SHELL")
.required(true)
.env("STARSHIP_SHELL"),
),
)
.subcommand(SubCommand::with_name("session").about("Generate random session key"));
let matches = app.clone().get_matches();
2019-04-02 04:45:49 +00:00
match matches.subcommand() {
("init", Some(sub_m)) => {
let shell_name = sub_m.value_of("shell").expect("Shell name missing.");
if sub_m.is_present("print_full_init") {
init::init_main(shell_name).expect("can't init_main");
} else {
init::init_stub(shell_name).expect("can't init_stub");
}
}
("prompt", Some(sub_m)) => print::prompt(sub_m.clone()),
("module", Some(sub_m)) => {
if sub_m.is_present("list") {
println!("Supported modules list");
println!("----------------------");
for modules in ALL_MODULES {
println!("{}", modules);
}
}
if let Some(module_name) = sub_m.value_of("name") {
print::module(module_name, sub_m.clone());
}
}
("config", Some(sub_m)) => {
if let Some(name) = sub_m.value_of("name") {
if let Some(value) = sub_m.value_of("value") {
configure::update_configuration(name, value)
}
} else {
configure::edit_configuration()
}
}
("print-config", Some(sub_m)) => {
let print_default = sub_m.is_present("default");
configure::print_configuration(print_default)
}
("toggle", Some(sub_m)) => {
if let Some(name) = sub_m.value_of("name") {
if let Some(value) = sub_m.value_of("key") {
configure::toggle_configuration(name, value)
} else {
configure::toggle_configuration(name, "disabled")
}
}
}
("bug-report", Some(_)) => bug_report::create(),
("time", _) => {
match SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.ok()
{
Some(time) => println!("{}", time.as_millis()),
None => println!("{}", -1),
}
}
("explain", Some(sub_m)) => print::explain(sub_m.clone()),
("timings", Some(sub_m)) => print::timings(sub_m.clone()),
("completions", Some(sub_m)) => {
let shell: Shell = sub_m
.value_of("shell")
.expect("Shell name missing.")
.parse()
.expect("Invalid shell");
app.gen_completions_to("starship", shell, &mut io::stdout().lock());
}
("session", _) => println!(
"{}",
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(16)
.map(char::from)
.collect::<String>()
),
(command, _) => unreachable!("Invalid subcommand: {}", command),
}
2019-04-02 03:23:03 +00:00
}