mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-07-20 02:45:00 +00:00
add packaging module to djls-python (#2)
This commit is contained in:
parent
931c0bc9fb
commit
4c67f6a90d
3 changed files with 183 additions and 1 deletions
|
@ -1,4 +1,5 @@
|
||||||
mod environment;
|
mod environment;
|
||||||
|
mod packaging;
|
||||||
mod python;
|
mod python;
|
||||||
|
|
||||||
pub use environment::PythonEnvironment;
|
pub use environment::PythonEnvironment;
|
||||||
|
|
166
crates/djls-python/src/packaging.rs
Normal file
166
crates/djls-python/src/packaging.rs
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Package {
|
||||||
|
name: String,
|
||||||
|
version: String,
|
||||||
|
location: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Package {
|
||||||
|
fn new(name: String, version: String, location: Option<PathBuf>) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
location,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &String {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version(&self) -> &String {
|
||||||
|
&self.version
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn location(&self) -> &Option<PathBuf> {
|
||||||
|
&self.location
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Package {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{} {}", self.name, self.version)?;
|
||||||
|
if let Some(location) = &self.location {
|
||||||
|
write!(f, " ({})", location.display())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Packages(HashMap<String, Package>);
|
||||||
|
|
||||||
|
impl Packages {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self(HashMap::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_python(py: Python) -> PyResult<Self> {
|
||||||
|
let importlib_metadata = py.import("importlib.metadata")?;
|
||||||
|
let distributions = importlib_metadata.call_method0("distributions")?;
|
||||||
|
|
||||||
|
let mut packages = Packages::new();
|
||||||
|
|
||||||
|
for dist in (distributions.try_iter()?).flatten() {
|
||||||
|
if let Ok(metadata) = dist.getattr("metadata") {
|
||||||
|
if let (Ok(name), Ok(version)) = (
|
||||||
|
metadata.get_item("Name")?.extract::<String>(),
|
||||||
|
dist.getattr("version")?.extract::<String>(),
|
||||||
|
) {
|
||||||
|
let location = match dist.call_method1("locate_file", ("",)) {
|
||||||
|
Ok(path) => path
|
||||||
|
.getattr("parent")?
|
||||||
|
.call_method0("as_posix")?
|
||||||
|
.extract::<String>()
|
||||||
|
.ok()
|
||||||
|
.map(PathBuf::from),
|
||||||
|
Err(_) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
packages
|
||||||
|
.0
|
||||||
|
.insert(name.clone(), Package::new(name, version, location));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(packages)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_executable(executable: &Path) -> Result<Self, PackagingError> {
|
||||||
|
let output = Command::new(executable)
|
||||||
|
.args([
|
||||||
|
"-c",
|
||||||
|
r#"
|
||||||
|
import json
|
||||||
|
import importlib.metadata
|
||||||
|
|
||||||
|
packages = {}
|
||||||
|
for dist in importlib.metadata.distributions():
|
||||||
|
try:
|
||||||
|
packages[dist.metadata["Name"]] = {
|
||||||
|
"name": dist.metadata["Name"],
|
||||||
|
"version": dist.version,
|
||||||
|
"location": dist.locate_file("").parent.as_posix() if dist.locate_file("") else None
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(json.dumps(packages))
|
||||||
|
"#,
|
||||||
|
])
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let output_str = String::from_utf8(output.stdout)?;
|
||||||
|
let packages_info: serde_json::Value = serde_json::from_str(&output_str)?;
|
||||||
|
|
||||||
|
Ok(packages_info
|
||||||
|
.as_object()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|(name, info)| {
|
||||||
|
(
|
||||||
|
name.clone(),
|
||||||
|
Package {
|
||||||
|
name: name.clone(),
|
||||||
|
version: info["version"].as_str().unwrap().to_string(),
|
||||||
|
location: info["location"].as_str().map(PathBuf::from),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<(String, Package)> for Packages {
|
||||||
|
fn from_iter<T: IntoIterator<Item = (String, Package)>>(iter: T) -> Self {
|
||||||
|
Self(HashMap::from_iter(iter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Packages {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut packages: Vec<_> = self.0.values().collect();
|
||||||
|
packages.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
|
||||||
|
if packages.is_empty() {
|
||||||
|
writeln!(f, " (no packages installed)")?;
|
||||||
|
} else {
|
||||||
|
for package in packages {
|
||||||
|
writeln!(f, "{}", package)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum PackagingError {
|
||||||
|
#[error("IO error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("JSON parsing error: {0}")]
|
||||||
|
Json(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("Python error: {0}")]
|
||||||
|
Python(#[from] PyErr),
|
||||||
|
|
||||||
|
#[error("UTF-8 conversion error: {0}")]
|
||||||
|
Utf8(#[from] std::string::FromUtf8Error),
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::packaging::{Packages, PackagingError};
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -135,6 +136,7 @@ pub struct Interpreter {
|
||||||
sys_base_prefix: PathBuf,
|
sys_base_prefix: PathBuf,
|
||||||
sys_executable: PathBuf,
|
sys_executable: PathBuf,
|
||||||
sys_path: Vec<PathBuf>,
|
sys_path: Vec<PathBuf>,
|
||||||
|
packages: Packages,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interpreter {
|
impl Interpreter {
|
||||||
|
@ -145,6 +147,7 @@ impl Interpreter {
|
||||||
sys_base_prefix: PathBuf,
|
sys_base_prefix: PathBuf,
|
||||||
sys_executable: PathBuf,
|
sys_executable: PathBuf,
|
||||||
sys_path: Vec<PathBuf>,
|
sys_path: Vec<PathBuf>,
|
||||||
|
packages: Packages,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
version_info,
|
version_info,
|
||||||
|
@ -153,6 +156,7 @@ impl Interpreter {
|
||||||
sys_base_prefix,
|
sys_base_prefix,
|
||||||
sys_executable,
|
sys_executable,
|
||||||
sys_path,
|
sys_path,
|
||||||
|
packages,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,6 +184,10 @@ impl Interpreter {
|
||||||
&self.sys_path
|
&self.sys_path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn packages(&self) -> &Packages {
|
||||||
|
&self.packages
|
||||||
|
}
|
||||||
|
|
||||||
pub fn for_build(py: Python) -> PyResult<Self> {
|
pub fn for_build(py: Python) -> PyResult<Self> {
|
||||||
let sys = py.import("sys")?;
|
let sys = py.import("sys")?;
|
||||||
|
|
||||||
|
@ -194,6 +202,7 @@ impl Interpreter {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.collect(),
|
.collect(),
|
||||||
|
Packages::from_python(py)?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,6 +237,7 @@ print(json.dumps({
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| PathBuf::from(p.as_str().unwrap()))
|
.map(|p| PathBuf::from(p.as_str().unwrap()))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
Packages::from_executable(executable)?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,7 +253,9 @@ impl fmt::Display for Interpreter {
|
||||||
writeln!(f, "{}", path.display())?;
|
writeln!(f, "{}", path.display())?;
|
||||||
}
|
}
|
||||||
writeln!(f, "Sysconfig Paths:")?;
|
writeln!(f, "Sysconfig Paths:")?;
|
||||||
write!(f, "{}", self.sysconfig_paths)
|
write!(f, "{}", self.sysconfig_paths)?;
|
||||||
|
writeln!(f, "\nInstalled Packages:")?;
|
||||||
|
write!(f, "{}", self.packages)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,6 +270,9 @@ pub enum PythonError {
|
||||||
#[error("JSON parsing error: {0}")]
|
#[error("JSON parsing error: {0}")]
|
||||||
Json(#[from] serde_json::Error),
|
Json(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("Packaging error: {0}")]
|
||||||
|
Packaging(#[from] PackagingError),
|
||||||
|
|
||||||
#[error("Integer parsing error: {0}")]
|
#[error("Integer parsing error: {0}")]
|
||||||
Parse(#[from] std::num::ParseIntError),
|
Parse(#[from] std::num::ParseIntError),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue