use std::{path::Path, sync::Arc};
use schemars::JsonSchema;
use crate::{
GuideEntry, HumanViewDef, Middleware, OutputSchema, RuntimeGroupSpec, SchemaRegistry,
parse_guides_from_markdown,
};
pub type ModuleRegister = Arc<dyn Fn(&mut ModuleContext<'_>) -> RuntimeGroupSpec + Send + Sync>;
pub trait CommandModule: Send + Sync + std::fmt::Debug + 'static {
fn category(&self) -> String;
fn guides(&self) -> Vec<GuideEntry> {
Vec::new()
}
fn views(&self) -> Vec<HumanViewDef> {
Vec::new()
}
fn register(&self, context: &mut ModuleContext<'_>) -> RuntimeGroupSpec;
}
#[derive(Clone)]
pub struct Module {
pub category: String,
pub guides: Vec<GuideEntry>,
pub views: Vec<HumanViewDef>,
pub register: ModuleRegister,
}
impl Module {
#[must_use]
pub fn new<F>(category: impl Into<String>, register: F) -> Self
where
F: Fn(&mut ModuleContext<'_>) -> RuntimeGroupSpec + Send + Sync + 'static,
{
Self {
category: category.into(),
guides: Vec::new(),
views: Vec::new(),
register: Arc::new(register),
}
}
#[must_use]
pub fn from_command_module<M>(module: M) -> Self
where
M: CommandModule,
{
let category = module.category();
let guides = module.guides();
let views = module.views();
let module = Arc::new(module);
Self {
category,
guides,
views,
register: Arc::new(move |context| module.register(context)),
}
}
#[must_use]
pub fn with_guide(mut self, guide: GuideEntry) -> Self {
self.guides.push(guide);
self
}
#[must_use]
pub fn with_guides(mut self, guides: impl IntoIterator<Item = GuideEntry>) -> Self {
self.guides.extend(guides);
self
}
#[must_use]
pub fn with_guides_from_markdown(
self,
files: impl IntoIterator<Item = (impl AsRef<Path>, impl AsRef<[u8]>)>,
) -> Self {
self.with_guides(parse_guides_from_markdown(files))
}
#[must_use]
pub fn with_view(mut self, view: HumanViewDef) -> Self {
self.views.push(view);
self
}
}
impl std::fmt::Debug for Module {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("Module")
.field("category", &self.category)
.field("guides", &self.guides)
.field("views", &self.views)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct ModuleContext<'middleware> {
middleware: &'middleware mut Middleware,
guides: Vec<GuideEntry>,
views: Vec<HumanViewDef>,
}
impl<'middleware> ModuleContext<'middleware> {
pub(crate) fn new(middleware: &'middleware mut Middleware) -> Self {
Self {
middleware,
guides: Vec::new(),
views: Vec::new(),
}
}
pub fn middleware(&self) -> &Middleware {
self.middleware
}
pub fn middleware_mut(&mut self) -> &mut Middleware {
self.middleware
}
pub fn config(&self) -> &crate::config::ConfigFile {
&self.middleware.config
}
pub fn schema_registry(&mut self) -> &mut SchemaRegistry {
&mut self.middleware.schema_registry
}
pub fn register_schema<T: OutputSchema>(&mut self, command_path: impl Into<String>) {
self.middleware
.schema_registry
.register::<T>(command_path.into());
}
pub fn register_json_schema<T: JsonSchema>(&mut self, command_path: impl Into<String>) {
self.middleware
.schema_registry
.register_json_schema::<T>(command_path.into());
}
pub fn register_view(&mut self, view: HumanViewDef) {
self.middleware.human_views.register(view.clone());
self.views.push(view);
}
pub fn add_guide(&mut self, guide: GuideEntry) {
self.guides.push(guide);
}
pub fn add_guides(&mut self, guides: impl IntoIterator<Item = GuideEntry>) {
self.guides.extend(guides);
}
pub fn add_guides_from_markdown(
&mut self,
files: impl IntoIterator<Item = (impl AsRef<Path>, impl AsRef<[u8]>)>,
) {
self.add_guides(parse_guides_from_markdown(files));
}
pub(crate) fn into_parts(self) -> (Vec<GuideEntry>, Vec<HumanViewDef>) {
(self.guides, self.views)
}
}