pymedusa_zip/
lib.rs

1/*
2 * Description: ???
3 *
4 * Copyright (C) 2023 Danny McClanahan <dmcC2@hypnicjerk.ai>
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0 (see LICENSE).
8 */
9
10//! ???
11
12/* These clippy lint descriptions are purely non-functional and do not affect the functionality
13 * or correctness of the code. */
14// #![warn(missing_docs)]
15
16/* Note: run clippy with: rustup run nightly cargo-clippy! */
17#![deny(unsafe_code)]
18/* Ensure any doctest warnings fails the doctest! */
19#![doc(test(attr(deny(warnings))))]
20/* Enable all clippy lints except for many of the pedantic ones. It's a shame this needs to be
21 * copied and pasted across crates, but there doesn't appear to be a way to include inner
22 * attributes from a common source. */
23#![deny(
24  clippy::all,
25  clippy::default_trait_access,
26  clippy::expl_impl_clone_on_copy,
27  clippy::if_not_else,
28  clippy::needless_continue,
29  clippy::single_match_else,
30  clippy::unseparated_literal_suffix,
31  clippy::used_underscore_binding
32)]
33/* It is often more clear to show that nothing is being moved. */
34#![allow(clippy::match_ref_pats)]
35/* Subjective style. */
36#![allow(
37  clippy::derived_hash_with_manual_eq,
38  clippy::len_without_is_empty,
39  clippy::redundant_field_names,
40  clippy::too_many_arguments,
41  clippy::single_component_path_imports,
42  clippy::double_must_use
43)]
44/* Default isn't as big a deal as people seem to think it is. */
45#![allow(clippy::new_without_default, clippy::new_ret_no_self)]
46/* Arc<Mutex> can be more clear than needing to grok Orderings. */
47#![allow(clippy::mutex_atomic)]
48
49use libmedusa_zip as lib;
50
51use pyo3::{exceptions::PyValueError, prelude::*};
52
53use std::path::PathBuf;
54
55
56#[pyclass]
57#[derive(Clone)]
58pub struct EntryName(pub String);
59
60#[pymethods]
61impl EntryName {
62  #[new]
63  fn new(name: String) -> PyResult<Self> {
64    /* TODO: better error! */
65    let parsed =
66      lib::EntryName::validate(name).map_err(|e| PyValueError::new_err(format!("{}", e)))?;
67    Ok(parsed.into())
68  }
69
70  fn __repr__(&self) -> String { format!("EntryName({:?})", &self.0) }
71
72  fn __str__(&self) -> String { self.0.clone() }
73}
74
75impl TryFrom<EntryName> for lib::EntryName {
76  type Error = lib::MedusaNameFormatError;
77
78  fn try_from(x: EntryName) -> Result<Self, Self::Error> {
79    let EntryName(x) = x;
80    Self::validate(x)
81  }
82}
83
84impl From<lib::EntryName> for EntryName {
85  fn from(x: lib::EntryName) -> Self { Self(x.into_string()) }
86}
87
88
89#[pyclass]
90#[derive(Clone)]
91pub struct FileSource {
92  #[pyo3(get)]
93  pub name: EntryName,
94  #[pyo3(get)]
95  pub source: PathBuf,
96}
97
98#[pymethods]
99impl FileSource {
100  #[new]
101  fn new(name: EntryName, source: PathBuf) -> Self { Self { name, source } }
102
103  fn __repr__(&self, py: Python<'_>) -> PyResult<String> {
104    let Self { name, source } = self;
105    let name = crate::util::repr(py, name.clone())?;
106    let source = crate::util::repr(py, source.clone())?;
107    Ok(format!("FileSource(name={}, source={})", name, source))
108  }
109}
110
111impl TryFrom<FileSource> for lib::FileSource {
112  type Error = lib::MedusaNameFormatError;
113
114  fn try_from(x: FileSource) -> Result<Self, Self::Error> {
115    let FileSource { name, source } = x;
116    let name: lib::EntryName = name.try_into()?;
117    Ok(Self { name, source })
118  }
119}
120
121impl From<lib::FileSource> for FileSource {
122  fn from(x: lib::FileSource) -> Self {
123    let lib::FileSource { name, source } = x;
124    Self {
125      name: name.into(),
126      source,
127    }
128  }
129}
130
131
132/* TODO: consider adding TailTasks as in pants's task_executor subcrate in
133 * case we ever end up spawning further background tasks or whatever. */
134#[cfg(feature = "sync")]
135pub(crate) static TOKIO_RUNTIME: once_cell::sync::Lazy<tokio::runtime::Runtime> =
136  once_cell::sync::Lazy::new(|| {
137    tokio::runtime::Runtime::new().expect("creating ffi runtime failed")
138  });
139
140
141fn add_submodule(parent: &PyModule, py: Python<'_>, child: &PyModule) -> PyResult<()> {
142  parent.add_submodule(child)?;
143  py.import("sys")?
144    .getattr("modules")?
145    .set_item(format!("{}.{}", parent.name()?, child.name()?), child)?;
146  Ok(())
147}
148
149/// A Python module implemented in Rust. The name of this function must match
150/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
151/// import the module.
152#[pymodule]
153fn pymedusa_zip(py: Python<'_>, medusa_zip: &PyModule) -> PyResult<()> {
154  let crawl = crawl::crawl_module(py)?;
155  add_submodule(medusa_zip, py, crawl)?;
156  let merge = merge::merge_module(py)?;
157  add_submodule(medusa_zip, py, merge)?;
158  let destination = destination::destination_module(py)?;
159  add_submodule(medusa_zip, py, destination)?;
160  let zip = zip::zip_module(py)?;
161  add_submodule(medusa_zip, py, zip)?;
162
163  medusa_zip.add_class::<EntryName>()?;
164  medusa_zip.add_class::<FileSource>()?;
165
166  Ok(())
167}
168
169mod crawl;
170mod destination;
171mod merge;
172mod zip;
173
174mod util;