mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-07-19 10:25:15 +00:00
add djls-django crate (#6)
* add djls-django crate * rework * oops * add check for GDAL and GeoDjango * lots of things * remove unused scripts * move scripts to dedicated mod and make static consts * inline gdal check * rename mod * rename mod * move server info to consts * adjust pyproject * hide rustfmt config * simplify django setup * adjust printing
This commit is contained in:
parent
b7a1de98dd
commit
fce343f44d
32 changed files with 1139 additions and 291 deletions
12
crates/djls-django/Cargo.toml
Normal file
12
crates/djls-django/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "djls-django"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
djls-python = { workspace = true }
|
||||
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
thiserror = "2.0.4"
|
64
crates/djls-django/src/apps.rs
Normal file
64
crates/djls-django/src/apps.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use djls_python::{Python, RunnerError, ScriptRunner};
|
||||
use serde::Deserialize;
|
||||
use std::fmt;
|
||||
|
||||
use crate::scripts;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct App(String);
|
||||
|
||||
impl App {
|
||||
pub fn name(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for App {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Apps(Vec<App>);
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct InstalledAppsCheck {
|
||||
has_app: bool,
|
||||
}
|
||||
|
||||
impl ScriptRunner for InstalledAppsCheck {
|
||||
const SCRIPT: &'static str = scripts::INSTALLED_APPS_CHECK;
|
||||
}
|
||||
|
||||
impl Apps {
|
||||
pub fn from_strings(apps: Vec<String>) -> Self {
|
||||
Self(apps.into_iter().map(App).collect())
|
||||
}
|
||||
|
||||
pub fn apps(&self) -> &[App] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn has_app(&self, name: &str) -> bool {
|
||||
self.0.iter().any(|app| app.0 == name)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &App> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
pub fn check_installed(py: &Python, app: &str) -> Result<bool, RunnerError> {
|
||||
let result = InstalledAppsCheck::run_with_py_args(py, app)?;
|
||||
Ok(result.has_app)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Apps {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for app in &self.0 {
|
||||
writeln!(f, " {}", app)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
120
crates/djls-django/src/django.rs
Normal file
120
crates/djls-django/src/django.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
use crate::apps::Apps;
|
||||
use crate::gis::{check_gis_setup, GISError};
|
||||
use crate::scripts;
|
||||
use crate::templates::TemplateTag;
|
||||
use djls_python::{ImportCheck, Python, RunnerError, ScriptRunner};
|
||||
use serde::Deserialize;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DjangoProject {
|
||||
py: Python,
|
||||
settings_module: String,
|
||||
installed_apps: Apps,
|
||||
templatetags: Vec<TemplateTag>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct DjangoSetup {
|
||||
installed_apps: Vec<String>,
|
||||
templatetags: Vec<TemplateTag>,
|
||||
}
|
||||
|
||||
impl ScriptRunner for DjangoSetup {
|
||||
const SCRIPT: &'static str = scripts::DJANGO_SETUP;
|
||||
}
|
||||
|
||||
impl DjangoProject {
|
||||
fn new(
|
||||
py: Python,
|
||||
settings_module: String,
|
||||
installed_apps: Apps,
|
||||
templatetags: Vec<TemplateTag>,
|
||||
) -> Self {
|
||||
Self {
|
||||
py,
|
||||
settings_module,
|
||||
installed_apps,
|
||||
templatetags,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup() -> Result<Self, ProjectError> {
|
||||
let settings_module =
|
||||
std::env::var("DJANGO_SETTINGS_MODULE").expect("DJANGO_SETTINGS_MODULE must be set");
|
||||
|
||||
let py = Python::initialize()?;
|
||||
|
||||
let has_django = ImportCheck::check(&py, "django")?;
|
||||
|
||||
if !has_django {
|
||||
return Err(ProjectError::DjangoNotFound);
|
||||
}
|
||||
|
||||
if !check_gis_setup(&py)? {
|
||||
eprintln!("Warning: GeoDjango detected but GDAL is not available.");
|
||||
eprintln!("Django initialization will be skipped. Some features may be limited.");
|
||||
eprintln!("To enable full functionality, please install GDAL and other GeoDjango prerequisites.");
|
||||
|
||||
return Ok(Self {
|
||||
py,
|
||||
settings_module,
|
||||
installed_apps: Apps::default(),
|
||||
templatetags: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
let setup = DjangoSetup::run_with_py(&py)?;
|
||||
|
||||
Ok(Self::new(
|
||||
py,
|
||||
settings_module,
|
||||
Apps::from_strings(setup.installed_apps.to_vec()),
|
||||
setup.templatetags.to_vec(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn py(&self) -> &Python {
|
||||
&self.py
|
||||
}
|
||||
|
||||
fn settings_module(&self) -> &String {
|
||||
&self.settings_module
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DjangoProject {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "Django Project")?;
|
||||
writeln!(f, "Settings Module: {}", self.settings_module)?;
|
||||
writeln!(f, "Installed Apps:")?;
|
||||
write!(f, "{}", self.installed_apps)?;
|
||||
writeln!(f, "Template Tags:")?;
|
||||
for tag in &self.templatetags {
|
||||
write!(f, "{}", tag)?;
|
||||
writeln!(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ProjectError {
|
||||
#[error("Django is not installed or cannot be imported")]
|
||||
DjangoNotFound,
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("GIS error: {0}")]
|
||||
Gis(#[from] GISError),
|
||||
|
||||
#[error("JSON parsing error: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Python(#[from] djls_python::PythonError),
|
||||
|
||||
#[error(transparent)]
|
||||
Runner(#[from] RunnerError),
|
||||
}
|
26
crates/djls-django/src/gis.rs
Normal file
26
crates/djls-django/src/gis.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use crate::apps::Apps;
|
||||
use djls_python::{Python, RunnerError};
|
||||
use std::process::Command;
|
||||
|
||||
pub fn check_gis_setup(py: &Python) -> Result<bool, GISError> {
|
||||
let has_geodjango = Apps::check_installed(py, "django.contrib.gis")?;
|
||||
let gdal_is_installed = Command::new("gdalinfo")
|
||||
.arg("--version")
|
||||
.output()
|
||||
.map(|output| output.status.success())
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(!has_geodjango || gdal_is_installed)
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum GISError {
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("JSON parsing error: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Runner(#[from] RunnerError),
|
||||
}
|
7
crates/djls-django/src/lib.rs
Normal file
7
crates/djls-django/src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod apps;
|
||||
mod django;
|
||||
mod gis;
|
||||
mod scripts;
|
||||
mod templates;
|
||||
|
||||
pub use django::DjangoProject;
|
4
crates/djls-django/src/scripts.rs
Normal file
4
crates/djls-django/src/scripts.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
use djls_python::include_script;
|
||||
|
||||
pub const DJANGO_SETUP: &str = include_script!("django_setup");
|
||||
pub const INSTALLED_APPS_CHECK: &str = include_script!("installed_apps_check");
|
30
crates/djls-django/src/templates.rs
Normal file
30
crates/djls-django/src/templates.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use serde::Deserialize;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct TemplateTag {
|
||||
name: String,
|
||||
library: String,
|
||||
doc: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for TemplateTag {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let library = if self.library.is_empty() {
|
||||
"builtins"
|
||||
} else {
|
||||
&self.library
|
||||
};
|
||||
|
||||
write!(f, "{} ({})", self.name, library)?;
|
||||
writeln!(f)?;
|
||||
|
||||
if let Some(doc) = &self.doc {
|
||||
for line in doc.trim_end().split("\n") {
|
||||
writeln!(f, "{}", line)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue