use crate::authentication::AuthenticationMethod;
use crate::environment_variables::{
ENV_VAR_DSH_CLI_AUTHENTICATION, ENV_VAR_DSH_CLI_BROWSER, ENV_VAR_DSH_CLI_CSV_QUOTE, ENV_VAR_DSH_CLI_CSV_SEPARATOR, ENV_VAR_DSH_CLI_DRY_RUN, ENV_VAR_DSH_CLI_ERROR_COLOR,
ENV_VAR_DSH_CLI_ERROR_STYLE, ENV_VAR_DSH_CLI_LABEL_COLOR, ENV_VAR_DSH_CLI_LABEL_STYLE, ENV_VAR_DSH_CLI_MATCHING_COLOR, ENV_VAR_DSH_CLI_MATCHING_STYLE, ENV_VAR_DSH_CLI_NO_ESCAPE,
ENV_VAR_DSH_CLI_NO_HEADERS, ENV_VAR_DSH_CLI_OUTPUT_FORMAT, ENV_VAR_DSH_CLI_QUIET, ENV_VAR_DSH_CLI_SHOW_EXECUTION_TIME, ENV_VAR_DSH_CLI_STDERR_COLOR,
ENV_VAR_DSH_CLI_STDERR_STYLE, ENV_VAR_DSH_CLI_STDOUT_COLOR, ENV_VAR_DSH_CLI_STDOUT_STYLE, ENV_VAR_DSH_CLI_SUPPRESS_EXIT_STATUS, ENV_VAR_DSH_CLI_TERMINAL_WIDTH,
ENV_VAR_DSH_CLI_VERBOSITY, ENV_VAR_DSH_CLI_WARNING_COLOR, ENV_VAR_DSH_CLI_WARNING_STYLE, ENV_VAR_NO_COLOR,
};
use crate::formatters::OutputFormat;
use crate::global_arguments::{
AUTHENTICATION_ARGUMENT, BROWSER_ARGUMENT, DRY_RUN_ARGUMENT, FORCE_ARGUMENT, NO_ESCAPE_ARGUMENT, NO_HEADERS_ARGUMENT, OUTPUT_FORMAT_ARGUMENT, QUIET_ARGUMENT,
SHOW_EXECUTION_TIME_ARGUMENT, SUPPRESS_EXIT_STATUS_ARGUMENT, TERMINAL_WIDTH_ARGUMENT, VERBOSITY_ARGUMENT,
};
use crate::settings::Settings;
use crate::style::{apply_default_warning_style, style_from, DshColor, DshStyle};
use crate::verbosity::Verbosity;
use crate::{environment_variable, environment_variable_specified};
use clap::builder::styling::Style;
use clap::ArgMatches;
use dsh_api::dsh_api_tenant::DshApiTenant;
use dsh_api::query_processor::Part;
use dsh_api::query_processor::Part::{Matching, NonMatching};
use getch_rs::{Getch, Key};
use itertools::Itertools;
use log::debug;
use rpassword::prompt_password;
use serde::{Deserialize, Serialize};
use std::ffi::OsStr;
use std::fmt::{Display, Formatter};
use std::io::{stderr, stdin, stdout, IsTerminal, Write};
use std::process;
use std::time::Instant;
use terminal_size::{terminal_size, Height, Width};
use OutputFormat::Csv;
#[derive(clap::ValueEnum, Eq, Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
pub enum BrowserMethod {
#[serde(rename = "instruct")]
Instruct,
#[serde(rename = "open")]
Open,
}
impl TryFrom<&str> for BrowserMethod {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"instruct" => Ok(Self::Instruct),
"open" => Ok(Self::Open),
_ => Err(format!("invalid browser method '{}'", value)),
}
}
}
impl Display for BrowserMethod {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Instruct => write!(f, "instruct"),
Self::Open => write!(f, "open"),
}
}
}
impl Default for BrowserMethod {
fn default() -> Self {
Self::Instruct
}
}
#[derive(Debug, Default)]
pub(crate) struct Context {
authentication_method: AuthenticationMethod,
browser_method: BrowserMethod,
csv_quote: Option<char>,
csv_separator: String,
dry_run: bool,
error_style: Style,
force: bool,
label_style: Style,
matching_style: Style,
output_format_specification: Option<OutputFormat>,
quiet: bool,
settings: Settings,
show_execution_time: bool,
show_headers: bool,
stderr_is_terminal: bool,
stderr_no_escape: bool,
stderr_style: Style,
stdin_is_terminal: bool,
stdout_is_terminal: bool,
stdout_no_escape: bool,
stdout_style: Style,
suppress_exit_status: bool,
terminal_width: Option<usize>,
verbosity: Verbosity,
warning_style: Style,
}
impl Context {
pub(crate) fn create(matches: &ArgMatches, settings: Settings) -> Result<Context, String> {
let stderr_is_terminal = stderr().is_terminal();
let stdin_is_terminal = stdin().is_terminal();
let stdout_is_terminal = stdout().is_terminal();
let csv_quote = Self::get_csv_quote(matches, &settings)?;
let csv_separator = Self::get_csv_separator(matches, &settings)?;
if let Some(quote) = csv_quote {
if csv_separator.contains(quote) {
return Err("csv separator string cannot contain quote character".to_string());
}
}
let authentication_method = Self::get_authentication_method(matches, &settings, stdin_is_terminal)?;
let browser_method = Self::get_browser_method(matches, &settings, stdin_is_terminal)?;
let dry_run = Self::get_dry_run(matches, &settings);
let (stderr_no_escape, stdout_no_escape) = if Self::get_no_escape(matches, &settings) { (true, true) } else { (!stderr_is_terminal, !stdout_is_terminal) };
let error_style = style_from(
&Self::get_style(ENV_VAR_DSH_CLI_ERROR_STYLE, matches, &settings.error_style, DshStyle::Bold)?,
&Self::get_color(ENV_VAR_DSH_CLI_ERROR_COLOR, matches, &settings.error_color, DshColor::Red)?,
);
let label_style = style_from(
&Self::get_style(ENV_VAR_DSH_CLI_LABEL_STYLE, matches, &settings.label_style, DshStyle::Bold)?,
&Self::get_color(ENV_VAR_DSH_CLI_LABEL_COLOR, matches, &settings.label_color, DshColor::Blue)?,
);
let matching_style = style_from(
&Self::get_style(ENV_VAR_DSH_CLI_MATCHING_STYLE, matches, &settings.matching_style, DshStyle::Bold)?,
&Self::get_color(ENV_VAR_DSH_CLI_MATCHING_COLOR, matches, &settings.matching_color, DshColor::Green)?,
);
let stderr_style = style_from(
&Self::get_style(ENV_VAR_DSH_CLI_STDERR_STYLE, matches, &settings.stderr_style, DshStyle::Dim)?,
&Self::get_color(ENV_VAR_DSH_CLI_STDERR_COLOR, matches, &settings.stderr_color, DshColor::Normal)?,
);
let stdout_style = style_from(
&Self::get_style(ENV_VAR_DSH_CLI_STDOUT_STYLE, matches, &settings.stdout_style, DshStyle::Normal)?,
&Self::get_color(ENV_VAR_DSH_CLI_STDOUT_COLOR, matches, &settings.stdout_color, DshColor::Normal)?,
);
let warning_style = style_from(
&Self::get_style(ENV_VAR_DSH_CLI_WARNING_STYLE, matches, &settings.warning_style, DshStyle::Bold)?,
&Self::get_color(ENV_VAR_DSH_CLI_WARNING_COLOR, matches, &settings.warning_color, DshColor::Blue)?,
);
let quiet = Self::get_quiet(matches, &settings);
let force = Self::get_force(matches, &settings);
let suppress_exit_status = Self::get_suppress_exit_status(matches, &settings);
let output_format_specification = Self::get_output_format_specification(matches, &settings)?;
let show_execution_time = Self::get_show_execution_time(matches, &settings);
let verbosity = Self::get_verbosity(matches, &settings)?;
let show_headers = !Self::get_no_headers(matches, &settings);
let terminal_width = Self::get_terminal_width(matches, &settings)?;
if dry_run && verbosity >= Verbosity::Medium {
eprintln!("dry-run mode enabled");
}
Ok(Context {
authentication_method,
browser_method,
csv_quote,
csv_separator,
dry_run,
error_style,
force,
label_style,
matching_style,
output_format_specification,
quiet,
settings,
show_execution_time,
show_headers,
stderr_is_terminal,
stderr_no_escape,
stderr_style,
stdin_is_terminal,
stdout_is_terminal,
stdout_no_escape,
stdout_style,
suppress_exit_status,
terminal_width,
verbosity,
warning_style,
})
}
pub(crate) fn authentication_method(&self) -> &AuthenticationMethod {
&self.authentication_method
}
pub(crate) fn browser_method(&self) -> &BrowserMethod {
&self.browser_method
}
pub(crate) fn csv_quote(&self) -> &Option<char> {
&self.csv_quote
}
pub(crate) fn csv_separator(&self) -> &String {
&self.csv_separator
}
pub(crate) fn dry_run(&self) -> bool {
self.dry_run
}
pub(crate) fn settings(&self) -> &Settings {
&self.settings
}
pub(crate) fn show_headers(&self) -> bool {
self.show_headers
}
pub(crate) fn stdin_is_terminal(&self) -> bool {
self.stdin_is_terminal
}
pub(crate) fn suppress_exit_status(&self) -> bool {
self.suppress_exit_status
}
pub(crate) fn terminal_width(&self) -> Option<usize> {
self.terminal_width
}
fn get_authentication_method(matches: &ArgMatches, settings: &Settings, stdin_is_terminal: bool) -> Result<AuthenticationMethod, String> {
match matches.get_one::<AuthenticationMethod>(AUTHENTICATION_ARGUMENT) {
Some(authentication_argument) => Ok(authentication_argument.to_owned()),
None => match environment_variable(ENV_VAR_DSH_CLI_AUTHENTICATION, matches)? {
Some(authentication_env_var) => AuthenticationMethod::try_from(authentication_env_var.as_str()),
None => match &settings.authentication {
Some(authentication_setting) => Ok(authentication_setting.to_owned()),
None => {
if stdin_is_terminal {
Ok(AuthenticationMethod::SingleSignOn)
} else {
Ok(AuthenticationMethod::Robot)
}
}
},
},
}
}
fn get_browser_method(matches: &ArgMatches, settings: &Settings, stdin_is_terminal: bool) -> Result<BrowserMethod, String> {
match matches.get_one::<BrowserMethod>(BROWSER_ARGUMENT) {
Some(browser_argument) => Ok(browser_argument.to_owned()),
None => match environment_variable(ENV_VAR_DSH_CLI_BROWSER, matches)? {
Some(browser_env_var) => BrowserMethod::try_from(browser_env_var.as_str()),
None => match &settings.browser {
Some(browser_setting) => Ok(browser_setting.to_owned()),
None => {
if stdin_is_terminal {
Ok(BrowserMethod::Open)
} else {
Ok(BrowserMethod::Instruct)
}
}
},
},
}
}
pub(crate) fn confirmed(&self, prompt: impl Display) -> Result<bool, String> {
if self.force {
self.eprintln(format!("{}, confirmed by --force option", prompt));
Ok(true)
} else if self.stdin_is_terminal {
self.eprint(format!("{} [y/N]", prompt));
let _ = stdout().lock().flush();
match Getch::new().getch() {
Ok(key) => match key {
Key::Char('y') | Key::Char('Y') => {
eprintln!();
Ok(true)
}
Key::Ctrl('c') => {
eprintln!("{}", apply_default_warning_style("\ninterrupted"));
process::exit(0);
}
_ => {
eprintln!();
Ok(false)
}
},
Err(error) => Err(format!("\nerror getting key event ({})", error)),
}
} else {
Ok(false)
}
}
fn get_csv_quote(matches: &ArgMatches, settings: &Settings) -> Result<Option<char>, String> {
match environment_variable(ENV_VAR_DSH_CLI_CSV_QUOTE, matches)? {
Some(csv_quote_env_var) => {
if csv_quote_env_var.len() == 1 {
Ok(csv_quote_env_var.chars().next())
} else {
Err("csv quote must one character".to_string())
}
}
None => Ok(settings.csv_quote),
}
}
fn get_csv_separator(matches: &ArgMatches, settings: &Settings) -> Result<String, String> {
match environment_variable(ENV_VAR_DSH_CLI_CSV_SEPARATOR, matches)? {
Some(csv_separator_env_var) => {
if !csv_separator_env_var.is_empty() {
Ok(csv_separator_env_var)
} else {
Err("seperator cannot be empty".to_string())
}
}
None => match settings.csv_separator.clone() {
Some(csv_separator_setting) => {
if !csv_separator_setting.is_empty() {
Ok(csv_separator_setting)
} else {
Err("seperator cannot be empty".to_string())
}
}
None => Ok(",".to_string()),
},
}
}
fn get_dry_run(matches: &ArgMatches, settings: &Settings) -> bool {
if matches.get_flag(DRY_RUN_ARGUMENT) {
debug!("dry run mode enabled (argument)");
true
} else if environment_variable_specified(ENV_VAR_DSH_CLI_DRY_RUN, matches) {
debug!("dry run mode enabled (environment variable '{}')", ENV_VAR_DSH_CLI_DRY_RUN);
true
} else if let Some(dry_run) = settings.dry_run {
if dry_run {
debug!("dry run mode enabled (settings)");
}
dry_run
} else {
false
}
}
fn get_force(matches: &ArgMatches, settings: &Settings) -> bool {
if matches.get_flag(FORCE_ARGUMENT) {
debug!("force mode enabled (argument)");
true
} else if environment_variable_specified(ENV_VAR_DSH_CLI_DRY_RUN, matches) {
debug!("force mode enabled (environment variable '{}')", ENV_VAR_DSH_CLI_DRY_RUN);
true
} else if let Some(dry_run) = settings.dry_run {
if dry_run {
debug!("force mode enabled (settings)");
}
dry_run
} else {
false
}
}
fn get_suppress_exit_status(matches: &ArgMatches, settings: &Settings) -> bool {
if matches.get_flag(SUPPRESS_EXIT_STATUS_ARGUMENT) {
debug!("suppress exit status enabled (argument)");
true
} else if environment_variable_specified(ENV_VAR_DSH_CLI_SUPPRESS_EXIT_STATUS, matches) {
debug!("suppress exit status enabled (environment variable '{}')", ENV_VAR_DSH_CLI_SUPPRESS_EXIT_STATUS);
true
} else if let Some(suppress_exit_status) = settings.suppress_exit_status {
if suppress_exit_status {
debug!("suppress exit status enabled (settings)");
}
suppress_exit_status
} else {
false
}
}
fn get_color(env_var: &str, matches: &ArgMatches, settings_color: &Option<DshColor>, default_color: DshColor) -> Result<DshColor, String> {
match environment_variable(env_var, matches)? {
Some(color_from_env_var) => DshColor::try_from(color_from_env_var.as_str()),
None => match settings_color {
Some(ref color_from_settings) => Ok(color_from_settings.clone()),
None => Ok(default_color),
},
}
}
fn get_style(env_var: &str, matches: &ArgMatches, settings_style: &Option<DshStyle>, default_style: DshStyle) -> Result<DshStyle, String> {
match environment_variable(env_var, matches)? {
Some(style_from_env_var) => DshStyle::try_from(style_from_env_var.as_str()),
None => match settings_style {
Some(ref style_from_settings) => Ok(style_from_settings.clone()),
None => Ok(default_style),
},
}
}
fn get_no_escape(matches: &ArgMatches, settings: &Settings) -> bool {
matches.get_flag(NO_ESCAPE_ARGUMENT)
|| environment_variable_specified(ENV_VAR_NO_COLOR, matches)
|| environment_variable_specified(ENV_VAR_DSH_CLI_NO_ESCAPE, matches)
|| settings.no_escape.unwrap_or(false)
}
fn get_no_headers(matches: &ArgMatches, settings: &Settings) -> bool {
matches.get_flag(NO_HEADERS_ARGUMENT) || environment_variable_specified(ENV_VAR_DSH_CLI_NO_HEADERS, matches) || settings.no_headers.unwrap_or(false)
}
fn get_output_format_specification(matches: &ArgMatches, settings: &Settings) -> Result<Option<OutputFormat>, String> {
match matches.get_one::<OutputFormat>(OUTPUT_FORMAT_ARGUMENT) {
Some(output_format_argument) => Ok(Some(output_format_argument.to_owned())),
None => match environment_variable(ENV_VAR_DSH_CLI_OUTPUT_FORMAT, matches)? {
Some(output_format_env_var) => OutputFormat::try_from(output_format_env_var.as_str())
.map_err(|error| format!("{} in environment variable {}", error, ENV_VAR_DSH_CLI_OUTPUT_FORMAT))
.map(Some),
None => match settings.output_format.clone() {
Some(output_format_from_settings) => Ok(Some(output_format_from_settings)),
None => Ok(None),
},
},
}
}
pub(crate) fn output_format(&self, default_output_format: Option<OutputFormat>) -> OutputFormat {
match self.output_format_specification {
Some(ref output_format_from_specification) => output_format_from_specification.clone(),
None => default_output_format.unwrap_or(if self.stdout_is_terminal { OutputFormat::Table } else { OutputFormat::Json }),
}
}
fn get_quiet(matches: &ArgMatches, settings: &Settings) -> bool {
matches.get_flag(QUIET_ARGUMENT) || environment_variable_specified(ENV_VAR_DSH_CLI_QUIET, matches) || settings.quiet.unwrap_or(false)
}
fn get_show_execution_time(matches: &ArgMatches, settings: &Settings) -> bool {
matches.get_flag(SHOW_EXECUTION_TIME_ARGUMENT) || environment_variable_specified(ENV_VAR_DSH_CLI_SHOW_EXECUTION_TIME, matches) || settings.show_execution_time.unwrap_or(false)
}
fn get_terminal_width(matches: &ArgMatches, settings: &Settings) -> Result<Option<usize>, String> {
match matches.get_one::<usize>(TERMINAL_WIDTH_ARGUMENT) {
Some(terminal_width_argument) => Ok(Some(terminal_width_argument.to_owned())),
None => match environment_variable(ENV_VAR_DSH_CLI_TERMINAL_WIDTH, matches)? {
Some(terminal_width_env_var) => match terminal_width_env_var.parse::<usize>() {
Ok(terminal_width) => {
if terminal_width < 40 {
Err(format!(
"terminal width in environment variable {} must be greater than or equal to 40",
ENV_VAR_DSH_CLI_TERMINAL_WIDTH
))
} else {
Ok(Some(terminal_width))
}
}
Err(_) => Err(format!(
"non-numerical value '{}' in environment variable {}",
terminal_width_env_var, ENV_VAR_DSH_CLI_TERMINAL_WIDTH
)),
},
None => match settings.terminal_width {
Some(terminal_width_from_settings) => Ok(Some(terminal_width_from_settings)),
None => {
if stdout().is_terminal() {
match terminal_size() {
Some((Width(width), Height(_))) => Ok(Some(width as usize)),
None => Ok(None),
}
} else {
Ok(None)
}
}
},
},
}
}
fn get_verbosity(matches: &ArgMatches, settings: &Settings) -> Result<Verbosity, String> {
match matches.get_one::<Verbosity>(VERBOSITY_ARGUMENT) {
Some(verbosity_argument) => Ok(verbosity_argument.to_owned()),
None => match environment_variable(ENV_VAR_DSH_CLI_VERBOSITY, matches)? {
Some(verbosity_env_var) => Verbosity::try_from(verbosity_env_var.as_str()).map_err(|error| format!("{} in environment variable {}", error, ENV_VAR_DSH_CLI_VERBOSITY)),
None => match settings.verbosity.clone() {
Some(verbosity_from_settings) => Ok(verbosity_from_settings),
None => Ok(Verbosity::Low),
},
},
}
}
pub(crate) fn open_url(&self, url: impl AsRef<OsStr> + Display, opening_target: impl Display) {
if self.dry_run() {
self.print_warning(format!("dry-run mode, opening {} canceled", opening_target));
self.print_warning(format!("{}", url));
} else {
match self.browser_method() {
BrowserMethod::Instruct => {
self.print_explanation(format!("opening {}", opening_target));
self.print_explanation("open url in your browser:");
self.print(format!("{}", url));
}
BrowserMethod::Open => match open::that(&url) {
Ok(()) => {
self.print_explanation(format!("opening {}", opening_target));
}
Err(error) => {
self.print_error(format!("could not open {} in your browser", opening_target));
debug!("{}", error);
self.print_explanation("open url in your browser:");
self.print(format!("{}", url));
}
},
}
}
}
pub(crate) fn now(&self) -> Instant {
Instant::now()
}
pub(crate) fn print<T: Display>(&self, output: T) {
if !self.quiet {
self.println(output)
}
}
pub(crate) fn print_serializable<T: Serialize>(&self, output: T, default_output_format: Option<OutputFormat>) {
if !self.quiet {
match self.output_format(default_output_format) {
Csv => self.print_warning("csv output is not supported here, use --output-format json|toml|yaml"),
OutputFormat::Json => match serde_json::to_string_pretty(&output) {
Ok(json) => self.println(json),
Err(_) => self.print_error("serializing to json failed"),
},
OutputFormat::JsonCompact => match serde_json::to_string(&output) {
Ok(json) => self.println(json),
Err(_) => self.print_error("serializing to json failed"),
},
OutputFormat::Plain => self.print_warning("plain output is not supported here, use --output-format json|toml|yaml"),
OutputFormat::Quiet => (),
OutputFormat::Table | OutputFormat::TableNoBorder => self.print_warning("table output is not supported here, use --output-format json|toml|yaml"),
OutputFormat::Toml => match toml::ser::to_string_pretty(&output) {
Ok(toml) => self.println(toml),
Err(_) => self.print_error("serializing to toml failed"),
},
OutputFormat::TomlCompact => match toml::ser::to_string(&output) {
Ok(toml) => self.println(toml),
Err(_) => self.print_error("serializing to toml failed"),
},
OutputFormat::Yaml => match serde_yaml::to_string(&output) {
Ok(yaml) => self.println(yaml),
Err(_) => self.print_error("serializing to yaml failed"),
},
}
}
}
pub(crate) fn print_progress_step(&self) {
if !self.quiet && self.stderr_is_terminal {
self.eprint(".");
}
}
pub(crate) fn print_prompt<T: Display>(&self, prompt: T) {
if !self.quiet && self.stderr_is_terminal {
self.eprint(prompt);
}
}
pub(crate) fn print_outcome<T: Display>(&self, outcome: T) {
if !self.quiet {
match self.verbosity {
Verbosity::Off | Verbosity::Low => (),
Verbosity::Medium | Verbosity::High => self.eprintln(outcome),
}
}
}
pub(crate) fn print_warning<T: Display>(&self, warning: T) {
if !self.quiet {
match self.verbosity {
Verbosity::Off => (),
Verbosity::Low | Verbosity::Medium | Verbosity::High => self.eprintln_warning(warning),
}
}
}
pub(crate) fn print_error<T: Display>(&self, error: T) {
if !self.quiet {
self.eprintln_error(error);
}
}
pub(crate) fn print_explanation<T: Display>(&self, explanation: T) {
if !self.quiet {
match self.verbosity {
Verbosity::Off | Verbosity::Low => (),
Verbosity::Medium | Verbosity::High => self.eprintln(explanation),
}
}
}
pub(crate) fn print_target(&self, dsh_api_tenant: &DshApiTenant) {
if !self.quiet {
match self.verbosity {
Verbosity::Off | Verbosity::Low | Verbosity::Medium => (),
Verbosity::High => self.eprintln(format!("target {}", dsh_api_tenant)),
}
}
}
pub(crate) fn print_execution_time(&self, start_instant: Instant) {
if !self.quiet && self.show_execution_time {
self.eprintln(format!("execution took {} milliseconds", Instant::now().duration_since(start_instant).as_millis()));
}
}
pub(crate) fn read_multi_line(&self, prompt: impl Display) -> Result<String, String> {
if self.stdin_is_terminal {
self.print_prompt(prompt);
}
let mut multi_line = String::new();
let stdin = stdin();
loop {
match stdin.read_line(&mut multi_line) {
Ok(0) => break,
Ok(_) => continue,
Err(_) => return Err("error reading line".to_string()),
}
}
Ok(multi_line)
}
pub(crate) fn read_single_line(&self, prompt: impl Display) -> Result<String, String> {
if self.stdin_is_terminal {
self.print_prompt(format!("{}: ", prompt));
}
let _ = stdout().lock().flush();
let mut line = String::new();
stdin().read_line(&mut line).expect("could not read line");
Ok(line.trim().to_string())
}
pub(crate) fn read_single_line_with_default(&self, prompt: impl Display, default: impl Display) -> Result<String, String> {
if self.stdin_is_terminal {
self.print_prompt(format!("{} [{}]: ", prompt, default));
}
let _ = stdout().lock().flush();
let mut line = String::new();
stdin().read_line(&mut line).expect("could not read line");
let trimmed = line.trim();
if trimmed.is_empty() {
Ok(default.to_string())
} else {
Ok(trimmed.to_string())
}
}
pub(crate) fn read_single_line_password(&self, prompt: impl Display) -> Result<String, String> {
if self.stdin_is_terminal {
match prompt_password(prompt) {
Ok(line) => Ok(line.trim().to_string()),
Err(_) => Err("empty input".to_string()),
}
} else {
self.read_single_line(prompt)
}
}
pub(crate) fn parts_to_string_for_stdout(&self, parts: &[Part], default_output_format: Option<OutputFormat>) -> String {
match self.output_format(default_output_format) {
OutputFormat::Table | OutputFormat::TableNoBorder => {
if self.stdout_no_escape {
Self::parts_to_string(parts)
} else {
parts
.iter()
.map(|part| match part {
Matching(matching_part) => format!("{}{}{:#}{}", self.matching_style, matching_part, self.matching_style, self.stdout_style),
NonMatching(non_matching_part) => non_matching_part.to_string(),
})
.collect_vec()
.join("")
}
}
_ => Self::parts_to_string(parts),
}
}
fn parts_to_string(parts: &[Part]) -> String {
parts.iter().map(|part| part.to_string()).collect_vec().join("")
}
pub(crate) fn apply_label_style_for_stdout<T: Display>(&self, text: T, default_output_format: Option<OutputFormat>) -> String {
match self.output_format(default_output_format) {
OutputFormat::Table | OutputFormat::TableNoBorder => {
if self.stdout_no_escape {
text.to_string()
} else {
format!("{}{}{:#}{}", self.label_style, text, self.label_style, self.stdout_style)
}
}
_ => text.to_string(),
}
}
pub(crate) fn csv_value(&self, value: &str) -> Result<String, String> {
if value.contains(self.csv_separator.as_str()) {
Err("csv value contains separator character".to_string())
} else if value.contains("\n") {
Err("csv value contains new line".to_string())
} else if let Some(csv_quote) = self.csv_quote {
if value.contains(csv_quote) {
Err("csv value contains quote character".to_string())
} else {
Ok(format!("{}{}{}", csv_quote, value, csv_quote))
}
} else {
Ok(value.to_string())
}
}
fn _apply_stderr_style<T: Display>(&self, text: T) -> String {
if self.stderr_no_escape {
text.to_string()
} else {
format!("{}{}{:#}", self.error_style, text, self.error_style)
}
}
fn eprintln_warning<T: Display>(&self, text: T) {
if self.stderr_no_escape {
eprintln!("{}", text)
} else {
eprintln!("{}{}{:#}", self.warning_style, text, self.warning_style)
}
}
fn eprintln_error<T: Display>(&self, text: T) {
if self.stderr_no_escape {
eprintln!("{}", text)
} else {
eprintln!("{}{}{:#}", self.error_style, text, self.error_style)
}
}
fn eprint<T: Display>(&self, text: T) {
if self.stderr_no_escape {
eprint!("{}", text)
} else {
eprint!("{}{}{:#}", self.stderr_style, text, self.stderr_style)
}
}
fn eprintln<T: Display>(&self, text: T) {
if self.stderr_no_escape {
eprintln!("{}", text)
} else {
eprintln!("{}{}{:#}", self.stderr_style, text, self.stderr_style)
}
}
fn println<T: Display>(&self, text: T) {
if self.stdout_no_escape {
println!("{}", text)
} else {
println!("{}{}{:#}", self.stdout_style, text, self.stdout_style)
}
}
}