Add support for system-wide installation (#62)

This commit is contained in:
Josh Thomas 2025-01-03 11:39:18 -06:00 committed by GitHub
parent 84d87a8857
commit b33d3f890d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 122 additions and 17 deletions

View file

@ -6,3 +6,5 @@ edition = "2021"
[dependencies]
pyo3 = { workspace = true }
tower-lsp = { workspace = true }
which = "7.0.1"

View file

@ -3,12 +3,15 @@ mod templatetags;
pub use templatetags::TemplateTags;
use pyo3::prelude::*;
use std::fmt;
use std::path::{Path, PathBuf};
use tower_lsp::lsp_types::*;
use which::which;
#[derive(Debug)]
pub struct DjangoProject {
path: PathBuf,
env: Option<PythonEnvironment>,
template_tags: Option<TemplateTags>,
}
@ -16,6 +19,7 @@ impl DjangoProject {
pub fn new(path: PathBuf) -> Self {
Self {
path,
env: None,
template_tags: None,
}
}
@ -36,19 +40,37 @@ impl DjangoProject {
}
pub fn initialize(&mut self) -> PyResult<()> {
let python_env = PythonEnvironment::new().ok_or_else(|| {
PyErr::new::<pyo3::exceptions::PyRuntimeError, _>("Could not find Python in PATH")
})?;
Python::with_gil(|py| {
// Add project to Python path
let sys = py.import("sys")?;
let py_path = sys.getattr("path")?;
py_path.call_method1("append", (self.path.to_str().unwrap(),))?;
// Setup Django
let django = py.import("django")?;
django.call_method0("setup")?;
if let Some(path_str) = self.path.to_str() {
py_path.call_method1("insert", (0, path_str))?;
}
self.template_tags = Some(TemplateTags::from_python(py)?);
for path in &python_env.sys_path {
if let Some(path_str) = path.to_str() {
py_path.call_method1("append", (path_str,))?;
}
}
Ok(())
self.env = Some(python_env);
match py.import("django") {
Ok(django) => {
django.call_method0("setup")?;
self.template_tags = Some(TemplateTags::from_python(py)?);
Ok(())
}
Err(e) => {
eprintln!("Failed to import Django: {}", e);
Err(e)
}
}
})
}
@ -60,3 +82,65 @@ impl DjangoProject {
&self.path
}
}
impl fmt::Display for DjangoProject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Project path: {}", self.path.display())?;
if let Some(py_env) = &self.env {
write!(f, "{}", py_env)?;
}
Ok(())
}
}
#[derive(Debug)]
struct PythonEnvironment {
python_path: PathBuf,
sys_path: Vec<PathBuf>,
sys_prefix: PathBuf,
}
impl PythonEnvironment {
fn new() -> Option<Self> {
let python_path = which("python").ok()?;
let prefix = python_path.parent()?.parent()?;
let mut sys_path = Vec::new();
sys_path.push(prefix.join("bin"));
if let Some(site_packages) = Self::find_site_packages(prefix) {
sys_path.push(site_packages);
}
Some(Self {
python_path: python_path.clone(),
sys_path,
sys_prefix: prefix.to_path_buf(),
})
}
#[cfg(windows)]
fn find_site_packages(prefix: &Path) -> Option<PathBuf> {
Some(prefix.join("Lib").join("site-packages"))
}
#[cfg(not(windows))]
fn find_site_packages(prefix: &Path) -> Option<PathBuf> {
std::fs::read_dir(prefix.join("lib"))
.ok()?
.filter_map(Result::ok)
.find(|e| e.file_name().to_string_lossy().starts_with("python"))
.map(|e| e.path().join("site-packages"))
}
}
impl fmt::Display for PythonEnvironment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Sys prefix: {}", self.sys_prefix.display())?;
writeln!(f, "Sys paths:")?;
for path in &self.sys_path {
writeln!(f, " {}", path.display())?;
}
Ok(())
}
}