From 62ec21584be61868d309d7c827facd58c8d4adb4 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 13 Dec 2024 00:17:28 -0600 Subject: [PATCH] implement basic config --- crates/djlc-cli/Cargo.toml | 6 ++- crates/djlc-cli/src/config.rs | 94 +++++++++++++++++++++++++++++++++++ crates/djlc-cli/src/main.rs | 15 +++++- 3 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 crates/djlc-cli/src/config.rs diff --git a/crates/djlc-cli/Cargo.toml b/crates/djlc-cli/Cargo.toml index 7349989..3e7ccda 100644 --- a/crates/djlc-cli/Cargo.toml +++ b/crates/djlc-cli/Cargo.toml @@ -9,10 +9,14 @@ djls-ipc = { workspace = true } djls-server = { workspace = true } anyhow = { workspace = true } +serde = { workspace = true } serde_json = { workspace = true } +thiserror = { workspace = true } tokio = { workspace = true } -clap = { version = "4.5.23", features = ["derive"] } +clap = { version = "4.5.23", features = ["derive", "env"] } +dirs = "5.0.1" +figment = { version = "0.10.19", features = ["env", "toml"] } tower-lsp = { version = "0.20.0", features = ["proposed"] } [[bin]] diff --git a/crates/djlc-cli/src/config.rs b/crates/djlc-cli/src/config.rs new file mode 100644 index 0000000..49b0cb5 --- /dev/null +++ b/crates/djlc-cli/src/config.rs @@ -0,0 +1,94 @@ +use clap::Args; +use figment::providers::{Env, Format, Serialized, Toml}; +use figment::Figment; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; + +const CONFIG_FILES: [&str; 3] = [ + "djls.toml", // highest priority + ".djls.toml", + "pyproject.toml", // lowest priority +]; + +#[derive(Args, Debug, Serialize, Deserialize)] +pub struct Config { + /// Override the virtual environment path + #[arg(long, env = "DJLS_VENV_PATH")] + pub venv_path: Option, + + /// Django settings module + #[arg(long, env = "DJANGO_SETTINGS_MODULE")] + pub django_settings_module: String, +} + +impl Default for Config { + fn default() -> Self { + Config { + venv_path: std::env::var("VIRTUAL_ENV").ok().map(PathBuf::from), + django_settings_module: std::env::var("DJANGO_SETTINGS_MODULE").unwrap_or_default(), + } + } +} + +impl Config { + fn validate(self) -> Result { + if self.django_settings_module.is_empty() { + return Err(ConfigError::MissingDjangoSettings); + } + Ok(self) + } +} + +fn find_config_up_tree(start_dir: &Path) -> Vec { + let mut configs = Vec::new(); + let mut current_dir = start_dir.to_path_buf(); + + while let Some(parent) = current_dir.parent() { + for &config_name in CONFIG_FILES.iter() { + let config_path = current_dir.join(config_name); + if config_path.exists() { + configs.push(config_path); + } + } + current_dir = parent.to_path_buf(); + } + + configs +} + +pub fn load_config(cli_config: &Config) -> Result { + let platform_config = dirs::config_dir() + .map(|dir| dir.join("djls/config.toml")) + .filter(|p| p.exists()); + + let mut figment = Figment::new() + .merge(Serialized::defaults(Config::default())) + .merge( + platform_config + .map(|p| Toml::file(&p)) + .unwrap_or_else(|| Toml::file("/dev/null")), + ); + + let current_dir = std::env::current_dir().map_err(ConfigError::CurrentDir)?; + for path in find_config_up_tree(¤t_dir) { + figment = figment.merge(Toml::file(&path)); + } + + let config: Config = figment + .merge(Env::raw().only(&["DJANGO_SETTINGS_MODULE"])) + .merge(Env::prefixed("DJLS_").split("_")) + .merge(Serialized::defaults(cli_config)) + .extract()?; + + config.validate() +} + +#[derive(Debug, thiserror::Error)] +pub enum ConfigError { + #[error("Django settings module not specified")] + MissingDjangoSettings, + #[error("could not determine current directory: {0}")] + CurrentDir(std::io::Error), + #[error("figment error: {0}")] + Figment(#[from] figment::Error), +} diff --git a/crates/djlc-cli/src/main.rs b/crates/djlc-cli/src/main.rs index 385e208..4b01de7 100644 --- a/crates/djlc-cli/src/main.rs +++ b/crates/djlc-cli/src/main.rs @@ -1,6 +1,9 @@ +mod config; + +use crate::config::{load_config, Config}; +use anyhow::Context; use clap::{Args, Parser, Subcommand}; -use djls_ipc::v1::*; -use djls_ipc::{ProcessError, PythonProcess, TransportError}; +use djls_ipc::PythonProcess; use std::ffi::OsStr; use std::time::Duration; @@ -8,6 +11,9 @@ use std::time::Duration; struct Cli { #[command(subcommand)] command: Commands, + + #[command(flatten)] + config: Config, } #[derive(Debug, Args)] @@ -40,16 +46,21 @@ enum Commands { #[tokio::main] async fn main() -> Result<(), Box> { let cli = Cli::parse(); + let config = load_config(&cli.config).context("failed to load configuration")?; match cli.command { Commands::Serve(opts) => { println!("Starting LSP server..."); + println!("With config: {:?}", config); + let python = PythonProcess::new::, &OsStr>( "djls.agent", None, opts.health_check_interval(), )?; + println!("LSP server started, beginning to serve..."); + djls_server::serve(python).await? } }