Add print-ready PDF (#127)
This commit is contained in:
		
							parent
							
								
									0910f3909f
								
							
						
					
					
						commit
						c9ed60daf5
					
				
							
								
								
									
										24
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @ -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 | ||||
|  | ||||
							
								
								
									
										105
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										105
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -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" | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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"] | ||||
|  | ||||
							
								
								
									
										189
									
								
								book/link2alias.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								book/link2alias.json
									
									
									
									
									
										Normal file
									
								
							| @ -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" | ||||
| } | ||||
							
								
								
									
										12
									
								
								helpers/json2redirects.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										12
									
								
								helpers/json2redirects.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,12 @@ | ||||
| #!/bin/bash | ||||
| 
 | ||||
| # Ensure the JSON file is provided as an argument | ||||
| if [ "$#" -ne 1 ]; then | ||||
|   echo "Usage: $0 <input_json_file>" | ||||
|   exit 1 | ||||
| fi | ||||
| 
 | ||||
| input_file=$1 | ||||
| 
 | ||||
| # Use jq to parse the JSON and format the output | ||||
| jq -r 'to_entries[] | "/" + .value + " " + .key' "$input_file" | ||||
| @ -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 { | ||||
|  | ||||
							
								
								
									
										15
									
								
								helpers/mdbook-link-shortener/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								helpers/mdbook-link-shortener/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -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" | ||||
							
								
								
									
										224
									
								
								helpers/mdbook-link-shortener/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								helpers/mdbook-link-shortener/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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, String>) -> 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<Book, Error> { | ||||
|         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::<BTreeMap<_, _>>(); | ||||
|             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<String, String>, | ||||
|     verify: bool, | ||||
| ) -> Result<String, anyhow::Error> { | ||||
|     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}")) | ||||
| } | ||||
							
								
								
									
										66
									
								
								helpers/mdbook-link-shortener/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								helpers/mdbook-link-shortener/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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<SubCommand>, | ||||
| } | ||||
| 
 | ||||
| #[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(()) | ||||
| } | ||||
							
								
								
									
										187
									
								
								site/redirects
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								site/redirects
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user