use super::Invocation;
use crate::error::CliError;
use crate::output::{Format, terminal_safe};
use crate::render::{pretty, table};
use crate::store::{PROGRAM, shown};
use crate::tenant::StoreView;
use crate::tenant::config::{ConfigValue, Shown};
use serde::Serialize;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct TenantRow<'a> {
name: &'a str,
config_path: &'a str,
tsg_id: &'a str,
active: bool,
}
#[derive(Serialize)]
struct SettingRow<'a> {
key: &'a str,
value: &'a Shown,
}
fn store_of(invocation: &Invocation) -> Result<StoreView, CliError> {
StoreView::open(&invocation.env)
}
const LIST_COLUMNS: [&str; 4] = ["Tenant", "Selected", "TSG ID", "Config file"];
pub fn list(invocation: &mut Invocation, format: Format) -> Result<(), CliError> {
let view = store_of(invocation)?;
let output = &mut invocation.output;
let index = &view.index;
if index.tenants.is_empty() && format == Format::Pretty {
pretty::empty_list(output, "tenants");
output.status(&format!(
"Run '{PROGRAM} tenant create <name>' to register one{}.",
view.import_suffix()
));
return Ok(());
}
if index.active.is_none() && !index.tenants.is_empty() {
output.status(&format!(
"No tenant selected; run '{PROGRAM} tenant switch <name>'."
));
}
let paths: Vec<String> = index
.tenants
.keys()
.map(|name| view.tenant_path(name).display().to_string())
.collect();
let rows: Vec<TenantRow<'_>> = index
.tenants
.iter()
.zip(&paths)
.map(|((name, tsg_id), config_path)| TenantRow {
name,
config_path,
tsg_id,
active: index.active.as_deref() == Some(name.as_str()),
})
.collect();
if format == Format::Json {
return output.json(&rows);
}
let cells: Vec<Vec<String>> = rows
.iter()
.map(|row| {
vec![
terminal_safe(row.name).into_owned(),
row.active.to_string(),
terminal_safe(row.tsg_id).into_owned(),
terminal_safe(row.config_path).into_owned(),
]
})
.collect();
output.data_line(&table::table(&LIST_COLUMNS, &cells));
Ok(())
}
fn shown_cell(value: &Shown) -> String {
match value {
Shown::Value(ConfigValue::Text(text)) => terminal_safe(text).into_owned(),
Shown::Value(ConfigValue::Integer(number)) => {
crate::render::js_integer(i128::from(*number))
}
Shown::Redacted(text) => (*text).to_owned(),
}
}
pub fn path(invocation: &mut Invocation, name: Option<&str>) -> Result<(), CliError> {
let view = store_of(invocation)?;
let selected = view.resolve(name, invocation.tenant_flag.as_deref())?;
invocation
.output
.data_line(&shown(&view.tenant_path(&selected.name)));
Ok(())
}
pub fn read(
invocation: &mut Invocation,
name: Option<&str>,
format: Format,
) -> Result<(), CliError> {
let view = store_of(invocation)?;
let selected = view.resolve(name, invocation.tenant_flag.as_deref())?;
let config = view.load_config(&selected)?;
invocation.output.status(&format!(
"Tenant {} (TSG {}): {}",
terminal_safe(&selected.name),
terminal_safe(&selected.tsg_id),
shown(&view.tenant_path(&selected.name))
));
let rows = config.read_rows();
if format == Format::Json {
let rows: Vec<SettingRow<'_>> = rows
.iter()
.map(|(key, value)| SettingRow { key, value })
.collect();
return invocation.output.json(&rows);
}
let cells = rows
.iter()
.map(|(key, value)| ((*key).to_owned(), shown_cell(value)))
.collect();
invocation.output.data_line(&table::key_value_table(cells));
Ok(())
}
pub fn get(
invocation: &mut Invocation,
name: &str,
key: &'static str,
format: Format,
) -> Result<(), CliError> {
let view = store_of(invocation)?;
let selected = view.resolve(Some(name), None)?;
let config = view.load_config(&selected)?;
let value = config.shown(key);
match format {
Format::Json => invocation.output.json(&[SettingRow { key, value: &value }]),
Format::Table => {
let cells = vec![(key.to_owned(), shown_cell(&value))];
invocation.output.data_line(&table::key_value_table(cells));
Ok(())
}
Format::Pretty => {
invocation
.output
.data_line(&terminal_safe(&value.display()));
Ok(())
}
}
}