use crate::branch::PyBranch;
use crate::controldir::PyControlDir;
use crate::debian::error::Error;
use crate::debian::TarballKind;
use crate::debian::VersionKind;
use crate::tree::PyTree;
use crate::RevisionId;
use debversion::Version;
use pyo3::prelude::*;
use pyo3::types::{PyCFunction, PyDict, PyTuple};
use std::collections::HashMap;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
pub struct PristineTarSource(Py<PyAny>);
impl From<Py<PyAny>> for PristineTarSource {
fn from(obj: Py<PyAny>) -> Self {
PristineTarSource(obj)
}
}
impl<'py> IntoPyObject<'py> for PristineTarSource {
type Target = PyAny;
type Output = Bound<'py, PyAny>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.0.clone_ref(py).into_bound(py))
}
}
pub struct UpstreamBranchSource(Py<PyAny>);
impl From<Py<PyAny>> for UpstreamBranchSource {
fn from(obj: Py<PyAny>) -> Self {
UpstreamBranchSource(obj)
}
}
impl<'py> IntoPyObject<'py> for UpstreamBranchSource {
type Target = PyAny;
type Output = Bound<'py, PyAny>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.0.clone_ref(py).into_bound(py))
}
}
pub struct Tarball {
pub filename: String,
pub component: TarballKind,
pub md5: String,
}
pub type Tarballs = Vec<Tarball>;
impl<'a, 'py> FromPyObject<'a, 'py> for Tarball {
type Error = PyErr;
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
Ok(Tarball {
filename: ob.get_item(0)?.extract()?,
component: ob.get_item(1)?.extract()?,
md5: ob.get_item(2)?.extract()?,
})
}
}
impl<'py> IntoPyObject<'py> for Tarball {
type Target = PyAny;
type Output = Bound<'py, PyAny>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let tuple = (self.filename, self.component, self.md5);
Ok(tuple.into_pyobject(py)?.into_any())
}
}
pub trait PyUpstreamSource: std::any::Any + std::fmt::Debug {
fn as_pyobject(&self) -> &Py<PyAny>;
}
pub trait UpstreamSource: std::fmt::Debug {
fn get_latest_version(
&self,
package: Option<&str>,
current_version: Option<&str>,
) -> Result<Option<(String, String)>, Error>;
fn get_recent_versions(
&self,
package: Option<&str>,
since_version: Option<&str>,
) -> Box<dyn Iterator<Item = (String, String)>>;
fn version_as_revisions(
&self,
package: Option<&str>,
version: &str,
tarballs: Option<Tarballs>,
) -> Result<HashMap<TarballKind, (RevisionId, PathBuf)>, Error>;
fn has_version(
&self,
package: Option<&str>,
version: &str,
tarballs: Option<Tarballs>,
) -> Result<bool, Error>;
fn fetch_tarballs(
&self,
package: Option<&str>,
version: &str,
target_dir: &Path,
components: Option<&[TarballKind]>,
) -> Result<Vec<PathBuf>, Error>;
}
impl<T: PyUpstreamSource> UpstreamSource for T {
fn get_latest_version(
&self,
package: Option<&str>,
current_version: Option<&str>,
) -> Result<Option<(String, String)>, Error> {
Python::attach(|py| {
Ok(self
.as_pyobject()
.call_method1(py, "get_latest_version", (package, current_version))?
.extract(py)?)
})
}
fn get_recent_versions(
&self,
package: Option<&str>,
since_version: Option<&str>,
) -> Box<dyn Iterator<Item = (String, String)>> {
let mut ret = vec![];
Python::attach(|py| -> PyResult<()> {
let recent_versions = self.as_pyobject().call_method1(
py,
"get_recent_versions",
(package, since_version),
)?;
while let Ok(Some((version, mangled_version))) =
recent_versions.call_method0(py, "__next__")?.extract(py)
{
ret.push((version, mangled_version));
}
Ok(())
})
.unwrap();
Box::new(ret.into_iter())
}
fn version_as_revisions(
&self,
package: Option<&str>,
version: &str,
tarballs: Option<Tarballs>,
) -> Result<HashMap<TarballKind, (RevisionId, PathBuf)>, Error> {
Python::attach(|py| {
Ok(self
.as_pyobject()
.call_method1(py, "version_as_revisions", (package, version, tarballs))?
.extract(py)?)
})
}
fn has_version(
&self,
package: Option<&str>,
version: &str,
tarballs: Option<Tarballs>,
) -> Result<bool, Error> {
Python::attach(|py| {
Ok(self
.as_pyobject()
.call_method1(py, "has_version", (package, version, tarballs))?
.extract(py)?)
})
}
fn fetch_tarballs(
&self,
package: Option<&str>,
version: &str,
target_dir: &Path,
components: Option<&[TarballKind]>,
) -> Result<Vec<PathBuf>, Error> {
Python::attach(|py| {
Ok(self
.as_pyobject()
.call_method1(
py,
"fetch_tarballs",
(package, version, target_dir, components.map(|x| x.to_vec())),
)?
.extract(py)?)
})
}
}
pub struct GenericUpstreamSource(Py<PyAny>);
impl<'py> IntoPyObject<'py> for GenericUpstreamSource {
type Target = PyAny;
type Output = Bound<'py, PyAny>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.0.into_pyobject(py)?.into_any())
}
}
impl<'a, 'py> FromPyObject<'a, 'py> for GenericUpstreamSource {
type Error = PyErr;
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
Ok(GenericUpstreamSource(obj.to_owned().unbind()))
}
}
impl PyUpstreamSource for GenericUpstreamSource {
fn as_pyobject(&self) -> &Py<PyAny> {
&self.0
}
}
impl GenericUpstreamSource {
pub fn new(obj: Py<PyAny>) -> Self {
Self(obj)
}
}
impl std::fmt::Debug for GenericUpstreamSource {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_fmt(format_args!("GenericUpstreamSource({:?})", self.0))
}
}
impl PyUpstreamSource for UpstreamBranchSource {
fn as_pyobject(&self) -> &Py<PyAny> {
&self.0
}
}
impl std::fmt::Debug for UpstreamBranchSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UpstreamBranchSource").finish()
}
}
impl UpstreamBranchSource {
pub fn upstream_branch(&self) -> Box<dyn PyBranch> {
let o = Python::attach(|py| self.as_pyobject().getattr(py, "upstream_branch").unwrap());
Box::new(crate::branch::GenericBranch::from(o))
}
pub fn revision_tree(
&self,
source_name: Option<&str>,
mangled_upstream_version: &str,
) -> Result<crate::tree::RevisionTree, Error> {
Python::attach(|py| {
Ok(crate::tree::RevisionTree(self.as_pyobject().call_method1(
py,
"revision_tree",
(source_name, mangled_upstream_version),
)?))
})
}
pub fn version_as_revision(
&self,
package: Option<&str>,
version: &str,
tarballs: Option<Tarballs>,
) -> Result<(RevisionId, PathBuf), Error> {
Python::attach(|py| {
Ok(self
.as_pyobject()
.call_method1(py, "version_as_revision", (package, version, tarballs))?
.extract(py)?)
})
}
pub fn from_branch(
upstream_branch: &dyn PyBranch,
version_kind: Option<VersionKind>,
local_dir: &dyn PyControlDir,
create_dist: Option<
impl Fn(&dyn PyTree, &str, &str, &Path, &Path) -> Result<OsString, Error>
+ Send
+ Sync
+ 'static,
>,
) -> Result<Self, Error> {
Python::attach(|py| {
let m = py.import("breezy.plugins.debian.upstream.branch").unwrap();
let cls = m.getattr("UpstreamBranchSource").unwrap();
let upstream_branch = upstream_branch.to_object(py);
let kwargs = PyDict::new(py);
kwargs.set_item("version_kind", version_kind.unwrap_or_default())?;
kwargs.set_item("local_dir", local_dir.to_object(py))?;
if let Some(create_dist) = create_dist {
let create_dist = move |args: &Bound<'_, PyTuple>,
_kwargs: Option<&Bound<'_, PyDict>>|
-> PyResult<_> {
let args = args.extract::<(Py<PyAny>, String, String, PathBuf, PathBuf)>()?;
create_dist(
&crate::tree::RevisionTree(args.0),
&args.1,
&args.2,
&args.3,
&args.4,
)
.map(|x| x.to_string_lossy().into_owned())
.map_err(|e| e.into())
};
let create_dist = PyCFunction::new_closure(py, None, None, create_dist)?;
kwargs.set_item("create_dist", create_dist)?;
}
Ok(UpstreamBranchSource(
cls.call_method("from_branch", (upstream_branch,), Some(&kwargs))?
.into(),
))
})
}
}
impl PyUpstreamSource for PristineTarSource {
fn as_pyobject(&self) -> &Py<PyAny> {
&self.0
}
}
impl std::fmt::Debug for PristineTarSource {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_fmt(format_args!("PristineTarSource({:?})", self.0))
}
}
impl PristineTarSource {
pub fn has_version(
&self,
package: Option<&str>,
version: &str,
tarballs: Option<Tarballs>,
try_hard: bool,
) -> Result<bool, Error> {
Python::attach(|py| {
Ok(self
.as_pyobject()
.call_method1(py, "has_version", (package, version, tarballs, try_hard))?
.extract(py)?)
})
}
}
pub fn upstream_version_add_revision(
upstream_branch: &dyn PyBranch,
version_string: &str,
revid: &RevisionId,
sep: Option<&str>,
) -> Result<String, Error> {
let sep = sep.unwrap_or("+");
Python::attach(|py| {
let m = py.import("breezy.plugins.debian.upstream.branch").unwrap();
let upstream_version_add_revision = m.getattr("upstream_version_add_revision").unwrap();
Ok(upstream_version_add_revision
.call_method1(
"upstream_version_add_revision",
(
upstream_branch.to_object(py),
version_string,
revid.clone(),
sep,
),
)?
.extract()?)
})
}
pub fn get_pristine_tar_source(
packaging_tree: &dyn PyTree,
packaging_branch: &dyn PyBranch,
) -> Result<PristineTarSource, Error> {
Python::attach(|py| {
let m = py.import("breezy.plugins.debian.upstream").unwrap();
let cls = m.getattr("get_pristine_tar_source").unwrap();
Ok(PristineTarSource(
cls.call1((packaging_tree.to_object(py), packaging_branch.to_object(py)))?
.into(),
))
})
}
pub fn run_dist_command(
revtree: &dyn PyTree,
package: Option<&str>,
version: &Version,
target_dir: &Path,
dist_command: &str,
include_controldir: bool,
subpath: &Path,
) -> Result<bool, Error> {
Python::attach(|py| {
let m = py.import("breezy.plugins.debian.upstream").unwrap();
let run_dist_command = m.getattr("run_dist_command").unwrap();
let kwargs = PyDict::new(py);
kwargs.set_item("revtree", revtree.to_object(py))?;
kwargs.set_item("package", package)?;
kwargs.set_item("version", version.to_string())?;
kwargs.set_item("target_dir", target_dir.to_string_lossy().to_string())?;
kwargs.set_item("dist_command", dist_command)?;
kwargs.set_item("include_controldir", include_controldir)?;
kwargs.set_item("subpath", subpath.to_string_lossy().to_string())?;
Ok(run_dist_command.call((), Some(&kwargs))?.extract()?)
})
}