Skip to content

Commit

Permalink
Fs structs (#202)
Browse files Browse the repository at this point in the history
* feat: std::fs structs

* fix: mypy types for fs structs

* update api docs
  • Loading branch information
jessekrubin authored Feb 18, 2025
1 parent 104c93f commit db86100
Show file tree
Hide file tree
Showing 18 changed files with 655 additions and 255 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
- internal
- Switch all lints from `#[allow(...)]`/`#![allow(...)]` to `#[expect(...)]`/`#![expect(...)]`
- Removed a bunch o commented out code
- `ryo3-std`
- added several `std::fs` structs

___

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 62 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ JsonValue = (
# =============================================================================


# -----------------------------------------------------------------------------
# STD::TIME
# -----------------------------------------------------------------------------
class Duration:
ZERO: Duration
MIN: Duration
Expand Down Expand Up @@ -310,6 +313,41 @@ class Instant:
def instant() -> Instant: ...


# -----------------------------------------------------------------------------
# STD::FS
# -----------------------------------------------------------------------------
class FileType:
def __repr__(self) -> str: ...
@property
def is_dir(self) -> bool: ...
@property
def is_file(self) -> bool: ...
@property
def is_symlink(self) -> bool: ...


class Metadata:
def __repr__(self) -> str: ...
@property
def file_type(self) -> FileType: ...
@property
def len(self) -> int: ...
@property
def is_empty(self) -> bool: ...
@property
def modified(self) -> pydt.datetime: ...
@property
def accessed(self) -> pydt.datetime: ...
@property
def created(self) -> pydt.datetime: ...
@property
def is_dir(self) -> bool: ...
@property
def is_file(self) -> bool: ...
@property
def is_symlink(self) -> bool: ...


# =============================================================================
# RY03-CORE
# =============================================================================
Expand Down Expand Up @@ -634,6 +672,30 @@ def globster(
# =============================================================================
# WALKDIR
# =============================================================================
class WalkDirEntry:
def __fspath__(self) -> str: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
@property
def path(self) -> FsPath: ...
@property
def file_name(self) -> str: ...
@property
def depth(self) -> int: ...
@property
def path_is_symlink(self) -> bool: ...
@property
def file_type(self) -> FileType: ...
@property
def is_dir(self) -> bool: ...
@property
def is_file(self) -> bool: ...
@property
def is_symlink(self) -> bool: ...
@property
def len(self) -> int: ...


class WalkdirGen:
"""walkdir::Walkdir iterable wrapper"""

Expand Down Expand Up @@ -715,7 +777,6 @@ def jiter_cache_usage() -> int: ...
# =============================================================================
# FORMATTING
# =============================================================================
def fmt_nbytes(nbytes: int) -> str: ...
def unindent(string: str) -> str: ...
def unindent_bytes(string: bytes) -> bytes: ...

Expand Down
90 changes: 55 additions & 35 deletions crates/ryo3-bytes/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,55 +64,70 @@ impl PyBytes {
self.as_ref()
}

/// Slice the underlying buffer using a Python slice object
///
/// This should behave the same as Python's byte slicing:
/// - `ValueError` if step is zero
/// - Negative indices a-ok
/// - If start/stop are out of bounds, they are clipped to the bounds of the buffer
/// - If start > stop, the slice is empty
///
/// This is NOT exposed to Python under the `#[pymethods]` impl
fn slice(&self, slice: &Bound<'_, PySlice>) -> PyResult<PyBytes> {
let len_isize = self.0.len() as isize;
let psi = slice.indices(len_isize)?;
let (start, stop, step) = (psi.start, psi.stop, psi.step);
if step == 0 {
return Err(PyValueError::new_err("step is zero"));
}
let bytes_length = self.0.len() as isize;
let (start, stop, step) = {
let slice_indicies = slice.indices(bytes_length)?;
(
slice_indicies.start,
slice_indicies.stop,
slice_indicies.step,
)
};

// I think this is right!?
let new_cap_usize = if (step > 0 && stop > start) || (step < 0 && stop < start) {
let new_capacity = if (step > 0 && stop > start) || (step < 0 && stop < start) {
(((stop - start).abs() + step.abs() - 1) / step.abs()) as usize
} else {
0
};

if new_cap_usize == 0 {
if new_capacity == 0 {
return Ok(PyBytes(Bytes::new()));
}
if step == 1 {
// if start < 0 and stop > len and step == 1 just copy?
if start < 0 && stop >= bytes_length {
let out = self.0.slice(..);
let py_bytes = PyBytes(out);
return Ok(py_bytes);
}

// if start < 0 and stop > len and step == 1 just copy?
if step == 1 && start < 0 && stop >= len_isize {
let out = self.0.slice(..);
let py_bytes = PyBytes(out);
return Ok(py_bytes);
}

if step == 1 && start >= 0 && stop <= len_isize && start < stop {
let out = self.0.slice(start as usize..stop as usize);
let py_bytes = PyBytes(out);
return Ok(py_bytes);
if start >= 0 && stop <= bytes_length && start < stop {
let out = self.0.slice(start as usize..stop as usize);
let py_bytes = PyBytes(out);
return Ok(py_bytes);
}
// fall through to the general case here...
}
let mut new_buf = BytesMut::with_capacity(new_cap_usize);
if step > 0 {
// forward
let mut new_buf = BytesMut::with_capacity(new_capacity);
new_buf.extend(
(start..stop)
.step_by(step as usize)
.map(|i| self.0[i as usize]),
);
Ok(PyBytes(new_buf.freeze()))
} else {
// backward
let mut new_buf = BytesMut::with_capacity(new_capacity);
new_buf.extend(
(stop + 1..=start)
.rev()
.step_by((-step) as usize)
.map(|i| self.0[i as usize]),
);
Ok(PyBytes(new_buf.freeze()))
}
Ok(PyBytes(new_buf.freeze()))
}
}

Expand Down Expand Up @@ -177,21 +192,27 @@ impl PyBytes {
self.0.as_ref() == other.0.as_ref()
}

fn __getitem__<'py>(&self, py: Python<'py>, key: BytesGetItemKey<'py>) -> PyResult<PyObject> {
fn __getitem__<'py>(
&self,
py: Python<'py>,
key: BytesGetItemKey<'py>,
) -> PyResult<Bound<'py, PyAny>> {
match key {
BytesGetItemKey::Int(mut index) => {
if index < 0 {
index += self.0.len() as isize;
}

if index < 0 {
return Err(PyIndexError::new_err("Index out of range"));
}
self.0
.get(index as usize)
.ok_or(PyIndexError::new_err("Index out of range"))?
.into_py_any(py)
.into_bound_py_any(py)
}
BytesGetItemKey::Slice(slice) => {
let s = self.slice(&slice)?;
s.into_py_any(py)
s.into_bound_py_any(py)
}
}
}
Expand Down Expand Up @@ -291,13 +312,12 @@ impl PyBytes {
/// Return True if the sequence is empty or all bytes in the sequence are ASCII, False
/// otherwise. ASCII bytes are in the range 0-0x7F.
fn isascii(&self) -> bool {
self.0.as_ref().is_ascii()
// for c in self.0.as_ref() {
// if !c.is_ascii() {
// return false;
// }
// }
// true
for c in self.0.as_ref() {
if !c.is_ascii() {
return false;
}
}
true
}

/// Return True if all bytes in the sequence are ASCII decimal digits and the sequence is not
Expand Down Expand Up @@ -396,7 +416,7 @@ impl<'py> FromPyObject<'py> for PyBytes {
///
/// This also implements AsRef<[u8]> because that is required for Bytes::from_owner
#[derive(Debug)]
pub struct PyBytesWrapper(Option<PyBuffer<u8>>);
struct PyBytesWrapper(Option<PyBuffer<u8>>);

impl Drop for PyBytesWrapper {
#[allow(unsafe_code)]
Expand Down Expand Up @@ -482,7 +502,7 @@ impl std::fmt::Debug for PyBytes {

/// A key for the `__getitem__` method of `PyBytes` - int/slice
#[derive(FromPyObject)]
pub enum BytesGetItemKey<'py> {
enum BytesGetItemKey<'py> {
/// An integer index
Int(isize),
/// A python slice
Expand Down
7 changes: 0 additions & 7 deletions crates/ryo3-bytes/src/bytes_dev.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Dev area for `pyo3-bytes`/`ryo3-bytes` crate
use crate::bytes::PyBytesWrapper;
use crate::{extract_vecu8_ref, PyBytes};
use pyo3::prelude::*;

Expand Down Expand Up @@ -34,17 +33,11 @@ fn bytes_sum_bytes_like(data: &Bound<'_, PyAny>) -> PyResult<u128> {
Ok(bytes_sum_impl(data))
}

#[pyfunction]
fn bytes_sum_bytes_wrapper(data: PyBytesWrapper) -> u128 {
bytes_sum_impl(data.as_ref())
}

/// ryo3-bytes python module registration
pub fn pymod_add(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(bytes_sum_pybytes, m)?)?;
m.add_function(wrap_pyfunction!(bytes_sum_rybytes_ref, m)?)?;
m.add_function(wrap_pyfunction!(bytes_sum_rybytes, m)?)?;
m.add_function(wrap_pyfunction!(bytes_sum_bytes_like, m)?)?;
m.add_function(wrap_pyfunction!(bytes_sum_bytes_wrapper, m)?)?;
Ok(())
}
16 changes: 15 additions & 1 deletion crates/ryo3-globset/src/globster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use pyo3::exceptions::PyValueError;
use pyo3::types::PyTuple;
use pyo3::{pyclass, pymethods, Bound, PyErr, PyResult, Python};
use ryo3_types::PathLike;
use std::path::Path;
use std::str::FromStr;

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -35,6 +36,18 @@ impl TryFrom<Vec<String>> for PyGlobster {
}
}

impl PyGlobster {
pub fn is_match<P: AsRef<Path>>(&self, path: P) -> bool {
let path = path.as_ref();
match (&self.0.globset, &self.0.nglobset) {
(Some(gs), Some(ngs)) => gs.is_match(path) && !ngs.is_match(path),
(Some(gs), None) => gs.is_match(path),
(None, Some(ngs)) => !ngs.is_match(path),
_ => false,
}
}
}

#[pymethods]
impl PyGlobster {
#[new]
Expand Down Expand Up @@ -131,7 +144,8 @@ impl PyGlobster {
}
}

pub fn is_match(&self, path: PathLike) -> bool {
#[pyo3(name = "is_match")]
pub fn py_is_match(&self, path: PathLike) -> bool {
match (&self.0.globset, &self.0.nglobset) {
(Some(gs), Some(ngs)) => gs.is_match(&path) && !ngs.is_match(&path),
(Some(gs), None) => gs.is_match(&path),
Expand Down
Loading

0 comments on commit db86100

Please sign in to comment.