From 42c19e751d97a786066a1f36b5fe63fc3d369fa9 Mon Sep 17 00:00:00 2001 From: Josh Thomas Date: Sat, 3 May 2025 22:09:19 -0500 Subject: [PATCH] Refactor and reorganize primary `djls` crate (#136) --- crates/djls/src/args.rs | 12 ++++ crates/djls/src/cli.rs | 28 ++++++++ crates/djls/src/commands.rs | 21 +++--- crates/djls/src/commands/serve.rs | 24 +++++++ crates/djls/src/lib.rs | 111 ++++++------------------------ 5 files changed, 98 insertions(+), 98 deletions(-) create mode 100644 crates/djls/src/args.rs create mode 100644 crates/djls/src/cli.rs create mode 100644 crates/djls/src/commands/serve.rs diff --git a/crates/djls/src/args.rs b/crates/djls/src/args.rs new file mode 100644 index 0000000..75a29db --- /dev/null +++ b/crates/djls/src/args.rs @@ -0,0 +1,12 @@ +use clap::Parser; + +#[derive(Parser, Debug, Clone)] +pub struct Args { + /// Do not print any output. + #[arg(global = true, long, short, conflicts_with = "verbose")] + pub quiet: bool, + + /// Use verbose output. + #[arg(global = true, action = clap::ArgAction::Count, long, short, conflicts_with = "quiet")] + pub verbose: u8, +} diff --git a/crates/djls/src/cli.rs b/crates/djls/src/cli.rs new file mode 100644 index 0000000..69967ae --- /dev/null +++ b/crates/djls/src/cli.rs @@ -0,0 +1,28 @@ +use crate::args::Args; +use crate::commands::{Command, DjlsCommand}; +use anyhow::Result; +use clap::Parser; +use std::process::ExitCode; + +/// Main CLI structure that defines the command-line interface +#[derive(Parser)] +#[command(name = "djls")] +#[command(version, about)] +pub struct Cli { + #[command(subcommand)] + pub command: DjlsCommand, + + #[command(flatten)] + pub args: Args, +} + +/// Parse CLI arguments and execute the chosen command +pub async fn run(args: Vec) -> Result { + let cli = Cli::try_parse_from(args).unwrap_or_else(|e| { + e.exit(); + }); + + match &cli.command { + DjlsCommand::Serve(cmd) => cmd.execute(&cli.args).await, + } +} diff --git a/crates/djls/src/commands.rs b/crates/djls/src/commands.rs index a25858a..6af5a67 100644 --- a/crates/djls/src/commands.rs +++ b/crates/djls/src/commands.rs @@ -1,13 +1,16 @@ -use clap::{Parser, ValueEnum}; +pub mod serve; -#[derive(Debug, Parser)] -pub struct Serve { - #[arg(short, long, default_value_t = ConnectionType::Stdio, value_enum)] - connection_type: ConnectionType, +use crate::args::Args; +use anyhow::Result; +use clap::Subcommand; +use std::process::ExitCode; + +pub trait Command { + async fn execute(&self, args: &Args) -> Result; } -#[derive(Clone, Debug, ValueEnum)] -enum ConnectionType { - Stdio, - Tcp, +#[derive(Debug, Subcommand)] +pub enum DjlsCommand { + /// Start the LSP server + Serve(self::serve::Serve), } diff --git a/crates/djls/src/commands/serve.rs b/crates/djls/src/commands/serve.rs new file mode 100644 index 0000000..3390c4d --- /dev/null +++ b/crates/djls/src/commands/serve.rs @@ -0,0 +1,24 @@ +use crate::args::Args; +use crate::commands::Command; +use anyhow::Result; +use clap::{Parser, ValueEnum}; +use std::process::ExitCode; + +#[derive(Debug, Parser)] +pub struct Serve { + #[arg(short, long, default_value_t = ConnectionType::Stdio, value_enum)] + connection_type: ConnectionType, +} + +#[derive(Clone, Debug, ValueEnum)] +enum ConnectionType { + Stdio, + Tcp, +} + +impl Command for Serve { + async fn execute(&self, _args: &Args) -> Result { + djls_server::serve().await?; + Ok(ExitCode::SUCCESS) + } +} diff --git a/crates/djls/src/lib.rs b/crates/djls/src/lib.rs index 9d8460e..ea70668 100644 --- a/crates/djls/src/lib.rs +++ b/crates/djls/src/lib.rs @@ -1,45 +1,12 @@ +mod args; +mod cli; mod commands; -use crate::commands::Serve; -use anyhow::Result; -use clap::{Parser, Subcommand}; use pyo3::prelude::*; use std::env; use std::process::ExitCode; -#[derive(Parser)] -#[command(name = "djls")] -#[command(version, about, long_about = None)] -pub struct Cli { - #[command(subcommand)] - command: Command, - - #[command(flatten)] - args: Args, -} - -#[derive(Debug, Subcommand)] -enum Command { - /// Start the LSP server - Serve(Serve), -} - -#[derive(Parser)] -pub struct Args { - #[command(flatten)] - global: GlobalArgs, -} - -#[derive(Parser, Debug, Clone)] -struct GlobalArgs { - /// Do not print any output. - #[arg(global = true, long, short, conflicts_with = "verbose")] - pub quiet: bool, - - /// Use verbose output. - #[arg(global = true, action = clap::ArgAction::Count, long, short, conflicts_with = "quiet")] - pub verbose: u8, -} +pub use cli::Cli; #[pyfunction] fn entrypoint(_py: Python) -> PyResult<()> { @@ -48,62 +15,28 @@ fn entrypoint(_py: Python) -> PyResult<()> { .chain(env::args().skip(2)) .collect(); - let runtime = tokio::runtime::Runtime::new().unwrap(); - let local = tokio::task::LocalSet::new(); - local.block_on(&runtime, async move { - tokio::select! { - // The main CLI program - result = main(args) => { - match result { - Ok(code) => { - if code != ExitCode::SUCCESS { - std::process::exit(1); - } - Ok::<(), PyErr>(()) - } - Err(e) => { - eprintln!("Error: {}", e); - if let Some(source) = e.source() { - eprintln!("Caused by: {}", source); - } - std::process::exit(1); - } - } - } - // Ctrl+C handling - _ = tokio::signal::ctrl_c() => { - println!("\nReceived Ctrl+C, shutting down..."); - // Cleanup code here if needed - std::process::exit(130); // Standard Ctrl+C exit code - } - // SIGTERM handling (Unix only) - _ = async { - #[cfg(unix)] - { - use tokio::signal::unix::{signal, SignalKind}; - let mut term = signal(SignalKind::terminate()).unwrap(); - term.recv().await; - } - } => { - println!("\nReceived termination signal, shutting down..."); - std::process::exit(143); // Standard SIGTERM exit code + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + + let result = runtime.block_on(cli::run(args)); + + match result { + Ok(code) => { + if code != ExitCode::SUCCESS { + std::process::exit(1); } + Ok(()) + } + Err(e) => { + eprintln!("Error: {}", e); + if let Some(source) = e.source() { + eprintln!("Caused by: {}", source); + } + std::process::exit(1); } - })?; - - Ok(()) -} - -async fn main(args: Vec) -> Result { - let cli = Cli::try_parse_from(args).unwrap_or_else(|e| { - e.exit(); - }); - - match cli.command { - Command::Serve(_serve) => djls_server::serve().await?, } - - Ok(ExitCode::SUCCESS) } #[pymodule]