strict-0.2.0/.cargo_vcs_info.json0000644000000001360000000000100123430ustar { "git": { "sha1": "66298f6a90c3a2955e8aa10d530d08fd3d84c480" }, "path_in_vcs": "" }strict-0.2.0/.github/workflows/rust.yml000064400000000000000000000003301046102023000162440ustar 00000000000000name: Rust on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose strict-0.2.0/.gitignore000064400000000000000000000005001046102023000131160ustar 00000000000000# Generated by Cargo # will have compiled files and executables /target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk strict-0.2.0/Cargo.toml0000644000000014140000000000100103410ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.56" name = "strict" version = "0.2.0" authors = ["dystroy "] description = "collections with strict bounds" readme = "README.md" keywords = ["vec"] categories = [] license = "MIT" repository = "https://github.com/Canop/strict" [dependencies] strict-0.2.0/Cargo.toml.orig000064400000000000000000000004711046102023000140240ustar 00000000000000[package] name = "strict" version = "0.2.0" authors = ["dystroy "] repository = "https://github.com/Canop/strict" description = "collections with strict bounds" edition = "2021" keywords = ["vec"] license = "MIT" categories = [] readme = "README.md" rust-version = "1.56" [dependencies] strict-0.2.0/LICENSE000064400000000000000000000020461046102023000121420ustar 00000000000000MIT License Copyright (c) 2019 Canop 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. strict-0.2.0/README.md000064400000000000000000000011421046102023000124100ustar 00000000000000[![MIT][s2]][l2] [![Latest Version][s1]][l1] [![docs][s3]][l3] [![Chat on Miaou][s4]][l4] [s1]: https://img.shields.io/crates/v/strict.svg [l1]: https://crates.io/crates/strict [s2]: https://img.shields.io/badge/license-MIT-blue.svg [l2]: LICENSE [s3]: https://docs.rs/strict/badge.svg [l3]: https://docs.rs/strict/ [s4]: https://miaou.dystroy.org/static/shields/room.svg [l4]: https://miaou.dystroy.org/3 # strict Provides the NonEmptyVec, a vec guaranteed to hold at least one element. Probably redundant with other better libs. Alternatives: * [non-empty](https://docs.rs/crate/non-empty/0.1.0) strict-0.2.0/src/lib.rs000064400000000000000000000003051046102023000130340ustar 00000000000000//! Provide some types with inherent bounds, useful when you want to avoid unwrap or want const //! matching. //! mod non_empty_vec; mod one_to_three; pub use {non_empty_vec::*, one_to_three::*}; strict-0.2.0/src/non_empty_vec.rs000064400000000000000000000103101046102023000151300ustar 00000000000000use std::{ convert::TryFrom, num::NonZeroUsize, ops::{Deref, Index, IndexMut}, slice, }; #[derive(Debug, Clone)] pub struct NotEnoughElementsError; /// a mostly costless wrapping of a vec, ensuring there's always at least one element. /// /// Follow the semantics of Vec (differing methods have a different name). /// #[derive(Debug, Clone)] pub struct NonEmptyVec { vec: Vec, } impl NonEmptyVec { #[inline] pub fn len(&self) -> NonZeroUsize { unsafe { NonZeroUsize::new_unchecked(self.vec.len()) } } #[inline] pub fn has_len(&self, len: usize) -> bool { self.vec.len() == len } #[inline] pub fn first(&self) -> &T { unsafe { self.vec.get_unchecked(0) } } #[inline] pub fn first_mut(&mut self) -> &mut T { unsafe { self.vec.get_unchecked_mut(0) } } #[inline] pub fn last(&self) -> &T { unsafe { self.vec.get_unchecked(self.vec.len() - 1) } } #[inline] pub fn last_mut(&mut self) -> &mut T { let idx = self.vec.len() - 1; unsafe { self.vec.get_unchecked_mut(idx) } } /// take the first item, discard the rest #[inline] pub fn take(mut self) -> T { self.vec.drain(..).next().unwrap() } #[inline] pub fn push(&mut self, value: T) { self.vec.push(value); } #[inline] pub fn insert(&mut self, insertion_idx: usize, value: T) { self.vec.insert(insertion_idx, value); } /// Removes the last element from a vector and returns it, or [`None`] if it /// contains only one element #[inline] pub fn pop(&mut self) -> Option { if self.vec.len() == 1 { None } else { self.vec.pop() } } #[inline] pub fn as_slice(&self) -> &[T] { &self.vec } #[inline] pub fn as_mut_slice(&mut self) -> &mut [T] { &mut self.vec } #[inline] pub fn remove(&mut self, idx: usize) -> Result { if self.vec.len() == 1 { Err(NotEnoughElementsError) } else { Ok(self.vec.remove(idx)) } } #[inline] pub fn swap_remove(&mut self, idx: usize) -> Result { if self.vec.len() == 1 { Err(NotEnoughElementsError) } else { Ok(self.vec.swap_remove(idx)) } } } impl TryFrom> for NonEmptyVec { type Error = NotEnoughElementsError; #[inline] fn try_from(vec: Vec) -> Result { if vec.is_empty() { Err(NotEnoughElementsError) } else { Ok(Self { vec }) } } } impl From for NonEmptyVec { #[inline] fn from(value: T) -> Self { Self { vec: vec![value] } } } impl Deref for NonEmptyVec { type Target = [T]; fn deref(&self) -> &[T] { self.vec.deref() } } impl> Index for NonEmptyVec { type Output = I::Output; #[inline] fn index(&self, index: I) -> &Self::Output { Index::index(self.as_slice(), index) } } impl> IndexMut for NonEmptyVec { #[inline] fn index_mut(&mut self, index: I) -> &mut Self::Output { IndexMut::index_mut(self.as_mut_slice(), index) } } impl<'a, T> IntoIterator for &'a mut NonEmptyVec { type Item = &'a mut T; type IntoIter = slice::IterMut<'a, T>; #[inline] fn into_iter(self) -> slice::IterMut<'a, T> { self.vec.iter_mut() } } impl<'a, T> IntoIterator for &'a NonEmptyVec { type Item = &'a T; type IntoIter = slice::Iter<'a, T>; #[inline] fn into_iter(self) -> slice::Iter<'a, T> { self.vec.iter() } } #[cfg(test)] mod non_empty_vec_tests { use {super::*, std::convert::TryInto}; #[test] fn test_pop_push() { let mut vec: NonEmptyVec = vec![1, 2].try_into().unwrap(); assert_eq!(vec.pop(), Some(2)); assert_eq!(vec.pop(), None); assert_eq!(vec[0], 1); vec[0] = 0; assert_eq!(*vec.first(), 0); let first: &mut usize = vec.first_mut(); *first = 4; assert_eq!(vec[0], 4); } } strict-0.2.0/src/one_to_three.rs000064400000000000000000000167451046102023000147570ustar 00000000000000use std::{fmt, hash}; /// An ordered set of 1, 2 or 3 elements, allowing pattern matching. /// /// Implements Copy, Clone, PartialEq, Eq, Debug, etc. if the element type does. pub enum OneToThree { One(T), Two(T, T), Three(T, T, T), } #[allow(clippy::len_without_is_empty)] impl OneToThree { pub fn one(a: T) -> Self { Self::One(a) } pub fn two(a: T, b: T) -> Self { Self::Two(a, b) } pub fn three(a: T, b: T, c: T) -> Self { Self::Three(a, b, c) } pub fn len(&self) -> usize { match self { Self::One(_) => 1, Self::Two(_, _) => 2, Self::Three(_, _, _) => 3, } } pub fn iter(&self) -> OneToThreeIter<'_, T> { OneToThreeIter::new(self) } pub fn first(&self) -> &T { match self { Self::One(f) => f, Self::Two(f, _) => f, Self::Three(f, _, _) => f, } } pub fn first_mut(&mut self) -> &mut T { match self { Self::One(ref mut f) => f, Self::Two(ref mut f, _) => f, Self::Three(ref mut f, _, _) => f, } } pub fn get(&self, i: usize) -> Option<&T> { match (i, self) { (0, _) => Some(self.first()), (1, Self::Two(_, b)) => Some(b), (1, Self::Three(_, b, _)) => Some(b), (2, Self::Three(_, _, c)) => Some(c), _ => None, } } pub fn get_mut(&mut self, i: usize) -> Option<&mut T> { match (i, self) { (0, Self::One(ref mut a)) => Some(a), (0, Self::Two(ref mut a, _)) => Some(a), (0, Self::Three(ref mut a, _, _)) => Some(a), (1, Self::Two(_, ref mut b)) => Some(b), (1, Self::Three(_, ref mut b, _)) => Some(b), (2, Self::Three(_, _, ref mut c)) => Some(c), _ => None, } } pub fn to_vec(self) -> Vec { match self { Self::One(a) => vec![a], Self::Two(a, b) => vec![a, b], Self::Three(a, b, c) => vec![a, b, c], } } pub fn to_ref_vec(&self) -> Vec<&T> { match self { Self::One(a) => vec![a], Self::Two(a, b) => vec![a, b], Self::Three(a, b, c) => vec![a, b, c], } } pub fn sorted(self) -> Self where T: PartialOrd, { match self { Self::One(a) => Self::One(a), Self::Two(a, b) => { if a < b { Self::Two(a, b) } else { Self::Two(b, a) } } Self::Three(a, b, c) => { if a < b { if b < c { Self::Three(a, b, c) } else if a < c { Self::Three(a, c, b) } else { Self::Three(c, a, b) } } else if a < c { Self::Three(b, a, c) } else if b < c { Self::Three(b, c, a) } else { Self::Three(c, b, a) } } } } pub fn map(self, f: F) -> OneToThree where F: Fn(T) -> B, { match self { Self::One(a) => OneToThree::One(f(a)), Self::Two(a, b) => OneToThree::Two(f(a), f(b)), Self::Three(a, b, c) => OneToThree::Three(f(a), f(b), f(c)), } } pub fn try_map(self, f: F) -> Result, E> where F: Fn(T) -> Result, { Ok(match self { Self::One(a) => OneToThree::One(f(a)?), Self::Two(a, b) => OneToThree::Two(f(a)?, f(b)?), Self::Three(a, b, c) => OneToThree::Three(f(a)?, f(b)?, f(c)?), }) } } impl Clone for OneToThree { fn clone(&self) -> Self { *self } } impl Copy for OneToThree {} impl PartialEq for OneToThree { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::One(a), Self::One(b)) => a == b, (Self::Two(a, b), Self::Two(c, d)) => a == c && b == d, (Self::Three(a, b, c), Self::Three(d, e, f)) => a == d && b == e && c == f, _ => false, } } } impl Eq for OneToThree {} impl fmt::Debug for OneToThree { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let v = self.to_ref_vec(); f.debug_list().entries(v).finish() } } impl hash::Hash for OneToThree { fn hash(&self, state: &mut H) { for e in self.iter() { e.hash(state); } } } pub struct OneToThreeIter<'i, T> { idx: usize, few: &'i OneToThree, } impl<'i, T> OneToThreeIter<'i, T> { pub fn new(few: &'i OneToThree) -> Self { Self { idx: 0, few } } } impl<'i, T> Iterator for OneToThreeIter<'i, T> { type Item = &'i T; fn next(&mut self) -> Option { let i = self.idx; self.idx += 1; self.few.get(i) } } impl<'a, T> IntoIterator for &'a OneToThree { type Item = &'a T; type IntoIter = OneToThreeIter<'a, T>; #[inline] fn into_iter(self) -> OneToThreeIter<'a, T> { self.iter() } } impl TryFrom> for OneToThree { type Error = &'static str; fn try_from(mut v: Vec) -> Result { let c = v.pop().ok_or("Empty vec")?; if let Some(b) = v.pop() { if let Some(a) = v.pop() { Ok(Self::Three(a, b, c)) } else { Ok(Self::Two(b, c)) } } else { Ok(Self::One(c)) } } } impl From for OneToThree { fn from(a: T) -> Self { Self::One(a) } } impl From<(T, T)> for OneToThree { fn from(t: (T, T)) -> Self { Self::Two(t.0, t.1) } } impl From<(T, T, T)> for OneToThree { fn from(t: (T, T, T)) -> Self { Self::Three(t.0, t.1, t.2) } } #[test] fn test_sort() { assert_eq!(OneToThree::one(1).sorted(), OneToThree::one(1)); assert_eq!(OneToThree::two(5, 2).sorted(), OneToThree::two(2, 5)); assert_eq!(OneToThree::two(1, 2).sorted(), OneToThree::two(1, 2)); assert_eq!( OneToThree::three(1, 2, 1).sorted(), OneToThree::three(1, 1, 2) ); assert_eq!( OneToThree::three(3, 2, 1).sorted(), OneToThree::three(1, 2, 3) ); assert_eq!( OneToThree::three(3, 2, 4).sorted(), OneToThree::three(2, 3, 4) ); assert_eq!( OneToThree::three(1, 2, 3).sorted(), OneToThree::three(1, 2, 3) ); assert_eq!( OneToThree::three(1, 3, 2).sorted(), OneToThree::three(1, 2, 3) ); assert_eq!( OneToThree::three(2, 1, 3).sorted(), OneToThree::three(1, 2, 3) ); assert_eq!( OneToThree::three(3, 1, 2).sorted(), OneToThree::three(1, 2, 3) ); } #[test] fn test_map() { assert_eq!( OneToThree::three(1, 2, 3).map(|x| x.to_string()), OneToThree::three("1".to_string(), "2".to_string(), "3".to_string()), ); } #[test] fn test_try_map() { assert_eq!( OneToThree::three("1", "-2", "3") .try_map(|x| x.parse()) .unwrap(), OneToThree::three(1, -2, 3), ); assert!(OneToThree::three("1", "-2", "3") .try_map::(|x| x.parse()) .is_err()); }