implement basic config

This commit is contained in:
Josh Thomas 2024-12-13 00:17:28 -06:00
parent 134f3fea54
commit 62ec21584b
3 changed files with 112 additions and 3 deletions

View file

@ -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]]

View file

@ -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<PathBuf>,
/// 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<Self, ConfigError> {
if self.django_settings_module.is_empty() {
return Err(ConfigError::MissingDjangoSettings);
}
Ok(self)
}
}
fn find_config_up_tree(start_dir: &Path) -> Vec<PathBuf> {
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<Config, ConfigError> {
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(&current_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),
}

View file

@ -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<dyn std::error::Error>> {
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::<Vec<&OsStr>, &OsStr>(
"djls.agent",
None,
opts.health_check_interval(),
)?;
println!("LSP server started, beginning to serve...");
djls_server::serve(python).await?
}
}