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
use crate::plugins::*;
use std::any::Any;
use std::collections::HashMap;

/// A control action, which can only be taken by one plugin. When run, control actions will return an `Option<R>` on what
/// their runners return, which will be `None` if no runner is set.
pub struct ControlPluginAction<A, R> {
    /// The name of the plugin that controls this action. As this is a control action, only one plugin can manage a single action.
    controller_name: String,
    /// The single runner function for this action. This may not be defined if no plugin takes this action.
    runner: Option<Runner<A, R>>,
}
impl<A, R> PluginAction<A, R, Option<R>> for ControlPluginAction<A, R> {
    /// Runs the single registered runner for the action.
    fn run(&self, action_data: A, plugin_data: &HashMap<String, Box<dyn Any + Send>>) -> Option<R> {
        // If no runner is defined, this won't have any effect (same as functional actions with no registered runners)
        self.runner.as_ref().map(|runner| {
            runner(
                &action_data,
                // We must have data registered for every active plugin (even if it's empty)
                &**plugin_data.get(&self.controller_name).unwrap_or_else(|| {
                    panic!(
                        "no plugin data for registered plugin {}",
                        &self.controller_name
                    )
                }),
            )
        })
    }
    fn register_plugin(
        &mut self,
        name: &str,
        runner: impl Fn(&A, &(dyn Any + Send)) -> R + Send + 'static,
    ) {
        self.register_plugin_box(name, Box::new(runner))
    }
    fn register_plugin_box(&mut self, name: &str, runner: Runner<A, R>) {
        // Check if the action has already been taken by another plugin
        if self.runner.is_some() {
            // We panic here because an explicitly requested plugin couldn't be loaded, so we have to assume that any further behavior in the engine is unwanted
            // Therefore, a graceful error would be inappropriate, this is critical in every sense
            panic!("attempted to register runner from plugin '{}' for control action that already had a registered runner from plugin '{}' (these plugins conflict, see the book for further details)", name, self.controller_name);
        }

        self.controller_name = name.to_string();
        self.runner = Some(runner);
    }
}
// Using a default implementation allows us to avoid the action data having to implement `Default` as well, which is frequently infeasible
impl<A, R> Default for ControlPluginAction<A, R> {
    fn default() -> Self {
        Self {
            controller_name: String::default(),
            runner: None,
        }
    }
}
impl<A, R> std::fmt::Debug for ControlPluginAction<A, R> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ControlPluginAction")
            .field("controller_name", &self.controller_name)
            .field("runner", &self.runner.as_ref().map(|_| "Runner"))
            .finish()
    }
}

/// All the control actions that a plugin can take.
#[derive(Default, Debug)]
pub struct ControlPluginActions {
    /// Actions pertaining to the modification of settings created with `PerseusApp`.
    pub settings_actions: ControlPluginSettingsActions,
    /// Actions pertaining to the build process.
    pub build_actions: ControlPluginBuildActions,
    /// Actions pertaining to the export process.
    pub export_actions: ControlPluginExportActions,
    /// Actions pertaining to the server.
    pub server_actions: ControlPluginServerActions,
    /// Actions pertaining to the client-side code.
    pub client_actions: ControlPluginClientActions,
}

/// Control actions that pertain to altering settings from `PerseusApp`.
#[derive(Default, Debug)]
pub struct ControlPluginSettingsActions {
    /// Sets an immutable store to be used everywhere. This will provided the current immutable store for reference.
    pub set_immutable_store:
        ControlPluginAction<crate::stores::ImmutableStore, crate::stores::ImmutableStore>,
    /// Sets the locales to be used everywhere, providing the current ones for reference.
    pub set_locales: ControlPluginAction<crate::i18n::Locales, crate::i18n::Locales>,
    /// Sets the app root to be used everywhere. This must correspond to the ID of an empty HTML `div`.
    pub set_app_root: ControlPluginAction<(), String>,
    /// Actions pertaining to the HTML shell, partitioned away for deliberate inconvenience (you should almost never use these).
    pub html_shell_actions: ControlPluginHtmlShellActions,
}
/// Control actions that pertain to the HTML shell. Note that these actions should be used extremely sparingly, as they are very rarely needed (see the available functional actions
/// for the HTML shell), and they can have confusing side effects for CSS hierarchies, as well as potentially interrupting Perseus' interpolation processes. Changing certain things
/// with these may break Perseus completely in certain cases!
#[derive(Default, Debug)]
pub struct ControlPluginHtmlShellActions {
    /// Overrides whatever the user provided as their HTML shell completely. Whatever you provide here MUST contain a `<head>` and a `<body>` at least, or Perseus will completely fail.
    pub set_shell: ControlPluginAction<(), String>,
}
/// Control actions that pertain to the build process.
#[derive(Default, Debug)]
pub struct ControlPluginBuildActions {}
/// Control actions that pertain to the export process.
#[derive(Default, Debug)]
pub struct ControlPluginExportActions {}
/// Control actions that pertain to the server.
#[derive(Default, Debug)]
pub struct ControlPluginServerActions {}
/// Control actions that pertain to the client-side code. As yet, there are none of these.
#[derive(Default, Debug)]
pub struct ControlPluginClientActions {}