diff --git a/CHANGELOG.md b/CHANGELOG.md index 2281250..d1ca684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ +## [0.5.0](https://github.com/Blobfolio/cdtoc/releases/tag/v0.5.0) - 2024-09-05 + +### Changed + +* Miscellaneous code cleanup and lints +* Improved documentation +* Bump MSRV to `1.81` +* Bump `brunch` to `0.6` + + + ## [0.4.0](https://github.com/Blobfolio/cdtoc/releases/tag/v0.4.0) - 2024-07-29 ### Changed diff --git a/CREDITS.md b/CREDITS.md index 7f2f11b..eecac08 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,18 +1,18 @@ # Project Dependencies Package: cdtoc - Version: 0.4.0 - Generated: 2024-07-29 19:36:09 UTC + Version: 0.5.0 + Generated: 2024-09-05 19:43:40 UTC | Package | Version | Author(s) | License | | ---- | ---- | ---- | ---- | | [block-buffer](https://github.com/RustCrypto/utils) | 0.10.4 | RustCrypto Developers | Apache-2.0 or MIT | | [cfg-if](https://github.com/alexcrichton/cfg-if) | 1.0.0 | [Alex Crichton](mailto:alex@alexcrichton.com) | Apache-2.0 or MIT | | [crypto-common](https://github.com/RustCrypto/traits) | 0.1.6 | RustCrypto Developers | Apache-2.0 or MIT | -| [dactyl](https://github.com/Blobfolio/dactyl) | 0.7.2 | [Blobfolio, LLC.](mailto:hello@blobfolio.com) | WTFPL | +| [dactyl](https://github.com/Blobfolio/dactyl) | 0.7.3 | [Blobfolio, LLC.](mailto:hello@blobfolio.com) | WTFPL | | [digest](https://github.com/RustCrypto/traits) | 0.10.7 | RustCrypto Developers | Apache-2.0 or MIT | | [faster-hex](https://github.com/NervosFoundation/faster-hex) | 0.9.0 | [zhangsoledad](mailto:787953403@qq.com) | MIT | | [generic-array](https://github.com/fizyk20/generic-array.git) | 0.14.7 | [Bartłomiej Kamiński](mailto:fizyk20@gmail.com) and [Aaron Trent](mailto:novacrazy@gmail.com) | MIT | | [itoa](https://github.com/dtolnay/itoa) | 1.0.11 | [David Tolnay](mailto:dtolnay@gmail.com) | Apache-2.0 or MIT | | [sha1](https://github.com/RustCrypto/hashes) | 0.10.6 | RustCrypto Developers | Apache-2.0 or MIT | -| [trimothy](https://github.com/Blobfolio/trimothy) | 0.3.0 | [Blobfolio, LLC.](mailto:hello@blobfolio.com) | WTFPL | +| [trimothy](https://github.com/Blobfolio/trimothy) | 0.3.1 | [Blobfolio, LLC.](mailto:hello@blobfolio.com) | WTFPL | | [typenum](https://github.com/paholg/typenum) | 1.17.0 | [Paho Lurie-Gregg](mailto:paho@paholg.com) and [Andre Bogus](mailto:bogusandre@gmail.com) | Apache-2.0 or MIT | diff --git a/Cargo.toml b/Cargo.toml index 3488ca4..7a15836 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "cdtoc" -version = "0.4.0" +version = "0.5.0" authors = ["Blobfolio, LLC. "] edition = "2021" -rust-version = "1.80" +rust-version = "1.81" description = "Parser and tools for CDTOC metadata tags." license = "WTFPL" repository = "https://github.com/Blobfolio/cdtoc" @@ -30,7 +30,7 @@ features = [ "accuraterip", "cddb", "ctdb", "musicbrainz", "serde" ] default-target = "x86_64-unknown-linux-gnu" [dev-dependencies] -brunch = "0.5.*" +brunch = "0.6.*" serde_json = "1.0.*" [dependencies] diff --git a/README.md b/README.md index 0b9408e..d9fd0c0 100644 --- a/README.md +++ b/README.md @@ -62,14 +62,14 @@ Add `cdtoc` to your `dependencies` in `Cargo.toml`, like: ```toml [dependencies] -cdtoc = "0.4.*" +cdtoc = "0.5.*" ``` The disc ID helpers require additional dependencies, so if you aren't using them, be sure to disable the default features (adding back any you _do_ want) to skip the overhead. ```toml [dependencies.cdtoc] -version = "0.4.*" +version = "0.5.*" default-features = false ``` diff --git a/src/accuraterip.rs b/src/accuraterip.rs index 121ad62..8dd55f4 100644 --- a/src/accuraterip.rs +++ b/src/accuraterip.rs @@ -84,7 +84,7 @@ impl fmt::Display for AccurateRip { } impl From<&Toc> for AccurateRip { - #[allow(clippy::cast_possible_truncation)] + #[expect(clippy::cast_possible_truncation, reason = "False positive.")] fn from(src: &Toc) -> Self { let mut b: u32 = 0; let mut c: u32 = 0; @@ -332,7 +332,15 @@ impl AccurateRip { /// empty. pub fn parse_drive_offsets(raw: &[u8]) -> Result, TocError> { + /// # Block Size. + /// + /// The size of each raw entry, in bytes. const BLOCK_SIZE: usize = 69; + + /// # Trim Callback. + /// + /// This is used to trim both ASCII whitespace and control characters, + /// as the raw data isn't afraid to null-pad its entries. const fn trim_vm(c: char) -> bool { c.is_ascii_whitespace() || c.is_ascii_control() } // There should be thousands of blocks, but we _need_ at least one! @@ -342,7 +350,7 @@ impl AccurateRip { // little-endian offset; the next 32 hold the vendor/model; the rest // we can ignore! let mut out = BTreeMap::default(); - for chunk in raw.chunks_exact(69) { + for chunk in raw.chunks_exact(BLOCK_SIZE) { // The offset is easy! let offset = i16::from_le_bytes([chunk[0], chunk[1]]); @@ -387,7 +395,7 @@ impl AccurateRip { else { Ok(out) } } - #[allow(unsafe_code)] + #[expect(unsafe_code, reason = "For performance.")] #[must_use] /// # Pretty Print. /// @@ -421,8 +429,9 @@ impl AccurateRip { faster_hex::hex_encode_fallback(&[self.0[8], self.0[7], self.0[6], self.0[5]], &mut out[13..21]); faster_hex::hex_encode_fallback(&[self.0[12], self.0[11], self.0[10], self.0[9]], &mut out[22..]); - // Safety: all bytes are ASCII. debug_assert!(out.is_ascii(), "Bug: AccurateRip checksum is not ASCII?!"); + + // Safety: all bytes are ASCII. unsafe { String::from_utf8_unchecked(out) } } } diff --git a/src/cddb.rs b/src/cddb.rs index e66f350..7acd7b7 100644 --- a/src/cddb.rs +++ b/src/cddb.rs @@ -77,7 +77,7 @@ impl From for u32 { } impl From<&Toc> for Cddb { - #[allow(clippy::cast_possible_truncation)] + #[expect(clippy::cast_possible_truncation, reason = "False positive.")] fn from(src: &Toc) -> Self { let mut len = src.audio_len(); let mut a: u32 = 0; diff --git a/src/ctdb.rs b/src/ctdb.rs index 2707cb3..dc6ccaf 100644 --- a/src/ctdb.rs +++ b/src/ctdb.rs @@ -13,13 +13,17 @@ use std::collections::BTreeMap; +/// # Stereo Sample Chunk Size. +/// +/// Each CDDA sample has a 16-bit left and 16-bit right value; combined they're +/// four bytes. const CHUNK_SIZE: usize = 4; impl Toc { #[cfg_attr(docsrs, doc(cfg(feature = "ctdb")))] - #[allow(clippy::missing_panics_doc)] + #[expect(clippy::missing_panics_doc, reason = "Panic is unreachable.")] #[must_use] /// # CUETools Database ID. /// @@ -114,7 +118,7 @@ impl Toc { /// ); /// ``` pub fn ctdb_checksum_url(&self) -> String { - let mut url = "http://db.cuetools.net/lookup2.php?version=3&ctdb=1&fuzzy=1&toc=".to_string(); + let mut url = "http://db.cuetools.net/lookup2.php?version=3&ctdb=1&fuzzy=1&toc=".to_owned(); let mut buf = itoa::Buffer::new(); // Leading data? diff --git a/src/lib.rs b/src/lib.rs index 1a212f2..ed34cf0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,46 +63,68 @@ Add `cdtoc` to your `dependencies` in `Cargo.toml`, like: ```ignore,toml [dependencies] -cdtoc = "0.4.*" +cdtoc = "0.5.*" ``` The disc ID helpers require additional dependencies, so if you aren't using them, be sure to disable the default features (adding back any you _do_ want) to skip the overhead. ```ignore,toml [dependencies.cdtoc] -version = "0.4.*" +version = "0.5.*" default-features = false ``` */ -#![deny(unsafe_code)] +#![deny( + clippy::allow_attributes_without_reason, + clippy::correctness, + unreachable_pub, + unsafe_code, +)] #![warn( - clippy::filetype_is_file, - clippy::integer_division, - clippy::needless_borrow, + clippy::complexity, clippy::nursery, clippy::pedantic, clippy::perf, - clippy::suboptimal_flops, + clippy::style, + + clippy::allow_attributes, + clippy::clone_on_ref_ptr, + clippy::create_dir, + clippy::filetype_is_file, + clippy::format_push_string, + clippy::get_unwrap, + clippy::impl_trait_in_params, + clippy::lossy_float_literal, + clippy::missing_assert_message, + clippy::missing_docs_in_private_items, + clippy::needless_raw_strings, + clippy::panic_in_result_fn, + clippy::pub_without_shorthand, + clippy::rest_pat_in_fully_bound_structs, + clippy::semicolon_inside_block, + clippy::str_to_string, + clippy::string_to_string, + clippy::todo, + clippy::undocumented_unsafe_blocks, clippy::unneeded_field_pattern, + clippy::unseparated_literal_suffix, + clippy::unwrap_in_result, + macro_use_extern_crate, missing_copy_implementations, - missing_debug_implementations, missing_docs, non_ascii_idents, trivial_casts, trivial_numeric_casts, - unreachable_pub, unused_crate_dependencies, unused_extern_crates, unused_import_braces, )] -#![allow( - clippy::doc_markdown, - clippy::module_name_repetitions, -)] +#![expect(clippy::doc_markdown, reason = "This gets annoying with names like MusicBrainz.")] +#![expect(clippy::module_name_repetitions, reason = "Repetition is preferred.")] #![cfg_attr(docsrs, feature(doc_cfg))] @@ -192,14 +214,21 @@ static ZEROES: [u8; 792] = [b'0'; 792]; /// assert_eq!(toc1.to_string(), "4+96+2D2B+6256+B327+D84A"); /// ``` pub struct Toc { + /// # Disc Type. kind: TocKind, + + /// # Start Sectors for Each Audio Track. audio: Vec, + + /// # Start Sector for Data Track (if any). data: u32, + + /// # Leadout Sector. leadout: u32, } impl fmt::Display for Toc { - #[allow(clippy::cast_possible_truncation)] + #[expect(clippy::cast_possible_truncation, reason = "False positive.")] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use trimothy::TrimSliceMatches; @@ -212,6 +241,7 @@ impl fmt::Display for Toc { if 16 <= audio_len { out.push(buf[0]); } out.push(buf[1]); + /// # Helper: Add Track to Buffer. macro_rules! push { ($v:expr) => ( faster_hex::hex_encode_fallback($v.to_be_bytes().as_slice(), &mut buf); @@ -666,7 +696,7 @@ impl Toc { /// ``` pub fn audio_sectors(&self) -> &[u32] { &self.audio } - #[allow(clippy::cast_possible_truncation)] + #[expect(clippy::cast_possible_truncation, reason = "False positive.")] #[must_use] /// # Audio Track. /// @@ -1079,27 +1109,27 @@ mod tests { 41202, 63497, 86687, - 109747, - 134332, - 151060, - 175895, - 193770, - 220125, + 109_747, + 134_332, + 151_060, + 175_895, + 193_770, + 220_125, ]; assert_eq!(toc.audio_len(), 11); assert_eq!(toc.audio_sectors(), §ors); assert_eq!(toc.data_sector(), None); - assert_eq!(toc.has_data(), false); + assert!(!toc.has_data()); assert_eq!(toc.kind(), TocKind::Audio); assert_eq!(toc.audio_leadin(), 150); - assert_eq!(toc.audio_leadout(), 244077); + assert_eq!(toc.audio_leadout(), 244_077); assert_eq!(toc.leadin(), 150); - assert_eq!(toc.leadout(), 244077); + assert_eq!(toc.leadout(), 244_077); assert_eq!(toc.to_string(), CDTOC_AUDIO); // This should match when built with the equivalent parts. assert_eq!( - Toc::from_parts(sectors, None, 244077), + Toc::from_parts(sectors, None, 244_077), Ok(toc), ); @@ -1133,25 +1163,25 @@ mod tests { 50767, 68115, 85410, - 106120, - 121770, - 136100, - 161870, + 106_120, + 121_770, + 136_100, + 161_870, ]; assert_eq!(toc.audio_len(), 10); assert_eq!(toc.audio_sectors(), §ors); - assert_eq!(toc.data_sector(), Some(186287)); - assert_eq!(toc.has_data(), true); + assert_eq!(toc.data_sector(), Some(186_287)); + assert!(toc.has_data()); assert_eq!(toc.kind(), TocKind::CDExtra); assert_eq!(toc.audio_leadin(), 150); - assert_eq!(toc.audio_leadout(), 174887); + assert_eq!(toc.audio_leadout(), 174_887); assert_eq!(toc.leadin(), 150); - assert_eq!(toc.leadout(), 225041); + assert_eq!(toc.leadout(), 225_041); assert_eq!(toc.to_string(), CDTOC_EXTRA); // This should match when built with the equivalent parts. assert_eq!( - Toc::from_parts(sectors, Some(186287), 225041), + Toc::from_parts(sectors, Some(186_287), 225_041), Ok(toc), ); } @@ -1167,26 +1197,26 @@ mod tests { 50767, 68115, 85410, - 106120, - 121770, - 136100, - 161870, - 186287, + 106_120, + 121_770, + 136_100, + 161_870, + 186_287, ]; assert_eq!(toc.audio_len(), 10); assert_eq!(toc.audio_sectors(), §ors); assert_eq!(toc.data_sector(), Some(150)); - assert_eq!(toc.has_data(), true); + assert!(toc.has_data()); assert_eq!(toc.kind(), TocKind::DataFirst); assert_eq!(toc.audio_leadin(), 14167); - assert_eq!(toc.audio_leadout(), 225041); + assert_eq!(toc.audio_leadout(), 225_041); assert_eq!(toc.leadin(), 150); - assert_eq!(toc.leadout(), 225041); + assert_eq!(toc.leadout(), 225_041); assert_eq!(toc.to_string(), CDTOC_DATA_AUDIO); // This should match when built with the equivalent parts. assert_eq!( - Toc::from_parts(sectors, Some(150), 225041), + Toc::from_parts(sectors, Some(150), 225_041), Ok(toc), ); } @@ -1205,6 +1235,7 @@ mod tests { } #[test] + #[expect(clippy::cognitive_complexity, reason = "It is what it is.")] /// # Test Kind Conversions. fn t_rekind() { // Start with audio. @@ -1222,20 +1253,20 @@ mod tests { 41202, 63497, 86687, - 109747, - 134332, - 151060, - 175895, - 193770, + 109_747, + 134_332, + 151_060, + 175_895, + 193_770, ] ); - assert_eq!(toc.data_sector(), Some(220125)); - assert_eq!(toc.has_data(), true); + assert_eq!(toc.data_sector(), Some(220_125)); + assert!(toc.has_data()); assert_eq!(toc.kind(), TocKind::CDExtra); assert_eq!(toc.audio_leadin(), 150); - assert_eq!(toc.audio_leadout(), 208725); + assert_eq!(toc.audio_leadout(), 208_725); assert_eq!(toc.leadin(), 150); - assert_eq!(toc.leadout(), 244077); + assert_eq!(toc.leadout(), 244_077); // Back again. assert!(toc.set_kind(TocKind::Audio).is_ok()); @@ -1251,21 +1282,21 @@ mod tests { 41202, 63497, 86687, - 109747, - 134332, - 151060, - 175895, - 193770, - 220125, + 109_747, + 134_332, + 151_060, + 175_895, + 193_770, + 220_125, ] ); assert_eq!(toc.data_sector(), Some(150)); - assert_eq!(toc.has_data(), true); + assert!(toc.has_data()); assert_eq!(toc.kind(), TocKind::DataFirst); assert_eq!(toc.audio_leadin(), 24047); - assert_eq!(toc.audio_leadout(), 244077); + assert_eq!(toc.audio_leadout(), 244_077); assert_eq!(toc.leadin(), 150); - assert_eq!(toc.leadout(), 244077); + assert_eq!(toc.leadout(), 244_077); // Back again. assert!(toc.set_kind(TocKind::Audio).is_ok()); diff --git a/src/musicbrainz.rs b/src/musicbrainz.rs index 72fcedd..4f445f1 100644 --- a/src/musicbrainz.rs +++ b/src/musicbrainz.rs @@ -9,13 +9,18 @@ use crate::{ +/// # Stereo Sample Chunk Size. +/// +/// Each CDDA sample has a 16-bit left and 16-bit right value; combined they're +/// four bytes. const CHUNK_SIZE: usize = 4; impl Toc { - #[allow(clippy::cast_possible_truncation, clippy::missing_panics_doc)] #[cfg_attr(docsrs, doc(cfg(feature = "musicbrainz")))] + #[expect(clippy::cast_possible_truncation, reason = "False positive.")] + #[expect(clippy::missing_panics_doc, reason = "False positive.")] #[must_use] /// # MusicBrainz ID. /// diff --git a/src/serde.rs b/src/serde.rs index 50f042e..47c610e 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -24,6 +24,7 @@ use std::fmt; +/// # Helper: Deserialize as String. macro_rules! deserialize_str_with { ($ty:ty, $fn:ident) => ( #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] @@ -58,6 +59,7 @@ macro_rules! deserialize_str_with { ); } +/// # Helper: Serialize as String. macro_rules! serialize_with { ($ty:ty, $fn:ident) => ( #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] @@ -100,7 +102,10 @@ impl Serialize for Duration { impl<'de> Deserialize<'de> for Track { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de> { + /// # Fields of Interest. const FIELDS: &[&str] = &["num", "pos", "from", "to"]; + + /// # Visitor Instance. struct TrackVisitor; impl<'de> de::Visitor<'de> for TrackVisitor { @@ -130,6 +135,7 @@ impl<'de> Deserialize<'de> for Track { let mut from = None; let mut to = None; + /// # Helper: Accept or Reject Value. macro_rules! set { ($var:ident, $name:literal) => ( if $var.is_none() { $var.replace(map.next_value()?); } @@ -179,6 +185,7 @@ impl Serialize for Track { impl<'de> Deserialize<'de> for TrackPosition { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de> { + /// # Visitor Instance. struct Visitor; impl<'de> de::Visitor<'de> for Visitor { diff --git a/src/shab64.rs b/src/shab64.rs index 65c3252..87ccd2e 100644 --- a/src/shab64.rs +++ b/src/shab64.rs @@ -91,7 +91,7 @@ impl ShaB64 { else { Err(TocError::ShaB64Decode) } } - #[allow(unsafe_code)] + #[expect(unsafe_code, reason = "For performance.")] #[must_use] /// # Pretty Print. /// @@ -116,11 +116,12 @@ impl ShaB64 { // And add one byte for padding. out.push(b'-'); - // Safety: our alphabet is ASCII. debug_assert!( out.len() == 28 && out.is_ascii(), "Bug: Sha/base64 ID is malformed." ); + + // Safety: our alphabet is ASCII. unsafe { String::from_utf8_unchecked(out) } } } diff --git a/src/time.rs b/src/time.rs index 86f6471..5654d5b 100644 --- a/src/time.rs +++ b/src/time.rs @@ -29,7 +29,10 @@ use std::{ +/// # Samples Per Sector. const SAMPLES_PER_SECTOR: u64 = 588; + +/// # Sectors Per Second. const SECTORS_PER_SECOND: u64 = 75; @@ -109,7 +112,7 @@ where u64: From { impl Eq for Duration {} impl fmt::Display for Duration { - #[allow(clippy::many_single_char_names)] + #[expect(clippy::many_single_char_names, reason = "Consistency is preferred.")] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (d, h, m, s, frames) = self.dhmsf(); if d == 0 { @@ -209,7 +212,11 @@ impl Duration { else { Err(TocError::CDDASampleCount) } } - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::integer_division)] + #[expect( + clippy::cast_possible_truncation, + clippy::cast_sign_loss, + reason = "False positive.", + )] #[must_use] /// # From Samples (Rescaled). /// @@ -236,7 +243,7 @@ impl Duration { if sample_rate == 0 || total_samples == 0 { Self::default() } else { let sample_rate = u64::from(sample_rate); - let (s, rem) = (total_samples / sample_rate, total_samples % sample_rate); + let (s, rem) = (total_samples.wrapping_div(sample_rate), total_samples % sample_rate); if rem == 0 { Self(s * SECTORS_PER_SECOND) } else { let f = (rem * 75).div_float(sample_rate) @@ -248,7 +255,8 @@ impl Duration { } impl Duration { - #[allow(clippy::many_single_char_names, clippy::cast_possible_truncation)] + #[expect(clippy::cast_possible_truncation, reason = "False positive.")] + #[expect(clippy::many_single_char_names, reason = "Consistency is preferred.")] #[must_use] /// # Days, Hours, Minutes, Seconds, Frames. /// @@ -343,7 +351,7 @@ impl Duration { /// ``` pub const fn sectors(self) -> u64 { self.0 } - #[allow(clippy::cast_precision_loss)] + #[expect(clippy::cast_precision_loss, reason = "False positive.")] #[must_use] /// # To `f64` (Lossy). /// @@ -409,7 +417,7 @@ impl Duration { ) } - #[allow(clippy::many_single_char_names)] + #[expect(clippy::many_single_char_names, reason = "Consistency is preferred.")] #[must_use] /// # To String Pretty. /// diff --git a/src/track.rs b/src/track.rs index 4c3210c..ed5de93 100644 --- a/src/track.rs +++ b/src/track.rs @@ -15,9 +15,16 @@ use std::ops::Range; /// /// It is the return value of [`Toc::audio_track`](crate::Toc::audio_track). pub struct Track { + /// # Track Number. pub(super) num: u8, + + /// # Track Position. pub(super) pos: TrackPosition, + + /// # Sector Range: Start. pub(super) from: u32, + + /// # Sector Range: End (Exclusive). pub(super) to: u32, } @@ -245,15 +252,23 @@ impl Track { /// /// It is the return value of [`Toc::audio_tracks`](crate::Toc::audio_tracks). pub struct Tracks<'a> { + /// # All Tracks. tracks: &'a [u32], + + /// # Leadout. leadout: u32, + + /// # Current Index. + /// + /// Each call to `Tracks.next()` will attempt to yield `tracks[pos]`. The + /// value is incremented afterward to prepare for the next `next` call. pos: usize, } impl Iterator for Tracks<'_> { type Item = Track; - #[allow(clippy::cast_possible_truncation)] + #[expect(clippy::cast_possible_truncation, reason = "False positive.")] fn next(&mut self) -> Option { let len = self.tracks.len(); if len <= self.pos { return None; } @@ -401,7 +416,7 @@ impl TrackPosition { -#[allow(clippy::cast_possible_truncation)] +#[expect(clippy::cast_possible_truncation, reason = "False positive.")] /// # LBA to MSF. /// /// Convert a logical block address (sectors) to minutes, seconds, and frames.