aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2021-05-05 11:08:51 +0000
committerFuwn <[email protected]>2021-05-05 11:08:51 +0000
commit2ef2fc8a528cb7953bc599588f09a5ad7ac62d2c (patch)
tree06a98fc1af4514c58ff14d634c1a89ec1e415be7
parentfeat(redirect_id): specify custom ip in config (diff)
downloadwhirl-2ef2fc8a528cb7953bc599588f09a5ad7ac62d2c.tar.xz
whirl-2ef2fc8a528cb7953bc599588f09a5ad7ac62d2c.zip
feat(global): add a shell-like prompt for interfacing with the server during operation
-rw-r--r--src/cli.rs2
-rw-r--r--src/lib.rs1
-rw-r--r--src/prompt/builtins.rs27
-rw-r--r--src/prompt/mod.rs98
-rw-r--r--src/prompt/structure.rs7
-rw-r--r--src/subs.rs16
6 files changed, 142 insertions, 9 deletions
diff --git a/src/cli.rs b/src/cli.rs
index d52d7d5..8fab8c2 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -18,7 +18,7 @@ impl Cli {
pub async fn execute(matches: ArgMatches<'_>) {
if matches.is_present("run") {
- run().await.unwrap();
+ run().await;
} else if let Some(cmd) = matches.subcommand_matches("config") {
if cmd.is_present("show") {
println!("{:#?}", Config::get().unwrap());
diff --git a/src/lib.rs b/src/lib.rs
index eeee8a6..662776a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -22,6 +22,7 @@ extern crate serde_derive;
pub mod cli;
pub mod config;
+pub mod prompt;
pub mod subs;
pub mod api;
diff --git a/src/prompt/builtins.rs b/src/prompt/builtins.rs
new file mode 100644
index 0000000..0ede1d3
--- /dev/null
+++ b/src/prompt/builtins.rs
@@ -0,0 +1,27 @@
+// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective
+// SPDX-License-Identifier: GPL-3.0-only
+
+use std::str::FromStr;
+
+pub enum BuiltIn {
+ Echo,
+ History,
+ Exit,
+}
+impl FromStr for BuiltIn {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "echo" => Ok(BuiltIn::Echo),
+ "history" => Ok(BuiltIn::History),
+ "exit" => Ok(BuiltIn::Exit),
+ _ => Err(()),
+ }
+ }
+}
+
+pub fn builtin_echo(args: &[String]) -> i32 {
+ println!("{}", args.join(" "));
+ 0
+}
diff --git a/src/prompt/mod.rs b/src/prompt/mod.rs
new file mode 100644
index 0000000..a1329bf
--- /dev/null
+++ b/src/prompt/mod.rs
@@ -0,0 +1,98 @@
+// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective
+// SPDX-License-Identifier: GPL-3.0-only
+
+mod builtins;
+mod structure;
+
+use std::{io, io::Write, str::FromStr};
+
+use crate::prompt::{
+ builtins::{builtin_echo, BuiltIn},
+ structure::Command,
+};
+
+pub struct Prompt;
+impl Prompt {
+ pub fn handle() {
+ Prompt::write_prompt();
+ Prompt::process_command(Prompt::tokenize_command(Prompt::read_command()));
+ }
+
+ fn write_prompt() {
+ print!("> ");
+ io::stdout().flush().unwrap();
+ }
+
+ fn read_command() -> String {
+ let mut input = String::new();
+ io::stdin()
+ .read_line(&mut input)
+ .expect("failed to read command from stdin");
+
+ input
+ }
+
+ fn tokenize_command(c: String) -> Command {
+ let mut command_split: Vec<String> = c.split_whitespace().map(|s| s.to_string()).collect();
+
+ Command {
+ keyword: command_split.remove(0),
+ args: command_split,
+ }
+ }
+
+ fn process_command(c: Command) -> i32 {
+ match BuiltIn::from_str(&c.keyword) {
+ Ok(BuiltIn::Echo) => builtin_echo(&c.args),
+ Ok(BuiltIn::Exit) => std::process::exit(0),
+ _ => {
+ println!("{}: command not found", &c.keyword);
+ 1
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tokenize_command {
+ use crate::prompt::Prompt;
+
+ #[test]
+ #[ignore]
+ fn empty_command() { assert_eq!("", Prompt::tokenize_command("".to_string()).keyword) }
+
+ #[test]
+ fn test_keyword() { assert_eq!("test", Prompt::tokenize_command("test".to_string()).keyword) }
+
+ #[test]
+ fn no_arg() { assert_eq!(0, Prompt::tokenize_command("test".to_string()).args.len()) }
+
+ #[test]
+ fn one_arg() {
+ assert_eq!(
+ 1,
+ Prompt::tokenize_command("test one".to_string()).args.len()
+ )
+ }
+
+ #[test]
+ fn multi_arg() {
+ assert_eq!(
+ 3,
+ Prompt::tokenize_command("test one two three".to_string())
+ .args
+ .len()
+ )
+ }
+
+ #[test]
+ #[ignore]
+ fn quotes() {
+ assert_eq!(
+ 2,
+ Prompt::tokenize_command("test \"one two\" three".to_string())
+ .args
+ .len()
+ )
+ }
+}
diff --git a/src/prompt/structure.rs b/src/prompt/structure.rs
new file mode 100644
index 0000000..b153cde
--- /dev/null
+++ b/src/prompt/structure.rs
@@ -0,0 +1,7 @@
+// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective
+// SPDX-License-Identifier: GPL-3.0-only
+
+pub struct Command {
+ pub keyword: String,
+ pub args: Vec<String>,
+}
diff --git a/src/subs.rs b/src/subs.rs
index df8dfd1..5e0d769 100644
--- a/src/subs.rs
+++ b/src/subs.rs
@@ -1,11 +1,10 @@
// Copyleft (ɔ) 2021-2021 The Whirlsplash Collective
// SPDX-License-Identifier: GPL-3.0-only
-use std::error::Error;
-
use crate::{
api::Api,
config::Config,
+ prompt::Prompt,
server::{
distributor::Distributor,
hub::Hub,
@@ -16,8 +15,8 @@ use crate::{
},
};
-pub async fn run() -> Result<(), Box<dyn Error>> {
- let threads = vec![
+pub async fn run() -> ! {
+ let _threads = vec![
tokio::spawn(async move {
let _ = Distributor::listen(
&*format!("0.0.0.0:{}", Config::get().unwrap().distributor.port),
@@ -36,9 +35,10 @@ pub async fn run() -> Result<(), Box<dyn Error>> {
let _ = Api::listen();
}),
];
- for thread in threads {
- let _ = thread.await;
- }
- Ok(())
+ std::thread::sleep(std::time::Duration::from_secs(2));
+ loop {
+ // TODO: Find a way to keep this persistent on the bottom row.
+ Prompt::handle();
+ }
}