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