├── .cargo └── config.toml ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-CC0 ├── README.md ├── appveyor.yml ├── assets └── graph.png ├── book.toml ├── build.rs ├── ci ├── deploy.sh ├── dictionary.txt ├── install_deps.sh ├── spellcheck.sh └── test_script.sh ├── crates └── web │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── broken.rs │ ├── lib.rs │ ├── links.rs │ ├── paginated.rs │ └── wiki.rs ├── libtest.rmeta ├── src ├── SUMMARY.md ├── about.md ├── algorithms.md ├── algorithms │ ├── randomness.md │ ├── randomness │ │ ├── rand-choose.md │ │ ├── rand-custom.md │ │ ├── rand-dist.md │ │ ├── rand-passwd.md │ │ ├── rand-range.md │ │ └── rand.md │ ├── sorting.md │ └── sorting │ │ ├── sort.md │ │ ├── sort_float.md │ │ └── sort_struct.md ├── cli.md ├── cli │ ├── ansi_terminal.md │ ├── ansi_terminal │ │ └── ansi_term-basic.md │ ├── arguments.md │ └── arguments │ │ └── clap-basic.md ├── compression.md ├── compression │ ├── tar.md │ └── tar │ │ ├── tar-compress.md │ │ ├── tar-decompress.md │ │ └── tar-strip-prefix.md ├── concurrency.md ├── concurrency │ ├── parallel.md │ ├── parallel │ │ ├── rayon-any-all.md │ │ ├── rayon-iter-mut.md │ │ ├── rayon-map-reduce.md │ │ ├── rayon-parallel-search.md │ │ ├── rayon-parallel-sort.md │ │ └── rayon-thumbnails.md │ ├── thread │ │ ├── crossbeam-complex.md │ │ ├── crossbeam-spawn.md │ │ ├── crossbeam-spsc.md │ │ ├── global-mut-state.md │ │ ├── threadpool-fractal.md │ │ └── threadpool-walk.md │ └── threads.md ├── cryptography.md ├── cryptography │ ├── encryption.md │ ├── encryption │ │ └── pbkdf2.md │ ├── hashing.md │ └── hashing │ │ ├── hmac.md │ │ └── sha-digest.md ├── data_structures.md ├── data_structures │ ├── bitfield.md │ └── bitfield │ │ └── bitfield.md ├── database.md ├── database │ ├── postgres.md │ ├── postgres │ │ ├── aggregate_data.md │ │ ├── create_tables.md │ │ └── insert_query_data.md │ ├── sqlite.md │ └── sqlite │ │ ├── initialization.md │ │ ├── insert_select.md │ │ └── transactions.md ├── datetime.md ├── datetime │ ├── duration.md │ ├── duration │ │ ├── checked.md │ │ ├── profile.md │ │ └── timezone.md │ ├── parse.md │ └── parse │ │ ├── current.md │ │ ├── format.md │ │ ├── string.md │ │ └── timestamp.md ├── development_tools.md ├── development_tools │ ├── build_tools.md │ ├── build_tools │ │ ├── cc-bundled-cpp.md │ │ ├── cc-bundled-static.md │ │ └── cc-defines.md │ ├── debugging.md │ ├── debugging │ │ ├── config_log.md │ │ ├── config_log │ │ │ ├── log-custom.md │ │ │ ├── log-env-variable.md │ │ │ ├── log-mod.md │ │ │ └── log-timestamp.md │ │ ├── log.md │ │ └── log │ │ │ ├── log-custom-logger.md │ │ │ ├── log-debug.md │ │ │ ├── log-error.md │ │ │ ├── log-stdout.md │ │ │ └── log-syslog.md │ ├── versioning.md │ └── versioning │ │ ├── semver-command.md │ │ ├── semver-complex.md │ │ ├── semver-increment.md │ │ ├── semver-latest.md │ │ └── semver-prerelease.md ├── encoding.md ├── encoding │ ├── complex.md │ ├── complex │ │ ├── endian-byte.md │ │ ├── json.md │ │ └── toml.md │ ├── csv.md │ ├── csv │ │ ├── delimiter.md │ │ ├── filter.md │ │ ├── invalid.md │ │ ├── read.md │ │ ├── serde-serialize.md │ │ ├── serialize.md │ │ └── transform.md │ ├── string │ │ ├── base64.md │ │ ├── hex.md │ │ ├── percent-encode.md │ │ └── url-encode.md │ └── strings.md ├── errors.md ├── errors │ ├── handle.md │ └── handle │ │ ├── backtrace.md │ │ ├── main.md │ │ └── retain.md ├── file.md ├── file │ ├── dir.md │ ├── dir │ │ ├── duplicate-name.md │ │ ├── find-file.md │ │ ├── ignore-case.md │ │ ├── loops.md │ │ ├── modified.md │ │ ├── png.md │ │ ├── sizes.md │ │ └── skip-dot.md │ ├── read-write.md │ └── read-write │ │ ├── memmap.md │ │ ├── read-file.md │ │ └── same-file.md ├── hardware.md ├── hardware │ ├── processor.md │ └── processor │ │ └── cpu-count.md ├── intro.md ├── links.md ├── main.rs ├── mem.md ├── mem │ ├── global_static.md │ └── global_static │ │ └── lazy-constant.md ├── net.md ├── net │ ├── server.md │ └── server │ │ └── listen-unused.md ├── os.md ├── os │ ├── external.md │ └── external │ │ ├── continuous.md │ │ ├── error-file.md │ │ ├── piped.md │ │ ├── process-output.md │ │ ├── read-env-variable.md │ │ └── send-input.md ├── science.md ├── science │ ├── mathematics.md │ └── mathematics │ │ ├── complex_numbers.md │ │ ├── complex_numbers │ │ ├── add-complex.md │ │ ├── create-complex.md │ │ └── mathematical-functions.md │ │ ├── linear_algebra.md │ │ ├── linear_algebra │ │ ├── add-matrices.md │ │ ├── deserialize-matrix.md │ │ ├── invert-matrix.md │ │ ├── multiply-matrices.md │ │ ├── multiply-scalar-vector-matrix.md │ │ ├── vector-comparison.md │ │ └── vector-norm.md │ │ ├── miscellaneous.md │ │ ├── miscellaneous │ │ └── big-integers.md │ │ ├── statistics.md │ │ ├── statistics │ │ ├── central-tendency.md │ │ └── standard-deviation.md │ │ ├── trigonometry.md │ │ └── trigonometry │ │ ├── latitude-longitude.md │ │ ├── side-length.md │ │ └── tan-sin-cos.md ├── text.md ├── text │ ├── regex.md │ ├── regex │ │ ├── email.md │ │ ├── filter-log.md │ │ ├── hashtags.md │ │ ├── phone.md │ │ └── replace.md │ ├── string_parsing.md │ └── string_parsing │ │ ├── from_str.md │ │ └── graphemes.md ├── web.md └── web │ ├── clients.md │ ├── clients │ ├── api │ │ ├── paginated.md │ │ ├── rate-limited.md │ │ ├── rest-get.md │ │ ├── rest-head.md │ │ └── rest-post.md │ ├── apis.md │ ├── authentication.md │ ├── authentication │ │ └── basic.md │ ├── download.md │ ├── download │ │ ├── basic.md │ │ ├── partial.md │ │ └── post-file.md │ ├── requests.md │ └── requests │ │ ├── get.md │ │ └── header.md │ ├── mime.md │ ├── mime │ ├── filename.md │ ├── request.md │ └── string.md │ ├── scraping.md │ ├── scraping │ ├── broken.md │ ├── extract-links.md │ └── unique.md │ ├── url.md │ └── url │ ├── base.md │ ├── fragment.md │ ├── new.md │ ├── origin.md │ └── parse.md ├── tests └── skeptic.rs ├── theme └── custom.css └── xtask ├── Cargo.toml ├── README.md └── src ├── main.rs ├── mdbook.rs └── tests.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --manifest-path ./xtask/Cargo.toml --" -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | fixes #ISSUE_ID 2 | 3 | :tada: Hi and welcome! Please read the text below and remove it - Thank you! :tada: 4 | 5 | No worries if anything in these lists is unclear. Just submit the PR and ask away! :+1: 6 | 7 | -------------------------- 8 | ### Things to check before submitting a PR 9 | 10 | - [ ] the tests are passing locally with `cargo xtask test all` 11 | - [ ] commits are squashed into one and rebased to latest master 12 | - [ ] PR contains correct "fixes #ISSUE_ID" clause to autoclose the issue on PR merge 13 | - if issue does not exist consider creating it or remove the clause 14 | - [ ] non rendered items are in sorted order (links, reference, identifiers, Cargo.toml) 15 | - [ ] links to docs.rs have wildcard version `https://docs.rs/tar/*/tar/struct.Entry.html` 16 | - [ ] example has standard [error handling](https://rust-lang-nursery.github.io/rust-cookbook/about.html#a-note-about-error-handling) 17 | - [ ] code identifiers in description are in hyperlinked backticks 18 | ```markdown 19 | [`Entry::unpack`]: https://docs.rs/tar/*/tar/struct.Entry.html#method.unpack 20 | ``` 21 | 22 | ### Things to do after submitting PR 23 | - [ ] check if CI is happy with your PR 24 | 25 | Thank you for reading, you may now delete this text! Thank you! :smile: -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | *~ 4 | book/ 5 | *.swp 6 | lines.txt 7 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - stable 5 | - nightly 6 | 7 | os: 8 | - linux 9 | - osx 10 | 11 | env: 12 | global: 13 | - secure: m28oDDxTcaLlbCXv9la/yz0PzafOCDuhOhmHRoc1ELQC0wc3r6HT3a2myrP5ewQQhaxYDzd2XXYDJB3odFV1qLQOp0hFDgNn/w3ctWZpJdLxIJN6dsaPL/azhE2hz7T+SPEoWLwTW1va6bu4wwzSOykt9//RIK0ZoyVMCRSAlMB965iMV2Nkw7SWdQZ8SlskMVk8sB103N5+WTtt6rse54jHnXTpFEq9q0EAXC1R3GBDKEWB7iwb0c++Kw46Fz86ZJJDotiVuxMtsEk0VfT0Yxx665is5Ko6sV4cahbuXqMIqYYEfqpTHNHadHWD1m1i32hW9Rjtt9fFZ+a8m9zfTixPlkfOZvQ94RnD2zqv9qiwFr8oR7t2SsZaB4aqPlJd45DqgnwQ1B0cmrUAsjSB2+1DQDkR4FgKFB/o1c6F6g8imNh+2OwiZXVLwIimXNJQ5xfZeObXFMrEZ0+uj7oxFX49EcwE/SvwsVJHST3/zL5QuQwa9/uVhW/x135/Z2ypVao2xydpow/KL8VwhX9YsOSP5ApffL4OLJ5hE9qwS/SShHGg8AenFqqm/UNFqWDU+C097YaWvG5PEvCVXvOofic65AUTCmwB+h3MSQmZIqz2sb/kwdbtkoRRR6maMgelQmg1JdIfQcKeTJIStIihjk54VENHPVAslz0oV7Ia5Bo= 14 | 15 | matrix: 16 | include: 17 | - rust: stable 18 | os: linux 19 | env: CONTENT_TESTS=1 20 | - rust: stable 21 | os: linux 22 | env: CONTENT_TESTS=1 CONTENT_TESTS_LINKS=1 23 | allow_failures: 24 | - rust: stable 25 | os: linux 26 | env: CONTENT_TESTS=1 CONTENT_TESTS_LINKS=1 27 | - rust: stable 28 | os: osx 29 | 30 | addons: 31 | apt: 32 | packages: 33 | - aspell 34 | - aspell-en 35 | 36 | before_install: 37 | - ./ci/install_deps.sh 38 | - export PATH=$HOME/.cargo/bin:$PATH 39 | 40 | script: ./ci/test_script.sh 41 | 42 | after_success: ./ci/deploy.sh 43 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-cookbook" 3 | version = "1.1.0" 4 | authors = ["Brian Anderson ", "Andrew Gauger "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | publish = false 8 | build = "build.rs" 9 | 10 | [dependencies] 11 | ansi_term = "0.11.0" 12 | approx = "0.3" 13 | base64 = "0.9" 14 | bitflags = "1.0" 15 | byteorder = "1.0" 16 | cc = "1.0" 17 | chrono = "0.4" 18 | clap = "4.5" 19 | crossbeam = "0.5" 20 | crossbeam-channel = "0.3.9" 21 | csv = "1.0" 22 | data-encoding = "2.1.0" 23 | env_logger = "0.11.3" 24 | flate2 = "1.0" 25 | glob = "0.3" 26 | image = "0.20" 27 | lazy_static = "1.0" 28 | log = "0.4" 29 | log4rs = "0.8" 30 | memmap = "0.7" 31 | mime = "0.3" 32 | nalgebra = { version = "0.33", features = ["serde-serialize"] } 33 | ndarray = { version = "0.16", features = ["approx"] } 34 | num = "0.4" 35 | num_cpus = "1.16" 36 | percent-encoding = "2.3" 37 | petgraph = "0.6" 38 | postgres = "0.19" 39 | rand = "0.8" 40 | rand_distr = "0.4" 41 | rayon = "1.10" 42 | regex = "1.11" 43 | reqwest = { version = "0.12", features = ["blocking", "json", "stream"] } 44 | ring = "0.17" 45 | rusqlite = { version = "0.32", features = ["chrono"] } 46 | same-file = "1.0" 47 | select = "0.6" 48 | semver = "1.0" 49 | serde = { version = "1.0", features = ["derive"] } 50 | serde_derive = "1.0" 51 | serde_json = "1.0" 52 | tar = "0.4" 53 | tempfile = "3.14" 54 | thiserror = "2" 55 | threadpool = "1.8" 56 | toml = "0.8" 57 | tokio = { version = "1", features = ["full"] } 58 | unicode-segmentation = "1.2.1" 59 | url = "2.5" 60 | walkdir = "2.5" 61 | 62 | [target.'cfg(target_os = "linux")'.dependencies] 63 | syslog = "5.0" 64 | 65 | [build-dependencies] 66 | skeptic = "0.13" 67 | walkdir = "2.5" 68 | 69 | [dev-dependencies] 70 | skeptic = "0.13" 71 | walkdir = "2.5" 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Rust Cookbook   [![Build Status travis]][travis] 2 | 3 | [Build Status travis]: https://api.travis-ci.com/rust-lang-nursery/rust-cookbook.svg?branch=master 4 | [travis]: https://travis-ci.com/rust-lang-nursery/rust-cookbook 5 | 6 | **[Read it here]**. 7 | 8 | This _Rust Cookbook_ is a collection of simple [Rust] examples that 9 | demonstrate good practices to accomplish common programming tasks, 10 | using the crates of the Rust ecosystem. 11 | 12 | These examples are complete, and suitable for copying directly into 13 | new cargo projects. They are tested and guaranteed to work. 14 | 15 | ## Read it offline 16 | 17 | If you'd like to read it locally: 18 | 19 | ```bash 20 | $ git clone https://github.com/rust-lang-nursery/rust-cookbook 21 | $ cd rust-cookbook 22 | $ cargo install mdbook --vers "0.4.43" 23 | $ mdbook serve --open 24 | ``` 25 | 26 | The output can also be opened from the `book` subdirectory in your web browser. 27 | 28 | ```bash 29 | $ xdg-open ./book/index.html # linux 30 | $ start .\book\index.html # windows 31 | $ open ./book/index.html # mac 32 | ``` 33 | 34 | [Read it here]: https://rust-lang-nursery.github.io/rust-cookbook 35 | [Rust]: https://www.rust-lang.org/ 36 | 37 | ## Contributing 38 | 39 | This project is intended to be easy for new [Rust] programmers to 40 | contribute to, and an easy way to get involved with the Rust 41 | community. It needs and welcomes help. 42 | 43 | For details see [CONTRIBUTING.md] on GitHub. 44 | 45 | [CONTRIBUTING.md]: https://github.com/rust-lang-nursery/rust-cookbook/blob/master/CONTRIBUTING.md 46 | 47 | ## License [![CC0-badge]][CC0-deed] 48 | 49 | Rust Cookbook is licensed under Creative Commons Zero v1.0 Universal License 50 | ([LICENSE-CC0](LICENSE-CC0) or https://creativecommons.org/publicdomain/zero/1.0/legalcode) 51 | 52 | Unless you explicitly state otherwise, any contribution intentionally submitted 53 | for inclusion in Rust Cookbook by you, as defined in the CC0-1.0 license, shall be 54 | [dedicated to the public domain][CC0-deed] and licensed as above, without any additional 55 | terms or conditions. 56 | 57 | [CC0-deed]: https://creativecommons.org/publicdomain/zero/1.0/deed.en 58 | [CC0-badge]: https://mirrors.creativecommons.org/presskit/buttons/80x15/svg/cc-zero.svg 59 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Based on the "trust" template v0.1.1 2 | # https://github.com/japaric/trust/tree/v0.1.1 3 | 4 | environment: 5 | global: 6 | CRATE_NAME: rust-cookbook 7 | 8 | matrix: 9 | # - TARGET: x86_64-pc-windows-gnu 10 | # RUST_VERSION: stable 11 | - TARGET: x86_64-pc-windows-msvc 12 | RUST_VERSION: stable 13 | - TARGET: x86_64-pc-windows-msvc 14 | RUST_VERSION: nightly 15 | 16 | install: 17 | - ps: >- 18 | If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') { 19 | $Env:PATH += ';C:\msys64\mingw64\bin' 20 | } ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') { 21 | $Env:PATH += ';C:\msys64\mingw32\bin' 22 | } 23 | - curl -sSf -o rustup-init.exe https://win.rustup.rs/ 24 | - rustup-init.exe -y --default-host %TARGET% --default-toolchain %RUST_VERSION% 25 | - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin 26 | - rustc -Vv 27 | - cargo -V 28 | 29 | test_script: 30 | - cargo clean 31 | - cargo build 32 | - cargo test 33 | 34 | branches: 35 | only: 36 | # Release tags 37 | - /^v\d+\.\d+\.\d+.*$/ 38 | - master 39 | 40 | notifications: 41 | - provider: Email 42 | on_build_success: false 43 | 44 | # Building is done in the test phase, so we disable Appveyor's build phase. 45 | build: false 46 | -------------------------------------------------------------------------------- /assets/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-lang-nursery/rust-cookbook/5234849973f33364299b6dc1e02d9bc1048a2152/assets/graph.png -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | 2 | [book] 3 | title = "Rust Cookbook" 4 | description = "Collection of useful Rust code examples" 5 | authors = ["Rust Language Community"] 6 | edition = "2018" 7 | multilingual = false 8 | language = "en" 9 | src = "src" 10 | 11 | [output.html] 12 | mathjax-support = false 13 | # theme = "theme" 14 | additional-css = ["theme/custom.css"] 15 | git-repository-url = "https://github.com/rust-lang-nursery/rust-cookbook" 16 | edit-url-template = "https://github.com/rust-lang-nursery/rust-cookbook/edit/master/{path}" 17 | 18 | [output.html.playpen] 19 | editable = false 20 | 21 | [output.html.search] 22 | limit-results = 20 23 | use-boolean-and = true 24 | boost-title = 2 25 | boost-hierarchy = 2 26 | boost-paragraph = 1 27 | expand = true 28 | heading-split-level = 2 29 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use walkdir::WalkDir; 2 | 3 | const REMOVED_TESTS: &[&str] = &[ 4 | "./src/web/clients/requests/header.md", 5 | "./src/web/clients/api/rate-limited.md", 6 | ]; 7 | 8 | fn main() { 9 | let paths = WalkDir::new("./src/").into_iter() 10 | // convert paths to Strings 11 | .map(|p| p.unwrap().path().to_str().unwrap().to_string()) 12 | // only compile markdown files 13 | .filter(|p| p.ends_with(".md")) 14 | .filter(|p| !REMOVED_TESTS.contains(&p.as_ref())) 15 | .collect::>(); 16 | 17 | skeptic::generate_doc_tests(&paths[..]); 18 | } 19 | -------------------------------------------------------------------------------- /ci/install_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit -o nounset 4 | 5 | echo "Running $0" 6 | 7 | if [ -z "${TRAVIS_BRANCH:-}" ]; then 8 | echo "This script may only be run from Travis!" 9 | exit 1 10 | fi 11 | 12 | if [[ "${CONTENT_TESTS:-}" == 1 ]]; then 13 | echo "Installing additional dependencies" 14 | 15 | if [[ "${CONTENT_TESTS_LINKS:-}" == 1 ]]; then 16 | 17 | pyenv install 3.6.0 18 | pyenv local 3.6.0 19 | pip3 install --user link-checker==0.1.0 20 | fi 21 | cargo install mdbook --vers '0.4.43' --debug 22 | fi 23 | 24 | exit 0 25 | -------------------------------------------------------------------------------- /ci/test_script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit -o nounset 4 | 5 | echo "Running $0" 6 | 7 | if [ -z "${TRAVIS_BRANCH:-}" ]; then 8 | echo "This script may only be run from Travis!" 9 | exit 1 10 | fi 11 | 12 | # Returns 1 if program is installed and 0 otherwise 13 | program_installed() { 14 | local return_=1 15 | 16 | type $1 >/dev/null 2>&1 || { local return_=0; } 17 | 18 | echo "$return_" 19 | } 20 | 21 | if [[ "${CONTENT_TESTS:-}" == 1 ]]; then 22 | # Ensure required programs are installed 23 | if [ $(program_installed mdbook) == 0 ]; then 24 | echo "Please install mdbook: cargo install mdbook." 25 | exit 1 26 | fi 27 | 28 | echo "Testing markup and descriptions" 29 | echo "Building site to book/" 30 | mdbook build 31 | echo "Checking spelling" 32 | ./ci/spellcheck.sh list 33 | 34 | if [[ "${CONTENT_TESTS_LINKS:-}" == 1 ]]; then 35 | if [ $(program_installed link-checker) == 0 ]; then 36 | echo "Please install link-checker: 'pip install link-checker'" 37 | exit 1 38 | fi 39 | echo "Checking local links:" 40 | # failing local link test is a hard error as there should be no false possitives 41 | link-checker --no-external ./book/ 42 | 43 | echo "Checking external links:" 44 | # we do not want to fail on false positives 45 | link-checker --no-local ./book/ --ignore '.*api.github.com*.' || true 46 | fi 47 | else 48 | echo "Testing code snippets" 49 | cargo build --verbose 50 | cargo test --verbose 51 | fi 52 | -------------------------------------------------------------------------------- /crates/web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "web" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | anyhow = "1.0.94" 8 | mime = "0.3.17" 9 | regex = "1.11.1" 10 | reqwest = { version = "0.12.9", features = ["blocking", "json", "stream"] } 11 | select = "0.6.0" 12 | serde = { version = "1.0.215", features = ["derive"] } 13 | tempfile = "3.14.0" 14 | thiserror = "2.0.5" 15 | tokio = { version = "1.42.0", features = ["full"] } 16 | url = "2.5.4" 17 | -------------------------------------------------------------------------------- /crates/web/README.md: -------------------------------------------------------------------------------- 1 | To see the test output run 2 | 3 | ```bash 4 | cargo test -- --nocapture 5 | ``` 6 | 7 | To test the wiki module run 8 | 9 | ```bash 10 | cargo test wiki -- --nocapture 11 | ``` 12 | -------------------------------------------------------------------------------- /crates/web/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod broken; 2 | mod paginated; 3 | mod links; 4 | mod wiki; 5 | 6 | #[cfg(test)] 7 | mod tests { 8 | use super::*; 9 | 10 | #[test] 11 | fn test_reverse_dependencies() -> reqwest::Result<()> { 12 | for dep in paginated::ReverseDependencies::of("serde")?.take(5) { 13 | let dependency = dep?; 14 | println!("{} depends on {}", dependency.id, dependency.crate_id); 15 | } 16 | Ok(()) 17 | } 18 | 19 | #[tokio::test] 20 | async fn test_links() -> Result<(), links::LinkError> { 21 | let page_links = links::get_links("https://rust-lang-nursery.github.io/rust-cookbook/").await?; 22 | for link in page_links { 23 | println!("{}", link); 24 | } 25 | Ok(()) 26 | } 27 | 28 | #[tokio::test] 29 | async fn test_broken() -> Result<(), broken::BrokenError> { 30 | let categorized = broken::check("https://rust-lang-nursery.github.io/rust-cookbook/web/scraping.html").await?; 31 | println!("OK: {:?}", categorized.ok); 32 | println!("Broken: {:?}", categorized.broken); 33 | Ok(()) 34 | } 35 | 36 | #[tokio::test] 37 | async fn test_wiki() -> anyhow::Result<()> { 38 | let content = reqwest::get( 39 | "https://en.wikipedia.org/w/index.php?title=Rust_(programming_language)&action=raw", 40 | ) 41 | .await? 42 | .text() 43 | .await?; 44 | 45 | println!("{:#?}", wiki::extract_links(content.as_str())); 46 | 47 | Ok(()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/web/src/links.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | use select::document::Document; 3 | use select::predicate::Name; 4 | 5 | #[derive(Error, Debug)] 6 | pub enum LinkError { 7 | #[error("Reqwest error: {0}")] 8 | ReqError(#[from] reqwest::Error), 9 | #[error("IO error: {0}")] 10 | IoError(#[from] std::io::Error), 11 | } 12 | 13 | pub async fn get_links(page: &str) -> Result>, LinkError> { 14 | let res = reqwest::get(page) 15 | .await? 16 | .text() 17 | .await?; 18 | 19 | let links = Document::from(res.as_str()) 20 | .find(Name("a")) 21 | .filter_map(|node| node.attr("href")) 22 | .into_iter() 23 | .map(|link| Box::::from(link.to_string())) 24 | .collect(); 25 | 26 | Ok(links) 27 | } 28 | 29 | -------------------------------------------------------------------------------- /crates/web/src/wiki.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | use std::borrow::Cow; 3 | use std::collections::HashSet; 4 | use std::sync::LazyLock; 5 | 6 | pub fn extract_links(content: &str) -> HashSet> { 7 | static WIKI_REGEX: LazyLock = LazyLock::new(|| Regex::new( 8 | r"(?x) 9 | \[\[(?P[^\[\]|]*)[^\[\]]*\]\] # internal links 10 | | 11 | (url=|URL\||\[)(?Phttp.*?)[ \|}] # external links 12 | " 13 | ) 14 | .unwrap() 15 | ); 16 | 17 | let links: HashSet<_> = WIKI_REGEX 18 | .captures_iter(content) 19 | .map(|c| match (c.name("internal"), c.name("external")) { 20 | (Some(val), None) => Cow::from(val.as_str()), 21 | (None, Some(val)) => Cow::from(val.as_str()), 22 | _ => unreachable!(), 23 | }) 24 | .collect::>(); 25 | 26 | links 27 | } 28 | -------------------------------------------------------------------------------- /libtest.rmeta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-lang-nursery/rust-cookbook/5234849973f33364299b6dc1e02d9bc1048a2152/libtest.rmeta -------------------------------------------------------------------------------- /src/algorithms.md: -------------------------------------------------------------------------------- 1 | # Algorithms 2 | 3 | | Recipe | Crates | Categories | 4 | |--------|--------|------------| 5 | | [Generate random numbers][ex-rand] | [![rand-badge]][rand] | [![cat-science-badge]][cat-science] | 6 | | [Generate random numbers within a range][ex-rand-range] | [![rand-badge]][rand] | [![cat-science-badge]][cat-science] | 7 | | [Generate random numbers with given distribution][ex-rand-dist] | [![rand-badge]][rand] [![rand_distr-badge]][rand_distr] | [![cat-science-badge]][cat-science] | 8 | | [Generate random values of a custom type][ex-rand-custom] | [![rand-badge]][rand] | [![cat-science-badge]][cat-science] | 9 | | [Create random passwords from a set of alphanumeric characters][ex-rand-passwd] | [![rand-badge]][rand] | [![cat-os-badge]][cat-os] | 10 | | [Create random passwords from a set of user-defined characters][ex-rand-choose] | [![rand-badge]][rand] | [![cat-os-badge]][cat-os] | 11 | | [Sort a Vector of Integers][ex-sort-integers] | [![std-badge]][std] | [![cat-science-badge]][cat-science] | 12 | | [Sort a Vector of Floats][ex-sort-floats] | [![std-badge]][std] | [![cat-science-badge]][cat-science] | 13 | | [Sort a Vector of Structs][ex-sort-structs] | [![std-badge]][std] | [![cat-science-badge]][cat-science] | 14 | 15 | [ex-rand]: algorithms/randomness.html#generate-random-numbers 16 | [ex-rand-range]: algorithms/randomness.html#generate-random-numbers-within-a-range 17 | [ex-rand-dist]: algorithms/randomness.html#generate-random-numbers-with-given-distribution 18 | [ex-rand-custom]: algorithms/randomness.html#generate-random-values-of-a-custom-type 19 | [ex-rand-passwd]: algorithms/randomness.html#create-random-passwords-from-a-set-of-alphanumeric-characters 20 | [ex-rand-choose]: algorithms/randomness.html#create-random-passwords-from-a-set-of-user-defined-characters 21 | [ex-sort-integers]: algorithms/sorting.html#sort-a-vector-of-integers 22 | [ex-sort-floats]: algorithms/sorting.html#sort-a-vector-of-floats 23 | [ex-sort-structs]: algorithms/sorting.html#sort-a-vector-of-structs 24 | 25 | {{#include links.md}} 26 | -------------------------------------------------------------------------------- /src/algorithms/randomness.md: -------------------------------------------------------------------------------- 1 | # Generate Random Values 2 | 3 | {{#include randomness/rand.md}} 4 | 5 | {{#include randomness/rand-range.md}} 6 | 7 | {{#include randomness/rand-dist.md}} 8 | 9 | {{#include randomness/rand-custom.md}} 10 | 11 | {{#include randomness/rand-passwd.md}} 12 | 13 | {{#include randomness/rand-choose.md}} 14 | 15 | {{#include ../links.md}} 16 | -------------------------------------------------------------------------------- /src/algorithms/randomness/rand-choose.md: -------------------------------------------------------------------------------- 1 | ## Create random passwords from a set of user-defined characters 2 | 3 | [![rand-badge]][rand] [![cat-os-badge]][cat-os] 4 | 5 | Randomly generates a string of given length ASCII characters with custom 6 | user-defined bytestring, with [`gen_range`]. 7 | 8 | ```rust,edition2018 9 | fn main() { 10 | use rand::Rng; 11 | const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ 12 | abcdefghijklmnopqrstuvwxyz\ 13 | 0123456789)(*&^%$#@!~"; 14 | const PASSWORD_LEN: usize = 30; 15 | let mut rng = rand::thread_rng(); 16 | 17 | let password: String = (0..PASSWORD_LEN) 18 | .map(|_| { 19 | let idx = rng.gen_range(0..CHARSET.len()); 20 | CHARSET[idx] as char 21 | }) 22 | .collect(); 23 | 24 | println!("{:?}", password); 25 | } 26 | ``` 27 | 28 | [`gen_range`]: https://docs.rs/rand/*/rand/trait.Rng.html#method.gen_range 29 | -------------------------------------------------------------------------------- /src/algorithms/randomness/rand-custom.md: -------------------------------------------------------------------------------- 1 | ## Generate random values of a custom type 2 | 3 | [![rand-badge]][rand] [![cat-science-badge]][cat-science] 4 | 5 | Randomly generates a tuple `(i32, bool, f64)` and variable of user defined type `Point`. 6 | Implements the [`Distribution`] trait on type Point for [`Standard`] in order to allow random generation. 7 | 8 | ```rust,edition2018 9 | use rand::Rng; 10 | use rand::distributions::{Distribution, Standard}; 11 | 12 | #[derive(Debug)] 13 | struct Point { 14 | x: i32, 15 | y: i32, 16 | } 17 | 18 | impl Distribution for Standard { 19 | fn sample(&self, rng: &mut R) -> Point { 20 | let (rand_x, rand_y) = rng.gen(); 21 | Point { 22 | x: rand_x, 23 | y: rand_y, 24 | } 25 | } 26 | } 27 | 28 | fn main() { 29 | let mut rng = rand::thread_rng(); 30 | let rand_tuple = rng.gen::<(i32, bool, f64)>(); 31 | let rand_point: Point = rng.gen(); 32 | println!("Random tuple: {:?}", rand_tuple); 33 | println!("Random Point: {:?}", rand_point); 34 | } 35 | ``` 36 | 37 | [`Distribution`]: https://docs.rs/rand/*/rand/distributions/trait.Distribution.html 38 | [`Standard`]: https://docs.rs/rand/*/rand/distributions/struct.Standard.html 39 | -------------------------------------------------------------------------------- /src/algorithms/randomness/rand-dist.md: -------------------------------------------------------------------------------- 1 | ## Generate random numbers with given distribution 2 | 3 | [![rand_distr-badge]][rand_distr] [![cat-science-badge]][cat-science] 4 | 5 | By default, random numbers in the `rand` crate have 6 | [uniform distribution]. The [`rand_distr`] crate provides 7 | other kinds of distributions. To use them, you instantiate 8 | a distribution, then sample from that distribution using 9 | [`Distribution::sample`] with help of a random-number 10 | generator [`rand::Rng`]. 11 | 12 | The [distributions available are documented here][rand-distributions]. 13 | An example using the [`Normal`] distribution is shown below. 14 | 15 | ```rust,edition2018,ignore 16 | use rand_distr::{Distribution, Normal, NormalError}; 17 | use rand::thread_rng; 18 | 19 | fn main() -> Result<(), NormalError> { 20 | let mut rng = thread_rng(); 21 | let normal = Normal::new(2.0, 3.0)?; 22 | let v = normal.sample(&mut rng); 23 | println!("{} is from a N(2, 9) distribution", v); 24 | Ok(()) 25 | } 26 | ``` 27 | 28 | [`Distribution::sample`]: https://docs.rs/rand/*/rand/distributions/trait.Distribution.html#tymethod.sample 29 | [`Normal`]: https://docs.rs/rand_distr/*/rand_distr/struct.Normal.html 30 | [`rand::Rng`]: https://docs.rs/rand/*/rand/trait.Rng.html 31 | [`rand_distr`]: https://docs.rs/rand_distr/*/rand_distr/index.html 32 | [rand-distributions]: https://docs.rs/rand_distr/*/rand_distr/index.html 33 | 34 | [uniform distribution]: https://en.wikipedia.org/wiki/Uniform_distribution_(continuous) 35 | -------------------------------------------------------------------------------- /src/algorithms/randomness/rand-passwd.md: -------------------------------------------------------------------------------- 1 | ## Create random passwords from a set of alphanumeric characters 2 | 3 | [![rand-badge]][rand] [![cat-os-badge]][cat-os] 4 | 5 | Randomly generates a string of given length ASCII characters in the range `A-Z, 6 | a-z, 0-9`, with [`Alphanumeric`] sample. 7 | 8 | ```rust,edition2018 9 | use rand::{thread_rng, Rng}; 10 | use rand::distributions::Alphanumeric; 11 | 12 | fn main() { 13 | let rand_string: String = thread_rng() 14 | .sample_iter(&Alphanumeric) 15 | .take(30) 16 | .map(char::from) 17 | .collect(); 18 | 19 | println!("{}", rand_string); 20 | } 21 | ``` 22 | 23 | [`Alphanumeric`]: https://docs.rs/rand/*/rand/distributions/struct.Alphanumeric.html 24 | -------------------------------------------------------------------------------- /src/algorithms/randomness/rand-range.md: -------------------------------------------------------------------------------- 1 | ## Generate random numbers within a range 2 | 3 | [![rand-badge]][rand] [![cat-science-badge]][cat-science] 4 | 5 | Generates a random value within half-open `[0, 10)` range (not including `10`) with [`Rng::gen_range`]. 6 | 7 | ```rust,edition2018 8 | use rand::Rng; 9 | 10 | fn main() { 11 | let mut rng = rand::thread_rng(); 12 | println!("Integer: {}", rng.gen_range(0..10)); 13 | println!("Float: {}", rng.gen_range(0.0..10.0)); 14 | } 15 | ``` 16 | 17 | [`Uniform`] can obtain values with [uniform distribution]. 18 | This has the same effect, but may be faster when repeatedly generating numbers 19 | in the same range. 20 | 21 | ```rust,edition2018 22 | 23 | use rand::distributions::{Distribution, Uniform}; 24 | 25 | fn main() { 26 | let mut rng = rand::thread_rng(); 27 | let die = Uniform::from(1..7); 28 | 29 | loop { 30 | let throw = die.sample(&mut rng); 31 | println!("Roll the die: {}", throw); 32 | if throw == 6 { 33 | break; 34 | } 35 | } 36 | } 37 | ``` 38 | 39 | [`Uniform`]: https://docs.rs/rand/*/rand/distributions/uniform/struct.Uniform.html 40 | [`Rng::gen_range`]: https://doc.rust-lang.org/rand/*/rand/trait.Rng.html#method.gen_range 41 | [uniform distribution]: https://en.wikipedia.org/wiki/Uniform_distribution_(continuous) 42 | -------------------------------------------------------------------------------- /src/algorithms/randomness/rand.md: -------------------------------------------------------------------------------- 1 | ## Generate random numbers 2 | 3 | [![rand-badge]][rand] [![cat-science-badge]][cat-science] 4 | 5 | Generates random numbers with help of random-number 6 | generator [`rand::Rng`] obtained via [`rand::thread_rng`]. Each thread has an 7 | initialized generator. Integers are uniformly distributed over the range of the 8 | type, and floating point numbers are uniformly distributed from 0 up to but not 9 | including 1. 10 | 11 | ```rust,edition2018 12 | use rand::Rng; 13 | 14 | fn main() { 15 | let mut rng = rand::thread_rng(); 16 | 17 | let n1: u8 = rng.gen(); 18 | let n2: u16 = rng.gen(); 19 | println!("Random u8: {}", n1); 20 | println!("Random u16: {}", n2); 21 | println!("Random u32: {}", rng.gen::()); 22 | println!("Random i32: {}", rng.gen::()); 23 | println!("Random float: {}", rng.gen::()); 24 | } 25 | ``` 26 | 27 | [`rand::Rng`]: https://docs.rs/rand/*/rand/trait.Rng.html 28 | [`rand::thread_rng`]: https://docs.rs/rand/*/rand/fn.thread_rng.html 29 | -------------------------------------------------------------------------------- /src/algorithms/sorting.md: -------------------------------------------------------------------------------- 1 | # Sorting Vectors 2 | 3 | {{#include sorting/sort.md}} 4 | {{#include sorting/sort_float.md}} 5 | {{#include sorting/sort_struct.md}} 6 | 7 | {{#include ../links.md}} 8 | -------------------------------------------------------------------------------- /src/algorithms/sorting/sort.md: -------------------------------------------------------------------------------- 1 | ## Sort a Vector of Integers 2 | 3 | [![std-badge]][std] [![cat-science-badge]][cat-science] 4 | 5 | This example sorts a Vector of integers via [`vec::sort`]. Alternative would 6 | be to use [`vec::sort_unstable`] which can be faster, but does not preserve 7 | the order of equal elements. 8 | 9 | ```rust,edition2018 10 | fn main() { 11 | let mut vec = vec![1, 5, 10, 2, 15]; 12 | 13 | vec.sort(); 14 | 15 | assert_eq!(vec, vec![1, 2, 5, 10, 15]); 16 | } 17 | ``` 18 | 19 | [`vec::sort`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort 20 | [`vec::sort_unstable`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort_unstable 21 | -------------------------------------------------------------------------------- /src/algorithms/sorting/sort_float.md: -------------------------------------------------------------------------------- 1 | ## Sort a Vector of Floats 2 | 3 | [![std-badge]][std] [![cat-science-badge]][cat-science] 4 | 5 | A Vector of f32 or f64 can be sorted with [`vec::sort_by`] and [`PartialOrd::partial_cmp`]. 6 | 7 | ```rust,edition2018 8 | fn main() { 9 | let mut vec = vec![1.1, 1.15, 5.5, 1.123, 2.0]; 10 | 11 | vec.sort_by(|a, b| a.partial_cmp(b).unwrap()); 12 | 13 | assert_eq!(vec, vec![1.1, 1.123, 1.15, 2.0, 5.5]); 14 | } 15 | ``` 16 | 17 | [`vec::sort_by`]: https://doc.rust-lang.org/std/primitive.slice.html#method.sort_by 18 | [`PartialOrd::partial_cmp`]: https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html#tymethod.partial_cmp 19 | -------------------------------------------------------------------------------- /src/algorithms/sorting/sort_struct.md: -------------------------------------------------------------------------------- 1 | ## Sort a Vector of Structs 2 | 3 | [![std-badge]][std] [![cat-science-badge]][cat-science] 4 | 5 | Sorts a Vector of Person structs with properties `name` and `age` by its natural 6 | order (By name and age). In order to make Person sortable you need four traits [`Eq`], 7 | [`PartialEq`], [`Ord`] and [`PartialOrd`]. These traits can be simply derived. 8 | You can also provide a custom comparator function using a [`vec:sort_by`] method and sort only by age. 9 | 10 | ```rust,edition2018 11 | #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] 12 | struct Person { 13 | name: String, 14 | age: u32 15 | } 16 | 17 | impl Person { 18 | pub fn new(name: &str, age: u32) -> Self { 19 | Person { 20 | name: name.to_string(), 21 | age 22 | } 23 | } 24 | } 25 | 26 | fn main() { 27 | let mut people = vec![ 28 | Person::new("Zoe", 25), 29 | Person::new("Al", 60), 30 | Person::new("John", 1), 31 | ]; 32 | 33 | // Sort people by derived natural order (Name and age) 34 | people.sort(); 35 | 36 | assert_eq!( 37 | people, 38 | vec![ 39 | Person::new("Al", 60), 40 | Person::new("John", 1), 41 | Person::new("Zoe", 25), 42 | ]); 43 | 44 | // Sort people by age 45 | people.sort_by(|a, b| b.age.cmp(&a.age)); 46 | 47 | assert_eq!( 48 | people, 49 | vec![ 50 | Person::new("Al", 60), 51 | Person::new("Zoe", 25), 52 | Person::new("John", 1), 53 | ]); 54 | 55 | } 56 | 57 | ``` 58 | 59 | [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html 60 | [`PartialEq`]: https://doc.rust-lang.org/std/cmp/trait.PartialEq.html 61 | [`Ord`]: https://doc.rust-lang.org/std/cmp/trait.Ord.html 62 | [`PartialOrd`]: https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html 63 | [`vec:sort_by`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort_by 64 | -------------------------------------------------------------------------------- /src/cli.md: -------------------------------------------------------------------------------- 1 | # Command Line 2 | 3 | | Recipe | Crates | Categories | 4 | |--------|--------|------------| 5 | | [Parse command line arguments][ex-clap-basic] | [![clap-badge]][clap] | [![cat-command-line-badge]][cat-command-line] | 6 | | [ANSI Terminal][ex-ansi_term-basic] | [![ansi_term-badge]][ansi_term]| [![cat-command-line-badge]][cat-command-line] | 7 | 8 | [ex-clap-basic]: cli/arguments.html#parse-command-line-arguments 9 | [ex-ansi_term-basic]: cli/ansi_terminal.html#ansi-terminal 10 | 11 | {{#include links.md}} -------------------------------------------------------------------------------- /src/cli/ansi_terminal.md: -------------------------------------------------------------------------------- 1 | # ANSI Terminal 2 | 3 | {{#include ansi_terminal/ansi_term-basic.md}} 4 | 5 | {{#include ../links.md}} -------------------------------------------------------------------------------- /src/cli/arguments.md: -------------------------------------------------------------------------------- 1 | # Clap basic 2 | 3 | {{#include arguments/clap-basic.md}} 4 | 5 | {{#include ../links.md}} 6 | -------------------------------------------------------------------------------- /src/compression.md: -------------------------------------------------------------------------------- 1 | # Compression 2 | 3 | | Recipe | Crates | Categories | 4 | |--------|--------|------------| 5 | | [Decompress a tarball][ex-tar-decompress] | [![flate2-badge]][flate2] [![tar-badge]][tar] | [![cat-compression-badge]][cat-compression] | 6 | | [Compress a directory into a tarball][ex-tar-compress] | [![flate2-badge]][flate2] [![tar-badge]][tar] | [![cat-compression-badge]][cat-compression] | 7 | | [Decompress a tarball while removing a prefix from the paths][ex-tar-strip-prefix] | [![flate2-badge]][flate2] [![tar-badge]][tar] | [![cat-compression-badge]][cat-compression] | 8 | 9 | [ex-tar-decompress]: compression/tar.html#decompress-a-tarball 10 | [ex-tar-compress]: compression/tar.html#compress-a-directory-into-tarball 11 | [ex-tar-strip-prefix]: compression/tar.html#decompress-a-tarball-while-removing-a-prefix-from-the-paths 12 | 13 | {{#include links.md}} 14 | -------------------------------------------------------------------------------- /src/compression/tar.md: -------------------------------------------------------------------------------- 1 | # Working with Tarballs 2 | 3 | {{#include tar/tar-decompress.md}} 4 | 5 | {{#include tar/tar-compress.md}} 6 | 7 | {{#include tar/tar-strip-prefix.md}} 8 | 9 | {{#include ../links.md}} 10 | -------------------------------------------------------------------------------- /src/compression/tar/tar-compress.md: -------------------------------------------------------------------------------- 1 | ## Compress a directory into tarball 2 | 3 | [![flate2-badge]][flate2] [![tar-badge]][tar] [![cat-compression-badge]][cat-compression] 4 | 5 | Compress `/var/log` directory into `archive.tar.gz`. 6 | 7 | Creates a [`File`] wrapped in [`GzEncoder`] 8 | and [`tar::Builder`].
Adds contents of `/var/log` directory recursively into the archive 9 | under `backup/logs` path with [`Builder::append_dir_all`]. 10 | [`GzEncoder`] is responsible for transparently compressing the 11 | data prior to writing it into `archive.tar.gz`. 12 | 13 | ```rust,edition2018,no_run 14 | 15 | use std::fs::File; 16 | use flate2::Compression; 17 | use flate2::write::GzEncoder; 18 | 19 | fn main() -> Result<(), std::io::Error> { 20 | let tar_gz = File::create("archive.tar.gz")?; 21 | let enc = GzEncoder::new(tar_gz, Compression::default()); 22 | let mut tar = tar::Builder::new(enc); 23 | tar.append_dir_all("backup/logs", "/var/log")?; 24 | tar.finish()?; 25 | Ok(()) 26 | } 27 | ``` 28 | 29 | To add the contents without renaming them, an empty string can be used as the first argument of [`Builder::append_dir_all`]: 30 | 31 | ```rust,edition2018,no_run 32 | 33 | use std::fs::File; 34 | use flate2::Compression; 35 | use flate2::write::GzEncoder; 36 | 37 | fn main() -> Result<(), std::io::Error> { 38 | let tar_gz = File::create("archive.tar.gz")?; 39 | let enc = GzEncoder::new(tar_gz, Compression::default()); 40 | let mut tar = tar::Builder::new(enc); 41 | tar.append_dir_all("", "/var/log")?; 42 | tar.finish()?; 43 | Ok(()) 44 | } 45 | ``` 46 | 47 | The default behavior of [`tar::Builder`] differs from the GNU `tar` utility's defaults [tar(1)], 48 | notably [`tar::Builder::follow_symlinks(true)`] is the equivalent of `tar --dereference`. 49 | 50 | [tar(1)]: https://man7.org/linux/man-pages/man1/tar.1.html 51 | [`Builder::append_dir_all`]: https://docs.rs/tar/*/tar/struct.Builder.html#method.append_dir_all 52 | [`File`]: https://doc.rust-lang.org/std/fs/struct.File.html 53 | [`GzEncoder`]: https://docs.rs/flate2/*/flate2/write/struct.GzEncoder.html 54 | [`tar::Builder`]: https://docs.rs/tar/*/tar/struct.Builder.html 55 | [`tar::Builder::follow_symlinks(true)`]: https://docs.rs/tar/latest/tar/struct.Builder.html#method.follow_symlinks 56 | -------------------------------------------------------------------------------- /src/compression/tar/tar-decompress.md: -------------------------------------------------------------------------------- 1 | ## Decompress a tarball 2 | 3 | [![flate2-badge]][flate2] [![tar-badge]][tar] [![cat-compression-badge]][cat-compression] 4 | 5 | Decompress ([`GzDecoder`]) and 6 | extract ([`Archive::unpack`]) all files from a compressed tarball 7 | named `archive.tar.gz` located in the current working directory 8 | to the same location. 9 | 10 | ```rust,edition2018,no_run 11 | 12 | use std::fs::File; 13 | use flate2::read::GzDecoder; 14 | use tar::Archive; 15 | 16 | fn main() -> Result<(), std::io::Error> { 17 | let path = "archive.tar.gz"; 18 | 19 | let tar_gz = File::open(path)?; 20 | let tar = GzDecoder::new(tar_gz); 21 | let mut archive = Archive::new(tar); 22 | archive.unpack(".")?; 23 | 24 | Ok(()) 25 | } 26 | ``` 27 | 28 | [`Archive::unpack`]: https://docs.rs/tar/*/tar/struct.Archive.html#method.unpack 29 | [`GzDecoder`]: https://docs.rs/flate2/*/flate2/read/struct.GzDecoder.html 30 | -------------------------------------------------------------------------------- /src/compression/tar/tar-strip-prefix.md: -------------------------------------------------------------------------------- 1 | ## Decompress a tarball while removing a prefix from the paths 2 | 3 | [![flate2-badge]][flate2] [![tar-badge]][tar] [![cat-compression-badge]][cat-compression] 4 | 5 | Iterate over the [`Archive::entries`]. Use [`Path::strip_prefix`] to remove 6 | the specified path prefix (`bundle/logs`). Finally, extract the [`tar::Entry`] 7 | via [`Entry::unpack`]. 8 | 9 | ```rust,edition2018,no_run 10 | # use error_chain::error_chain; 11 | use std::fs::File; 12 | use std::path::PathBuf; 13 | use flate2::read::GzDecoder; 14 | use tar::Archive; 15 | # 16 | # error_chain! { 17 | # foreign_links { 18 | # Io(std::io::Error); 19 | # StripPrefixError(::std::path::StripPrefixError); 20 | # } 21 | # } 22 | 23 | fn main() -> Result<(), std::io::Error> { 24 | let file = File::open("archive.tar.gz")?; 25 | let mut archive = Archive::new(GzDecoder::new(file)); 26 | let prefix = "bundle/logs"; 27 | 28 | println!("Extracted the following files:"); 29 | archive 30 | .entries()? 31 | .filter_map(|e| e.ok()) 32 | .map(|mut entry| -> Result> { 33 | let path = entry.path()?.strip_prefix(prefix)?.to_owned(); 34 | entry.unpack(&path)?; 35 | Ok(path) 36 | }) 37 | .filter_map(|e| e.ok()) 38 | .for_each(|x| println!("> {}", x.display())); 39 | 40 | Ok(()) 41 | } 42 | ``` 43 | 44 | [`Archive::entries`]: https://docs.rs/tar/*/tar/struct.Archive.html#method.entries 45 | [`Entry::unpack`]: https://docs.rs/tar/*/tar/struct.Entry.html#method.unpack 46 | [`Path::strip_prefix`]: https://doc.rust-lang.org/std/path/struct.Path.html#method.strip_prefix 47 | [`tar::Entry`]: https://docs.rs/tar/*/tar/struct.Entry.html 48 | -------------------------------------------------------------------------------- /src/concurrency/parallel.md: -------------------------------------------------------------------------------- 1 | # Parallel Tasks 2 | 3 | {{#include parallel/rayon-iter-mut.md}} 4 | 5 | {{#include parallel/rayon-any-all.md}} 6 | 7 | {{#include parallel/rayon-parallel-search.md}} 8 | 9 | {{#include parallel/rayon-parallel-sort.md}} 10 | 11 | {{#include parallel/rayon-map-reduce.md}} 12 | 13 | {{#include parallel/rayon-thumbnails.md}} 14 | 15 | {{#include ../links.md}} 16 | -------------------------------------------------------------------------------- /src/concurrency/parallel/rayon-any-all.md: -------------------------------------------------------------------------------- 1 | ## Test in parallel if any or all elements of a collection match a given predicate 2 | 3 | [![rayon-badge]][rayon] [![cat-concurrency-badge]][cat-concurrency] 4 | 5 | This example demonstrates using the [`rayon::any`] and [`rayon::all`] methods, which are parallelized counterparts to [`std::any`] and [`std::all`]. [`rayon::any`] checks in parallel whether any element of the iterator matches the predicate, and returns as soon as one is found. [`rayon::all`] checks in parallel whether all elements of the iterator match the predicate, and returns as soon as a non-matching element is found. 6 | 7 | ```rust,edition2018 8 | use rayon::prelude::*; 9 | 10 | fn main() { 11 | let mut vec = vec![2, 4, 6, 8]; 12 | 13 | assert!(!vec.par_iter().any(|n| (*n % 2) != 0)); 14 | assert!(vec.par_iter().all(|n| (*n % 2) == 0)); 15 | assert!(!vec.par_iter().any(|n| *n > 8 )); 16 | assert!(vec.par_iter().all(|n| *n <= 8 )); 17 | 18 | vec.push(9); 19 | 20 | assert!(vec.par_iter().any(|n| (*n % 2) != 0)); 21 | assert!(!vec.par_iter().all(|n| (*n % 2) == 0)); 22 | assert!(vec.par_iter().any(|n| *n > 8 )); 23 | assert!(!vec.par_iter().all(|n| *n <= 8 )); 24 | } 25 | ``` 26 | 27 | [`rayon::all`]: https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.all 28 | [`rayon::any`]: https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.any 29 | [`std::all`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.all 30 | [`std::any`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.any 31 | -------------------------------------------------------------------------------- /src/concurrency/parallel/rayon-iter-mut.md: -------------------------------------------------------------------------------- 1 | ## Mutate the elements of an array in parallel 2 | 3 | [![rayon-badge]][rayon] [![cat-concurrency-badge]][cat-concurrency] 4 | 5 | The example uses the `rayon` crate, which is a data parallelism library for Rust. 6 | `rayon` provides the [`par_iter_mut`] method for any parallel iterable data type. 7 | This is an iterator-like chain that potentially executes in parallel. 8 | 9 | ```rust,edition2018 10 | use rayon::prelude::*; 11 | 12 | fn main() { 13 | let mut arr = [0, 7, 9, 11]; 14 | arr.par_iter_mut().for_each(|p| *p -= 1); 15 | println!("{:?}", arr); 16 | } 17 | ``` 18 | 19 | [`par_iter_mut`]: https://docs.rs/rayon/*/rayon/iter/trait.IntoParallelRefMutIterator.html#tymethod.par_iter_mut 20 | -------------------------------------------------------------------------------- /src/concurrency/parallel/rayon-map-reduce.md: -------------------------------------------------------------------------------- 1 | ## Map-reduce in parallel 2 | 3 | [![rayon-badge]][rayon] [![cat-concurrency-badge]][cat-concurrency] 4 | 5 | This example uses [`rayon::filter`], [`rayon::map`], and [`rayon::reduce`] 6 | to calculate the average age of `Person` objects whose age is over 30. 7 | 8 | [`rayon::filter`] returns elements from a collection that satisfy the given 9 | predicate. [`rayon::map`] performs an operation on every element, creating a 10 | new iteration, and [`rayon::reduce`] performs an operation given the previous 11 | reduction and the current element. Also shows use of [`rayon::sum`], 12 | which has the same result as the reduce operation in this example. 13 | 14 | ```rust,edition2018 15 | use rayon::prelude::*; 16 | 17 | struct Person { 18 | age: u32, 19 | } 20 | 21 | fn main() { 22 | let v: Vec = vec![ 23 | Person { age: 23 }, 24 | Person { age: 19 }, 25 | Person { age: 42 }, 26 | Person { age: 17 }, 27 | Person { age: 17 }, 28 | Person { age: 31 }, 29 | Person { age: 30 }, 30 | ]; 31 | 32 | let num_over_30 = v.par_iter().filter(|&x| x.age > 30).count() as f32; 33 | let sum_over_30 = v.par_iter() 34 | .map(|x| x.age) 35 | .filter(|&x| x > 30) 36 | .reduce(|| 0, |x, y| x + y); 37 | 38 | let alt_sum_30: u32 = v.par_iter() 39 | .map(|x| x.age) 40 | .filter(|&x| x > 30) 41 | .sum(); 42 | 43 | let avg_over_30 = sum_over_30 as f32 / num_over_30; 44 | let alt_avg_over_30 = alt_sum_30 as f32/ num_over_30; 45 | 46 | assert!((avg_over_30 - alt_avg_over_30).abs() < std::f32::EPSILON); 47 | println!("The average age of people older than 30 is {}", avg_over_30); 48 | } 49 | ``` 50 | 51 | [`rayon::filter`]: https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.filter 52 | [`rayon::map`]: https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.map 53 | [`rayon::reduce`]: https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.reduce 54 | [`rayon::sum`]: https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.sum 55 | -------------------------------------------------------------------------------- /src/concurrency/parallel/rayon-parallel-search.md: -------------------------------------------------------------------------------- 1 | ## Search items using given predicate in parallel 2 | 3 | [![rayon-badge]][rayon] [![cat-concurrency-badge]][cat-concurrency] 4 | 5 | This example uses [`rayon::find_any`] and [`par_iter`] to search a vector in 6 | parallel for an element satisfying the predicate in the given closure. 7 | 8 | If there are multiple elements satisfying the predicate defined in the closure 9 | argument of [`rayon::find_any`], `rayon` returns the first one found, not 10 | necessarily the first one. 11 | 12 | Also note that the argument to the closure is a reference to a reference 13 | (`&&x`). See the discussion on [`std::find`] for additional details. 14 | 15 | ```rust,edition2018 16 | use rayon::prelude::*; 17 | 18 | fn main() { 19 | let v = vec![6, 2, 1, 9, 3, 8, 11]; 20 | 21 | let f1 = v.par_iter().find_any(|&&x| x == 9); 22 | let f2 = v.par_iter().find_any(|&&x| x % 2 == 0 && x > 6); 23 | let f3 = v.par_iter().find_any(|&&x| x > 8); 24 | 25 | assert_eq!(f1, Some(&9)); 26 | assert_eq!(f2, Some(&8)); 27 | assert!(f3 > Some(&8)); 28 | } 29 | ``` 30 | 31 | [`par_iter`]: https://docs.rs/rayon/*/rayon/iter/trait.IntoParallelRefIterator.html#tymethod.par_iter 32 | [`rayon::find_any`]: https://docs.rs/rayon/*/rayon/iter/trait.ParallelIterator.html#method.find_any 33 | [`std::find`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.find 34 | -------------------------------------------------------------------------------- /src/concurrency/parallel/rayon-parallel-sort.md: -------------------------------------------------------------------------------- 1 | ## Sort a vector in parallel 2 | 3 | [![rayon-badge]][rayon] [![rand-badge]][rand] [![cat-concurrency-badge]][cat-concurrency] 4 | 5 | This example will sort in parallel a vector of Strings. 6 | 7 | Allocate a vector of empty Strings. `par_iter_mut().for_each` populates random 8 | values in parallel. Although [multiple options] 9 | exist to sort an enumerable data type, [`par_sort_unstable`] 10 | is usually faster than [stable sorting] algorithms. 11 | 12 | ```rust,edition2018 13 | 14 | use rand::{Rng, thread_rng}; 15 | use rand::distributions::Alphanumeric; 16 | use rayon::prelude::*; 17 | 18 | fn main() { 19 | let mut vec = vec![String::new(); 100_000]; 20 | vec.par_iter_mut().for_each(|p| { 21 | let mut rng = thread_rng(); 22 | *p = (0..5).map(|_| rng.sample(&Alphanumeric) as char).collect() 23 | }); 24 | vec.par_sort_unstable(); 25 | } 26 | ``` 27 | 28 | [`par_sort_unstable`]: https://docs.rs/rayon/*/rayon/slice/trait.ParallelSliceMut.html#method.par_sort_unstable 29 | [multiple options]: https://docs.rs/rayon/*/rayon/slice/trait.ParallelSliceMut.html 30 | [stable sorting]: https://docs.rs/rayon/*/rayon/slice/trait.ParallelSliceMut.html#method.par_sort 31 | -------------------------------------------------------------------------------- /src/concurrency/thread/crossbeam-spawn.md: -------------------------------------------------------------------------------- 1 | ## Spawn a short-lived thread 2 | 3 | [![crossbeam-badge]][crossbeam] [![cat-concurrency-badge]][cat-concurrency] 4 | 5 | The example uses the [crossbeam] crate, which provides data structures and functions 6 | for concurrent and parallel programming. [`Scope::spawn`] spawns a new scoped thread that is guaranteed 7 | to terminate before returning from the closure that passed into [`crossbeam::scope`] function, meaning that 8 | you can reference data from the calling function. 9 | 10 | This example splits the array in half and performs the work in separate threads. 11 | 12 | ```rust,edition2018 13 | fn main() { 14 | let arr = &[1, 25, -4, 10]; 15 | let max = find_max(arr); 16 | assert_eq!(max, Some(25)); 17 | } 18 | 19 | fn find_max(arr: &[i32]) -> Option { 20 | const THRESHOLD: usize = 2; 21 | 22 | if arr.len() <= THRESHOLD { 23 | return arr.iter().cloned().max(); 24 | } 25 | 26 | let mid = arr.len() / 2; 27 | let (left, right) = arr.split_at(mid); 28 | 29 | crossbeam::scope(|s| { 30 | let thread_l = s.spawn(|_| find_max(left)); 31 | let thread_r = s.spawn(|_| find_max(right)); 32 | 33 | let max_l = thread_l.join().unwrap()?; 34 | let max_r = thread_r.join().unwrap()?; 35 | 36 | Some(max_l.max(max_r)) 37 | }).unwrap() 38 | } 39 | ``` 40 | 41 | [`crossbeam::scope`]: https://docs.rs/crossbeam/*/crossbeam/fn.scope.html 42 | [`Scope::spawn`]: https://docs.rs/crossbeam/*/crossbeam/thread/struct.Scope.html#method.spawn 43 | -------------------------------------------------------------------------------- /src/concurrency/thread/crossbeam-spsc.md: -------------------------------------------------------------------------------- 1 | # Pass data between two threads 2 | 3 | [![crossbeam-badge]][crossbeam] [![cat-concurrency-badge]][cat-concurrency] 4 | 5 | This example demonstrates the use of [crossbeam-channel] in a single producer, single 6 | consumer (SPSC) setting. We build off the [ex-crossbeam-spawn] example by using 7 | [`crossbeam::scope`] and [`Scope::spawn`] to manage the producer thread. Data is 8 | exchanged between the two threads using a [`crossbeam_channel::unbounded`] 9 | channel, meaning there is no limit to the number of storeable messages. The 10 | producer thread sleeps for half a second in between messages. 11 | 12 | ```rust,edition2018 13 | 14 | use std::{thread, time}; 15 | use crossbeam_channel::unbounded; 16 | 17 | fn main() { 18 | let (snd, rcv) = unbounded(); 19 | let n_msgs = 5; 20 | crossbeam::scope(|s| { 21 | s.spawn(|_| { 22 | for i in 0..n_msgs { 23 | snd.send(i).unwrap(); 24 | thread::sleep(time::Duration::from_millis(100)); 25 | } 26 | }); 27 | }).unwrap(); 28 | for _ in 0..n_msgs { 29 | let msg = rcv.recv().unwrap(); 30 | println!("Received {}", msg); 31 | } 32 | } 33 | ``` 34 | 35 | [crossbeam-channel]: https://docs.rs/crate/crossbeam-channel/ 36 | [ex-crossbeam-spawn]: #spawn-a-short-lived-thread 37 | [`crossbeam::scope`]: https://docs.rs/crossbeam/*/crossbeam/fn.scope.html 38 | [`Scope::spawn`]: https://docs.rs/crossbeam/*/crossbeam/thread/struct.Scope.html#method.spawn 39 | [`crossbeam_channel::unbounded`]: https://docs.rs/crossbeam-channel/*/crossbeam_channel/fn.unbounded.html 40 | -------------------------------------------------------------------------------- /src/concurrency/thread/global-mut-state.md: -------------------------------------------------------------------------------- 1 | ## Maintain global mutable state 2 | 3 | [![lazy_static-badge]][lazy_static] [![cat-rust-patterns-badge]][cat-rust-patterns] 4 | 5 | Declare global state using [lazy_static]. [lazy_static] 6 | creates a globally available `static ref` which requires a [`Mutex`] 7 | to allow mutation (also see [`RwLock`]). The [`Mutex`] wrap ensures 8 | the state cannot be simultaneously accessed by multiple threads, preventing 9 | race conditions. A [`MutexGuard`] must be acquired to read or mutate the 10 | value stored in a [`Mutex`]. 11 | 12 | ```rust,edition2018 13 | # use error_chain::error_chain; 14 | use lazy_static::lazy_static; 15 | use std::sync::Mutex; 16 | # 17 | # error_chain!{ } 18 | 19 | lazy_static! { 20 | static ref FRUIT: Mutex> = Mutex::new(Vec::new()); 21 | } 22 | 23 | fn insert(fruit: &str) -> Result<()> { 24 | let mut db = FRUIT.lock().map_err(|_| "Failed to acquire MutexGuard")?; 25 | db.push(fruit.to_string()); 26 | Ok(()) 27 | } 28 | 29 | fn main() -> Result<()> { 30 | insert("apple")?; 31 | insert("orange")?; 32 | insert("peach")?; 33 | { 34 | let db = FRUIT.lock().map_err(|_| "Failed to acquire MutexGuard")?; 35 | 36 | db.iter().enumerate().for_each(|(i, item)| println!("{}: {}", i, item)); 37 | } 38 | insert("grape")?; 39 | Ok(()) 40 | } 41 | ``` 42 | 43 | [`Mutex`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html 44 | [`MutexGuard`]: https://doc.rust-lang.org/std/sync/struct.MutexGuard.html 45 | [`RwLock`]: https://doc.rust-lang.org/std/sync/struct.RwLock.html 46 | -------------------------------------------------------------------------------- /src/concurrency/threads.md: -------------------------------------------------------------------------------- 1 | # Threads 2 | 3 | {{#include thread/crossbeam-spawn.md}} 4 | 5 | {{#include thread/crossbeam-complex.md}} 6 | 7 | {{#include thread/crossbeam-spsc.md}} 8 | 9 | {{#include thread/global-mut-state.md}} 10 | 11 | {{#include thread/threadpool-walk.md}} 12 | 13 | {{#include thread/threadpool-fractal.md}} 14 | 15 | {{#include ../links.md}} 16 | -------------------------------------------------------------------------------- /src/cryptography.md: -------------------------------------------------------------------------------- 1 | # Cryptography 2 | 3 | | Recipe | Crates | Categories | 4 | |--------|--------|------------| 5 | | [Calculate the SHA-256 digest of a file][ex-sha-digest] | [![ring-badge]][ring] [![data-encoding-badge]][data-encoding] | [![cat-cryptography-badge]][cat-cryptography] | 6 | | [Sign and verify a message with an HMAC digest][ex-hmac] | [![ring-badge]][ring] | [![cat-cryptography-badge]][cat-cryptography] | 7 | | [Salt and hash a password with PBKDF2][ex-pbkdf2] | [![ring-badge]][ring] [![data-encoding-badge]][data-encoding] | [![cat-cryptography-badge]][cat-cryptography] | 8 | 9 | [ex-sha-digest]: cryptography/hashing.html#calculate-the-sha-256-digest-of-a-file 10 | [ex-hmac]: cryptography/hashing.html#sign-and-verify-a-message-with-hmac-digest 11 | [ex-pbkdf2]: cryptography/encryption.html#salt-and-hash-a-password-with-pbkdf2 12 | 13 | {{#include links.md}} 14 | -------------------------------------------------------------------------------- /src/cryptography/encryption.md: -------------------------------------------------------------------------------- 1 | # Encryption 2 | 3 | {{#include encryption/pbkdf2.md}} 4 | 5 | {{#include ../links.md}} 6 | -------------------------------------------------------------------------------- /src/cryptography/hashing.md: -------------------------------------------------------------------------------- 1 | # Hashing 2 | 3 | {{#include hashing/sha-digest.md}} 4 | 5 | {{#include hashing/hmac.md}} 6 | 7 | {{#include ../links.md}} 8 | -------------------------------------------------------------------------------- /src/cryptography/hashing/hmac.md: -------------------------------------------------------------------------------- 1 | ## Sign and verify a message with HMAC digest 2 | 3 | [![ring-badge]][ring] [![cat-cryptography-badge]][cat-cryptography] 4 | 5 | Uses [`ring::hmac`] to creates a [`hmac::Signature`] of a string then verifies the signature is correct. 6 | 7 | 8 | ```rust,edition2018 9 | use ring::{hmac, rand}; 10 | use ring::rand::SecureRandom; 11 | use ring::error::Unspecified; 12 | 13 | fn main() -> Result<(), Unspecified> { 14 | let mut key_value = [0u8; 48]; 15 | let rng = rand::SystemRandom::new(); 16 | rng.fill(&mut key_value)?; 17 | let key = hmac::Key::new(hmac::HMAC_SHA256, &key_value); 18 | 19 | let message = "Legitimate and important message."; 20 | let signature = hmac::sign(&key, message.as_bytes()); 21 | hmac::verify(&key, message.as_bytes(), signature.as_ref())?; 22 | 23 | Ok(()) 24 | } 25 | ``` 26 | 27 | [`hmac::Signature`]: https://briansmith.org/rustdoc/ring/hmac/struct.Signature.html 28 | [`ring::hmac`]: https://briansmith.org/rustdoc/ring/hmac/ 29 | -------------------------------------------------------------------------------- /src/cryptography/hashing/sha-digest.md: -------------------------------------------------------------------------------- 1 | ## Calculate the SHA-256 digest of a file 2 | 3 | [![ring-badge]][ring] [![data-encoding-badge]][data-encoding] [![cat-cryptography-badge]][cat-cryptography] 4 | 5 | Writes some data to a file, then calculates the SHA-256 [`digest::Digest`] of 6 | the file's contents using [`digest::Context`]. 7 | 8 | ```rust,edition2018 9 | # use error_chain::error_chain; 10 | use data_encoding::HEXUPPER; 11 | use ring::digest::{Context, Digest, SHA256}; 12 | use std::fs::File; 13 | use std::io::{BufReader, Read, Write}; 14 | # 15 | # error_chain! { 16 | # foreign_links { 17 | # Io(std::io::Error); 18 | # Decode(data_encoding::DecodeError); 19 | # } 20 | # } 21 | 22 | fn sha256_digest(mut reader: R) -> Result { 23 | let mut context = Context::new(&SHA256); 24 | let mut buffer = [0; 1024]; 25 | 26 | loop { 27 | let count = reader.read(&mut buffer)?; 28 | if count == 0 { 29 | break; 30 | } 31 | context.update(&buffer[..count]); 32 | } 33 | 34 | Ok(context.finish()) 35 | } 36 | 37 | fn main() -> Result<()> { 38 | let path = "file.txt"; 39 | 40 | let mut output = File::create(path)?; 41 | write!(output, "We will generate a digest of this text")?; 42 | 43 | let input = File::open(path)?; 44 | let reader = BufReader::new(input); 45 | let digest = sha256_digest(reader)?; 46 | 47 | println!("SHA-256 digest is {}", HEXUPPER.encode(digest.as_ref())); 48 | 49 | Ok(()) 50 | } 51 | ``` 52 | 53 | [`digest::Context`]: https://briansmith.org/rustdoc/ring/digest/struct.Context.html 54 | [`digest::Digest`]: https://briansmith.org/rustdoc/ring/digest/struct.Digest.html 55 | -------------------------------------------------------------------------------- /src/data_structures.md: -------------------------------------------------------------------------------- 1 | # Data Structures 2 | 3 | | Recipe | Crates | Categories | 4 | |--------|--------|------------| 5 | | [Define and operate on a type represented as a bitfield][ex-bitflags] | [![bitflags-badge]][bitflags] | [![cat-no-std-badge]][cat-no-std] | 6 | 7 | [ex-bitflags]: data_structures/bitfield.html#define-and-operate-on-a-type-represented-as-a-bitfield 8 | 9 | {{#include links.md}} 10 | -------------------------------------------------------------------------------- /src/data_structures/bitfield.md: -------------------------------------------------------------------------------- 1 | # Bitfield 2 | 3 | {{#include bitfield/bitfield.md}} 4 | 5 | {{#include ../links.md}} 6 | -------------------------------------------------------------------------------- /src/data_structures/bitfield/bitfield.md: -------------------------------------------------------------------------------- 1 | ## Define and operate on a type represented as a bitfield 2 | 3 | [![bitflags-badge]][bitflags] [![cat-no-std-badge]][cat-no-std] 4 | 5 | Creates type safe bitfield type `MyFlags` with help of [`bitflags!`] macro 6 | and implements elementary `clear` operation as well as [`Display`] trait for it. 7 | Subsequently, shows basic bitwise operations and formatting. 8 | 9 | ```rust,edition2018 10 | use bitflags::bitflags; 11 | use std::fmt; 12 | 13 | bitflags! { 14 | struct MyFlags: u32 { 15 | const FLAG_A = 0b00000001; 16 | const FLAG_B = 0b00000010; 17 | const FLAG_C = 0b00000100; 18 | const FLAG_ABC = Self::FLAG_A.bits 19 | | Self::FLAG_B.bits 20 | | Self::FLAG_C.bits; 21 | } 22 | } 23 | 24 | impl MyFlags { 25 | pub fn clear(&mut self) -> &mut MyFlags { 26 | self.bits = 0; 27 | self 28 | } 29 | } 30 | 31 | impl fmt::Display for MyFlags { 32 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 33 | write!(f, "{:032b}", self.bits) 34 | } 35 | } 36 | 37 | fn main() { 38 | let e1 = MyFlags::FLAG_A | MyFlags::FLAG_C; 39 | let e2 = MyFlags::FLAG_B | MyFlags::FLAG_C; 40 | assert_eq!((e1 | e2), MyFlags::FLAG_ABC); 41 | assert_eq!((e1 & e2), MyFlags::FLAG_C); 42 | assert_eq!((e1 - e2), MyFlags::FLAG_A); 43 | assert_eq!(!e2, MyFlags::FLAG_A); 44 | 45 | let mut flags = MyFlags::FLAG_ABC; 46 | assert_eq!(format!("{}", flags), "00000000000000000000000000000111"); 47 | assert_eq!(format!("{}", flags.clear()), "00000000000000000000000000000000"); 48 | assert_eq!(format!("{:?}", MyFlags::FLAG_B), "FLAG_B"); 49 | assert_eq!(format!("{:?}", MyFlags::FLAG_A | MyFlags::FLAG_B), "FLAG_A | FLAG_B"); 50 | } 51 | ``` 52 | 53 | [`bitflags!`]: https://docs.rs/bitflags/*/bitflags/macro.bitflags.html 54 | [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html 55 | -------------------------------------------------------------------------------- /src/database.md: -------------------------------------------------------------------------------- 1 | # Database 2 | 3 | | Recipe | Crates | Categories | 4 | |--------|--------|------------| 5 | | [Create a SQLite database][ex-sqlite-initialization] | [![rusqlite-badge]][rusqlite] | [![cat-database-badge]][cat-database] | 6 | | [Insert and Query data][ex-sqlite-insert-select] | [![rusqlite-badge]][rusqlite] | [![cat-database-badge]][cat-database] | 7 | | [Create tables in a Postgres database][ex-postgres-create-tables] | [![postgres-badge]][postgres] | [![cat-database-badge]][cat-database] | 8 | | [Insert and Query data][ex-postgres-insert-query-data] | [![postgres-badge]][postgres] | [![cat-database-badge]][cat-database] | 9 | | [Aggregate data][ex-postgres-aggregate-data] | [![postgres-badge]][postgres] | [![cat-database-badge]][cat-database] | 10 | 11 | [ex-sqlite-initialization]: database/sqlite.html#create-a-sqlite-database 12 | [ex-sqlite-insert-select]: database/sqlite.html#insert-and-select-data 13 | [ex-postgres-create-tables]: database/postgres.html#create-tables-in-a-postgres-database 14 | [ex-postgres-insert-query-data]: database/postgres.html#insert-and-query-data 15 | [ex-postgres-aggregate-data]: database/postgres.html#aggregate-data 16 | 17 | {{#include links.md}} 18 | -------------------------------------------------------------------------------- /src/database/postgres.md: -------------------------------------------------------------------------------- 1 | # Working with Postgres 2 | 3 | {{#include postgres/create_tables.md}} 4 | 5 | {{#include postgres/insert_query_data.md}} 6 | 7 | {{#include postgres/aggregate_data.md}} 8 | 9 | {{#include ../links.md}} 10 | -------------------------------------------------------------------------------- /src/database/postgres/aggregate_data.md: -------------------------------------------------------------------------------- 1 | ## Aggregate data 2 | 3 | [![postgres-badge]][postgres] [![cat-database-badge]][cat-database] 4 | 5 | This recipe lists the nationalities of the first 7999 artists in the database of the [`Museum of Modern Art`] in descending order. 6 | 7 | ```rust,edition2018,no_run 8 | use postgres::{Client, Error, NoTls}; 9 | 10 | struct Nation { 11 | nationality: String, 12 | count: i64, 13 | } 14 | 15 | fn main() -> Result<(), Error> { 16 | let mut client = Client::connect( 17 | "postgresql://postgres:postgres@127.0.0.1/moma", 18 | NoTls, 19 | )?; 20 | 21 | for row in client.query 22 | ("SELECT nationality, COUNT(nationality) AS count 23 | FROM artists GROUP BY nationality ORDER BY count DESC", &[])? { 24 | 25 | let (nationality, count) : (Option, Option) 26 | = (row.get (0), row.get (1)); 27 | 28 | if nationality.is_some () && count.is_some () { 29 | 30 | let nation = Nation{ 31 | nationality: nationality.unwrap(), 32 | count: count.unwrap(), 33 | }; 34 | println!("{} {}", nation.nationality, nation.count); 35 | 36 | } 37 | } 38 | 39 | Ok(()) 40 | } 41 | ``` 42 | 43 | [`Museum of Modern Art`]: https://github.com/MuseumofModernArt/collection/blob/main/Artists.csv 44 | -------------------------------------------------------------------------------- /src/database/postgres/create_tables.md: -------------------------------------------------------------------------------- 1 | ## Create tables in a Postgres database 2 | 3 | [![postgres-badge]][postgres] [![cat-database-badge]][cat-database] 4 | 5 | Use the [`postgres`] crate to create tables in a Postgres database. 6 | 7 | [`Client::connect`] helps in connecting to an existing database. The recipe uses a URL string format with `Client::connect`. It assumes an existing database named `library`, the username is `postgres` and the password is `postgres`. 8 | 9 | ```rust,edition2018,no_run 10 | use postgres::{Client, NoTls, Error}; 11 | 12 | fn main() -> Result<(), Error> { 13 | let mut client = Client::connect("postgresql://postgres:postgres@localhost/library", NoTls)?; 14 | 15 | client.batch_execute(" 16 | CREATE TABLE IF NOT EXISTS author ( 17 | id SERIAL PRIMARY KEY, 18 | name VARCHAR NOT NULL, 19 | country VARCHAR NOT NULL 20 | ) 21 | ")?; 22 | 23 | client.batch_execute(" 24 | CREATE TABLE IF NOT EXISTS book ( 25 | id SERIAL PRIMARY KEY, 26 | title VARCHAR NOT NULL, 27 | author_id INTEGER NOT NULL REFERENCES author 28 | ) 29 | ")?; 30 | 31 | Ok(()) 32 | 33 | } 34 | ``` 35 | 36 | [`postgres`]: https://docs.rs/postgres/0.17.2/postgres/ 37 | [`Client::connect`]: https://docs.rs/postgres/0.17.2/postgres/struct.Client.html#method.connect 38 | -------------------------------------------------------------------------------- /src/database/postgres/insert_query_data.md: -------------------------------------------------------------------------------- 1 | ## Insert and Query data 2 | 3 | [![postgres-badge]][postgres] [![cat-database-badge]][cat-database] 4 | 5 | The recipe inserts data into the `author` table using [`execute`] method of `Client`. Then, displays the data from the `author` table using [`query`] method of `Client`. 6 | 7 | 8 | ```rust,edition2018,no_run 9 | use postgres::{Client, NoTls, Error}; 10 | use std::collections::HashMap; 11 | 12 | struct Author { 13 | _id: i32, 14 | name: String, 15 | country: String 16 | } 17 | 18 | fn main() -> Result<(), Error> { 19 | let mut client = Client::connect("postgresql://postgres:postgres@localhost/library", 20 | NoTls)?; 21 | 22 | let mut authors = HashMap::new(); 23 | authors.insert(String::from("Chinua Achebe"), "Nigeria"); 24 | authors.insert(String::from("Rabindranath Tagore"), "India"); 25 | authors.insert(String::from("Anita Nair"), "India"); 26 | 27 | for (key, value) in &authors { 28 | let author = Author { 29 | _id: 0, 30 | name: key.to_string(), 31 | country: value.to_string() 32 | }; 33 | 34 | client.execute( 35 | "INSERT INTO author (name, country) VALUES ($1, $2)", 36 | &[&author.name, &author.country], 37 | )?; 38 | } 39 | 40 | for row in client.query("SELECT id, name, country FROM author", &[])? { 41 | let author = Author { 42 | _id: row.get(0), 43 | name: row.get(1), 44 | country: row.get(2), 45 | }; 46 | println!("Author {} is from {}", author.name, author.country); 47 | } 48 | 49 | Ok(()) 50 | 51 | } 52 | ``` 53 | 54 | [`execute`]: https://docs.rs/postgres/0.17.2/postgres/struct.Client.html#method.execute 55 | [`query`]: https://docs.rs/postgres/0.17.2/postgres/struct.Client.html#method.query 56 | -------------------------------------------------------------------------------- /src/database/sqlite.md: -------------------------------------------------------------------------------- 1 | # SQLite 2 | 3 | {{#include sqlite/initialization.md}} 4 | 5 | {{#include sqlite/insert_select.md}} 6 | 7 | {{#include sqlite/transactions.md}} 8 | 9 | {{#include ../links.md}} 10 | -------------------------------------------------------------------------------- /src/database/sqlite/initialization.md: -------------------------------------------------------------------------------- 1 | ## Create a SQLite database 2 | 3 | [![rusqlite-badge]][rusqlite] [![cat-database-badge]][cat-database] 4 | 5 | Use the `rusqlite` crate to open SQLite databases. See 6 | [crate][documentation] for compiling on Windows. 7 | 8 | [`Connection::open`] will create the database if it doesn't already exist. 9 | 10 | ```rust,edition2024,no_run 11 | use rusqlite::{Connection, Result}; 12 | 13 | fn main() -> Result<()> { 14 | let conn = Connection::open("cats.db")?; 15 | 16 | conn.execute( 17 | "create table if not exists cat_colors ( 18 | id integer primary key, 19 | name text not null unique 20 | )", 21 | (), 22 | )?; 23 | conn.execute( 24 | "create table if not exists cats ( 25 | id integer primary key, 26 | name text not null, 27 | color_id integer not null references cat_colors(id) 28 | )", 29 | (), 30 | )?; 31 | 32 | Ok(()) 33 | } 34 | ``` 35 | 36 | [`Connection::open`]: https://docs.rs/rusqlite/*/rusqlite/struct.Connection.html#method.open 37 | 38 | [documentation]: https://github.com/rusqlite/rusqlite#user-content-notes-on-building-rusqlite-and-libsqlite3-sys 39 | -------------------------------------------------------------------------------- /src/database/sqlite/transactions.md: -------------------------------------------------------------------------------- 1 | ## Using transactions 2 | 3 | [![rusqlite-badge]][rusqlite] [![cat-database-badge]][cat-database] 4 | 5 | [`Connection::open`] will open the `cats.db` database from the top recipe. 6 | 7 | Begin a transaction with [`Connection::transaction`]. Transactions will 8 | roll back unless committed explicitly with [`Transaction::commit`]. 9 | 10 | In the following example, colors add to a table having 11 | a unique constraint on the color name. When an attempt to insert 12 | a duplicate color is made, the transaction rolls back. 13 | 14 | 15 | ```rust,edition2024,no_run 16 | use rusqlite::{Connection, Result}; 17 | 18 | fn main() -> Result<()> { 19 | let mut conn = Connection::open("cats.db")?; 20 | 21 | successful_tx(&mut conn)?; 22 | 23 | let res = rolled_back_tx(&mut conn); 24 | assert!(res.is_err()); 25 | 26 | Ok(()) 27 | } 28 | 29 | fn successful_tx(conn: &mut Connection) -> Result<()> { 30 | let tx = conn.transaction()?; 31 | 32 | tx.execute("delete from cat_colors", [])?; 33 | tx.execute("insert into cat_colors (name) values (?1)", ["lavender"])?; 34 | tx.execute("insert into cat_colors (name) values (?1)", ["blue"])?; 35 | 36 | tx.commit() 37 | } 38 | 39 | fn rolled_back_tx(conn: &mut Connection) -> Result<()> { 40 | let tx = conn.transaction()?; 41 | 42 | tx.execute("delete from cat_colors", [])?; 43 | tx.execute("insert into cat_colors (name) values (?1)", ["lavender"])?; 44 | tx.execute("insert into cat_colors (name) values (?1)", ["blue"])?; 45 | tx.execute("insert into cat_colors (name) values (?1)", ["lavender"])?; 46 | 47 | tx.commit() 48 | } 49 | ``` 50 | 51 | [`Connection::transaction`]: https://docs.rs/rusqlite/*/rusqlite/struct.Connection.html#method.transaction 52 | [`Transaction::commit`]: https://docs.rs/rusqlite/*/rusqlite/struct.Transaction.html#method.commit 53 | -------------------------------------------------------------------------------- /src/datetime.md: -------------------------------------------------------------------------------- 1 | # Date and Time 2 | 3 | | Recipe | Crates | Categories | 4 | |--------|--------|------------| 5 | | [Measure elapsed time][ex-measure-elapsed-time] | [![std-badge]][std] | [![cat-time-badge]][cat-time] | 6 | | [Perform checked date and time calculations][ex-datetime-arithmetic] | [![chrono-badge]][chrono] | [![cat-date-and-time-badge]][cat-date-and-time] | 7 | | [Convert a local time to another timezone][ex-convert-datetime-timezone] | [![chrono-badge]][chrono] | [![cat-date-and-time-badge]][cat-date-and-time] | 8 | | [Examine the date and time][ex-examine-date-and-time] | [![chrono-badge]][chrono] | [![cat-date-and-time-badge]][cat-date-and-time] | 9 | | [Convert date to UNIX timestamp and vice versa][ex-convert-datetime-timestamp] | [![chrono-badge]][chrono] | [![cat-date-and-time-badge]][cat-date-and-time] | 10 | | [Display formatted date and time][ex-format-datetime] | [![chrono-badge]][chrono] | [![cat-date-and-time-badge]][cat-date-and-time] | 11 | | [Parse string into DateTime struct][ex-parse-datetime] | [![chrono-badge]][chrono] | [![cat-date-and-time-badge]][cat-date-and-time] | 12 | 13 | [ex-measure-elapsed-time]: datetime/duration.html#measure-the-elapsed-time-between-two-code-sections 14 | [ex-datetime-arithmetic]: datetime/duration.html#perform-checked-date-and-time-calculations 15 | [ex-convert-datetime-timezone]: datetime/duration.html#convert-a-local-time-to-another-timezone 16 | [ex-examine-date-and-time]: datetime/parse.html#examine-the-date-and-time 17 | [ex-convert-datetime-timestamp]: datetime/parse.html#convert-date-to-unix-timestamp-and-vice-versa 18 | [ex-format-datetime]: datetime/parse.html#display-formatted-date-and-time 19 | [ex-parse-datetime]: datetime/parse.html#parse-string-into-datetime-struct 20 | 21 | {{#include links.md}} 22 | -------------------------------------------------------------------------------- /src/datetime/duration.md: -------------------------------------------------------------------------------- 1 | # Duration and Calculation 2 | 3 | {{#include duration/profile.md}} 4 | 5 | {{#include duration/checked.md}} 6 | 7 | {{#include duration/timezone.md}} 8 | 9 | {{#include ../links.md}} 10 | -------------------------------------------------------------------------------- /src/datetime/duration/checked.md: -------------------------------------------------------------------------------- 1 | ## Perform checked date and time calculations 2 | 3 | [![chrono-badge]][chrono] [![cat-date-and-time-badge]][cat-date-and-time] 4 | 5 | Calculates and displays the date and time two weeks from now using 6 | [`DateTime::checked_add_signed`] and the date of the day before that using 7 | [`DateTime::checked_sub_signed`]. The methods return None if the date and time 8 | cannot be calculated. 9 | 10 | Escape sequences that are available for the 11 | [`DateTime::format`] can be found at [`chrono::format::strftime`]. 12 | 13 | ```rust,edition2018 14 | use chrono::{DateTime, Duration, Utc}; 15 | 16 | fn day_earlier(date_time: DateTime) -> Option> { 17 | date_time.checked_sub_signed(Duration::days(1)) 18 | } 19 | 20 | fn main() { 21 | let now = Utc::now(); 22 | println!("{}", now); 23 | 24 | let almost_three_weeks_from_now = now.checked_add_signed(Duration::weeks(2)) 25 | .and_then(|in_2weeks| in_2weeks.checked_add_signed(Duration::weeks(1))) 26 | .and_then(day_earlier); 27 | 28 | match almost_three_weeks_from_now { 29 | Some(x) => println!("{}", x), 30 | None => eprintln!("Almost three weeks from now overflows!"), 31 | } 32 | 33 | match now.checked_add_signed(Duration::max_value()) { 34 | Some(x) => println!("{}", x), 35 | None => eprintln!("We can't use chrono to tell the time for the Solar System to complete more than one full orbit around the galactic center."), 36 | } 37 | } 38 | ``` 39 | 40 | [`chrono::format::strftime`]: https://docs.rs/chrono/*/chrono/format/strftime/index.html 41 | [`DateTime::checked_add_signed`]: https://docs.rs/chrono/*/chrono/struct.Date.html#method.checked_add_signed 42 | [`DateTime::checked_sub_signed`]: https://docs.rs/chrono/*/chrono/struct.Date.html#method.checked_sub_signed 43 | [`DateTime::format`]: https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.format 44 | -------------------------------------------------------------------------------- /src/datetime/duration/profile.md: -------------------------------------------------------------------------------- 1 | ## Measure the elapsed time between two code sections 2 | 3 | [![std-badge]][std] [![cat-time-badge]][cat-time] 4 | 5 | Measures [`time::Instant::elapsed`] since [`time::Instant::now`]. 6 | 7 | Calling [`time::Instant::elapsed`] returns a [`time::Duration`] that we print at the end of the example. 8 | This method will not mutate or reset the [`time::Instant`] object. 9 | 10 | ```rust,edition2018 11 | use std::time::{Duration, Instant}; 12 | # use std::thread; 13 | # 14 | # fn expensive_function() { 15 | # thread::sleep(Duration::from_secs(1)); 16 | # } 17 | 18 | fn main() { 19 | let start = Instant::now(); 20 | expensive_function(); 21 | let duration = start.elapsed(); 22 | 23 | println!("Time elapsed in expensive_function() is: {:?}", duration); 24 | } 25 | ``` 26 | 27 | [`time::Duration`]: https://doc.rust-lang.org/std/time/struct.Duration.html 28 | [`time::Instant::elapsed`]: https://doc.rust-lang.org/std/time/struct.Instant.html#method.elapsed 29 | [`time::Instant::now`]: https://doc.rust-lang.org/std/time/struct.Instant.html#method.now 30 | [`time::Instant`]:https://doc.rust-lang.org/std/time/struct.Instant.html 31 | -------------------------------------------------------------------------------- /src/datetime/duration/timezone.md: -------------------------------------------------------------------------------- 1 | ## Convert a local time to another timezone 2 | 3 | [![chrono-badge]][chrono] [![cat-date-and-time-badge]][cat-date-and-time] 4 | 5 | Gets the local time and displays it using [`offset::Local::now`] and then converts it to the UTC standard using the [`DateTime::from_utc`] struct method. A time is then converted using the [`offset::FixedOffset`] struct and the UTC time is then converted to UTC+8 and UTC-2. 6 | 7 | ```rust,edition2018 8 | 9 | use chrono::{DateTime, FixedOffset, Local, Utc}; 10 | 11 | fn main() { 12 | let local_time = Local::now(); 13 | let utc_time = DateTime::::from_utc(local_time.naive_utc(), Utc); 14 | let china_timezone = FixedOffset::east(8 * 3600); 15 | let rio_timezone = FixedOffset::west(2 * 3600); 16 | println!("Local time now is {}", local_time); 17 | println!("UTC time now is {}", utc_time); 18 | println!( 19 | "Time in Hong Kong now is {}", 20 | utc_time.with_timezone(&china_timezone) 21 | ); 22 | println!("Time in Rio de Janeiro now is {}", utc_time.with_timezone(&rio_timezone)); 23 | } 24 | ``` 25 | 26 | [`DateTime::from_utc`]:https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.from_utc 27 | [`offset::FixedOffset`]: https://docs.rs/chrono/*/chrono/offset/struct.FixedOffset.html 28 | [`offset::Local::now`]: https://docs.rs/chrono/*/chrono/offset/struct.Local.html#method.now 29 | -------------------------------------------------------------------------------- /src/datetime/parse.md: -------------------------------------------------------------------------------- 1 | # Parsing and Displaying 2 | 3 | {{#include parse/current.md}} 4 | 5 | {{#include parse/timestamp.md}} 6 | 7 | {{#include parse/format.md}} 8 | 9 | {{#include parse/string.md}} 10 | 11 | {{#include ../links.md}} 12 | -------------------------------------------------------------------------------- /src/datetime/parse/current.md: -------------------------------------------------------------------------------- 1 | ## Examine the date and time 2 | 3 | [![chrono-badge]][chrono] [![cat-date-and-time-badge]][cat-date-and-time] 4 | 5 | Gets the current UTC [`DateTime`] and its hour/minute/second via [`Timelike`] 6 | and its year/month/day/weekday via [`Datelike`]. 7 | 8 | ```rust,edition2018 9 | use chrono::{Datelike, Timelike, Utc}; 10 | 11 | fn main() { 12 | let now = Utc::now(); 13 | 14 | let (is_pm, hour) = now.hour12(); 15 | println!( 16 | "The current UTC time is {:02}:{:02}:{:02} {}", 17 | hour, 18 | now.minute(), 19 | now.second(), 20 | if is_pm { "PM" } else { "AM" } 21 | ); 22 | println!( 23 | "And there have been {} seconds since midnight", 24 | now.num_seconds_from_midnight() 25 | ); 26 | 27 | let (is_common_era, year) = now.year_ce(); 28 | println!( 29 | "The current UTC date is {}-{:02}-{:02} {:?} ({})", 30 | year, 31 | now.month(), 32 | now.day(), 33 | now.weekday(), 34 | if is_common_era { "CE" } else { "BCE" } 35 | ); 36 | println!( 37 | "And the Common Era began {} days ago", 38 | now.num_days_from_ce() 39 | ); 40 | } 41 | ``` 42 | 43 | [`Datelike`]: https://docs.rs/chrono/*/chrono/trait.Datelike.html 44 | [`DateTime`]: https://docs.rs/chrono/*/chrono/struct.DateTime.html 45 | [`Timelike`]: https://docs.rs/chrono/*/chrono/trait.Timelike.html 46 | -------------------------------------------------------------------------------- /src/datetime/parse/format.md: -------------------------------------------------------------------------------- 1 | ## Display formatted date and time 2 | 3 | [![chrono-badge]][chrono] [![cat-date-and-time-badge]][cat-date-and-time] 4 | 5 | Gets and displays the current time in UTC using [`Utc::now`]. Formats the 6 | current time in the well-known formats [RFC 2822] using [`DateTime::to_rfc2822`] 7 | and [RFC 3339] using [`DateTime::to_rfc3339`], and in a custom format using 8 | [`DateTime::format`]. 9 | 10 | ```rust,edition2018 11 | use chrono::{DateTime, Utc}; 12 | 13 | fn main() { 14 | let now: DateTime = Utc::now(); 15 | 16 | println!("UTC now is: {}", now); 17 | println!("UTC now in RFC 2822 is: {}", now.to_rfc2822()); 18 | println!("UTC now in RFC 3339 is: {}", now.to_rfc3339()); 19 | println!("UTC now in a custom format is: {}", now.format("%a %b %e %T %Y")); 20 | } 21 | ``` 22 | 23 | [`DateTime::format`]: https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.format 24 | [`DateTime::to_rfc2822`]: https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.to_rfc2822 25 | [`DateTime::to_rfc3339`]: https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.to_rfc3339 26 | [`Utc::now`]: https://docs.rs/chrono/*/chrono/offset/struct.Utc.html#method.now 27 | 28 | [RFC 2822]: https://www.ietf.org/rfc/rfc2822.txt 29 | [RFC 3339]: https://www.ietf.org/rfc/rfc3339.txt 30 | -------------------------------------------------------------------------------- /src/datetime/parse/timestamp.md: -------------------------------------------------------------------------------- 1 | ## Convert date to UNIX timestamp and vice versa 2 | [![chrono-badge]][chrono] [![cat-date-and-time-badge]][cat-date-and-time] 3 | 4 | Converts a date given by [`NaiveDate::from_ymd`] and [`NaiveTime::from_hms`] 5 | to [UNIX timestamp] using [`NaiveDateTime::timestamp`]. 6 | Then it calculates what was the date after one billion seconds 7 | since January 1, 1970 0:00:00 UTC, using [`NaiveDateTime::from_timestamp`]. 8 | 9 | ```rust,edition2018 10 | 11 | use chrono::{NaiveDate, NaiveDateTime}; 12 | 13 | fn main() { 14 | let date_time: NaiveDateTime = NaiveDate::from_ymd(2017, 11, 12).and_hms(17, 33, 44); 15 | println!( 16 | "Number of seconds between 1970-01-01 00:00:00 and {} is {}.", 17 | date_time, date_time.timestamp()); 18 | 19 | let date_time_after_a_billion_seconds = NaiveDateTime::from_timestamp(1_000_000_000, 0); 20 | println!( 21 | "Date after a billion seconds since 1970-01-01 00:00:00 was {}.", 22 | date_time_after_a_billion_seconds); 23 | } 24 | ``` 25 | 26 | [`NaiveDate::from_ymd`]: https://docs.rs/chrono/*/chrono/naive/struct.NaiveDate.html#method.from_ymd 27 | [`NaiveDateTime::from_timestamp`]: https://docs.rs/chrono/*/chrono/naive/struct.NaiveDateTime.html#method.from_timestamp 28 | [`NaiveDateTime::timestamp`]: https://docs.rs/chrono/*/chrono/naive/struct.NaiveDateTime.html#method.timestamp 29 | [`NaiveTime::from_hms`]: https://docs.rs/chrono/*/chrono/naive/struct.NaiveTime.html#method.from_hms 30 | 31 | [UNIX timestamp]: https://en.wikipedia.org/wiki/Unix_time 32 | -------------------------------------------------------------------------------- /src/development_tools/build_tools.md: -------------------------------------------------------------------------------- 1 | # Build Time Tooling 2 | 3 | This section covers "build-time" tooling, or code that is run prior to compiling a crate's source code. 4 | Conventionally, build-time code lives in a **build.rs** file and is commonly referred to as a "build script". 5 | Common use cases include rust code generation and compilation of bundled C/C++/asm code. 6 | See crates.io's [documentation on the matter][build-script-docs] for more information. 7 | 8 | {{#include build_tools/cc-bundled-static.md}} 9 | 10 | {{#include build_tools/cc-bundled-cpp.md}} 11 | 12 | {{#include build_tools/cc-defines.md}} 13 | 14 | {{#include ../links.md}} 15 | 16 | [build-script-docs]: http://doc.crates.io/build-script.html 17 | -------------------------------------------------------------------------------- /src/development_tools/build_tools/cc-bundled-cpp.md: -------------------------------------------------------------------------------- 1 | ## Compile and link statically to a bundled C++ library 2 | 3 | [![cc-badge]][cc] [![cat-development-tools-badge]][cat-development-tools] 4 | 5 | Linking a bundled C++ library is very similar to linking a bundled C library. The two core differences when compiling and statically linking a bundled C++ library are specifying a C++ compiler via the builder method [`cpp(true)`][cc-build-cpp] and preventing name mangling by the C++ compiler by adding the `extern "C"` section at the top of our C++ source file. 6 | 7 | 8 | ### `Cargo.toml` 9 | 10 | ```toml 11 | [package] 12 | ... 13 | build = "build.rs" 14 | 15 | [build-dependencies] 16 | cc = "1" 17 | ``` 18 | 19 | ### `build.rs` 20 | 21 | ```rust,edition2018,no_run 22 | fn main() { 23 | cc::Build::new() 24 | .cpp(true) 25 | .file("src/foo.cpp") 26 | .compile("foo"); 27 | } 28 | ``` 29 | 30 | ### `src/foo.cpp` 31 | 32 | ```cpp 33 | extern "C" { 34 | int multiply(int x, int y); 35 | } 36 | 37 | int multiply(int x, int y) { 38 | return x*y; 39 | } 40 | ``` 41 | 42 | ### `src/main.rs` 43 | 44 | ```rust,edition2018,ignore 45 | extern { 46 | fn multiply(x : i32, y : i32) -> i32; 47 | } 48 | 49 | fn main(){ 50 | unsafe { 51 | println!("{}", multiply(5,7)); 52 | } 53 | } 54 | ``` 55 | 56 | [cc-build-cpp]: https://docs.rs/cc/*/cc/struct.Build.html#method.cpp 57 | -------------------------------------------------------------------------------- /src/development_tools/build_tools/cc-defines.md: -------------------------------------------------------------------------------- 1 | ## Compile a C library while setting custom defines 2 | 3 | [![cc-badge]][cc] [![cat-development-tools-badge]][cat-development-tools] 4 | 5 | It is simple to build bundled C code with custom defines using [`cc::Build::define`]. 6 | The method takes an [`Option`] value, so it is possible to create defines such as `#define APP_NAME "foo"` 7 | as well as `#define WELCOME` (pass `None` as the value for a value-less define). This example builds 8 | a bundled C file with dynamic defines set in `build.rs` and prints "**Welcome to foo - version 1.0.2**" 9 | when run. Cargo sets some [environment variables][cargo-env] which may be useful for some custom defines. 10 | 11 | 12 | ### `Cargo.toml` 13 | 14 | ```toml 15 | [package] 16 | ... 17 | version = "1.0.2" 18 | build = "build.rs" 19 | 20 | [build-dependencies] 21 | cc = "1" 22 | ``` 23 | 24 | ### `build.rs` 25 | 26 | ```rust,edition2018,no_run 27 | fn main() { 28 | cc::Build::new() 29 | .define("APP_NAME", "\"foo\"") 30 | .define("VERSION", format!("\"{}\"", env!("CARGO_PKG_VERSION")).as_str()) 31 | .define("WELCOME", None) 32 | .file("src/foo.c") 33 | .compile("foo"); 34 | } 35 | ``` 36 | 37 | ### `src/foo.c` 38 | 39 | ```c 40 | #include 41 | 42 | void print_app_info() { 43 | #ifdef WELCOME 44 | printf("Welcome to "); 45 | #endif 46 | printf("%s - version %s\n", APP_NAME, VERSION); 47 | } 48 | ``` 49 | 50 | ### `src/main.rs` 51 | 52 | ```rust,edition2018,ignore 53 | extern { 54 | fn print_app_info(); 55 | } 56 | 57 | fn main(){ 58 | unsafe { 59 | print_app_info(); 60 | } 61 | } 62 | ``` 63 | 64 | [cargo-env]: https://doc.rust-lang.org/cargo/reference/environment-variables.html 65 | -------------------------------------------------------------------------------- /src/development_tools/debugging/config_log.md: -------------------------------------------------------------------------------- 1 | # Configure Logging 2 | 3 | {{#include config_log/log-mod.md}} 4 | 5 | {{#include config_log/log-env-variable.md}} 6 | 7 | {{#include config_log/log-timestamp.md}} 8 | 9 | {{#include config_log/log-custom.md}} 10 | 11 | {{#include ../../links.md}} 12 | -------------------------------------------------------------------------------- /src/development_tools/debugging/config_log/log-custom.md: -------------------------------------------------------------------------------- 1 | ## Log messages to a custom location 2 | 3 | [![log-badge]][log] [![log4rs-badge]][log4rs] [![cat-debugging-badge]][cat-debugging] 4 | 5 | [log4rs] configures log output to a custom location. [log4rs] can use either an 6 | external YAML file or a builder configuration. 7 | 8 | Create the log configuration with [`log4rs::append::file::FileAppender`]. An 9 | appender defines the logging destination. The configuration continues with 10 | encoding using a custom pattern from [`log4rs::encode::pattern`]. 11 | Assigns the configuration to [`log4rs::config::Config`] and sets the default 12 | [`log::LevelFilter`]. 13 | 14 | ```rust,edition2018,no_run 15 | # use error_chain::error_chain; 16 | 17 | use log::LevelFilter; 18 | use log4rs::append::file::FileAppender; 19 | use log4rs::encode::pattern::PatternEncoder; 20 | use log4rs::config::{Appender, Config, Root}; 21 | # 22 | # error_chain! { 23 | # foreign_links { 24 | # Io(std::io::Error); 25 | # LogConfig(log4rs::config::Errors); 26 | # SetLogger(log::SetLoggerError); 27 | # } 28 | # } 29 | 30 | fn main() -> Result<()> { 31 | let logfile = FileAppender::builder() 32 | .encoder(Box::new(PatternEncoder::new("{l} - {m}\n"))) 33 | .build("log/output.log")?; 34 | 35 | let config = Config::builder() 36 | .appender(Appender::builder().build("logfile", Box::new(logfile))) 37 | .build(Root::builder() 38 | .appender("logfile") 39 | .build(LevelFilter::Info))?; 40 | 41 | log4rs::init_config(config)?; 42 | 43 | log::info!("Hello, world!"); 44 | 45 | Ok(()) 46 | } 47 | ``` 48 | 49 | [`log4rs::append::file::FileAppender`]: https://docs.rs/log4rs/*/log4rs/append/file/struct.FileAppender.html 50 | [`log4rs::config::Config`]: https://docs.rs/log4rs/*/log4rs/config/struct.Config.html 51 | [`log4rs::encode::pattern`]: https://docs.rs/log4rs/*/log4rs/encode/pattern/index.html 52 | [`log::LevelFilter`]: https://docs.rs/log/*/log/enum.LevelFilter.html 53 | -------------------------------------------------------------------------------- /src/development_tools/debugging/config_log/log-env-variable.md: -------------------------------------------------------------------------------- 1 | ## Use a custom environment variable to set up logging 2 | 3 | [![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] 4 | 5 | [`Builder`] configures logging. 6 | 7 | [`Builder::from_env`] parses `MY_APP_LOG` 8 | environment variable contents in the form of [`RUST_LOG`] syntax. 9 | Then, [`Builder::init`] initializes the logger. 10 | 11 | ```rust,edition2018 12 | use env_logger::Builder; 13 | 14 | fn main() { 15 | Builder::from_env("MY_APP_LOG").init(); 16 | 17 | log::info!("informational message"); 18 | log::warn!("warning message"); 19 | log::error!("this is an error {}", "message"); 20 | } 21 | ``` 22 | 23 | [`Builder`]: https://docs.rs/env_logger/*/env_logger/struct.Builder.html 24 | [`Builder::from_env`]: https://docs.rs/env_logger/*/env_logger/struct.Builder.html#method.from_env 25 | [`Builder::init`]: https://docs.rs/env_logger/*/env_logger/struct.Builder.html#method.init 26 | [`RUST_LOG`]: https://docs.rs/env_logger/*/env_logger/#enabling-logging 27 | -------------------------------------------------------------------------------- /src/development_tools/debugging/config_log/log-mod.md: -------------------------------------------------------------------------------- 1 | ## Enable log levels per module 2 | 3 | [![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] 4 | 5 | Creates two modules `foo` and nested `foo::bar` with logging directives 6 | controlled separately with [`RUST_LOG`] environmental variable. 7 | 8 | ```rust,edition2018 9 | 10 | mod foo { 11 | mod bar { 12 | pub fn run() { 13 | log::warn!("[bar] warn"); 14 | log::info!("[bar] info"); 15 | log::debug!("[bar] debug"); 16 | } 17 | } 18 | 19 | pub fn run() { 20 | log::warn!("[foo] warn"); 21 | log::info!("[foo] info"); 22 | log::debug!("[foo] debug"); 23 | bar::run(); 24 | } 25 | } 26 | 27 | fn main() { 28 | env_logger::init(); 29 | log::warn!("[root] warn"); 30 | log::info!("[root] info"); 31 | log::debug!("[root] debug"); 32 | foo::run(); 33 | } 34 | ``` 35 | 36 | [`RUST_LOG`] environment variable controls [`env_logger`][env_logger] output. 37 | Module declarations take comma separated entries formatted like 38 | `path::to::module=log_level`. Run the `test` application as follows: 39 | 40 | ```bash 41 | RUST_LOG="warn,test::foo=info,test::foo::bar=debug" ./test 42 | ``` 43 | 44 | Sets the default [`log::Level`] to `warn`, module `foo` and module `foo::bar` 45 | to `info` and `debug`. 46 | 47 | ```bash 48 | WARN:test: [root] warn 49 | WARN:test::foo: [foo] warn 50 | INFO:test::foo: [foo] info 51 | WARN:test::foo::bar: [bar] warn 52 | INFO:test::foo::bar: [bar] info 53 | DEBUG:test::foo::bar: [bar] debug 54 | ``` 55 | 56 | [`log::Level`]: https://docs.rs/log/*/log/enum.Level.html 57 | [`RUST_LOG`]: https://docs.rs/env_logger/*/env_logger/#enabling-logging 58 | -------------------------------------------------------------------------------- /src/development_tools/debugging/config_log/log-timestamp.md: -------------------------------------------------------------------------------- 1 | ## Include timestamp in log messages 2 | 3 | [![log-badge]][log] [![env_logger-badge]][env_logger] [![chrono-badge]][chrono] [![cat-debugging-badge]][cat-debugging] 4 | 5 | Creates a custom logger configuration with [`Builder`]. 6 | Each log entry calls [`Local::now`] to get the current [`DateTime`] in local 7 | timezone and uses [`DateTime::format`] with [`strftime::specifiers`] to format 8 | a timestamp used in the final log. 9 | 10 | The example calls [`Builder::format`] to set a closure which formats each 11 | message text with timestamp, [`Record::level`] and body ([`Record::args`]). 12 | 13 | ```rust,edition2018 14 | use std::io::Write; 15 | use chrono::Local; 16 | use env_logger::Builder; 17 | use log::LevelFilter; 18 | 19 | fn main() { 20 | Builder::new() 21 | .format(|buf, record| { 22 | writeln!(buf, 23 | "{} [{}] - {}", 24 | Local::now().format("%Y-%m-%dT%H:%M:%S"), 25 | record.level(), 26 | record.args() 27 | ) 28 | }) 29 | .filter(None, LevelFilter::Info) 30 | .init(); 31 | 32 | log::warn!("warn"); 33 | log::info!("info"); 34 | log::debug!("debug"); 35 | } 36 | ``` 37 | stderr output will contain 38 | ``` 39 | 2017-05-22T21:57:06 [WARN] - warn 40 | 2017-05-22T21:57:06 [INFO] - info 41 | ``` 42 | 43 | [`DateTime::format`]: https://docs.rs/chrono/*/chrono/struct.DateTime.html#method.format 44 | [`DateTime`]: https://docs.rs/chrono/*/chrono/datetime/struct.DateTime.html 45 | [`Local::now`]: https://docs.rs/chrono/*/chrono/offset/struct.Local.html#method.now 46 | [`Builder`]: https://docs.rs/env_logger/*/env_logger/struct.Builder.html 47 | [`Builder::format`]: https://docs.rs/env_logger/*/env_logger/struct.Builder.html#method.format 48 | [`Record::args`]: https://docs.rs/log/*/log/struct.Record.html#method.args 49 | [`Record::level`]: https://docs.rs/log/*/log/struct.Record.html#method.level 50 | [`strftime::specifiers`]: https://docs.rs/chrono/*/chrono/format/strftime/index.html#specifiers 51 | -------------------------------------------------------------------------------- /src/development_tools/debugging/log.md: -------------------------------------------------------------------------------- 1 | # Log Messages 2 | 3 | {{#include log/log-debug.md}} 4 | 5 | {{#include log/log-error.md}} 6 | 7 | {{#include log/log-stdout.md}} 8 | 9 | {{#include log/log-custom-logger.md}} 10 | 11 | {{#include log/log-syslog.md}} 12 | 13 | {{#include ../../links.md}} 14 | -------------------------------------------------------------------------------- /src/development_tools/debugging/log/log-custom-logger.md: -------------------------------------------------------------------------------- 1 | ## Log messages with a custom logger 2 | 3 | [![log-badge]][log] [![cat-debugging-badge]][cat-debugging] 4 | 5 | Implements a custom logger `ConsoleLogger` which prints to stdout. 6 | In order to use the logging macros, `ConsoleLogger` implements 7 | the [`log::Log`] trait and [`log::set_logger`] installs it. 8 | 9 | ```rust,edition2018 10 | use log::{Record, Level, Metadata, LevelFilter, SetLoggerError}; 11 | 12 | static CONSOLE_LOGGER: ConsoleLogger = ConsoleLogger; 13 | 14 | struct ConsoleLogger; 15 | 16 | impl log::Log for ConsoleLogger { 17 | fn enabled(&self, metadata: &Metadata) -> bool { 18 | metadata.level() <= Level::Info 19 | } 20 | 21 | fn log(&self, record: &Record) { 22 | if self.enabled(record.metadata()) { 23 | println!("Rust says: {} - {}", record.level(), record.args()); 24 | } 25 | } 26 | 27 | fn flush(&self) {} 28 | } 29 | 30 | fn main() -> Result<(), SetLoggerError> { 31 | log::set_logger(&CONSOLE_LOGGER)?; 32 | log::set_max_level(LevelFilter::Info); 33 | 34 | log::info!("hello log"); 35 | log::warn!("warning"); 36 | log::error!("oops"); 37 | Ok(()) 38 | } 39 | ``` 40 | 41 | [`log::Log`]: https://docs.rs/log/*/log/trait.Log.html 42 | [`log::set_logger`]: https://docs.rs/log/*/log/fn.set_logger.html 43 | -------------------------------------------------------------------------------- /src/development_tools/debugging/log/log-debug.md: -------------------------------------------------------------------------------- 1 | ## Log a debug message to the console 2 | 3 | [![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] 4 | 5 | The `log` crate provides logging utilities. The `env_logger` crate configures 6 | logging via an environment variable. The [`log::debug!`] macro works like other 7 | [`std::fmt`] formatted strings. 8 | 9 | ```rust,edition2018 10 | 11 | fn execute_query(query: &str) { 12 | log::debug!("Executing query: {}", query); 13 | } 14 | 15 | fn main() { 16 | env_logger::init(); 17 | 18 | execute_query("DROP TABLE students"); 19 | } 20 | ``` 21 | 22 | No output prints when running this code. By default, the 23 | log level is `error`, and any lower levels are dropped. 24 | 25 | Set the `RUST_LOG` environment variable to print the message: 26 | 27 | ``` 28 | $ RUST_LOG=debug cargo run 29 | ``` 30 | 31 | Cargo prints debugging information then the 32 | following line at the very end of the output: 33 | 34 | ``` 35 | DEBUG:main: Executing query: DROP TABLE students 36 | ``` 37 | 38 | [`log::debug!`]: https://docs.rs/log/*/log/macro.debug.html 39 | [`std::fmt`]: https://doc.rust-lang.org/std/fmt/ 40 | -------------------------------------------------------------------------------- /src/development_tools/debugging/log/log-error.md: -------------------------------------------------------------------------------- 1 | ## Log an error message to the console 2 | 3 | [![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] 4 | 5 | Proper error handling considers exceptions exceptional. Here, an error logs 6 | to stderr with `log`'s convenience macro [`log::error!`]. 7 | 8 | ```rust,edition2018 9 | 10 | fn execute_query(_query: &str) -> Result<(), &'static str> { 11 | Err("I'm afraid I can't do that") 12 | } 13 | 14 | fn main() { 15 | env_logger::init(); 16 | 17 | let response = execute_query("DROP TABLE students"); 18 | if let Err(err) = response { 19 | log::error!("Failed to execute query: {}", err); 20 | } 21 | } 22 | ``` 23 | 24 | [`log::error!`]: https://docs.rs/log/*/log/macro.error.html 25 | -------------------------------------------------------------------------------- /src/development_tools/debugging/log/log-stdout.md: -------------------------------------------------------------------------------- 1 | ## Log to stdout instead of stderr 2 | 3 | [![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] 4 | 5 | Creates a custom logger configuration using the [`Builder::target`] to set the target of the log output to [`Target::Stdout`]. 6 | 7 | ```rust,edition2018 8 | 9 | use env_logger::{Builder, Target}; 10 | 11 | fn main() { 12 | Builder::new() 13 | .target(Target::Stdout) 14 | .init(); 15 | 16 | log::error!("This error has been printed to Stdout"); 17 | } 18 | ``` 19 | 20 | [`Builder::target`]: https://docs.rs/env_logger/*/env_logger/struct.Builder.html#method.target 21 | [`Target::Stdout`]: https://docs.rs/env_logger/*/env_logger/fmt/enum.Target.html 22 | -------------------------------------------------------------------------------- /src/development_tools/debugging/log/log-syslog.md: -------------------------------------------------------------------------------- 1 | ## Log to the Unix syslog 2 | 3 | [![log-badge]][log] [![syslog-badge]][syslog] [![cat-debugging-badge]][cat-debugging] 4 | 5 | Logs messages to [UNIX syslog]. Initializes logger backend 6 | with [`syslog::init`]. [`syslog::Facility`] records the program submitting 7 | the log entry's classification, [`log::LevelFilter`] denotes allowed log verbosity 8 | and `Option<&str>` holds optional application name. 9 | 10 | ```rust,edition2018 11 | # #[cfg(target_os = "linux")] 12 | # #[cfg(target_os = "linux")] 13 | use syslog::{Facility, Error}; 14 | 15 | # #[cfg(target_os = "linux")] 16 | fn main() -> Result<(), Error> { 17 | syslog::init(Facility::LOG_USER, 18 | log::LevelFilter::Debug, 19 | Some("My app name"))?; 20 | log::debug!("this is a debug {}", "message"); 21 | log::error!("this is an error!"); 22 | Ok(()) 23 | } 24 | 25 | # #[cfg(not(target_os = "linux"))] 26 | # fn main() { 27 | # println!("So far, only Linux systems are supported."); 28 | # } 29 | ``` 30 | 31 | [`log::LevelFilter`]: https://docs.rs/log/*/log/enum.LevelFilter.html 32 | [`syslog::Facility`]: https://docs.rs/syslog/*/syslog/enum.Facility.html 33 | [`syslog::init`]: https://docs.rs/syslog/*/syslog/fn.init.html 34 | 35 | [UNIX syslog]: https://www.gnu.org/software/libc/manual/html_node/Overview-of-Syslog.html 36 | -------------------------------------------------------------------------------- /src/development_tools/versioning.md: -------------------------------------------------------------------------------- 1 | # Versioning 2 | 3 | {{#include versioning/semver-increment.md}} 4 | 5 | {{#include versioning/semver-complex.md}} 6 | 7 | {{#include versioning/semver-prerelease.md}} 8 | 9 | {{#include versioning/semver-latest.md}} 10 | 11 | {{#include versioning/semver-command.md}} 12 | 13 | {{#include ../links.md}} 14 | -------------------------------------------------------------------------------- /src/development_tools/versioning/semver-command.md: -------------------------------------------------------------------------------- 1 | ## Check external command version for compatibility 2 | 3 | [![semver-badge]][semver] [![cat-text-processing-badge]][cat-text-processing] [![cat-os-badge]][cat-os] 4 | 5 | Runs `git --version` using [`Command`], then parses the version number into a 6 | [`semver::Version`] using [`Version::parse`]. [`VersionReq::matches`] compares 7 | [`semver::VersionReq`] to the parsed version. The command output resembles 8 | "git version x.y.z". 9 | 10 | ```rust,edition2018,no_run 11 | # use error_chain::error_chain; 12 | 13 | use std::process::Command; 14 | use semver::{Version, VersionReq}; 15 | # 16 | # error_chain! { 17 | # foreign_links { 18 | # Io(std::io::Error); 19 | # Utf8(std::string::FromUtf8Error); 20 | # SemVer(semver::SemVerError); 21 | # SemVerReq(semver::ReqParseError); 22 | # } 23 | # } 24 | 25 | fn main() -> Result<()> { 26 | let version_constraint = "> 1.12.0"; 27 | let version_test = VersionReq::parse(version_constraint)?; 28 | let output = Command::new("git").arg("--version").output()?; 29 | 30 | if !output.status.success() { 31 | error_chain::bail!("Command executed with failing error code"); 32 | } 33 | 34 | let stdout = String::from_utf8(output.stdout)?; 35 | let version = stdout.split(" ").last().ok_or_else(|| { 36 | "Invalid command output" 37 | })?; 38 | let parsed_version = Version::parse(version)?; 39 | 40 | if !version_test.matches(&parsed_version) { 41 | error_chain::bail!("Command version lower than minimum supported version (found {}, need {})", 42 | parsed_version, version_constraint); 43 | } 44 | 45 | Ok(()) 46 | } 47 | ``` 48 | 49 | [`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html 50 | [`semver::Version`]: https://docs.rs/semver/*/semver/struct.Version.html 51 | [`semver::VersionReq`]: https://docs.rs/semver/*/semver/struct.VersionReq.html 52 | [`Version::parse`]: https://docs.rs/semver/*/semver/struct.Version.html#method.parse 53 | [`VersionReq::matches`]: https://docs.rs/semver/*/semver/struct.VersionReq.html#method.matches 54 | -------------------------------------------------------------------------------- /src/development_tools/versioning/semver-complex.md: -------------------------------------------------------------------------------- 1 | ## Parse a complex version string. 2 | 3 | [![semver-badge]][semver] [![cat-config-badge]][cat-config] 4 | 5 | Constructs a [`semver::Version`] from a complex version string using [`Version::parse`]. The string 6 | contains pre-release and build metadata as defined in the [Semantic Versioning Specification]. 7 | 8 | Note that, in accordance with the Specification, build metadata is parsed but not considered when 9 | comparing versions. In other words, two versions may be equal even if their build strings differ. 10 | 11 | ```rust,edition2018 12 | use semver::{Identifier, Version, SemVerError}; 13 | 14 | fn main() -> Result<(), SemVerError> { 15 | let version_str = "1.0.49-125+g72ee7853"; 16 | let parsed_version = Version::parse(version_str)?; 17 | 18 | assert_eq!( 19 | parsed_version, 20 | Version { 21 | major: 1, 22 | minor: 0, 23 | patch: 49, 24 | pre: vec![Identifier::Numeric(125)], 25 | build: vec![], 26 | } 27 | ); 28 | assert_eq!( 29 | parsed_version.build, 30 | vec![Identifier::AlphaNumeric(String::from("g72ee7853"))] 31 | ); 32 | 33 | let serialized_version = parsed_version.to_string(); 34 | assert_eq!(&serialized_version, version_str); 35 | 36 | Ok(()) 37 | } 38 | ``` 39 | 40 | [`semver::Version`]: https://docs.rs/semver/*/semver/struct.Version.html 41 | [`Version::parse`]: https://docs.rs/semver/*/semver/struct.Version.html#method.parse 42 | 43 | [Semantic Versioning Specification]: http://semver.org/ 44 | -------------------------------------------------------------------------------- /src/development_tools/versioning/semver-increment.md: -------------------------------------------------------------------------------- 1 | ## Parse and increment a version string. 2 | 3 | [![semver-badge]][semver] [![cat-config-badge]][cat-config] 4 | 5 | Constructs a [`semver::Version`] from a string literal using [`Version::parse`], 6 | then increments it by patch, minor, and major version number one by one. 7 | 8 | Note that in accordance with the [Semantic Versioning Specification], 9 | incrementing the minor version number resets the patch version number to 0 and 10 | incrementing the major version number resets both the minor and patch version 11 | numbers to 0. 12 | 13 | ```rust,edition2018 14 | use semver::{Version, SemVerError}; 15 | 16 | fn main() -> Result<(), SemVerError> { 17 | let mut parsed_version = Version::parse("0.2.6")?; 18 | 19 | assert_eq!( 20 | parsed_version, 21 | Version { 22 | major: 0, 23 | minor: 2, 24 | patch: 6, 25 | pre: vec![], 26 | build: vec![], 27 | } 28 | ); 29 | 30 | parsed_version.increment_patch(); 31 | assert_eq!(parsed_version.to_string(), "0.2.7"); 32 | println!("New patch release: v{}", parsed_version); 33 | 34 | parsed_version.increment_minor(); 35 | assert_eq!(parsed_version.to_string(), "0.3.0"); 36 | println!("New minor release: v{}", parsed_version); 37 | 38 | parsed_version.increment_major(); 39 | assert_eq!(parsed_version.to_string(), "1.0.0"); 40 | println!("New major release: v{}", parsed_version); 41 | 42 | Ok(()) 43 | } 44 | ``` 45 | 46 | [`semver::Version`]: https://docs.rs/semver/*/semver/struct.Version.html 47 | [`Version::parse`]: https://docs.rs/semver/*/semver/struct.Version.html#method.parse 48 | 49 | [Semantic Versioning Specification]: http://semver.org/ 50 | -------------------------------------------------------------------------------- /src/development_tools/versioning/semver-latest.md: -------------------------------------------------------------------------------- 1 | ## Find the latest version satisfying given range 2 | 3 | [![semver-badge]][semver] [![cat-config-badge]][cat-config] 4 | 5 | Given a list of version &strs, finds the latest [`semver::Version`]. 6 | [`semver::VersionReq`] filters the list with [`VersionReq::matches`]. 7 | Also demonstrates `semver` pre-release preferences. 8 | 9 | ```rust,edition2018 10 | # use error_chain::error_chain; 11 | 12 | use semver::{Version, VersionReq}; 13 | # 14 | # error_chain! { 15 | # foreign_links { 16 | # SemVer(semver::SemVerError); 17 | # SemVerReq(semver::ReqParseError); 18 | # } 19 | # } 20 | 21 | fn find_max_matching_version<'a, I>(version_req_str: &str, iterable: I) -> Result> 22 | where 23 | I: IntoIterator, 24 | { 25 | let vreq = VersionReq::parse(version_req_str)?; 26 | 27 | Ok( 28 | iterable 29 | .into_iter() 30 | .filter_map(|s| Version::parse(s).ok()) 31 | .filter(|s| vreq.matches(s)) 32 | .max(), 33 | ) 34 | } 35 | 36 | fn main() -> Result<()> { 37 | assert_eq!( 38 | find_max_matching_version("<= 1.0.0", vec!["0.9.0", "1.0.0", "1.0.1"])?, 39 | Some(Version::parse("1.0.0")?) 40 | ); 41 | 42 | assert_eq!( 43 | find_max_matching_version( 44 | ">1.2.3-alpha.3", 45 | vec![ 46 | "1.2.3-alpha.3", 47 | "1.2.3-alpha.4", 48 | "1.2.3-alpha.10", 49 | "1.2.3-beta.4", 50 | "3.4.5-alpha.9", 51 | ] 52 | )?, 53 | Some(Version::parse("1.2.3-beta.4")?) 54 | ); 55 | 56 | Ok(()) 57 | } 58 | ``` 59 | 60 | [`semver::Version`]: https://docs.rs/semver/*/semver/struct.Version.html 61 | [`semver::VersionReq`]: https://docs.rs/semver/*/semver/struct.VersionReq.html 62 | [`VersionReq::matches`]: https://docs.rs/semver/*/semver/struct.VersionReq.html#method.matches 63 | -------------------------------------------------------------------------------- /src/development_tools/versioning/semver-prerelease.md: -------------------------------------------------------------------------------- 1 | ## Check if given version is pre-release. 2 | 3 | [![semver-badge]][semver] [![cat-config-badge]][cat-config] 4 | 5 | Given two versions, [`is_prerelease`] asserts that one is pre-release and the other is not. 6 | 7 | ```rust,edition2018 8 | use semver::{Version, SemVerError}; 9 | 10 | fn main() -> Result<(), SemVerError> { 11 | let version_1 = Version::parse("1.0.0-alpha")?; 12 | let version_2 = Version::parse("1.0.0")?; 13 | 14 | assert!(version_1.is_prerelease()); 15 | assert!(!version_2.is_prerelease()); 16 | 17 | Ok(()) 18 | } 19 | ``` 20 | 21 | [`is_prerelease`]: https://docs.rs/semver/*/semver/struct.Version.html#method.is_prerelease 22 | -------------------------------------------------------------------------------- /src/encoding/complex.md: -------------------------------------------------------------------------------- 1 | # Structured Data 2 | 3 | {{#include complex/json.md}} 4 | 5 | {{#include complex/toml.md}} 6 | 7 | {{#include complex/endian-byte.md}} 8 | 9 | {{#include ../links.md}} 10 | -------------------------------------------------------------------------------- /src/encoding/complex/endian-byte.md: -------------------------------------------------------------------------------- 1 | ## Read and write integers in little-endian byte order 2 | 3 | [![byteorder-badge]][byteorder] [![cat-encoding-badge]][cat-encoding] 4 | 5 | `byteorder` can reverse the significant bytes of structured data. This may 6 | be necessary when receiving information over the network, such that bytes 7 | received are from another system. 8 | 9 | ```rust,edition2018 10 | 11 | use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; 12 | use std::io::Error; 13 | 14 | #[derive(Default, PartialEq, Debug)] 15 | struct Payload { 16 | kind: u8, 17 | value: u16, 18 | } 19 | 20 | fn main() -> Result<(), Error> { 21 | let original_payload = Payload::default(); 22 | let encoded_bytes = encode(&original_payload)?; 23 | let decoded_payload = decode(&encoded_bytes)?; 24 | assert_eq!(original_payload, decoded_payload); 25 | Ok(()) 26 | } 27 | 28 | fn encode(payload: &Payload) -> Result, Error> { 29 | let mut bytes = vec![]; 30 | bytes.write_u8(payload.kind)?; 31 | bytes.write_u16::(payload.value)?; 32 | Ok(bytes) 33 | } 34 | 35 | fn decode(mut bytes: &[u8]) -> Result { 36 | let payload = Payload { 37 | kind: bytes.read_u8()?, 38 | value: bytes.read_u16::()?, 39 | }; 40 | Ok(payload) 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /src/encoding/complex/json.md: -------------------------------------------------------------------------------- 1 | ## Serialize and deserialize unstructured JSON 2 | 3 | [![serde-json-badge]][serde-json] [![cat-encoding-badge]][cat-encoding] 4 | 5 | The [`serde_json`] crate provides a [`from_str`] function to parse a `&str` of 6 | JSON. 7 | 8 | Unstructured JSON can be parsed into a universal [`serde_json::Value`] type that 9 | is able to represent any valid JSON data. 10 | 11 | The example below shows a `&str` of JSON being parsed. The expected value is declared using the [`json!`] macro. 12 | 13 | ```rust,edition2018 14 | use serde_json::json; 15 | use serde_json::{Value, Error}; 16 | 17 | fn main() -> Result<(), Error> { 18 | let j = r#"{ 19 | "userid": 103609, 20 | "verified": true, 21 | "access_privileges": [ 22 | "user", 23 | "admin" 24 | ] 25 | }"#; 26 | 27 | let parsed: Value = serde_json::from_str(j)?; 28 | 29 | let expected = json!({ 30 | "userid": 103609, 31 | "verified": true, 32 | "access_privileges": [ 33 | "user", 34 | "admin" 35 | ] 36 | }); 37 | 38 | assert_eq!(parsed, expected); 39 | 40 | Ok(()) 41 | } 42 | ``` 43 | 44 | [`from_str`]: https://docs.serde.rs/serde_json/fn.from_str.html 45 | [`json!`]: https://docs.serde.rs/serde_json/macro.json.html 46 | [`serde_json`]: https://docs.serde.rs/serde_json/ 47 | [`serde_json::Value`]: https://docs.serde.rs/serde_json/enum.Value.html 48 | -------------------------------------------------------------------------------- /src/encoding/complex/toml.md: -------------------------------------------------------------------------------- 1 | ## Deserialize a TOML configuration file 2 | 3 | [![toml-badge]][toml] [![cat-encoding-badge]][cat-encoding] 4 | 5 | Parse some TOML into a universal `toml::Value` that is able to represent any 6 | valid TOML data. 7 | 8 | ```rust,edition2018 9 | use toml::{Value, de::Error}; 10 | 11 | fn main() -> Result<(), Error> { 12 | let toml_content = r#" 13 | [package] 14 | name = "your_package" 15 | version = "0.1.0" 16 | authors = ["You! "] 17 | 18 | [dependencies] 19 | serde = "1.0" 20 | "#; 21 | 22 | let package_info: Value = toml::from_str(toml_content)?; 23 | 24 | assert_eq!(package_info["dependencies"]["serde"].as_str(), Some("1.0")); 25 | assert_eq!(package_info["package"]["name"].as_str(), 26 | Some("your_package")); 27 | 28 | Ok(()) 29 | } 30 | ``` 31 | 32 | Parse TOML into your own structs using [Serde]. 33 | 34 | ```rust,edition2018 35 | use serde::Deserialize; 36 | 37 | use toml::de::Error; 38 | use std::collections::HashMap; 39 | 40 | #[derive(Deserialize)] 41 | struct Config { 42 | package: Package, 43 | dependencies: HashMap, 44 | } 45 | 46 | #[derive(Deserialize)] 47 | struct Package { 48 | name: String, 49 | version: String, 50 | authors: Vec, 51 | } 52 | 53 | fn main() -> Result<(), Error> { 54 | let toml_content = r#" 55 | [package] 56 | name = "your_package" 57 | version = "0.1.0" 58 | authors = ["You! "] 59 | 60 | [dependencies] 61 | serde = "1.0" 62 | "#; 63 | 64 | let package_info: Config = toml::from_str(toml_content)?; 65 | 66 | assert_eq!(package_info.package.name, "your_package"); 67 | assert_eq!(package_info.package.version, "0.1.0"); 68 | assert_eq!(package_info.package.authors, vec!["You! "]); 69 | assert_eq!(package_info.dependencies["serde"], "1.0"); 70 | 71 | Ok(()) 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /src/encoding/csv.md: -------------------------------------------------------------------------------- 1 | # CSV processing 2 | 3 | {{#include csv/read.md}} 4 | 5 | {{#include csv/delimiter.md}} 6 | 7 | {{#include csv/filter.md}} 8 | 9 | {{#include csv/invalid.md}} 10 | 11 | {{#include csv/serialize.md}} 12 | 13 | {{#include csv/serde-serialize.md}} 14 | 15 | {{#include csv/transform.md}} 16 | 17 | {{#include ../links.md}} 18 | -------------------------------------------------------------------------------- /src/encoding/csv/delimiter.md: -------------------------------------------------------------------------------- 1 | ## Read CSV records with different delimiter 2 | 3 | [![csv-badge]][csv] [![cat-encoding-badge]][cat-encoding] 4 | 5 | Reads CSV records with a tab [`delimiter`]. 6 | 7 | ```rust,edition2018 8 | use csv::Error; 9 | use serde::Deserialize; 10 | #[derive(Debug, Deserialize)] 11 | struct Record { 12 | name: String, 13 | place: String, 14 | #[serde(deserialize_with = "csv::invalid_option")] 15 | id: Option, 16 | } 17 | 18 | use csv::ReaderBuilder; 19 | 20 | fn main() -> Result<(), Error> { 21 | let data = "name\tplace\tid\n\ 22 | Mark\tMelbourne\t46\n\ 23 | Ashley\tZurich\t92"; 24 | 25 | let mut reader = ReaderBuilder::new().delimiter(b'\t').from_reader(data.as_bytes()); 26 | for result in reader.deserialize::() { 27 | println!("{:?}", result?); 28 | } 29 | 30 | Ok(()) 31 | } 32 | ``` 33 | 34 | [`delimiter`]: https://docs.rs/csv/1.0.0-beta.3/csv/struct.ReaderBuilder.html#method.delimiter 35 | -------------------------------------------------------------------------------- /src/encoding/csv/filter.md: -------------------------------------------------------------------------------- 1 | ## Filter CSV records matching a predicate 2 | 3 | [![csv-badge]][csv] [![cat-encoding-badge]][cat-encoding] 4 | 5 | Returns _only_ the rows from `data` with a field that matches `query`. 6 | 7 | ```rust,edition2018 8 | # use error_chain::error_chain; 9 | 10 | use std::io; 11 | # 12 | # error_chain!{ 13 | # foreign_links { 14 | # Io(std::io::Error); 15 | # CsvError(csv::Error); 16 | # } 17 | # } 18 | 19 | fn main() -> Result<()> { 20 | let query = "CA"; 21 | let data = "\ 22 | City,State,Population,Latitude,Longitude 23 | Kenai,AK,7610,60.5544444,-151.2583333 24 | Oakman,AL,,33.7133333,-87.3886111 25 | Sandfort,AL,,32.3380556,-85.2233333 26 | West Hollywood,CA,37031,34.0900000,-118.3608333"; 27 | 28 | let mut rdr = csv::ReaderBuilder::new().from_reader(data.as_bytes()); 29 | let mut wtr = csv::Writer::from_writer(io::stdout()); 30 | 31 | wtr.write_record(rdr.headers()?)?; 32 | 33 | for result in rdr.records() { 34 | let record = result?; 35 | if record.iter().any(|field| field == query) { 36 | wtr.write_record(&record)?; 37 | } 38 | } 39 | 40 | wtr.flush()?; 41 | Ok(()) 42 | } 43 | ``` 44 | 45 | _Disclaimer: this example has been adapted from [the csv crate tutorial](https://docs.rs/csv/*/csv/tutorial/index.html#filter-by-search)_. 46 | -------------------------------------------------------------------------------- /src/encoding/csv/invalid.md: -------------------------------------------------------------------------------- 1 | ## Handle invalid CSV data with Serde 2 | 3 | [![csv-badge]][csv] [![serde-badge]][serde] [![cat-encoding-badge]][cat-encoding] 4 | 5 | CSV files often contain invalid data. For these cases, the `csv` crate 6 | provides a custom deserializer, [`csv::invalid_option`], which automatically 7 | converts invalid data to None values. 8 | 9 | ```rust,edition2018 10 | use csv::Error; 11 | use serde::Deserialize; 12 | 13 | #[derive(Debug, Deserialize)] 14 | struct Record { 15 | name: String, 16 | place: String, 17 | #[serde(deserialize_with = "csv::invalid_option")] 18 | id: Option, 19 | } 20 | 21 | fn main() -> Result<(), Error> { 22 | let data = "name,place,id 23 | mark,sydney,46.5 24 | ashley,zurich,92 25 | akshat,delhi,37 26 | alisha,colombo,xyz"; 27 | 28 | let mut rdr = csv::Reader::from_reader(data.as_bytes()); 29 | for result in rdr.deserialize() { 30 | let record: Record = result?; 31 | println!("{:?}", record); 32 | } 33 | 34 | Ok(()) 35 | } 36 | ``` 37 | 38 | [`csv::invalid_option`]: https://docs.rs/csv/*/csv/fn.invalid_option.html 39 | -------------------------------------------------------------------------------- /src/encoding/csv/read.md: -------------------------------------------------------------------------------- 1 | ## Read CSV records 2 | 3 | [![csv-badge]][csv] [![cat-encoding-badge]][cat-encoding] 4 | 5 | Reads standard CSV records into [`csv::StringRecord`] — a weakly typed 6 | data representation which expects valid UTF-8 rows. Alternatively, 7 | [`csv::ByteRecord`] makes no assumptions about UTF-8. 8 | 9 | ```rust,edition2018 10 | use csv::Error; 11 | 12 | fn main() -> Result<(), Error> { 13 | let csv = "year,make,model,description 14 | 1948,Porsche,356,Luxury sports car 15 | 1967,Ford,Mustang fastback 1967,American car"; 16 | 17 | let mut reader = csv::Reader::from_reader(csv.as_bytes()); 18 | for record in reader.records() { 19 | let record = record?; 20 | println!( 21 | "In {}, {} built the {} model. It is a {}.", 22 | &record[0], 23 | &record[1], 24 | &record[2], 25 | &record[3] 26 | ); 27 | } 28 | 29 | Ok(()) 30 | } 31 | ``` 32 | 33 | Serde deserializes data into strongly type structures. See the 34 | [`csv::Reader::deserialize`] method. 35 | 36 | ```rust,edition2018 37 | use serde::Deserialize; 38 | #[derive(Deserialize)] 39 | struct Record { 40 | year: u16, 41 | make: String, 42 | model: String, 43 | description: String, 44 | } 45 | 46 | fn main() -> Result<(), csv::Error> { 47 | let csv = "year,make,model,description 48 | 1948,Porsche,356,Luxury sports car 49 | 1967,Ford,Mustang fastback 1967,American car"; 50 | 51 | let mut reader = csv::Reader::from_reader(csv.as_bytes()); 52 | 53 | for record in reader.deserialize() { 54 | let record: Record = record?; 55 | println!( 56 | "In {}, {} built the {} model. It is a {}.", 57 | record.year, 58 | record.make, 59 | record.model, 60 | record.description 61 | ); 62 | } 63 | 64 | Ok(()) 65 | } 66 | ``` 67 | 68 | [`csv::ByteRecord`]: https://docs.rs/csv/*/csv/struct.ByteRecord.html 69 | [`csv::Reader::deserialize`]: https://docs.rs/csv/*/csv/struct.Reader.html#method.deserialize 70 | [`csv::StringRecord`]: https://docs.rs/csv/*/csv/struct.StringRecord.html 71 | -------------------------------------------------------------------------------- /src/encoding/csv/serde-serialize.md: -------------------------------------------------------------------------------- 1 | ## Serialize records to CSV using Serde 2 | 3 | [![csv-badge]][csv] [![serde-badge]][serde] [![cat-encoding-badge]][cat-encoding] 4 | 5 | The following example shows how to serialize custom structs as CSV records using 6 | the [serde] crate. 7 | 8 | ```rust,edition2018 9 | # use error_chain::error_chain; 10 | use serde::Serialize; 11 | use std::io; 12 | # 13 | # error_chain! { 14 | # foreign_links { 15 | # IOError(std::io::Error); 16 | # CSVError(csv::Error); 17 | # } 18 | # } 19 | 20 | #[derive(Serialize)] 21 | struct Record<'a> { 22 | name: &'a str, 23 | place: &'a str, 24 | id: u64, 25 | } 26 | 27 | fn main() -> Result<()> { 28 | let mut wtr = csv::Writer::from_writer(io::stdout()); 29 | 30 | let rec1 = Record { name: "Mark", place: "Melbourne", id: 56}; 31 | let rec2 = Record { name: "Ashley", place: "Sydney", id: 64}; 32 | let rec3 = Record { name: "Akshat", place: "Delhi", id: 98}; 33 | 34 | wtr.serialize(rec1)?; 35 | wtr.serialize(rec2)?; 36 | wtr.serialize(rec3)?; 37 | 38 | wtr.flush()?; 39 | 40 | Ok(()) 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /src/encoding/csv/serialize.md: -------------------------------------------------------------------------------- 1 | ## Serialize records to CSV 2 | 3 | [![csv-badge]][csv] [![cat-encoding-badge]][cat-encoding] 4 | 5 | This example shows how to serialize a Rust tuple. [`csv::writer`] supports automatic 6 | serialization from Rust types into CSV records. [`write_record`] writes 7 | a simple record containing string data only. Data with more complex values 8 | such as numbers, floats, and options use [`serialize`]. Since CSV 9 | writer uses internal buffer, always explicitly [`flush`] when done. 10 | 11 | ```rust,edition2018 12 | # use error_chain::error_chain; 13 | 14 | use std::io; 15 | # 16 | # error_chain! { 17 | # foreign_links { 18 | # CSVError(csv::Error); 19 | # IOError(std::io::Error); 20 | # } 21 | # } 22 | 23 | fn main() -> Result<()> { 24 | let mut wtr = csv::Writer::from_writer(io::stdout()); 25 | 26 | wtr.write_record(&["Name", "Place", "ID"])?; 27 | 28 | wtr.serialize(("Mark", "Sydney", 87))?; 29 | wtr.serialize(("Ashley", "Dublin", 32))?; 30 | wtr.serialize(("Akshat", "Delhi", 11))?; 31 | 32 | wtr.flush()?; 33 | Ok(()) 34 | } 35 | ``` 36 | 37 | [`csv::Writer`]: https://docs.rs/csv/*/csv/struct.Writer.html 38 | [`flush`]: https://docs.rs/csv/*/csv/struct.Writer.html#method.flush 39 | [`serialize`]: https://docs.rs/csv/*/csv/struct.Writer.html#method.serialize 40 | [`write_record`]: https://docs.rs/csv/*/csv/struct.Writer.html#method.write_record 41 | -------------------------------------------------------------------------------- /src/encoding/string/base64.md: -------------------------------------------------------------------------------- 1 | ## Encode and decode base64 2 | 3 | [![base64-badge]][base64] [![cat-encoding-badge]][cat-encoding] 4 | 5 | Encodes byte slice into `base64` String using [`encode`] 6 | and decodes it with [`decode`]. 7 | 8 | ```rust,edition2018 9 | # use error_chain::error_chain; 10 | 11 | use std::str; 12 | use base64::{encode, decode}; 13 | # 14 | # error_chain! { 15 | # foreign_links { 16 | # Base64(base64::DecodeError); 17 | # Utf8Error(str::Utf8Error); 18 | # } 19 | # } 20 | 21 | fn main() -> Result<()> { 22 | let hello = b"hello rustaceans"; 23 | let encoded = encode(hello); 24 | let decoded = decode(&encoded)?; 25 | 26 | println!("origin: {}", str::from_utf8(hello)?); 27 | println!("base64 encoded: {}", encoded); 28 | println!("back to origin: {}", str::from_utf8(&decoded)?); 29 | 30 | Ok(()) 31 | } 32 | ``` 33 | 34 | [`decode`]: https://docs.rs/base64/*/base64/fn.decode.html 35 | [`encode`]: https://docs.rs/base64/*/base64/fn.encode.html 36 | -------------------------------------------------------------------------------- /src/encoding/string/hex.md: -------------------------------------------------------------------------------- 1 | ## Encode and decode hex 2 | 3 | [![data-encoding-badge]][data-encoding] [![cat-encoding-badge]][cat-encoding] 4 | 5 | The [`data_encoding`] crate provides a `HEXUPPER::encode` method which 6 | takes a `&[u8]` and returns a `String` containing the hexadecimal 7 | representation of the data. 8 | 9 | Similarly, a `HEXUPPER::decode` method is provided which takes a `&[u8]` and 10 | returns a `Vec` if the input data is successfully decoded. 11 | 12 | The example below converts `&[u8]` data to hexadecimal equivalent. Compares this 13 | value to the expected value. 14 | 15 | ```rust,edition2018 16 | use data_encoding::{HEXUPPER, DecodeError}; 17 | 18 | fn main() -> Result<(), DecodeError> { 19 | let original = b"The quick brown fox jumps over the lazy dog."; 20 | let expected = "54686520717569636B2062726F776E20666F78206A756D7073206F76\ 21 | 657220746865206C617A7920646F672E"; 22 | 23 | let encoded = HEXUPPER.encode(original); 24 | assert_eq!(encoded, expected); 25 | 26 | let decoded = HEXUPPER.decode(&encoded.into_bytes())?; 27 | assert_eq!(&decoded[..], &original[..]); 28 | 29 | Ok(()) 30 | } 31 | ``` 32 | 33 | [`data_encoding`]: https://docs.rs/data-encoding/*/data_encoding/ 34 | -------------------------------------------------------------------------------- /src/encoding/string/percent-encode.md: -------------------------------------------------------------------------------- 1 | ## Percent-encode a string 2 | 3 | [![percent-encoding-badge]][percent-encoding] [![cat-encoding-badge]][cat-encoding] 4 | 5 | Encode an input string with [percent-encoding][percent-encoding-wiki] using the 6 | [`utf8_percent_encode`] function from the `percent-encoding` crate. Then decode 7 | using the [`percent_decode`] function. 8 | 9 | ```rust,edition2018 10 | use percent_encoding::{utf8_percent_encode, percent_decode, AsciiSet, CONTROLS}; 11 | use std::str::Utf8Error; 12 | 13 | /// https://url.spec.whatwg.org/#fragment-percent-encode-set 14 | const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`'); 15 | 16 | fn main() -> Result<(), Utf8Error> { 17 | let input = "confident, productive systems programming"; 18 | 19 | let iter = utf8_percent_encode(input, FRAGMENT); 20 | let encoded: String = iter.collect(); 21 | assert_eq!(encoded, "confident,%20productive%20systems%20programming"); 22 | 23 | let iter = percent_decode(encoded.as_bytes()); 24 | let decoded = iter.decode_utf8()?; 25 | assert_eq!(decoded, "confident, productive systems programming"); 26 | 27 | Ok(()) 28 | } 29 | ``` 30 | 31 | The encode set defines which bytes (in addition to non-ASCII and controls) need 32 | to be percent-encoded. The choice of this set depends on context. For example, 33 | `url` encodes `?` in a URL path but not in a query string. 34 | 35 | The return value of encoding is an iterator of `&str` slices which collect into 36 | a `String`. 37 | 38 | [`percent_decode`]: https://docs.rs/percent-encoding/*/percent_encoding/fn.percent_decode.html 39 | [`utf8_percent_encode`]: https://docs.rs/percent-encoding/*/percent_encoding/fn.utf8_percent_encode.html 40 | 41 | [percent-encoding]: https://docs.rs/percent-encoding/ 42 | [percent-encoding-wiki]: https://en.wikipedia.org/wiki/Percent-encoding 43 | -------------------------------------------------------------------------------- /src/encoding/string/url-encode.md: -------------------------------------------------------------------------------- 1 | ## Encode a string as application/x-www-form-urlencoded 2 | 3 | [![url-badge]][url] [![cat-encoding-badge]][cat-encoding] 4 | 5 | Encodes a string into [application/x-www-form-urlencoded] syntax 6 | using the [`form_urlencoded::byte_serialize`] and subsequently 7 | decodes it with [`form_urlencoded::parse`]. Both functions return iterators 8 | that collect into a `String`. 9 | 10 | ```rust,edition2018 11 | use url::form_urlencoded::{byte_serialize, parse}; 12 | 13 | fn main() { 14 | let urlencoded: String = byte_serialize("What is ❤?".as_bytes()).collect(); 15 | assert_eq!(urlencoded, "What+is+%E2%9D%A4%3F"); 16 | println!("urlencoded:'{}'", urlencoded); 17 | 18 | let decoded: String = parse(urlencoded.as_bytes()) 19 | .map(|(key, val)| [key, val].concat()) 20 | .collect(); 21 | assert_eq!(decoded, "What is ❤?"); 22 | println!("decoded:'{}'", decoded); 23 | } 24 | ``` 25 | 26 | [`form_urlencoded::byte_serialize`]: https://docs.rs/url/*/url/form_urlencoded/fn.byte_serialize.html 27 | [`form_urlencoded::parse`]: https://docs.rs/url/*/url/form_urlencoded/fn.parse.html 28 | 29 | [application/x-www-form-urlencoded]: https://url.spec.whatwg.org/#application/x-www-form-urlencoded 30 | -------------------------------------------------------------------------------- /src/encoding/strings.md: -------------------------------------------------------------------------------- 1 | # Character Sets 2 | 3 | {{#include string/percent-encode.md}} 4 | 5 | {{#include string/url-encode.md}} 6 | 7 | {{#include string/hex.md}} 8 | 9 | {{#include string/base64.md}} 10 | 11 | {{#include ../links.md}} 12 | -------------------------------------------------------------------------------- /src/errors.md: -------------------------------------------------------------------------------- 1 | # Error Handling 2 | 3 | | Recipe | Crates | Categories | 4 | |--------|--------|------------| 5 | | [Composing errors with an enum][ex-thiserror] | [![thiserror-badge]][thiserror] | [![cat-rust-patterns-badge]][cat-rust-patterns] | 6 | | [Dynamic errors with anyhow][ex-anyhow] | [![anyhow-badge]][anyhow] | [![cat-rust-patterns-badge]][cat-rust-patterns] | 7 | | [Handle errors correctly in main][ex-error-chain-simple-error-handling] | [![error-chain-badge]][error-chain] | [![cat-rust-patterns-badge]][cat-rust-patterns] | 8 | 9 | [ex-thiserror]: errors/handle.html#thiserror 10 | [ex-anyhow]: errors/handle.html#anyhow 11 | [ex-error-chain-simple-error-handling]: errors/handle.html#handle-errors-correctly-in-main 12 | 13 | {{#include links.md}} 14 | -------------------------------------------------------------------------------- /src/errors/handle.md: -------------------------------------------------------------------------------- 1 | # Error Handling 2 | 3 | {{#include handle/main.md}} 4 | 5 | {{#include ../links.md}} 6 | -------------------------------------------------------------------------------- /src/file/dir.md: -------------------------------------------------------------------------------- 1 | # Directory Traversal 2 | 3 | {{#include dir/modified.md}} 4 | 5 | {{#include dir/loops.md}} 6 | 7 | {{#include dir/duplicate-name.md}} 8 | 9 | {{#include dir/find-file.md}} 10 | 11 | {{#include dir/skip-dot.md}} 12 | 13 | {{#include dir/sizes.md}} 14 | 15 | {{#include dir/png.md}} 16 | 17 | {{#include dir/ignore-case.md}} 18 | 19 | {{#include ../links.md}} 20 | -------------------------------------------------------------------------------- /src/file/dir/duplicate-name.md: -------------------------------------------------------------------------------- 1 | ## Recursively find duplicate file names 2 | 3 | [![walkdir-badge]][walkdir] [![cat-filesystem-badge]][cat-filesystem] 4 | 5 | Find recursively in the current directory duplicate filenames, 6 | printing them only once. 7 | 8 | ```rust,edition2018,no_run 9 | use std::collections::HashMap; 10 | use walkdir::WalkDir; 11 | 12 | fn main() { 13 | let mut filenames = HashMap::new(); 14 | 15 | for entry in WalkDir::new(".") 16 | .into_iter() 17 | .filter_map(Result::ok) 18 | .filter(|e| !e.file_type().is_dir()) { 19 | let f_name = String::from(entry.file_name().to_string_lossy()); 20 | let counter = filenames.entry(f_name.clone()).or_insert(0); 21 | *counter += 1; 22 | 23 | if *counter == 2 { 24 | println!("{}", f_name); 25 | } 26 | } 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /src/file/dir/find-file.md: -------------------------------------------------------------------------------- 1 | ## Recursively find all files with given predicate 2 | 3 | [![walkdir-badge]][walkdir] [![cat-filesystem-badge]][cat-filesystem] 4 | 5 | Find JSON files modified within the last day in the current directory. 6 | Using [`follow_links`] ensures symbolic links are followed like they were 7 | normal directories and files. 8 | 9 | ```rust,edition2018,no_run 10 | # use error_chain::error_chain; 11 | 12 | use walkdir::WalkDir; 13 | # 14 | # error_chain! { 15 | # foreign_links { 16 | # WalkDir(walkdir::Error); 17 | # Io(std::io::Error); 18 | # SystemTime(std::time::SystemTimeError); 19 | # } 20 | # } 21 | 22 | fn main() -> Result<()> { 23 | for entry in WalkDir::new(".") 24 | .follow_links(true) 25 | .into_iter() 26 | .filter_map(|e| e.ok()) { 27 | let f_name = entry.file_name().to_string_lossy(); 28 | let sec = entry.metadata()?.modified()?; 29 | 30 | if f_name.ends_with(".json") && sec.elapsed()?.as_secs() < 86400 { 31 | println!("{}", f_name); 32 | } 33 | } 34 | 35 | Ok(()) 36 | } 37 | ``` 38 | 39 | [`follow_links`]: https://docs.rs/walkdir/*/walkdir/struct.WalkDir.html#method.follow_links 40 | -------------------------------------------------------------------------------- /src/file/dir/ignore-case.md: -------------------------------------------------------------------------------- 1 | ## Find all files with given pattern ignoring filename case. 2 | 3 | [![glob-badge]][glob] [![cat-filesystem-badge]][cat-filesystem] 4 | 5 | Find all image files in the `/media/` directory matching the `img_[0-9]*.png` pattern. 6 | 7 | A custom [`MatchOptions`] struct is passed to the [`glob_with`] function making the glob pattern case insensitive while keeping the other options [`Default`]. 8 | 9 | ```rust,edition2018,no_run 10 | use error_chain::error_chain; 11 | use glob::{glob_with, MatchOptions}; 12 | 13 | error_chain! { 14 | foreign_links { 15 | Glob(glob::GlobError); 16 | Pattern(glob::PatternError); 17 | } 18 | } 19 | 20 | fn main() -> Result<()> { 21 | let options = MatchOptions { 22 | case_sensitive: false, 23 | ..Default::default() 24 | }; 25 | 26 | for entry in glob_with("/media/img_[0-9]*.png", options)? { 27 | println!("{}", entry?.display()); 28 | } 29 | 30 | Ok(()) 31 | } 32 | ``` 33 | 34 | [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html 35 | [`glob_with`]: https://docs.rs/glob/*/glob/fn.glob_with.html 36 | [`MatchOptions`]: https://docs.rs/glob/*/glob/struct.MatchOptions.html 37 | -------------------------------------------------------------------------------- /src/file/dir/loops.md: -------------------------------------------------------------------------------- 1 | ## Find loops for a given path 2 | 3 | [![same_file-badge]][same_file] [![cat-filesystem-badge]][cat-filesystem] 4 | 5 | Use [`same_file::is_same_file`] to detect loops for a given path. 6 | For example, a loop could be created on a Unix system via symlinks: 7 | ```bash 8 | mkdir -p /tmp/foo/bar/baz 9 | ln -s /tmp/foo/ /tmp/foo/bar/baz/qux 10 | ``` 11 | The following would assert that a loop exists. 12 | 13 | ```rust,edition2018,no_run 14 | use std::io; 15 | use std::path::{Path, PathBuf}; 16 | use same_file::is_same_file; 17 | 18 | fn contains_loop>(path: P) -> io::Result> { 19 | let path = path.as_ref(); 20 | let mut path_buf = path.to_path_buf(); 21 | while path_buf.pop() { 22 | if is_same_file(&path_buf, path)? { 23 | return Ok(Some((path_buf, path.to_path_buf()))); 24 | } else if let Some(looped_paths) = contains_loop(&path_buf)? { 25 | return Ok(Some(looped_paths)); 26 | } 27 | } 28 | return Ok(None); 29 | } 30 | 31 | fn main() { 32 | assert_eq!( 33 | contains_loop("/tmp/foo/bar/baz/qux/bar/baz").unwrap(), 34 | Some(( 35 | PathBuf::from("/tmp/foo"), 36 | PathBuf::from("/tmp/foo/bar/baz/qux") 37 | )) 38 | ); 39 | } 40 | ``` 41 | 42 | [`same_file::is_same_file`]: https://docs.rs/same-file/*/same_file/fn.is_same_file.html 43 | -------------------------------------------------------------------------------- /src/file/dir/png.md: -------------------------------------------------------------------------------- 1 | ## Find all png files recursively 2 | 3 | [![glob-badge]][glob] [![cat-filesystem-badge]][cat-filesystem] 4 | 5 | Recursively find all PNG files in the current directory. 6 | In this case, the `**` pattern matches the current directory and all subdirectories. 7 | 8 | Use the `**` pattern in any path portion. For example, `/media/**/*.png` 9 | matches all PNGs in `media` and it's subdirectories. 10 | 11 | ```rust,edition2018,no_run 12 | # use error_chain::error_chain; 13 | 14 | use glob::glob; 15 | # 16 | # error_chain! { 17 | # foreign_links { 18 | # Glob(glob::GlobError); 19 | # Pattern(glob::PatternError); 20 | # } 21 | # } 22 | 23 | fn main() -> Result<()> { 24 | for entry in glob("**/*.png")? { 25 | println!("{}", entry?.display()); 26 | } 27 | 28 | Ok(()) 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /src/file/dir/sizes.md: -------------------------------------------------------------------------------- 1 | ## Recursively calculate file sizes at given depth 2 | 3 | [![walkdir-badge]][walkdir] [![cat-filesystem-badge]][cat-filesystem] 4 | 5 | Recursion depth can be flexibly set by [`WalkDir::min_depth`] & [`WalkDir::max_depth`] methods. 6 | Calculates sum of all file sizes to 3 subfolders depth, ignoring files in the root folder. 7 | 8 | ```rust,edition2018 9 | use walkdir::WalkDir; 10 | 11 | fn main() { 12 | let total_size = WalkDir::new(".") 13 | .min_depth(1) 14 | .max_depth(3) 15 | .into_iter() 16 | .filter_map(|entry| entry.ok()) 17 | .filter_map(|entry| entry.metadata().ok()) 18 | .filter(|metadata| metadata.is_file()) 19 | .fold(0, |acc, m| acc + m.len()); 20 | 21 | println!("Total size: {} bytes.", total_size); 22 | } 23 | ``` 24 | 25 | [`WalkDir::max_depth`]: https://docs.rs/walkdir/*/walkdir/struct.WalkDir.html#method.max_depth 26 | [`WalkDir::min_depth`]: https://docs.rs/walkdir/*/walkdir/struct.WalkDir.html#method.min_depth 27 | -------------------------------------------------------------------------------- /src/file/dir/skip-dot.md: -------------------------------------------------------------------------------- 1 | ## Traverse directories while skipping dotfiles 2 | 3 | [![walkdir-badge]][walkdir] [![cat-filesystem-badge]][cat-filesystem] 4 | 5 | Uses [`filter_entry`] to descend recursively into entries passing the 6 | `is_not_hidden` predicate thus skipping hidden files and directories. 7 | [`Iterator::filter`] applies to each [`WalkDir::DirEntry`] even if the parent 8 | is a hidden directory. 9 | 10 | Root dir `"."` yields through [`WalkDir::depth`] usage in `is_not_hidden` 11 | predicate. 12 | 13 | ```rust,edition2018,no_run 14 | use walkdir::{DirEntry, WalkDir}; 15 | 16 | fn is_not_hidden(entry: &DirEntry) -> bool { 17 | entry 18 | .file_name() 19 | .to_str() 20 | .map(|s| entry.depth() == 0 || !s.starts_with(".")) 21 | .unwrap_or(false) 22 | } 23 | 24 | fn main() { 25 | WalkDir::new(".") 26 | .into_iter() 27 | .filter_entry(|e| is_not_hidden(e)) 28 | .filter_map(|v| v.ok()) 29 | .for_each(|x| println!("{}", x.path().display())); 30 | } 31 | ``` 32 | 33 | [`filter_entry`]: https://docs.rs/walkdir/*/walkdir/struct.IntoIter.html#method.filter_entry 34 | [`Iterator::filter`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter 35 | [`WalkDir::depth`]: https://docs.rs/walkdir/*/walkdir/struct.DirEntry.html#method.depth 36 | [`WalkDir::DirEntry`]: https://docs.rs/walkdir/*/walkdir/struct.DirEntry.html 37 | -------------------------------------------------------------------------------- /src/file/read-write.md: -------------------------------------------------------------------------------- 1 | # Read & Write 2 | 3 | {{#include read-write/read-file.md}} 4 | 5 | {{#include read-write/same-file.md}} 6 | 7 | {{#include read-write/memmap.md}} 8 | 9 | {{#include ../links.md}} 10 | -------------------------------------------------------------------------------- /src/file/read-write/memmap.md: -------------------------------------------------------------------------------- 1 | ## Access a file randomly using a memory map 2 | 3 | [![memmap-badge]][memmap] [![cat-filesystem-badge]][cat-filesystem] 4 | 5 | Creates a memory map of a file using [memmap] and simulates some non-sequential 6 | reads from the file. Using a memory map means you just index into a slice rather 7 | than dealing with [`seek`] to navigate a File. 8 | 9 | The [`Mmap::map`] function assumes the file 10 | behind the memory map is not being modified at the same time by another process 11 | or else a [race condition] occurs. 12 | 13 | ```rust,edition2018 14 | use memmap::Mmap; 15 | use std::fs::File; 16 | use std::io::{Write, Error}; 17 | 18 | fn main() -> Result<(), Error> { 19 | # write!(File::create("content.txt")?, "My hovercraft is full of eels!")?; 20 | # 21 | let file = File::open("content.txt")?; 22 | let map = unsafe { Mmap::map(&file)? }; 23 | 24 | let random_indexes = [0, 1, 2, 19, 22, 10, 11, 29]; 25 | assert_eq!(&map[3..13], b"hovercraft"); 26 | let random_bytes: Vec = random_indexes.iter() 27 | .map(|&idx| map[idx]) 28 | .collect(); 29 | assert_eq!(&random_bytes[..], b"My loaf!"); 30 | Ok(()) 31 | } 32 | ``` 33 | 34 | [`Mmap::map`]: https://docs.rs/memmap/*/memmap/struct.Mmap.html#method.map 35 | [`seek`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.seek 36 | 37 | [race condition]: https://en.wikipedia.org/wiki/Race_condition#File_systems 38 | -------------------------------------------------------------------------------- /src/file/read-write/read-file.md: -------------------------------------------------------------------------------- 1 | ## Read lines of strings from a file 2 | 3 | [![std-badge]][std] [![cat-filesystem-badge]][cat-filesystem] 4 | 5 | Writes a three-line message to a file, then reads it back a line at a 6 | time with the [`Lines`] iterator created by 7 | [`BufRead::lines`]. [`File`] implements [`Read`] which provides [`BufReader`] 8 | trait. [`File::create`] opens a [`File`] for writing, [`File::open`] for 9 | reading. 10 | 11 | ```rust,edition2018 12 | use std::fs::File; 13 | use std::io::{Write, BufReader, BufRead, Error}; 14 | 15 | fn main() -> Result<(), Error> { 16 | let path = "lines.txt"; 17 | 18 | let mut output = File::create(path)?; 19 | write!(output, "Rust\n💖\nFun")?; 20 | 21 | let input = File::open(path)?; 22 | let buffered = BufReader::new(input); 23 | 24 | for line in buffered.lines() { 25 | println!("{}", line?); 26 | } 27 | 28 | Ok(()) 29 | } 30 | ``` 31 | 32 | [`BufRead::lines`]: https://doc.rust-lang.org/std/io/trait.BufRead.html#method.lines 33 | [`BufRead`]: https://doc.rust-lang.org/std/io/trait.BufRead.html 34 | [`BufReader`]: https://doc.rust-lang.org/std/io/struct.BufReader.html 35 | [`File::create`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.create 36 | [`File::open`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.open 37 | [`File`]: https://doc.rust-lang.org/std/fs/struct.File.html 38 | [`Lines`]: https://doc.rust-lang.org/std/io/struct.Lines.html 39 | [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html 40 | -------------------------------------------------------------------------------- /src/file/read-write/same-file.md: -------------------------------------------------------------------------------- 1 | ## Avoid writing and reading from a same file 2 | 3 | [![same_file-badge]][same_file] [![cat-filesystem-badge]][cat-filesystem] 4 | 5 | Use [`same_file::Handle`] to a file that can be tested for equality with 6 | other handles. In this example, the handles of file to be read from and 7 | to be written to are tested for equality. 8 | 9 | ```rust,edition2018,no_run 10 | use same_file::Handle; 11 | use std::fs::File; 12 | use std::io::{BufRead, BufReader, Error, ErrorKind}; 13 | use std::path::Path; 14 | 15 | fn main() -> Result<(), Error> { 16 | let path_to_read = Path::new("new.txt"); 17 | 18 | let stdout_handle = Handle::stdout()?; 19 | let handle = Handle::from_path(path_to_read)?; 20 | 21 | if stdout_handle == handle { 22 | return Err(Error::new( 23 | ErrorKind::Other, 24 | "You are reading and writing to the same file", 25 | )); 26 | } else { 27 | let file = File::open(&path_to_read)?; 28 | let file = BufReader::new(file); 29 | for (num, line) in file.lines().enumerate() { 30 | println!("{} : {}", num, line?.to_uppercase()); 31 | } 32 | } 33 | 34 | Ok(()) 35 | } 36 | ``` 37 | 38 | ```bash 39 | cargo run 40 | ``` 41 | displays the contents of the file new.txt. 42 | 43 | ```bash 44 | cargo run >> ./new.txt 45 | ``` 46 | errors because the two files are same. 47 | 48 | [`same_file::Handle`]: https://docs.rs/same-file/*/same_file/struct.Handle.html 49 | -------------------------------------------------------------------------------- /src/hardware.md: -------------------------------------------------------------------------------- 1 | # Hardware Support 2 | 3 | | Recipe | Crates | Categories | 4 | |--------|--------|------------| 5 | | [Check number of logical cpu cores][ex-check-cpu-cores] | [![num_cpus-badge]][num_cpus] | [![cat-hardware-support-badge]][cat-hardware-support] | 6 | 7 | [ex-check-cpu-cores]: hardware/processor.html#check-number-of-logical-cpu-cores 8 | 9 | {{#include links.md}} 10 | -------------------------------------------------------------------------------- /src/hardware/processor.md: -------------------------------------------------------------------------------- 1 | # Processor 2 | 3 | {{#include processor/cpu-count.md}} 4 | 5 | {{#include ../links.md}} 6 | -------------------------------------------------------------------------------- /src/hardware/processor/cpu-count.md: -------------------------------------------------------------------------------- 1 | ## Check number of logical cpu cores 2 | 3 | [![num_cpus-badge]][num_cpus] [![cat-hardware-support-badge]][cat-hardware-support] 4 | 5 | Shows the number of logical CPU cores in current machine using [`num_cpus::get`]. 6 | 7 | ```rust,edition2018 8 | fn main() { 9 | println!("Number of logical cores is {}", num_cpus::get()); 10 | } 11 | ``` 12 | -------------------------------------------------------------------------------- /src/intro.md: -------------------------------------------------------------------------------- 1 | # Cookin' with Rust 2 | 3 | This _Rust Cookbook_ is a collection of 4 | simple examples that demonstrate good practices to accomplish common 5 | programming tasks, using the crates of the Rust ecosystem. 6 | 7 | [Read more about _Rust Cookbook_](about.html), including tips for 8 | how to read the book, how to use the examples, and notes on conventions. 9 | 10 | ## Contributing 11 | 12 | This project is intended to be easy for new Rust programmers to 13 | contribute to, and an easy way to get involved with the Rust 14 | community. It needs and welcomes help. For details see 15 | [CONTRIBUTING.md]. 16 | 17 | [CONTRIBUTING.md]: https://github.com/rust-lang-nursery/rust-cookbook/blob/master/CONTRIBUTING.md 18 | 19 | {{#include algorithms.md}} 20 | 21 | {{#include cli.md}} 22 | 23 | {{#include compression.md}} 24 | 25 | {{#include concurrency.md}} 26 | 27 | {{#include cryptography.md}} 28 | 29 | {{#include data_structures.md}} 30 | 31 | {{#include database.md}} 32 | 33 | {{#include datetime.md}} 34 | 35 | {{#include development_tools.md}} 36 | 37 | {{#include encoding.md}} 38 | 39 | {{#include file.md}} 40 | 41 | {{#include hardware.md}} 42 | 43 | {{#include mem.md}} 44 | 45 | {{#include net.md}} 46 | 47 | {{#include os.md}} 48 | 49 | {{#include science.md}} 50 | 51 | {{#include text.md}} 52 | 53 | {{#include web.md}} 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("This program does nothing."); 3 | println!("See documentation at https://github.com/rust-lang-nursery/rust-cookbook"); 4 | } 5 | -------------------------------------------------------------------------------- /src/mem.md: -------------------------------------------------------------------------------- 1 | # Memory Management 2 | 3 | | Recipe | Crates | Categories | 4 | |--------|--------|------------| 5 | | [Declare lazily evaluated constant][ex-lazy-constant] | [![lazy_static-badge]][lazy_static] | [![cat-caching-badge]][cat-caching] [![cat-rust-patterns-badge]][cat-rust-patterns] | 6 | 7 | [ex-lazy-constant]: mem/global_static.html#declare-lazily-evaluated-constant 8 | 9 | {{#include links.md}} 10 | -------------------------------------------------------------------------------- /src/mem/global_static.md: -------------------------------------------------------------------------------- 1 | # Constants 2 | 3 | {{#include global_static/lazy-constant.md}} 4 | 5 | {{#include ../links.md}} 6 | -------------------------------------------------------------------------------- /src/mem/global_static/lazy-constant.md: -------------------------------------------------------------------------------- 1 | ## Declare lazily evaluated constant 2 | 3 | [![lazy_static-badge]][lazy_static] [![cat-caching-badge]][cat-caching] [![cat-rust-patterns-badge]][cat-rust-patterns] 4 | 5 | Declares a lazily evaluated constant [`HashMap`]. The [`HashMap`] will 6 | be evaluated once and stored behind a global static reference. 7 | 8 | ```rust,edition2018 9 | use lazy_static::lazy_static; 10 | use std::collections::HashMap; 11 | 12 | lazy_static! { 13 | static ref PRIVILEGES: HashMap<&'static str, Vec<&'static str>> = { 14 | let mut map = HashMap::new(); 15 | map.insert("James", vec!["user", "admin"]); 16 | map.insert("Jim", vec!["user"]); 17 | map 18 | }; 19 | } 20 | 21 | fn show_access(name: &str) { 22 | let access = PRIVILEGES.get(name); 23 | println!("{}: {:?}", name, access); 24 | } 25 | 26 | fn main() { 27 | let access = PRIVILEGES.get("James"); 28 | println!("James: {:?}", access); 29 | 30 | show_access("Jim"); 31 | } 32 | ``` 33 | 34 | [`HashMap`]: https://doc.rust-lang.org/std/collections/struct.HashMap.html 35 | -------------------------------------------------------------------------------- /src/net.md: -------------------------------------------------------------------------------- 1 | # Networking 2 | 3 | 4 | | Recipe | Crates | Categories | 5 | |--------|--------|------------| 6 | | [Listen on unused port TCP/IP][ex-random-port-tcp] | [![std-badge]][std] | [![cat-net-badge]][cat-net] | 7 | 8 | [ex-random-port-tcp]: net/server.html#listen-on-unused-port-tcpip 9 | 10 | {{#include links.md}} 11 | -------------------------------------------------------------------------------- /src/net/server.md: -------------------------------------------------------------------------------- 1 | # Server 2 | 3 | {{#include server/listen-unused.md}} 4 | 5 | {{#include ../links.md}} 6 | -------------------------------------------------------------------------------- /src/net/server/listen-unused.md: -------------------------------------------------------------------------------- 1 | ## Listen on unused port TCP/IP 2 | 3 | [![std-badge]][std] [![cat-net-badge]][cat-net] 4 | 5 | In this example, the port is displayed on the console, and the program will 6 | listen until a request is made. `TcpListener::bind` uses a random port 7 | allocated by the OS when requested to bind to port 0. 8 | 9 | ```rust,edition2018,no_run 10 | use std::net::TcpListener; 11 | use std::io::{Read, Error}; 12 | 13 | fn main() -> Result<(), Error> { 14 | let listener = TcpListener::bind("localhost:0")?; 15 | let port = listener.local_addr()?; 16 | println!("Listening on {}, access this port to end the program", port); 17 | let (mut tcp_stream, addr) = listener.accept()?; //block until requested 18 | println!("Connection received! {:?} is sending data.", addr); 19 | let mut input = String::new(); 20 | let _ = tcp_stream.read_to_string(&mut input)?; 21 | println!("{:?} says {}", addr, input); 22 | Ok(()) 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /src/os.md: -------------------------------------------------------------------------------- 1 | # Operating System 2 | 3 | | Recipe | Crates | Categories | 4 | |--------|--------|------------| 5 | | [Run an external command and process stdout][ex-parse-subprocess-output] | [![regex-badge]][regex] | [![cat-os-badge]][cat-os] [![cat-text-processing-badge]][cat-text-processing] | 6 | | [Run an external command passing it stdin and check for an error code][ex-parse-subprocess-input] | [![regex-badge]][regex] | [![cat-os-badge]][cat-os] [![cat-text-processing-badge]][cat-text-processing] | 7 | | [Run piped external commands][ex-run-piped-external-commands] | [![std-badge]][std] | [![cat-os-badge]][cat-os] | 8 | | [Redirect both stdout and stderr of child process to the same file][ex-redirect-stdout-stderr-same-file] | [![std-badge]][std] | [![cat-os-badge]][cat-os] | 9 | | [Continuously process child process' outputs][ex-continuous-process-output] | [![std-badge]][std] | [![cat-os-badge]][cat-os][![cat-text-processing-badge]][cat-text-processing] | 10 | | [Read environment variable][ex-read-env-variable] | [![std-badge]][std] | [![cat-os-badge]][cat-os] | 11 | 12 | 13 | [ex-parse-subprocess-output]: os/external.html#run-an-external-command-and-process-stdout 14 | [ex-parse-subprocess-input]: os/external.html#run-an-external-command-passing-it-stdin-and-check-for-an-error-code 15 | [ex-run-piped-external-commands]: os/external.html#run-piped-external-commands 16 | [ex-redirect-stdout-stderr-same-file]: os/external.html#redirect-both-stdout-and-stderr-of-child-process-to-the-same-file 17 | [ex-continuous-process-output]: os/external.html#continuously-process-child-process-outputs 18 | [ex-read-env-variable]: os/external.html#read-environment-variable 19 | 20 | 21 | {{#include links.md}} 22 | -------------------------------------------------------------------------------- /src/os/external.md: -------------------------------------------------------------------------------- 1 | # External Command 2 | 3 | {{#include external/process-output.md}} 4 | 5 | {{#include external/send-input.md}} 6 | 7 | {{#include external/piped.md}} 8 | 9 | {{#include external/error-file.md}} 10 | 11 | {{#include external/continuous.md}} 12 | 13 | {{#include external/read-env-variable.md}} 14 | 15 | {{#include ../links.md}} 16 | -------------------------------------------------------------------------------- /src/os/external/continuous.md: -------------------------------------------------------------------------------- 1 | ## Continuously process child process' outputs 2 | 3 | [![std-badge]][std] [![cat-os-badge]][cat-os] 4 | 5 | In [Run an external command and process stdout](#run-an-external-command-and-process-stdout), 6 | processing doesn't start until external [`Command`] is finished. 7 | The recipe below calls [`Stdio::piped`] to create a pipe, and reads 8 | `stdout` continuously as soon as the [`BufReader`] is updated. 9 | 10 | The below recipe is equivalent to the Unix shell command 11 | `journalctl | grep usb`. 12 | 13 | ```rust,edition2018,no_run 14 | use std::process::{Command, Stdio}; 15 | use std::io::{BufRead, BufReader, Error, ErrorKind}; 16 | 17 | fn main() -> Result<(), Error> { 18 | let stdout = Command::new("journalctl") 19 | .stdout(Stdio::piped()) 20 | .spawn()? 21 | .stdout 22 | .ok_or_else(|| Error::new(ErrorKind::Other,"Could not capture standard output."))?; 23 | 24 | let reader = BufReader::new(stdout); 25 | 26 | reader 27 | .lines() 28 | .filter_map(|line| line.ok()) 29 | .filter(|line| line.find("usb").is_some()) 30 | .for_each(|line| println!("{}", line)); 31 | 32 | Ok(()) 33 | } 34 | ``` 35 | 36 | [`BufReader`]: https://doc.rust-lang.org/std/io/struct.BufReader.html 37 | [`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html 38 | [`Stdio::piped`]: https://doc.rust-lang.org/std/process/struct.Stdio.html 39 | -------------------------------------------------------------------------------- /src/os/external/error-file.md: -------------------------------------------------------------------------------- 1 | ## Redirect both stdout and stderr of child process to the same file 2 | 3 | [![std-badge]][std] [![cat-os-badge]][cat-os] 4 | 5 | Spawns a child process and redirects `stdout` and `stderr` to the same 6 | file. It follows the same idea as [run piped external 7 | commands](#run-piped-external-commands), however [`process::Stdio`] 8 | writes to a specified file. [`File::try_clone`] references the same file handle 9 | for `stdout` and `stderr`. It will ensure that both handles write with the same 10 | cursor position. 11 | 12 | The below recipe is equivalent to run the Unix shell command `ls 13 | . oops >out.txt 2>&1`. 14 | 15 | ```rust,edition2018,no_run 16 | use std::fs::File; 17 | use std::io::Error; 18 | use std::process::{Command, Stdio}; 19 | 20 | fn main() -> Result<(), Error> { 21 | let outputs = File::create("out.txt")?; 22 | let errors = outputs.try_clone()?; 23 | 24 | Command::new("ls") 25 | .args(&[".", "oops"]) 26 | .stdout(Stdio::from(outputs)) 27 | .stderr(Stdio::from(errors)) 28 | .spawn()? 29 | .wait_with_output()?; 30 | 31 | Ok(()) 32 | } 33 | ``` 34 | 35 | [`File::try_clone`]: https://doc.rust-lang.org/std/fs/struct.File.html#method.try_clone 36 | [`process::Stdio`]: https://doc.rust-lang.org/std/process/struct.Stdio.html 37 | -------------------------------------------------------------------------------- /src/os/external/piped.md: -------------------------------------------------------------------------------- 1 | ## Run piped external commands 2 | 3 | [![std-badge]][std] [![cat-os-badge]][cat-os] 4 | 5 | Shows up to the 10th biggest files and subdirectories in 6 | the current working directory. It is equivalent to running: `du -ah . | 7 | sort -hr | head -n 10`. 8 | 9 | [`Command`]s represent a process. Output of a child process is captured with a 10 | [`Stdio::piped`] between parent and child. 11 | 12 | ```rust,edition2018,no_run 13 | # use error_chain::error_chain; 14 | # 15 | use std::process::{Command, Stdio}; 16 | # 17 | # error_chain! { 18 | # foreign_links { 19 | # Io(std::io::Error); 20 | # Utf8(std::string::FromUtf8Error); 21 | # } 22 | # } 23 | 24 | fn main() -> Result<()> { 25 | let directory = std::env::current_dir()?; 26 | let mut du_output_child = Command::new("du") 27 | .arg("-ah") 28 | .arg(&directory) 29 | .stdout(Stdio::piped()) 30 | .spawn()?; 31 | 32 | if let Some(du_output) = du_output_child.stdout.take() { 33 | let mut sort_output_child = Command::new("sort") 34 | .arg("-hr") 35 | .stdin(du_output) 36 | .stdout(Stdio::piped()) 37 | .spawn()?; 38 | 39 | du_output_child.wait()?; 40 | 41 | if let Some(sort_output) = sort_output_child.stdout.take() { 42 | let head_output_child = Command::new("head") 43 | .args(&["-n", "10"]) 44 | .stdin(sort_output) 45 | .stdout(Stdio::piped()) 46 | .spawn()?; 47 | 48 | let head_stdout = head_output_child.wait_with_output()?; 49 | 50 | sort_output_child.wait()?; 51 | 52 | println!( 53 | "Top 10 biggest files and directories in '{}':\n{}", 54 | directory.display(), 55 | String::from_utf8(head_stdout.stdout).unwrap() 56 | ); 57 | } 58 | } 59 | 60 | Ok(()) 61 | } 62 | ``` 63 | 64 | [`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html 65 | [`Stdio::piped`]: https://doc.rust-lang.org/std/process/struct.Stdio.html 66 | -------------------------------------------------------------------------------- /src/os/external/process-output.md: -------------------------------------------------------------------------------- 1 | ## Run an external command and process stdout 2 | 3 | [![regex-badge]][regex] [![cat-os-badge]][cat-os] [![cat-text-processing-badge]][cat-text-processing] 4 | 5 | Runs `git log --oneline` as an external [`Command`] and inspects its [`Output`] 6 | using [`Regex`] to get the hash and message of the last 5 commits. 7 | 8 | ```rust,edition2018,no_run 9 | # use error_chain::error_chain; 10 | 11 | use std::process::Command; 12 | use regex::Regex; 13 | # 14 | # error_chain!{ 15 | # foreign_links { 16 | # Io(std::io::Error); 17 | # Regex(regex::Error); 18 | # Utf8(std::string::FromUtf8Error); 19 | # } 20 | # } 21 | 22 | #[derive(PartialEq, Default, Clone, Debug)] 23 | struct Commit { 24 | hash: String, 25 | message: String, 26 | } 27 | 28 | fn main() -> Result<()> { 29 | let output = Command::new("git").arg("log").arg("--oneline").output()?; 30 | 31 | if !output.status.success() { 32 | error_chain::bail!("Command executed with failing error code"); 33 | } 34 | 35 | let pattern = Regex::new(r"(?x) 36 | ([0-9a-fA-F]+) # commit hash 37 | (.*) # The commit message")?; 38 | 39 | String::from_utf8(output.stdout)? 40 | .lines() 41 | .filter_map(|line| pattern.captures(line)) 42 | .map(|cap| { 43 | Commit { 44 | hash: cap[1].to_string(), 45 | message: cap[2].trim().to_string(), 46 | } 47 | }) 48 | .take(5) 49 | .for_each(|x| println!("{:?}", x)); 50 | 51 | Ok(()) 52 | } 53 | ``` 54 | 55 | [`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html 56 | [`Output`]: https://doc.rust-lang.org/std/process/struct.Output.html 57 | [`Regex`]: https://docs.rs/regex/*/regex/struct.Regex.html 58 | -------------------------------------------------------------------------------- /src/os/external/read-env-variable.md: -------------------------------------------------------------------------------- 1 | ## Read Environment Variable 2 | 3 | [![std-badge]][std] [![cat-os-badge]][cat-os] 4 | 5 | Reads an environment variable via [std::env::var]. 6 | 7 | ```rust,edition2018,no_run 8 | use std::env; 9 | use std::fs; 10 | use std::io::Error; 11 | 12 | fn main() -> Result<(), Error> { 13 | // read `config_path` from the environment variable `CONFIG`. 14 | // If `CONFIG` isn't set, fall back to a default config path. 15 | let config_path = env::var("CONFIG") 16 | .unwrap_or("/etc/myapp/config".to_string()); 17 | 18 | let config: String = fs::read_to_string(config_path)?; 19 | println!("Config: {}", config); 20 | 21 | Ok(()) 22 | } 23 | ``` 24 | 25 | [std::env::var]: https://doc.rust-lang.org/std/env/fn.var.html 26 | -------------------------------------------------------------------------------- /src/os/external/send-input.md: -------------------------------------------------------------------------------- 1 | ## Run an external command passing it stdin and check for an error code 2 | 3 | [![std-badge]][std] [![cat-os-badge]][cat-os] 4 | 5 | Opens the `python` interpreter using an external [`Command`] and passes it a 6 | python statement for execution. [`Output`] of statement is then parsed. 7 | 8 | ```rust,edition2018,no_run 9 | # use error_chain::error_chain; 10 | # 11 | use std::collections::HashSet; 12 | use std::io::Write; 13 | use std::process::{Command, Stdio}; 14 | # 15 | # error_chain!{ 16 | # errors { CmdError } 17 | # foreign_links { 18 | # Io(std::io::Error); 19 | # Utf8(std::string::FromUtf8Error); 20 | # } 21 | # } 22 | 23 | fn main() -> Result<()> { 24 | let mut child = Command::new("python").stdin(Stdio::piped()) 25 | .stderr(Stdio::piped()) 26 | .stdout(Stdio::piped()) 27 | .spawn()?; 28 | 29 | child.stdin 30 | .as_mut() 31 | .ok_or("Child process stdin has not been captured!")? 32 | .write_all(b"import this; copyright(); credits(); exit()")?; 33 | 34 | let output = child.wait_with_output()?; 35 | 36 | if output.status.success() { 37 | let raw_output = String::from_utf8(output.stdout)?; 38 | let words = raw_output.split_whitespace() 39 | .map(|s| s.to_lowercase()) 40 | .collect::>(); 41 | println!("Found {} unique words:", words.len()); 42 | println!("{:#?}", words); 43 | Ok(()) 44 | } else { 45 | let err = String::from_utf8(output.stderr)?; 46 | error_chain::bail!("External command failed:\n {}", err) 47 | } 48 | } 49 | ``` 50 | 51 | [`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html 52 | [`Output`]: https://doc.rust-lang.org/std/process/struct.Output.html 53 | -------------------------------------------------------------------------------- /src/science/mathematics/complex_numbers.md: -------------------------------------------------------------------------------- 1 | # Complex numbers 2 | 3 | {{#include complex_numbers/create-complex.md}} 4 | {{#include complex_numbers/add-complex.md}} 5 | {{#include complex_numbers/mathematical-functions.md}} 6 | 7 | {{#include ../../links.md}} 8 | -------------------------------------------------------------------------------- /src/science/mathematics/complex_numbers/add-complex.md: -------------------------------------------------------------------------------- 1 | ## Adding complex numbers 2 | 3 | [![num-badge]][num] [![cat-science-badge]][cat-science] 4 | 5 | Performing mathematical operations on complex numbers is the same as on 6 | built in types: the numbers in question must be of the same type (i.e. floats 7 | or integers). 8 | 9 | ```rust,edition2018 10 | fn main() { 11 | let complex_num1 = num::complex::Complex::new(10.0, 20.0); // Must use floats 12 | let complex_num2 = num::complex::Complex::new(3.1, -4.2); 13 | 14 | let sum = complex_num1 + complex_num2; 15 | 16 | println!("Sum: {}", sum); 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /src/science/mathematics/complex_numbers/create-complex.md: -------------------------------------------------------------------------------- 1 | ## Creating complex numbers 2 | 3 | [![num-badge]][num] [![cat-science-badge]][cat-science] 4 | 5 | Creates complex numbers of type [`num::complex::Complex`]. Both the real and 6 | imaginary part of the complex number must be of the same type. 7 | 8 | ```rust,edition2018 9 | fn main() { 10 | let complex_integer = num::complex::Complex::new(10, 20); 11 | let complex_float = num::complex::Complex::new(10.1, 20.1); 12 | 13 | println!("Complex integer: {}", complex_integer); 14 | println!("Complex float: {}", complex_float); 15 | } 16 | ``` 17 | 18 | [`num::complex::Complex`]: https://autumnai.github.io/cuticula/num/complex/struct.Complex.html 19 | -------------------------------------------------------------------------------- /src/science/mathematics/complex_numbers/mathematical-functions.md: -------------------------------------------------------------------------------- 1 | ## Mathematical functions 2 | 3 | [![num-badge]][num] [![cat-science-badge]][cat-science] 4 | 5 | Complex numbers have a range of interesting properties when it comes to 6 | how they interact with other mathematical functions, most notibly the family 7 | of sine functions as well as the number e. To use these functions with 8 | complex numbers, the Complex type has a few built in 9 | functions, all of which can be found here: [`num::complex::Complex`]. 10 | 11 | ```rust,edition2018 12 | use std::f64::consts::PI; 13 | use num::complex::Complex; 14 | 15 | fn main() { 16 | let x = Complex::new(0.0, 2.0*PI); 17 | 18 | println!("e^(2i * pi) = {}", x.exp()); // =~1 19 | } 20 | ``` 21 | 22 | [`num::complex::Complex`]: https://autumnai.github.io/cuticula/num/complex/struct.Complex.html 23 | -------------------------------------------------------------------------------- /src/science/mathematics/linear_algebra.md: -------------------------------------------------------------------------------- 1 | # Linear Algebra 2 | 3 | {{#include linear_algebra/add-matrices.md}} 4 | {{#include linear_algebra/multiply-matrices.md}} 5 | {{#include linear_algebra/multiply-scalar-vector-matrix.md}} 6 | {{#include linear_algebra/vector-comparison.md}} 7 | {{#include linear_algebra/vector-norm.md}} 8 | {{#include linear_algebra/invert-matrix.md}} 9 | {{#include linear_algebra/deserialize-matrix.md}} 10 | 11 | {{#include ../../links.md}} 12 | -------------------------------------------------------------------------------- /src/science/mathematics/linear_algebra/add-matrices.md: -------------------------------------------------------------------------------- 1 | ## Adding matrices 2 | [![ndarray-badge]][ndarray] [![cat-science-badge]][cat-science] 3 | 4 | Creates two 2-D matrices with [`ndarray::arr2`] and sums them element-wise. 5 | 6 | Note the sum is computed as `let sum = &a + &b`. The `&` operator is used to avoid consuming `a` and `b`, making them available later for display. A new array is created containing their sum. 7 | 8 | ```rust,edition2018 9 | use ndarray::arr2; 10 | 11 | fn main() { 12 | let a = arr2(&[[1, 2, 3], 13 | [4, 5, 6]]); 14 | 15 | let b = arr2(&[[6, 5, 4], 16 | [3, 2, 1]]); 17 | 18 | let sum = &a + &b; 19 | 20 | println!("{}", a); 21 | println!("+"); 22 | println!("{}", b); 23 | println!("="); 24 | println!("{}", sum); 25 | } 26 | ``` 27 | 28 | [`ndarray::arr2`]: https://docs.rs/ndarray/*/ndarray/fn.arr2.html 29 | -------------------------------------------------------------------------------- /src/science/mathematics/linear_algebra/deserialize-matrix.md: -------------------------------------------------------------------------------- 1 | ## (De)-Serialize a Matrix 2 | [![ndarray-badge]][ndarray] [![cat-science-badge]][cat-science] 3 | 4 | Serialize and deserialize a matrix to and from JSON. Serialization is taken care of 5 | by [`serde_json::to_string`] and [`serde_json::from_str`] performs deserialization. 6 | 7 | Note that serialization followed by deserialization gives back the original matrix. 8 | 9 | ```rust,edition2018 10 | use nalgebra::DMatrix; 11 | 12 | fn main() -> Result<(), std::io::Error> { 13 | let row_slice: Vec = (1..5001).collect(); 14 | let matrix = DMatrix::from_row_slice(50, 100, &row_slice); 15 | 16 | // serialize matrix 17 | let serialized_matrix = serde_json::to_string(&matrix)?; 18 | 19 | // deserialize matrix 20 | let deserialized_matrix: DMatrix = serde_json::from_str(&serialized_matrix)?; 21 | 22 | // verify that `deserialized_matrix` is equal to `matrix` 23 | assert!(deserialized_matrix == matrix); 24 | 25 | Ok(()) 26 | } 27 | ``` 28 | 29 | [`serde_json::to_string`]: https://docs.rs/serde_json/*/serde_json/fn.to_string.html 30 | [`serde_json::from_str`]: https://docs.rs/serde_json/*/serde_json/fn.from_str.html 31 | -------------------------------------------------------------------------------- /src/science/mathematics/linear_algebra/invert-matrix.md: -------------------------------------------------------------------------------- 1 | ## Invert matrix 2 | [![nalgebra-badge]][nalgebra] [![cat-science-badge]][cat-science] 3 | 4 | Creates a 3x3 matrix with [`nalgebra::Matrix3`] and inverts it, if possible. 5 | 6 | ```rust,edition2018 7 | use nalgebra::Matrix3; 8 | 9 | fn main() { 10 | let m1 = Matrix3::new(2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 2.0, 1.0, 2.0); 11 | println!("m1 = {}", m1); 12 | match m1.try_inverse() { 13 | Some(inv) => { 14 | println!("The inverse of m1 is: {}", inv); 15 | } 16 | None => { 17 | println!("m1 is not invertible!"); 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | [`nalgebra::Matrix3`]: https://docs.rs/nalgebra/*/nalgebra/base/type.Matrix3.html 24 | -------------------------------------------------------------------------------- /src/science/mathematics/linear_algebra/multiply-matrices.md: -------------------------------------------------------------------------------- 1 | ## Multiplying matrices 2 | [![ndarray-badge]][ndarray] [![cat-science-badge]][cat-science] 3 | 4 | Creates two matrices with [`ndarray::arr2`] and performs matrix multiplication on them with [`ndarray::ArrayBase::dot`]. 5 | 6 | ```rust,edition2018 7 | use ndarray::arr2; 8 | 9 | fn main() { 10 | let a = arr2(&[[1, 2, 3], 11 | [4, 5, 6]]); 12 | 13 | let b = arr2(&[[6, 3], 14 | [5, 2], 15 | [4, 1]]); 16 | 17 | println!("{}", a.dot(&b)); 18 | } 19 | ``` 20 | 21 | [`ndarray::arr2`]: https://docs.rs/ndarray/*/ndarray/fn.arr2.html 22 | [`ndarray::ArrayBase::dot`]: https://docs.rs/ndarray/*/ndarray/struct.ArrayBase.html#method.dot-1 23 | -------------------------------------------------------------------------------- /src/science/mathematics/linear_algebra/multiply-scalar-vector-matrix.md: -------------------------------------------------------------------------------- 1 | ## Multiply a scalar with a vector with a matrix 2 | [![ndarray-badge]][ndarray] [![cat-science-badge]][cat-science] 3 | 4 | Creates a 1-D array (vector) with [`ndarray::arr1`] and a 2-D array (matrix) 5 | with [`ndarray::arr2`]. 6 | 7 | First, a scalar is multiplied by the vector to get 8 | another vector. Then, the matrix is multiplied by the new vector with 9 | [`ndarray::Array2::dot`]. (Matrix multiplication is performed using `dot`, while 10 | the `*` operator performs element-wise multiplication.) 11 | 12 | In `ndarray`, 1-D arrays can be interpreted as either row or column vectors 13 | depending on context. If representing the orientation of a vector is important, 14 | a 2-D array with one row or one column must be used instead. In this example, 15 | the vector is a 1-D array on the right-hand side, so `dot` handles it as a column 16 | vector. 17 | 18 | ```rust,edition2018 19 | use ndarray::{arr1, arr2, Array1}; 20 | 21 | fn main() { 22 | let scalar = 4; 23 | 24 | let vector = arr1(&[1, 2, 3]); 25 | 26 | let matrix = arr2(&[[4, 5, 6], 27 | [7, 8, 9]]); 28 | 29 | let new_vector: Array1<_> = scalar * vector; 30 | println!("{}", new_vector); 31 | 32 | let new_matrix = matrix.dot(&new_vector); 33 | println!("{}", new_matrix); 34 | } 35 | ``` 36 | 37 | [`ndarray::arr1`]: https://docs.rs/ndarray/*/ndarray/fn.arr1.html 38 | [`ndarray::arr2`]: https://docs.rs/ndarray/*/ndarray/fn.arr2.html 39 | [`ndarray::Array2::dot`]: https://docs.rs/ndarray/*/ndarray/struct.ArrayBase.html#method.dot-1 -------------------------------------------------------------------------------- /src/science/mathematics/linear_algebra/vector-comparison.md: -------------------------------------------------------------------------------- 1 | ## Vector comparison 2 | [![ndarray-badge]][ndarray] 3 | 4 | The [ndarray] crate supports a number of ways to create arrays -- this recipe creates 5 | [`ndarray::Array`]s from `std::Vec` using `from`. Then, it sums the arrays element-wise. 6 | 7 | This recipe contains an example of comparing two floating-point vectors element-wise. 8 | Floating-point numbers are often stored inexactly, making exact comparisons difficult. 9 | However, the [`assert_abs_diff_eq!`] macro from the [`approx`] crate allows for convenient 10 | element-wise comparisons. To use the `approx` crate with `ndarray`, the `approx` 11 | feature must be added to the `ndarray` dependency in `Cargo.toml`. For example, 12 | `ndarray = { version = "0.13", features = ["approx"] }`. 13 | 14 | This recipe also contains additional ownership examples. Here, `let z = a + b` consumes 15 | `a` and `b`, updates `a` with the result, then moves ownership to `z`. Alternatively, 16 | `let w = &c + &d` creates a new vector without consuming `c` or `d`, allowing 17 | their modification later. See [Binary Operators With Two Arrays] for additional detail. 18 | 19 | ```rust,edition2018 20 | use approx::assert_abs_diff_eq; 21 | use ndarray::Array; 22 | 23 | fn main() { 24 | let a = Array::from(vec![1., 2., 3., 4., 5.]); 25 | let b = Array::from(vec![5., 4., 3., 2., 1.]); 26 | let mut c = Array::from(vec![1., 2., 3., 4., 5.]); 27 | let mut d = Array::from(vec![5., 4., 3., 2., 1.]); 28 | 29 | let z = a + b; 30 | let w = &c + &d; 31 | 32 | assert_abs_diff_eq!(z, Array::from(vec![6., 6., 6., 6., 6.])); 33 | 34 | println!("c = {}", c); 35 | c[0] = 10.; 36 | d[1] = 10.; 37 | 38 | assert_abs_diff_eq!(w, Array::from(vec![6., 6., 6., 6., 6.])); 39 | 40 | } 41 | ``` 42 | 43 | [`approx`]: https://docs.rs/approx/*/approx/index.html 44 | [`assert_abs_diff_eq!`]: https://docs.rs/approx/*/approx/macro.assert_abs_diff_eq.html 45 | [Binary Operators With Two Arrays]: https://docs.rs/ndarray/*/ndarray/struct.ArrayBase.html#binary-operators-with-two-arrays 46 | [ndarray]: https://docs.rs/crate/ndarray/* 47 | [`ndarray::Array`]: https://docs.rs/ndarray/*/ndarray/struct.ArrayBase.html 48 | -------------------------------------------------------------------------------- /src/science/mathematics/miscellaneous.md: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | 3 | {{#include miscellaneous/big-integers.md}} 4 | 5 | {{#include ../../links.md}} -------------------------------------------------------------------------------- /src/science/mathematics/miscellaneous/big-integers.md: -------------------------------------------------------------------------------- 1 | ## Big integers 2 | 3 | [![num-badge]][num] [![cat-science-badge]][cat-science] 4 | 5 | Calculation for integers exceeding 128 bits are possible with [`BigInt`]. 6 | 7 | ```rust,edition2018 8 | use num::bigint::{BigInt, ToBigInt}; 9 | 10 | fn factorial(x: i32) -> BigInt { 11 | if let Some(mut factorial) = 1.to_bigint() { 12 | for i in 1..=x { 13 | factorial = factorial * i; 14 | } 15 | factorial 16 | } 17 | else { 18 | panic!("Failed to calculate factorial!"); 19 | } 20 | } 21 | 22 | fn main() { 23 | println!("{}! equals {}", 100, factorial(100)); 24 | } 25 | ``` 26 | 27 | [`BigInt`]: https://docs.rs/num/0.2.0/num/struct.BigInt.html -------------------------------------------------------------------------------- /src/science/mathematics/statistics.md: -------------------------------------------------------------------------------- 1 | # Statistics 2 | {{#include statistics/central-tendency.md}} 3 | {{#include statistics/standard-deviation.md}} 4 | 5 | 6 | {{#include ../../links.md}} 7 | -------------------------------------------------------------------------------- /src/science/mathematics/statistics/standard-deviation.md: -------------------------------------------------------------------------------- 1 | ### Standard deviation 2 | 3 | [![std-badge]][std] [![cat-science-badge]][cat-science] 4 | 5 | This example calculates the standard deviation and z-score of a set of measurements. 6 | 7 | The standard deviation is defined as the square root of the variance (here calculated with f32's [`sqrt`], where the variance is the [`sum`] of the squared difference between each measurement and the [`mean`], divided by the number of measurements. 8 | 9 | The z-score is the number of standard deviations a single measurement spans away from the [`mean`] of the data set. 10 | 11 | 12 | ```rust,edition2018 13 | fn mean(data: &[i32]) -> Option { 14 | let sum = data.iter().sum::() as f32; 15 | let count = data.len(); 16 | 17 | match count { 18 | positive if positive > 0 => Some(sum / count as f32), 19 | _ => None, 20 | } 21 | } 22 | 23 | fn std_deviation(data: &[i32]) -> Option { 24 | match (mean(data), data.len()) { 25 | (Some(data_mean), count) if count > 0 => { 26 | let variance = data.iter().map(|value| { 27 | let diff = data_mean - (*value as f32); 28 | 29 | diff * diff 30 | }).sum::() / count as f32; 31 | 32 | Some(variance.sqrt()) 33 | }, 34 | _ => None 35 | } 36 | } 37 | 38 | fn main() { 39 | let data = [3, 1, 6, 1, 5, 8, 1, 8, 10, 11]; 40 | 41 | let data_mean = mean(&data); 42 | println!("Mean is {:?}", data_mean); 43 | 44 | let data_std_deviation = std_deviation(&data); 45 | println!("Standard deviation is {:?}", data_std_deviation); 46 | 47 | let zscore = match (data_mean, data_std_deviation) { 48 | (Some(mean), Some(std_deviation)) => { 49 | let diff = data[4] as f32 - mean; 50 | 51 | Some(diff / std_deviation) 52 | }, 53 | _ => None 54 | }; 55 | println!("Z-score of data at index 4 (with value {}) is {:?}", data[4], zscore); 56 | } 57 | ``` 58 | 59 | [sqrt]: https://doc.rust-lang.org/std/primitive.f32.html#method.sqrt 60 | [sum]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.sum 61 | [mean]: science/mathematics/statistics/central-tendency.html 62 | -------------------------------------------------------------------------------- /src/science/mathematics/trigonometry.md: -------------------------------------------------------------------------------- 1 | # Trigonometry 2 | 3 | {{#include trigonometry/side-length.md}} 4 | {{#include trigonometry/tan-sin-cos.md}} 5 | {{#include trigonometry/latitude-longitude.md}} 6 | 7 | {{#include ../../links.md}} 8 | -------------------------------------------------------------------------------- /src/science/mathematics/trigonometry/side-length.md: -------------------------------------------------------------------------------- 1 | ## Calculating the side length of a triangle 2 | 3 | [![std-badge]][std] [![cat-science-badge]][cat-science] 4 | 5 | Calculates the length of the hypotenuse of a right-angle triangle with an angle of 1 radian and opposite side length of 80. 6 | 7 | ```rust,edition2018 8 | fn main() { 9 | let angle: f64 = 1.0; 10 | let side_length = 80.0; 11 | 12 | let hypotenuse = side_length / angle.sin(); 13 | 14 | println!("Hypotenuse: {}", hypotenuse); 15 | } 16 | ``` 17 | -------------------------------------------------------------------------------- /src/science/mathematics/trigonometry/tan-sin-cos.md: -------------------------------------------------------------------------------- 1 | ## Verifying tan is equal to sin divided by cos 2 | 3 | [![std-badge]][std] [![cat-science-badge]][cat-science] 4 | 5 | Verifies tan(x) is equal to sin(x)/cos(x) for x = 6. 6 | 7 | ```rust,edition2018 8 | fn main() { 9 | let x: f64 = 6.0; 10 | 11 | let a = x.tan(); 12 | let b = x.sin() / x.cos(); 13 | 14 | assert_eq!(a, b); 15 | } 16 | ``` 17 | -------------------------------------------------------------------------------- /src/text.md: -------------------------------------------------------------------------------- 1 | # Text Processing 2 | 3 | | Recipe | Crates | Categories | 4 | |--------|--------|------------| 5 | | [Collect Unicode Graphemes][ex-unicode-graphemes] | [![unicode-segmentation-badge]][unicode-segmentation] | [![cat-encoding-badge]][cat-text-processing] | 6 | | [Verify and extract login from an email address][ex-verify-extract-email] | [![regex-badge]][regex] [![lazy_static-badge]][lazy_static] | [![cat-text-processing-badge]][cat-text-processing] | 7 | | [Extract a list of unique #Hashtags from a text][ex-extract-hashtags] | [![regex-badge]][regex] [![lazy_static-badge]][lazy_static] | [![cat-text-processing-badge]][cat-text-processing] | 8 | | [Extract phone numbers from text][ex-phone] | [![regex-badge]][regex] | [![cat-text-processing-badge]][cat-text-processing] | 9 | | [Filter a log file by matching multiple regular expressions][ex-regex-filter-log] | [![regex-badge]][regex] | [![cat-text-processing-badge]][cat-text-processing] 10 | | [Replace all occurrences of one text pattern with another pattern.][ex-regex-replace-named] | [![regex-badge]][regex] [![lazy_static-badge]][lazy_static] | [![cat-text-processing-badge]][cat-text-processing] | 11 | | [Implement the `FromStr` trait for a custom `struct`][string_parsing-from_str] | [![std-badge]][std] | [![cat-text-processing-badge]][cat-text-processing] | 12 | 13 | [ex-verify-extract-email]: text/regex.html#verify-and-extract-login-from-an-email-address 14 | [ex-extract-hashtags]: text/regex.html#extract-a-list-of-unique-hashtags-from-a-text 15 | [ex-phone]: text/regex.html#extract-phone-numbers-from-text 16 | [ex-regex-filter-log]: text/regex.html#filter-a-log-file-by-matching-multiple-regular-expressions 17 | [ex-regex-replace-named]: text/regex.html#replace-all-occurrences-of-one-text-pattern-with-another-pattern 18 | 19 | [ex-unicode-graphemes]: text/string_parsing.html#collect-unicode-graphemes 20 | [string_parsing-from_str]: text/string_parsing.html#implement-the-fromstr-trait-for-a-custom-struct 21 | 22 | {{#include links.md}} 23 | -------------------------------------------------------------------------------- /src/text/regex.md: -------------------------------------------------------------------------------- 1 | # Regular Expressions 2 | 3 | {{#include regex/email.md}} 4 | 5 | {{#include regex/hashtags.md}} 6 | 7 | {{#include regex/phone.md}} 8 | 9 | {{#include regex/filter-log.md}} 10 | 11 | {{#include regex/replace.md}} 12 | 13 | {{#include ../links.md}} 14 | -------------------------------------------------------------------------------- /src/text/regex/email.md: -------------------------------------------------------------------------------- 1 | ## Verify and extract login from an email address 2 | 3 | [![regex-badge]][regex] [![lazy_static-badge]][lazy_static] [![cat-text-processing-badge]][cat-text-processing] 4 | 5 | Validates that an email address is formatted correctly, and extracts everything 6 | before the @ symbol. 7 | 8 | ```rust,edition2018 9 | use lazy_static::lazy_static; 10 | 11 | use regex::Regex; 12 | 13 | fn extract_login(input: &str) -> Option<&str> { 14 | lazy_static! { 15 | static ref RE: Regex = Regex::new(r"(?x) 16 | ^(?P[^@\s]+)@ 17 | ([[:word:]]+\.)* 18 | [[:word:]]+$ 19 | ").unwrap(); 20 | } 21 | RE.captures(input).and_then(|cap| { 22 | cap.name("login").map(|login| login.as_str()) 23 | }) 24 | } 25 | 26 | fn main() { 27 | assert_eq!(extract_login(r"I❤email@example.com"), Some(r"I❤email")); 28 | assert_eq!( 29 | extract_login(r"sdf+sdsfsd.as.sdsd@jhkk.d.rl"), 30 | Some(r"sdf+sdsfsd.as.sdsd") 31 | ); 32 | assert_eq!(extract_login(r"More@Than@One@at.com"), None); 33 | assert_eq!(extract_login(r"Not an email@email"), None); 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /src/text/regex/filter-log.md: -------------------------------------------------------------------------------- 1 | ## Filter a log file by matching multiple regular expressions 2 | 3 | [![regex-badge]][regex] [![cat-text-processing-badge]][cat-text-processing] 4 | 5 | Reads a file named `application.log` and only outputs the lines 6 | containing “version X.X.X”, some IP address followed by port 443 7 | (e.g. “192.168.0.1:443”), or a specific warning. 8 | 9 | A [`regex::RegexSetBuilder`] composes a [`regex::RegexSet`]. 10 | Since backslashes are very common in regular expressions, using 11 | [raw string literals] makes them more readable. 12 | 13 | ```rust,edition2018,no_run 14 | # use error_chain::error_chain; 15 | 16 | use std::fs::File; 17 | use std::io::{BufReader, BufRead}; 18 | use regex::RegexSetBuilder; 19 | 20 | # error_chain! { 21 | # foreign_links { 22 | # Io(std::io::Error); 23 | # Regex(regex::Error); 24 | # } 25 | # } 26 | # 27 | fn main() -> Result<()> { 28 | let log_path = "application.log"; 29 | let buffered = BufReader::new(File::open(log_path)?); 30 | 31 | let set = RegexSetBuilder::new(&[ 32 | r#"version "\d\.\d\.\d""#, 33 | r#"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:443"#, 34 | r#"warning.*timeout expired"#, 35 | ]).case_insensitive(true) 36 | .build()?; 37 | 38 | buffered 39 | .lines() 40 | .filter_map(|line| line.ok()) 41 | .filter(|line| set.is_match(line.as_str())) 42 | .for_each(|x| println!("{}", x)); 43 | 44 | Ok(()) 45 | } 46 | ``` 47 | 48 | [`regex::RegexSet`]: https://docs.rs/regex/*/regex/struct.RegexSet.html 49 | [`regex::RegexSetBuilder`]: https://docs.rs/regex/*/regex/struct.RegexSetBuilder.html 50 | 51 | [raw string literals]: https://doc.rust-lang.org/reference/tokens.html#raw-string-literals 52 | -------------------------------------------------------------------------------- /src/text/regex/hashtags.md: -------------------------------------------------------------------------------- 1 | ## Extract a list of unique #Hashtags from a text 2 | 3 | [![regex-badge]][regex] [![lazy_static-badge]][lazy_static] [![cat-text-processing-badge]][cat-text-processing] 4 | 5 | Extracts, sorts, and deduplicates list of hashtags from text. 6 | 7 | The hashtag regex given here only catches Latin hashtags that start with a 8 | letter. The complete [twitter hashtag regex] is much more complicated. 9 | 10 | ```rust,edition2018 11 | use lazy_static::lazy_static; 12 | 13 | use regex::Regex; 14 | use std::collections::HashSet; 15 | 16 | fn extract_hashtags(text: &str) -> HashSet<&str> { 17 | lazy_static! { 18 | static ref HASHTAG_REGEX : Regex = Regex::new( 19 | r"\#[a-zA-Z][0-9a-zA-Z_]*" 20 | ).unwrap(); 21 | } 22 | HASHTAG_REGEX.find_iter(text).map(|mat| mat.as_str()).collect() 23 | } 24 | 25 | fn main() { 26 | let tweet = "Hey #world, I just got my new #dog, say hello to Till. #dog #forever #2 #_ "; 27 | let tags = extract_hashtags(tweet); 28 | assert!(tags.contains("#dog") && tags.contains("#forever") && tags.contains("#world")); 29 | assert_eq!(tags.len(), 3); 30 | } 31 | ``` 32 | 33 | [twitter hashtag regex]: https://github.com/twitter/twitter-text/blob/c9fc09782efe59af4ee82855768cfaf36273e170/java/src/com/twitter/Regex.java#L255 34 | -------------------------------------------------------------------------------- /src/text/regex/replace.md: -------------------------------------------------------------------------------- 1 | ## Replace all occurrences of one text pattern with another pattern. 2 | 3 | [![regex-badge]][regex] [![lazy_static-badge]][lazy_static] [![cat-text-processing-badge]][cat-text-processing] 4 | 5 | Replaces all occurrences of the standard ISO 8601 *YYYY-MM-DD* date pattern 6 | with the equivalent American English date with slashes. 7 | For example `2013-01-15` becomes `01/15/2013`. 8 | 9 | The method [`Regex::replace_all`] replaces all occurrences of the whole regex. 10 | `&str` implements the `Replacer` trait which allows variables like `$abcde` to 11 | refer to corresponding named capture groups `(?PREGEX)` from the search 12 | regex. See the [replacement string syntax] for examples and escaping detail. 13 | 14 | ```rust,edition2018 15 | use lazy_static::lazy_static; 16 | 17 | use std::borrow::Cow; 18 | use regex::Regex; 19 | 20 | fn reformat_dates(before: &str) -> Cow { 21 | lazy_static! { 22 | static ref ISO8601_DATE_REGEX : Regex = Regex::new( 23 | r"(?P\d{4})-(?P\d{2})-(?P\d{2})" 24 | ).unwrap(); 25 | } 26 | ISO8601_DATE_REGEX.replace_all(before, "$m/$d/$y") 27 | } 28 | 29 | fn main() { 30 | let before = "2012-03-14, 2013-01-15 and 2014-07-05"; 31 | let after = reformat_dates(before); 32 | assert_eq!(after, "03/14/2012, 01/15/2013 and 07/05/2014"); 33 | } 34 | ``` 35 | 36 | [`Regex::replace_all`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace_all 37 | 38 | [replacement string syntax]: https://docs.rs/regex/*/regex/struct.Regex.html#replacement-string-syntax 39 | -------------------------------------------------------------------------------- /src/text/string_parsing.md: -------------------------------------------------------------------------------- 1 | # String Parsing 2 | 3 | {{#include string_parsing/graphemes.md}} 4 | 5 | {{#include string_parsing/from_str.md}} 6 | 7 | {{#include ../links.md}} 8 | -------------------------------------------------------------------------------- /src/text/string_parsing/from_str.md: -------------------------------------------------------------------------------- 1 | ## Implement the `FromStr` trait for a custom `struct` 2 | 3 | [![std-badge]][std] [![cat-text-processing-badge]][cat-text-processing] 4 | 5 | Creates a custom struct `RGB` and implements the `FromStr` trait to convert a provided color hex code into its RGB color code. 6 | 7 | ```rust,edition2018 8 | use std::str::FromStr; 9 | 10 | #[derive(Debug, PartialEq)] 11 | struct RGB { 12 | r: u8, 13 | g: u8, 14 | b: u8, 15 | } 16 | 17 | impl FromStr for RGB { 18 | type Err = std::num::ParseIntError; 19 | 20 | // Parses a color hex code of the form '#rRgGbB..' into an 21 | // instance of 'RGB' 22 | fn from_str(hex_code: &str) -> Result { 23 | 24 | // u8::from_str_radix(src: &str, radix: u32) converts a string 25 | // slice in a given base to u8 26 | let r: u8 = u8::from_str_radix(&hex_code[1..3], 16)?; 27 | let g: u8 = u8::from_str_radix(&hex_code[3..5], 16)?; 28 | let b: u8 = u8::from_str_radix(&hex_code[5..7], 16)?; 29 | 30 | Ok(RGB { r, g, b }) 31 | } 32 | } 33 | 34 | fn main() { 35 | let code: &str = &r"#fa7268"; 36 | match RGB::from_str(code) { 37 | Ok(rgb) => { 38 | println!( 39 | r"The RGB color code is: R: {} G: {} B: {}", 40 | rgb.r, rgb.g, rgb.b 41 | ); 42 | } 43 | Err(_) => { 44 | println!("{} is not a valid color hex code!", code); 45 | } 46 | } 47 | 48 | // test whether from_str performs as expected 49 | assert_eq!( 50 | RGB::from_str(&r"#fa7268").unwrap(), 51 | RGB { 52 | r: 250, 53 | g: 114, 54 | b: 104 55 | } 56 | ); 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /src/text/string_parsing/graphemes.md: -------------------------------------------------------------------------------- 1 | ## Collect Unicode Graphemes 2 | 3 | [![unicode-segmentation-badge]][`unicode-segmentation`] [![cat-text-processing-badge]][cat-text-processing] 4 | 5 | Collect individual Unicode graphemes from UTF-8 string using the 6 | [`UnicodeSegmentation::graphemes`] function from the [`unicode-segmentation`] crate. 7 | 8 | ```rust,edition2018 9 | use unicode_segmentation::UnicodeSegmentation; 10 | 11 | fn main() { 12 | let name = "José Guimarães\r\n"; 13 | let graphemes = UnicodeSegmentation::graphemes(name, true) 14 | .collect::>(); 15 | assert_eq!(graphemes[3], "é"); 16 | } 17 | ``` 18 | 19 | [`UnicodeSegmentation::graphemes`]: https://docs.rs/unicode-segmentation/*/unicode_segmentation/trait.UnicodeSegmentation.html#tymethod.graphemes 20 | [`unicode-segmentation`]: https://docs.rs/unicode-segmentation/1.2.1/unicode_segmentation/ 21 | -------------------------------------------------------------------------------- /src/web/clients/api/paginated.md: -------------------------------------------------------------------------------- 1 | ## Consume a paginated RESTful API 2 | 3 | [![reqwest-badge]][reqwest] [![serde-badge]][serde] [![cat-net-badge]][cat-net] [![cat-encoding-badge]][cat-encoding] 4 | 5 | Wraps a paginated web API in a convenient Rust iterator. The iterator lazily 6 | fetches the next page of results from the remote server as it arrives at the end of each page. 7 | 8 | ```rust,edition2024,no_run 9 | mod paginated { 10 | {{#include ../../../../crates/web/src/paginated.rs}} 11 | } 12 | 13 | fn main() -> Result<()> { 14 | for dep in paginated::ReverseDependencies::of("serde")? { 15 | let dependency = dep?; 16 | println!("{} depends on {}", dependency.id, dependency.crate_id); 17 | } 18 | Ok(()) 19 | } 20 | ``` 21 | -------------------------------------------------------------------------------- /src/web/clients/api/rest-get.md: -------------------------------------------------------------------------------- 1 | ## Query the GitHub API 2 | 3 | [![reqwest-badge]][reqwest] [![serde-badge]][serde] [![cat-net-badge]][cat-net] [![cat-encoding-badge]][cat-encoding] 4 | 5 | Queries GitHub [stargazers API v3](https://developer.github.com/v3/activity/starring/#list-stargazers) 6 | with [`reqwest::get`] to get list of all users who have marked a GitHub project with a star. 7 | [`reqwest::Response`] is deserialized with [`Response::json`] into `User` objects implementing [`serde::Deserialize`]. 8 | 9 | [tokio::main] is used to set up the async executor and the process waits for [`reqwest::get`] to complete before 10 | processing the response into User instances. 11 | 12 | ```rust,edition2018,no_run 13 | use serde::Deserialize; 14 | use reqwest::Error; 15 | use reqwest::header::USER_AGENT; 16 | 17 | #[derive(Deserialize, Debug)] 18 | struct User { 19 | login: String, 20 | id: u32, 21 | } 22 | 23 | #[tokio::main] 24 | async fn main() -> Result<(), Error> { 25 | let request_url = format!("https://api.github.com/repos/{owner}/{repo}/stargazers", 26 | owner = "rust-lang-nursery", 27 | repo = "rust-cookbook"); 28 | println!("{}", request_url); 29 | 30 | let client = reqwest::Client::new(); 31 | let response = client 32 | .get(request_url) 33 | .header(USER_AGENT, "rust-web-api-client") // gh api requires a user-agent header 34 | .send() 35 | .await?; 36 | 37 | let users: Vec = response.json().await?; 38 | println!("{:?}", users); 39 | Ok(()) 40 | } 41 | ``` 42 | 43 | [`reqwest::get`]: https://docs.rs/reqwest/*/reqwest/fn.get.html 44 | [`reqwest::Response`]: https://docs.rs/reqwest/*/reqwest/struct.Response.html 45 | [`Response::json`]: https://docs.rs/reqwest/*/reqwest/struct.Response.html#method.json 46 | [`serde::Deserialize`]: https://docs.rs/serde/*/serde/trait.Deserialize.html 47 | -------------------------------------------------------------------------------- /src/web/clients/api/rest-head.md: -------------------------------------------------------------------------------- 1 | ## Check if an API resource exists 2 | 3 | [![reqwest-badge]][reqwest] [![cat-net-badge]][cat-net] 4 | 5 | Query the GitHub Users Endpoint using a HEAD 6 | request ([`Client::head`]) and then inspect the response code to determine 7 | success. This is a quick way to query a rest resource without needing to receive 8 | a body. [`reqwest::Client`] configured with [`ClientBuilder::timeout`] ensures 9 | a request will not last longer than a timeout. 10 | 11 | Due to both [`ClientBuilder::build`] and [`ReqwestBuilder::send`] returning [`reqwest::Error`] 12 | types, the shortcut [`reqwest::Result`] is used for the main function return type. 13 | 14 | ```rust,edition2018,no_run 15 | use reqwest::Result; 16 | use std::time::Duration; 17 | use reqwest::ClientBuilder; 18 | 19 | #[tokio::main] 20 | async fn main() -> Result<()> { 21 | let user = "ferris-the-crab"; 22 | let request_url = format!("https://api.github.com/users/{}", user); 23 | println!("{}", request_url); 24 | 25 | let timeout = Duration::new(5, 0); 26 | let client = ClientBuilder::new().timeout(timeout).build()?; 27 | let response = client.head(&request_url).send().await?; 28 | 29 | if response.status().is_success() { 30 | println!("{} is a user!", user); 31 | } else { 32 | println!("{} is not a user!", user); 33 | } 34 | 35 | Ok(()) 36 | } 37 | ``` 38 | 39 | [`ClientBuilder::build`]: https://docs.rs/reqwest/*/reqwest/struct.ClientBuilder.html#method.build 40 | [`Client::head`]: https://docs.rs/reqwest/*/reqwest/struct.Client.html#method.head 41 | [`ClientBuilder::timeout`]: https://docs.rs/reqwest/*/reqwest/struct.ClientBuilder.html#method.timeout 42 | [`RequestBuilder::send`]: https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html#method.send 43 | [`reqwest::Client`]: https://docs.rs/reqwest/*/reqwest/struct.Client.html 44 | [`reqwest::Error`]: https://docs.rs/reqwest/*/reqwest/struct.Error.html 45 | [`reqwest::Result`]:https://docs.rs/reqwest/*/reqwest/type.Result.html 46 | -------------------------------------------------------------------------------- /src/web/clients/apis.md: -------------------------------------------------------------------------------- 1 | # Calling a Web API 2 | 3 | {{#include api/rest-get.md}} 4 | 5 | {{#include api/rest-head.md}} 6 | 7 | {{#include api/rest-post.md}} 8 | 9 | {{#include api/paginated.md}} 10 | 11 | {{#include ../../links.md}} 12 | -------------------------------------------------------------------------------- /src/web/clients/authentication.md: -------------------------------------------------------------------------------- 1 | # Authentication 2 | 3 | {{#include authentication/basic.md}} 4 | 5 | {{#include ../../links.md}} 6 | -------------------------------------------------------------------------------- /src/web/clients/authentication/basic.md: -------------------------------------------------------------------------------- 1 | ## Basic Authentication 2 | 3 | [![reqwest-badge]][reqwest] [![cat-net-badge]][cat-net] 4 | 5 | Uses [`reqwest::RequestBuilder::basic_auth`] to perform a basic HTTP authentication. 6 | 7 | ```rust,edition2018,no_run 8 | use reqwest::blocking::Client; 9 | use reqwest::Error; 10 | 11 | fn main() -> Result<(), Error> { 12 | let client = Client::new(); 13 | 14 | let user_name = "testuser".to_string(); 15 | let password: Option = None; 16 | 17 | let response = client 18 | .get("https://httpbin.org/") 19 | .basic_auth(user_name, password) 20 | .send(); 21 | 22 | println!("{:?}", response); 23 | 24 | Ok(()) 25 | } 26 | ``` 27 | 28 | [`reqwest::RequestBuilder::basic_auth`]: https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html#method.basic_auth 29 | -------------------------------------------------------------------------------- /src/web/clients/download.md: -------------------------------------------------------------------------------- 1 | # Downloads 2 | 3 | {{#include download/basic.md}} 4 | 5 | {{#include download/post-file.md}} 6 | 7 | {{#include download/partial.md}} 8 | 9 | {{#include ../../links.md}} 10 | -------------------------------------------------------------------------------- /src/web/clients/download/basic.md: -------------------------------------------------------------------------------- 1 | ## Download a file to a temporary directory 2 | 3 | [![reqwest-badge]][reqwest] [![tempfile-badge]][tempfile] [![cat-net-badge]][cat-net] [![cat-filesystem-badge]][cat-filesystem] 4 | 5 | Creates a temporary directory with [`tempfile::Builder`] and downloads 6 | a file over HTTP using [`reqwest::get`] asynchronously. 7 | 8 | Creates a target [`File`] with name obtained from [`Response::url`] within 9 | [`tempdir()`] and writes downloaded data into it with [`Writer::write_all`]. 10 | The temporary directory is automatically removed on program exit. 11 | 12 | ```rust,edition2018,no_run 13 | use error_chain::error_chain; 14 | use std::io::Write; 15 | use std::fs::File; 16 | use tempfile::Builder; 17 | 18 | error_chain! { 19 | foreign_links { 20 | Io(std::io::Error); 21 | HttpRequest(reqwest::Error); 22 | } 23 | } 24 | 25 | #[tokio::main] 26 | async fn main() -> Result<()> { 27 | let tmp_dir = Builder::new().prefix("example").tempdir()?; 28 | let target = "https://www.rust-lang.org/logos/rust-logo-512x512.png"; 29 | let response = reqwest::get(target).await?; 30 | 31 | let mut dest = { 32 | let fname = response 33 | .url() 34 | .path_segments() 35 | .and_then(|segments| segments.last()) 36 | .and_then(|name| if name.is_empty() { None } else { Some(name) }) 37 | .unwrap_or("tmp.bin"); 38 | 39 | println!("file to download: '{}'", fname); 40 | let fname = tmp_dir.path().join(fname); 41 | println!("will be located under: '{:?}'", fname); 42 | File::create(fname)? 43 | }; 44 | let content = response.bytes().await?; 45 | dest.write_all(&content)?; 46 | Ok(()) 47 | } 48 | ``` 49 | 50 | [`File`]: https://doc.rust-lang.org/std/fs/struct.File.html 51 | [`reqwest::get`]: https://docs.rs/reqwest/*/reqwest/fn.get.html 52 | [`Response::url`]: https://docs.rs/reqwest/*/reqwest/struct.Response.html#method.url 53 | [`tempfile::Builder`]: https://docs.rs/tempfile/*/tempfile/struct.Builder.html 54 | [`tempdir()`]: https://docs.rs/tempfile/*/tempfile/struct.Builder.html#method.tempdir 55 | [`Writer::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all 56 | -------------------------------------------------------------------------------- /src/web/clients/download/post-file.md: -------------------------------------------------------------------------------- 1 | ## POST a file to paste-rs 2 | 3 | [![reqwest-badge]][reqwest] [![cat-net-badge]][cat-net] 4 | 5 | [`reqwest::Client`] establishes a connection to https://paste.rs 6 | following the [`reqwest::RequestBuilder`] pattern. Calling [`Client::post`] 7 | with a URL establishes the destination, [`RequestBuilder::body`] sets the 8 | content to send by reading the file, and [`RequestBuilder::send`] blocks until 9 | the file uploads and the response returns. [`read_to_string`] returns the 10 | response and displays in the console. 11 | 12 | ```rust,edition2018,no_run 13 | use error_chain::error_chain; 14 | use std::fs::File; 15 | use std::io::Read; 16 | 17 | error_chain! { 18 | foreign_links { 19 | HttpRequest(reqwest::Error); 20 | IoError(::std::io::Error); 21 | } 22 | } 23 | #[tokio::main] 24 | 25 | async fn main() -> Result<()> { 26 | let paste_api = "https://paste.rs"; 27 | let mut file = File::open("message")?; 28 | 29 | let mut contents = String::new(); 30 | file.read_to_string(&mut contents)?; 31 | 32 | let client = reqwest::Client::new(); 33 | let res = client.post(paste_api) 34 | .body(contents) 35 | .send() 36 | .await?; 37 | let response_text = res.text().await?; 38 | println!("Your paste is located at: {}",response_text ); 39 | Ok(()) 40 | } 41 | ``` 42 | 43 | [`Client::post`]: https://docs.rs/reqwest/*/reqwest/struct.Client.html#method.post 44 | [`read_to_string`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_to_string 45 | [`RequestBuilder::body`]: https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html#method.body 46 | [`RequestBuilder::send`]: https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html#method.send 47 | [`reqwest::Client`]: https://docs.rs/reqwest/*/reqwest/struct.Client.html 48 | [`reqwest::RequestBuilder`]: https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html 49 | -------------------------------------------------------------------------------- /src/web/clients/requests.md: -------------------------------------------------------------------------------- 1 | # Making Requests 2 | 3 | {{#include requests/get.md}} 4 | 5 | {{#include requests/header.md}} 6 | 7 | {{#include ../../links.md}} 8 | -------------------------------------------------------------------------------- /src/web/mime.md: -------------------------------------------------------------------------------- 1 | # Media Types 2 | 3 | {{#include mime/string.md}} 4 | 5 | {{#include mime/filename.md}} 6 | 7 | {{#include mime/request.md}} 8 | 9 | {{#include ../links.md}} 10 | -------------------------------------------------------------------------------- /src/web/mime/filename.md: -------------------------------------------------------------------------------- 1 | ## Get MIME type from filename 2 | 3 | [![mime-badge]][mime] [![cat-encoding-badge]][cat-encoding] 4 | 5 | The following example shows how to return the correct MIME type from a given 6 | filename using the [mime] crate. The program will check for file extensions 7 | and match against a known list. The return value is [`mime:Mime`]. 8 | 9 | ```rust,edition2018 10 | use mime::Mime; 11 | 12 | fn find_mimetype (filename : &String) -> Mime{ 13 | 14 | let parts : Vec<&str> = filename.split('.').collect(); 15 | 16 | let res = match parts.last() { 17 | Some(v) => 18 | match *v { 19 | "png" => mime::IMAGE_PNG, 20 | "jpg" => mime::IMAGE_JPEG, 21 | "json" => mime::APPLICATION_JSON, 22 | &_ => mime::TEXT_PLAIN, 23 | }, 24 | None => mime::TEXT_PLAIN, 25 | }; 26 | return res; 27 | } 28 | 29 | fn main() { 30 | let filenames = vec!("foobar.jpg", "foo.bar", "foobar.png"); 31 | for file in filenames { 32 | let mime = find_mimetype(&file.to_owned()); 33 | println!("MIME for {}: {}", file, mime); 34 | } 35 | 36 | } 37 | ``` 38 | 39 | [`mime:Mime`]: https://docs.rs/mime/*/mime/struct.Mime.html 40 | -------------------------------------------------------------------------------- /src/web/mime/string.md: -------------------------------------------------------------------------------- 1 | ## Get MIME type from string 2 | 3 | [![mime-badge]][mime] [![cat-encoding-badge]][cat-encoding] 4 | 5 | The following example shows how to parse a [`MIME`] type from a string using the 6 | [mime] crate. [`FromStrError`] produces a default [`MIME`] type in an 7 | `unwrap_or` clause. 8 | 9 | ```rust,edition2018 10 | use mime::{Mime, APPLICATION_OCTET_STREAM}; 11 | 12 | fn main() { 13 | let invalid_mime_type = "i n v a l i d"; 14 | let default_mime = invalid_mime_type 15 | .parse::() 16 | .unwrap_or(APPLICATION_OCTET_STREAM); 17 | 18 | println!( 19 | "MIME for {:?} used default value {:?}", 20 | invalid_mime_type, default_mime 21 | ); 22 | 23 | let valid_mime_type = "TEXT/PLAIN"; 24 | let parsed_mime = valid_mime_type 25 | .parse::() 26 | .unwrap_or(APPLICATION_OCTET_STREAM); 27 | 28 | println!( 29 | "MIME for {:?} was parsed as {:?}", 30 | valid_mime_type, parsed_mime 31 | ); 32 | } 33 | ``` 34 | 35 | [`FromStrError`]: https://docs.rs/mime/*/mime/struct.FromStrError.html 36 | [`MIME`]: https://docs.rs/mime/*/mime/struct.Mime.html 37 | -------------------------------------------------------------------------------- /src/web/scraping.md: -------------------------------------------------------------------------------- 1 | # Extracting Links 2 | 3 | {{#include scraping/extract-links.md}} 4 | 5 | {{#include scraping/broken.md}} 6 | 7 | {{#include scraping/unique.md}} 8 | 9 | {{#include ../links.md}} 10 | -------------------------------------------------------------------------------- /src/web/scraping/broken.md: -------------------------------------------------------------------------------- 1 | ## Check a webpage for broken links 2 | 3 | [![reqwest-badge]][reqwest] [![select-badge]][select] [![url-badge]][url] [![cat-net-badge]][cat-net] 4 | 5 | Call `get_base_url` to retrieve the base URL. If the document has a base tag, 6 | get the href [`attr`] from base tag. [`Position::BeforePath`] of the original 7 | URL acts as a default. 8 | 9 | Iterates through links in the document and creates a [`tokio::spawn`] task that will 10 | parse an individual link with [`url::ParseOptions`] and [`Url::parse`]). 11 | The task makes a request to the links with [reqwest] and verifies 12 | [`StatusCode`]. Then the tasks `await` completion before ending the program. 13 | 14 | ```rust,edition2024 15 | mod broken { 16 | {{#include ../../../crates/web/src/broken.rs}} 17 | } 18 | 19 | [tokio::main] 20 | fn main() -> anyhow::Result<()> { 21 | let categorized = broken::check("https://www.rust-lang.org/en-US/").await?; 22 | println!("OK: {:?}", categorized.ok); 23 | println!("Broken: {:?}", categorized.broken); 24 | Ok(()) 25 | } 26 | ``` 27 | 28 | [`attr`]: https://docs.rs/select/*/select/node/struct.Node.html#method.attr 29 | [`Position::BeforePath`]: https://docs.rs/url/*/url/enum.Position.html#variant.BeforePath 30 | [`StatusCode`]: https://docs.rs/reqwest/*/reqwest/struct.StatusCode.html 31 | [`tokio::spawn`]: https://docs.rs/tokio/*/tokio/fn.spawn.html 32 | [`url::Parse`]: https://docs.rs/url/*/url/struct.Url.html#method.parse 33 | [`url::ParseOptions`]: https://docs.rs/url/*/url/struct.ParseOptions.html 34 | -------------------------------------------------------------------------------- /src/web/scraping/extract-links.md: -------------------------------------------------------------------------------- 1 | ## Extract all links from a webpage HTML 2 | 3 | [![reqwest-badge]][reqwest] [![select-badge]][select] [![cat-net-badge]][cat-net] 4 | 5 | Use [`reqwest::get`] to perform a HTTP GET request and then use 6 | [`Document::from_read`] to parse the response into a HTML document. 7 | [`find`] with the criteria of [`Name`] is "a" retrieves all links. 8 | Call [`filter_map`] on the [`Selection`] retrieves URLs 9 | from links that have the "href" [`attr`] (attribute). 10 | 11 | ```rust,edition2024,no_run 12 | mod links { 13 | {{#include ../../../crates/web/src/links.rs}} 14 | } 15 | 16 | #[tokio::main] 17 | async fn main() -> Result<(), links::LinkError> { 18 | let page_links = links::get_links("https://www.rust-lang.org/en-US/").await?; 19 | for link in page_links { 20 | println!("{}", link); 21 | } 22 | Ok(()) 23 | } 24 | ``` 25 | 26 | [`attr`]: https://docs.rs/select/*/select/node/struct.Node.html#method.attr 27 | [`Document::from_read`]: https://docs.rs/select/*/select/document/struct.Document.html#method.from_read 28 | [`filter_map`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.filter_map 29 | [`find`]: https://docs.rs/select/*/select/document/struct.Document.html#method.find 30 | [`Name`]: https://docs.rs/select/*/select/predicate/struct.Name.html 31 | [`reqwest::get`]: https://docs.rs/reqwest/*/reqwest/fn.get.html 32 | [`Selection`]: https://docs.rs/select/*/select/selection/struct.Selection.html 33 | -------------------------------------------------------------------------------- /src/web/scraping/unique.md: -------------------------------------------------------------------------------- 1 | ## Extract all unique links from a MediaWiki markup 2 | 3 | [![reqwest-badge]][reqwest] [![regex-badge]][regex] [![cat-net-badge]][cat-net] 4 | 5 | Pull the source of a MediaWiki page using [`reqwest::get`] and then 6 | look for all entries of internal and external links with 7 | [`Regex::captures_iter`]. Using [`Cow`] avoids excessive [`String`] allocations. 8 | 9 | MediaWiki link syntax is described [here][MediaWiki link syntax]. The calling 10 | function will retain the whole document, and links will be returned as slice 11 | references to the original document. 12 | 13 | ```rust,edition2024,no_run 14 | mod wiki { 15 | {{#include ../../../crates/web/src/wiki.rs}} 16 | } 17 | 18 | #[tokio::main] 19 | async fn main() -> anyhow::Result<()> { 20 | let content = reqwest::get( 21 | "https://en.wikipedia.org/w/index.php?title=Rust_(programming_language)&action=raw", 22 | ) 23 | .await? 24 | .text() 25 | .await?; 26 | 27 | println!("{:#?}", wiki::extract_links(content.as_str())); 28 | 29 | Ok(()) 30 | } 31 | 32 | ``` 33 | 34 | [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html 35 | [`reqwest::get`]: https://docs.rs/reqwest/*/reqwest/fn.get.html 36 | [`Regex::captures_iter`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.captures_iter 37 | [`String`]: https://doc.rust-lang.org/std/string/struct.String.html 38 | [`LazyLock`]: https://doc.rust-lang.org/std/sync/struct.LazyLock.html 39 | 40 | [MediaWiki link syntax]: https://www.mediawiki.org/wiki/Help:Links 41 | -------------------------------------------------------------------------------- /src/web/url.md: -------------------------------------------------------------------------------- 1 | # Uniform Resource Location 2 | 3 | {{#include url/parse.md}} 4 | 5 | {{#include url/base.md}} 6 | 7 | {{#include url/new.md}} 8 | 9 | {{#include url/origin.md}} 10 | 11 | {{#include url/fragment.md}} 12 | 13 | {{#include ../links.md}} 14 | -------------------------------------------------------------------------------- /src/web/url/base.md: -------------------------------------------------------------------------------- 1 | ## Create a base URL by removing path segments 2 | 3 | [![url-badge]][url] [![cat-net-badge]][cat-net] 4 | 5 | A base URL includes a protocol and a domain. Base URLs have no folders, 6 | files or query strings. Each of those items are stripped out of the given 7 | URL. [`PathSegmentsMut::clear`] removes paths and [`Url::set_query`] removes 8 | query string. 9 | 10 | ```rust,edition2018 11 | # use error_chain::error_chain; 12 | 13 | use url::Url; 14 | # 15 | # error_chain! { 16 | # foreign_links { 17 | # UrlParse(url::ParseError); 18 | # } 19 | # errors { 20 | # CannotBeABase 21 | # } 22 | # } 23 | 24 | fn main() -> Result<()> { 25 | let full = "https://github.com/rust-lang/cargo?asdf"; 26 | 27 | let url = Url::parse(full)?; 28 | let base = base_url(url)?; 29 | 30 | assert_eq!(base.as_str(), "https://github.com/"); 31 | println!("The base of the URL is: {}", base); 32 | 33 | Ok(()) 34 | } 35 | 36 | fn base_url(mut url: Url) -> Result { 37 | match url.path_segments_mut() { 38 | Ok(mut path) => { 39 | path.clear(); 40 | } 41 | Err(_) => { 42 | return Err(Error::from_kind(ErrorKind::CannotBeABase)); 43 | } 44 | } 45 | 46 | url.set_query(None); 47 | 48 | Ok(url) 49 | } 50 | ``` 51 | 52 | [`PathSegmentsMut::clear`]: https://docs.rs/url/*/url/struct.PathSegmentsMut.html#method.clear 53 | [`Url::set_query`]: https://docs.rs/url/*/url/struct.Url.html#method.set_query 54 | -------------------------------------------------------------------------------- /src/web/url/fragment.md: -------------------------------------------------------------------------------- 1 | ## Remove fragment identifiers and query pairs from a URL 2 | 3 | [![url-badge]][url] [![cat-net-badge]][cat-net] 4 | 5 | Parses [`Url`] and slices it with [`url::Position`] to strip unneeded URL parts. 6 | 7 | ```rust,edition2018 8 | 9 | 10 | use url::{Url, Position, ParseError}; 11 | 12 | fn main() -> Result<(), ParseError> { 13 | let parsed = Url::parse("https://github.com/rust-lang/rust/issues?labels=E-easy&state=open")?; 14 | let cleaned: &str = &parsed[..Position::AfterPath]; 15 | println!("cleaned: {}", cleaned); 16 | Ok(()) 17 | } 18 | ``` 19 | 20 | [`url::Position`]: https://docs.rs/url/*/url/enum.Position.html 21 | [`Url`]: https://docs.rs/url/*/url/struct.Url.html 22 | -------------------------------------------------------------------------------- /src/web/url/new.md: -------------------------------------------------------------------------------- 1 | ## Create new URLs from a base URL 2 | 3 | [![url-badge]][url] [![cat-net-badge]][cat-net] 4 | 5 | The [`join`] method creates a new URL from a base and relative path. 6 | 7 | ```rust,edition2018 8 | 9 | use url::{Url, ParseError}; 10 | 11 | fn main() -> Result<(), ParseError> { 12 | let path = "/rust-lang/cargo"; 13 | 14 | let gh = build_github_url(path)?; 15 | 16 | assert_eq!(gh.as_str(), "https://github.com/rust-lang/cargo"); 17 | println!("The joined URL is: {}", gh); 18 | 19 | Ok(()) 20 | } 21 | 22 | fn build_github_url(path: &str) -> Result { 23 | const GITHUB: &'static str = "https://github.com"; 24 | 25 | let base = Url::parse(GITHUB).expect("hardcoded URL is known to be valid"); 26 | let joined = base.join(path)?; 27 | 28 | Ok(joined) 29 | } 30 | ``` 31 | 32 | [`join`]: https://docs.rs/url/*/url/struct.Url.html#method.join 33 | -------------------------------------------------------------------------------- /src/web/url/origin.md: -------------------------------------------------------------------------------- 1 | ## Extract the URL origin (scheme / host / port) 2 | 3 | [![url-badge]][url] [![cat-net-badge]][cat-net] 4 | 5 | The [`Url`] struct exposes various methods to extract information about the URL 6 | it represents. 7 | 8 | ```rust,edition2018 9 | 10 | use url::{Url, Host, ParseError}; 11 | 12 | fn main() -> Result<(), ParseError> { 13 | let s = "ftp://rust-lang.org/examples"; 14 | 15 | let url = Url::parse(s)?; 16 | 17 | assert_eq!(url.scheme(), "ftp"); 18 | assert_eq!(url.host(), Some(Host::Domain("rust-lang.org"))); 19 | assert_eq!(url.port_or_known_default(), Some(21)); 20 | println!("The origin is as expected!"); 21 | 22 | Ok(()) 23 | } 24 | ``` 25 | 26 | [`origin`] produces the same result. 27 | 28 | ```rust,edition2018 29 | # use error_chain::error_chain; 30 | 31 | use url::{Url, Origin, Host}; 32 | 33 | # error_chain! { 34 | # foreign_links { 35 | # UrlParse(url::ParseError); 36 | # } 37 | # } 38 | # 39 | fn main() -> Result<()> { 40 | let s = "ftp://rust-lang.org/examples"; 41 | 42 | let url = Url::parse(s)?; 43 | 44 | let expected_scheme = "ftp".to_owned(); 45 | let expected_host = Host::Domain("rust-lang.org".to_owned()); 46 | let expected_port = 21; 47 | let expected = Origin::Tuple(expected_scheme, expected_host, expected_port); 48 | 49 | let origin = url.origin(); 50 | assert_eq!(origin, expected); 51 | println!("The origin is as expected!"); 52 | 53 | Ok(()) 54 | } 55 | ``` 56 | 57 | [`origin`]: https://docs.rs/url/*/url/struct.Url.html#method.origin 58 | [`Url`]: https://docs.rs/url/*/url/struct.Url.html 59 | -------------------------------------------------------------------------------- /src/web/url/parse.md: -------------------------------------------------------------------------------- 1 | ## Parse a URL from a string to a `Url` type 2 | 3 | [![url-badge]][url] [![cat-net-badge]][cat-net] 4 | 5 | The [`parse`] method from the `url` crate validates and parses a `&str` into a 6 | [`Url`] struct. The input string may be malformed so this method returns 7 | `Result`. 8 | 9 | Once the URL has been parsed, it can be used with all of the methods in the 10 | `Url` type. 11 | 12 | ```rust,edition2018 13 | 14 | use url::{Url, ParseError}; 15 | 16 | fn main() -> Result<(), ParseError> { 17 | let s = "https://github.com/rust-lang/rust/issues?labels=E-easy&state=open"; 18 | 19 | let parsed = Url::parse(s)?; 20 | println!("The path part of the URL is: {}", parsed.path()); 21 | 22 | Ok(()) 23 | } 24 | ``` 25 | 26 | [`parse`]: https://docs.rs/url/*/url/struct.Url.html#method.parse 27 | [`Url`]: https://docs.rs/url/*/url/struct.Url.html 28 | -------------------------------------------------------------------------------- /tests/skeptic.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/skeptic-tests.rs")); 2 | -------------------------------------------------------------------------------- /theme/custom.css: -------------------------------------------------------------------------------- 1 | table { 2 | width: 100%; 3 | } 4 | 5 | table td:first-child { 6 | width: 65%; 7 | } 8 | 9 | table td:nth-child(2) { 10 | width: 20%; 11 | padding: 3px; 12 | } 13 | 14 | table td:nth-child(3) { 15 | padding: 3px; 16 | } 17 | 18 | /* underline hyperlinked inline code items */ 19 | a:hover > .hljs { 20 | text-decoration: underline; 21 | } 22 | 23 | /* color hyperlinked inline code items 24 | identically to normal links */ 25 | .rust a > .hljs, 26 | .navy a > .hljs, 27 | .coal a > .hljs{ 28 | color: #2b79a2; 29 | } 30 | 31 | .light a > .hljs { 32 | color: #4183c4; 33 | } 34 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /xtask/README.md: -------------------------------------------------------------------------------- 1 | # xtask - (Rust Cookbook) 2 | 3 | **Rust Dependencies**: 4 | - Make sure you have the required tools installed: 5 | ```bash 6 | cargo install mdbook@0.4.43 lychee@0.17.0 7 | ``` 8 | 9 | ## Available Tasks 10 | 11 | ### `test` 12 | Run various tests for the project. You can specify individual tests or run them all. 13 | 14 | - `cargo`: Run the `cargo test` command for the Rust code. 15 | - `spellcheck`: Run the spellcheck script. 16 | - `link`: Verify links within the project. 17 | - `all`: Run all the tests (default). 18 | 19 | **Usage:** 20 | ```bash 21 | cargo xtask test [all|cargo|spellcheck|link] 22 | ``` 23 | 24 | ### `book` 25 | Build or serve the project's documentation using `mdbook`. 26 | 27 | - `build`: Build the book (default). 28 | - `serve`: Serve the book locally and open it in a browser. 29 | 30 | **Usage:** 31 | ```bash 32 | cargo xtask book [build|serve] 33 | ``` 34 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | mod tests; 2 | mod mdbook; 3 | 4 | use std::path::{Path, PathBuf}; 5 | use std::{env, error::Error}; 6 | 7 | fn main() { 8 | if let Err(e) = try_main() { 9 | eprintln!("{}", e); 10 | std::process::exit(-1); 11 | } 12 | } 13 | 14 | fn try_main() -> Result<(), Box> { 15 | let task = env::args().nth(1); 16 | match task.as_deref() { 17 | Some("test") => { 18 | let sub_task = env::args().nth(2).unwrap_or_else(|| "all".to_string()); 19 | tests::run_test(&sub_task)? 20 | } 21 | Some("book") => { 22 | let sub_task = env::args().nth(2).unwrap_or_else(|| "build".to_string()); 23 | mdbook::run_book(&sub_task)? 24 | } 25 | _ => print_help(), 26 | } 27 | Ok(()) 28 | } 29 | 30 | fn project_root() -> PathBuf { 31 | Path::new(&env!("CARGO_MANIFEST_DIR")) 32 | .ancestors() 33 | .nth(1) 34 | .unwrap() 35 | .to_path_buf() 36 | } 37 | 38 | fn print_help() { 39 | eprintln!("Available tasks:"); 40 | eprintln!( 41 | " test [all|cargo|spellcheck|link] - Run the tests. Use 'all' to run all tests (default), or specify individual tests." 42 | ); 43 | eprintln!( 44 | " book [build] - Build the book using mdbook. Default if no subcommand is specified." 45 | ); 46 | eprintln!(" book serve - Serve the book using mdbook and open it in a browser."); 47 | eprintln!(); 48 | eprintln!("Usage:"); 49 | eprintln!(" cargo xtask [subcommand]"); 50 | eprintln!(); 51 | eprintln!("Examples:"); 52 | eprintln!(" cargo xtask test"); 53 | eprintln!(" cargo xtask test all"); 54 | eprintln!(" cargo xtask test cargo"); 55 | eprintln!(" cargo xtask book"); 56 | eprintln!(" cargo xtask book serve"); 57 | } -------------------------------------------------------------------------------- /xtask/src/mdbook.rs: -------------------------------------------------------------------------------- 1 | use crate::project_root; 2 | use std::{error::Error, process::Command}; 3 | 4 | pub fn run_book(task: &str) -> Result<(), Box> { 5 | let args: &[&str] = if task == "serve" { &["--open"] } else { &[] }; 6 | 7 | execute_mdbook_command(task, args)?; 8 | 9 | Ok(()) 10 | } 11 | 12 | fn execute_mdbook_command(command: &str, additional_args: &[&str]) -> Result<(), Box> { 13 | check_mdbook_version()?; 14 | 15 | let book_dest = project_root().join("book").to_str().unwrap().to_string(); 16 | 17 | let mut args = vec![command, "--dest-dir", &book_dest]; 18 | args.extend_from_slice(additional_args); 19 | 20 | let status = Command::new("mdbook") 21 | .current_dir(project_root()) 22 | .args(&args) 23 | .status()?; 24 | 25 | if !status.success() { 26 | return Err(format!("`mdbook {command}` failed to run successfully!").into()); 27 | } 28 | 29 | Ok(()) 30 | } 31 | 32 | fn check_mdbook_version() -> Result<(), Box> { 33 | let required_version = "0.4.43"; 34 | 35 | let output = Command::new("mdbook").arg("--version").output()?; 36 | 37 | if !output.status.success() { 38 | println!("Error: `mdbook` not found. Please ensure it is installed!"); 39 | println!("You can install it using:"); 40 | println!(" cargo install mdbook@{required_version}"); 41 | return Err(Box::new(std::io::Error::new( 42 | std::io::ErrorKind::NotFound, 43 | "`mdbook` is not installed", 44 | ))); 45 | } 46 | 47 | let version_output = String::from_utf8_lossy(&output.stdout); 48 | let version_str = version_output.trim(); 49 | 50 | if !version_str.starts_with(&format!("mdbook {}", required_version)) { 51 | println!( 52 | "Warning: You are using version {version_str} of `mdbook`. Version {required_version} is required." 53 | ); 54 | println!( 55 | "Errors may occur if using a different version. Please install version {required_version}:" 56 | 57 | ); 58 | println!(" cargo install mdbook@{required_version}"); 59 | } 60 | 61 | Ok(()) 62 | } 63 | --------------------------------------------------------------------------------