countio-0.3.0/.cargo_vcs_info.json0000644000000001360000000000100125140ustar { "git": { "sha1": "df670e5b085342c81a4e06acaa5db1cd2fdeef4e" }, "path_in_vcs": "" }countio-0.3.0/.github/dependabot.yml000064400000000000000000000020731046102023000154760ustar 00000000000000version: 2 updates: # Enable version updates for cargo - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" timezone: "Europe/Berlin" day: "monday" time: "04:00" open-pull-requests-limit: 5 labels: - "chore" commit-message: prefix: "chore(deps)" prefix-development: "chore(deps-dev)" rebase-strategy: "auto" versioning-strategy: "auto" # Group patch and minor updates together to reduce PR noise groups: rust-dependencies: patterns: - "*" update-types: - "minor" - "patch" # Version updates for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" timezone: "Europe/Berlin" day: "monday" time: "04:00" open-pull-requests-limit: 5 labels: - "chore" commit-message: prefix: "chore(actions)" # Group all GitHub Actions updates together to reduce PR noise groups: github-actions: patterns: - "*" countio-0.3.0/.github/workflows/build.yml000064400000000000000000000045761046102023000165370ustar 00000000000000name: Build & Test on: push: branches: - "main" - "release" paths: - "src/**" - "Cargo.toml" - "Cargo.lock" - "rustfmt.toml" - ".github/workflows/build.yml" pull_request: branches: - "main" - "release" paths: - "src/**" - "Cargo.toml" - "Cargo.lock" - "rustfmt.toml" - ".github/workflows/build.yml" schedule: - cron: "0 2 * * *" workflow_dispatch: env: CARGO_TERM_COLOR: always jobs: test: name: Test runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Cache dependencies uses: Swatinem/rust-cache@v2 - name: Run tests (all features) run: cargo test --all-features fmt: name: Format runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: Cache dependencies uses: Swatinem/rust-cache@v2 - name: Check formatting run: cargo fmt -- --check clippy: name: Clippy runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: components: clippy - name: Cache dependencies uses: Swatinem/rust-cache@v2 - name: Run clippy run: cargo clippy --all-targets --all-features -- -D warnings docs: name: Documentation runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Cache dependencies uses: Swatinem/rust-cache@v2 - name: Check documentation run: cargo doc --no-deps --all-features env: RUSTDOCFLAGS: -D warnings msrv: name: MSRV runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: "1.85" - name: Cache dependencies uses: Swatinem/rust-cache@v2 - name: Run tests (all features) run: cargo test --all-features countio-0.3.0/.github/workflows/publish.yml000064400000000000000000000020741046102023000170750ustar 00000000000000name: Release on: push: tags: - "v*" permissions: contents: write jobs: create-release: name: Create Release runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 - name: Get the version id: get_version run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - name: Create Release uses: softprops/action-gh-release@v2 with: name: Release ${{ steps.get_version.outputs.VERSION }} draft: false prerelease: false generate_release_notes: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} publish-crate: name: Publish to crates.io runs-on: ubuntu-latest needs: create-release steps: - name: Checkout code uses: actions/checkout@v6 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Cache dependencies uses: Swatinem/rust-cache@v2 - name: Publish to crates.io run: cargo publish --token ${{ secrets.CRATES_TOKEN }} countio-0.3.0/.github/workflows/security.yml000064400000000000000000000022671046102023000173020ustar 00000000000000name: Security on: push: branches: - "main" - "release" paths: - "src/**" - "Cargo.toml" - "Cargo.lock" - "deny.toml" - "rustfmt.toml" - ".github/workflows/security.yml" pull_request: branches: - "main" - "release" paths: - "src/**" - "Cargo.toml" - "Cargo.lock" - "deny.toml" - "rustfmt.toml" - ".github/workflows/security.yml" schedule: - cron: "0 2 * * *" workflow_dispatch: env: CARGO_TERM_COLOR: always jobs: security-audit: name: Security Audit runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Cache dependencies uses: Swatinem/rust-cache@v2 - name: Install cargo-audit uses: taiki-e/install-action@v2 with: tool: cargo-audit - name: Run cargo audit run: cargo audit --deny warnings - name: Install cargo-deny uses: taiki-e/install-action@v2 with: tool: cargo-deny - name: Run cargo deny run: cargo deny check all countio-0.3.0/.gitignore000064400000000000000000000005031046102023000132720ustar 00000000000000# OS Thumbs.db .DS_Store # IDE and Editors .vs/ .vscode/ .idea/ .zed/ # Rust debug/ target/ Cargo.lock **/*.rs.bk *.pdb # Build output dist/ build/ output/ # Environment files .env* !.env.example # Logs logs/ *.log *.log* # Backup and temporary files *.bak *.backup *.tmp tmp/ temp/ # Other .ignore*/ LLM.md .claude countio-0.3.0/CHANGELOG.md000064400000000000000000000036421046102023000131220ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.3.0] - 2025-01-21 ### Added - `Progress` wrapper for tracking progress with percentage calculations - Separate `expected_reader_bytes` and `expected_writer_bytes` tracking in `Progress` - `reader_percentage()` and `writer_percentage()` methods for `Progress` - `with_expected_reader_bytes()`, `with_expected_writer_bytes()`, and `with_expected_bytes()` constructors - `Clone` implementation for `Counter` and `Progress` when `D: Clone` - `Default` implementation for `Counter` and `Progress` when `D: Default` - `Debug` implementation for `Progress` when `D: Debug` - `reset()` method for both `Counter` and `Progress` to reset byte counters - `std` feature flag (enabled by default) for `std::io` trait implementations ### Changed - **Breaking:** Renamed `bytes_read()` to `reader_bytes()` for consistency - **Breaking:** Renamed `bytes_written()` to `writer_bytes()` for consistency - **Breaking:** Renamed `bytes_processed()` to `total_bytes()` - **Breaking:** Changed `with_bytes()` parameter order to `(inner, reader_bytes, writer_bytes)` - Reworked CI pipeline - Bumped MSRV to 1.85 and updated to Rust 2024 edition - Updated README to accurately document available features ### Removed - **Breaking:** Removed `counter()` and `counter_mut()` from `Progress` (use delegated methods instead) ## [0.2.0] - 2024-01-01 - Initial release with `Counter` wrapper for byte counting - Support for `std::io`, `futures_io`, and `tokio::io` traits [Unreleased]: https://github.com/spire-rs/countio/compare/v0.3.0...HEAD [0.3.0]: https://github.com/spire-rs/countio/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/spire-rs/countio/releases/tag/v0.2.0 countio-0.3.0/CONTRIBUTING.md000064400000000000000000000036321046102023000135410ustar 00000000000000# Contributing Thank you for your interest in contributing to countio! We appreciate your help in making this project better. ## Code of Conduct This project follows the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct). By participating, you are expected to uphold this code. ## Getting Started 1. Fork the repository on GitHub 2. Clone your fork locally: ```bash git clone https://github.com/YOUR_USERNAME/countio.git cd countio ``` 3. Add the upstream repository: ```bash git remote add upstream https://github.com/spire-rs/countio.git ``` ## Development Setup ### Prerequisites - Rust 1.85 or later - Cargo (comes with Rust) ### Building the Project ```bash # Build with default features (std) cargo build # Build with all features cargo build --all-features # Run tests cargo test --all-features # Check formatting cargo fmt -- --check # Run clippy cargo clippy --all-features -- -D warnings ``` ## Making Changes 1. Create a new branch for your changes: ```bash git checkout -b feature/your-feature-name ``` 2. Make your changes following our code style 3. Add tests for any new functionality 4. Ensure all tests pass: ```bash cargo test --all-features ``` 5. Update documentation if needed ## Submitting Changes 1. Commit your changes with clear, descriptive commit messages 2. Push your changes to your fork: ```bash git push origin feature/your-feature-name ``` 3. Create a Pull Request on GitHub ### Pull Request Guidelines - Keep PRs focused on a single feature or fix - Update CHANGELOG.md with your changes - Ensure CI passes (tests, formatting, clippy) ## Reporting Bugs If you find a bug, please create an issue on GitHub with: - Steps to reproduce the issue - Expected vs actual behavior - Rust version and operating system ## License By contributing, you agree that your contributions will be licensed under the MIT License. countio-0.3.0/Cargo.lock0000644000000117370000000000100105000ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "bytes" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "countio" version = "0.3.0" dependencies = [ "futures-io", "futures-test", "futures-util", "tokio", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-test" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5961fb6311645f46e2cdc2964a8bfae6743fd72315eaec181a71ae3eb2467113" dependencies = [ "futures-core", "futures-executor", "futures-io", "futures-macro", "futures-sink", "futures-task", "futures-util", "pin-project", ] [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "pin-project" version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "syn" version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tokio" version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "pin-project-lite", "tokio-macros", ] [[package]] name = "tokio-macros" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" countio-0.3.0/Cargo.toml0000644000000036560000000000100105240ustar # 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 = "2024" rust-version = "1.85" name = "countio" version = "0.3.0" authors = ["Oleh Martsokha "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = """ Byte counting for std::io::{Read, Write, Seek} and its async variants from futures and tokio. """ homepage = "https://github.com/spire-rs/countio" documentation = "https://docs.rs/countio" readme = "README.md" keywords = [ "byte", "tokio", "futures", "parsing", ] categories = [ "parsing", "asynchronous", ] license = "MIT" repository = "https://github.com/spire-rs/countio" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [features] default = ["std"] full = [ "std", "tokio", "futures", ] futures = ["dep:futures-io"] std = [] tokio = ["dep:tokio"] [lib] name = "countio" path = "src/lib.rs" [[example]] name = "counter" path = "examples/counter.rs" [[example]] name = "progress" path = "examples/progress.rs" [dependencies.futures-io] version = "0.3" features = ["std"] optional = true default-features = false [dependencies.tokio] version = "1" optional = true default-features = false [dev-dependencies.futures-test] version = "0.3" features = ["std"] default-features = false [dev-dependencies.futures-util] version = "0.3" default-features = false [dev-dependencies.tokio] version = "1" features = [ "rt", "macros", "io-util", ] countio-0.3.0/Cargo.toml.orig000064400000000000000000000026121046102023000141740ustar 00000000000000# https://doc.rust-lang.org/cargo/reference/manifest.html [package] name = "countio" version = "0.3.0" readme = "./README.md" edition = "2024" rust-version = "1.85" authors = ["Oleh Martsokha "] license = "MIT" repository = "https://github.com/spire-rs/countio" homepage = "https://github.com/spire-rs/countio" documentation = "https://docs.rs/countio" categories = ["parsing", "asynchronous"] keywords = ["byte", "tokio", "futures", "parsing"] description = """ Byte counting for std::io::{Read, Write, Seek} and its async variants from futures and tokio. """ [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] default = ["std"] ## Enables all features. full = ["std", "tokio", "futures"] ## Enables `std::io::{Read, Write, Seek}` support. std = [] ## Enables `tokio::io::{AsyncRead, AsyncWrite, AsyncSeek}` support. tokio = ["dep:tokio"] ## Enables `futures_io::{AsyncRead, AsyncWrite, AsyncSeek}` support. futures = ["dep:futures-io"] [dependencies] tokio = { version = "1", default-features = false, optional = true } futures-io = { version = "0.3", default-features = false, optional = true, features = ["std"] } [dev-dependencies] tokio = { version = "1", features = ["rt", "macros", "io-util"] } futures-util = { version = "0.3", default-features = false } futures-test = { version = "0.3", default-features = false, features = ["std"] } countio-0.3.0/LICENSE.txt000064400000000000000000000020531046102023000131270ustar 00000000000000MIT License Copyright (c) 2024 Spire Team 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. countio-0.3.0/README.md000064400000000000000000000055301046102023000125660ustar 00000000000000## countio [![Build Status][action-badge]][action-url] [![Crate Docs][docs-badge]][docs-url] [![Crate Version][crates-badge]][crates-url] **Check out other `spire` projects [here](https://github.com/spire-rs).** [action-badge]: https://img.shields.io/github/actions/workflow/status/spire-rs/countio/build.yml?branch=main&label=build&logo=github&style=flat-square [action-url]: https://github.com/spire-rs/countio/actions/workflows/build.yml [crates-badge]: https://img.shields.io/crates/v/countio.svg?logo=rust&style=flat-square [crates-url]: https://crates.io/crates/countio [docs-badge]: https://img.shields.io/docsrs/countio?logo=Docs.rs&style=flat-square [docs-url]: https://docs.rs/countio The wrapper struct to enable byte counting for `std::io::{Read, Write, Seek}` and its asynchronous variants from `futures` and `tokio` crates. Supports bidirectional I/O objects (like `TcpStream` or `Cursor`) that implement both read and write traits, tracking read and write byte counts independently. ### Features - `std` to enable `std::io::{Read, BufRead, Write, Seek}`. **Enabled by default**. - `futures` to enable `futures_io::{AsyncRead, AsyncBufRead, AsyncWrite, AsyncSeek}`. - `tokio` to enable `tokio::io::{AsyncRead, AsyncBufRead, AsyncWrite, AsyncSeek}`. - `full` to enable all features (`std`, `futures`, and `tokio`). ### Examples - `std::io::Read`: ```rust use std::io::{BufRead, BufReader, Result}; use countio::Counter; fn main() -> Result<()> { let reader = "Hello World!".as_bytes(); let reader = BufReader::new(reader); let mut reader = Counter::new(reader); let mut buf = String::new(); let len = reader.read_line(&mut buf)?; assert_eq!(len, reader.reader_bytes()); Ok(()) } ``` - `std::io::Write`: ```rust use std::io::{BufWriter, Write, Result}; use countio::Counter; fn main() -> Result<()> { let writer = Vec::new(); let writer = BufWriter::new(writer); let mut writer = Counter::new(writer); let buf = "Hello World!".as_bytes(); let len = writer.write(buf)?; writer.flush()?; assert_eq!(len, writer.writer_bytes()); Ok(()) } ``` - Bidirectional I/O with `Progress`: ```rust use std::io::{Cursor, Read, Write, Result}; use countio::Progress; fn main() -> Result<()> { let mut data = vec![0u8; 100]; let cursor = Cursor::new(&mut data); let mut progress = Progress::with_expected_bytes(cursor, 50, 50); // Write some data progress.write_all(b"Hello")?; assert_eq!(progress.writer_bytes(), 5); assert_eq!(progress.writer_percentage(), Some(0.1)); // Seek back and read progress.get_mut().set_position(0); let mut buf = [0u8; 5]; progress.read_exact(&mut buf)?; assert_eq!(progress.reader_bytes(), 5); assert_eq!(progress.reader_percentage(), Some(0.1)); Ok(()) } ``` ### Other crates - [SOF3/count-write](https://crates.io/crates/count-write) countio-0.3.0/deny.toml000064400000000000000000000046161046102023000131470ustar 00000000000000# Configuration for cargo-deny # See: https://embarkstudios.github.io/cargo-deny/ [graph] targets = [ { triple = "x86_64-unknown-linux-gnu" }, { triple = "x86_64-unknown-linux-musl" }, { triple = "x86_64-apple-darwin" }, { triple = "aarch64-apple-darwin" }, { triple = "x86_64-pc-windows-msvc" }, ] [advisories] # The path where the advisory database is cloned/fetched into db-path = "~/.cargo/advisory-db" # The url(s) of the advisory databases to use db-urls = ["https://github.com/rustsec/advisory-db"] # The lint level for unmaintained crates unmaintained = "all" # The lint level for crates that have been yanked from their source registry yanked = "deny" # A list of advisory IDs to ignore ignore = [ # Add specific advisory IDs to ignore if needed ] [licenses] # Confidence threshold for detecting a license from a license text (higher = stricter) confidence-threshold = 0.9 # Private licenses are not allowed private = { ignore = false, registries = [] } # Warn if an allowed license is not used in the dependency graph unused-allowed-license = "warn" # List of explicitly allowed licenses (single licenses only) allow = [ "MIT", "Apache-2.0", "Apache-2.0 WITH LLVM-exception", "BSD-2-Clause", "BSD-3-Clause", "ISC", "Unicode-3.0", "Unicode-DFS-2016", "Unlicense", "BSL-1.0", "CC0-1.0", "CDLA-Permissive-2.0", "LGPL-2.1-or-later", "Zlib", ] # For compound licenses, we'll be permissive and only block if they contain denied licenses exceptions = [] [bans] # Lint level for when multiple versions of the same crate are detected multiple-versions = "warn" # Lint level for when a crate version requirement is `*` wildcards = "deny" # The graph highlighting used when creating dotgraphs for crates with multiple versions highlight = "all" # List of crates that are allowed allow = [] # List of crates to deny deny = [] # Skip checking certain crates that are known to have complex but acceptable licensing skip = [] skip-tree = [] [sources] # Lint level for what to happen when a crate from a crate registry that is not in the allow list unknown-registry = "deny" # Lint level for what to happen when a crate from a git repository that is not in the allow list unknown-git = "deny" # List of URLs for allowed crate registries allow-registry = ["https://github.com/rust-lang/crates.io-index"] # List of URLs for allowed Git repositories allow-git = [] countio-0.3.0/examples/counter.rs000064400000000000000000000040351046102023000151510ustar 00000000000000//! Counter usage examples //! //! This example demonstrates the basic functionality of the `Counter` wrapper //! for tracking bytes read and written through I/O operations. use std::io::{Read, Write}; use countio::Counter; fn main() -> std::io::Result<()> { println!("=== Counter Examples ===\n"); // Example 1: Reading with Counter reading_example()?; // Example 2: Writing with Counter writing_example()?; // Example 3: Large numbers (u128 total) large_numbers_example()?; Ok(()) } /// Example: Track bytes read from a data source fn reading_example() -> std::io::Result<()> { println!("Example: Reading with Counter"); let data = b"Hello, World! This is test data."; let mut reader = Counter::new(&data[..]); let mut buffer = [0u8; 10]; let bytes_read = reader.read(&mut buffer)?; println!(" Read: {} bytes", bytes_read); println!(" Total read: {}", reader.reader_bytes()); println!(" Total (u128): {}\n", reader.total_bytes()); Ok(()) } /// Example: Track bytes written to a destination fn writing_example() -> std::io::Result<()> { println!("Example: Writing with Counter"); let mut writer = Counter::new(Vec::new()); writer.write_all(b"Hello, ")?; writer.write_all(b"World!")?; println!(" Written: {} bytes", writer.writer_bytes()); println!(" Total (u128): {}", writer.total_bytes()); let data = writer.into_inner(); println!(" Data: {:?}\n", String::from_utf8(data).unwrap()); Ok(()) } /// Example: Demonstrate u128 support for very large counts fn large_numbers_example() -> std::io::Result<()> { println!("Example: Large numbers (u128 support)"); let large_count = usize::MAX / 2; let counter = Counter::with_bytes(Vec::::new(), large_count, large_count); println!(" Reader: {} bytes", counter.reader_bytes()); println!(" Writer: {} bytes", counter.writer_bytes()); println!(" Total (u128): {}", counter.total_bytes()); println!(" No overflow: {}", counter.total_bytes() < u128::MAX); Ok(()) } countio-0.3.0/examples/progress.rs000064400000000000000000000051551046102023000153420ustar 00000000000000//! Progress tracking examples //! //! This example demonstrates the `Progress` wrapper for tracking //! percentage completion and processing statistics. use std::io::{Read, Write}; use countio::Progress; fn main() -> std::io::Result<()> { println!("=== Progress Examples ===\n"); // Example 1: Progress with known write total known_write_total_example()?; // Example 2: Progress with unknown total unknown_total_example()?; // Example 3: Reading progress reading_example()?; Ok(()) } /// Example: Track progress when total write size is known fn known_write_total_example() -> std::io::Result<()> { println!("Example: Progress with known write total"); let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); progress.write_all(b"Hello")?; println!( " After 'Hello': {:.1}%", progress.writer_percentage().unwrap_or(0.0) * 100.0 ); progress.write_all(b", World!")?; println!( " After ', World!': {:.1}%", progress.writer_percentage().unwrap_or(0.0) * 100.0 ); println!(" Total processed: {} bytes\n", progress.total_bytes()); Ok(()) } /// Example: Track progress when total size is unknown fn unknown_total_example() -> std::io::Result<()> { println!("Example: Progress with unknown total"); let mut progress = Progress::new(Vec::new()); progress.write_all(b"Processing data")?; println!(" Processed: {} bytes", progress.total_bytes()); println!(" Writer percentage: {:?}", progress.writer_percentage()); // Discover total size during processing progress.set_expected_writer_bytes(Some(50)); println!(" After setting expected to 50:"); println!( " Writer percentage: {:.1}%\n", progress.writer_percentage().unwrap_or(0.0) * 100.0 ); Ok(()) } /// Example: Track reading progress fn reading_example() -> std::io::Result<()> { println!("Example: Reading progress"); let data = b"This is test data for reading progress tracking."; let total_size = data.len(); let mut progress = Progress::with_expected_reader_bytes(&data[..], total_size); let mut buffer = [0u8; 10]; while progress.reader_bytes() < data.len() { let bytes_read = progress.read(&mut buffer)?; if bytes_read == 0 { break; } let pct = progress.reader_percentage().unwrap_or(0.0) * 100.0; println!( " Read: {}/{} bytes ({:.1}%)", progress.reader_bytes(), total_size, pct ); } println!(" Reading complete: {} bytes\n", progress.reader_bytes()); Ok(()) } countio-0.3.0/rustfmt.toml000064400000000000000000000002311046102023000137010ustar 00000000000000# https://rust-lang.github.io/rustfmt group_imports = "StdExternalCrate" imports_granularity = "Module" reorder_impl_items = true merge_derives = false countio-0.3.0/src/counter/futures.rs000064400000000000000000000077171046102023000156310ustar 00000000000000use std::io::Result; use std::pin::Pin; use std::task::{Context, Poll}; use futures_io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite}; use crate::Counter; impl AsyncRead for Counter { fn poll_read( self: Pin<&mut Self>, ctx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { let counter = self.get_mut(); let pin = Pin::new(&mut counter.inner); let poll = pin.poll_read(ctx, buf); if let Poll::Ready(Ok(bytes)) = poll { counter.reader_bytes += bytes; } poll } } impl AsyncBufRead for Counter { fn poll_fill_buf(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let counter = self.get_mut(); let pin = Pin::new(&mut counter.inner); pin.poll_fill_buf(ctx) } fn consume(self: Pin<&mut Self>, amt: usize) { let counter = self.get_mut(); counter.reader_bytes += amt; let pin = Pin::new(&mut counter.inner); pin.consume(amt); } } impl AsyncWrite for Counter { fn poll_write(self: Pin<&mut Self>, ctx: &mut Context<'_>, buf: &[u8]) -> Poll> { let counter = self.get_mut(); let pin = Pin::new(&mut counter.inner); let poll = pin.poll_write(ctx, buf); if let Poll::Ready(Ok(bytes)) = poll { counter.writer_bytes += bytes; } poll } fn poll_flush(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let counter = self.get_mut(); let pin = Pin::new(&mut counter.inner); pin.poll_flush(ctx) } fn poll_close(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let counter = self.get_mut(); let pin = Pin::new(&mut counter.inner); pin.poll_close(ctx) } } impl AsyncSeek for Counter { fn poll_seek( self: Pin<&mut Self>, ctx: &mut Context<'_>, pos: std::io::SeekFrom, ) -> Poll> { let counter = self.get_mut(); let pin = Pin::new(&mut counter.inner); pin.poll_seek(ctx, pos) } } #[cfg(test)] mod test { use futures_util::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}; use super::*; #[futures_test::test] async fn test_reader() -> Result<()> { let mut reader = Counter::new(&b"Hello World!"[..]); let mut buf = Vec::new(); let len = reader.read_to_end(&mut buf).await?; assert_eq!(len, reader.reader_bytes()); assert_eq!(len as u128, reader.total_bytes()); Ok(()) } #[futures_test::test] async fn test_buf_reader() -> Result<()> { let reader = BufReader::new(&b"Hello World!"[..]); let mut reader = Counter::new(reader); let mut buf = String::new(); let len = reader.read_line(&mut buf).await?; assert_eq!(len, reader.reader_bytes()); assert_eq!(reader.total_bytes(), 12); Ok(()) } #[futures_test::test] async fn test_writer() -> Result<()> { let writer = BufWriter::new(Vec::new()); let mut writer = Counter::new(writer); let len = writer.write(b"Hello World!").await?; writer.flush().await?; assert_eq!(len, writer.writer_bytes()); assert_eq!(writer.total_bytes(), 12); Ok(()) } #[futures_test::test] async fn test_zero_byte_ops() -> Result<()> { let mut counter = Counter::new(Vec::new()); let len = counter.write(&[]).await?; assert_eq!(len, 0); assert_eq!(counter.writer_bytes(), 0); assert_eq!(counter.total_bytes(), 0); let mut reader = Counter::new(&b""[..]); let mut buf = [0u8; 10]; let len = reader.read(&mut buf).await?; assert_eq!(len, 0); assert_eq!(reader.reader_bytes(), 0); assert_eq!(reader.total_bytes(), 0); Ok(()) } } countio-0.3.0/src/counter/mod.rs000064400000000000000000000265341046102023000147110ustar 00000000000000#[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] mod stdlib; #[cfg(feature = "futures")] #[cfg_attr(docsrs, doc(cfg(feature = "futures")))] mod futures; #[cfg(feature = "tokio")] #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] mod tokio; /// A wrapper that adds byte counting functionality to any reader, writer, or seeker. /// /// `Counter` tracks the total number of bytes read from and written to the underlying /// I/O object, providing methods to query these statistics at any time. /// /// # Type Parameter /// /// * `D` - The underlying I/O object (reader, writer, or seeker) /// /// # Examples /// /// ## Basic Usage with `std::io` /// /// ```rust /// use std::io::{Read, Write}; /// use countio::Counter; /// /// // Counting bytes read /// let data = b"Hello, World!"; /// let mut reader = Counter::new(&data[..]); /// let mut buffer = [0u8; 5]; /// reader.read(&mut buffer).unwrap(); /// assert_eq!(reader.reader_bytes(), 5); /// /// // Counting bytes written /// let mut writer = Counter::new(Vec::new()); /// writer.write_all(b"Hello").unwrap(); /// assert_eq!(writer.writer_bytes(), 5); /// ``` /// /// ## With Buffered I/O /// /// ```rust /// use std::io::{BufRead, BufReader, BufWriter, Write}; /// use countio::Counter; /// /// // Buffered reading /// let data = "Line 1\nLine 2\nLine 3\n"; /// let reader = BufReader::new(data.as_bytes()); /// let mut counter = Counter::new(reader); /// let mut line = String::new(); /// counter.read_line(&mut line).unwrap(); /// assert_eq!(counter.reader_bytes(), 7); /// /// // Buffered writing /// let writer = BufWriter::new(Vec::new()); /// let mut counter = Counter::new(writer); /// counter.write_all(b"Hello, World!").unwrap(); /// counter.flush().unwrap(); /// assert_eq!(counter.writer_bytes(), 13); /// ``` /// /// # Performance /// /// The overhead of byte counting is minimal - just an integer addition per I/O operation. /// The wrapper implements all relevant traits (`Read`, `Write`, `Seek`, etc.) by /// delegating to the inner object while tracking byte counts. pub struct Counter { pub(crate) inner: D, pub(crate) reader_bytes: usize, pub(crate) writer_bytes: usize, } impl Counter { /// Creates a new `Counter` wrapping the given I/O object with zero read/written bytes. /// /// # Arguments /// /// * `inner` - The I/O object to wrap (reader, writer, seeker, etc.) /// /// # Examples /// /// ```rust /// use countio::Counter; /// use std::io::Cursor; /// /// let data = vec![1, 2, 3, 4, 5]; /// let cursor = Cursor::new(data); /// let counter = Counter::new(cursor); /// /// assert_eq!(counter.reader_bytes(), 0); /// assert_eq!(counter.writer_bytes(), 0); /// ``` #[inline] pub const fn new(inner: D) -> Self { Self::with_bytes(inner, 0, 0) } /// Creates a new `Counter` with pre-existing read/written byte counts. /// /// This is useful when you want to continue counting from a previous session /// or when wrapping an I/O object that has already processed some data. /// /// # Arguments /// /// * `inner` - The I/O object to wrap /// * `reader_bytes` - Initial count of bytes read /// * `writer_bytes` - Initial count of bytes written /// /// # Examples /// /// ```rust /// use countio::Counter; /// use std::io::Cursor; /// /// let data = vec![1, 2, 3, 4, 5]; /// let cursor = Cursor::new(data); /// let counter = Counter::with_bytes(cursor, 100, 50); /// /// assert_eq!(counter.reader_bytes(), 100); /// assert_eq!(counter.writer_bytes(), 50); /// ``` #[inline] pub const fn with_bytes(inner: D, reader_bytes: usize, writer_bytes: usize) -> Self { Self { inner, reader_bytes, writer_bytes, } } /// Returns the total number of bytes read from the underlying I/O object. /// /// This count includes all bytes successfully read through any of the /// `Read` or `BufRead` trait methods. /// /// # Examples /// /// ```rust /// use std::io::Read; /// use countio::Counter; /// /// let data = b"Hello, World!"; /// let mut reader = Counter::new(&data[..]); /// let mut buffer = [0u8; 5]; /// /// reader.read_exact(&mut buffer).unwrap(); /// assert_eq!(reader.reader_bytes(), 5); /// assert_eq!(reader.writer_bytes(), 0); /// ``` #[inline] pub const fn reader_bytes(&self) -> usize { self.reader_bytes } /// Returns the total number of bytes written to the underlying I/O object. /// /// This count includes all bytes successfully written through any of the /// `Write` trait methods. /// /// # Examples /// /// ```rust /// use std::io::Write; /// use countio::Counter; /// /// let mut writer = Counter::new(Vec::new()); /// writer.write_all(b"Hello").unwrap(); /// writer.write_all(b", World!").unwrap(); /// /// assert_eq!(writer.writer_bytes(), 13); /// assert_eq!(writer.reader_bytes(), 0); /// ``` #[inline] pub const fn writer_bytes(&self) -> usize { self.writer_bytes } /// Returns the total number of bytes processed (read + written) as a `u128`. /// /// This is the sum of all bytes that have been read from or written to /// the underlying I/O object since the `Counter` was created. Using `u128` /// prevents overflow issues that could occur with large byte counts. /// /// # Examples /// /// ```rust /// use std::io::{Read, Write}; /// use countio::Counter; /// /// let mut counter = Counter::new(Vec::new()); /// counter.write_all(b"Hello").unwrap(); /// /// let data = b"World"; /// let mut reader = Counter::new(&data[..]); /// let mut buf = [0u8; 5]; /// reader.read(&mut buf).unwrap(); /// /// assert_eq!(counter.total_bytes(), 5); /// assert_eq!(reader.total_bytes(), 5); /// ``` #[inline] pub const fn total_bytes(&self) -> u128 { (self.reader_bytes as u128) + (self.writer_bytes as u128) } /// Consumes the `Counter` and returns the underlying I/O object. /// /// This is useful when you need to recover the original I/O object, /// perhaps to pass it to code that doesn't know about the `Counter` wrapper. /// /// # Examples /// /// ```rust /// use std::io::Write; /// use countio::Counter; /// /// let original_writer = Vec::new(); /// let mut counter = Counter::new(original_writer); /// counter.write_all(b"Hello").unwrap(); /// /// let recovered_writer = counter.into_inner(); /// assert_eq!(recovered_writer, b"Hello"); /// ``` #[inline] pub fn into_inner(self) -> D { self.inner } /// Gets a reference to the underlying I/O object. /// /// This allows you to access the wrapped object's methods directly /// without consuming the `Counter`. /// /// # Examples /// /// ```rust /// use countio::Counter; /// use std::io::Cursor; /// /// let data = vec![1, 2, 3, 4, 5]; /// let cursor = Cursor::new(data.clone()); /// let counter = Counter::new(cursor); /// /// assert_eq!(counter.get_ref().position(), 0); /// ``` #[inline] pub const fn get_ref(&self) -> &D { &self.inner } /// Gets a mutable reference to the underlying I/O object. /// /// This allows you to modify the wrapped object directly. Note that /// any bytes read or written through the direct reference will not /// be counted by the `Counter`. /// /// # Examples /// /// ```rust /// use countio::Counter; /// use std::io::Cursor; /// /// let data = vec![1, 2, 3, 4, 5]; /// let cursor = Cursor::new(data); /// let mut counter = Counter::new(cursor); /// /// counter.get_mut().set_position(2); /// assert_eq!(counter.get_ref().position(), 2); /// ``` #[inline] pub const fn get_mut(&mut self) -> &mut D { &mut self.inner } /// Resets the byte counters to zero without affecting the underlying I/O object. /// /// This is useful when you want to start counting from a fresh state /// without recreating the wrapper or losing the underlying object's state. /// /// # Examples /// /// ```rust /// use std::io::{Read, Write}; /// use countio::Counter; /// /// let mut counter = Counter::new(Vec::new()); /// counter.write_all(b"Hello").unwrap(); /// assert_eq!(counter.writer_bytes(), 5); /// /// counter.reset(); /// assert_eq!(counter.writer_bytes(), 0); /// assert_eq!(counter.reader_bytes(), 0); /// /// // The underlying data is preserved /// assert_eq!(counter.get_ref(), b"Hello"); /// ``` #[inline] pub const fn reset(&mut self) { self.reader_bytes = 0; self.writer_bytes = 0; } } impl From for Counter { #[inline] fn from(inner: D) -> Self { Self::new(inner) } } impl Clone for Counter { fn clone(&self) -> Self { Self { inner: self.inner.clone(), reader_bytes: self.reader_bytes, writer_bytes: self.writer_bytes, } } } impl Default for Counter { fn default() -> Self { Self::new(D::default()) } } impl core::fmt::Debug for Counter { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Counter") .field("inner", &self.inner) .field("read", &self.reader_bytes) .field("written", &self.writer_bytes) .finish() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_inner() { let writer = vec![8u8]; assert_eq!(writer.len(), 1); let mut writer = Counter::new(writer); writer.get_mut().clear(); assert_eq!(writer.get_ref().len(), 0); let writer = writer.into_inner(); assert_eq!(writer.len(), 0); } #[test] fn test_from() { let _: Counter<_> = Vec::::new().into(); } #[test] fn test_with_bytes_creates_counter_with_initial_counts() { let counter = Counter::with_bytes(Vec::::new(), 100, 200); assert_eq!(counter.reader_bytes(), 100); assert_eq!(counter.writer_bytes(), 200); assert_eq!(counter.total_bytes(), 300); } #[test] fn test_reset() { use std::io::Write; let mut counter = Counter::new(Vec::new()); counter.write_all(b"Hello").unwrap(); assert_eq!(counter.writer_bytes(), 5); counter.reset(); assert_eq!(counter.writer_bytes(), 0); assert_eq!(counter.reader_bytes(), 0); assert_eq!(counter.total_bytes(), 0); // Data is preserved assert_eq!(counter.get_ref(), b"Hello"); } #[test] fn test_clone() { use std::io::Write; let mut counter = Counter::new(Vec::new()); counter.write_all(b"Hello").unwrap(); let cloned = counter.clone(); assert_eq!(cloned.writer_bytes(), 5); assert_eq!(cloned.get_ref(), b"Hello"); } #[test] fn test_default() { let counter: Counter> = Counter::default(); assert_eq!(counter.reader_bytes(), 0); assert_eq!(counter.writer_bytes(), 0); assert!(counter.get_ref().is_empty()); } } countio-0.3.0/src/counter/stdlib.rs000064400000000000000000000066751046102023000154170ustar 00000000000000use std::io::{BufRead, Read, Result, Seek, SeekFrom, Write}; use crate::Counter; impl Read for Counter { fn read(&mut self, buf: &mut [u8]) -> Result { let bytes = self.inner.read(buf)?; self.reader_bytes += bytes; Ok(bytes) } } impl BufRead for Counter { fn fill_buf(&mut self) -> Result<&[u8]> { self.inner.fill_buf() } fn consume(&mut self, amt: usize) { self.reader_bytes += amt; self.inner.consume(amt); } } impl Write for Counter { fn write(&mut self, buf: &[u8]) -> Result { let bytes = self.inner.write(buf)?; self.writer_bytes += bytes; Ok(bytes) } #[inline] fn flush(&mut self) -> Result<()> { self.inner.flush() } } impl Seek for Counter { #[inline] fn seek(&mut self, pos: SeekFrom) -> Result { self.inner.seek(pos) } } #[cfg(test)] mod test { use std::io::{BufReader, BufWriter}; use super::*; #[test] fn test_reader() -> Result<()> { let mut reader = Counter::new(&b"Hello World!"[..]); let mut buf = Vec::new(); let len = reader.read_to_end(&mut buf)?; assert_eq!(len, reader.reader_bytes()); assert_eq!(len as u128, reader.total_bytes()); Ok(()) } #[test] fn test_buf_reader() -> Result<()> { let reader = BufReader::new(&b"Hello World!"[..]); let mut reader = Counter::new(reader); let mut buf = String::new(); let len = reader.read_line(&mut buf)?; assert_eq!(len, reader.reader_bytes()); assert_eq!(len as u128, reader.total_bytes()); Ok(()) } #[test] fn test_writer() -> Result<()> { let writer = Vec::new(); let writer = BufWriter::new(writer); let mut writer = Counter::new(writer); let len = writer.write(b"Hello World!")?; writer.flush()?; assert_eq!(len, writer.writer_bytes()); assert_eq!(len as u128, writer.total_bytes()); Ok(()) } #[test] fn test_debug() { let writer = Vec::::new(); let writer = Counter::new(writer); let fmt = "Counter { inner: [], read: 0, written: 0 }"; assert_eq!(format!("{writer:?}"), fmt); } #[test] fn test_seek() -> Result<()> { use std::io::{Cursor, SeekFrom}; let data = b"Hello, World!".to_vec(); let cursor = Cursor::new(data); let mut counter = Counter::new(cursor); let pos = counter.seek(SeekFrom::Start(0))?; assert_eq!(pos, 0); let pos = counter.seek(SeekFrom::End(0))?; assert_eq!(pos, 13); let pos = counter.seek(SeekFrom::Current(-5))?; assert_eq!(pos, 8); assert_eq!(counter.reader_bytes(), 0); assert_eq!(counter.writer_bytes(), 0); assert_eq!(counter.total_bytes(), 0); Ok(()) } #[test] fn test_zero_byte_ops() -> Result<()> { let mut counter = Counter::new(Vec::new()); let len = counter.write(&[])?; assert_eq!(len, 0); assert_eq!(counter.writer_bytes(), 0); assert_eq!(counter.total_bytes(), 0); let mut reader = Counter::new(&b""[..]); let mut buf = [0u8; 10]; let len = reader.read(&mut buf)?; assert_eq!(len, 0); assert_eq!(reader.reader_bytes(), 0); assert_eq!(reader.total_bytes(), 0); Ok(()) } } countio-0.3.0/src/counter/tokio.rs000064400000000000000000000113231046102023000152450ustar 00000000000000use std::io::{Result, SeekFrom}; use std::pin::Pin; use std::task::{Context, Poll}; use tokio::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf}; use crate::Counter; impl AsyncRead for Counter { fn poll_read( self: Pin<&mut Self>, ctx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { let counter = self.get_mut(); let pin = Pin::new(&mut counter.inner); let bytes = buf.filled().len(); let poll = pin.poll_read(ctx, buf); if matches!(poll, Poll::Ready(Ok(()))) { let bytes = buf.filled().len() - bytes; counter.reader_bytes += bytes; } poll } } impl AsyncBufRead for Counter { fn poll_fill_buf(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let counter = self.get_mut(); let pin = Pin::new(&mut counter.inner); pin.poll_fill_buf(ctx) } fn consume(self: Pin<&mut Self>, amt: usize) { let counter = self.get_mut(); counter.reader_bytes += amt; let pin = Pin::new(&mut counter.inner); pin.consume(amt); } } impl AsyncWrite for Counter { fn poll_write(self: Pin<&mut Self>, ctx: &mut Context<'_>, buf: &[u8]) -> Poll> { let counter = self.get_mut(); let pin = Pin::new(&mut counter.inner); let poll = pin.poll_write(ctx, buf); if let Poll::Ready(Ok(bytes)) = poll { counter.writer_bytes += bytes; } poll } fn poll_flush(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let counter = self.get_mut(); let pin = Pin::new(&mut counter.inner); pin.poll_flush(ctx) } fn poll_shutdown(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let counter = self.get_mut(); let pin = Pin::new(&mut counter.inner); pin.poll_shutdown(ctx) } } impl AsyncSeek for Counter { fn start_seek(self: Pin<&mut Self>, position: SeekFrom) -> Result<()> { let counter = self.get_mut(); let pin = Pin::new(&mut counter.inner); pin.start_seek(position) } fn poll_complete(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let counter = self.get_mut(); let pin = Pin::new(&mut counter.inner); pin.poll_complete(ctx) } } #[cfg(test)] mod tests { use std::io::Cursor; use tokio::io::{ AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufReader, BufWriter, }; use super::*; #[tokio::test] async fn test_reader() -> Result<()> { let mut reader = Counter::new(&b"Hello World!"[..]); let mut buf = Vec::new(); let len = reader.read_to_end(&mut buf).await?; assert_eq!(len, reader.reader_bytes()); Ok(()) } #[tokio::test] async fn test_buf_reader() -> Result<()> { let reader = BufReader::new(&b"Hello World!"[..]); let mut reader = Counter::new(reader); let mut buf = String::new(); let len = reader.read_line(&mut buf).await?; assert_eq!(len, reader.reader_bytes()); Ok(()) } #[tokio::test] async fn test_writer() -> Result<()> { let writer = BufWriter::new(Vec::new()); let mut writer = Counter::new(writer); let len = writer.write(b"Hello World!").await?; writer.flush().await?; assert_eq!(len, writer.writer_bytes()); Ok(()) } #[tokio::test] async fn test_seek() -> Result<()> { let data = b"Hello, World!".to_vec(); let cursor = Cursor::new(data); let mut counter = Counter::new(cursor); let pos = counter.seek(SeekFrom::Start(0)).await?; assert_eq!(pos, 0); let pos = counter.seek(SeekFrom::End(0)).await?; assert_eq!(pos, 13); let pos = counter.seek(SeekFrom::Current(-5)).await?; assert_eq!(pos, 8); assert_eq!(counter.reader_bytes(), 0); assert_eq!(counter.writer_bytes(), 0); assert_eq!(counter.total_bytes(), 0); Ok(()) } #[tokio::test] async fn test_zero_byte_ops() -> Result<()> { let mut counter = Counter::new(Vec::new()); let len = counter.write(&[]).await?; assert_eq!(len, 0); assert_eq!(counter.writer_bytes(), 0); assert_eq!(counter.total_bytes(), 0); let mut reader = Counter::new(&b""[..]); let mut buf = [0u8; 10]; let len = reader.read(&mut buf).await?; assert_eq!(len, 0); assert_eq!(reader.reader_bytes(), 0); assert_eq!(reader.total_bytes(), 0); Ok(()) } } countio-0.3.0/src/lib.rs000064400000000000000000000004171046102023000132110ustar 00000000000000#![forbid(unsafe_code)] #![warn(clippy::pedantic)] #![warn(clippy::nursery)] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] pub use counter::Counter; pub use progress::Progress; mod counter; mod progress; #[doc(hidden)] pub mod prelude; countio-0.3.0/src/prelude.rs000064400000000000000000000005361046102023000141050ustar 00000000000000//! Convenience re-exports for glob imports. //! //! This module provides a quick way to import the most commonly used types: //! //! ```rust //! use countio::prelude::*; //! //! let counter = Counter::new(Vec::::new()); //! let progress = Progress::with_expected_writer_bytes(Vec::::new(), 100); //! ``` pub use crate::{Counter, Progress}; countio-0.3.0/src/progress/futures.rs000064400000000000000000000101561046102023000160050ustar 00000000000000use std::io::{Result, SeekFrom}; use std::pin::Pin; use std::task::{Context, Poll}; use futures_io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite}; use crate::Progress; impl AsyncRead for Progress { fn poll_read( self: Pin<&mut Self>, ctx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.poll_read(ctx, buf) } } impl AsyncBufRead for Progress { fn poll_fill_buf(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.poll_fill_buf(ctx) } fn consume(self: Pin<&mut Self>, amt: usize) { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.consume(amt); } } impl AsyncWrite for Progress { fn poll_write(self: Pin<&mut Self>, ctx: &mut Context<'_>, buf: &[u8]) -> Poll> { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.poll_write(ctx, buf) } fn poll_flush(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.poll_flush(ctx) } fn poll_close(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.poll_close(ctx) } } impl AsyncSeek for Progress { fn poll_seek(self: Pin<&mut Self>, ctx: &mut Context<'_>, pos: SeekFrom) -> Poll> { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.poll_seek(ctx, pos) } } #[cfg(test)] mod test { use std::io::Result; use futures_util::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}; use super::*; #[futures_test::test] async fn test_reader() -> Result<()> { let mut reader = Progress::new(&b"Hello World!"[..]); let mut buf = Vec::new(); let len = reader.read_to_end(&mut buf).await?; assert_eq!(len, reader.reader_bytes()); assert_eq!(len as u128, reader.total_bytes()); Ok(()) } #[futures_test::test] async fn test_buf_reader() -> Result<()> { let reader = BufReader::new(&b"Hello World!"[..]); let mut reader = Progress::new(reader); let mut buf = String::new(); let len = reader.read_line(&mut buf).await?; assert_eq!(len, reader.reader_bytes()); assert_eq!(len as u128, reader.total_bytes()); Ok(()) } #[futures_test::test] async fn test_writer() -> Result<()> { let writer = BufWriter::new(Vec::new()); let mut writer = Progress::new(writer); let len = writer.write(b"Hello World!").await?; writer.flush().await?; assert_eq!(len, writer.writer_bytes()); assert_eq!(len as u128, writer.total_bytes()); Ok(()) } #[futures_test::test] async fn test_progress_with_known_total() -> Result<()> { let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); progress.write_all(b"Hello").await?; assert_eq!(progress.writer_percentage(), Some(0.05)); assert_eq!(progress.writer_bytes(), 5); assert_eq!(progress.total_bytes(), 5); Ok(()) } #[futures_test::test] async fn test_zero_byte_ops() -> Result<()> { let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); let len = progress.write(&[]).await?; assert_eq!(len, 0); assert_eq!(progress.writer_bytes(), 0); assert_eq!(progress.writer_percentage(), Some(0.0)); let mut reader = Progress::new(&b""[..]); let mut buf = [0u8; 10]; let len = reader.read(&mut buf).await?; assert_eq!(len, 0); assert_eq!(reader.reader_bytes(), 0); Ok(()) } } countio-0.3.0/src/progress/mod.rs000064400000000000000000000352311046102023000150700ustar 00000000000000#[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] mod stdlib; #[cfg(feature = "futures")] #[cfg_attr(docsrs, doc(cfg(feature = "futures")))] mod futures; #[cfg(feature = "tokio")] #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))] mod tokio; use crate::Counter; /// A progress tracker that extends `Counter` with percentage completion tracking. /// /// `Progress` wraps a `Counter` and adds tracking for expected total bytes /// and derived metrics useful for progress reporting. Supports separate tracking /// for read and write operations. /// /// # Examples /// /// ```rust /// use countio::Progress; /// use std::io::Write; /// /// let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 1024); /// progress.write_all(b"Hello").unwrap(); /// /// assert_eq!(progress.total_bytes(), 5); /// assert_eq!(progress.expected_writer_bytes(), Some(1024)); /// assert!(progress.writer_percentage().unwrap() < 1.0); /// ``` pub struct Progress { counter: Counter, expected_reader_bytes: Option, expected_writer_bytes: Option, } impl Progress { /// Creates a new `Progress` with unknown expected sizes. /// /// # Examples /// /// ```rust /// use countio::Progress; /// /// let progress = Progress::new(Vec::::new()); /// assert_eq!(progress.expected_reader_bytes(), None); /// assert_eq!(progress.expected_writer_bytes(), None); /// ``` pub const fn new(inner: D) -> Self { Self { counter: Counter::new(inner), expected_reader_bytes: None, expected_writer_bytes: None, } } /// Creates a new `Progress` with a known expected read size. /// /// # Examples /// /// ```rust /// use countio::Progress; /// /// let progress = Progress::with_expected_reader_bytes(&b"hello"[..], 5); /// assert_eq!(progress.expected_reader_bytes(), Some(5)); /// assert_eq!(progress.expected_writer_bytes(), None); /// ``` pub const fn with_expected_reader_bytes(inner: D, expected: usize) -> Self { Self { counter: Counter::new(inner), expected_reader_bytes: Some(expected), expected_writer_bytes: None, } } /// Creates a new `Progress` with a known expected write size. /// /// # Examples /// /// ```rust /// use countio::Progress; /// /// let progress = Progress::with_expected_writer_bytes(Vec::::new(), 1024); /// assert_eq!(progress.expected_reader_bytes(), None); /// assert_eq!(progress.expected_writer_bytes(), Some(1024)); /// ``` pub const fn with_expected_writer_bytes(inner: D, expected: usize) -> Self { Self { counter: Counter::new(inner), expected_reader_bytes: None, expected_writer_bytes: Some(expected), } } /// Creates a new `Progress` with known expected read and write sizes. /// /// # Examples /// /// ```rust /// use countio::Progress; /// use std::io::Cursor; /// /// let cursor = Cursor::new(vec![0u8; 100]); /// let progress = Progress::with_expected_bytes(cursor, 100, 50); /// assert_eq!(progress.expected_reader_bytes(), Some(100)); /// assert_eq!(progress.expected_writer_bytes(), Some(50)); /// ``` pub const fn with_expected_bytes( inner: D, expected_reader_bytes: usize, expected_writer_bytes: usize, ) -> Self { Self { counter: Counter::new(inner), expected_reader_bytes: Some(expected_reader_bytes), expected_writer_bytes: Some(expected_writer_bytes), } } /// Returns the total number of bytes processed (read + written). #[inline] pub const fn total_bytes(&self) -> u128 { self.counter.total_bytes() } /// Returns the number of bytes read. #[inline] pub const fn reader_bytes(&self) -> usize { self.counter.reader_bytes() } /// Returns the number of bytes written. #[inline] pub const fn writer_bytes(&self) -> usize { self.counter.writer_bytes() } /// Returns the expected number of bytes to read, if known. #[inline] pub const fn expected_reader_bytes(&self) -> Option { self.expected_reader_bytes } /// Returns the expected number of bytes to write, if known. #[inline] pub const fn expected_writer_bytes(&self) -> Option { self.expected_writer_bytes } /// Sets the expected number of bytes to read. pub const fn set_expected_reader_bytes(&mut self, expected: Option) { self.expected_reader_bytes = expected; } /// Sets the expected number of bytes to write. pub const fn set_expected_writer_bytes(&mut self, expected: Option) { self.expected_writer_bytes = expected; } /// Returns the read completion percentage (0.0 to 1.0) if expected read size is known. /// /// # Examples /// /// ```rust /// use countio::Progress; /// use std::io::Read; /// /// let data = b"Hello, World!"; /// let mut progress = Progress::with_expected_reader_bytes(&data[..], 13); /// let mut buf = [0u8; 5]; /// progress.read(&mut buf).unwrap(); /// /// assert!((progress.reader_percentage().unwrap() - 0.3846).abs() < 0.001); /// ``` #[allow(clippy::cast_precision_loss)] pub fn reader_percentage(&self) -> Option { self.expected_reader_bytes.map(|expected| { if expected == 0 { 1.0 } else { (self.reader_bytes() as f64) / (expected as f64) } }) } /// Returns the write completion percentage (0.0 to 1.0) if expected write size is known. /// /// # Examples /// /// ```rust /// use countio::Progress; /// use std::io::Write; /// /// let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); /// progress.write_all(b"Hello").unwrap(); /// /// assert_eq!(progress.writer_percentage(), Some(0.05)); /// ``` #[allow(clippy::cast_precision_loss)] pub fn writer_percentage(&self) -> Option { self.expected_writer_bytes.map(|expected| { if expected == 0 { 1.0 } else { (self.writer_bytes() as f64) / (expected as f64) } }) } /// Consumes the `Progress` and returns the underlying I/O object. #[inline] pub fn into_inner(self) -> D { self.counter.into_inner() } /// Gets a reference to the underlying I/O object. #[inline] pub const fn get_ref(&self) -> &D { self.counter.get_ref() } /// Gets a mutable reference to the underlying I/O object. #[inline] pub const fn get_mut(&mut self) -> &mut D { self.counter.get_mut() } /// Resets the byte counters to zero without affecting the underlying I/O object /// or the expected totals. /// /// # Examples /// /// ```rust /// use std::io::Write; /// use countio::Progress; /// /// let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); /// progress.write_all(b"Hello").unwrap(); /// assert_eq!(progress.writer_bytes(), 5); /// assert_eq!(progress.writer_percentage(), Some(0.05)); /// /// progress.reset(); /// assert_eq!(progress.writer_bytes(), 0); /// assert_eq!(progress.writer_percentage(), Some(0.0)); /// assert_eq!(progress.expected_writer_bytes(), Some(100)); // Expected preserved /// ``` #[inline] pub const fn reset(&mut self) { self.counter.reset(); } } impl From> for Progress { fn from(counter: Counter) -> Self { Self { counter, expected_reader_bytes: None, expected_writer_bytes: None, } } } impl Clone for Progress { fn clone(&self) -> Self { Self { counter: self.counter.clone(), expected_reader_bytes: self.expected_reader_bytes, expected_writer_bytes: self.expected_writer_bytes, } } } impl Default for Progress { fn default() -> Self { Self::new(D::default()) } } impl core::fmt::Debug for Progress { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Progress") .field("counter", &self.counter) .field("expected_reader_bytes", &self.expected_reader_bytes) .field("expected_writer_bytes", &self.expected_writer_bytes) .field("reader_percentage", &self.reader_percentage()) .field("writer_percentage", &self.writer_percentage()) .finish() } } #[cfg(test)] mod tests { use std::io::{Read, Result, Write}; use super::*; #[test] fn test_progress_basic() { let progress = Progress::new(Vec::::new()); assert_eq!(progress.total_bytes(), 0); assert_eq!(progress.expected_reader_bytes(), None); assert_eq!(progress.expected_writer_bytes(), None); let progress = Progress::with_expected_writer_bytes(Vec::::new(), 1000); assert_eq!(progress.expected_writer_bytes(), Some(1000)); assert_eq!(progress.expected_reader_bytes(), None); let progress = Progress::with_expected_reader_bytes(&b"test"[..], 4); assert_eq!(progress.expected_reader_bytes(), Some(4)); assert_eq!(progress.expected_writer_bytes(), None); } #[test] fn test_progress_with_expected_bytes() { use std::io::Cursor; let cursor = Cursor::new(vec![0u8; 100]); let progress = Progress::with_expected_bytes(cursor, 100, 50); assert_eq!(progress.expected_reader_bytes(), Some(100)); assert_eq!(progress.expected_writer_bytes(), Some(50)); } #[test] fn test_progress_writer_percentage() -> Result<()> { let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); progress.write_all(b"Hello")?; assert_eq!(progress.writer_percentage(), Some(0.05)); let zero_progress = Progress::with_expected_writer_bytes(Vec::::new(), 0); assert_eq!(zero_progress.writer_percentage(), Some(1.0)); let unknown_progress = Progress::new(Vec::::new()); assert_eq!(unknown_progress.writer_percentage(), None); Ok(()) } #[test] fn test_progress_reader_percentage() -> Result<()> { let data = b"Hello, World!"; let mut progress = Progress::with_expected_reader_bytes(&data[..], 13); let mut buf = [0u8; 5]; progress.read_exact(&mut buf)?; let pct = progress.reader_percentage().unwrap(); assert!((pct - 5.0 / 13.0).abs() < 0.0001); Ok(()) } #[test] fn test_progress_from_counter() { let counter = Counter::new(Vec::::new()); let progress = Progress::from(counter); assert_eq!(progress.total_bytes(), 0); assert_eq!(progress.expected_reader_bytes(), None); assert_eq!(progress.expected_writer_bytes(), None); } #[test] fn test_progress_set_expected() { let mut progress = Progress::new(Vec::::new()); assert_eq!(progress.expected_writer_bytes(), None); assert_eq!(progress.writer_percentage(), None); progress.set_expected_writer_bytes(Some(100)); assert_eq!(progress.expected_writer_bytes(), Some(100)); assert_eq!(progress.writer_percentage(), Some(0.0)); progress.set_expected_writer_bytes(None); assert_eq!(progress.expected_writer_bytes(), None); assert_eq!(progress.writer_percentage(), None); progress.set_expected_reader_bytes(Some(50)); assert_eq!(progress.expected_reader_bytes(), Some(50)); } #[test] fn test_progress_percentage_edge_cases() -> Result<()> { let progress = Progress::with_expected_writer_bytes(Vec::::new(), 0); assert_eq!(progress.writer_percentage(), Some(1.0)); let progress = Progress::with_expected_writer_bytes(Vec::::new(), usize::MAX); assert_eq!(progress.writer_percentage(), Some(0.0)); let mut progress = Progress::with_expected_writer_bytes(Vec::new(), usize::MAX); progress.write_all(b"x")?; let percentage = progress.writer_percentage().unwrap(); assert!(percentage > 0.0 && percentage < 0.000_000_1); Ok(()) } #[test] fn test_progress_from_counter_with_existing_data() -> Result<()> { let mut counter = Counter::new(Vec::new()); counter.write_all(b"existing")?; let progress = Progress::from(counter); assert_eq!(progress.total_bytes(), 8); assert_eq!(progress.writer_bytes(), 8); assert_eq!(progress.expected_writer_bytes(), None); Ok(()) } #[test] fn test_progress_large_byte_counts() -> Result<()> { let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 1000); for _ in 0..100 { progress.write_all(b"1234567890")?; } assert_eq!(progress.writer_bytes(), 1000); assert_eq!(progress.total_bytes(), 1000); assert_eq!(progress.writer_percentage(), Some(1.0)); Ok(()) } #[test] fn test_progress_reset() -> Result<()> { let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); progress.write_all(b"Hello")?; assert_eq!(progress.writer_bytes(), 5); progress.reset(); assert_eq!(progress.writer_bytes(), 0); assert_eq!(progress.writer_percentage(), Some(0.0)); assert_eq!(progress.expected_writer_bytes(), Some(100)); Ok(()) } #[test] fn test_progress_clone() -> Result<()> { let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); progress.write_all(b"Hello")?; let cloned = progress.clone(); assert_eq!(cloned.writer_bytes(), 5); assert_eq!(cloned.expected_writer_bytes(), Some(100)); Ok(()) } #[test] fn test_progress_default() { let progress: Progress> = Progress::default(); assert_eq!(progress.reader_bytes(), 0); assert_eq!(progress.writer_bytes(), 0); assert_eq!(progress.expected_reader_bytes(), None); assert_eq!(progress.expected_writer_bytes(), None); } #[test] fn test_progress_debug() -> Result<()> { let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); progress.write_all(b"test")?; let debug_str = format!("{progress:?}"); assert!(debug_str.contains("Progress")); assert!(debug_str.contains("written")); assert!(debug_str.contains("expected_writer_bytes")); Ok(()) } } countio-0.3.0/src/progress/stdlib.rs000064400000000000000000000076101046102023000155720ustar 00000000000000use std::io::{BufRead, Read, Result, Seek, SeekFrom, Write}; use crate::Progress; impl Read for Progress { fn read(&mut self, buf: &mut [u8]) -> Result { self.counter.read(buf) } } impl BufRead for Progress { fn fill_buf(&mut self) -> Result<&[u8]> { self.counter.fill_buf() } fn consume(&mut self, amt: usize) { self.counter.consume(amt); } } impl Write for Progress { fn write(&mut self, buf: &[u8]) -> Result { self.counter.write(buf) } fn flush(&mut self) -> Result<()> { self.counter.flush() } } impl Seek for Progress { fn seek(&mut self, pos: SeekFrom) -> Result { self.counter.seek(pos) } } #[cfg(test)] mod test { use std::io::{BufReader, BufWriter, Cursor}; use super::*; #[test] fn test_reader() -> Result<()> { let mut reader = Progress::new(&b"Hello World!"[..]); let mut buf = Vec::new(); let len = reader.read_to_end(&mut buf)?; assert_eq!(len, reader.reader_bytes()); assert_eq!(len as u128, reader.total_bytes()); Ok(()) } #[test] fn test_buf_reader() -> Result<()> { let reader = BufReader::new(&b"Hello World!"[..]); let mut reader = Progress::new(reader); let mut buf = String::new(); let len = reader.read_line(&mut buf)?; assert_eq!(len, reader.reader_bytes()); assert_eq!(len as u128, reader.total_bytes()); Ok(()) } #[test] fn test_writer() -> Result<()> { let writer = BufWriter::new(Vec::new()); let mut writer = Progress::new(writer); let len = writer.write(b"Hello World!")?; writer.flush()?; assert_eq!(len, writer.writer_bytes()); assert_eq!(len as u128, writer.total_bytes()); Ok(()) } #[test] fn test_seek() -> Result<()> { let data = b"Hello, World!".to_vec(); let cursor = Cursor::new(data); let mut progress = Progress::with_expected_reader_bytes(cursor, 13); let pos = progress.seek(SeekFrom::Start(0))?; assert_eq!(pos, 0); let pos = progress.seek(SeekFrom::End(0))?; assert_eq!(pos, 13); let pos = progress.seek(SeekFrom::Current(-5))?; assert_eq!(pos, 8); assert_eq!(progress.reader_bytes(), 0); assert_eq!(progress.writer_bytes(), 0); assert_eq!(progress.total_bytes(), 0); Ok(()) } #[test] fn test_progress_with_known_total() -> Result<()> { let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); progress.write_all(b"Hello")?; assert_eq!(progress.writer_percentage(), Some(0.05)); assert_eq!(progress.writer_bytes(), 5); assert_eq!(progress.total_bytes(), 5); Ok(()) } #[test] fn test_progress_edge_cases() -> Result<()> { let zero_progress = Progress::with_expected_writer_bytes(Vec::::new(), 0); assert_eq!(zero_progress.writer_percentage(), Some(1.0)); let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); let len = progress.write(&[])?; assert_eq!(len, 0); assert_eq!(progress.writer_bytes(), 0); assert_eq!(progress.writer_percentage(), Some(0.0)); Ok(()) } #[test] fn test_zero_byte_ops() -> Result<()> { let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); let len = progress.write(&[])?; assert_eq!(len, 0); assert_eq!(progress.writer_bytes(), 0); assert_eq!(progress.total_bytes(), 0); let mut reader = Progress::new(&b""[..]); let mut buf = [0u8; 10]; let len = reader.read(&mut buf)?; assert_eq!(len, 0); assert_eq!(reader.reader_bytes(), 0); assert_eq!(reader.total_bytes(), 0); Ok(()) } } countio-0.3.0/src/progress/tokio.rs000064400000000000000000000103771046102023000154420ustar 00000000000000use std::io::{Result, SeekFrom}; use std::pin::Pin; use std::task::{Context, Poll}; use tokio::io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, ReadBuf}; use crate::Progress; impl AsyncRead for Progress { fn poll_read( self: Pin<&mut Self>, ctx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.poll_read(ctx, buf) } } impl AsyncBufRead for Progress { fn poll_fill_buf(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.poll_fill_buf(ctx) } fn consume(self: Pin<&mut Self>, amt: usize) { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.consume(amt); } } impl AsyncWrite for Progress { fn poll_write(self: Pin<&mut Self>, ctx: &mut Context<'_>, buf: &[u8]) -> Poll> { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.poll_write(ctx, buf) } fn poll_flush(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.poll_flush(ctx) } fn poll_shutdown(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.poll_shutdown(ctx) } } impl AsyncSeek for Progress { fn start_seek(self: Pin<&mut Self>, position: SeekFrom) -> Result<()> { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.start_seek(position) } fn poll_complete(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { let progress = self.get_mut(); let pin = Pin::new(&mut progress.counter); pin.poll_complete(ctx) } } #[cfg(test)] mod tests { use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}; use super::*; #[tokio::test] async fn test_reader() -> Result<()> { let mut reader = Progress::new(&b"Hello World!"[..]); let mut buf = Vec::new(); let len = reader.read_to_end(&mut buf).await?; assert_eq!(len, reader.reader_bytes()); assert_eq!(len as u128, reader.total_bytes()); Ok(()) } #[tokio::test] async fn test_buf_reader() -> Result<()> { let reader = BufReader::new(&b"Hello World!"[..]); let mut reader = Progress::new(reader); let mut buf = String::new(); let len = reader.read_line(&mut buf).await?; assert_eq!(len, reader.reader_bytes()); assert_eq!(len as u128, reader.total_bytes()); Ok(()) } #[tokio::test] async fn test_writer() -> Result<()> { let writer = BufWriter::new(Vec::new()); let mut writer = Progress::new(writer); let len = writer.write(b"Hello World!").await?; writer.flush().await?; assert_eq!(len, writer.writer_bytes()); assert_eq!(len as u128, writer.total_bytes()); Ok(()) } #[tokio::test] async fn test_progress_with_known_total() -> Result<()> { let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); progress.write_all(b"Hello").await?; assert_eq!(progress.writer_percentage(), Some(0.05)); assert_eq!(progress.writer_bytes(), 5); assert_eq!(progress.total_bytes(), 5); Ok(()) } #[tokio::test] async fn test_zero_byte_ops() -> Result<()> { let mut progress = Progress::with_expected_writer_bytes(Vec::new(), 100); let len = progress.write(&[]).await?; assert_eq!(len, 0); assert_eq!(progress.writer_bytes(), 0); assert_eq!(progress.writer_percentage(), Some(0.0)); let mut reader = Progress::new(&b""[..]); let mut buf = [0u8; 10]; let len = reader.read(&mut buf).await?; assert_eq!(len, 0); assert_eq!(reader.reader_bytes(), 0); Ok(()) } }