summaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/cmd_system.rs85
-rw-r--r--src/cmd/cmds.rs2
-rw-r--r--src/cmd/cmds/status.rs45
-rw-r--r--src/cmd/cmds/template.rs45
-rw-r--r--src/cmd/errors.rs107
-rw-r--r--src/cmd/processer.rs40
-rw-r--r--src/cmd/renderer.rs54
7 files changed, 378 insertions, 0 deletions
diff --git a/src/cmd/cmd_system.rs b/src/cmd/cmd_system.rs
new file mode 100644
index 0000000..3edd5cc
--- /dev/null
+++ b/src/cmd/cmd_system.rs
@@ -0,0 +1,85 @@
+use serde::Serialize;
+
+use crate::{
+ cmd::{
+ errors::{CmdExecuteError, CmdPrepareError, CmdProcessError},
+ renderer::{JVRenderResult, JVResultRenderer},
+ },
+ r_println,
+};
+use std::future::Future;
+
+pub struct JVCommandContext {
+ pub help: bool,
+ pub confirmed: bool,
+}
+
+pub trait JVCommand<Argument, Input, Output, Renderer>
+where
+ Argument: clap::Parser + Send + Sync,
+ Input: Send + Sync,
+ Output: Serialize + Send + Sync,
+ Renderer: JVResultRenderer<Output> + Send + Sync,
+{
+ /// Get help string for the command
+ fn get_help_str() -> String;
+
+ /// performing any necessary post-execution processing
+ fn process(
+ args: Vec<String>,
+ ctx: JVCommandContext,
+ ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send + Sync
+ where
+ Self: Sync,
+ {
+ Self::process_with_renderer::<Renderer>(args, ctx)
+ }
+
+ /// Process the command output with a custom renderer,
+ /// performing any necessary post-execution processing
+ fn process_with_renderer<R: JVResultRenderer<Output> + Send + Sync>(
+ args: Vec<String>,
+ ctx: JVCommandContext,
+ ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send + Sync
+ where
+ Self: Sync,
+ {
+ async move {
+ let mut full_args = vec!["jv".to_string()];
+ full_args.extend(args);
+ let parsed_args = match Argument::try_parse_from(full_args) {
+ Ok(args) => args,
+ Err(_) => return Err(CmdProcessError::ParseError(Self::get_help_str())),
+ };
+ // If the help flag is used, skip execution and directly print help
+ if ctx.help {
+ let mut r = JVRenderResult::default();
+ r_println!(r, "{}", Self::get_help_str());
+ return Ok(r);
+ }
+ let input = match Self::prepare(parsed_args, ctx).await {
+ Ok(input) => input,
+ Err(e) => return Err(CmdProcessError::from(e)),
+ };
+ let output = match Self::exec(input).await {
+ Ok(output) => output,
+ Err(e) => return Err(CmdProcessError::from(e)),
+ };
+ match R::render(&output).await {
+ Ok(r) => Ok(r),
+ Err(e) => Err(CmdProcessError::from(e)),
+ }
+ }
+ }
+
+ /// Prepare to run the command,
+ /// converting Clap input into the command's supported input
+ fn prepare(
+ args: Argument,
+ ctx: JVCommandContext,
+ ) -> impl Future<Output = Result<Input, CmdPrepareError>> + Send + Sync;
+
+ /// Run the command phase,
+ /// returning an output structure, waiting for rendering
+ fn exec(args: Input) -> impl Future<Output = Result<Output, CmdExecuteError>> + Send + Sync;
+}
diff --git a/src/cmd/cmds.rs b/src/cmd/cmds.rs
new file mode 100644
index 0000000..e06480c
--- /dev/null
+++ b/src/cmd/cmds.rs
@@ -0,0 +1,2 @@
+pub mod _registry;
+pub mod status;
diff --git a/src/cmd/cmds/status.rs b/src/cmd/cmds/status.rs
new file mode 100644
index 0000000..bbc78e8
--- /dev/null
+++ b/src/cmd/cmds/status.rs
@@ -0,0 +1,45 @@
+use clap::Parser;
+use serde::Serialize;
+
+use crate::cmd::{
+ cmd_system::{JVCommand, JVCommandContext},
+ errors::{CmdExecuteError, CmdPrepareError, CmdRenderError},
+ renderer::{JVRenderResult, JVResultRenderer},
+};
+
+pub struct JVStatusCommand;
+
+#[derive(Parser, Debug)]
+pub struct JVStatusArgument;
+
+pub struct JVStatusInput;
+
+#[derive(Serialize)]
+pub struct JVStatusOutput;
+
+impl JVCommand<JVStatusArgument, JVStatusInput, JVStatusOutput, JVStatusRenderer>
+ for JVStatusCommand
+{
+ async fn prepare(
+ _args: JVStatusArgument,
+ _ctx: JVCommandContext,
+ ) -> Result<JVStatusInput, CmdPrepareError> {
+ Ok(JVStatusInput)
+ }
+
+ async fn exec(args: JVStatusInput) -> Result<JVStatusOutput, CmdExecuteError> {
+ todo!()
+ }
+
+ fn get_help_str() -> String {
+ "".to_string()
+ }
+}
+
+pub struct JVStatusRenderer;
+
+impl JVResultRenderer<JVStatusOutput> for JVStatusRenderer {
+ async fn render(data: &JVStatusOutput) -> Result<JVRenderResult, CmdRenderError> {
+ todo!()
+ }
+}
diff --git a/src/cmd/cmds/template.rs b/src/cmd/cmds/template.rs
new file mode 100644
index 0000000..8874121
--- /dev/null
+++ b/src/cmd/cmds/template.rs
@@ -0,0 +1,45 @@
+use clap::Parser;
+use serde::Serialize;
+
+use crate::subcmd::{
+ cmd::JVCommand,
+ errors::{CmdExecuteError, CmdPrepareError, CmdRenderError},
+ renderer::{JVRenderResult, JVResultRenderer},
+};
+
+pub struct JVUnknownCommand;
+
+#[derive(Parser, Debug)]
+pub struct JVUnknownArgument;
+
+pub struct JVUnknownInput;
+
+#[derive(Serialize)]
+pub struct JVUnknownOutput;
+
+impl JVCommand<JVUnknownArgument, JVUnknownInput, JVUnknownOutput, JVStatusRenderer>
+ for JVUnknownCommand
+{
+ async fn prepare(
+ _args: JVUnknownArgument,
+ _ctx: JVCommandContext,
+ ) -> Result<JVUnknownInput, CmdPrepareError> {
+ todo!()
+ }
+
+ async fn exec(_args: JVUnknownInput) -> Result<JVUnknownOutput, CmdExecuteError> {
+ todo!()
+ }
+
+ fn get_help_str() -> String {
+ "".to_string()
+ }
+}
+
+pub struct JVStatusRenderer;
+
+impl JVResultRenderer<JVUnknownOutput> for JVStatusRenderer {
+ async fn render(_data: &JVUnknownOutput) -> Result<JVRenderResult, CmdRenderError> {
+ todo!()
+ }
+}
diff --git a/src/cmd/errors.rs b/src/cmd/errors.rs
new file mode 100644
index 0000000..e1cf835
--- /dev/null
+++ b/src/cmd/errors.rs
@@ -0,0 +1,107 @@
+#[derive(thiserror::Error, Debug)]
+pub enum CmdPrepareError {
+ #[error("IO error: {0}")]
+ Io(#[from] std::io::Error),
+
+ #[error("{0}")]
+ Error(String),
+}
+
+impl CmdPrepareError {
+ pub fn new(msg: impl AsRef<str>) -> Self {
+ CmdPrepareError::Error(msg.as_ref().to_string())
+ }
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum CmdExecuteError {
+ #[error("IO error: {0}")]
+ Io(#[from] std::io::Error),
+
+ #[error("Content not prepared, cannot run")]
+ Prepare(#[from] CmdPrepareError),
+
+ #[error("{0}")]
+ Error(String),
+}
+
+impl CmdExecuteError {
+ pub fn new(msg: impl AsRef<str>) -> Self {
+ CmdExecuteError::Error(msg.as_ref().to_string())
+ }
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum CmdRenderError {
+ #[error("IO error: {0}")]
+ Io(#[from] std::io::Error),
+
+ #[error("Preparation failed, cannot render")]
+ Prepare(#[from] CmdPrepareError),
+
+ #[error("Execution failed, no output content obtained before rendering")]
+ Execute(#[from] CmdExecuteError),
+
+ #[error("{0}")]
+ Error(String),
+}
+
+impl CmdRenderError {
+ pub fn new(msg: impl AsRef<str>) -> Self {
+ CmdRenderError::Error(msg.as_ref().to_string())
+ }
+}
+
+#[derive(thiserror::Error, Debug)]
+pub enum CmdProcessError {
+ #[error("Prepare error: {0}")]
+ Prepare(#[from] CmdPrepareError),
+
+ #[error("Execute error: {0}")]
+ Execute(#[from] CmdExecuteError),
+
+ #[error("Render error: {0}")]
+ Render(#[from] CmdRenderError),
+
+ #[error("{0}")]
+ Error(String),
+
+ #[error("Node `{0}` not found!")]
+ NoNodeFound(String),
+
+ #[error("No matching command found")]
+ NoMatchingCommand,
+
+ #[error("Ambiguous command, multiple matches found")]
+ AmbiguousCommand(Vec<String>),
+
+ #[error("Parse error")]
+ ParseError(String),
+}
+
+impl CmdProcessError {
+ pub fn new(msg: impl AsRef<str>) -> Self {
+ CmdProcessError::Error(msg.as_ref().to_string())
+ }
+
+ pub fn prepare_err(&self) -> Option<&CmdPrepareError> {
+ match self {
+ CmdProcessError::Prepare(e) => Some(e),
+ _ => None,
+ }
+ }
+
+ pub fn execute_err(&self) -> Option<&CmdExecuteError> {
+ match self {
+ CmdProcessError::Execute(e) => Some(e),
+ _ => None,
+ }
+ }
+
+ pub fn render_err(&self) -> Option<&CmdRenderError> {
+ match self {
+ CmdProcessError::Render(e) => Some(e),
+ _ => None,
+ }
+ }
+}
diff --git a/src/cmd/processer.rs b/src/cmd/processer.rs
new file mode 100644
index 0000000..bc84b7d
--- /dev/null
+++ b/src/cmd/processer.rs
@@ -0,0 +1,40 @@
+use crate::cmd::cmd_system::JVCommandContext;
+use crate::cmd::cmds::_registry::{jv_cmd_nodes, jv_cmd_process_node};
+use crate::cmd::errors::CmdProcessError;
+use crate::cmd::renderer::JVRenderResult;
+
+pub async fn jv_cmd_process(
+ args: Vec<String>,
+ ctx: JVCommandContext,
+) -> Result<JVRenderResult, CmdProcessError> {
+ let nodes = jv_cmd_nodes();
+ let command = args.join(" ");
+
+ // Find nodes that match the beginning of the command
+ let matching_nodes: Vec<&String> = nodes
+ .iter()
+ .filter(|node| command.starts_with(node.as_str()))
+ .collect();
+
+ match matching_nodes.len() {
+ 0 => {
+ // No matching node found
+ return Err(CmdProcessError::NoMatchingCommand);
+ }
+ 1 => {
+ let matched_prefix = matching_nodes[0];
+ let prefix_len = matched_prefix.split_whitespace().count();
+ let trimmed_args: Vec<String> = args.into_iter().skip(prefix_len).collect();
+ return jv_cmd_process_node(matched_prefix, trimmed_args, ctx).await;
+ }
+ _ => {
+ // Multiple matching nodes found
+ return Err(CmdProcessError::AmbiguousCommand(
+ matching_nodes
+ .iter()
+ .map(|s| s.to_string())
+ .collect::<Vec<String>>(),
+ ));
+ }
+ }
+}
diff --git a/src/cmd/renderer.rs b/src/cmd/renderer.rs
new file mode 100644
index 0000000..eefa0f6
--- /dev/null
+++ b/src/cmd/renderer.rs
@@ -0,0 +1,54 @@
+use std::fmt::{Display, Formatter};
+
+use serde::Serialize;
+
+use crate::cmd::errors::CmdRenderError;
+
+pub trait JVResultRenderer<Data>
+where
+ Data: Serialize,
+{
+ fn render(
+ data: &Data,
+ ) -> impl Future<Output = Result<JVRenderResult, CmdRenderError>> + Send + Sync;
+}
+
+#[derive(Default, Debug, PartialEq)]
+pub struct JVRenderResult {
+ render_text: String,
+}
+
+impl Display for JVRenderResult {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}\n", self.render_text.trim())
+ }
+}
+
+impl JVRenderResult {
+ pub fn print(&mut self, text: &str) {
+ self.render_text.push_str(text);
+ }
+
+ pub fn println(&mut self, text: &str) {
+ self.render_text.push_str(text);
+ self.render_text.push('\n');
+ }
+
+ pub fn clear(&mut self) {
+ self.render_text.clear();
+ }
+}
+
+#[macro_export]
+macro_rules! r_print {
+ ($result:expr, $($arg:tt)*) => {
+ $result.print(&format!($($arg)*));
+ };
+}
+
+#[macro_export]
+macro_rules! r_println {
+ ($result:expr, $($arg:tt)*) => {
+ $result.println(&format!($($arg)*));
+ };
+}