diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 77495c09aa..9a79ec88b1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,14 +18,25 @@ jobs: run: | mkdir bin curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.21/mdbook-v0.4.21-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin - echo "$(pwd)/bin" >> ${GITHUB_PATH} + echo "$(pwd)/bin" >> "${GITHUB_PATH}" - name: Report versions run: | rustup --version rustc -Vv mdbook --version + + # mdBook does not currently have particularly good support for “external” + # crates. To make the test suite work correctly with `trpl`, we must first + # build `trpl` itself (`mdbook` will not do it), and then explicitly pass + # its `deps` path as a library search path for `mdbook test`. That will make + # sure all the crates can be resolved when running the tests. + - name: Build `trpl` crate + run: | + cd packages/trpl + cargo build - name: Run tests - run: mdbook test + run: + mdbook test --library-path packages/trpl/target/debug/deps package_tests: name: Run package tests runs-on: ubuntu-latest @@ -65,7 +76,7 @@ jobs: run: | mkdir bin curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.21/mdbook-v0.4.21-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin - echo "$(pwd)/bin" >> ${GITHUB_PATH} + echo "$(pwd)/bin" >> "${GITHUB_PATH}" - name: Install mdbook-trpl-note run: cargo install --path packages/mdbook-trpl-note - name: Install mdbook-trpl-listing @@ -82,7 +93,7 @@ jobs: aspell --version shellcheck --version - name: Shellcheck - run: find . -name '*.sh' | xargs shellcheck + run: find . -name '*.sh' -print0 | xargs -0 shellcheck - name: Spellcheck run: bash ci/spellcheck.sh list - name: Lint for local file paths diff --git a/2018-edition/src/ch17-03-oo-design-patterns.md b/2018-edition/src/ch17-03-oo-design-patterns.md index 1b74425fe2..9513538f57 100644 --- a/2018-edition/src/ch17-03-oo-design-patterns.md +++ b/2018-edition/src/ch17-03-oo-design-patterns.md @@ -3,8 +3,8 @@ The 2018 edition of the book is no longer distributed with Rust's documentation. If you came here via a link or web search, you may want to check out [the current -version of the book](../ch17-03-oo-design-patterns.html) instead. +version of the book](../ch18-03-oo-design-patterns.html) instead. If you have an internet connection, you can [find a copy distributed with Rust -1.30](https://doc.rust-lang.org/1.30.0/book/2018-edition/ch17-03-oo-design-patterns.html). \ No newline at end of file +1.30](https://doc.rust-lang.org/1.30.0/book/2018-edition/ch17-03-oo-design-patterns.html). diff --git a/ADMIN_TASKS.md b/ADMIN_TASKS.md index 7e9ba2cc04..9caff3004c 100644 --- a/ADMIN_TASKS.md +++ b/ADMIN_TASKS.md @@ -133,3 +133,13 @@ $ dot dot/trpl04-01.dot -Tsvg > src/img/trpl04-01.svg In the generated SVG, remove the width and the height attributes from the `svg` element and set the `viewBox` attribute to `0.00 0.00 1000.00 1000.00` or other values that don't cut off the image. + +## Publish a preview to GitHub Pages + +We sometimes publish to GitHub Pages for in-progress previews. The recommended +flow for publishing is: + +- Install the `ghp-import` tool by running `pip install ghp-import` (or `pipx install ghp-import`, using [pipx][pipx]). +- In the root, run `tools/generate-preview.sh` + +[pipx]: https://pipx.pypa.io/stable/#install-pipx diff --git a/book.toml b/book.toml index b73ff03b7a..800adcf0f5 100644 --- a/book.toml +++ b/book.toml @@ -15,3 +15,6 @@ git-repository-url = "https://github.com/rust-lang/book" [preprocessor.trpl-listing] output-mode = "default" + +[rust] +edition = "2021" diff --git a/ci/dictionary.txt b/ci/dictionary.txt index 6329ed1238..9fd8e33e41 100644 --- a/ci/dictionary.txt +++ b/ci/dictionary.txt @@ -55,6 +55,7 @@ bool boolean Boolean Booleans +booleans Bors BorrowMutError BoxMeUp @@ -109,6 +110,7 @@ deallocate deallocated deallocating deallocation +debounce debuginfo decl decrementing @@ -145,6 +147,7 @@ DisplayBacktrace DivAssign DraftPost DSTs +durations ebook ebooks Edsger @@ -179,6 +182,7 @@ filesystem's filesystems filmmaking Firefox +FirstAwaitPoint FnMut FnOnce formatter @@ -197,6 +201,7 @@ grapheme Grapheme growable gzip +handoff hardcode hardcoded hardcoding @@ -241,6 +246,7 @@ inline instantiation internet interoperate +IntoFuture IntoIterator InvalidDigit invariants @@ -256,6 +262,7 @@ isize iter iterator's JavaScript +JoinAll JoinHandle Kay's kinded @@ -294,6 +301,8 @@ Metadata metaprogramming mibbit Mibbit +microcontroller +microcontrollers millis minigrep mixup @@ -307,10 +316,12 @@ monomorphized MoveMessage Mozilla mpsc +MSRV msvc MulAssign multibyte multithreaded +multithreading mutex mutex's Mutex @@ -318,6 +329,7 @@ mutexes Mutexes MutexGuard mutext +MyAsyncStateMachine MyBox myprogram namespace @@ -359,6 +371,7 @@ OutlinePrint overloadable overread PanicPayload +parallelizable param parameterize ParseIntError @@ -371,6 +384,7 @@ PlaceholderType polymorphism PoolCreationError portia +postfix powershell PowerShell powi @@ -396,6 +410,8 @@ RangeTo RangeFull README READMEs +ReadFinished +ReceiverStream rect recurse recv @@ -438,6 +454,7 @@ sampleproject screenshot searchstring SecondaryColor +SecondAwaitPoint SelectBox semver SemVer @@ -455,6 +472,7 @@ SliceIndex Smalltalk snuck someproject +SomeType someusername SPDX spdx @@ -470,6 +488,7 @@ stdlib stdout steveklabnik's stringify +StreamExt Stroustrup Stroustrup's struct @@ -514,6 +533,8 @@ tlborm tlsv TODO TokenStream +Tokio +tokio toml TOML toolchain @@ -538,6 +559,7 @@ uncomment Uncomment uncommenting unevaluated +unhandled Uninstalling uninstall unittests @@ -556,6 +578,7 @@ username USERPROFILE usize UsState +util utils vals variable's diff --git a/dot/trpl17-01.dot b/dot/trpl17-01.dot new file mode 100644 index 0000000000..97bd15e894 --- /dev/null +++ b/dot/trpl17-01.dot @@ -0,0 +1,44 @@ +digraph { + dpi = 300.0; + + rankdir = "LR"; + + // makes ordering between subgraphs work + newrank = true; + + node [shape = diamond;]; + + subgraph cluster_task_a { + label = "Task A"; + + A1; + A2; + A3; + A4; + + A1 -> A2 -> A3 -> A4 -> A0 [style = invis;]; + + // for vertical alignment purposes only + A0 [style = invis;]; + + // Makes the heights line up between the boxes. + A4 -> A0 [style = invis;]; + } + + subgraph cluster_task_b { + label = "Task B"; + + // for horizontal alignment purposes only + // newrank = true; + + B0 [style = invis;]; + + B1; + B2; + B3; + + B0 -> B1 -> B2 -> B3 [style = invis;]; + } + + A1 -> B1 -> A2 -> B2 -> A3 -> A4 -> B3; +} \ No newline at end of file diff --git a/dot/trpl17-02.dot b/dot/trpl17-02.dot new file mode 100644 index 0000000000..512f33cb9b --- /dev/null +++ b/dot/trpl17-02.dot @@ -0,0 +1,20 @@ +digraph { + dpi = 300.0; + + rankdir = "LR"; + splines = false; + cluster = true; + + node [shape = diamond;]; + + subgraph cluster_ColleagueA { + newrank = true; + label = "Task 1"; + A1 -> A2 -> A3 -> A4; + } + + subgraph cluster_ColleagueB { + label = "Task 2"; + B1 -> B2 -> B3; + } +} \ No newline at end of file diff --git a/listings/ch17-async-await/listing-17-04/src/main.rs b/listings/ch17-async-await/listing-17-04/src/main.rs index 6974137d66..563aee05b7 100644 --- a/listings/ch17-async-await/listing-17-04/src/main.rs +++ b/listings/ch17-async-await/listing-17-04/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + // ANCHOR: main fn main() { trpl::block_on(async { diff --git a/listings/ch17-async-await/listing-17-05/src/main.rs b/listings/ch17-async-await/listing-17-05/src/main.rs index 1a727a9a09..2fe6a5f29d 100644 --- a/listings/ch17-async-await/listing-17-05/src/main.rs +++ b/listings/ch17-async-await/listing-17-05/src/main.rs @@ -1,3 +1,6 @@ +extern crate trpl; // required for mdbook test + +// ANCHOR: all use std::time::Duration; fn main() { @@ -15,3 +18,4 @@ fn main() { } }); } +// ANCHOR_END: all diff --git a/listings/ch17-async-await/listing-17-06/src/main.rs b/listings/ch17-async-await/listing-17-06/src/main.rs index c7e74c8233..67c735fd39 100644 --- a/listings/ch17-async-await/listing-17-06/src/main.rs +++ b/listings/ch17-async-await/listing-17-06/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::time::Duration; fn main() { diff --git a/listings/ch17-async-await/listing-17-07/src/main.rs b/listings/ch17-async-await/listing-17-07/src/main.rs index 7f38d5ebdf..ef52e32840 100644 --- a/listings/ch17-async-await/listing-17-07/src/main.rs +++ b/listings/ch17-async-await/listing-17-07/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::time::Duration; fn main() { diff --git a/listings/ch17-async-await/listing-17-08/src/main.rs b/listings/ch17-async-await/listing-17-08/src/main.rs index bd19c58886..743a22e239 100644 --- a/listings/ch17-async-await/listing-17-08/src/main.rs +++ b/listings/ch17-async-await/listing-17-08/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + fn main() { trpl::block_on(async { // ANCHOR: channel diff --git a/listings/ch17-async-await/listing-17-09/src/main.rs b/listings/ch17-async-await/listing-17-09/src/main.rs index 7a96259e29..2a98d028bb 100644 --- a/listings/ch17-async-await/listing-17-09/src/main.rs +++ b/listings/ch17-async-await/listing-17-09/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::time::Duration; fn main() { diff --git a/listings/ch17-async-await/listing-17-10/src/main.rs b/listings/ch17-async-await/listing-17-10/src/main.rs index cbcd9ee60b..1eb0a163ef 100644 --- a/listings/ch17-async-await/listing-17-10/src/main.rs +++ b/listings/ch17-async-await/listing-17-10/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::time::Duration; fn main() { diff --git a/listings/ch17-async-await/listing-17-11/src/main.rs b/listings/ch17-async-await/listing-17-11/src/main.rs index 626999e4f8..025ed3a1ab 100644 --- a/listings/ch17-async-await/listing-17-11/src/main.rs +++ b/listings/ch17-async-await/listing-17-11/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::time::Duration; fn main() { diff --git a/listings/ch17-async-await/listing-17-12/src/main.rs b/listings/ch17-async-await/listing-17-12/src/main.rs index e51b82ac8a..03fa6a61b5 100644 --- a/listings/ch17-async-await/listing-17-12/src/main.rs +++ b/listings/ch17-async-await/listing-17-12/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::time::Duration; fn main() { diff --git a/listings/ch17-async-await/listing-17-13/Cargo.lock b/listings/ch17-async-await/listing-17-13/Cargo.lock index c0e8bb2b3f..2e0f3ebedb 100644 --- a/listings/ch17-async-await/listing-17-13/Cargo.lock +++ b/listings/ch17-async-await/listing-17-13/Cargo.lock @@ -265,12 +265,24 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "trpl" version = "0.1.0" dependencies = [ "futures", "tokio", + "tokio-stream", ] [[package]] diff --git a/listings/ch17-async-await/listing-17-13/src/main.rs b/listings/ch17-async-await/listing-17-13/src/main.rs index 945442dd8a..87b244daa8 100644 --- a/listings/ch17-async-await/listing-17-13/src/main.rs +++ b/listings/ch17-async-await/listing-17-13/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::time::Duration; fn main() { diff --git a/listings/ch17-async-await/listing-17-14/src/main.rs b/listings/ch17-async-await/listing-17-14/src/main.rs index 097114bb73..684b174bdc 100644 --- a/listings/ch17-async-await/listing-17-14/src/main.rs +++ b/listings/ch17-async-await/listing-17-14/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::time::Duration; fn main() { diff --git a/listings/ch17-async-await/listing-17-15/src/main.rs b/listings/ch17-async-await/listing-17-15/src/main.rs index beff676dbe..5bc9c2c3bd 100644 --- a/listings/ch17-async-await/listing-17-15/src/main.rs +++ b/listings/ch17-async-await/listing-17-15/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::time::Duration; fn main() { diff --git a/listings/ch17-async-await/listing-17-16/src/main.rs b/listings/ch17-async-await/listing-17-16/src/main.rs index b712d344c0..020a4d37f3 100644 --- a/listings/ch17-async-await/listing-17-16/src/main.rs +++ b/listings/ch17-async-await/listing-17-16/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{future::Future, time::Duration}; fn main() { diff --git a/listings/ch17-async-await/listing-17-17/Cargo.lock b/listings/ch17-async-await/listing-17-17/Cargo.lock index c0e8bb2b3f..2e0f3ebedb 100644 --- a/listings/ch17-async-await/listing-17-17/Cargo.lock +++ b/listings/ch17-async-await/listing-17-17/Cargo.lock @@ -265,12 +265,24 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "trpl" version = "0.1.0" dependencies = [ "futures", "tokio", + "tokio-stream", ] [[package]] diff --git a/listings/ch17-async-await/listing-17-17/src/main.rs b/listings/ch17-async-await/listing-17-17/src/main.rs index 5520a58ca5..b7fed9b427 100644 --- a/listings/ch17-async-await/listing-17-17/src/main.rs +++ b/listings/ch17-async-await/listing-17-17/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{ future::Future, pin::{pin, Pin}, diff --git a/listings/ch17-async-await/listing-17-18/src/main.rs b/listings/ch17-async-await/listing-17-18/src/main.rs index 78e17a3846..2c5748960d 100644 --- a/listings/ch17-async-await/listing-17-18/src/main.rs +++ b/listings/ch17-async-await/listing-17-18/src/main.rs @@ -1,11 +1,17 @@ -use std::time::Duration; +extern crate trpl; // required for mdbook test + +use std::{ + future::Future, + pin::{pin, Pin}, + time::Duration, +}; fn main() { trpl::block_on(async { let (tx, mut rx) = trpl::channel(); let tx1 = tx.clone(); - let tx1_fut = async move { + let tx1_fut = pin!(async move { let vals = vec![ String::from("hi"), String::from("from"), @@ -17,15 +23,15 @@ fn main() { tx1.send(val).unwrap(); trpl::sleep(Duration::from_secs(1)).await; } - }; + }); - let rx_fut = async { + let rx_fut = pin!(async { while let Some(value) = rx.recv().await { println!("received '{value}'"); } - }; + }); - let tx_fut = async move { + let tx_fut = pin!(async move { let vals = vec![ String::from("more"), String::from("messages"), @@ -37,11 +43,13 @@ fn main() { tx.send(val).unwrap(); trpl::sleep(Duration::from_secs(1)).await; } - }; + }); // ANCHOR: here - let futures = vec![tx1_fut, rx_fut, tx_fut]; - trpl::join_all(futures).await; + let futures: Vec>> = + vec![tx1_fut, rx_fut, tx_fut]; // ANCHOR_END: here + + trpl::join_all(futures).await; }); } diff --git a/listings/ch17-async-await/listing-17-19/Cargo.lock b/listings/ch17-async-await/listing-17-19/Cargo.lock index c0e8bb2b3f..2e0f3ebedb 100644 --- a/listings/ch17-async-await/listing-17-19/Cargo.lock +++ b/listings/ch17-async-await/listing-17-19/Cargo.lock @@ -265,12 +265,24 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "trpl" version = "0.1.0" dependencies = [ "futures", "tokio", + "tokio-stream", ] [[package]] diff --git a/listings/ch17-async-await/listing-17-19/src/main.rs b/listings/ch17-async-await/listing-17-19/src/main.rs index 6603223a5b..2088dbe21a 100644 --- a/listings/ch17-async-await/listing-17-19/src/main.rs +++ b/listings/ch17-async-await/listing-17-19/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + fn main() { trpl::block_on(async { // ANCHOR: here diff --git a/listings/ch17-async-await/listing-17-20/src/main.rs b/listings/ch17-async-await/listing-17-20/src/main.rs index eb86c1a486..0ac89fe6c1 100644 --- a/listings/ch17-async-await/listing-17-20/src/main.rs +++ b/listings/ch17-async-await/listing-17-20/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::time::Duration; fn main() { diff --git a/listings/ch17-async-await/listing-17-21/src/main.rs b/listings/ch17-async-await/listing-17-21/src/main.rs index 9d8dedfb3b..fa5cfe3c80 100644 --- a/listings/ch17-async-await/listing-17-21/src/main.rs +++ b/listings/ch17-async-await/listing-17-21/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{thread, time::Duration}; fn main() { diff --git a/listings/ch17-async-await/listing-17-22/src/main.rs b/listings/ch17-async-await/listing-17-22/src/main.rs index e9474873c9..55975462fa 100644 --- a/listings/ch17-async-await/listing-17-22/src/main.rs +++ b/listings/ch17-async-await/listing-17-22/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{thread, time::Duration}; fn main() { diff --git a/listings/ch17-async-await/listing-17-23/src/main.rs b/listings/ch17-async-await/listing-17-23/src/main.rs index ae9536c5bd..f9c795fc0c 100644 --- a/listings/ch17-async-await/listing-17-23/src/main.rs +++ b/listings/ch17-async-await/listing-17-23/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{thread, time::Duration}; fn main() { diff --git a/listings/ch17-async-await/listing-17-24/src/main.rs b/listings/ch17-async-await/listing-17-24/src/main.rs index 7efd8fff9a..e95e286a39 100644 --- a/listings/ch17-async-await/listing-17-24/src/main.rs +++ b/listings/ch17-async-await/listing-17-24/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{thread, time::Duration}; fn main() { diff --git a/listings/ch17-async-await/listing-17-25/src/main.rs b/listings/ch17-async-await/listing-17-25/src/main.rs index ef99235640..eaea8509c3 100644 --- a/listings/ch17-async-await/listing-17-25/src/main.rs +++ b/listings/ch17-async-await/listing-17-25/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::time::{Duration, Instant}; fn main() { diff --git a/listings/ch17-async-await/listing-17-26/src/main.rs b/listings/ch17-async-await/listing-17-26/src/main.rs index 4727e58c48..58c83229d4 100644 --- a/listings/ch17-async-await/listing-17-26/src/main.rs +++ b/listings/ch17-async-await/listing-17-26/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::time::Duration; fn main() { diff --git a/listings/ch17-async-await/listing-17-27/src/main.rs b/listings/ch17-async-await/listing-17-27/src/main.rs index a53169bfb2..d30da0d226 100644 --- a/listings/ch17-async-await/listing-17-27/src/main.rs +++ b/listings/ch17-async-await/listing-17-27/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{future::Future, time::Duration}; fn main() { diff --git a/listings/ch17-async-await/listing-17-28/src/main.rs b/listings/ch17-async-await/listing-17-28/src/main.rs index 639d9c8978..20e81e77c4 100644 --- a/listings/ch17-async-await/listing-17-28/src/main.rs +++ b/listings/ch17-async-await/listing-17-28/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{future::Future, time::Duration}; use trpl::Either; diff --git a/listings/ch17-async-await/listing-17-29/src/main.rs b/listings/ch17-async-await/listing-17-29/src/main.rs index db9c724d4f..7900b39ce4 100644 --- a/listings/ch17-async-await/listing-17-29/src/main.rs +++ b/listings/ch17-async-await/listing-17-29/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + fn main() { trpl::block_on(async { // ANCHOR: stream diff --git a/listings/ch17-async-await/listing-17-30/src/main.rs b/listings/ch17-async-await/listing-17-30/src/main.rs index ad546a68fc..8f8dd7d93f 100644 --- a/listings/ch17-async-await/listing-17-30/src/main.rs +++ b/listings/ch17-async-await/listing-17-30/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use trpl::StreamExt; fn main() { diff --git a/listings/ch17-async-await/listing-17-31/src/main.rs b/listings/ch17-async-await/listing-17-31/src/main.rs index 5cda39eb4c..d312640b7f 100644 --- a/listings/ch17-async-await/listing-17-31/src/main.rs +++ b/listings/ch17-async-await/listing-17-31/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use trpl::StreamExt; fn main() { diff --git a/listings/ch17-async-await/listing-17-32/src/main.rs b/listings/ch17-async-await/listing-17-32/src/main.rs index 5f818ae9f6..dd1693790d 100644 --- a/listings/ch17-async-await/listing-17-32/src/main.rs +++ b/listings/ch17-async-await/listing-17-32/src/main.rs @@ -1,3 +1,6 @@ +extern crate trpl; // required for mdbook test + +// ANCHOR: all use trpl::{ReceiverStream, Stream, StreamExt}; fn main() { @@ -20,3 +23,4 @@ fn get_messages() -> impl Stream { ReceiverStream::new(rx) } +// ANCHOR_END: all diff --git a/listings/ch17-async-await/listing-17-33/src/main.rs b/listings/ch17-async-await/listing-17-33/src/main.rs index c99c1f3636..6ad2bc72f9 100644 --- a/listings/ch17-async-await/listing-17-33/src/main.rs +++ b/listings/ch17-async-await/listing-17-33/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + // ANCHOR: timeout use std::{pin::pin, time::Duration}; use trpl::{ReceiverStream, Stream, StreamExt}; diff --git a/listings/ch17-async-await/listing-17-34/src/main.rs b/listings/ch17-async-await/listing-17-34/src/main.rs index 4dc6ed1fdb..7446a2e795 100644 --- a/listings/ch17-async-await/listing-17-34/src/main.rs +++ b/listings/ch17-async-await/listing-17-34/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{pin::pin, time::Duration}; use trpl::{ReceiverStream, Stream, StreamExt}; diff --git a/listings/ch17-async-await/listing-17-35/src/main.rs b/listings/ch17-async-await/listing-17-35/src/main.rs index 5dbcb1d96e..a2b36ca487 100644 --- a/listings/ch17-async-await/listing-17-35/src/main.rs +++ b/listings/ch17-async-await/listing-17-35/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{pin::pin, time::Duration}; use trpl::{ReceiverStream, Stream, StreamExt}; diff --git a/listings/ch17-async-await/listing-17-36/src/main.rs b/listings/ch17-async-await/listing-17-36/src/main.rs index dde9b34c6f..b4cb9c094c 100644 --- a/listings/ch17-async-await/listing-17-36/src/main.rs +++ b/listings/ch17-async-await/listing-17-36/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{pin::pin, time::Duration}; use trpl::{ReceiverStream, Stream, StreamExt}; diff --git a/listings/ch17-async-await/listing-17-37/src/main.rs b/listings/ch17-async-await/listing-17-37/src/main.rs index 4d2c747cc7..4e28174860 100644 --- a/listings/ch17-async-await/listing-17-37/src/main.rs +++ b/listings/ch17-async-await/listing-17-37/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{pin::pin, time::Duration}; use trpl::{ReceiverStream, Stream, StreamExt}; diff --git a/listings/ch17-async-await/listing-17-38/src/main.rs b/listings/ch17-async-await/listing-17-38/src/main.rs index 5103787668..899d14fc23 100644 --- a/listings/ch17-async-await/listing-17-38/src/main.rs +++ b/listings/ch17-async-await/listing-17-38/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{pin::pin, time::Duration}; use trpl::{ReceiverStream, Stream, StreamExt}; diff --git a/listings/ch17-async-await/listing-17-39/src/main.rs b/listings/ch17-async-await/listing-17-39/src/main.rs index 15a3fddd30..1b4e686ea2 100644 --- a/listings/ch17-async-await/listing-17-39/src/main.rs +++ b/listings/ch17-async-await/listing-17-39/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{pin::pin, time::Duration}; use trpl::{ReceiverStream, Stream, StreamExt}; diff --git a/listings/ch17-async-await/listing-17-40/src/main.rs b/listings/ch17-async-await/listing-17-40/src/main.rs index d25f8492ce..7bd605d528 100644 --- a/listings/ch17-async-await/listing-17-40/src/main.rs +++ b/listings/ch17-async-await/listing-17-40/src/main.rs @@ -1,3 +1,5 @@ +extern crate trpl; // required for mdbook test + use std::{pin::pin, thread, time::Duration}; use trpl::{ReceiverStream, Stream, StreamExt}; @@ -45,9 +47,11 @@ fn get_messages() -> impl Stream { fn get_intervals() -> impl Stream { let (tx, rx) = trpl::channel(); + // This is *not* `trpl::spawn` but `std::thread::spawn`! thread::spawn(move || { let mut count = 0; loop { + // Likewise, this is *not* `trpl::sleep` but `std::thread::sleep`! thread::sleep(Duration::from_millis(1)); count += 1; diff --git a/listings/ch17-async-await/no-listing-stream-ext/Cargo.lock b/listings/ch17-async-await/no-listing-stream-ext/Cargo.lock new file mode 100644 index 0000000000..d30b928a6c --- /dev/null +++ b/listings/ch17-async-await/no-listing-stream-ext/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "async_await" +version = "0.1.0" diff --git a/listings/ch17-async-await/no-listing-stream-ext/Cargo.toml b/listings/ch17-async-await/no-listing-stream-ext/Cargo.toml new file mode 100644 index 0000000000..67729afc80 --- /dev/null +++ b/listings/ch17-async-await/no-listing-stream-ext/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "async_await" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/listings/ch17-async-await/no-listing-stream-ext/output.txt b/listings/ch17-async-await/no-listing-stream-ext/output.txt new file mode 100644 index 0000000000..46e6cd3111 --- /dev/null +++ b/listings/ch17-async-await/no-listing-stream-ext/output.txt @@ -0,0 +1,14 @@ +$ cargo run + Compiling async_await v0.1.0 (/Users/chris/dev/rust-lang/book/listings/ch17-async-await/listing-17-01) +warning: unused implementer of `Future` that must be used + --> src/main.rs:3:5 + | +3 | hello("async"); + | ^^^^^^^^^^^^^^ + | + = note: futures do nothing unless you `.await` or poll them + = note: `#[warn(unused_must_use)]` on by default + +warning: `async_await` (bin "async_await") generated 1 warning + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.21s + Running `target/debug/async_await` diff --git a/listings/ch17-async-await/no-listing-stream-ext/src/lib.rs b/listings/ch17-async-await/no-listing-stream-ext/src/lib.rs new file mode 100644 index 0000000000..e236c99260 --- /dev/null +++ b/listings/ch17-async-await/no-listing-stream-ext/src/lib.rs @@ -0,0 +1,18 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; + +trait Stream { + type Item; + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>; +} + +// ANCHOR: here +trait StreamExt: Stream { + async fn next(&mut self) -> Option + where + Self: Unpin; +} +// ANCHOR_END: here diff --git a/packages/mdbook-trpl-listing/Cargo.lock b/packages/mdbook-trpl-listing/Cargo.lock index dba6f20985..c61da8a7f4 100644 --- a/packages/mdbook-trpl-listing/Cargo.lock +++ b/packages/mdbook-trpl-listing/Cargo.lock @@ -362,6 +362,21 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "html_parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f56db07b6612644f6f7719f8ef944f75fff9d6378fdf3d316fd32194184abd" +dependencies = [ + "doc-comment", + "pest", + "pest_derive", + "serde", + "serde_derive", + "serde_json", + "thiserror", +] + [[package]] name = "humantime" version = "2.1.0" @@ -482,13 +497,13 @@ version = "0.1.0" dependencies = [ "assert_cmd", "clap", + "html_parser", "mdbook", "pulldown-cmark", "pulldown-cmark-to-cmark", "serde_json", "thiserror", "toml 0.8.13", - "xmlparser", ] [[package]] @@ -1162,9 +1177,3 @@ checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" dependencies = [ "memchr", ] - -[[package]] -name = "xmlparser" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" diff --git a/packages/mdbook-trpl-listing/Cargo.toml b/packages/mdbook-trpl-listing/Cargo.toml index 4cf009b3f2..20bfe347db 100644 --- a/packages/mdbook-trpl-listing/Cargo.toml +++ b/packages/mdbook-trpl-listing/Cargo.toml @@ -7,13 +7,13 @@ edition = "2021" [dependencies] clap = { version = "4", features = ["derive"] } +html_parser = "0.7.0" mdbook = { version = "0.4", default-features = false } # only need the library pulldown-cmark = { version = "0.10", features = ["simd"] } pulldown-cmark-to-cmark = "13" serde_json = "1" thiserror = "1.0.60" toml = "0.8.12" -xmlparser = "0.13.6" [dev-dependencies] assert_cmd = "2" diff --git a/packages/mdbook-trpl-listing/src/lib.rs b/packages/mdbook-trpl-listing/src/lib.rs index 59d338fe56..619cafdf38 100644 --- a/packages/mdbook-trpl-listing/src/lib.rs +++ b/packages/mdbook-trpl-listing/src/lib.rs @@ -1,3 +1,4 @@ +use html_parser::Dom; use mdbook::{ book::Book, errors::Result, @@ -7,7 +8,6 @@ use mdbook::{ }; use pulldown_cmark::{html, Event}; use pulldown_cmark_to_cmark::cmark; -use xmlparser::{Token, Tokenizer}; /// A preprocessor for rendering listings more elegantly. /// @@ -98,7 +98,7 @@ impl Preprocessor for TrplListing { } fn supports_renderer(&self, renderer: &str) -> bool { - renderer == "html" || renderer == "markdown" + renderer == "html" || renderer == "markdown" || renderer == "test" } } @@ -139,104 +139,29 @@ impl TryFrom<&str> for Mode { } fn rewrite_listing(src: &str, mode: Mode) -> Result { - let parser = new_cmark_parser(src, true); - - struct State<'e> { - current_listing: Option, - events: Vec, String>>, - } - - let final_state = parser.fold( - State { - current_listing: None, + let final_state = new_cmark_parser(src, true).try_fold( + ListingState { + current: None, events: vec![], }, |mut state, ev| { match ev { Event::Html(tag) => { if tag.starts_with(" { - match local.as_str() { - "number" => builder - .with_number(value.as_str()), - "caption" => builder - .with_caption(value.as_str()), - "file-name" => builder - .with_file_name(value.as_str()), - _ => builder, // TODO: error on extra attrs? - } - } - _ => builder, - } - }) - .build(); - - match listing_result { - Ok(listing) => { - let opening_event = match mode { - Mode::Default => { - let opening_html = - listing.opening_html(); - Event::Html(opening_html.into()) - } - Mode::Simple => { - let opening_text = - listing.opening_text(); - Event::Text(opening_text.into()) - } - }; - - state.current_listing = Some(listing); - state.events.push(Ok(opening_event)); - } - Err(reason) => state.events.push(Err(reason)), - } + state.open_listing(tag, mode)?; } else if tag.starts_with("") { - let trailing = if !tag.ends_with('>') { - tag.replace("", "") - } else { - String::from("") - }; - - match state.current_listing { - Some(listing) => { - let closing_event = match mode { - Mode::Default => { - let closing_html = - listing.closing_html(&trailing); - Event::Html(closing_html.into()) - } - Mode::Simple => { - let closing_text = - listing.closing_text(&trailing); - Event::Text(closing_text.into()) - } - }; - - state.current_listing = None; - state.events.push(Ok(closing_event)); - } - None => state.events.push(Err(String::from( - "Closing `` without opening tag.", - ))), - } + state.close_listing(tag, mode); } else { state.events.push(Ok(Event::Html(tag))); } } ev => state.events.push(Ok(ev)), }; - state + Ok::, String>(state) }, - ); + )?; - if final_state.current_listing.is_some() { + if final_state.current.is_some() { return Err("Unclosed listing".into()); } @@ -257,10 +182,107 @@ fn rewrite_listing(src: &str, mode: Mode) -> Result { Ok(buf) } +struct ListingState<'e> { + current: Option, + events: Vec, String>>, +} + +impl<'e> ListingState<'e> { + fn open_listing( + &mut self, + tag: pulldown_cmark::CowStr<'_>, + mode: Mode, + ) -> Result<(), String> { + // We do not *keep* the version constructed here, just temporarily + // construct it so the HTML parser, which expects properly closed tags + // to parse it as a *tag* rather than a *weird text node*, which accept + // it and provide a useful view of it. + let to_parse = tag.to_owned().to_string() + ""; + let listing = Dom::parse(&to_parse) + .map_err(|e| e.to_string())? + .children + .into_iter() + .filter_map(|node| match node { + html_parser::Node::Element(element) => Some(element.attributes), + html_parser::Node::Text(_) | html_parser::Node::Comment(_) => { + None + } + }) + .flatten() + .try_fold(ListingBuilder::new(), |builder, (key, maybe_value)| { + match (key.as_str(), maybe_value) { + ("number", Some(value)) => Ok(builder.with_number(value)), + ("number", None) => { + Err(String::from("number attribute without value")) + } + ("caption", Some(value)) => Ok(builder.with_caption(value)), + ("caption", None) => { + Err(String::from("caption attribute without value")) + } + ("file-name", Some(value)) => { + Ok(builder.with_file_name(value)) + } + ("file-name", None) => { + Err(String::from("file-name attribute without value")) + } + + _ => Ok(builder), // TODO: error on extra attrs? + } + })? + .build(); + + let opening_event = match mode { + Mode::Default => { + let opening_html = listing.opening_html(); + Event::Html(opening_html.into()) + } + Mode::Simple => { + let opening_text = listing.opening_text(); + Event::Text(opening_text.into()) + } + }; + + self.current = Some(listing); + self.events.push(Ok(opening_event)); + Ok(()) + } + + fn close_listing(&mut self, tag: pulldown_cmark::CowStr<'_>, mode: Mode) { + let trailing = if !tag.ends_with('>') { + tag.replace("", "") + } else { + String::from("") + }; + + match &self.current { + Some(listing) => { + let closing_event = match mode { + Mode::Default => { + let closing_html = listing.closing_html(&trailing); + Event::Html(closing_html.into()) + } + Mode::Simple => { + let closing_text = listing.closing_text(&trailing); + Event::Text(closing_text.into()) + } + }; + + self.current = None; + self.events.push(Ok(closing_event)); + } + None => { + self.events.push(Err(String::from( + "Closing `` without opening tag.", + ))); + } + } + } +} + #[derive(Debug)] struct Listing { - number: String, - caption: String, + number: Option, + caption: Option, file_name: Option, } @@ -277,12 +299,21 @@ impl Listing { } fn closing_html(&self, trailing: &str) -> String { - format!( - r#"
Listing {number}: {caption}
-{trailing}"#, - number = self.number, - caption = self.caption - ) + match (&self.number, &self.caption) { + (Some(number), Some(caption)) => format!( + r#"
Listing {number}: {caption}
+{trailing}"# + ), + (None, Some(caption)) => format!( + r#"
{caption}
+{trailing}"# + ), + (Some(number), None) => format!( + r#"
Listing {number}
+{trailing}"# + ), + (None, None) => format!("{trailing}"), + } } fn opening_text(&self) -> String { @@ -293,22 +324,25 @@ impl Listing { } fn closing_text(&self, trailing: &str) -> String { - format!( - "Listing {number}: {caption}{trailing}", - number = self.number, - caption = self.caption, - ) + match (&self.number, &self.caption) { + (Some(number), Some(caption)) => { + format!("Listing {number}: {caption}{trailing}") + } + (None, Some(caption)) => format!("{caption}{trailing}"), + (Some(number), None) => format!("Listing {number}{trailing}"), + (None, None) => trailing.into(), + } } } -struct ListingBuilder<'a> { - number: Option<&'a str>, - caption: Option<&'a str>, - file_name: Option<&'a str>, +struct ListingBuilder { + number: Option, + caption: Option, + file_name: Option, } -impl<'a> ListingBuilder<'a> { - fn new() -> ListingBuilder<'a> { +impl ListingBuilder { + fn new() -> ListingBuilder { ListingBuilder { number: None, caption: None, @@ -316,432 +350,40 @@ impl<'a> ListingBuilder<'a> { } } - fn with_number(mut self, value: &'a str) -> Self { + fn with_number(mut self, value: String) -> Self { self.number = Some(value); self } - fn with_caption(mut self, value: &'a str) -> Self { + fn with_caption(mut self, value: String) -> Self { self.caption = Some(value); self } - fn with_file_name(mut self, value: &'a str) -> Self { + fn with_file_name(mut self, value: String) -> Self { self.file_name = Some(value); self } - fn build(self) -> Result { - let number = self - .number - .ok_or_else(|| String::from("Missing number"))? - .to_owned(); - - let caption = self - .caption - .map(|caption_source| { - let events = new_cmark_parser(caption_source, true); - let mut buf = String::with_capacity(caption_source.len() * 2); - html::push_html(&mut buf, events); - - // This is not particularly principled, but since the only - // place it is used is here, for caption source handling, it - // is “fine”. - buf.replace("

", "").replace("

", "").replace('\n', "") - }) - .ok_or_else(|| String::from("Missing caption"))? - .to_owned(); + fn build(self) -> Listing { + let caption = self.caption.map(|caption_source| { + let events = new_cmark_parser(&caption_source, true); + let mut buf = String::with_capacity(caption_source.len() * 2); + html::push_html(&mut buf, events); + + // This is not particularly principled, but since the only + // place it is used is here, for caption source handling, it + // is “fine”. + buf.replace("

", "").replace("

", "").replace('\n', "") + }); - Ok(Listing { - number, + Listing { + number: self.number.map(String::from), caption, file_name: self.file_name.map(String::from), - }) + } } } #[cfg(test)] -mod tests { - use super::*; - - /// Note: This inserts an additional backtick around the re-emitted code. - /// It is not clear *why*, but that seems to be an artifact of the rendering - /// done by the `pulldown_cmark_to_cmark` crate. - #[test] - fn default_mode_works() { - let result = rewrite_listing( - r#"- -```rust -fn main() {} -``` - -"#, - Mode::Default, - ); - - assert_eq!( - &result.unwrap(), - r#"
-Filename: src/main.rs - -````rust -fn main() {} -```` - -
Listing 1-2: A write-up which might include inline Markdown like code etc.
-
"# - ); - } - - #[test] - fn simple_mode_works() { - let result = rewrite_listing( - r#"- -```rust -fn main() {} -``` - -"#, - Mode::Simple, - ); - - assert_eq!( - &result.unwrap(), - r#" -Filename: src/main.rs - -````rust -fn main() {} -```` - -Listing 1-2: A write-up which might include inline Markdown like code etc."# - ); - } - - #[test] - fn actual_listing() { - let result = rewrite_listing( - r#"Now open the *main.rs* file you just created and enter the code in Listing 1-1. - -- -```rust -fn main() { - println!("Hello, world!"); -} -``` - - - -Save the file and go back to your terminal window"#, - Mode::Default, - ); - - assert!(result.is_ok()); - assert_eq!( - result.unwrap(), - r#"Now open the *main.rs* file you just created and enter the code in Listing 1-1. - -
-Filename: main.rs - -````rust -fn main() { - println!("Hello, world!"); -} -```` - -
Listing 1-1: A program that prints Hello, world!
-
- -Save the file and go back to your terminal window"# - ); - } - - #[test] - fn no_filename() { - let result = rewrite_listing( - r#"This is the opening. - -- -```rust -fn main() {} -``` - - - -This is the closing."#, - Mode::Default, - ); - - assert!(result.is_ok()); - assert_eq!( - result.unwrap(), - r#"This is the opening. - -
- -````rust -fn main() {} -```` - -
Listing 1-1: This is the caption
-
- -This is the closing."# - ); - } - - /// Check that the config options are correctly handled. - /// - /// Note: none of these tests particularly exercise the *wiring*. They just - /// assume that the config itself is done correctly. This is a small enough - /// chunk of code that it easy to verify by hand at present. If it becomes - /// more complex in the future, it would be good to revisit and integrate - /// the same kinds of tests as the unit tests above here. - #[cfg(test)] - mod config { - use super::*; - - // TODO: what *should* the behavior here be? I *think* it should error, - // in that there is a problem if it is invoked without that info. - #[test] - fn no_config() { - let input_json = r##"[ - { - "root": "/path/to/book", - "config": { - "book": { - "authors": ["AUTHOR"], - "language": "en", - "multilingual": false, - "src": "src", - "title": "TITLE" - }, - "preprocessor": {} - }, - "renderer": "html", - "mdbook_version": "0.4.21" - }, - { - "sections": [ - { - "Chapter": { - "name": "Chapter 1", - "content": "# Chapter 1\n", - "number": [1], - "sub_items": [], - "path": "chapter_1.md", - "source_path": "chapter_1.md", - "parent_names": [] - } - } - ], - "__non_exhaustive": null - } - ]"##; - let input_json = input_json.as_bytes(); - let (ctx, book) = - mdbook::preprocess::CmdPreprocessor::parse_input(input_json) - .unwrap(); - let result = TrplListing.run(&ctx, book); - assert!(result.is_err()); - let err = result.unwrap_err(); - assert_eq!(format!("{err}"), "No config for trpl-listing"); - } - - #[test] - fn empty_config() { - let input_json = r##"[ - { - "root": "/path/to/book", - "config": { - "book": { - "authors": ["AUTHOR"], - "language": "en", - "multilingual": false, - "src": "src", - "title": "TITLE" - }, - "preprocessor": { - "trpl-listing": {} - } - }, - "renderer": "html", - "mdbook_version": "0.4.21" - }, - { - "sections": [ - { - "Chapter": { - "name": "Chapter 1", - "content": "# Chapter 1\n", - "number": [1], - "sub_items": [], - "path": "chapter_1.md", - "source_path": "chapter_1.md", - "parent_names": [] - } - } - ], - "__non_exhaustive": null - } - ]"##; - let input_json = input_json.as_bytes(); - let (ctx, book) = - mdbook::preprocess::CmdPreprocessor::parse_input(input_json) - .unwrap(); - let result = TrplListing.run(&ctx, book); - assert!(result.is_ok()); - } - - #[test] - fn specify_default() { - let input_json = r##"[ - { - "root": "/path/to/book", - "config": { - "book": { - "authors": ["AUTHOR"], - "language": "en", - "multilingual": false, - "src": "src", - "title": "TITLE" - }, - "preprocessor": { - "trpl-listing": { - "output-mode": "default" - } - } - }, - "renderer": "html", - "mdbook_version": "0.4.21" - }, - { - "sections": [ - { - "Chapter": { - "name": "Chapter 1", - "content": "# Chapter 1\n", - "number": [1], - "sub_items": [], - "path": "chapter_1.md", - "source_path": "chapter_1.md", - "parent_names": [] - } - } - ], - "__non_exhaustive": null - } - ]"##; - let input_json = input_json.as_bytes(); - let (ctx, book) = - mdbook::preprocess::CmdPreprocessor::parse_input(input_json) - .unwrap(); - let result = TrplListing.run(&ctx, book); - assert!(result.is_ok()); - } - - #[test] - fn specify_simple() { - let input_json = r##"[ - { - "root": "/path/to/book", - "config": { - "book": { - "authors": ["AUTHOR"], - "language": "en", - "multilingual": false, - "src": "src", - "title": "TITLE" - }, - "preprocessor": { - "trpl-listing": { - "output-mode": "simple" - } - } - }, - "renderer": "html", - "mdbook_version": "0.4.21" - }, - { - "sections": [ - { - "Chapter": { - "name": "Chapter 1", - "content": "# Chapter 1\n", - "number": [1], - "sub_items": [], - "path": "chapter_1.md", - "source_path": "chapter_1.md", - "parent_names": [] - } - } - ], - "__non_exhaustive": null - } - ]"##; - let input_json = input_json.as_bytes(); - let (ctx, book) = - mdbook::preprocess::CmdPreprocessor::parse_input(input_json) - .unwrap(); - let result = TrplListing.run(&ctx, book); - assert!(result.is_ok()); - } - - #[test] - fn specify_invalid() { - let input_json = r##"[ - { - "root": "/path/to/book", - "config": { - "book": { - "authors": ["AUTHOR"], - "language": "en", - "multilingual": false, - "src": "src", - "title": "TITLE" - }, - "preprocessor": { - "trpl-listing": { - "output-mode": "nonsense" - } - } - }, - "renderer": "html", - "mdbook_version": "0.4.21" - }, - { - "sections": [ - { - "Chapter": { - "name": "Chapter 1", - "content": "# Chapter 1\n", - "number": [1], - "sub_items": [], - "path": "chapter_1.md", - "source_path": "chapter_1.md", - "parent_names": [] - } - } - ], - "__non_exhaustive": null - } - ]"##; - let input_json = input_json.as_bytes(); - let (ctx, book) = - mdbook::preprocess::CmdPreprocessor::parse_input(input_json) - .unwrap(); - let result = TrplListing.run(&ctx, book); - assert!(result.is_err()); - let err = result.unwrap_err(); - assert_eq!( - format!("{err}"), - "Bad config value '\"nonsense\"' for key 'output-mode'" - ); - } - } -} +mod tests; diff --git a/packages/mdbook-trpl-listing/src/tests/config.rs b/packages/mdbook-trpl-listing/src/tests/config.rs new file mode 100644 index 0000000000..d44004b3e2 --- /dev/null +++ b/packages/mdbook-trpl-listing/src/tests/config.rs @@ -0,0 +1,242 @@ +//! Check that the config options are correctly handled. +//! +//! Note: none of these tests particularly exercise the *wiring*. They just +//! assume that the config itself is done correctly. This is a small enough +//! chunk of code that it easy to verify by hand at present. If it becomes +//! more complex in the future, it would be good to revisit and integrate +//! the same kinds of tests as the unit tests above here. + +use super::*; + +// TODO: what *should* the behavior here be? I *think* it should error, +// in that there is a problem if it is invoked without that info. +#[test] +fn no_config() { + let input_json = r##"[ + { + "root": "/path/to/book", + "config": { + "book": { + "authors": ["AUTHOR"], + "language": "en", + "multilingual": false, + "src": "src", + "title": "TITLE" + }, + "preprocessor": {} + }, + "renderer": "html", + "mdbook_version": "0.4.21" + }, + { + "sections": [ + { + "Chapter": { + "name": "Chapter 1", + "content": "# Chapter 1\n", + "number": [1], + "sub_items": [], + "path": "chapter_1.md", + "source_path": "chapter_1.md", + "parent_names": [] + } + } + ], + "__non_exhaustive": null + } + ]"##; + let input_json = input_json.as_bytes(); + let (ctx, book) = + mdbook::preprocess::CmdPreprocessor::parse_input(input_json).unwrap(); + let result = TrplListing.run(&ctx, book); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!(format!("{err}"), "No config for trpl-listing"); +} + +#[test] +fn empty_config() { + let input_json = r##"[ + { + "root": "/path/to/book", + "config": { + "book": { + "authors": ["AUTHOR"], + "language": "en", + "multilingual": false, + "src": "src", + "title": "TITLE" + }, + "preprocessor": { + "trpl-listing": {} + } + }, + "renderer": "html", + "mdbook_version": "0.4.21" + }, + { + "sections": [ + { + "Chapter": { + "name": "Chapter 1", + "content": "# Chapter 1\n", + "number": [1], + "sub_items": [], + "path": "chapter_1.md", + "source_path": "chapter_1.md", + "parent_names": [] + } + } + ], + "__non_exhaustive": null + } + ]"##; + let input_json = input_json.as_bytes(); + let (ctx, book) = + mdbook::preprocess::CmdPreprocessor::parse_input(input_json).unwrap(); + let result = TrplListing.run(&ctx, book); + assert!(result.is_ok()); +} + +#[test] +fn specify_default() { + let input_json = r##"[ + { + "root": "/path/to/book", + "config": { + "book": { + "authors": ["AUTHOR"], + "language": "en", + "multilingual": false, + "src": "src", + "title": "TITLE" + }, + "preprocessor": { + "trpl-listing": { + "output-mode": "default" + } + } + }, + "renderer": "html", + "mdbook_version": "0.4.21" + }, + { + "sections": [ + { + "Chapter": { + "name": "Chapter 1", + "content": "# Chapter 1\n", + "number": [1], + "sub_items": [], + "path": "chapter_1.md", + "source_path": "chapter_1.md", + "parent_names": [] + } + } + ], + "__non_exhaustive": null + } + ]"##; + let input_json = input_json.as_bytes(); + let (ctx, book) = + mdbook::preprocess::CmdPreprocessor::parse_input(input_json).unwrap(); + let result = TrplListing.run(&ctx, book); + assert!(result.is_ok()); +} + +#[test] +fn specify_simple() { + let input_json = r##"[ + { + "root": "/path/to/book", + "config": { + "book": { + "authors": ["AUTHOR"], + "language": "en", + "multilingual": false, + "src": "src", + "title": "TITLE" + }, + "preprocessor": { + "trpl-listing": { + "output-mode": "simple" + } + } + }, + "renderer": "html", + "mdbook_version": "0.4.21" + }, + { + "sections": [ + { + "Chapter": { + "name": "Chapter 1", + "content": "# Chapter 1\n", + "number": [1], + "sub_items": [], + "path": "chapter_1.md", + "source_path": "chapter_1.md", + "parent_names": [] + } + } + ], + "__non_exhaustive": null + } + ]"##; + let input_json = input_json.as_bytes(); + let (ctx, book) = + mdbook::preprocess::CmdPreprocessor::parse_input(input_json).unwrap(); + let result = TrplListing.run(&ctx, book); + assert!(result.is_ok()); +} + +#[test] +fn specify_invalid() { + let input_json = r##"[ + { + "root": "/path/to/book", + "config": { + "book": { + "authors": ["AUTHOR"], + "language": "en", + "multilingual": false, + "src": "src", + "title": "TITLE" + }, + "preprocessor": { + "trpl-listing": { + "output-mode": "nonsense" + } + } + }, + "renderer": "html", + "mdbook_version": "0.4.21" + }, + { + "sections": [ + { + "Chapter": { + "name": "Chapter 1", + "content": "# Chapter 1\n", + "number": [1], + "sub_items": [], + "path": "chapter_1.md", + "source_path": "chapter_1.md", + "parent_names": [] + } + } + ], + "__non_exhaustive": null + } + ]"##; + let input_json = input_json.as_bytes(); + let (ctx, book) = + mdbook::preprocess::CmdPreprocessor::parse_input(input_json).unwrap(); + let result = TrplListing.run(&ctx, book); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!( + format!("{err}"), + "Bad config value '\"nonsense\"' for key 'output-mode'" + ); +} diff --git a/packages/mdbook-trpl-listing/src/tests/mod.rs b/packages/mdbook-trpl-listing/src/tests/mod.rs new file mode 100644 index 0000000000..5ae5c8e964 --- /dev/null +++ b/packages/mdbook-trpl-listing/src/tests/mod.rs @@ -0,0 +1,192 @@ +use super::*; + +/// Note: This inserts an additional backtick around the re-emitted code. +/// It is not clear *why*, but that seems to be an artifact of the rendering +/// done by the `pulldown_cmark_to_cmark` crate. +#[test] +fn default_mode_works() { + let result = rewrite_listing( + r#"+ +```rust +fn main() {} +``` + +"#, + Mode::Default, + ); + + assert_eq!( + &result.unwrap(), + r#"
+Filename: src/main.rs + +````rust +fn main() {} +```` + +
Listing 1-2: A write-up which might include inline Markdown like code etc.
+
"# + ); +} + +#[test] +fn simple_mode_works() { + let result = rewrite_listing( + r#"+ +```rust +fn main() {} +``` + +"#, + Mode::Simple, + ); + + assert_eq!( + &result.unwrap(), + r#" +Filename: src/main.rs + +````rust +fn main() {} +```` + +Listing 1-2: A write-up which might include inline Markdown like code etc."# + ); +} + +#[test] +fn listing_with_embedded_angle_brackets() { + let result = rewrite_listing( + r#"+ +```rust +fn get_a_box_of(t: T) -> Box { + Box::new(T) +} +``` + +"#, + Mode::Default, + ); + + assert_eq!( + &result.unwrap(), + r#"
+ +````rust +fn get_a_box_of(t: T) -> Box { + Box::new(T) +} +```` + +
Listing 34-5: This has a Box<T> in it.
+
"# + ); +} + +#[test] +fn actual_listing() { + let result = rewrite_listing( + r#"Now open the *main.rs* file you just created and enter the code in Listing 1-1. + ++ +```rust +fn main() { + println!("Hello, world!"); +} +``` + + + +Save the file and go back to your terminal window"#, + Mode::Default, + ); + + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + r#"Now open the *main.rs* file you just created and enter the code in Listing 1-1. + +
+Filename: main.rs + +````rust +fn main() { + println!("Hello, world!"); +} +```` + +
Listing 1-1: A program that prints Hello, world!
+
+ +Save the file and go back to your terminal window"# + ); +} + +#[test] +fn no_filename() { + let result = rewrite_listing( + r#"This is the opening. + ++ +```rust +fn main() {} +``` + + + +This is the closing."#, + Mode::Default, + ); + + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + r#"This is the opening. + +
+ +````rust +fn main() {} +```` + +
Listing 1-1: This is the caption
+
+ +This is the closing."# + ); +} + +#[test] +fn without_number() { + let result = rewrite_listing( + r#"+ +```rust +fn main() {} +``` + +"#, + Mode::Default, + ); + + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + r#"
+Filename: src/main.rs + +````rust +fn main() {} +```` + +
"# + ); +} + +#[cfg(test)] +mod config; diff --git a/packages/mdbook-trpl-note/src/lib.rs b/packages/mdbook-trpl-note/src/lib.rs index 1508a566ed..115641bac2 100644 --- a/packages/mdbook-trpl-note/src/lib.rs +++ b/packages/mdbook-trpl-note/src/lib.rs @@ -49,7 +49,7 @@ impl Preprocessor for TrplNote { } fn supports_renderer(&self, renderer: &str) -> bool { - renderer == "html" || renderer == "markdown" + renderer == "html" || renderer == "markdown" || renderer == "test" } } diff --git a/second-edition/src/ch17-03-oo-design-patterns.md b/second-edition/src/ch17-03-oo-design-patterns.md index 46bec2692f..b29dc2393c 100644 --- a/second-edition/src/ch17-03-oo-design-patterns.md +++ b/second-edition/src/ch17-03-oo-design-patterns.md @@ -3,8 +3,8 @@ The second edition of the book is no longer distributed with Rust's documentation. If you came here via a link or web search, you may want to check out [the current -version of the book](../ch17-03-oo-design-patterns.html) instead. +version of the book](../ch18-03-oo-design-patterns.html) instead. If you have an internet connection, you can [find a copy distributed with Rust -1.30](https://doc.rust-lang.org/1.30.0/book/second-edition/ch17-03-oo-design-patterns.html). \ No newline at end of file +1.30](https://doc.rust-lang.org/1.30.0/book/second-edition/ch17-03-oo-design-patterns.html). diff --git a/second-edition/src/ch18-00-patterns.md b/second-edition/src/ch18-00-patterns.md index 6bd221fa3c..e315b35274 100644 --- a/second-edition/src/ch18-00-patterns.md +++ b/second-edition/src/ch18-00-patterns.md @@ -3,8 +3,8 @@ The second edition of the book is no longer distributed with Rust's documentation. If you came here via a link or web search, you may want to check out [the current -version of the book](../ch18-00-patterns.html) instead. +version of the book](../ch19-00-patterns.html) instead. If you have an internet connection, you can [find a copy distributed with Rust -1.30](https://doc.rust-lang.org/1.30.0/book/second-edition/ch18-00-patterns.html). \ No newline at end of file +1.30](https://doc.rust-lang.org/1.30.0/book/second-edition/ch18-00-patterns.html). diff --git a/src/appendix-03-derivable-traits.md b/src/appendix-03-derivable-traits.md index 8b9b3d3584..d3951f9e5f 100644 --- a/src/appendix-03-derivable-traits.md +++ b/src/appendix-03-derivable-traits.md @@ -184,4 +184,4 @@ ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struc ch04-01-what-is-ownership.html#stack-only-data-copy [ways-variables-and-data-interact-clone]: ch04-01-what-is-ownership.html#ways-variables-and-data-interact-clone -[macros]: ch19-06-macros.html#macros +[macros]: ch20-06-macros.html#macros diff --git a/src/ch03-03-how-functions-work.md b/src/ch03-03-how-functions-work.md index b282cbb2ab..bb8c73a62f 100644 --- a/src/ch03-03-how-functions-work.md +++ b/src/ch03-03-how-functions-work.md @@ -125,7 +125,8 @@ assigning a value to it with the `let` keyword is a statement. In Listing 3-1, Function definitions are also statements; the entire preceding example is a -statement in itself. +statement in itself. (As we will see below, *calling* a function is not a +statement.) Statements do not return values. Therefore, you can’t assign a `let` statement to another variable, as the following code tries to do; you’ll get an error: diff --git a/src/ch05-03-method-syntax.md b/src/ch05-03-method-syntax.md index d25e55b18c..f806021687 100644 --- a/src/ch05-03-method-syntax.md +++ b/src/ch05-03-method-syntax.md @@ -250,6 +250,6 @@ But structs aren’t the only way you can create custom types: let’s turn to Rust’s enum feature to add another tool to your toolbox. [enums]: ch06-00-enums.html -[trait-objects]: ch17-02-trait-objects.md +[trait-objects]: ch18-02-trait-objects.md [public]: ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html#exposing-paths-with-the-pub-keyword [modules]: ch07-02-defining-modules-to-control-scope-and-privacy.html diff --git a/src/ch06-02-match.md b/src/ch06-02-match.md index 6a510df402..eec1ac4a89 100644 --- a/src/ch06-02-match.md +++ b/src/ch06-02-match.md @@ -6,7 +6,7 @@ Rust has an extremely powerful control flow construct called `match` that allows you to compare a value against a series of patterns and then execute code based on which pattern matches. Patterns can be made up of literal values, variable names, wildcards, and many other things; [Chapter -18][ch18-00-patterns] covers all the different kinds of patterns +18][ch19-00-patterns] covers all the different kinds of patterns and what they do. The power of `match` comes from the expressiveness of the patterns and the fact that the compiler confirms that all possible cases are handled. @@ -246,9 +246,10 @@ that doesn’t match a pattern in an earlier arm, and we don’t want to run any code in this case. There’s more about patterns and matching that we’ll cover in [Chapter -18][ch18-00-patterns]. For now, we’re going to move on to the +19][ch19-00-patterns]. For now, we’re going to move on to the `if let` syntax, which can be useful in situations where the `match` expression is a bit wordy. [tuples]: ch03-02-data-types.html#the-tuple-type -[ch18-00-patterns]: ch18-00-patterns.html + +[ch19-00-patterns]: ch19-00-patterns.html diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index 35bcd11474..78a87c7392 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -533,5 +533,5 @@ let’s return to the topic of how to decide which is appropriate to use in whic cases. [handle_failure]: ch02-00-guessing-game-tutorial.html#handling-potential-failure-with-result -[trait-objects]: ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types +[trait-objects]: ch18-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types [termination]: ../std/process/trait.Termination.html diff --git a/src/ch09-03-to-panic-or-not-to-panic.md b/src/ch09-03-to-panic-or-not-to-panic.md index 40f93d67ce..138a783d00 100644 --- a/src/ch09-03-to-panic-or-not-to-panic.md +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -219,4 +219,4 @@ Now that you’ve seen useful ways that the standard library uses generics with the `Option` and `Result` enums, we’ll talk about how generics work and how you can use them in your code. -[encoding]: ch17-03-oo-design-patterns.html#encoding-states-and-behavior-as-types +[encoding]: ch18-03-oo-design-patterns.html#encoding-states-and-behavior-as-types diff --git a/src/ch10-02-traits.md b/src/ch10-02-traits.md index 53e9b8115c..855ee1fc4b 100644 --- a/src/ch10-02-traits.md +++ b/src/ch10-02-traits.md @@ -385,5 +385,5 @@ that checks for behavior at runtime because we’ve already checked at compile time. Doing so improves performance without having to give up the flexibility of generics. -[using-trait-objects-that-allow-for-values-of-different-types]: ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types +[using-trait-objects-that-allow-for-values-of-different-types]: ch18-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types [methods]: ch05-03-method-syntax.html#defining-methods diff --git a/src/ch12-00-an-io-project.md b/src/ch12-00-an-io-project.md index bae99c75f7..7780ac2738 100644 --- a/src/ch12-00-an-io-project.md +++ b/src/ch12-00-an-io-project.md @@ -47,4 +47,4 @@ detail. [ch10]: ch10-00-generics.html [ch11]: ch11-00-testing.html [ch13]: ch13-00-functional-features.html -[ch17]: ch17-00-oop.html +[ch17]: ch18-00-oop.html diff --git a/src/ch12-03-improving-error-handling-and-modularity.md b/src/ch12-03-improving-error-handling-and-modularity.md index 0de3ec2c22..bcff43a6d9 100644 --- a/src/ch12-03-improving-error-handling-and-modularity.md +++ b/src/ch12-03-improving-error-handling-and-modularity.md @@ -491,5 +491,5 @@ write some tests! [ch9-custom-types]: ch09-03-to-panic-or-not-to-panic.html#creating-custom-types-for-validation [ch9-error-guidelines]: ch09-03-to-panic-or-not-to-panic.html#guidelines-for-error-handling [ch9-result]: ch09-02-recoverable-errors-with-result.html -[ch17]: ch17-00-oop.html +[ch17]: ch18-00-oop.html [ch9-question-mark]: ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator diff --git a/src/ch15-01-box.md b/src/ch15-01-box.md index 53e829a4c3..1878f6f195 100644 --- a/src/ch15-01-box.md +++ b/src/ch15-01-box.md @@ -252,4 +252,4 @@ even more important to the functionality provided by the other smart pointer types we’ll discuss in the rest of this chapter. Let’s explore these two traits in more detail. -[trait-objects]: ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types +[trait-objects]: ch18-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types diff --git a/src/ch16-04-extensible-concurrency-sync-and-send.md b/src/ch16-04-extensible-concurrency-sync-and-send.md index 1b74554341..ede5c66856 100644 --- a/src/ch16-04-extensible-concurrency-sync-and-send.md +++ b/src/ch16-04-extensible-concurrency-sync-and-send.md @@ -62,6 +62,11 @@ uphold them. ## Summary +This isn’t the last you’ll see of concurrency in this book: the whole next +chapter focuses on async programming, and the project in Chapter 20 will use the +concepts in this chapter in a more realistic situation than the smaller examples +discussed here. + As mentioned earlier, because very little of how Rust handles concurrency is part of the language, many concurrency solutions are implemented as crates. These evolve more quickly than the standard library, so be sure to search @@ -77,9 +82,6 @@ run on multiple threads without the kinds of hard-to-track-down bugs common in other languages. Concurrent programming is no longer a concept to be afraid of: go forth and make your programs concurrent, fearlessly! -This is just your first step into Rust’s concurrency story. In the next chapter, -we will explore a complementary approach, asynchronous programming. - [sharing-a-mutext-between-multiple-threads]: ch16-03-shared-state.html#sharing-a-mutext-between-multiple-threads [nomicon]: ../nomicon/index.html diff --git a/src/ch17-00-async-await.md b/src/ch17-00-async-await.md index bc1a301a6c..e68c7420fc 100644 --- a/src/ch17-00-async-await.md +++ b/src/ch17-00-async-await.md @@ -1,29 +1,5 @@ ## Async and Await -The threading-based concurrency model is one of the oldest concurrency models in -computing, and it was present and well-supported in Rust since 1.0. In the past -few decades, though, many programming languages have been experimenting with -other approaches to concurrency, especially asynchronous programming, or -*async*. - -It took a few years to work out the right design for async in Rust. After a -bunch of experimentation and design work in the library ecosystem, Rust added -language-level support for async in Rust 1.39, in 2019, and there is a thriving -ecosystem of crates supporting all sorts of interesting capabilities offered by -those language primitives. - -In the rest of this chapter, we will: - -* see how to use Rust’s `async` and `.await` syntax -* explore how to use the async model to solve some of the same challenges we - looked at in Chapter 16 -* look at how multithreading and async provide complementary solutions, which - you can even use together in many cases - -First, though, let’s explore what async gives us. - -### Why Async? - Many operations we ask the computer to do can take a while to finish. For example, if you used a video editor to create a video of a family celebration, exporting it could take anywhere from minutes to hours. Similarly, downloading a @@ -38,25 +14,24 @@ That would be a pretty frustrating experience, though. Instead, your computer can (and does!) invisibly interrupt the export often enough to let you get other work done along the way. -The file download is different. It does not take up very much CPU time. You are -mostly waiting on data to transfer across the network. You can start reading -from a network socket, but it might take a while for all the data to arrive and -be fed into the socket by the network controller. Even once the data has all -arrived, videos can be quite large, so it might take some time to load all the -data from the socket. Maybe it only takes a second or two—but that is a very -long time for a modern processor, which can do billions of operations every -second. It would be nice to be able to put the CPU to use for other work while -waiting for the network call to finish—so, again, your computer will once again -invisibly interrupt your program so other things can happen while the network -operation is still ongoing. +The file download is different. It does not take up very much CPU time. The CPU +needs to wait on data to arrive from the network. While you can start reading +the data once some of it arrives, it might take a while for the rest to arrive. +Even once the data has all arrived, videos can be quite large, so it might take +some time to load all the data from the network. Maybe it only takes a second or +two—but that is a very long time for a modern processor, which can do billions +of operations every second. It would be nice to be able to put the CPU to use +for other work while waiting for the network call to finish—so, again, your +computer will once again invisibly interrupt your program so other things can +happen while the network operation is still ongoing. > Note: The video export is the kind of operation which is often described as -> “CPU-bound”. It is limited by the speed of the computer’s *CPU and GPU*, and -> how much of that speed it can use. The video download is the kind of operation -> which is often described as “IO-bound,” because it is limited by the speed of -> the computer’s *input and output*. It can only go as fast as the data can be -> sent across the network, which means that it can only go as fast as the data -> can be written to the socket by the network controller. +> “CPU-bound” or “compute-bound”. It is limited by the speed of the computer’s +> ability to process data within the *CPU* or *GPU*, and how much of that speed +> it can use. The video download is the kind of operation which is often +> described as “IO-bound,” because it is limited by the speed of the computer’s +> *input and output*. It can only go as fast as the data can be sent across the +> network. In both of these examples, the concurrency only happens at the level of a whole program. The operating system interrupts one program to let other @@ -67,123 +42,64 @@ opportunities for concurrency that the operating system cannot see. For example, if we are building a tool to manage file downloads, we should be able to write our program in such a way that starting one download does not lock up the UI, and users should be able to start multiple downloads at the same -time. Many operating system APIs for interacting with network sockets are -*blocking*, though. That is, the function calls block further progress in the -program when they are called until they return. +time. Many operating system APIs for interacting with the network are +*blocking*, though. That is, these APIs block the program’s progress until the +data that they are processing is completely ready. > Note: This is how *most* function calls work, if you think about it! However, -we normally reserve the term “blocking” for function calls which interact with -files, network sockets, or other resources on the computer, because those are -the places where an individual program would benefit from the operation being -*non*-blocking. - -We could use threads to avoid blocking while downloading files, by using a -dedicated thread. But it would be nicer if the call were not blocking in the -first place. - -One way to accomplish that would be to use an API built around callbacks. For -each blocking operation, we could pass in a function to call once the operation -completes: - -```rust,ignore -network_socket.read_non_blocking(|result| { - // ... -}); -``` +> we normally reserve the term “blocking” for function calls which interact with +> files, the network, or other resources on the computer, because those are the +> places where an individual program would benefit from the operation being +> *non*-blocking. -Or we could register callbacks to run when events happen: - -```rust,ignore -network_socket.add_listener(Event::ReadFinished, |event| { - // ... -}); -``` +We could avoid blocking our main thread by spawning a dedicated thread to +download each file. But it would be nicer if the call were not blocking in the +first place. It would also be nice if we could write in the same direct style +we use in blocking code. Something like this: -Or we could have our functions return a type with `and_then` method, which in -turn accepts a callback which can do more work of the same sort (Historically, -this was the way that Rust did async): - -```rust,ignore -network_socket.read_non_blocking().and_then(|result| { - /* another non_blocking operation */ -}); +```rust,ignore,does_not_compile +let data = fetch_data_from(url).await; +println!("{data}"); ``` -Each of these makes it harder to understand both the control flow and the flow -of data through the program. You can end up with event handler callbacks -scattered across the code base, or groups of deeply nested callbacks, or long -chains of `and_then` calls. +That is exactly what Rust’s async abstraction gives us. Before we see how this +works in practice, though, we need to take a short detour into the differences +between parallelism and concurrency. -There are also no particularly good ways to get data out of those callbacks. -With other common types in Rust, we often use pattern-matching in scenarios like -this. When we are using callbacks we do not yet have the data at the time we -call `read_non_blocking`—and we will not have it until the callback gets called. -That means that there is no way to match on the data it will return: it is not -here yet! +### Parallelism and Concurrency -As an alternative, we might try something like this, imagining a -`read_non_blocking` which has exactly the kind of `and_then` method described -above. If we were to try to do that, though, with code kind of like this, it -would not even compile: +In the previous chapter we treated parallelism and concurrency as mostly +interchangeable. Now we need to distinguish between them more precisely, because +the differences will show up as we start working. -```rust,ignore,does_not_compile -let mut data = None; -network_socket.read_non_blocking().and_then(|result| { - data = Some(result); -}); -println!("{data:?}"); -``` +Think about working on a software project as a team. -In this very broken code, the callback passed to `and_then` needs a mutable -reference to `data`, but the `println` macro needs to borrow a reference to -`data` to be able to print it. Rust would helpfully tell us that we cannot -borrow `data` immutably to print it because it is still borrowed mutably for the -`and_then` callback. This is not just Rust being fussy, either: the result of -this would normally always just print the `None` value, but if the read -*happened* to go fast enough, it is possible it could sometimes print some -string data instead. That is *definitely* not what we want! +When an individual works on several different tasks before any of them is +complete, this is *concurrency*. Maybe you have two different projects checked +out on your computer, and when you get bored or stuck on one project, you switch +to the other. You are just one person, and you cannot make progress on both +tasks at the exact same time—but you can multi-task, making progress on multiple +tasks by switching between them. -We also could not cancel `read_non_blocking`: once it has started, it will run -till it finishes unless the whole program stops. +
-What we really want to be able to write is something much more direct, like we -would write in blocking code, but with the benefits of getting the data when it -is available and *not* blocking the rest of the program while waiting for the -data to arrive—something like this: +Concurrent work flow -```rust,ignore,does_not_compile -let data = network_socket.read(&path).await; -println!("{data}"); -``` +
Figure 17-1: A concurrent workflow, switching between Task A and Task B
-That is exactly what Rust’s async abstraction gives us. It is designed to help -us solve all of these issues. Before we will see how this works in practice, -though, we need to dig a little deeper into the differences between parallelism -and concurrency. +
-### Parallelism and Concurrency +When you agree to split up a group of tasks between the people on the team, with +each person taking one task and working on it alone, this is *parallelism*. Each +person on the team can make progress at the exact same time. -In the previous chapter we treated parallelism and concurrency as mostly -interchangeable. Now we need to distinguish between them more precisely, because -the differences will show up as we start working: +
-* *Parallelism* is when operations can happen simultaneously. +Concurrent work flow -* *Concurrency* is when operations can make progress without having to wait for - all other operations to complete. +
Figure 17-2: A parallel workflow, where work happens on Task A and Task B independently
-Think about working on a software project as a team. When you agree to split up -a group of tasks between a group of people, with each person working on one task -and delivering them separately, this is *parallelism*. Each person on the team -can be making progress at the exact same time. On the other hand, when an -individual works on several different tasks before any of them is complete, this -is *concurrency*. Maybe you have two different projects checked out on your -computer, and when you get bored or stuck on one project, you switch to the -other. You are just one person, and you cannot make progress on both tasks at -the exact same time—but you can multi-task, making progress on multiple tasks by -switching between them. Work on one does not necessarily *block* working on the -other. +
With both of these situations, you might have to coordinate between different tasks. Maybe you *thought* the task that one person was working on was totally @@ -195,20 +111,30 @@ working on needs the result from another of your tasks. Now your concurrent work has also become serial. Parallelism and concurrency can intersect with each other, too. For example, if -it turns out your coworker is waiting on one of your projects to finish, then -you might need to focus on that project and not give any time to your other task -until it is done, so your own work stops being concurrent. - -On a machine with a single CPU core, the CPU can only do one operation at a -time, but we can still have concurrency. Using tools like threads, processes, -and async, the computer can pause one activity and switch to others before -eventually cycling back to that first activity again. On a machine with multiple -CPU cores, we can actually do work in parallel. One core can be doing one thing -while another core does something completely unrelated, and those actually -happen at the same time. +it turns out your coworker is waiting on one of your projects to finish, you +might need to focus on that project and not give any time to your other task +until it is done. In that case, you and your coworker are no longer able to work +in parallel *and* you are no longer able to work concurrently. + +The same basic dynamics come into play with software and hardware. On a machine +with a single CPU core, the CPU can only do one operation at a time, but it can +still work concurrently. Using tools like threads, processes, and async, the +computer can pause one activity and switch to others before eventually cycling +back to that first activity again. On a machine with multiple CPU cores, it can +also do work in parallel. One core can be doing one thing while another core +does something completely unrelated, and those actually happen at the same +time. When working with async in Rust, we are always dealing with concurrency. Depending on the hardware, the operating system, and the async runtime we are using—more on async runtimes shortly!—that concurrency may or may not also use -parallelism under the hood. Now, let’s dive into how async programming in Rust -actually works! +parallelism under the hood. + +Now, let’s dive into how async programming in Rust actually works! In the rest +of this chapter, we will: + +* see how to use Rust’s `async` and `.await` syntax +* explore how to use the async model to solve some of the same challenges we + looked at in Chapter 16 +* look at how multithreading and async provide complementary solutions, which + you can even use together in many cases diff --git a/src/ch17-01-futures-and-syntax.md b/src/ch17-01-futures-and-syntax.md index f5e912ba79..272471247a 100644 --- a/src/ch17-01-futures-and-syntax.md +++ b/src/ch17-01-futures-and-syntax.md @@ -41,8 +41,10 @@ In Rust, writing `async fn` is equivalent to writing a function which returns a defined like this instead: ```rust -fn hello(name: &str) -> impl Future { - async { +use std::future::Future; + +fn hello<'a>(name: &'a str) -> impl Future + 'a { + async move { let greeting = format!("Hello, {name}!"); println!("{greeting}"); } @@ -56,9 +58,13 @@ Let’s walk through each part of the transformed version: * The returned trait is a `Future`, with an associated type of `Output`. Notice that the `Output` type is `()`, which is the same as the the original return type from the `async fn` version of `hello`. -* The whole body of the function is wrapped in an `async` block. Remember that - blocks are expressions. This whole block is the expression returned from the - function. +* All of the code called in the body of the original function is wrapped in an + `async move` block. Remember that blocks are expressions. This whole block is + the expression returned from the function. +* The new function body is an `async move` block because of how it uses the + `name` argument. +* The new version of the function makes the lifetime of the `name` parameter + explicit so that it can reference it in the output type. * The async block itself has the “unit” value `()`, since it ends with a `println!` statement. That value matches the `Output` type in the return type. @@ -70,10 +76,11 @@ return type of the original `async fn`. Thus, calling `hello` in Listing 17-1 returned a `Future`. Then Rust warned us that we did not do anything with the future. This is because -futures are *lazy*: they don’t do anything until you ask them to. This should -remind you of our discussion of iterators [back in Chapter 13][iterators-lazy]. -Iterators do nothing unless you call their `.next()` method—whether directly, or -using `for` loops or methods like `.map()` which use `.next()` under the hood. +futures are *lazy*: they don’t do anything until you ask them to with `await`. +This should remind you of our discussion of iterators [back in Chapter +13][iterators-lazy]. Iterators do nothing unless you call their `.next()` +method—whether directly, or using `for` loops or methods like `.map()` which use +`.next()` under the hood. With futures, the same basic idea applies: they do nothing unless you explicitly ask them to. This laziness allows Rust to avoid running async code until it is @@ -127,12 +134,12 @@ However, we get another compiler error here: The problem is that async code needs a *runtime*: a Rust crate which manages the details of executing asynchronous code. -Most languages which support async bundle a runtime with the language. At least -for now, Rust does not. Instead, there are many different async runtimes -available, each of which makes different tradeoffs suitable to the use case they -target. For example, a high-throughput web server with dozens of CPU cores and -terabytes of RAM has very different different needs than a microcontroller with -a single core, one gigabyte of RAM, and no ability to do heap allocations. +Most languages which support async bundle a runtime with the language. Rust does +not. Instead, there are many different async runtimes available, each of which +makes different tradeoffs suitable to the use case they target. For example, a +high-throughput web server with many CPU cores and a large amount of RAM has +very different different needs than a microcontroller with a single core, a +small amount of RAM, and no ability to do heap allocations. > ### The `trpl` Crate > @@ -148,7 +155,7 @@ a single core, one gigabyte of RAM, and no ability to do heap allocations. > - Tokio is the most widely used async runtime in Rust today, especially (but > not only!) for web applications. There are other great runtimes out there, > and they may be more suitable for your purposes. We use Tokio under the hood -> for `trpl` because it good and widely used. +> for `trpl` because it is good and widely used. > > In some cases, `trpl` also renames or wraps the original APIs to let us stay > focused on the details relevant to chapter. If you want to understand what the @@ -194,6 +201,9 @@ operations can be implemented with different data, but with a common interface. Here is the definition of the trait: ```rust +use std::pin::Pin; +use std::task::{Context, Poll}; + pub trait Future { type Output; diff --git a/src/ch17-02-concurrency-with-async.md b/src/ch17-02-concurrency-with-async.md index 40b2927e42..a57d056814 100644 --- a/src/ch17-02-concurrency-with-async.md +++ b/src/ch17-02-concurrency-with-async.md @@ -21,7 +21,7 @@ to implement the same counting example as with threads, in Listing 17-5. ```rust -{{#rustdoc_include ../listings/ch17-async-await/listing-17-05/src/main.rs}} +{{#rustdoc_include ../listings/ch17-async-await/listing-17-05/src/main.rs:all}} ``` @@ -202,9 +202,11 @@ It is hard to see the effect of async in Listing 17-8, though, since the message will arrive right away! Let’s go ahead and send a whole series of messages, and sleep in between them, as shown in Listing 17-9: + + -```rust +```rust,ignore {{#rustdoc_include ../listings/ch17-async-await/listing-17-09/src/main.rs:many-messages}} ``` @@ -260,9 +262,11 @@ each message, we need to put the `tx` and `rx` operations in their own async blocks. Then the runtime can execute each of them separately using `trpl::join`, just like in the counting example. + + -```rust +```rust,ignore {{#rustdoc_include ../listings/ch17-async-await/listing-17-10/src/main.rs:futures}} ``` diff --git a/src/ch17-03-more-futures.md b/src/ch17-03-more-futures.md index afc00eb3e8..b27e68ad73 100644 --- a/src/ch17-03-more-futures.md +++ b/src/ch17-03-more-futures.md @@ -154,7 +154,7 @@ That already made a big difference. Now when we run the compiler, we only have the errors mentioning `Unpin`. Although there are three of them, notice that each is very similar in its contents. - + -```rust +```rust,ignore {{#rustdoc_include ../listings/ch17-async-await/listing-17-27/src/main.rs:declaration}} ``` diff --git a/src/ch17-05-streams.md b/src/ch17-05-streams.md index 49b69f6246..8a44d37059 100644 --- a/src/ch17-05-streams.md +++ b/src/ch17-05-streams.md @@ -21,7 +21,7 @@ method, and then awaiting the output, as in Listing 17-29. -```rust,does_not_compile +```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch17-async-await/listing-17-29/src/main.rs:stream}} ``` @@ -112,32 +112,6 @@ press time! --> but there *is* a very common definition used throughout the ecosystem. Let’s review the definitions of the `Iterator` and `Future` traits, so we can build up to how a `Stream` trait that merges them together might look. -The `Iterator` trait defines an associated type `Item` and a function `next`, -which produces `Some(Item)` until the underlying iterator is empty, and then -produces `None`. - - - -```rust -trait Iterator { - type Item; - - fn next(&mut self) -> Option; -} -``` - -The `Future` trait defines an associated item `Output` and a function `poll`, -which produces `Poll::Pending` while waiting and then `Poll::Ready(Output)` once -the future is ready. - -```rust -trait Future { - type Output; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll; -} -``` - From `Iterator`, we have the idea of a sequence: its `next` method provides an `Option`. From `Future`, we have the idea of readiness over time: its `poll` method provides a `Poll`. To represent a sequence of @@ -145,6 +119,9 @@ items which become ready over time, we define a `Stream` trait which has all of those features put together: ```rust +use std::pin::Pin; +use std::task::{Context, Poll}; + trait Stream { type Item; @@ -178,26 +155,25 @@ is much nicer, though, so the `StreamExt` trait supplies the `next` method so we can do just that. ```rust -trait StreamExt { - async fn next(&mut self) -> Option; -} +{{#rustdoc_include ../listings/ch17-async-await/no-listing-stream-ext/src/lib.rs:here}} ``` > Note: The actual definition we will use looks slightly different than this, > because it supports versions of Rust which did not yet support using async > functions in traits. As a result, it looks like this: > -> ```rust +> ```rust,ignore > fn next(&mut self) -> Next<'_, Self> where Self: Unpin; > ``` > -> That `Next` type is just a simple `struct` which implements `Future`, so that -> `.await` can work with this! +> That `Next` type is just a simple `struct` which implements `Future` and gives +> a way to name the lifetime of the reference to `self` with `Next<'_, Self>`, +> so that `.await` can work with this! The `StreamExt` trait is also the home of all the interesting methods available to use with streams. `StreamExt` is automatically implemented for every type @@ -232,7 +208,7 @@ print all the messages from the stream. ```rust -{{#rustdoc_include ../listings/ch17-async-await/listing-17-32/src/main.rs}} +{{#rustdoc_include ../listings/ch17-async-await/listing-17-32/src/main.rs:all}} ``` @@ -389,9 +365,9 @@ Then we merge the `messages` and `intervals` streams with the `merge` method. Finally, we loop over that combined stream instead of over `messages` (Listing 17-36). -+ -```rust +```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch17-async-await/listing-17-36/src/main.rs:main}} ``` @@ -417,9 +393,11 @@ out with `Duration::from_secs(10)`. Finally, we need to make `merged` both mutable, so that the `while let` loop’s `next` calls can iterate through the stream, and pinned, so that it is safe to do so. + + -```rust +```rust,ignore {{#rustdoc_include ../listings/ch17-async-await/listing-17-37/src/main.rs:main}} ``` @@ -530,4 +508,4 @@ That is a good note to turn to our final section and wrap up this walk through async in Rust, by discussing how futures (including streams), tasks, and threads relate to each other, and how you can use them together. -[17-02-messages]: /ch17-02-concurrency-with-async.md#message-passing +[17-02-messages]: ch17-02-concurrency-with-async.md#message-passing diff --git a/src/ch17-06-futures-tasks-threads.md b/src/ch17-06-futures-tasks-threads.md index 5a65ab66af..7ee933c4f7 100644 --- a/src/ch17-06-futures-tasks-threads.md +++ b/src/ch17-06-futures-tasks-threads.md @@ -29,7 +29,7 @@ could do the exact same thing with a thread! In Listing 17-39, we used the `thread::spawn` and `thread::sleep` APIs from the standard library in the `get_intervals` function. -+ ```rust {{#rustdoc_include ../listings/ch17-async-await/listing-17-40/src/main.rs:threads}} @@ -74,7 +74,7 @@ up correctly. These limitations make threads harder to compose than futures. It is much more difficult, for example, to build something like the `timeout` we built in [“Building Our Own Async Abstractions”][combining-futures], or the `throttle` -method we used with streams in [“Working With Streams”][streams]. The fact that +method we used with streams in [“Composing Streams”][streams]. The fact that futures are richer data structures means they *can* be composed together more naturally, as we have seen. @@ -116,5 +116,5 @@ as your Rust programs get bigger. In addition, we’ll discuss how Rust’s idio relate to those you might be familiar with from object-oriented programming. -[combining-futures]: /ch17-04-more-ways-of-combining-futures.md#building-our-own-async-abstractions -[streams]: /ch17-05-streams.md#working-with-streams +[combining-futures]: ch17-04-more-ways-of-combining-futures.md#building-our-own-async-abstractions +[streams]: ch17-05-streams.md#composing-streams diff --git a/src/ch18-02-trait-objects.md b/src/ch18-02-trait-objects.md index 2d3fea24a1..c6a90ab2ff 100644 --- a/src/ch18-02-trait-objects.md +++ b/src/ch18-02-trait-objects.md @@ -253,4 +253,4 @@ support in Listing 17-9, so it’s a trade-off to consider. [performance-of-code-using-generics]: ch10-01-syntax.html#performance-of-code-using-generics -[dynamically-sized]: ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait +[dynamically-sized]: ch20-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait diff --git a/src/ch18-03-oo-design-patterns.md b/src/ch18-03-oo-design-patterns.md index 13503ef02b..6044841631 100644 --- a/src/ch18-03-oo-design-patterns.md +++ b/src/ch18-03-oo-design-patterns.md @@ -513,4 +513,4 @@ lots of flexibility. We’ve looked at them briefly throughout the book but haven’t seen their full capability yet. Let’s go! [more-info-than-rustc]: ch09-03-to-panic-or-not-to-panic.html#cases-in-which-you-have-more-information-than-the-compiler -[macros]: ch19-06-macros.html#macros +[macros]: ch20-06-macros.html#macros diff --git a/src/ch19-01-all-the-places-for-patterns.md b/src/ch19-01-all-the-places-for-patterns.md index 58f5af573b..e6f9ebd2cb 100644 --- a/src/ch19-01-all-the-places-for-patterns.md +++ b/src/ch19-01-all-the-places-for-patterns.md @@ -247,4 +247,4 @@ be irrefutable; in other circumstances, they can be refutable. We’ll discuss these two concepts next. [ignoring-values-in-a-pattern]: -ch18-03-pattern-syntax.html#ignoring-values-in-a-pattern +ch19-03-pattern-syntax.html#ignoring-values-in-a-pattern diff --git a/src/ch20-03-advanced-traits.md b/src/ch20-03-advanced-traits.md index 789591357b..a32b0e1da7 100644 --- a/src/ch20-03-advanced-traits.md +++ b/src/ch20-03-advanced-traits.md @@ -459,7 +459,7 @@ behavior—we would have to implement just the methods we do want manually. This newtype pattern is also useful even when traits are not involved. Let’s switch focus and look at some advanced ways to interact with Rust’s type system. -[newtype]: ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types +[newtype]: ch20-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types [implementing-a-trait-on-a-type]: ch10-02-traits.html#implementing-a-trait-on-a-type [traits-defining-shared-behavior]: diff --git a/src/ch20-04-advanced-types.md b/src/ch20-04-advanced-types.md index 2dfed23cca..427c108aa4 100644 --- a/src/ch20-04-advanced-types.md +++ b/src/ch20-04-advanced-types.md @@ -288,10 +288,10 @@ pointer. In this case, we’ve chosen a reference. Next, we’ll talk about functions and closures! [encapsulation-that-hides-implementation-details]: -ch17-01-what-is-oo.html#encapsulation-that-hides-implementation-details +ch18-01-what-is-oo.html#encapsulation-that-hides-implementation-details [string-slices]: ch04-03-slices.html#string-slices [the-match-control-flow-operator]: ch06-02-match.html#the-match-control-flow-operator [using-trait-objects-that-allow-for-values-of-different-types]: -ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types -[using-the-newtype-pattern]: ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types +ch18-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types +[using-the-newtype-pattern]: ch20-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types diff --git a/src/ch20-05-advanced-functions-and-closures.md b/src/ch20-05-advanced-functions-and-closures.md index 88c46847d5..d0de1dfd0a 100644 --- a/src/ch20-05-advanced-functions-and-closures.md +++ b/src/ch20-05-advanced-functions-and-closures.md @@ -119,12 +119,12 @@ We can use a trait object: This code will compile just fine. For more about trait objects, refer to the section [“Using Trait Objects That Allow for Values of Different Types”][using-trait-objects-that-allow-for-values-of-different-types] in Chapter 17. +ignore --> in Chapter 18. Next, let’s look at macros! [advanced-traits]: -ch19-03-advanced-traits.html#advanced-traits +ch20-03-advanced-traits.html#advanced-traits [enum-values]: ch06-01-defining-an-enum.html#enum-values [using-trait-objects-that-allow-for-values-of-different-types]: -ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types +ch18-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types diff --git a/src/ch21-02-multithreaded.md b/src/ch21-02-multithreaded.md index cec2272db5..82d6fdf8f6 100644 --- a/src/ch21-02-multithreaded.md +++ b/src/ch21-02-multithreaded.md @@ -690,7 +690,7 @@ the associated block. In Listing 20-21, the lock remains held for the duration of the call to `job()`, meaning other workers cannot receive jobs. [creating-type-synonyms-with-type-aliases]: -ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases +ch20-04-advanced-types.html#creating-type-synonyms-with-type-aliases [integer-types]: ch03-02-data-types.html#integer-types [fn-traits]: ch13-01-closures.html#moving-captured-values-out-of-the-closure-and-the-fn-traits diff --git a/src/img/trp17-02.svg b/src/img/trp17-02.svg new file mode 100644 index 0000000000..1ad970d72c --- /dev/null +++ b/src/img/trp17-02.svg @@ -0,0 +1 @@ +Task 1Task 2ParallelA1A2A3A4B1B2B3 \ No newline at end of file diff --git a/src/img/trpl17-01.svg b/src/img/trpl17-01.svg new file mode 100644 index 0000000000..c99989ec31 --- /dev/null +++ b/src/img/trpl17-01.svg @@ -0,0 +1,110 @@ + + + + + + + + +cluster_task_a + +Task A + + +cluster_task_b + +Task B + + + +A1 + +A1 + + + +A2 + +A2 + + + + +B1 + +B1 + + + +A1->B1 + + + + + +A3 + +A3 + + + + +B2 + +B2 + + + +A2->B2 + + + + + +A4 + +A4 + + + + +A3->A4 + + + + + + + + +B3 + +B3 + + + +A4->B3 + + + + + + + +B1->A2 + + + + + + +B2->A3 + + + + + + diff --git a/src/img/trpl17-02.svg b/src/img/trpl17-02.svg new file mode 100644 index 0000000000..1ddb5d4cad --- /dev/null +++ b/src/img/trpl17-02.svg @@ -0,0 +1,94 @@ + + + + + + + + +cluster_ColleagueA + +Task 1 + + +cluster_ColleagueB + +Task 2 + + + +A1 + +A1 + + + +A2 + +A2 + + + +A1->A2 + + + + + +A3 + +A3 + + + +A2->A3 + + + + + +A4 + +A4 + + + +A3->A4 + + + + + +B1 + +B1 + + + +B2 + +B2 + + + +B1->B2 + + + + + +B3 + +B3 + + + +B2->B3 + + + + + diff --git a/tools/generate-preview.sh b/tools/generate-preview.sh new file mode 100755 index 0000000000..a5d9022b9e --- /dev/null +++ b/tools/generate-preview.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +mdbook build +cp ./tools/preview-robots.txt ./book/robots.txt +ghp-import -m "rebuild GitHub Pages from generated-book" book +git push origin gh-pages diff --git a/tools/preview-robots.txt b/tools/preview-robots.txt new file mode 100644 index 0000000000..1f53798bb4 --- /dev/null +++ b/tools/preview-robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: /