use crate::log::{logln, LogColorize};
use crate::model::app::AppComponentName;
use crate::model::component::{
agent_interface_name, render_type, show_exported_functions, Component,
};
use crate::model::text::fmt::{
format_export, log_table, FieldsBuilder, MessageWithFields, MessageWithFieldsIndentMode,
TextView,
};
use cli_table::Table;
use colored::Colorize;
use golem_common::model::agent::AgentId;
use golem_wasm_ast::analysis::AnalysedType;
use indoc::indoc;
use textwrap::WordSplitter;
pub struct WorkerNameHelp;
impl MessageWithFields for WorkerNameHelp {
fn message(&self) -> String {
"Accepted agent name formats:"
.log_color_help_group()
.to_string()
}
fn fields(&self) -> Vec<(String, String)> {
let mut fields = FieldsBuilder::new();
fields.field(
"<AGENT>",
&indoc!(
"
Standalone agent name, usable when only one component is selected based on the
current application directory.
"
),
);
fields.field(
"<COMPONENT>/<AGENT>",
&indoc!(
"
Component specific agent name.
When used in an application (sub)directory then the given component name is fuzzy
matched against the application component names. If no matches are found, then
a the component name is used as is.
When used in a directory without application manifest(s), then the full component
name is expected.
"
),
);
fields.field(
"<PROJECT>/<COMPONENT>/<AGENT>",
&indoc!(
"
Project and component specific agent name.
Behaves the same as <COMPONENT>/<AGENT>, except it can refer to components in a
specific project.
"
),
);
fields.field(
"<ACCOUNT>/<PROJECT>/<COMPONENT>/<AGENT>",
&indoc!(
"
Account, project and component specific agent name.
Behaves the same as <COMPONENT>/<AGENT>, except it can refer to components in a
specific project owned by another account
"
),
);
fields.build()
}
fn indent_mode() -> MessageWithFieldsIndentMode {
MessageWithFieldsIndentMode::IdentFields
}
fn format_field_name(name: String) -> String {
name.log_color_highlight().to_string()
}
}
pub struct ComponentNameHelp;
impl MessageWithFields for ComponentNameHelp {
fn message(&self) -> String {
"Accepted component name formats:"
.log_color_help_group()
.to_string()
}
fn fields(&self) -> Vec<(String, String)> {
let mut fields = FieldsBuilder::new();
fields.field(
"<COMPONENT>",
&indoc!(
"
Standalone component name.
When used in an application (sub)directory then the given component name is fuzzy
matched against the application component names. If no matches are found, then
a the component name is used as is.
When used in a directory without application manifest(s), then the full component
name is expected.
"
),
);
fields.field(
"<PROJECT>/<COMPONENT>",
&indoc!(
"
Project specific component name.
Behaves the same as <COMPONENT>, except it can refer to components in a specific
project.
"
),
);
fields.field(
"<ACCOUNT>/<PROJECT>/<COMPONENT>",
&indoc!(
"
Account and Project specific component name.
Behaves the same as <COMPONENT>, except it can refer to components in a specific
project owned by another account.
"
),
);
fields.build()
}
fn indent_mode() -> MessageWithFieldsIndentMode {
MessageWithFieldsIndentMode::IdentFields
}
fn format_field_name(name: String) -> String {
name.log_color_highlight().to_string()
}
}
pub struct AvailableComponentNamesHelp(pub Vec<AppComponentName>);
impl TextView for AvailableComponentNamesHelp {
fn log(&self) {
if self.0.is_empty() {
logln(
"The application contains no components."
.log_color_warn()
.to_string(),
);
return;
}
logln(
"Available application components:"
.bold()
.underline()
.to_string(),
);
for component_name in &self.0 {
logln(format!(" - {component_name}"));
}
logln("");
}
}
pub struct AvailableFunctionNamesHelp {
pub component_name: String,
pub agent_name: Option<String>,
pub function_names: Vec<String>,
}
impl AvailableFunctionNamesHelp {
pub fn new(component: &Component, agent_id: Option<&AgentId>) -> Self {
AvailableFunctionNamesHelp {
component_name: component.component_name.0.clone(),
agent_name: agent_id
.as_ref()
.map(|a| a.wrapper_agent_type().to_string()),
function_names: show_exported_functions(
component.metadata.exports(),
false,
agent_id
.and_then(|agent_id| {
agent_interface_name(component, agent_id.wrapper_agent_type())
})
.as_deref(),
),
}
}
}
impl TextView for AvailableFunctionNamesHelp {
fn log(&self) {
if self.function_names.is_empty() {
match &self.agent_name {
Some(agent_name) => {
logln(
format!(
"No methods are available for agent {}.",
agent_name.underline()
)
.log_color_warn()
.to_string(),
);
}
None => {
logln(
format!(
"No functions are available for component {}.",
self.component_name.underline()
)
.log_color_warn()
.to_string(),
);
}
}
return;
}
match &self.agent_name {
Some(agent_name) => {
logln(
format!("Available method names for agent {}:", agent_name)
.bold()
.underline()
.to_string(),
);
}
None => {
logln(
format!(
"Available function names for component {}:",
self.component_name
)
.bold()
.underline()
.to_string(),
);
}
}
for function_name in &self.function_names {
logln(format!(" - {}", format_export(function_name)));
}
logln("");
}
}
pub struct AvailableAgentConstructorsHelp {
pub component_name: String,
pub constructors: Vec<String>,
}
impl TextView for AvailableAgentConstructorsHelp {
fn log(&self) {
if self.constructors.is_empty() {
logln(
format!(
"No agent constructors are available for component {}.",
self.component_name.log_color_highlight()
)
.log_color_warn()
.to_string(),
);
return;
}
logln(
format!(
"Available agent constructors for component {}:",
self.component_name
)
.bold()
.underline()
.to_string(),
);
for constructor in &self.constructors {
logln(format!(" - {}", format_export(constructor)));
}
logln("");
}
}
pub struct ArgumentError {
pub type_: Option<AnalysedType>,
pub value: Option<String>,
pub error: Option<String>,
}
#[derive(Table)]
pub struct ParameterErrorTable {
#[table(title = "Parameter type")]
pub parameter_type_: String,
#[table(title = "Argument value")]
pub argument_value: String,
#[table(title = "Error")]
pub error: String,
}
impl From<&ArgumentError> for ParameterErrorTable {
fn from(value: &ArgumentError) -> Self {
Self {
parameter_type_: textwrap::wrap(
&value.type_.as_ref().map(render_type).unwrap_or_default(),
textwrap::Options::new(30).word_splitter(WordSplitter::NoHyphenation),
)
.join("\n"),
argument_value: value.value.clone().unwrap_or_default(),
error: value.error.clone().unwrap_or_default(),
}
}
}
pub struct ParameterErrorTableView(pub Vec<ArgumentError>);
impl TextView for ParameterErrorTableView {
fn log(&self) {
log_table::<_, ParameterErrorTable>(self.0.as_slice());
}
}