shrs_core/
plugin.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Plugin System
//!
//! Plugins is a mechanism for third parties to bundle up custom functionality and distribute them
//! to other users in a way that is easily installable and configurable. Generally, plugins are
//! published as rust crates, so the user will simply include your plugin as a dependency.
//!
//! To use a plugin, you include it in [`ShellConfig`] when constructing the shell.
//! ```ignore
//! use shrs_hello_plugin::HelloPlugin;
//!
//! // Do any initialization/configuration the plugin provides
//! let hello_plugin = HelloPlugin::new();
//!
//! // Using the plugin is as easy as:
//! let myshell = ShellBuilder::default().with_plugin(hello_plugin);
//! ```
//!
//! To develop your own plugins, it's as easy as implementing the [`Plugin`] trait. The [`Plugin`]
//! trait has [`Plugin::init()`] and [`Plugin::post_init()`] methods, which allows your plugin to
//! hook into the shell initialization process, and insert additional state like custom builtins,
//! keybindings, state, hooks and much more.
//! ```
//! # use shrs_core::prelude::*;
//! # #[derive(HookEvent)]
//! # struct MyHookEvent {}
//! // Define a struct for your plugin with any configuration as it's fields
//! pub struct MyPlugin {
//!     number: u32,
//! }
//!
//! impl MyPlugin {
//!     // A common pattern is to expose some constructor to allow the user to configure the plugin
//!     pub fn new(number: u32) -> Self {
//!         Self {
//!             number
//!         }
//!     }
//! }
//!
//! impl Plugin for MyPlugin {
//!
//!     fn init(&self, config: &mut ShellConfig) -> anyhow::Result<()> {
//!         # let my_hook = |event: &MyHookEvent| -> anyhow::Result<()> {
//!         #     Ok(())
//!         # };
//!         #
//!         # let my_state = ();
//!         // Insert any state here
//!         config.hooks.insert(my_hook);
//!         config.states.insert(my_state);
//!         Ok(())
//!     }
//!
//!     fn meta(&self) -> PluginMeta {
//!         PluginMeta {
//!             name: "MyPlugin".into(),
//!             description: "My demo plugin".into(),
//!             help: None,
//!         }
//!     }
//! }
//! ```

use log::warn;

use crate::prelude::{Shell, ShellConfig, States};

/// Metadata for your plugin
#[derive(Debug)]
pub struct PluginMeta {
    /// Name of the plugin
    pub name: String,
    /// Brief description on what the plugin does
    pub description: String,
    /// Optional help message to be used by the help builtin
    pub help: Option<String>,
}

impl PluginMeta {
    /// Construct a new plugin meta data
    pub fn new<S: ToString>(name: S, description: S, help: Option<S>) -> Self {
        Self {
            name: name.to_string(),
            description: description.to_string(),
            help: help.map(|s| s.to_string()),
        }
    }
}

/// How should the plugin be handled if it errors during initialization
#[derive(Debug)]
pub enum FailMode {
    /// Display a warning but continue with shell initialization
    Warn,
    /// Abort entire shell initialization process and crash
    Abort,
}

impl Default for PluginMeta {
    fn default() -> Self {
        Self {
            name: String::from("unnamed plugin"),
            description: String::from("a plugin for shrs"),
            help: None,
        }
    }
}

/// Implement this trait to build your own plugins
pub trait Plugin {
    /// Plugin initialization
    ///
    /// Hook onto the initialization of the shell and add any hooks, functions, state variables
    /// that you would like
    fn init(&self, config: &mut ShellConfig) -> anyhow::Result<()>;

    /// Plugin post initialization
    ///
    /// Gets called once after the shell has completed initialization process, giving access to
    /// runtime shells state. This should be used if you depend on shell other state.
    fn post_init(&self, _sh: &mut Shell, _states: &mut States) -> anyhow::Result<()> {
        Ok(())
    }

    /// Return metadata related to the plugin
    fn meta(&self) -> PluginMeta {
        // TODO this is currently an optional method to make migrating all the existing plugins a
        // bit easier. Could remove the default implementation in the future
        warn!("Using default plugin metadata. Please specify this information for your plugin by implementing Plugin::meta()");
        PluginMeta::default()
    }

    /// Get the fail mode for this plugin
    ///
    /// Provide implementation for this if you want non-default behavior. See [`FailMode`].
    fn fail_mode(&self) -> FailMode {
        // Default to more strict fail mode to let users know faster there's a bug
        //
        // Should consider more how good of an idea this is
        FailMode::Abort
    }
}

/// Extension trait to make [ShellConfig] support plugins
pub trait ShellPlugin {
    fn with_plugin(&mut self, plugin: impl Plugin);
}