1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
//! This module provides extensions to the Rust standard library.
// Modules
#[cfg(test)]
#[path = "tests/std.rs"]
mod tests;
// Packages
use rust_decimal::{
Decimal,
prelude::ToPrimitive,
};
use std::{
env,
ffi::OsString,
path::{Component as PathComponent, Path, PathBuf},
};
// Structs
// LimitIterator
/// This struct provides an iterator that limits the number of items returned.
///
/// This will be returned from the [`limit()`](IteratorExt::limit()) method, and
/// will generally not be used directly.
///
/// # See also
///
/// * [`IteratorExt::limit()`]
///
#[derive(Clone, Debug)]
pub struct LimitIterator<I> {
// Private properties
/// The iterator to limit.
iter: I,
/// The maximum number of items to return.
limit: Option<usize>,
/// The number of items returned so far.
count: usize,
}
impl<I: Iterator> Iterator for LimitIterator<I> {
type Item = I::Item;
// next
fn next(&mut self) -> Option<Self::Item> {
#[cfg_attr( feature = "reasons", allow(clippy::arithmetic_side_effects, reason = "Range is controlled"))]
#[cfg_attr(not(feature = "reasons"), allow(clippy::arithmetic_side_effects))]
if let Some(limit) = self.limit {
if self.count >= limit {
return None;
}
// In this location, the count is guaranteed to not exceed the limit, so
// this will not overflow and a checked_add() is not required.
self.count += 1;
}
self.iter.next()
}
}
// Traits
//§ AsStr
/// This trait provides an [`as_str()`](AsStr::as_str()) method.
///
/// This trait requires the presence of an [`as_str()`](AsStr::as_str()) method.
/// It's not possible to apply this trait purely as a marker to the existing
/// types such as [`String`] that already have an [`as_str()`](AsStr::as_str())
/// method and have it recognised that they already have it, due to Rust's
/// implementation determination allowing multiple methods of the same name,
/// differentiated by trait. In other words, our trait could define a method
/// with the same name and signature as another trait, but an implementation of
/// the function would not be considered to satisfy both. Both traits would have
/// to have their methods specifically implemented, even if identical, and then
/// the conflict would be resolved at call-time by specifying which trait's
/// method is being called.
///
/// However, it is possible to apply this trait and call the underlying method
/// on the type, for such cases as this may be required. This trait should
/// therefore be applied to any types of interest, for which the [`as_str()`](crate::serde::as_str())
/// serialisation function provided by the [`serde`](crate::serde) module is
/// intended to be specified. Suitable standard and common types such as
/// [`String`] and [`str`] have already had this trait implemented, and those
/// implementations will be brought into scope when this trait is used.
///
/// In reality, implementations onto standard types should not really be
/// necessary, as this trait exists primarily for use with the
/// [`serde::as_str()`](crate::serde::as_str()) method, and Serde already knows
/// how to handle such types so there is no real advantage to be gained by
/// implementing this trait for such types. The intent and purpose of this trait
/// is to provide a way to specify a string representation for types that do not
/// already have one, such as dual-nature enums, i.e. where they can be
/// represented as either a string or a number. Still, the trait has been
/// applied to some common types for consistency and completeness.
///
/// The only current drawback is that trait functions cannot currently be
/// declared as `const`, and the scope of the [`as_str()`](AsStr::as_str())
/// method is usually such that it could be declared as `const` otherwise.
///
pub trait AsStr {
// as_str
/// Provides a string slice representation of the type.
#[must_use]
fn as_str(&self) -> &str;
}
impl AsStr for String {
// as_str
fn as_str(&self) -> &str {
// This simply calls the existing method, i.e. String.as_str(), but is
// required to allow the trait to be applied to the type.
self.as_str()
}
}
impl AsStr for str {
// as_str
fn as_str(&self) -> &str {
// This simply returns the existing value, i.e. self, but is required
// to allow the trait to be applied to the type.
self
}
}
//§ FromIntWithScale
/// Converts from an integer to a floating-point number with a specified scale.
///
/// This trait requires the presence of a [`from_int_with_scale()`](FromIntWithScale::from_int_with_scale())
/// method, which converts from an integer to a floating-point number with a
/// specified scale, i.e. a certain number of decimal places. For example, if
/// the scale is `2`, then the integer `1234` would be converted to the
/// floating-point number `12.34`. This is most useful when dealing with
/// currencies.
///
/// The trait is implemented for the standard floating-point types, i.e. [`f32`]
/// and [`f64`], and for the [`Decimal`] type from the [`rust_decimal`](https://crates.io/crates/rust_decimal)
/// crate. For the corresponding integer types expressed as the generic `T`, it
/// is implemented for the standard integer types [`i8`], [`i16`], [`i32`],
/// [`i64`], [`i128`], [`u8`], [`u16`], [`u32`], [`u64`], and [`u128`].
///
/// Note that not all of these integer types can have their full range
/// represented by all of the floating-point types, and so naive conversion may
/// result in them being truncated or rounded. To avoid this happening
/// invisibly, the conversion will return [`None`] if the input number cannot be
/// accurately represented in the output type. Care should be taken to assess
/// the likelihood of this occurring, and to ensure that the correct types are
/// used. This cannot be guaranteed by the compiler, as the outcome depends
/// partly on the type and partly on the scale factor, and so an assessment has
/// to be made at runtime.
///
pub trait FromIntWithScale<T>: Sized {
// from_int_with_scale
/// Converts from an integer to a floating-point number with a specified
/// scale.
///
/// This function converts from an integer to a floating-point number with a
/// specified scale, i.e. a certain number of decimal places. For example,
/// if the scale is `2`, then the integer `1234` would be converted to the
/// floating-point number `12.34`. This is most useful when dealing with
/// currencies.
///
/// Note that not all integer types can have their full range represented by
/// all of the floating-point types, and so naive conversion may result in
/// them being truncated or rounded. To avoid this happening invisibly, the
/// conversion will return [`None`] if the input number cannot be accurately
/// represented in the output type. Care should be taken to assess the
/// likelihood of this occurring, and to ensure that the correct types are
/// used. This cannot be guaranteed by the compiler, as the outcome depends
/// partly on the type and partly on the scale factor, and so an assessment
/// has to be made at runtime.
///
/// # Parameters
///
/// * `value` - The integer value to convert.
/// * `scale` - The scale factor, i.e. the number of decimal places. Note
/// that this is essentially limited to a maximum of 19 DP of
/// movement for an [`f32`] or [`f64`] without overflowing, and
/// 28 DP for a [`Decimal`].
///
/// # See also
///
/// * [`ToIntWithScale::to_int_with_scale()`]
///
fn from_int_with_scale(value: T, scale: u8) -> Option<Self>;
}
// impl_from_int_with_scale_for_float
/// Implements the [`FromIntWithScale`] trait for floating-point types.
macro_rules! impl_from_int_with_scale_for_float {
($t:ty, f32) => {
impl FromIntWithScale<$t> for f32 {
// from_int_with_scale
fn from_int_with_scale(value: $t, scale: u8) -> Option<Self> {
let factor = 10_u32.checked_pow(scale as u32)?;
let scaled = value as f32 / factor as f32;
// We need to manually check if the value exceeds the range of integer
// values supported by an f32, as that will result in a loss of precision.
#[cfg_attr( feature = "reasons", allow(trivial_numeric_casts,
reason = "Trivial casts here are due to the macro permutations"
))]
#[cfg_attr(not(feature = "reasons"), allow(trivial_numeric_casts))]
#[cfg_attr( feature = "reasons", allow(clippy::invalid_upcast_comparisons,
reason = "Superfluous upcast comparisons here are due to the macro permutations"
))]
#[cfg_attr(not(feature = "reasons"), allow(clippy::invalid_upcast_comparisons))]
if scaled.is_infinite() || (value as u128) > 0x0100_0000_u128 || (value as i128) < -0x0100_0000_i128 {
None
} else {
Some(scaled)
}
}
}
};
($t:ty, f64) => {
impl FromIntWithScale<$t> for f64 {
// from_int_with_scale
fn from_int_with_scale(value: $t, scale: u8) -> Option<Self> {
let factor = 10_u64.checked_pow(scale as u32)?;
let scaled = value as f64 / factor as f64;
// We need to manually check if the value exceeds the range of integer
// values supported by an f64, as that will result in a loss of precision.
#[cfg_attr( feature = "reasons", allow(trivial_numeric_casts,
reason = "Trivial casts here are due to the macro permutations"
))]
#[cfg_attr(not(feature = "reasons"), allow(trivial_numeric_casts))]
#[cfg_attr( feature = "reasons", allow(clippy::invalid_upcast_comparisons,
reason = "Superfluous upcast comparisons here are due to the macro permutations"
))]
#[cfg_attr(not(feature = "reasons"), allow(clippy::invalid_upcast_comparisons))]
if scaled.is_infinite() || (value as u128) > 0x0020_0000_0000_0000_u128 || (value as i128) < -0x0020_0000_0000_0000_i128 {
None
} else {
Some(scaled)
}
}
}
};
}
impl_from_int_with_scale_for_float!(i8, f32);
impl_from_int_with_scale_for_float!(i16, f32);
impl_from_int_with_scale_for_float!(i32, f32);
impl_from_int_with_scale_for_float!(i64, f32);
impl_from_int_with_scale_for_float!(i128, f32);
impl_from_int_with_scale_for_float!(i8, f64);
impl_from_int_with_scale_for_float!(i16, f64);
impl_from_int_with_scale_for_float!(i32, f64);
impl_from_int_with_scale_for_float!(i64, f64);
impl_from_int_with_scale_for_float!(i128, f64);
impl_from_int_with_scale_for_float!(u8, f32);
impl_from_int_with_scale_for_float!(u16, f32);
impl_from_int_with_scale_for_float!(u32, f32);
impl_from_int_with_scale_for_float!(u64, f32);
impl_from_int_with_scale_for_float!(u128, f32);
impl_from_int_with_scale_for_float!(u8, f64);
impl_from_int_with_scale_for_float!(u16, f64);
impl_from_int_with_scale_for_float!(u32, f64);
impl_from_int_with_scale_for_float!(u64, f64);
impl_from_int_with_scale_for_float!(u128, f64);
// impl_from_int_with_scale_for_decimal
/// Implements the [`FromIntWithScale`] trait for the [`Decimal`] type.
macro_rules! impl_from_int_with_scale_for_decimal {
(i128) => {
impl FromIntWithScale<i128> for Decimal {
// from_int_with_scale
fn from_int_with_scale(value: i128, scale: u8) -> Option<Self> {
// We should be able to rely upon Decimal::try_from_i128_with_scale() to
// perform the necessary checks, but it currently has issues with numbers
// larger than the supported 96-bit range, so we need to check manually.
if value > Decimal::MAX.to_i128().unwrap() || value < Decimal::MIN.to_i128().unwrap() {
None
} else {
Decimal::try_from_i128_with_scale(value, scale as u32).ok()
}
}
}
};
(u128) => {
impl FromIntWithScale<u128> for Decimal {
// from_int_with_scale
fn from_int_with_scale(value: u128, scale: u8) -> Option<Self> {
// We should be able to rely upon Decimal::try_from_i128_with_scale() to
// perform the necessary checks, but it currently has issues with numbers
// larger than the supported 96-bit range, so we need to check manually.
// Regardless of this, we would have to check if the value is larger than
// supported by an i128 in any case.
if value > Decimal::MAX.to_u128().unwrap() || (value as i128) < Decimal::MIN.to_i128().unwrap() {
None
} else {
Decimal::try_from_i128_with_scale(value as i128, scale as u32).ok()
}
}
}
};
($t:ty) => {
impl FromIntWithScale<$t> for Decimal {
// from_int_with_scale
fn from_int_with_scale(value: $t, scale: u8) -> Option<Self> {
// Everything less than 128 bits will fit safely into the Decimal's range.
Decimal::try_from_i128_with_scale(value as i128, scale as u32).ok()
}
}
};
}
impl_from_int_with_scale_for_decimal!(i8);
impl_from_int_with_scale_for_decimal!(i16);
impl_from_int_with_scale_for_decimal!(i32);
impl_from_int_with_scale_for_decimal!(i64);
impl_from_int_with_scale_for_decimal!(i128);
impl_from_int_with_scale_for_decimal!(u8);
impl_from_int_with_scale_for_decimal!(u16);
impl_from_int_with_scale_for_decimal!(u32);
impl_from_int_with_scale_for_decimal!(u64);
impl_from_int_with_scale_for_decimal!(u128);
//§ ToIntWithScale
/// Converts from a floating-point number to an integer with a specified scale.
///
/// This trait requires the presence of a [`to_int_with_scale()`](ToIntWithScale::to_int_with_scale())
/// method, which converts from a floating-point number to an integer with a
/// specified scale, i.e. a certain number of decimal places. For example, if
/// the scale is `2`, then the floating-point number `12.34` would be converted
/// to the integer `1234`. This is most useful when dealing with currencies.
///
/// The trait is implemented for the standard floating-point types, i.e. [`f32`]
/// and [`f64`], and for the [`Decimal`] type from the [`rust_decimal`](https://crates.io/crates/rust_decimal)
/// crate. For the corresponding integer types expressed as the generic `T`, it
/// is implemented for the standard integer types [`i8`], [`i16`], [`i32`],
/// [`i64`], [`i128`], [`u8`], [`u16`], [`u32`], [`u64`], and [`u128`].
///
/// Note that not all of these floating-point types can have their full range
/// represented by all of the integer types, and so naive conversion may result
/// in them being truncated or rounded. To avoid this happening invisibly, the
/// conversion will return [`None`] if the input number cannot be accurately
/// represented in the output type. Care should be taken to assess the
/// likelihood of this occurring, and to ensure that the correct types are used.
/// This cannot be guaranteed by the compiler, as the outcome depends partly on
/// the type and partly on the scale factor, and so an assessment has to be made
/// at runtime.
///
pub trait ToIntWithScale<T>: Sized {
// to_int_with_scale
/// Converts from a floating-point number to an integer with a specified
/// scale.
///
/// This function converts from a floating-point number to an integer with a
/// specified scale, i.e. a certain number of decimal places. For example,
/// if the scale is `2`, then the integer `1234` would be converted to the
/// floating-point number `12.34`. This is most useful when dealing with
/// currencies.
///
/// Note that not all floating-point types can have their full range
/// represented by all of the integer types, and so naive conversion may
/// result in them being truncated or rounded. To avoid this happening
/// invisibly, the conversion will return [`None`] if the input number
/// cannot be accurately represented in the output type. Care should be
/// taken to assess the likelihood of this occurring, and to ensure that the
/// correct types are used. This cannot be guaranteed by the compiler, as
/// the outcome depends partly on the type and partly on the scale factor,
/// and so an assessment has to be made at runtime.
///
/// # Parameters
///
/// * `scale` - The scale factor, i.e. the number of decimal places. Note
/// that this is essentially limited to a maximum of 19 DP of
/// movement without overflowing.
///
/// # See also
///
/// * [`FromIntWithScale::from_int_with_scale()`]
///
fn to_int_with_scale(&self, scale: u8) -> Option<T>;
}
// impl_to_int_with_scale_for_float
/// Implements the [`ToIntWithScale`] trait for floating-point types.
macro_rules! impl_to_int_with_scale_for_float {
($t:ty, $f:ty) => {
impl ToIntWithScale<$t> for $f {
// to_int_with_scale
fn to_int_with_scale(&self, scale: u8) -> Option<$t> {
let factor = 10_u64.checked_pow(scale as u32)?;
let scaled = (self * factor as $f).round();
if scaled.is_infinite() || scaled > <$t>::MAX as $f || scaled < <$t>::MIN as $f {
None
} else {
Some(scaled as $t)
}
}
}
};
}
impl_to_int_with_scale_for_float!(i8, f32);
impl_to_int_with_scale_for_float!(i16, f32);
impl_to_int_with_scale_for_float!(i32, f32);
impl_to_int_with_scale_for_float!(i64, f32);
impl_to_int_with_scale_for_float!(i128, f32);
impl_to_int_with_scale_for_float!(i8, f64);
impl_to_int_with_scale_for_float!(i16, f64);
impl_to_int_with_scale_for_float!(i32, f64);
impl_to_int_with_scale_for_float!(i64, f64);
impl_to_int_with_scale_for_float!(i128, f64);
impl_to_int_with_scale_for_float!(u8, f32);
impl_to_int_with_scale_for_float!(u16, f32);
impl_to_int_with_scale_for_float!(u32, f32);
impl_to_int_with_scale_for_float!(u64, f32);
impl_to_int_with_scale_for_float!(u128, f32);
impl_to_int_with_scale_for_float!(u8, f64);
impl_to_int_with_scale_for_float!(u16, f64);
impl_to_int_with_scale_for_float!(u32, f64);
impl_to_int_with_scale_for_float!(u64, f64);
impl_to_int_with_scale_for_float!(u128, f64);
// impl_to_int_with_scale_for_decimal
/// Implements the [`ToIntWithScale`] trait for the [`Decimal`] type.
macro_rules! impl_to_int_with_scale_for_decimal {
(i128) => {
impl ToIntWithScale<i128> for Decimal {
fn to_int_with_scale(&self, scale: u8) -> Option<i128> {
// The integer range of the Decimal type is less than that of an i128, but
// we cannot convert first and then scale, because the floating-point
// component will be truncated and lost. We therefore need to scale first,
// but this restricts the range of the final outcome to that of the Decimal
// type, which is 96 bits.
let factor = 10_u64.checked_pow(scale as u32)?;
(self.checked_mul(Decimal::from(factor))?.round()).to_i128()
}
}
};
(u128) => {
impl ToIntWithScale<u128> for Decimal {
fn to_int_with_scale(&self, scale: u8) -> Option<u128> {
// The integer range of the Decimal type is less than that of an i128, but
// we cannot convert first and then scale, because the floating-point
// component will be truncated and lost. We therefore need to scale first,
// but this restricts the range of the final outcome to that of the Decimal
// type, which is 96 bits.
let factor = 10_u64.checked_pow(scale as u32)?;
(self.checked_mul(Decimal::from(factor))?.round()).to_u128()
}
}
};
($t:ty) => {
impl ToIntWithScale<$t> for Decimal {
fn to_int_with_scale(&self, scale: u8) -> Option<$t> {
let factor = 10_u64.checked_pow(scale as u32)?;
let scaled = self.checked_mul(Decimal::from(factor))?.round();
// Everything less than 128 bits will fit safely into the Decimal's range.
if scaled > Decimal::from(<$t>::MAX) || scaled < Decimal::from(<$t>::MIN) {
None
} else {
scaled.to_i128().and_then(|value| value.try_into().ok())
}
}
}
};
}
impl_to_int_with_scale_for_decimal!(i8);
impl_to_int_with_scale_for_decimal!(i16);
impl_to_int_with_scale_for_decimal!(i32);
impl_to_int_with_scale_for_decimal!(i64);
impl_to_int_with_scale_for_decimal!(i128);
impl_to_int_with_scale_for_decimal!(u8);
impl_to_int_with_scale_for_decimal!(u16);
impl_to_int_with_scale_for_decimal!(u32);
impl_to_int_with_scale_for_decimal!(u64);
impl_to_int_with_scale_for_decimal!(u128);
//§ IteratorExt
/// This trait provides additional functionality to [`Iterator`].
pub trait IteratorExt: Iterator {
// limit
/// Limits the number of items returned by an iterator.
///
/// This is the same as [`Iterator::take()`], but accepts an [`Option`], so
/// that the limit does not have to be specified. It allows a match such as
/// `foo.iter().take(match limit { Some(n) => n, None => foo.len() })`
/// to be simplified to `foo.iter().limit(limit)`, and is especially useful
/// when `foo` is of unknown or infinite length.
///
/// # Parameters
///
/// * `limit` - The maximum number of items to return. If [`None`], no limit
/// will be applied.
///
fn limit(self, limit: Option<usize>) -> LimitIterator<Self> where Self: Sized {
LimitIterator { iter: self, limit, count: 0 }
}
}
impl<I: Iterator> IteratorExt for I {}
//§ PathExt
/// This trait provides additional functionality to [`Path`].
pub trait PathExt {
// append
/// Appends a string to a path.
///
/// Adds a string to the end of a path, and returns the result as a new
/// path. This is specifically different to both [`push()`](PathBuf::push())
/// and [`join()`](Path::join()), as it simply appends the string without
/// having any further effect on the path. By contrast, [`push()`](PathBuf::push())
/// and [`join()`](Path::join()) will append a new string as a new path
/// component, which will then be normalized, and will also replace the path
/// entirely if the string is an absolute path.
///
/// # Parameters
///
/// * `suffix` - The string to append to the path.
///
/// # See also
///
/// * [`std::path::Path::join()`]
/// * [`std::path::PathBuf::push()`]
///
fn append<P: AsRef<Path>>(&self, suffix: P) -> PathBuf;
// is_subjective
/// Checks if the path is specifically relative to the current directory.
///
/// Returns `true` if the path starts with a reference to the current
/// directory, i.e. `.` or `..` (as `..` is the parent of the current
/// directory and therefore related to it), making it specifically and
/// explicitly related to the current working directory. This can be
/// described as a subjective relative path, as opposed to an objective
/// relative path which is generically relative because it lacks a root
/// component.
///
/// A path that is subjective is also always relative. It is not possible to
/// have a subjective absolute path, as that would be a contradiction in
/// terms. However, objective paths may be either absolute or relative.
/// There is therefore no method `is_objective()`, as it does not currently
/// appear to have a useful purpose.
///
/// # See also
///
/// * [`std::path::Path::is_absolute()`]
/// * [`std::path::Path::is_relative()`]
///
fn is_subjective(&self) -> bool;
// normalize
/// Normalizes the path.
///
/// Computes the canonicalized, absolute path of a file or directory, but
/// without expanding symlinks or checking existence. A path that starts
/// with `.` or without an initial separator will be interpreted relative to
/// the current working directory (or the filesystem root if the current
/// working directory is not accessible). Empty paths and paths of `.` alone
/// will result in the current working directory being returned.
///
/// This function will normalize the path by removing any `.` and `..`
/// segments and returning the "real" path. It does this without touching
/// the filesystem, and so is an abstract but also simpler version of
/// [`canonicalize()`](Path::canonicalize()), which does a number of
/// filesystem checks. It does check for the current working directory, on
/// which to base relative paths, but does not perform any other checks.
///
/// Key differences are that [`canonicalize()`](Path::canonicalize()) will
/// return an error if the path does not exist, and will resolve symlinks.
/// This function will remove `.` segments, and will remove the parent
/// segment along with the current segment for `..` segments.
///
/// # See also
///
/// * [`restrict()`](PathExt::restrict())
/// * [`std::fs::canonicalize()`]
/// * [`std::path::Path::canonicalize()`]
///
fn normalize(&self) -> PathBuf;
// restrict
/// Restricts the path.
///
/// Computes the canonicalized, absolute path of a file or directory, but
/// without allowing parent directory traversal to go beyond the base path.
/// If no base path is specified, the current working directory will be
/// used. If the path starts with `.` then this will be interpreted relative
/// to the base path.
///
/// This function calls [`normalize()`](PathExt::normalize()), and so the
/// fundamental behaviour of the resolution performed is the same as that
/// function. The difference is that this function will not allow the path
/// to go beyond the base path, and so any `..` segments will simply be
/// removed from the path if they would otherwise go beyond the anchor
/// point.
///
/// This does have the effect that if a path does try to traverse too far,
/// it may lose additional components. For example, a path of `../foo` will
/// end up losing the `foo` component, as the logic will be that `foo` is
/// intended to be a sibling to the base path and not a child of it, and is
/// therefore invalid. So if the base directory is `/home/user` then a path
/// of `../foo` will be resolved to `/home/user` and not `/home/user/foo`.
/// The effect of this continues further, in that all children of `foo` will
/// also be deemed invalid. So `../foo/bar` will also be resolved to
/// `/home/user`, and not `/home/user/foo/bar` or `/home/user/bar`. Care
/// should therefore be taken when using this function to ensure that the
/// path returned is valid for the intended use.
///
/// In the case of the path being absolute, it will be resolved and then
/// compared against the base path. If the path is a child of the base path
/// then it will be returned - otherwise the base path will be returned, as
/// the path is invalid. For example, if the base directory is `/home/user`
/// then a path of `/home/user/foo` will be returned, but a path of
/// `/home/otheruser` will return `/home/user`.
///
/// Note that this function does not touch the filesystem, does not expand
/// symlinks, and does not check that the path exists - including the
/// base path. Hence when this documentation talks about base directory,
/// it does so interchangeably with base path, as the valid intent would be
/// for the base path to be a directory, but this is not actually checked.
///
/// # Parameters
///
/// * `base` - The base path to use. If this is [`None`] then the current
/// working directory will be used.
///
/// # See also
///
/// * [`normalize()`](PathExt::normalize())
///
fn restrict<P: AsRef<Path>>(&self, base: P) -> PathBuf;
// strip_parentdirs
/// Removes references to parent directories, i.e. `..`.
///
/// Removes any [`ParentDir`](std::path::Component::ParentDir) components
/// from either the beginning of the path or anywhere in the path.
///
/// This function does not touch the filesystem, or check if the path is
/// valid or exists. It will also not attempt to resolve the parent
/// directory references that it removes, so they will be taken out with no
/// effect on the rest of the path.
///
/// # Parameters
///
/// * `remove_all` - If `true` then all parent directory references will be
/// removed, otherwise only those at the beginning of the
/// path will be removed.
///
/// # See also
///
/// * [`std::path::Component`]
/// * [`std::path::Path::components()`]
///
fn strip_parentdirs(&self, remove_all: bool) -> PathBuf;
// strip_root
/// Makes the path relative by removing the root and/or prefix components.
///
/// Removes any components from the path that are considered to be the root
/// or prefix of the path. The prefix is this context is not the same as in
/// [`strip_prefix()`](Path::strip_prefix()), which removes a specific
/// string prefix from the path. Rather, the prefix here is a
/// [`PrefixComponent`](std::path::PrefixComponent). A path is considered to
/// be absolute if it has a root on Unix, or if it has both root and prefix
/// on Windows. Therefore, in order to convert the path to be relative, both
/// the root and prefix must be removed.
///
/// This function does not touch the filesystem, or check if the path is
/// valid or exists. It will also not attempt to resolve special directory
/// references such as `.` or `..`.
///
/// # See also
///
/// * [`std::path::Path::components()`]
/// * [`std::path::Path::has_root()`]
/// * [`std::path::Path::is_absolute()`]
/// * [`std::path::Path::strip_prefix()`]
/// * [`std::path::Prefix`]
/// * [`std::path::PrefixComponent`]
///
fn strip_root(&self) -> PathBuf;
}
impl PathExt for Path {
// append
fn append<P: AsRef<Self>>(&self, suffix: P) -> PathBuf {
PathBuf::from([
self.as_os_str().to_os_string(),
OsString::from(suffix.as_ref()),
].into_iter().collect::<OsString>())
}
// is_subjective
fn is_subjective(&self) -> bool {
self.is_relative() && {
let mut components = self.components();
matches!(components.next(), Some(PathComponent::CurDir | PathComponent::ParentDir))
}
}
// normalize
fn normalize(&self) -> PathBuf {
let cwd = env::current_dir().unwrap_or_else(|_| PathBuf::from("/"));
if self.as_os_str().is_empty() {
return cwd;
}
let mut segments: Vec<OsString> = vec![];
for (i, component) in self.components().enumerate() {
match component {
PathComponent::Prefix(_) |
PathComponent::RootDir => {
if i == 0 {
segments.push(component.as_os_str().to_os_string());
}
},
PathComponent::CurDir |
PathComponent::ParentDir => {
if i == 0 {
segments.append(
cwd.components()
.map(|c| c.as_os_str().to_os_string())
.collect::<Vec<OsString>>()
.as_mut()
);
}
if component == PathComponent::ParentDir && segments.len() > 1 {
drop(segments.pop());
}
},
PathComponent::Normal(_) => {
if i == 0 {
segments.push(cwd.as_os_str().to_os_string());
}
segments.push(component.as_os_str().to_os_string());
},
}
}
segments.iter().collect()
}
// restrict
fn restrict<P: AsRef<Self>>(&self, base: P) -> PathBuf {
let basepath = base.as_ref().normalize();
if self.as_os_str().is_empty() {
return basepath;
}
let mut path = if self.is_absolute() {
self.to_path_buf()
} else {
basepath.join(self)
}.normalize();
if !path.starts_with(&basepath) {
path = basepath;
}
path
}
// strip_parentdirs
fn strip_parentdirs(&self, remove_all: bool) -> PathBuf {
if self.as_os_str().is_empty() || (!remove_all && self.is_absolute()) {
return self.to_owned();
}
let mut at_start = true;
let mut segments: Vec<OsString> = vec![];
for component in self.components() {
match component {
PathComponent::Prefix(_) |
PathComponent::RootDir |
PathComponent::CurDir |
PathComponent::Normal(_) => {
segments.push(component.as_os_str().to_os_string());
at_start = false;
},
PathComponent::ParentDir => {
if !remove_all && !at_start {
segments.push(component.as_os_str().to_os_string());
}
},
}
}
segments.iter().collect()
}
// strip_root
fn strip_root(&self) -> PathBuf {
if self.as_os_str().is_empty() || self.is_relative() {
return self.to_owned();
}
let mut segments: Vec<OsString> = vec![];
for component in self.components() {
match component {
PathComponent::Prefix(_) |
PathComponent::RootDir => {},
PathComponent::CurDir |
PathComponent::ParentDir |
PathComponent::Normal(_) => {
segments.push(component.as_os_str().to_os_string());
},
}
}
segments.iter().collect()
}
}