initial version
- preludes
- slice::{RotateInsertExt, SplitOn}, cstr::{UnixCStrAs, CStrSplit}, str::UnixStrAs
- exhaustive testing and checking
- documentation for everything
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
[codespell]
|
||||||
|
skip = ./target
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"proseWrap": "always",
|
||||||
|
"printWidth": 80
|
||||||
|
}
|
||||||
Generated
+7
@@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "boilerext"
|
||||||
|
version = "0.1.0"
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "boilerext"
|
||||||
|
description = "Rlib with useful boilerplate extension traits for QoL"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://git.javalsai.tuxcord.net/rust/boilerext"
|
||||||
|
keywords = ["ext", "trait", "extension-trait"]
|
||||||
|
categories = ["rust-patterns"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
[lints.clippy]
|
||||||
|
cargo = { level = "warn", priority = -1 }
|
||||||
|
correctness = { level = "deny", priority = -1 }
|
||||||
|
nursery = { level = "deny", priority = -1 }
|
||||||
|
option_if_let_else = { level = "allow" }
|
||||||
|
|
||||||
|
pedantic = { level = "deny", priority = -1 }
|
||||||
|
perf = { level = "deny", priority = -1 }
|
||||||
|
style = { level = "deny", priority = -1 }
|
||||||
|
unwrap_used = "deny"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2026 javalsai
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# boilerext
|
||||||
|
|
||||||
|
Rlib with useful boilerplate extension traits for QoL.
|
||||||
|
|
||||||
|
Aims to extend methods that have negligible performance cost, which only
|
||||||
|
reasonable barrier is their lenthyness or unintuitiveness.
|
||||||
|
|
||||||
|
Uses unsafe rust where its safetyness is immediately obvious to squeeze
|
||||||
|
performance without fear of breaking safety contracts if the abstracted method
|
||||||
|
wasn't there.
|
||||||
|
|
||||||
|
# Docs
|
||||||
|
|
||||||
|
Documentation is not hosted publicly for now, clone and run `cargo doc` to
|
||||||
|
generate it.
|
||||||
|
|
||||||
|
# Development
|
||||||
|
|
||||||
|
This project uses cargo test/miri test/doc/fmt/check/clippy, codespell, prettier
|
||||||
|
and shellcheck to check and help in development.
|
||||||
|
|
||||||
|
You can check for everything (as long as you have the tools) with
|
||||||
|
`scripts/check.sh`. For additional stuff pass `extra` as the first argument to
|
||||||
|
said script.
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
This code is licensed under the MIT license, a copy can be found at the root of
|
||||||
|
this project.
|
||||||
|
|
||||||
|
The philosophy of the project is just to provide small extension traits with
|
||||||
|
accessibility ease, so the MIT license was chosen to avoid legal friction.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
doc-valid-idents = ["QoL", ".."]
|
||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
ANSIRED="\033[1;31m"
|
||||||
|
ANSIRESET="\033[0m"
|
||||||
|
errprint() {
|
||||||
|
printf "%b%s%b\n" \
|
||||||
|
"$ANSIRED" "$*" "$ANSIRESET" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -f "Cargo.toml" ] || {
|
||||||
|
errprint "No Cargo.toml present"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
set -x
|
||||||
|
|
||||||
|
cargo check -q
|
||||||
|
cargo clippy -q
|
||||||
|
cargo test -q
|
||||||
|
cargo doc -q
|
||||||
|
cargo fmt --check
|
||||||
|
codespell
|
||||||
|
prettier --check .
|
||||||
|
git ls-files -z "*.sh" | xargs -0 shellcheck
|
||||||
|
)
|
||||||
|
|
||||||
|
if [ "${1:-}" == "extra" ]; then (
|
||||||
|
set -x
|
||||||
|
|
||||||
|
cargo +nightly miri test -q
|
||||||
|
) fi
|
||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
//! Prelude with extended method names that should never collide.
|
||||||
|
//!
|
||||||
|
//! Trait identifiers are not exported, only the implementation methods are.
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # use std::ffi::CStr;
|
||||||
|
//! use boilerext::prelude::*;
|
||||||
|
//! CStr::split_ch;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ```compile_fail
|
||||||
|
//! use boilerext::prelude::*;
|
||||||
|
//! CStrSplit;
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
pub use crate::{cstr::CStrSplit as _, slice::RotateInsertExt as _, slice::SplitOn as _};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod unix_prelude {
|
||||||
|
//! Analogous to [`crate::prelude`] but only for traits exclusive to unix-based systems.
|
||||||
|
|
||||||
|
pub use crate::{cstr::UnixCStrAs as _, str::UnixStrAs as _};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod slice {
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
|
/// Rotates the slice like `rotate_right` while inserting a given value and returning the
|
||||||
|
/// popped-off elements.
|
||||||
|
pub trait RotateInsertExt<T, const N: usize> {
|
||||||
|
/// Checked version of [`rotate_insert`](Self::rotate_insert)
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use boilerext::slice::RotateInsertExt as _;
|
||||||
|
/// let array: &mut [u8] = &mut [];
|
||||||
|
/// assert_eq!(None, array[..].checked_rotate_insert([0]));
|
||||||
|
/// ```
|
||||||
|
fn checked_rotate_insert(&mut self, insert: [T; N]) -> Option<[T; N]>;
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If the slice is smaller than `N`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use boilerext::slice::RotateInsertExt as _;
|
||||||
|
/// let array: &mut [u8] = &mut [0, 1, 2, 3, 4];
|
||||||
|
/// // [ 2.. ];
|
||||||
|
///
|
||||||
|
/// // [5, 6] => [2, 3, 4]
|
||||||
|
/// // [5, 6, 2] => [3, 4]
|
||||||
|
/// let taken_out = array[2..].rotate_insert([5, 6]);
|
||||||
|
/// assert_eq!(array, &[0, 1, 5, 6, 2]);
|
||||||
|
/// assert_eq!(taken_out, [3, 4]);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// # use boilerext::slice::RotateInsertExt as _;
|
||||||
|
/// let array: &mut [u8] = &mut [];
|
||||||
|
/// let taken_out = array[..].rotate_insert([0]);
|
||||||
|
/// ```
|
||||||
|
fn rotate_insert(&mut self, insert: [T; N]) -> [T; N] {
|
||||||
|
self.checked_rotate_insert(insert)
|
||||||
|
.expect("slice should be bigger than the rotation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> RotateInsertExt<T, N> for [T] {
|
||||||
|
fn checked_rotate_insert(&mut self, mut insert: [T; N]) -> Option<[T; N]> {
|
||||||
|
if self.len() < N {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rotate_right(N);
|
||||||
|
// swaps `insert` with the rotated part of `self` such that `insert` also becomes the
|
||||||
|
// return slice
|
||||||
|
for idx in 0..N {
|
||||||
|
std::mem::swap(&mut self[idx], &mut insert[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(insert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Intended to resemble the currently nightly feature `slice_split_once`
|
||||||
|
/// ([#112811](https://github.com/rust-lang/rust/issues/112811)) but for a value instead of an
|
||||||
|
/// equality predicate.
|
||||||
|
pub trait SplitOn<T: Eq> {
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use boilerext::slice::SplitOn as _;
|
||||||
|
/// let (l, r) = b"abcde".split_on(b'c').expect("to contain 'c'");
|
||||||
|
/// assert_eq!(l, b"ab");
|
||||||
|
/// assert_eq!(r, b"de");
|
||||||
|
///
|
||||||
|
/// let None = b"abcde".split_on(b'0') else { panic!("to not contain '0'"); };
|
||||||
|
/// ```
|
||||||
|
fn split_on(&self, value: impl Borrow<T>) -> Option<(&[T], &[T])>;
|
||||||
|
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use boilerext::slice::SplitOn as _;
|
||||||
|
/// let mut slice: [u8; _] = *b"abcde";
|
||||||
|
/// let (l, r): (&mut _, &mut _) = slice.split_on_mut(b'c').expect("to contain 'c'");
|
||||||
|
/// assert_eq!(l, b"ab");
|
||||||
|
/// assert_eq!(r, b"de");
|
||||||
|
///
|
||||||
|
/// let None = slice.split_on_mut(b'0') else { panic!("to not contain '0'"); };
|
||||||
|
/// ```
|
||||||
|
fn split_on_mut(&mut self, value: impl Borrow<T>) -> Option<(&mut [T], &mut [T])>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Eq> SplitOn<T> for [T] {
|
||||||
|
fn split_on(&self, value: impl Borrow<T>) -> Option<(&[T], &[T])> {
|
||||||
|
let (idx, _) = self
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, b)| *b == value.borrow())?;
|
||||||
|
// SAFETY: idx is IN-OF-BOUNDS index of the slice
|
||||||
|
unsafe { Some((self.get_unchecked(0..idx), self.get_unchecked((idx + 1)..))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_on_mut(&mut self, value: impl Borrow<T>) -> Option<(&mut [T], &mut [T])> {
|
||||||
|
let (idx, _) = self
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, b)| *b == value.borrow())?;
|
||||||
|
// SAFETY: idx is IN-OF-BOUNDS index of the slice
|
||||||
|
let (a, b) = unsafe { self.split_at_mut_unchecked(idx) };
|
||||||
|
Some((a, &mut b[1..])) // b includes index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod cstr {
|
||||||
|
use std::{
|
||||||
|
ffi::{CStr, OsStr},
|
||||||
|
num::NonZeroU8,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::slice::SplitOn;
|
||||||
|
|
||||||
|
/// Implements transparent `as_*` methods for unix systems.
|
||||||
|
pub trait UnixCStrAs {
|
||||||
|
fn as_os_str(&self) -> &OsStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnixCStrAs for CStr {
|
||||||
|
fn as_os_str(&self) -> &OsStr {
|
||||||
|
use std::os::unix::ffi::OsStrExt as _;
|
||||||
|
OsStr::from_bytes(self.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements split methods for [`CStr`]ings
|
||||||
|
pub trait CStrSplit {
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use std::num::NonZeroU8;
|
||||||
|
/// # use boilerext::cstr::CStrSplit;
|
||||||
|
/// let string = c"abcde";
|
||||||
|
///
|
||||||
|
/// let (l, r) = string.split_ch(NonZeroU8::new(b'c').unwrap()).expect("to contain 'c'");
|
||||||
|
/// assert_eq!(l, b"ab");
|
||||||
|
/// assert_eq!(r, c"de");
|
||||||
|
/// ```
|
||||||
|
fn split_ch(&self, ch: NonZeroU8) -> Option<(&[u8], &CStr)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CStrSplit for CStr {
|
||||||
|
fn split_ch(&self, ch: NonZeroU8) -> Option<(&[u8], &CStr)> {
|
||||||
|
let self_slice = self.to_bytes_with_nul();
|
||||||
|
|
||||||
|
let (l, r) = self_slice.split_on(ch.get())?;
|
||||||
|
// SAFETY: split originated in a slice with a final NULL byte.
|
||||||
|
// Hence, second split, occurring before NULL byte because `ch` can't be 0, will include
|
||||||
|
// the final byte.
|
||||||
|
debug_assert!(r.ends_with(&[0]));
|
||||||
|
Some((l, unsafe { Self::from_bytes_with_nul_unchecked(r) }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod str {
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
|
||||||
|
/// Implements transparent `as_*` methods for unix systems.
|
||||||
|
pub trait UnixStrAs {
|
||||||
|
fn as_os_str(&self) -> &OsStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnixStrAs for str {
|
||||||
|
fn as_os_str(&self) -> &OsStr {
|
||||||
|
use std::os::unix::ffi::OsStrExt as _;
|
||||||
|
OsStr::from_bytes(self.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user