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
mod dump;
mod factorio_control;
mod get_setting;
#[cfg(all(debug_assertions, feature = "gui"))]
mod gui;
mod quit;
mod rcon_send;
#[cfg(feature = "restapi")]
mod restapi_control;
#[cfg(feature = "lua")]
mod run_script;
mod set_setting;

use crate::context::Context;
use crate::{paths, APP_ABOUT, APP_NAME};
use factorio_bot_core::miette;
use factorio_bot_core::miette::{miette, IntoDiagnostic};
use reedline_repl_rs::{yansi::Paint, Repl};
use std::fmt;

fn subcommands() -> Vec<Box<dyn Subcommand>> {
  vec![
    factorio_control::build(),
    #[cfg(all(debug_assertions, feature = "gui"))]
    gui::build(),
    #[cfg(feature = "lua")]
    run_script::build(),
    rcon_send::build(),
    #[cfg(feature = "restapi")]
    restapi_control::build(),
    set_setting::build(),
    get_setting::build(),
    quit::build(),
    dump::build(),
  ]
}

pub async fn start(context: Context) -> miette::Result<()> {
  let instance_state = context.instance_state.clone();
  let mut repl: Repl<Context, Error> = Repl::new(context)
    .with_name(APP_NAME)
    .with_description(APP_ABOUT)
    .with_version(env!("CARGO_PKG_VERSION"))
    .with_prompt("repl")
    .with_history(paths::data_local_dir().join("repl_history"), 50)
    .with_on_after_command_async(|context| Box::pin(update_prompt(context)));
  for subcommand in subcommands() {
    repl = subcommand.build_command(repl);
  }
  repl.run_async().await.into_diagnostic()?;
  let mut instance_state = instance_state.write().await;
  if let Some(instance_state) = instance_state.take() {
    instance_state.stop()?;
  }
  Ok(())
}

async fn update_prompt(context: &mut Context) -> Result<Option<String>> {
  let instance_state = context.instance_state.read().await;
  let mut prompt = "repl".to_owned();
  if instance_state.is_some() {
    prompt += &Box::new(Paint::blue(" [running]").bold()).to_string();
  };
  Ok(Some(prompt))
}

pub trait Subcommand {
  fn name(&self) -> &str;
  fn build_command(&self, repl: Repl<Context, Error>) -> Repl<Context, Error>;
}

pub struct Error(miette::Error);
pub type Result<T> = std::result::Result<T, Error>;

impl From<reedline_repl_rs::Error> for Error {
  fn from(e: reedline_repl_rs::Error) -> Self {
    Self(miette!(e.to_string()))
  }
}
impl From<miette::Error> for Error {
  fn from(e: miette::Error) -> Self {
    Self(e)
  }
}
impl fmt::Display for Error {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{}", self.0)
  }
}
impl fmt::Debug for Error {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "{}", self.0)
  }
}