Add CLI handling, add command to generate/test configuration
This commit is contained in:
parent
fdc3c2ea3c
commit
66faac396a
257
Cargo.lock
generated
257
Cargo.lock
generated
@ -17,6 +17,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
@ -64,6 +70,37 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.0.0-beta.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
"clap_derive",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"os_str_bytes",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.0.0-beta.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
@ -73,6 +110,17 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"lazy_static",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.1"
|
||||
@ -82,6 +130,72 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
@ -113,6 +227,12 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
@ -181,6 +301,21 @@ dependencies = [
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
@ -199,17 +334,43 @@ dependencies = [
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazymc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"clap",
|
||||
"colored",
|
||||
"derive_builder",
|
||||
"dotenv",
|
||||
"futures",
|
||||
"libc",
|
||||
@ -217,6 +378,7 @@ dependencies = [
|
||||
"minecraft-protocol",
|
||||
"pretty_env_logger",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"toml",
|
||||
]
|
||||
@ -339,6 +501,15 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.7"
|
||||
@ -361,6 +532,30 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.32"
|
||||
@ -563,6 +758,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.81"
|
||||
@ -583,6 +784,35 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.13.0"
|
||||
@ -622,6 +852,27 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
@ -638,6 +889,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
13
Cargo.toml
13
Cargo.toml
@ -19,15 +19,18 @@ exclude = [
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bytes = "1.1"
|
||||
clap = "3.0.0-beta.5"
|
||||
colored = "2.0"
|
||||
derive_builder = "0.10"
|
||||
dotenv = "0.15"
|
||||
futures = { version = "0.3", default-features = false }
|
||||
libc = "0.2"
|
||||
minecraft-protocol = { git = "https://github.com/timvisee/minecraft-protocol", rev = "4348c27" }
|
||||
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "macros", "time", "process", "signal"] }
|
||||
|
||||
dotenv = "0.15"
|
||||
log = "0.4"
|
||||
minecraft-protocol = { git = "https://github.com/timvisee/minecraft-protocol", rev = "4348c27" }
|
||||
pretty_env_logger = "0.4"
|
||||
|
||||
serde = "1.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "macros", "time", "process", "signal"] }
|
||||
toml = "0.5"
|
||||
|
@ -5,19 +5,19 @@
|
||||
#
|
||||
# Public IP and port users will connect to.
|
||||
# Shows sleeping status, starts server on connect, and proxies to ingress server.
|
||||
address_egress = "0.0.0.0:25565"
|
||||
address = "0.0.0.0:25565"
|
||||
|
||||
[server]
|
||||
# Server directory.
|
||||
directory = "."
|
||||
|
||||
# Command to start the server.
|
||||
command = "java -Xms1G -Xmx1G -jar server.jar --nogui"
|
||||
command = "java -Xmx1G -Xms1G -jar server.jar --nogui"
|
||||
|
||||
# Ingress address.
|
||||
#
|
||||
# Internal IP and port of server started by lazymc to proxy to.
|
||||
address_ingress = "127.0.0.1:25566"
|
||||
address = "127.0.0.1:25566"
|
||||
|
||||
[time]
|
||||
# Sleep after number of seconds.
|
||||
|
39
src/action/config_generate.rs
Normal file
39
src/action/config_generate.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
||||
use crate::util::cli::prompt_yes;
|
||||
use crate::util::error::{quit, quit_error, ErrorHintsBuilder};
|
||||
|
||||
/// Invoke config test command.
|
||||
pub fn invoke(matches: &ArgMatches) {
|
||||
// Get config path, attempt to canonicalize
|
||||
let mut path = PathBuf::from(matches.value_of("config").unwrap());
|
||||
if let Ok(p) = path.canonicalize() {
|
||||
path = p;
|
||||
}
|
||||
|
||||
// Confirm to overwrite if it exists
|
||||
if path.is_file() {
|
||||
if !prompt_yes(
|
||||
&format!(
|
||||
"Config file already exists, overwrite?\nPath: {}",
|
||||
path.to_str().unwrap_or("?")
|
||||
),
|
||||
Some(true),
|
||||
) {
|
||||
quit();
|
||||
}
|
||||
}
|
||||
|
||||
// Generate file
|
||||
if let Err(err) = fs::write(&path, include_bytes!("../../res/lazymc.toml")) {
|
||||
quit_error(
|
||||
anyhow!(err).context("Failed to generate config file"),
|
||||
ErrorHintsBuilder::default().build().unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
eprintln!("Config saved at: {}", path.to_str().unwrap_or("?"));
|
||||
}
|
38
src/action/config_test.rs
Normal file
38
src/action/config_test.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::util::error::{quit_error, quit_error_msg, ErrorHintsBuilder};
|
||||
|
||||
/// Invoke config test command.
|
||||
pub fn invoke(matches: &ArgMatches) {
|
||||
// Get config path, attempt to canonicalize
|
||||
let mut path = PathBuf::from(matches.value_of("config").unwrap());
|
||||
if let Ok(p) = path.canonicalize() {
|
||||
path = p;
|
||||
}
|
||||
|
||||
// Ensure it exists
|
||||
if !path.is_file() {
|
||||
quit_error_msg(
|
||||
format!("Config file does not exist at: {}", path.to_str().unwrap()),
|
||||
ErrorHintsBuilder::default().build().unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// Try to load config
|
||||
let _config = match Config::load(path) {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
quit_error(
|
||||
anyhow!(err).context("Failed to load and parse config"),
|
||||
ErrorHintsBuilder::default().build().unwrap(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: do additional config tests: server dir correct, command set?
|
||||
|
||||
eprintln!("Config loaded successfully!");
|
||||
}
|
3
src/action/mod.rs
Normal file
3
src/action/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod config_generate;
|
||||
pub mod config_test;
|
||||
pub mod start;
|
1
src/action/start.rs
Normal file
1
src/action/start.rs
Normal file
@ -0,0 +1 @@
|
||||
// Placeholder
|
33
src/cli.rs
Normal file
33
src/cli.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use clap::{App, AppSettings, Arg};
|
||||
|
||||
/// The clap app for CLI argument parsing.
|
||||
pub fn app() -> App<'static> {
|
||||
App::new(crate_name!())
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
.about(crate_description!())
|
||||
.subcommand(
|
||||
App::new("start")
|
||||
.alias("run")
|
||||
.about("Start lazymc and server (default)"),
|
||||
)
|
||||
.subcommand(
|
||||
App::new("config")
|
||||
.alias("cfg")
|
||||
.about("Config actions")
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
.subcommand(App::new("generate").alias("gen").about("Generate config"))
|
||||
.subcommand(App::new("test").about("Test config")),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("config")
|
||||
.short('c')
|
||||
.alias("cfg")
|
||||
.long("config")
|
||||
.global(true)
|
||||
.value_name("FILE")
|
||||
.default_value(crate::config::CONFIG_FILE)
|
||||
.about("Use config file")
|
||||
.takes_value(true),
|
||||
)
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Default configuration file location.
|
||||
const CONFIG_FILE: &str = "lazymc.toml";
|
||||
pub const CONFIG_FILE: &str = "lazymc.toml";
|
||||
|
||||
/// Configuration.
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -26,8 +26,8 @@ pub struct Config {
|
||||
|
||||
impl Config {
|
||||
/// Load configuration form file.
|
||||
pub fn load() -> Result<Self, io::Error> {
|
||||
let data = fs::read(CONFIG_FILE)?;
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
|
||||
let data = fs::read(path)?;
|
||||
let config = toml::from_slice(&data)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
92
src/main.rs
92
src/main.rs
@ -1,17 +1,28 @@
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
#[macro_use]
|
||||
extern crate derive_builder;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub(crate) mod action;
|
||||
pub(crate) mod cli;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod monitor;
|
||||
pub(crate) mod proto;
|
||||
pub(crate) mod server;
|
||||
pub(crate) mod types;
|
||||
pub(crate) mod util;
|
||||
|
||||
use std::error::Error;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bytes::BytesMut;
|
||||
use clap::{App, ArgMatches};
|
||||
use futures::FutureExt;
|
||||
use minecraft_protocol::data::chat::{Message, Payload};
|
||||
use minecraft_protocol::data::server_status::*;
|
||||
@ -24,10 +35,12 @@ use tokio::io;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use crate::util::error::{quit_error, quit_error_msg, ErrorHints, ErrorHintsBuilder};
|
||||
use config::*;
|
||||
use proto::{Client, ClientState, RawPacket, PROTO_DEFAULT_PROTOCOL, PROTO_DEFAULT_VERSION};
|
||||
use server::ServerState;
|
||||
|
||||
/// Main entrypoint.
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), ()> {
|
||||
// Initialize logging
|
||||
@ -35,16 +48,79 @@ async fn main() -> Result<(), ()> {
|
||||
let _ = dotenv::dotenv();
|
||||
pretty_env_logger::init();
|
||||
|
||||
// Build clap app, invoke intended action
|
||||
let app = cli::app();
|
||||
invoke_action(app).await
|
||||
}
|
||||
|
||||
/// Invoke an action.
|
||||
async fn invoke_action<'a>(app: App<'a>) -> Result<(), ()> {
|
||||
let matches = app.get_matches();
|
||||
|
||||
// Config operations
|
||||
if let Some(ref matches) = matches.subcommand_matches("config") {
|
||||
if let Some(ref matches) = matches.subcommand_matches("generate") {
|
||||
action::config_generate::invoke(matches);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(ref matches) = matches.subcommand_matches("test") {
|
||||
action::config_test::invoke(matches);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
unimplemented!("Config logic here!");
|
||||
}
|
||||
|
||||
// Start server
|
||||
start(&matches).await
|
||||
}
|
||||
|
||||
/// Load configuration file.
|
||||
fn load_config(matches: &ArgMatches) -> Config {
|
||||
// Get config path, attempt to canonicalize
|
||||
let mut path = PathBuf::from(matches.value_of("config").unwrap());
|
||||
if let Ok(p) = path.canonicalize() {
|
||||
path = p;
|
||||
}
|
||||
|
||||
// Ensure configuration file exists
|
||||
if !path.is_file() {
|
||||
quit_error_msg(
|
||||
format!(
|
||||
"Conig file does not exist: {}",
|
||||
path.to_str().unwrap_or("?")
|
||||
),
|
||||
ErrorHintsBuilder::default()
|
||||
.config(true)
|
||||
.config_generate(true)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// Load config
|
||||
let config = match Config::load() {
|
||||
Ok(config) => Arc::new(config),
|
||||
let config = match Config::load(path) {
|
||||
Ok(config) => config,
|
||||
Err(err) => {
|
||||
error!("Failed to load configuration:");
|
||||
error!("{}", err);
|
||||
return Err(());
|
||||
quit_error(
|
||||
anyhow!(err).context("Failed to load config"),
|
||||
ErrorHintsBuilder::default()
|
||||
.config(true)
|
||||
.config_test(true)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
/// Start lazymc.
|
||||
async fn start(matches: &ArgMatches) -> Result<(), ()> {
|
||||
// Load config and server state
|
||||
let config = Arc::new(load_config(matches));
|
||||
let server_state = Arc::new(ServerState::default());
|
||||
|
||||
// Listen for new connections
|
||||
@ -52,8 +128,10 @@ async fn main() -> Result<(), ()> {
|
||||
let listener = TcpListener::bind(config.public.address)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to start: {}", err);
|
||||
()
|
||||
quit_error(
|
||||
anyhow!(err).context("Failed to start proxy server"),
|
||||
ErrorHints::default(),
|
||||
);
|
||||
})?;
|
||||
|
||||
info!(
|
||||
|
89
src/util/cli.rs
Normal file
89
src/util/cli.rs
Normal file
@ -0,0 +1,89 @@
|
||||
// Allow dead code, until we've fully implemented CLI/error handling
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::io::{stderr, stdin, Write};
|
||||
|
||||
use crate::util::error::{quit_error, ErrorHints};
|
||||
|
||||
/// Prompt the user to enter some value.
|
||||
/// The prompt that is shown should be passed to `msg`,
|
||||
/// excluding the `:` suffix.
|
||||
pub fn prompt(msg: &str) -> String {
|
||||
// Show the prompt
|
||||
eprint!("{}: ", msg);
|
||||
let _ = stderr().flush();
|
||||
|
||||
// Get the input
|
||||
let mut input = String::new();
|
||||
if let Err(err) = stdin()
|
||||
.read_line(&mut input)
|
||||
.map_err(|err| -> anyhow::Error { err.into() })
|
||||
{
|
||||
quit_error(
|
||||
err.context("failed to read input from prompt"),
|
||||
ErrorHints::default(),
|
||||
);
|
||||
}
|
||||
|
||||
// Trim and return
|
||||
input.trim().to_owned()
|
||||
}
|
||||
|
||||
/// Prompt the user for a question, allowing a yes or now answer.
|
||||
/// True is returned if yes was answered, false if no.
|
||||
///
|
||||
/// A default may be given, which is chosen if no-interact mode is
|
||||
/// enabled, or if enter was pressed by the user without entering anything.
|
||||
pub fn prompt_yes(msg: &str, def: Option<bool>) -> bool {
|
||||
// Define the available options string
|
||||
let options = format!(
|
||||
"[{}/{}]",
|
||||
match def {
|
||||
Some(def) if def => "Y",
|
||||
_ => "y",
|
||||
},
|
||||
match def {
|
||||
Some(def) if !def => "N",
|
||||
_ => "n",
|
||||
}
|
||||
);
|
||||
|
||||
// Get the user input
|
||||
let answer = prompt(&format!("{} {}", msg, options));
|
||||
|
||||
// Assume the default if the answer is empty
|
||||
if answer.is_empty() && def.is_some() {
|
||||
return def.unwrap();
|
||||
}
|
||||
|
||||
// Derive a boolean and return
|
||||
match derive_bool(&answer) {
|
||||
Some(answer) => answer,
|
||||
None => prompt_yes(msg, def),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to derive true or false (yes or no) from the given input.
|
||||
/// None is returned if no boolean could be derived accurately.
|
||||
fn derive_bool(input: &str) -> Option<bool> {
|
||||
// Process the input
|
||||
let input = input.trim().to_lowercase();
|
||||
|
||||
// Handle short or incomplete answers
|
||||
match input.as_str() {
|
||||
"y" | "ye" | "t" | "1" => return Some(true),
|
||||
"n" | "f" | "0" => return Some(false),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Handle complete answers with any suffix
|
||||
if input.starts_with("yes") || input.starts_with("true") {
|
||||
return Some(true);
|
||||
}
|
||||
if input.starts_with("no") || input.starts_with("false") {
|
||||
return Some(false);
|
||||
}
|
||||
|
||||
// The answer could not be determined, return none
|
||||
None
|
||||
}
|
193
src/util/error.rs
Normal file
193
src/util/error.rs
Normal file
@ -0,0 +1,193 @@
|
||||
// Allow dead code, until we've fully implemented CLI/error handling
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::io::{self, Write};
|
||||
pub use std::process::exit;
|
||||
|
||||
use anyhow::anyhow;
|
||||
|
||||
use crate::util::style::{highlight, highlight_error, highlight_info, highlight_warning};
|
||||
|
||||
/// Print the given error in a proper format for the user,
|
||||
/// with it's causes.
|
||||
pub fn print_error(err: anyhow::Error) {
|
||||
// Report each printable error, count them
|
||||
let count = err
|
||||
.chain()
|
||||
.map(|err| format!("{}", err))
|
||||
.filter(|err| !err.is_empty())
|
||||
.enumerate()
|
||||
.map(|(i, err)| {
|
||||
if i == 0 {
|
||||
eprintln!("{} {}", highlight_error("error:"), err);
|
||||
} else {
|
||||
eprintln!("{} {}", highlight_error("caused by:"), err);
|
||||
}
|
||||
})
|
||||
.count();
|
||||
|
||||
// Fall back to a basic message
|
||||
if count == 0 {
|
||||
eprintln!(
|
||||
"{} {}",
|
||||
highlight_error("error:"),
|
||||
"an undefined error occurred"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Print the given error message in a proper format for the user,
|
||||
/// with it's causes.
|
||||
pub fn print_error_msg<S>(err: S)
|
||||
where
|
||||
S: AsRef<str> + Display + Debug + Sync + Send + 'static,
|
||||
{
|
||||
print_error(anyhow!(err));
|
||||
}
|
||||
|
||||
/// Print a warning.
|
||||
pub fn print_warning<S>(err: S)
|
||||
where
|
||||
S: AsRef<str> + Display + Debug + Sync + Send + 'static,
|
||||
{
|
||||
eprintln!("{} {}", highlight_warning("warning:"), err);
|
||||
}
|
||||
|
||||
/// Quit the application regularly.
|
||||
pub fn quit() -> ! {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/// Quit the application with an error code,
|
||||
/// and print the given error.
|
||||
pub fn quit_error(err: anyhow::Error, hints: impl Borrow<ErrorHints>) -> ! {
|
||||
// Print the error
|
||||
print_error(err);
|
||||
|
||||
// Print error hints
|
||||
hints.borrow().print(false);
|
||||
|
||||
// Quit
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/// Quit the application with an error code,
|
||||
/// and print the given error message.
|
||||
pub fn quit_error_msg<S>(err: S, hints: impl Borrow<ErrorHints>) -> !
|
||||
where
|
||||
S: AsRef<str> + Display + Debug + Sync + Send + 'static,
|
||||
{
|
||||
quit_error(anyhow!(err), hints);
|
||||
}
|
||||
|
||||
/// The error hint configuration.
|
||||
#[derive(Clone, Builder)]
|
||||
#[builder(default)]
|
||||
pub struct ErrorHints {
|
||||
/// A list of info messages to print along with the error.
|
||||
info: Vec<String>,
|
||||
|
||||
/// Show about the config flag.
|
||||
config: bool,
|
||||
|
||||
/// Show about the config generate command.
|
||||
config_generate: bool,
|
||||
|
||||
/// Show about the config test command.
|
||||
config_test: bool,
|
||||
|
||||
/// Show about the verbose flag.
|
||||
verbose: bool,
|
||||
|
||||
/// Show about the help flag.
|
||||
help: bool,
|
||||
}
|
||||
|
||||
impl ErrorHints {
|
||||
/// Check whether any hint should be printed.
|
||||
pub fn any(&self) -> bool {
|
||||
self.config || self.config_generate || self.config_test || self.verbose || self.help
|
||||
}
|
||||
|
||||
/// Print the error hints.
|
||||
pub fn print(&self, end_newline: bool) {
|
||||
// Print info messages
|
||||
for msg in &self.info {
|
||||
eprintln!("{} {}", highlight_info("info:"), msg);
|
||||
}
|
||||
|
||||
// Stop if nothing should be printed
|
||||
if !self.any() {
|
||||
return;
|
||||
}
|
||||
|
||||
eprint!("\n");
|
||||
|
||||
// Print hints
|
||||
let bin = crate::util::bin_name();
|
||||
if self.config {
|
||||
eprintln!(
|
||||
"Use '{}' to select a config file",
|
||||
highlight("--config FILE")
|
||||
);
|
||||
}
|
||||
if self.config_generate {
|
||||
eprintln!(
|
||||
"Use '{}' to generate a new config file",
|
||||
highlight(&format!("{} config generate", bin))
|
||||
);
|
||||
}
|
||||
if self.config_test {
|
||||
eprintln!(
|
||||
"Use '{}' to test a config file",
|
||||
highlight(&format!("{} config test -c FILE", bin))
|
||||
);
|
||||
}
|
||||
if self.verbose {
|
||||
eprintln!("For a detailed log add '{}'", highlight("--verbose"));
|
||||
}
|
||||
if self.help {
|
||||
eprintln!("For more information add '{}'", highlight("--help"));
|
||||
}
|
||||
|
||||
// End with additional newline
|
||||
if end_newline {
|
||||
eprintln!();
|
||||
}
|
||||
|
||||
// Flush
|
||||
let _ = io::stderr().flush();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ErrorHints {
|
||||
fn default() -> Self {
|
||||
ErrorHints {
|
||||
info: Vec::new(),
|
||||
config: false,
|
||||
config_generate: false,
|
||||
config_test: false,
|
||||
verbose: true,
|
||||
help: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorHintsBuilder {
|
||||
/// Add a single info entry.
|
||||
pub fn add_info(mut self, info: String) -> Self {
|
||||
// Initialize the info list
|
||||
if self.info.is_none() {
|
||||
self.info = Some(Vec::new());
|
||||
}
|
||||
|
||||
// Add the item to the info list
|
||||
if let Some(ref mut list) = self.info {
|
||||
list.push(info);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
25
src/util/mod.rs
Normal file
25
src/util/mod.rs
Normal file
@ -0,0 +1,25 @@
|
||||
pub mod cli;
|
||||
pub mod error;
|
||||
pub mod style;
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Get the name of the executable that was invoked.
|
||||
///
|
||||
/// When a symbolic or hard link is used, the name of the link is returned.
|
||||
///
|
||||
/// This attempts to obtain the binary name in the following order:
|
||||
/// - name in first item of program arguments via `std::env::args`
|
||||
/// - current executable name via `std::env::current_exe`
|
||||
/// - crate name
|
||||
pub fn bin_name() -> String {
|
||||
env::args_os()
|
||||
.next()
|
||||
.filter(|path| !path.is_empty())
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| env::current_exe().ok())
|
||||
.and_then(|p| p.file_name().map(|n| n.to_owned()))
|
||||
.and_then(|n| n.into_string().ok())
|
||||
.unwrap_or_else(|| crate_name!().into())
|
||||
}
|
21
src/util/style.rs
Normal file
21
src/util/style.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use colored::{ColoredString, Colorize};
|
||||
|
||||
/// Highlight the given text with a color.
|
||||
pub fn highlight(msg: &str) -> ColoredString {
|
||||
msg.yellow()
|
||||
}
|
||||
|
||||
/// Highlight the given text with an error color.
|
||||
pub fn highlight_error(msg: &str) -> ColoredString {
|
||||
msg.red().bold()
|
||||
}
|
||||
|
||||
/// Highlight the given text with an warning color.
|
||||
pub fn highlight_warning(msg: &str) -> ColoredString {
|
||||
highlight(msg).bold()
|
||||
}
|
||||
|
||||
/// Highlight the given text with an info color
|
||||
pub fn highlight_info(msg: &str) -> ColoredString {
|
||||
msg.cyan()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user