aboutsummaryrefslogtreecommitdiff
path: root/mingling_core/src/program/exec.rs
blob: cdbb2b07cd84303c9ba5f6da439d9fc2d9e71576 (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
#![allow(clippy::borrowed_box)]

use std::fmt::Display;

use crate::{
    AnyOutput, ChainProcess, Dispatcher, Next, Program, ProgramCollect, RenderResult,
    error::ProgramInternalExecuteError,
};

pub mod error;

pub async fn exec<C, G>(program: Program<C, G>) -> Result<RenderResult, ProgramInternalExecuteError>
where
    C: ProgramCollect<Enum = G>,
    G: Display,
{
    let mut current;

    // Match user input
    match match_user_input(&program) {
        Ok((dispatcher, args)) => {
            // Entry point
            current = match dispatcher.begin(args) {
                ChainProcess::Ok((any, Next::Renderer)) => return Ok(render::<C, G>(any)),
                ChainProcess::Ok((any, Next::Chain)) => any,
                ChainProcess::Err(e) => return Err(e.into()),
            };
        }
        Err(ProgramInternalExecuteError::DispatcherNotFound) => {
            // No matching Dispatcher is found
            return Err(ProgramInternalExecuteError::DispatcherNotFound);
        }
        Err(e) => return Err(e),
    };

    loop {
        current = {
            // If a chain exists, execute as a chain
            if C::has_chain(&current) {
                match C::do_chain(current).await {
                    ChainProcess::Ok((any, Next::Renderer)) => return Ok(render::<C, G>(any)),
                    ChainProcess::Ok((any, Next::Chain)) => any,
                    ChainProcess::Err(e) => return Err(e.into()),
                }
            }
            // If no chain exists, attempt to render
            else if C::has_renderer(&current) {
                let mut render_result = RenderResult::default();
                C::render(current, &mut render_result);
                return Ok(render_result);
            }
            // No renderer exists
            else {
                let renderer_name = current.member_id.to_string();
                return Err(ProgramInternalExecuteError::RendererNotFound(renderer_name));
            }
        };
    }
}

/// Match user input against registered dispatchers and return the matched dispatcher and remaining arguments.
fn match_user_input<C, G>(
    program: &Program<C, G>,
) -> Result<(&Box<dyn Dispatcher<G>>, Vec<String>), ProgramInternalExecuteError>
where
    C: ProgramCollect<Enum = G>,
    G: Display,
{
    let nodes = get_nodes(program);
    let command = format!("{} ", program.args.join(" "));

    // Find all nodes that match the command prefix
    let matching_nodes: Vec<&(String, &Box<dyn Dispatcher<G>>)> = nodes
        .iter()
        // Also add a space to the node string to ensure consistent matching logic
        .filter(|(node_str, _)| command.starts_with(&format!("{} ", node_str)))
        .collect();

    match matching_nodes.len() {
        0 => {
            // No matching node found
            Err(ProgramInternalExecuteError::DispatcherNotFound)
        }
        1 => {
            let matched_prefix = matching_nodes[0];
            let prefix_len = matched_prefix.0.split_whitespace().count();
            let trimmed_args: Vec<String> = program.args.iter().skip(prefix_len).cloned().collect();
            Ok((matched_prefix.1, trimmed_args))
        }
        _ => {
            // Multiple matching nodes found
            // Find the node with the longest length (most specific match)
            let matched_prefix = matching_nodes
                .iter()
                .max_by_key(|node| node.0.len())
                .unwrap();

            let prefix_len = matched_prefix.0.split_whitespace().count();
            let trimmed_args: Vec<String> = program.args.iter().skip(prefix_len).cloned().collect();
            Ok((matched_prefix.1, trimmed_args))
        }
    }
}

#[inline(always)]
fn render<C: ProgramCollect<Enum = G>, G: Display>(any: AnyOutput<G>) -> RenderResult {
    let mut render_result = RenderResult::default();
    C::render(any, &mut render_result);
    render_result
}

// Get all registered dispatcher names from the program
fn get_nodes<C: ProgramCollect<Enum = G>, G: Display>(
    program: &Program<C, G>,
) -> Vec<(String, &Box<dyn Dispatcher<G>>)> {
    program
        .dispatcher
        .iter()
        .map(|disp| {
            let node_str = disp
                .node()
                .to_string()
                .split('.')
                .collect::<Vec<_>>()
                .join(" ");
            (node_str, disp)
        })
        .collect()
}