pax_global_header00006660000000000000000000000064146116666130014524gustar00rootroot0000000000000052 comment=388d72d12d2b91485ff83469be7d6a72728ff4a9 async-fn-stream-0.2.2/000077500000000000000000000000001461166661300145345ustar00rootroot00000000000000async-fn-stream-0.2.2/.gitignore000066400000000000000000000000241461166661300165200ustar00rootroot00000000000000/target /Cargo.lock async-fn-stream-0.2.2/CHANGELOG.md000066400000000000000000000011131461166661300163410ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. ## [0.2.2] - 2024-04-23 ### ๐Ÿš€ Features - Implement emit_err for try_fn_stream ### ๐Ÿšœ Refactor - Use internal_emit instead of duplicate code ### ๐Ÿ“š Documentation - Improve documentation and some panic messages ### ๐Ÿงช Testing - Add test for emit_err ## [0.2.1] - 2024-04-18 ### ๐Ÿšœ Refactor - Replace futures by futures-util and futures-executor ### ๐Ÿ“š Documentation - Replace futures by futures-util ### ๐Ÿงช Testing - Remove unnecessary import async-fn-stream-0.2.2/Cargo.toml000066400000000000000000000013071461166661300164650ustar00rootroot00000000000000[package] name = "async-fn-stream" description = "Lightweight implementation of `async-stream` without macros" version = "0.2.2" edition = "2021" license = "MIT" homepage = "https://github.com/dmitryvk/async-fn-stream" repository = "https://github.com/dmitryvk/async-fn-stream" keywords = ["async", "stream"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] futures-util = { version = "0.3", default-features = false, features = ["std"] } pin-project-lite = "0.2" [dev-dependencies] anyhow = "1" futures-executor = { version = "0.3", default-features = false, features = ["std", "thread-pool"] } tokio = { version = "1.0", features = ["full"] } async-fn-stream-0.2.2/LICENSE000066400000000000000000000020361461166661300155420ustar00rootroot00000000000000Copyright 2022 Dmitry Kalyanov 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.async-fn-stream-0.2.2/README.md000066400000000000000000000057541461166661300160260ustar00rootroot00000000000000A version of [async-stream](https://github.com/tokio-rs/async-stream) without macros. This crate provides generic implementations of `Stream` trait. `Stream` is an asynchronous version of `std::iter::Iterator`. Two functions are provided - `fn_stream` and `try_fn_stream`. # Usage ## Basic Usage If you need to create a stream that may result in error, use `try_fn_stream`, otherwise use `fn_stream`. To create a stream: 1. Invoke `fn_stream` or `try_fn_stream`, passing a closure (anonymous function). 2. Closure will accept an `emitter`. To return value from the stream, call `.emit(value)` on `emitter` and `.await` on its result. Once stream consumer has processed the value and called `.next()` on stream, `.await` will return. ## Returning errors `try_fn_stream` provides some conveniences for returning errors: 1. Errors can be return from closure via `return Err(...)` or the question mark (`?`) operator. This will end the stream. 2. An `emitter` also has an `emit_err()` method to return errors without ending the stream. # Examples Finite stream of numbers ```rust use async_fn_stream::fn_stream; use futures_util::Stream; fn build_stream() -> impl Stream { fn_stream(|emitter| async move { for i in 0..3 { // yield elements from stream via `emitter` emitter.emit(i).await; } }) } ``` Read numbers from text file, with error handling ```rust use anyhow::Context; use async_fn_stream::try_fn_stream; use futures_util::{pin_mut, Stream, StreamExt}; use tokio::{ fs::File, io::{AsyncBufReadExt, BufReader}, }; fn read_numbers(file_name: String) -> impl Stream> { try_fn_stream(|emitter| async move { // Return errors via `?` operator. let file = BufReader::new(File::open(file_name).await.context("Failed to open file")?); pin_mut!(file); let mut line = String::new(); loop { line.clear(); let byte_count = file .read_line(&mut line) .await .context("Failed to read line")?; if byte_count == 0 { break; } for token in line.split_ascii_whitespace() { let Ok(number) = token.parse::() else { // Return errors via the `emit_err` method. emitter.emit_err( anyhow::anyhow!("Failed to convert string \"{token}\" to number") ).await; continue; }; emitter.emit(number).await; } } Ok(()) }) } ``` # Why not `async-stream`? [async-stream](https://github.com/tokio-rs/async-stream) is great! It has a nice syntax, but it is based on macros which brings some flaws: * proc-macros sometimes interacts badly with IDEs such as rust-analyzer or IntelliJ Rust. see e.g. * proc-macros may increase build times async-fn-stream-0.2.2/cliff.toml000066400000000000000000000065001461166661300165150ustar00rootroot00000000000000# git-cliff ~ default configuration file # https://git-cliff.org/docs/configuration # # Lines starting with "#" are comments. # Configuration options are organized into tables and keys. # See documentation for more information on available options. [changelog] # changelog header header = """ # Changelog\n All notable changes to this project will be documented in this file.\n """ # template for the changelog body # https://keats.github.io/tera/docs/#introduction body = """ {% if version %}\ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} {% else %}\ ## [unreleased] {% endif %}\ {% for group, commits in commits | group_by(attribute="group") %} ### {{ group | striptags | trim | upper_first }} {% for commit in commits %} - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ {% if commit.breaking %}[**breaking**] {% endif %}\ {{ commit.message | upper_first }}\ {% endfor %} {% endfor %}\n """ # template for the changelog footer footer = """ """ # remove the leading and trailing s trim = true # postprocessors postprocessors = [ # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL ] [git] # parse the commits based on https://www.conventionalcommits.org conventional_commits = true # filter out the commits that are not conventional filter_unconventional = true # process each line of a commit as an individual commit split_commits = false # regex for preprocessing the commit messages commit_preprocessors = [ # Replace issue numbers #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, # Check spelling of the commit with https://github.com/crate-ci/typos # If the spelling is incorrect, it will be automatically fixed. #{ pattern = '.*', replace_command = 'typos --write-changes -' }, ] # regex for parsing and grouping commits commit_parsers = [ { message = "^feat", group = "๐Ÿš€ Features" }, { message = "^fix", group = "๐Ÿ› Bug Fixes" }, { message = "^doc", group = "๐Ÿ“š Documentation" }, { message = "^perf", group = "โšก Performance" }, { message = "^refactor", group = "๐Ÿšœ Refactor" }, { message = "^style", group = "๐ŸŽจ Styling" }, { message = "^test", group = "๐Ÿงช Testing" }, { message = "^chore\\(release\\): prepare for", skip = true }, { message = "^chore\\(deps.*\\)", skip = true }, { message = "^chore\\(pr\\)", skip = true }, { message = "^chore\\(pull\\)", skip = true }, { message = "^chore|^ci", group = "โš™๏ธ Miscellaneous Tasks" }, { body = ".*security", group = "๐Ÿ›ก๏ธ Security" }, { message = "^revert", group = "โ—€๏ธ Revert" }, ] # protect breaking changes from being skipped due to matching a skipping commit_parser protect_breaking_commits = false # filter out the commits that are not matched by commit parsers filter_commits = false # regex for matching git tags # tag_pattern = "v[0-9].*" # regex for skipping tags # skip_tags = "" # regex for ignoring tags # ignore_tags = "" # sort the tags topologically topo_order = false # sort the commits inside sections by oldest/newest order sort_commits = "oldest" # limit the number of commits included in the changelog. # limit_commits = 42 async-fn-stream-0.2.2/examples/000077500000000000000000000000001461166661300163525ustar00rootroot00000000000000async-fn-stream-0.2.2/examples/file_numbers.rs000066400000000000000000000024711461166661300213760ustar00rootroot00000000000000use std::env::args; use anyhow::Context; use async_fn_stream::try_fn_stream; use futures_util::{pin_mut, Stream, StreamExt}; use tokio::{ fs::File, io::{AsyncBufReadExt, BufReader}, }; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { let file_name = args().nth(1).unwrap(); let stream = read_numbers(file_name); pin_mut!(stream); while let Some(number) = stream.next().await { println!("number: {}", number?); } Ok(()) } fn read_numbers(file_name: String) -> impl Stream> { try_fn_stream(|emitter| async move { let file = BufReader::new(File::open(file_name).await.context("Failed to open file")?); pin_mut!(file); let mut line = String::new(); loop { line.clear(); let byte_count = file .read_line(&mut line) .await .context("Failed to read line")?; if byte_count == 0 { break; } for token in line.split_ascii_whitespace() { let number: i32 = token .parse() .with_context(|| format!("Failed to conver string \"{token}\" to number"))?; emitter.emit(number).await; } } Ok(()) }) } async-fn-stream-0.2.2/examples/numbers.rs000066400000000000000000000012131461166661300203700ustar00rootroot00000000000000use async_fn_stream::fn_stream; use futures_util::{pin_mut, Stream, StreamExt}; fn build_stream() -> impl Stream { fn_stream(|emitter| async move { for i in 0..3 { // yield elements from stream via `collector` emitter.emit(i).await; } }) } async fn example() { let stream = build_stream(); pin_mut!(stream); let mut numbers = Vec::new(); while let Some(number) = stream.next().await { print!("{} ", number); numbers.push(number); } println!(); assert_eq!(numbers, vec![0, 1, 2]); } pub fn main() { futures_executor::block_on(example()); } async-fn-stream-0.2.2/src/000077500000000000000000000000001461166661300153235ustar00rootroot00000000000000async-fn-stream-0.2.2/src/lib.rs000066400000000000000000000311151461166661300164400ustar00rootroot00000000000000#![doc = include_str!("../README.md")] use std::{ pin::Pin, sync::{Arc, Mutex}, task::{Poll, Waker}, }; use futures_util::{Future, FutureExt, Stream}; use pin_project_lite::pin_project; /// An intermediary that transfers values from stream to its consumer pub struct StreamEmitter { inner: Arc>>, } /// An intermediary that transfers values from stream to its consumer pub struct TryStreamEmitter { inner: Arc>>>, } struct Inner { value: Option, waker: Option, } pin_project! { /// Implementation of [`Stream`] trait created by [`fn_stream`]. pub struct FnStream> { #[pin] fut: Fut, inner: Arc>>, } } /// Create a new infallible stream which is implemented by `func`. /// /// Caller should pass an async function which will return successive stream elements via [`StreamEmitter::emit`]. /// /// # Example /// /// ```rust /// use async_fn_stream::fn_stream; /// use futures_util::Stream; /// /// fn build_stream() -> impl Stream { /// fn_stream(|emitter| async move { /// for i in 0..3 { /// // yield elements from stream via `emitter` /// emitter.emit(i).await; /// } /// }) /// } /// ``` pub fn fn_stream>( func: impl FnOnce(StreamEmitter) -> Fut, ) -> FnStream { FnStream::new(func) } impl> FnStream { fn new) -> Fut>(func: F) -> Self { let inner = Arc::new(Mutex::new(Inner { value: None, waker: None, })); let emitter = StreamEmitter { inner: inner.clone(), }; let fut = func(emitter); Self { fut, inner } } } impl> Stream for FnStream { type Item = T; fn poll_next( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { let mut this = self.project(); this.inner.lock().expect("Mutex was poisoned").waker = Some(cx.waker().clone()); let r = this.fut.poll_unpin(cx); match r { std::task::Poll::Ready(()) => Poll::Ready(None), std::task::Poll::Pending => { let value = this.inner.lock().expect("Mutex was poisoned").value.take(); match value { None => Poll::Pending, Some(value) => Poll::Ready(Some(value)), } } } } } /// Create a new fallible stream which is implemented by `func`. /// /// Caller should pass an async function which can: /// /// - return successive stream elements via [`StreamEmitter::emit`] /// - return transient errors via [`StreamEmitter::emit_err`] /// - return fatal errors as [`Result::Err`] /// /// # Example /// ```rust /// use async_fn_stream::try_fn_stream; /// use futures_util::Stream; /// /// fn build_stream() -> impl Stream> { /// try_fn_stream(|emitter| async move { /// for i in 0..3 { /// // yield elements from stream via `emitter` /// emitter.emit(i).await; /// } /// /// // return errors view emitter without ending the stream /// emitter.emit_err(anyhow::anyhow!("An error happened")); /// /// // return errors from stream, ending the stream /// Err(anyhow::anyhow!("An error happened")) /// }) /// } /// ``` pub fn try_fn_stream>>( func: impl FnOnce(TryStreamEmitter) -> Fut, ) -> TryFnStream { TryFnStream::new(func) } pin_project! { /// Implementation of [`Stream`] trait created by [`try_fn_stream`]. pub struct TryFnStream>> { is_err: bool, #[pin] fut: Fut, inner: Arc>>>, } } impl>> TryFnStream { fn new) -> Fut>(func: F) -> Self { let inner = Arc::new(Mutex::new(Inner { value: None, waker: None, })); let emitter = TryStreamEmitter { inner: inner.clone(), }; let fut = func(emitter); Self { is_err: false, fut, inner, } } } impl>> Stream for TryFnStream { type Item = Result; fn poll_next( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> Poll> { if self.is_err { return Poll::Ready(None); } let mut this = self.project(); this.inner.lock().expect("Mutex was poisoned").waker = Some(cx.waker().clone()); let r = this.fut.poll_unpin(cx); match r { std::task::Poll::Ready(Ok(())) => Poll::Ready(None), std::task::Poll::Ready(Err(e)) => { *this.is_err = true; Poll::Ready(Some(Err(e))) } std::task::Poll::Pending => { let value = this.inner.lock().expect("Mutex was poisoned").value.take(); match value { None => Poll::Pending, Some(value) => Poll::Ready(Some(value)), } } } } } impl StreamEmitter { /// Emit value from a stream and wait until stream consumer calls [`futures_util::StreamExt::next`] again. /// /// # Panics /// Will panic if: /// * `emit` is called twice without awaiting result of first call /// * `emit` is called not in context of polling the stream #[must_use = "Ensure that emit() is awaited"] pub fn emit(&self, value: T) -> CollectFuture { let mut inner = self.inner.lock().expect("Mutex was poisoned"); let inner = &mut *inner; if inner.value.is_some() { panic!("StreamEmitter::emit() was called without `.await`'ing result of previous emit") } inner.value = Some(value); inner .waker .take() .expect("StreamEmitter::emit() should only be called in context of Future::poll()") .wake(); CollectFuture { polled: false } } } impl TryStreamEmitter { fn internal_emit(&self, res: Result) -> CollectFuture { let mut inner = self.inner.lock().expect("Mutex was poisoned"); let inner = &mut *inner; if inner.value.is_some() { panic!( "TreStreamEmitter::emit/emit_err() was called without `.await`'ing result of previous collect" ) } inner.value = Some(res); inner .waker .take() .expect("TreStreamEmitter::emit/emit_err() should only be called in context of Future::poll()") .wake(); CollectFuture { polled: false } } /// Emit value from a stream and wait until stream consumer calls [`futures_util::StreamExt::next`] again. /// /// # Panics /// Will panic if: /// * `emit`/`emit_err` is called twice without awaiting result of the first call /// * `emit` is called not in context of polling the stream #[must_use = "Ensure that emit() is awaited"] pub fn emit(&self, value: T) -> CollectFuture { self.internal_emit(Ok(value)) } /// Emit error from a stream and wait until stream consumer calls [`futures_util::StreamExt::next`] again. /// /// # Panics /// Will panic if: /// * `emit`/`emit_err` is called twice without awaiting result of the first call /// * `emit_err` is called not in context of polling the stream #[must_use = "Ensure that emit_err() is awaited"] pub fn emit_err(&self, err: E) -> CollectFuture { self.internal_emit(Err(err)) } } /// Future returned from [`StreamEmitter::emit`]. pub struct CollectFuture { polled: bool, } impl Future for CollectFuture { type Output = (); fn poll(self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> Poll { if self.polled { Poll::Ready(()) } else { self.get_mut().polled = true; Poll::Pending } } } #[cfg(test)] mod tests { use std::io::ErrorKind; use futures_util::{pin_mut, StreamExt}; use super::*; #[test] fn infallible_works() { futures_executor::block_on(async { let stream = fn_stream(|collector| async move { eprintln!("stream 1"); collector.emit(1).await; eprintln!("stream 2"); collector.emit(2).await; eprintln!("stream 3"); }); pin_mut!(stream); assert_eq!(Some(1), stream.next().await); assert_eq!(Some(2), stream.next().await); assert_eq!(None, stream.next().await); }); } #[test] fn infallible_lifetime() { let a = 1; futures_executor::block_on(async { let b = 2; let a = &a; let b = &b; let stream = fn_stream(|collector| async move { eprintln!("stream 1"); collector.emit(a).await; eprintln!("stream 2"); collector.emit(b).await; eprintln!("stream 3"); }); pin_mut!(stream); assert_eq!(Some(a), stream.next().await); assert_eq!(Some(b), stream.next().await); assert_eq!(None, stream.next().await); }); } #[test] #[should_panic] fn infallible_panics_on_multiple_collects() { futures_executor::block_on(async { #[allow(unused_must_use)] let stream = fn_stream(|collector| async move { eprintln!("stream 1"); collector.emit(1); collector.emit(2); eprintln!("stream 3"); }); pin_mut!(stream); assert_eq!(Some(1), stream.next().await); assert_eq!(Some(2), stream.next().await); assert_eq!(None, stream.next().await); }); } #[test] fn fallible_works() { futures_executor::block_on(async { let stream = try_fn_stream(|collector| async move { eprintln!("try stream 1"); collector.emit(1).await; eprintln!("try stream 2"); collector.emit(2).await; eprintln!("try stream 3"); Err(std::io::Error::from(ErrorKind::Other)) }); pin_mut!(stream); assert_eq!(1, stream.next().await.unwrap().unwrap()); assert_eq!(2, stream.next().await.unwrap().unwrap()); assert!(stream.next().await.unwrap().is_err()); assert!(stream.next().await.is_none()); }); } #[test] fn fallible_emit_err_works() { futures_executor::block_on(async { let stream = try_fn_stream(|collector| async move { eprintln!("try stream 1"); collector.emit(1).await; eprintln!("try stream 2"); collector.emit(2).await; eprintln!("try stream 3"); collector .emit_err(std::io::Error::from(ErrorKind::Other)) .await; eprintln!("try stream 4"); Err(std::io::Error::from(ErrorKind::Other)) }); pin_mut!(stream); assert_eq!(1, stream.next().await.unwrap().unwrap()); assert_eq!(2, stream.next().await.unwrap().unwrap()); assert!(stream.next().await.unwrap().is_err()); assert!(stream.next().await.unwrap().is_err()); assert!(stream.next().await.is_none()); }); } #[test] fn method_async() { struct St { a: String, } impl St { async fn f1(&self) -> impl Stream { self.f2().await } async fn f2(&self) -> impl Stream { fn_stream(|collector| async move { collector.emit(self.a.as_str()).await; collector.emit(self.a.as_str()).await; collector.emit(self.a.as_str()).await; }) } } futures_executor::block_on(async { let l = St { a: "qwe".to_owned(), }; let s = l.f1().await; let z: Vec<&str> = s.collect().await; assert_eq!(z, ["qwe", "qwe", "qwe"]); }) } }