use pyo3::exceptions::PyStopIteration;
use pyo3::prelude::*;
use pyo3::types::PyDict;
use std::path::Path;
pub fn export<T: crate::tree::PyTree>(
tree: &T,
target: &std::path::Path,
subdir: Option<&std::path::Path>,
) -> Result<(), crate::error::Error> {
Python::attach(|py| {
let m = py.import("breezy.export").unwrap();
let export = m.getattr("export").unwrap();
let kwargs = PyDict::new(py);
let subdir = if subdir.is_none() || subdir == Some(Path::new("")) {
None
} else {
Some(subdir.unwrap().to_string_lossy().to_string())
};
kwargs.set_item("subdir", subdir).unwrap();
export.call(
(
tree.to_object(py),
target.to_string_lossy().to_string(),
"dir",
py.None(),
),
Some(&kwargs),
)?;
Ok(())
})
}
#[derive(Debug, Clone, Copy)]
pub enum ArchiveFormat {
Tgz,
Tbz2,
Tar,
Zip,
}
impl ArchiveFormat {
fn as_str(&self) -> &'static str {
match self {
ArchiveFormat::Tgz => "tgz",
ArchiveFormat::Tbz2 => "tbz2",
ArchiveFormat::Tar => "tar",
ArchiveFormat::Zip => "zip",
}
}
}
pub struct ArchiveIter(pyo3::Py<PyAny>);
impl Iterator for ArchiveIter {
type Item = Result<Vec<u8>, crate::error::Error>;
fn next(&mut self) -> Option<Self::Item> {
Python::attach(|py| match self.0.call_method0(py, "__next__") {
Ok(v) => Some(v.extract::<Vec<u8>>(py).map_err(Into::into)),
Err(e) if e.is_instance_of::<PyStopIteration>(py) => None,
Err(e) => Some(Err(e.into())),
})
}
}
pub fn archive<T: crate::tree::PyTree>(
tree: &T,
format: ArchiveFormat,
name: &str,
subdir: Option<&Path>,
root: Option<&str>,
) -> Result<ArchiveIter, crate::error::Error> {
Python::attach(|py| {
let kwargs = PyDict::new(py);
if let Some(s) = subdir {
kwargs.set_item("subdir", s.to_string_lossy().to_string())?;
}
if let Some(r) = root {
kwargs.set_item("root", r)?;
}
let obj = tree.to_object(py);
let iter = obj.call_method(py, "archive", (format.as_str(), name), Some(&kwargs))?;
let iter = py.import("builtins")?.getattr("iter")?.call1((iter,))?;
Ok(ArchiveIter(iter.unbind()))
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::controldir::create_standalone_workingtree;
use crate::tree::MutableTree;
use crate::workingtree::WorkingTree;
use serial_test::serial;
use std::path::Path;
#[serial]
#[test]
fn test_export_tree() {
let env = crate::testing::TestEnv::new();
let tmp_dir = tempfile::tempdir().unwrap();
let wt = create_standalone_workingtree(tmp_dir.path(), "2a").unwrap();
let tree = wt.basis_tree().unwrap();
let target_tmp = tempfile::tempdir().unwrap();
let target_dir = target_tmp.path().join("export_target");
let result = export(&tree, &target_dir, None);
assert!(result.is_ok());
std::mem::drop(env);
}
#[serial]
#[test]
fn test_export_with_subdir() {
let env = crate::testing::TestEnv::new();
let tmp_dir = tempfile::tempdir().unwrap();
let wt = create_standalone_workingtree(tmp_dir.path(), "2a").unwrap();
std::fs::write(tmp_dir.path().join("file.txt"), "content").unwrap();
wt.add(&[Path::new("file.txt")]).unwrap();
wt.build_commit().message("Add file").commit().unwrap();
let tree = wt.basis_tree().unwrap();
let target_tmp = tempfile::tempdir().unwrap();
let target_dir = target_tmp.path().join("export_subdir");
let result = export(&tree, &target_dir, None);
assert!(result.is_ok());
std::mem::drop(env);
}
#[serial]
#[test]
fn test_export_with_empty_subdir() {
let env = crate::testing::TestEnv::new();
let tmp_dir = tempfile::tempdir().unwrap();
let wt = create_standalone_workingtree(tmp_dir.path(), "2a").unwrap();
let tree = wt.basis_tree().unwrap();
let target_tmp = tempfile::tempdir().unwrap();
let target_dir = target_tmp.path().join("export_empty");
let subdir = Path::new("");
let result = export(&tree, &target_dir, Some(subdir));
assert!(result.is_ok());
std::mem::drop(env);
}
}