diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d793ad..c0c2aac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,10 @@ jobs: with: fetch-depth: 0 - uses: actions-rust-lang/setup-rust-toolchain@v1 - - name: Install plugin + - name: Install exercise plugin run: cargo install --path helpers/mdbook-exercise-linker + - name: Install link shortener plugin + run: cargo install --path helpers/mdbook-link-shortener - name: Install mdbook-pandoc and related dependencies run: | cargo install mdbook-pandoc --locked --version 0.7.1 @@ -63,6 +65,8 @@ jobs: with: tool: mdbook - name: Build book + env: + LINK_SHORTENER_VERIFY: "true" run: | cd book mdbook build @@ -82,11 +86,25 @@ jobs: # When you support multiple formats, the output directory changes # to include the format in its path. path: book/book/html - # Upload the PDF book as an artifact + - uses: actions/upload-artifact@v4 + with: + name: online-pdf + path: book/book/pandoc/pdf/100-exercises-to-learn-rust.pdf - uses: actions/upload-artifact@v4 with: name: paperback - path: book/book/pandoc/pdf/100-exercises-to-learn-rust.pdf + path: book/book/pandoc/paperback/100-exercises-to-learn-rust.pdf + + is_fresh: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: sudo apt-get update && sudo apt-get install -y jq + - run: | + ./helpers/json2redirects.sh book/link2alias.json > site/redirects + # Verify nothing has changed, meaning that the redirect file is up-to-date + - run: | + git diff --exit-code site/redirects formatter: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 2bf677f..d560369 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,6 +163,15 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -280,6 +289,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -304,6 +314,18 @@ dependencies = [ "clap", ] +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap_lex" version = "0.7.2" @@ -448,6 +470,12 @@ dependencies = [ name = "drop" version = "0.1.0" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "elasticlunr-rs" version = "3.0.2" @@ -667,6 +695,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -771,6 +808,12 @@ dependencies = [ name = "heap" version = "0.1.0" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1037,6 +1080,15 @@ dependencies = [ "ticket_fields", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1182,7 +1234,7 @@ dependencies = [ "once_cell", "opener", "pathdiff", - "pulldown-cmark", + "pulldown-cmark 0.10.3", "regex", "serde", "serde_json", @@ -1206,6 +1258,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "mdbook-link-shortener" +version = "0.1.0" +dependencies = [ + "anyhow", + "bimap", + "clap", + "itertools", + "mdbook", + "pulldown-cmark 0.11.0", + "pulldown-cmark-to-cmark", + "semver", + "serde_json", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1615,7 +1682,20 @@ checksum = "76979bea66e7875e7509c4ec5300112b316af87fa7a252ca91c448b32dfe3993" dependencies = [ "bitflags 2.6.0", "memchr", - "pulldown-cmark-escape", + "pulldown-cmark-escape 0.10.1", + "unicase", +] + +[[package]] +name = "pulldown-cmark" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8746739f11d39ce5ad5c2520a9b75285310dbfe78c541ccf832d38615765aec0" +dependencies = [ + "bitflags 2.6.0", + "getopts", + "memchr", + "pulldown-cmark-escape 0.11.0", "unicase", ] @@ -1625,6 +1705,21 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3" +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "15.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c77db841443d89a57ae94f22d29c022f6d9f41b00bddbf1f4024dbaf4bdce1" +dependencies = [ + "pulldown-cmark 0.11.0", +] + [[package]] name = "quote" version = "1.0.36" @@ -2290,6 +2385,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + [[package]] name = "unwrap" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index ef73559..52e8a87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,11 @@ [workspace] -members = ["exercises/*/*", "helpers/common", "helpers/mdbook-exercise-linker", "helpers/ticket_fields"] +members = [ + "exercises/*/*", + "helpers/common", + "helpers/mdbook-exercise-linker", + "helpers/mdbook-link-shortener", + "helpers/ticket_fields", +] resolver = "2" # This is needed to guarantee the expected behaviour on that specific exercise, diff --git a/book/book.toml b/book/book.toml index 10b0188..3580ba5 100644 --- a/book/book.toml +++ b/book/book.toml @@ -39,8 +39,44 @@ header-includes = [ "\\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\},fontsize=\\small}", ] +[output.pandoc.profile.paperback] +output-file = "100-exercises-to-learn-rust.pdf" +to = "latex" +highlight-style = "monochrome" +# We use `lualatext` because, right now, it's the only engine +# that supports fallback fonts, which we need for emojis. +pdf-engine = "lualatex" + +[output.pandoc.profile.paperback.variables] +subtitle = "A hands-on course by Mainmatter" +# You can get these fonts here: https://fonts.google.com/selection?query=noto+color+ +mainfont = "Noto Serif" +sansfont = "Noto Sans" +monofont = "Noto Sans Mono" +mainfontfallback = ["Noto Color Emoji:mode=harf"] +sansfontfallback = ["Noto Color Emoji:mode=harf"] +monofontfallback = [ + "Noto Color Emoji:mode=harf", +] +urlstyle = "rm" +documentclass = "book" +fontsize = "11pt" +geometry = "papersize={8in,10in},top=2cm,bottom=2cm,left=2.4cm,right=2.4cm" +header-includes = [ + # Reduce font size of code blocks + "\\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\},fontsize=\\small}", +] +links-as-notes = true + [output.html] git-repository-url = "https://github.com/mainmatter/100-exercises-to-learn-rust" [preprocessor.exercise-linker] exercise_root_url = "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises" + +[preprocessor.link-shortener] +base_url = "https://ruex.io" +renderers = ["pandoc"] +mapping = "link2alias.json" +verify = false +after = ["exercise-linker"] diff --git a/book/link2alias.json b/book/link2alias.json new file mode 100644 index 0000000..c2f3c3b --- /dev/null +++ b/book/link2alias.json @@ -0,0 +1,189 @@ +{ + "https://blog.acolyer.org/2019/05/28/cheri-abi/": "f2u", + "https://crates.io": "f4q", + "https://crates.io/crates/cargo-modules": "f2n", + "https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-types": "ffr", + "https://doc.rust-lang.org/book/title-page.html": "f6t", + "https://doc.rust-lang.org/cargo/reference/cargo-targets.html#cargo-targets": "f4m", + "https://doc.rust-lang.org/cargo/reference/profiles.html": "ffc", + "https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html": "f45", + "https://doc.rust-lang.org/nomicon/": "f6u", + "https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast": "f2z", + "https://doc.rust-lang.org/reference/items/implementations.html#trait-implementation-coherence": "fzf", + "https://doc.rust-lang.org/reference/lifetime-elision.html": "f4c", + "https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html": "fxy", + "https://doc.rust-lang.org/std/cmp/index.html": "fzm", + "https://doc.rust-lang.org/std/cmp/trait.PartialEq.html": "fzz", + "https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html": "fzb", + "https://doc.rust-lang.org/std/convert/trait.From.html#implementors": "fzp", + "https://doc.rust-lang.org/std/convert/trait.Into.html#implementors": "fzl", + "https://doc.rust-lang.org/std/iter/trait.FusedIterator.html": "f4s", + "https://doc.rust-lang.org/std/iter/trait.Iterator.html": "fxf", + "https://doc.rust-lang.org/std/keyword.for.html": "ffj", + "https://doc.rust-lang.org/std/keyword.while.html": "ffh", + "https://doc.rust-lang.org/std/macro.panic.html": "ffl", + "https://doc.rust-lang.org/std/mem/fn.size_of.html": "f27", + "https://doc.rust-lang.org/std/ops/index.html": "fzn", + "https://doc.rust-lang.org/std/ops/trait.Add.html": "fz4", + "https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coercion": "fzt", + "https://doc.rust-lang.org/std/ops/trait.Div.html": "fzv", + "https://doc.rust-lang.org/std/ops/trait.Mul.html": "fz6", + "https://doc.rust-lang.org/std/ops/trait.Rem.html": "fz8", + "https://doc.rust-lang.org/std/ops/trait.Sub.html": "fzx", + "https://doc.rust-lang.org/std/prelude/index.html": "f2c", + "https://doc.rust-lang.org/std/primitive.i32.html#associatedconstant.MAX": "ffe", + "https://doc.rust-lang.org/std/primitive.i32.html#associatedconstant.MIN": "ff7", + "https://doc.rust-lang.org/std/primitive.u32.html#associatedconstant.MAX": "ffw", + "https://doc.rust-lang.org/std/slice/struct.Iter.html": "f4d", + "https://doc.rust-lang.org/std/string/struct.String.html": "f26", + "https://doc.rust-lang.org/std/sync/atomic/index.html": "fxh", + "https://doc.rust-lang.org/std/vec/struct.Vec.html#method.iter": "f4j", + "https://docs.rs/dhat/latest/dhat/": "f2y", + "https://docs.rs/itertools/": "fx2", + "https://docs.rs/thiserror/latest/thiserror/": "f4n", + "https://docs.rs/tokio-stream/latest/tokio_stream/": "f65", + "https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html#method.merge": "f6m", + "https://docs.rs/tokio-util/latest/tokio_util/sync/struct.CancellationToken.html": "f63", + "https://docs.rs/tokio/latest/tokio/task/struct.JoinError.html": "f6z", + "https://docs.rust-embedded.org/book/": "f6k", + "https://en.wikipedia.org/wiki/Dangling_pointer": "f2h", + "https://en.wikipedia.org/wiki/Data_segment": "fx7", + "https://en.wikipedia.org/wiki/Memory_address": "f2r", + "https://en.wikipedia.org/wiki/Stack_overflow": "f2e", + "https://en.wikipedia.org/wiki/Two%27s_complement": "ff9", + "https://en.wikipedia.org/wiki/UTF-8": "f2v", + "https://exercism.io": "f6r", + "https://github.com/LukeMathWalker/cargo-chef": "ffb", + "https://github.com/LukeMathWalker/wiremock-rs": "ffm", + "https://github.com/dtolnay/cargo-expand": "fzq", + "https://github.com/dtolnay/proc-macro-workshop": "fzw", + "https://github.com/mainmatter/100-exercises-to-learn-rust": "ff6", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/01_intro/00_welcome": "ff3", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/01_intro/01_syntax": "ffq", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/00_intro": "ff5", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/01_integers": "fft", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/02_variables": "ffy", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/03_if_else": "ffu", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/04_panics": "ffk", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/05_factorial": "ffs", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/06_while": "ffg", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/07_for": "ffd", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/08_overflow": "f2f", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/09_saturating": "f22", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/10_as_casting": "f24", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/00_intro": "f2x", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/01_struct": "f28", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/02_validation": "f2b", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/03_modules": "f2m", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/04_visibility": "f23", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/05_encapsulation": "f2q", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/06_ownership": "f25", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/07_setters": "f2w", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/08_stack": "f29", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/09_heap": "f2p", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/10_references_in_memory": "f2l", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/11_destructor": "f2g", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/12_outro": "f2j", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/00_intro": "f2d", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/01_trait": "f2a", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/02_orphan_rule": "fz2", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/03_operator_overloading": "fz3", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/04_derive": "fz7", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/05_trait_bounds": "fz9", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/06_str_slice": "fzr", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/07_deref": "fzy", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/08_sized": "fzu", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/09_from": "fzk", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/10_assoc_vs_generic": "fzs", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/11_clone": "fzh", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/12_copy": "fzg", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/13_drop": "fzj", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/14_outro": "fzc", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/00_intro": "fza", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/01_enum": "f4f", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/02_match": "f42", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/03_variants_with_data": "f4z", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/04_if_let": "f44", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/05_nullability": "f4x", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/06_fallibility": "f46", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/07_unwrap": "f4v", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/08_error_enums": "f48", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/09_error_trait": "f4b", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/10_packages": "f43", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/11_dependencies": "f4w", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/12_thiserror": "f47", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/13_try_from": "f4e", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/14_source": "f49", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/15_outro": "f4y", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/00_intro": "f4u", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/01_arrays": "f4p", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/02_vec": "f4l", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/03_resizing": "f4k", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/04_iterators": "f4h", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/05_iter": "f4g", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/06_lifetimes": "f4a", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/07_combinators": "fxz", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/08_impl_trait": "fx4", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/09_impl_trait_2": "fxx", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/10_slices": "fx6", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/11_mutable_slices": "fxv", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/12_two_states": "fx8", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/13_index": "fxb", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/14_index_mut": "fxn", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/15_hashmap": "fxm", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/16_btreemap": "fx3", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/00_intro": "fxq", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/01_threads": "fxw", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/02_static": "fxe", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/03_leak": "fx9", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/04_scoped_threads": "fxr", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/05_channels": "fxt", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/06_interior_mutability": "fxu", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/07_ack": "fxp", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/08_client": "fxl", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/09_bounded": "fxk", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/10_patch": "fxs", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/11_locks": "fxj", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/12_rw_lock": "fxd", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/13_without_channels": "fxc", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/14_sync": "fxa", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/00_intro": "f6f", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/01_async_fn": "f62", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/02_spawn": "f64", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/03_runtime": "f6x", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/04_future": "f66", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/05_blocking": "f68", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/06_async_aware_primitives": "f6b", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/07_cancellation": "f6q", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/08_outro": "f6e", + "https://github.com/mainmatter/100-exercises-to-learn-rust/tree/solutions": "ffz", + "https://github.com/mainmatter/rust-advanced-testing-workshop": "fzd", + "https://github.com/rust-lang/rustlings": "f69", + "https://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/": "ffa", + "https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/": "f4r", + "https://mainmatter.com/contact/": "ff2", + "https://mainmatter.com/rust-consulting/": "fff", + "https://marabos.nl/atomics/": "fxg", + "https://nostarch.com/rust-rustaceans": "f6p", + "https://owasp.org/www-community/vulnerabilities/Doubly_freeing_memory": "f2k", + "https://owasp.org/www-community/vulnerabilities/Using_freed_memory": "f2s", + "https://pavex.dev": "ffn", + "https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=36e5ddbe3b3f741dfa9f74c956622bac": "ffp", + "https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=afedf7062298ca8f5a248bc551062eaa": "fx5", + "https://rust-exercises.com/100-exercises-to-learn-rust.pdf": "ffx", + "https://rust-exercises.com/100-exercises/": "ff4", + "https://rust-exercises.com/advanced-testing/": "f6s", + "https://rust-exercises.com/telemetry/": "f6h", + "https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case": "fze", + "https://rust-lang.github.io/wg-async/vision/submitted_stories/status_quo/barbara_battles_buffered_streams.html": "f6w", + "https://ryhl.io/blog/async-what-is-blocking/": "f6v", + "https://tokio.rs/tokio/tutorial/select": "f6n", + "https://valgrind.org/docs/manual/dh-manual.html": "f2t", + "https://veykril.github.io/tlborm/": "fz5", + "https://without.boats/blog/the-scoped-task-trilemma/": "f67", + "https://www.lpalmieri.com/": "ffv", + "https://www.lpalmieri.com/posts/2020-12-11-zero-to-production-6-domain-modelling/": "f4t", + "https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/": "f6y", + "https://www.youtube.com/playlist?list=PLqbS7AVVErFirH9armw8yXlE6dacF-A6z": "f6l", + "https://zero2prod.com": "ff8" +} \ No newline at end of file diff --git a/helpers/json2redirects.sh b/helpers/json2redirects.sh new file mode 100755 index 0000000..2b17989 --- /dev/null +++ b/helpers/json2redirects.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Ensure the JSON file is provided as an argument +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +input_file=$1 + +# Use jq to parse the JSON and format the output +jq -r 'to_entries[] | "/" + .value + " " + .key' "$input_file" diff --git a/helpers/mdbook-exercise-linker/src/lib.rs b/helpers/mdbook-exercise-linker/src/lib.rs index 4f7fb1f..38700ae 100644 --- a/helpers/mdbook-exercise-linker/src/lib.rs +++ b/helpers/mdbook-exercise-linker/src/lib.rs @@ -3,7 +3,6 @@ use mdbook::book::Book; use mdbook::preprocess::{Preprocessor, PreprocessorContext}; use mdbook::BookItem; -/// A no-op preprocessor. pub struct ExerciseLinker; impl ExerciseLinker { diff --git a/helpers/mdbook-link-shortener/Cargo.toml b/helpers/mdbook-link-shortener/Cargo.toml new file mode 100644 index 0000000..944aedd --- /dev/null +++ b/helpers/mdbook-link-shortener/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "mdbook-link-shortener" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.86" +bimap = { version = "0.6.3", features = ["serde"] } +clap = { version = "4.5.4", features = ["derive"] } +itertools = "0.13.0" +mdbook = "0.4.40" +pulldown-cmark = "0.11.0" +pulldown-cmark-to-cmark = "15" +semver = "1.0.23" +serde_json = "1.0.117" diff --git a/helpers/mdbook-link-shortener/src/lib.rs b/helpers/mdbook-link-shortener/src/lib.rs new file mode 100644 index 0000000..28bd556 --- /dev/null +++ b/helpers/mdbook-link-shortener/src/lib.rs @@ -0,0 +1,224 @@ +use anyhow::{Context, Error}; +use bimap::BiHashMap; +use itertools::Itertools; +use mdbook::book::{Book, Chapter}; +use mdbook::preprocess::{Preprocessor, PreprocessorContext}; +use mdbook::BookItem; +use std::collections::{BTreeMap, BTreeSet}; +use std::fs::File; +use std::path::PathBuf; +use std::str::FromStr; + +pub struct LinkShortener; + +struct AliasGenerator { + cursors: [usize; 3], +} + +impl AliasGenerator { + const ALPHABET: &'static [u8] = b"f2z4x6v8bnm3q5w7e9rtyuplkshgjdca"; + + fn new() -> AliasGenerator { + AliasGenerator { cursors: [0, 0, 0] } + } + + /// Generate a 4 alphanumeric long alias, starting from "aaaa" and incrementing by one each time + /// until "9999", using only lowercase letters and numbers. + /// We skip ambiguous characters like "0", "o", "1", "l". + fn next(&mut self) -> String { + let mut alias = String::with_capacity(4); + for cursor in &mut self.cursors { + alias.push(Self::ALPHABET[*cursor] as char); + } + + for cursor in self.cursors.iter_mut().rev() { + if *cursor == Self::ALPHABET.len() - 1 { + *cursor = 0; + } else { + *cursor += 1; + break; + } + } + + alias + } + + /// Generate a unique alias that is not already used by the `link2alias` map. + fn next_until_unique(&mut self, link2alias: &BiHashMap) -> String { + let mut alias = self.next(); + while link2alias.contains_right(&alias) { + alias = self.next(); + } + alias + } +} + +impl LinkShortener { + pub fn new() -> LinkShortener { + LinkShortener + } +} + +impl Preprocessor for LinkShortener { + fn name(&self) -> &str { + "link-shortener" + } + + fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result { + let config = ctx + .config + .get_preprocessor(self.name()) + .context("Failed to get preprocessor configuration")?; + let root_url = { + let root_url = config.get("base_url").context("Failed to get `base_url`")?; + root_url + .as_str() + .context("`base_url` is not a string")? + .to_owned() + }; + let mapping = { + let mapping = config.get("mapping").context("Failed to get `mapping`")?; + let mapping = mapping + .as_str() + .context("`mapping` is not a string")? + .to_owned(); + PathBuf::from_str(&mapping).context("Failed to parse `mapping` as a path")? + }; + let mut link2alias = { + match File::open(&mapping) { + Ok(file) => { + serde_json::from_reader(file).context("Failed to parse existing mapping")? + } + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + BiHashMap::new() + } else { + return Err(e).context("Failed to open existing mapping"); + } + } + } + }; + let verify = config + .get("verify") + .context("Failed to get `verify`")? + .as_bool() + .context("`verify` is not a boolean")?; + // Env var overrides config + let verify = std::env::var("LINK_SHORTENER_VERIFY") + .map(|v| v == "true") + .unwrap_or(verify); + + let mut alias_gen = AliasGenerator::new(); + + book.sections.iter_mut().for_each(|i| { + if let BookItem::Chapter(c) = i { + c.content = replace_anchors(c, &root_url, &mut alias_gen, &mut link2alias, verify) + .expect("Error converting links for chapter"); + for i in c.sub_items.iter_mut() { + if let BookItem::Chapter(sub_chapter) = i { + sub_chapter.content = replace_anchors( + sub_chapter, + &root_url, + &mut alias_gen, + &mut link2alias, + verify, + ) + .expect("Error converting links for subchapter"); + } + } + } + }); + + if !verify { + std::fs::create_dir_all(mapping.parent().expect("Mapping file path has no parent"))?; + let mut file = File::create(&mapping).context("Failed to upsert mapping file")?; + let ordered = link2alias.iter().collect::>(); + serde_json::to_writer_pretty(&mut file, &ordered)?; + } + + Ok(book) + } + + fn supports_renderer(&self, _renderer: &str) -> bool { + true + } +} + +fn replace_anchors( + chapter: &mut Chapter, + root_url: &str, + alias_gen: &mut AliasGenerator, + link2alias: &mut BiHashMap, + verify: bool, +) -> Result { + use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; + use pulldown_cmark_to_cmark::cmark; + + let mut buf = String::with_capacity(chapter.content.len()); + + let mut unshortened_links = BTreeSet::new(); + let events = Parser::new_ext(&chapter.content, Options::all()) + .map(|e| { + let Event::Start(Tag::Link { + link_type, + dest_url, + title, + id, + }) = &e + else { + return e; + }; + + match link_type { + LinkType::Autolink + | LinkType::Shortcut + | LinkType::Inline + | LinkType::Reference + | LinkType::Collapsed => { + if dest_url.starts_with("http") { + let alias = if let Some(alias) = link2alias.get_by_left(dest_url.as_ref()) { + alias.to_owned() + } else { + if verify { + unshortened_links.insert(dest_url.to_string()); + return e; + } + let alias = alias_gen.next_until_unique(&link2alias); + alias + }; + link2alias.insert(dest_url.to_string(), alias.clone()); + + Event::Start(Tag::Link { + link_type: link_type.to_owned(), + dest_url: CowStr::from(format!( + "{root_url}/{alias}", + root_url = root_url, + alias = alias + )), + title: title.clone(), + id: id.clone(), + }) + } else { + e + } + } + LinkType::Email + | LinkType::ReferenceUnknown + | LinkType::CollapsedUnknown + | LinkType::ShortcutUnknown => e, + } + }) + .collect_vec(); + + if verify && !unshortened_links.is_empty() { + let unshortened_links = unshortened_links.iter().join(", "); + return Err(anyhow::anyhow!( + "The following links are not shortened: {unshortened_links}\nRun again with `LINK_SHORTENER_VERIFY=false` to update the mapping \ + with the shortened links." + )); + } + + cmark(events.into_iter(), &mut buf) + .map(|_| buf) + .map_err(|err| anyhow::anyhow!("Markdown serialization failed: {err}")) +} diff --git a/helpers/mdbook-link-shortener/src/main.rs b/helpers/mdbook-link-shortener/src/main.rs new file mode 100644 index 0000000..a69d424 --- /dev/null +++ b/helpers/mdbook-link-shortener/src/main.rs @@ -0,0 +1,66 @@ +use clap::Parser; +use mdbook::errors::Error; +use mdbook::preprocess::{CmdPreprocessor, Preprocessor}; +use mdbook_link_shortener::LinkShortener; +use semver::{Version, VersionReq}; +use std::io; +use std::process; + +#[derive(clap::Parser, Debug)] +#[command(version, about)] +pub struct Cli { + #[command(subcommand)] + sub: Option, +} + +#[derive(clap::Parser, Debug)] +pub enum SubCommand { + #[clap(name = "supports")] + Supports(Supports), +} + +#[derive(clap::Parser, Debug)] +pub struct Supports { + #[arg(long)] + renderer: String, +} + +fn main() -> Result<(), anyhow::Error> { + let cli = Cli::parse(); + let preprocessor = LinkShortener::new(); + + if let Some(SubCommand::Supports(Supports { renderer })) = cli.sub { + let code = if preprocessor.supports_renderer(&renderer) { + 0 + } else { + 1 + }; + process::exit(code); + } + + handle_preprocessing(&preprocessor)?; + + Ok(()) +} + +fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<(), Error> { + let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?; + + let book_version = Version::parse(&ctx.mdbook_version)?; + let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?; + + if !version_req.matches(&book_version) { + eprintln!( + "Warning: The {} plugin was built against version {} of mdbook, \ + but we're being called from version {}", + pre.name(), + mdbook::MDBOOK_VERSION, + ctx.mdbook_version + ); + } + + let processed_book = pre.run(&ctx, book)?; + serde_json::to_writer(io::stdout(), &processed_book)?; + + Ok(()) +} diff --git a/site/redirects b/site/redirects new file mode 100644 index 0000000..761e454 --- /dev/null +++ b/site/redirects @@ -0,0 +1,187 @@ +/f2u https://blog.acolyer.org/2019/05/28/cheri-abi/ +/f4q https://crates.io +/f2n https://crates.io/crates/cargo-modules +/ffr https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-types +/f6t https://doc.rust-lang.org/book/title-page.html +/f4m https://doc.rust-lang.org/cargo/reference/cargo-targets.html#cargo-targets +/ffc https://doc.rust-lang.org/cargo/reference/profiles.html +/f45 https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html +/f6u https://doc.rust-lang.org/nomicon/ +/f2z https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast +/fzf https://doc.rust-lang.org/reference/items/implementations.html#trait-implementation-coherence +/f4c https://doc.rust-lang.org/reference/lifetime-elision.html +/fxy https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html +/fzm https://doc.rust-lang.org/std/cmp/index.html +/fzz https://doc.rust-lang.org/std/cmp/trait.PartialEq.html +/fzb https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html +/fzp https://doc.rust-lang.org/std/convert/trait.From.html#implementors +/fzl https://doc.rust-lang.org/std/convert/trait.Into.html#implementors +/f4s https://doc.rust-lang.org/std/iter/trait.FusedIterator.html +/fxf https://doc.rust-lang.org/std/iter/trait.Iterator.html +/ffj https://doc.rust-lang.org/std/keyword.for.html +/ffh https://doc.rust-lang.org/std/keyword.while.html +/ffl https://doc.rust-lang.org/std/macro.panic.html +/f27 https://doc.rust-lang.org/std/mem/fn.size_of.html +/fzn https://doc.rust-lang.org/std/ops/index.html +/fz4 https://doc.rust-lang.org/std/ops/trait.Add.html +/fzt https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coercion +/fzv https://doc.rust-lang.org/std/ops/trait.Div.html +/fz6 https://doc.rust-lang.org/std/ops/trait.Mul.html +/fz8 https://doc.rust-lang.org/std/ops/trait.Rem.html +/fzx https://doc.rust-lang.org/std/ops/trait.Sub.html +/f2c https://doc.rust-lang.org/std/prelude/index.html +/ffe https://doc.rust-lang.org/std/primitive.i32.html#associatedconstant.MAX +/ff7 https://doc.rust-lang.org/std/primitive.i32.html#associatedconstant.MIN +/ffw https://doc.rust-lang.org/std/primitive.u32.html#associatedconstant.MAX +/f4d https://doc.rust-lang.org/std/slice/struct.Iter.html +/f26 https://doc.rust-lang.org/std/string/struct.String.html +/fxh https://doc.rust-lang.org/std/sync/atomic/index.html +/f4j https://doc.rust-lang.org/std/vec/struct.Vec.html#method.iter +/f2y https://docs.rs/dhat/latest/dhat/ +/fx2 https://docs.rs/itertools/ +/f4n https://docs.rs/thiserror/latest/thiserror/ +/f65 https://docs.rs/tokio-stream/latest/tokio_stream/ +/f6m https://docs.rs/tokio-stream/latest/tokio_stream/trait.StreamExt.html#method.merge +/f63 https://docs.rs/tokio-util/latest/tokio_util/sync/struct.CancellationToken.html +/f6z https://docs.rs/tokio/latest/tokio/task/struct.JoinError.html +/f6k https://docs.rust-embedded.org/book/ +/f2h https://en.wikipedia.org/wiki/Dangling_pointer +/fx7 https://en.wikipedia.org/wiki/Data_segment +/f2r https://en.wikipedia.org/wiki/Memory_address +/f2e https://en.wikipedia.org/wiki/Stack_overflow +/ff9 https://en.wikipedia.org/wiki/Two%27s_complement +/f2v https://en.wikipedia.org/wiki/UTF-8 +/f6r https://exercism.io +/ffb https://github.com/LukeMathWalker/cargo-chef +/ffm https://github.com/LukeMathWalker/wiremock-rs +/fzq https://github.com/dtolnay/cargo-expand +/fzw https://github.com/dtolnay/proc-macro-workshop +/ff6 https://github.com/mainmatter/100-exercises-to-learn-rust +/ff3 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/01_intro/00_welcome +/ffq https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/01_intro/01_syntax +/ff5 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/00_intro +/fft https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/01_integers +/ffy https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/02_variables +/ffu https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/03_if_else +/ffk https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/04_panics +/ffs https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/05_factorial +/ffg https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/06_while +/ffd https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/07_for +/f2f https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/08_overflow +/f22 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/09_saturating +/f24 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/02_basic_calculator/10_as_casting +/f2x https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/00_intro +/f28 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/01_struct +/f2b https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/02_validation +/f2m https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/03_modules +/f23 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/04_visibility +/f2q https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/05_encapsulation +/f25 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/06_ownership +/f2w https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/07_setters +/f29 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/08_stack +/f2p https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/09_heap +/f2l https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/10_references_in_memory +/f2g https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/11_destructor +/f2j https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/03_ticket_v1/12_outro +/f2d https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/00_intro +/f2a https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/01_trait +/fz2 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/02_orphan_rule +/fz3 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/03_operator_overloading +/fz7 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/04_derive +/fz9 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/05_trait_bounds +/fzr https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/06_str_slice +/fzy https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/07_deref +/fzu https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/08_sized +/fzk https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/09_from +/fzs https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/10_assoc_vs_generic +/fzh https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/11_clone +/fzg https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/12_copy +/fzj https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/13_drop +/fzc https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/04_traits/14_outro +/fza https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/00_intro +/f4f https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/01_enum +/f42 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/02_match +/f4z https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/03_variants_with_data +/f44 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/04_if_let +/f4x https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/05_nullability +/f46 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/06_fallibility +/f4v https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/07_unwrap +/f48 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/08_error_enums +/f4b https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/09_error_trait +/f43 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/10_packages +/f4w https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/11_dependencies +/f47 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/12_thiserror +/f4e https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/13_try_from +/f49 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/14_source +/f4y https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/05_ticket_v2/15_outro +/f4u https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/00_intro +/f4p https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/01_arrays +/f4l https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/02_vec +/f4k https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/03_resizing +/f4h https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/04_iterators +/f4g https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/05_iter +/f4a https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/06_lifetimes +/fxz https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/07_combinators +/fx4 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/08_impl_trait +/fxx https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/09_impl_trait_2 +/fx6 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/10_slices +/fxv https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/11_mutable_slices +/fx8 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/12_two_states +/fxb https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/13_index +/fxn https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/14_index_mut +/fxm https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/15_hashmap +/fx3 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/06_ticket_management/16_btreemap +/fxq https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/00_intro +/fxw https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/01_threads +/fxe https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/02_static +/fx9 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/03_leak +/fxr https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/04_scoped_threads +/fxt https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/05_channels +/fxu https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/06_interior_mutability +/fxp https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/07_ack +/fxl https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/08_client +/fxk https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/09_bounded +/fxs https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/10_patch +/fxj https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/11_locks +/fxd https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/12_rw_lock +/fxc https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/13_without_channels +/fxa https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/07_threads/14_sync +/f6f https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/00_intro +/f62 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/01_async_fn +/f64 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/02_spawn +/f6x https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/03_runtime +/f66 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/04_future +/f68 https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/05_blocking +/f6b https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/06_async_aware_primitives +/f6q https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/07_cancellation +/f6e https://github.com/mainmatter/100-exercises-to-learn-rust/tree/main/exercises/08_futures/08_outro +/ffz https://github.com/mainmatter/100-exercises-to-learn-rust/tree/solutions +/fzd https://github.com/mainmatter/rust-advanced-testing-workshop +/f69 https://github.com/rust-lang/rustlings +/ffa https://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/ +/f4r https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ +/ff2 https://mainmatter.com/contact/ +/fff https://mainmatter.com/rust-consulting/ +/fxg https://marabos.nl/atomics/ +/f6p https://nostarch.com/rust-rustaceans +/f2k https://owasp.org/www-community/vulnerabilities/Doubly_freeing_memory +/f2s https://owasp.org/www-community/vulnerabilities/Using_freed_memory +/ffn https://pavex.dev +/ffp https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=36e5ddbe3b3f741dfa9f74c956622bac +/fx5 https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=afedf7062298ca8f5a248bc551062eaa +/ffx https://rust-exercises.com/100-exercises-to-learn-rust.pdf +/ff4 https://rust-exercises.com/100-exercises/ +/f6s https://rust-exercises.com/advanced-testing/ +/f6h https://rust-exercises.com/telemetry/ +/fze https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case +/f6w https://rust-lang.github.io/wg-async/vision/submitted_stories/status_quo/barbara_battles_buffered_streams.html +/f6v https://ryhl.io/blog/async-what-is-blocking/ +/f6n https://tokio.rs/tokio/tutorial/select +/f2t https://valgrind.org/docs/manual/dh-manual.html +/fz5 https://veykril.github.io/tlborm/ +/f67 https://without.boats/blog/the-scoped-task-trilemma/ +/ffv https://www.lpalmieri.com/ +/f4t https://www.lpalmieri.com/posts/2020-12-11-zero-to-production-6-domain-modelling/ +/f6y https://www.oreilly.com/library/view/programming-rust-2nd/9781492052586/ +/f6l https://www.youtube.com/playlist?list=PLqbS7AVVErFirH9armw8yXlE6dacF-A6z +/ff8 https://zero2prod.com