aboutsummaryrefslogtreecommitdiff
path: root/mingling_core/src/program.rs
blob: 11e1bbf1dbd85a5615b16474e8c3b045c42c0d83 (plain) (blame)
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#[cfg(not(windows))]
use std::env;

use crate::{
    AnyOutput, GlobalResources, asset::dispatcher::Dispatcher, error::ChainProcessError,
    hook::ProgramHook,
};
use std::{
    collections::HashMap,
    sync::{Arc, Mutex},
};

#[doc(hidden)]
pub mod error;
#[doc(hidden)]
pub mod exec;
#[doc(hidden)]
pub mod setup;

pub mod hook;

mod collection;
pub use collection::*;

mod once_exec;

#[cfg(feature = "repl")]
#[doc(hidden)]
pub mod repl_exec;

mod single_instance;
pub use single_instance::*;

mod config;
pub use config::*;

mod flag;
pub use flag::*;

mod string_vec;
pub use string_vec::*;

/// Program, used to define the behavior of the entire command-line program
#[derive(Default)]
pub struct Program<C>
where
    C: ProgramCollect<Enum = C>,
{
    pub(crate) collect: std::marker::PhantomData<C>,

    pub(crate) args: Vec<String>,

    #[cfg(not(feature = "dispatch_tree"))]
    pub(crate) dispatcher: Vec<Box<dyn Dispatcher<C> + Send + Sync>>,

    /// Program stdout settings.
    ///
    /// This struct controls the program's output behavior, including whether
    /// to output errors, render results, suppress panic messages, enable
    /// verbose/quiet/debug modes, colored output, and progress indicators.
    ///
    /// # Convention-only fields
    ///
    /// Fields marked with convention only are not enforced by the framework;
    /// they serve as a shared convention for applications to follow consistently.
    pub stdout_setting: ProgramStdoutSetting,

    /// User-defined context that can be accessed throughout the program.
    ///
    /// This field holds a user-defined context value that can be
    /// accessed and modified at any point during program execution. It is
    /// useful for passing shared state or configuration across different
    /// parts of the program.
    pub user_context: ProgramUserContext,

    #[cfg(feature = "structural_renderer")]
    /// Setting for the structural renderer.
    ///
    /// This field is only available when the `structural_renderer` feature is enabled.
    pub structural_renderer_name: StructuralRendererSetting,

    pub(crate) hooks: Vec<ProgramHook<C>>,

    pub(crate) resources: GlobalResources,
}

impl<C> Program<C>
where
    C: ProgramCollect<Enum = C>,
{
    /// Creates a new Program instance, initializing command-line arguments from the environment.
    #[must_use]
    pub fn new() -> Self {
        #[cfg(not(windows))]
        return Self::new_with_args(env::args().collect::<Vec<String>>());

        #[cfg(windows)]
        return Self::new_with_args({
            std::env::args_os()
                .map(|arg| {
                    use std::os::windows::ffi::OsStrExt;

                    let wide: Vec<u16> = arg.encode_wide().collect();
                    String::from_utf16_lossy(&wide)
                })
                .collect::<Vec<_>>()
        });
    }

    /// Creates a new Program instance with the provided command-line arguments.
    pub fn new_with_args(args: impl Into<StringVec>) -> Self {
        Program {
            collect: std::marker::PhantomData,
            args: args.into().into(),

            #[cfg(not(feature = "dispatch_tree"))]
            dispatcher: Vec::new(),

            stdout_setting: ProgramStdoutSetting::default(),
            user_context: ProgramUserContext::default(),

            #[cfg(feature = "structural_renderer")]
            structural_renderer_name: StructuralRendererSetting::Disable,

            hooks: Vec::new(),

            resources: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    /// Returns a reference to the current program instance, if set.
    ///
    /// # Panics
    ///
    /// Panics if the program has not been initialized yet.
    pub fn this_program() -> &'static Program<C>
    where
        C: 'static,
    {
        THIS_PROGRAM
            .get_raw()
            .unwrap()
            .downcast_ref::<Program<C>>()
            .unwrap()
    }

    /// Returns a reference to the program's command-line arguments.
    #[must_use]
    pub fn get_args(&self) -> &[String] {
        &self.args
    }

    /// Returns a mutable reference to the program's command-line arguments.
    #[must_use]
    pub fn get_args_mut(&mut self) -> &mut [String] {
        &mut self.args
    }

    /// Takes ownership of the program's command-line arguments, replacing them with an empty Vec.
    /// This is useful when you need to transfer the arguments to another context or process them
    /// and then replace them later.
    #[must_use]
    pub fn take_args(&mut self) -> Vec<String> {
        std::mem::take(&mut self.args)
    }

    /// Replaces the program's command-line arguments with a new set and returns the old ones.
    ///
    /// # Arguments
    ///
    /// * `args` - The new command-line arguments to set.
    ///
    /// # Returns
    ///
    /// The previous command-line arguments.
    pub fn replace_args(&mut self, args: Vec<String>) -> Vec<String> {
        std::mem::replace(&mut self.args, args)
    }

    /// Get all registered dispatcher names from the program
    #[must_use]
    pub fn get_nodes(
        &'static self,
    ) -> Vec<(String, &'static (dyn Dispatcher<C> + Send + Sync + 'static))> {
        get_nodes(self)
    }

    /// Dynamically dispatch input arguments to registered entry types
    ///
    /// # Errors
    ///
    /// Returns `Err(ChainProcessError)` if the dispatch fails,
    /// e.g., if no dispatcher is found for the given arguments.
    pub fn dispatch_args_dynamic(
        &'static self,
        args: impl Into<StringVec>,
    ) -> Result<AnyOutput<C>, ChainProcessError> {
        let sv: Vec<String> = args.into().into();
        match exec::dispatch_args_dynamic(self, &sv) {
            Ok(ok) => Ok(ok),
            Err(e) => Err(e.into()),
        }
    }

    /// Use a prefix tree to quickly match arguments and dispatch to an Entry
    #[cfg(feature = "dispatch_tree")]
    pub fn dispatch_args_trie(
        &'static self,
        args: impl Into<StringVec>,
    ) -> Result<AnyOutput<C>, ChainProcessError> {
        let string_vec: Vec<String> = args.into().into();
        match C::dispatch_args_trie(&string_vec) {
            Ok(ok) => Ok(ok),
            Err(e) => Err(e.into()),
        }
    }
}

/// Get all registered dispatcher names from the program
#[allow(unused_variables)]
#[must_use]
pub fn get_nodes<C: ProgramCollect<Enum = C>>(
    program: &'static Program<C>,
) -> Vec<(String, &'static (dyn Dispatcher<C> + Send + Sync + 'static))> {
    #[cfg(feature = "dispatch_tree")]
    let r = C::get_nodes();

    #[cfg(feature = "dispatch_tree")]
    {
        #[cfg(feature = "debug")]
        {
            let node_strs: Vec<String> = r.iter().map(|v| v.0.clone()).collect();
            crate::info!("All Nodes: [{}]", node_strs.join(", "));
        }
    }

    #[cfg(not(feature = "dispatch_tree"))]
    let r: Vec<_> = program
        .dispatcher
        .iter()
        .map(|disp| {
            let node_str = disp
                .node()
                .to_string()
                .split('.')
                .collect::<Vec<_>>()
                .join(" ");
            (node_str, &**disp)
        })
        .collect();

    #[cfg(not(feature = "dispatch_tree"))]
    {
        #[cfg(feature = "debug")]
        {
            let node_strs: Vec<String> = r.iter().map(|v| v.0.clone()).collect();
            crate::info!("All Nodes: [{}]", node_strs.join(", "));
        }
    }

    r
}