use clap::Parser;
use diffedit3::local_server::{MergeToolError, run_server};
use thiserror::Error;
use tracing_subscriber::layer::SubscriberExt as _;
use tracing_subscriber::util::SubscriberInitExt as _;
type PortRange = std::ops::RangeInclusive<usize>;
#[derive(Parser)]
#[command(version, about)]
pub struct LocalServerCli {
#[command(flatten)]
lib_cli: diffedit3::fs::Cli,
#[arg(
long, short, default_values = ["8080-8090", "0"],
value_name = "PORT_OR_PORT_RANGE", value_parser = parse_port_range
)]
port: Vec<PortRange>,
#[arg(long, short = 'N')]
no_browser: bool,
#[arg(long, short)]
verbose: bool,
}
#[derive(Debug, PartialEq, Eq, Clone, Error)]
enum ParsePortRangeError {
#[error(
"A port range must be one unsigned integer or two unsigned integers separated by a -, for \
example 1234 or 1234-56789"
)]
SyntaxError,
#[error("The minimum port {0} cannot be greater than the maximum port {1}")]
InequalityError(usize, usize),
}
fn parse_port_range(s: &str) -> Result<PortRange, ParsePortRangeError> {
let parse_usize = |s: &str| {
s.parse::<usize>()
.map_err(|_| ParsePortRangeError::SyntaxError)
};
match s.split_once('-') {
None => {
let port = parse_usize(s)?;
Ok(port..=port)
}
Some((first, second)) => {
let (min_port, max_port) = (parse_usize(first)?, parse_usize(second)?);
if min_port <= max_port {
Ok(min_port..=max_port)
} else {
Err(ParsePortRangeError::InequalityError(min_port, max_port))
}
}
}
}
fn exit_with_cli_error(s: String) -> ! {
eprintln!("{s}");
std::process::exit(2)
}
#[tokio::main]
async fn main() {
let cli = LocalServerCli::parse();
let input: Box<dyn diffedit3::DataInterface> = match cli.lib_cli.into_data_interface() {
Ok(i) => i,
Err(err) => {
exit_with_cli_error(err.to_string());
}
};
let mut tracing_filter = tracing_subscriber::EnvFilter::from_default_env();
if cli.verbose {
tracing_filter = tracing_filter.add_directive("poem=info".parse().unwrap());
}
tracing_subscriber::registry()
.with(tracing_filter)
.with(tracing_subscriber::fmt::layer())
.init();
if let Err(err) = run_server(
input,
Box::new(cli.port.into_iter().flatten()),
!cli.no_browser,
)
.await
{
std::process::exit(match err {
MergeToolError::IOError(err) => {
eprintln!("{err}");
3
}
MergeToolError::FailedToOpenPort(_) => {
eprintln!("Failed to open HTTP port: {err}");
3
}
MergeToolError::RequestedExitWithCode(code) => code,
MergeToolError::CtrlC => 130,
});
};
}