diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 2c2f4089..a9da83cd 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -18,9 +18,14 @@ - [Data Structures](data_structures.md) - [Constants](data_structures/constant.md) - [Custom](data_structures/custom.md) +- [Development Tools](development_tools.md) + - [Debugging](development_tools/debugging.md) + - [Log Messages](development_tools/debugging/log.md) + - [Configure Logging](development_tools/debugging/config_log.md) + - [Versioning](development_tools/versioning.md) + - [Build Time Tooling](development_tools/build_tools.md) - [Basics](basics.md) - [Encoding](encoding.md) - [Networking](net.md) - [Application development](app.md) - [Logging](logging.md) -- [Build Time Tooling](build_tools.md) diff --git a/src/app.md b/src/app.md index 7aad1bcf..a0e9118d 100644 --- a/src/app.md +++ b/src/app.md @@ -358,263 +358,15 @@ fn run() -> Result<()> { # quick_main!(run); ``` -[ex-semver-increment]: #ex-semver-increment - -## Parse and increment a version string. +{{#include development_tools/versioning/semver-increment.md}} -[![semver-badge]][semver] [![cat-config-badge]][cat-config] +{{#include development_tools/versioning/semver-complex.md}} -Constructs a [`semver::Version`] from a string literal using [`Version::parse`], then increments it by patch, minor, and major version number one by one. +{{#include development_tools/versioning/semver-prerelease.md}} -Note that in accordance with the [Semantic Versioning Specification], incrementing the minor version number resets the patch version number to 0 and incrementing the major version number resets both the minor and patch version numbers to 0. +{{#include development_tools/versioning/semver-latest.md}} -```rust -# #[macro_use] -# extern crate error_chain; -extern crate semver; - -use semver::Version; -# -# error_chain! { -# foreign_links { -# SemVer(semver::SemVerError); -# } -# } - -fn run() -> Result<()> { - let mut parsed_version = Version::parse("0.2.6")?; - - assert_eq!( - parsed_version, - Version { - major: 0, - minor: 2, - patch: 6, - pre: vec![], - build: vec![], - } - ); - - parsed_version.increment_patch(); - assert_eq!(parsed_version.to_string(), "0.2.7"); - println!("New patch release: v{}", parsed_version); - - parsed_version.increment_minor(); - assert_eq!(parsed_version.to_string(), "0.3.0"); - println!("New minor release: v{}", parsed_version); - - parsed_version.increment_major(); - assert_eq!(parsed_version.to_string(), "1.0.0"); - println!("New major release: v{}", parsed_version); - - Ok(()) -} -# -# quick_main!(run); -``` - -[ex-semver-complex]: #ex-semver-complex - -## Parse a complex version string. - -[![semver-badge]][semver] [![cat-config-badge]][cat-config] - -Constructs a [`semver::Version`] from a complex version string using [`Version::parse`]. The string -contains pre-release and build metadata as defined in the [Semantic Versioning Specification]. - -Note that, in accordance with the Specification, build metadata is parsed but not considered when -comparing versions. In other words, two versions may be equal even if their build strings differ. - -```rust -# #[macro_use] -# extern crate error_chain; -extern crate semver; - -use semver::{Identifier, Version}; -# -# error_chain! { -# foreign_links { -# SemVer(semver::SemVerError); -# } -# } - -fn run() -> Result<()> { - let version_str = "1.0.49-125+g72ee7853"; - let parsed_version = Version::parse(version_str)?; - - assert_eq!( - parsed_version, - Version { - major: 1, - minor: 0, - patch: 49, - pre: vec![Identifier::Numeric(125)], - build: vec![], - } - ); - assert_eq!( - parsed_version.build, - vec![Identifier::AlphaNumeric(String::from("g72ee7853"))] - ); - - let serialized_version = parsed_version.to_string(); - assert_eq!(&serialized_version, version_str); - - Ok(()) -} -# -# quick_main!(run); -``` - -[ex-semver-prerelease]: #ex-semver-prerelease - -## Check if given version is pre-release. - -[![semver-badge]][semver] [![cat-config-badge]][cat-config] - -Given two versions, we assert (by using [`is_prerelease`]) that one is pre-release and that the other is not. - -```rust -# #[macro_use] -# extern crate error_chain; -extern crate semver; - -use semver::Version; -# -# error_chain! { -# foreign_links { -# SemVer(semver::SemVerError); -# } -# } - -fn run() -> Result<()> { - let version_1 = Version::parse("1.0.0-alpha")?; - let version_2 = Version::parse("1.0.0")?; - - assert!(version_1.is_prerelease()); - assert!(!version_2.is_prerelease()); - - Ok(()) -} -# -# quick_main!(run); -``` - -[ex-semver-latest]: #ex-semver-latest - -## Find the latest version satisfying given range -[![semver-badge]][semver] [![cat-config-badge]][cat-config] - -Given a list of version &strs, finds the latest [`semver::Version`] that satisfying a given [`semver::VersionReq`] using [`VersionReq::matches`]. - -```rust -# #[macro_use] -# extern crate error_chain; -extern crate semver; - -use semver::{Version, VersionReq}; -# -# error_chain! { -# foreign_links { -# SemVer(semver::SemVerError); -# SemVerReq(semver::ReqParseError); -# } -# } - -fn find_max_matching_version<'a, I>(version_req_str: &str, iterable: I) -> Result> -where - I: IntoIterator, -{ - let vreq = VersionReq::parse(version_req_str)?; - - Ok( - iterable - .into_iter() - .filter_map(|s| Version::parse(s).ok()) - .filter(|s| vreq.matches(s)) - .max(), - ) -} - -fn run() -> Result<()> { - assert_eq!( - find_max_matching_version("<= 1.0.0", vec!["0.9.0", "1.0.0", "1.0.1"])?, - Some(Version::parse("1.0.0")?) - ); - - // Shows Semver precedence for pre-release tags - assert_eq!( - find_max_matching_version( - ">1.2.3-alpha.3", - vec![ - "1.2.3-alpha.3", - "1.2.3-alpha.4", - "1.2.3-alpha.10", - "1.2.3-beta.4", - "3.4.5-alpha.9", - ] - )?, - Some(Version::parse("1.2.3-beta.4")?) - ); - - Ok(()) -} -# -# quick_main!(run); -``` - -[ex-semver-command]: #ex-semver-command - -## Check external command version for compatibility -[![semver-badge]][semver] [![cat-text-processing-badge]][cat-text-processing] [![cat-os-badge]][cat-os] - -Runs `git --version` using [`Command`], then parses the version number into a [`semver::Version`] -using [`Version::parse`]. A [`semver::VersionReq`] is used to compare the parsed version to a -minimum version requirement. - -```rust,no_run -# #[macro_use] -# extern crate error_chain; -extern crate semver; - -use std::process::Command; -use semver::{Version, VersionReq}; -# -# error_chain! { -# foreign_links { -# Io(std::io::Error); -# Utf8(std::string::FromUtf8Error); -# SemVer(semver::SemVerError); -# SemVerReq(semver::ReqParseError); -# } -# } - -fn run() -> Result<()> { - let version_constraint = "> 1.12.0"; - let version_test = VersionReq::parse(version_constraint)?; - let output = Command::new("git").arg("--version").output()?; - - if !output.status.success() { - bail!("Command executed with failing error code"); - } - - let stdout = String::from_utf8(output.stdout)?; - // `git --version` output: "git version x.y.z" - let version = stdout.split(" ").last().ok_or_else(|| { - "Invalid command output" - })?; - let parsed_version = Version::parse(version)?; - - if !version_test.matches(&parsed_version) { - bail!("Command version lower than minimum supported version (found {}, need {})", - parsed_version, version_constraint); - } - - Ok(()) -} -# -# quick_main!(run); -``` +{{#include development_tools/versioning/semver-command.md}} {{#include links.md}} diff --git a/src/basics.md b/src/basics.md index 00322448..eda085d2 100644 --- a/src/basics.md +++ b/src/basics.md @@ -689,8 +689,6 @@ fn run() -> Result<()> { {{#include cryptography/encryption/pbkdf2.md}} - -[ex-bitflags]: #ex-bitflags {{#include data_structures/custom/bitfield.md}} [ex-random-file-access]: #ex-random-file-access diff --git a/src/build_tools.md b/src/build_tools.md deleted file mode 100644 index df368cb7..00000000 --- a/src/build_tools.md +++ /dev/null @@ -1,254 +0,0 @@ -# Build Time Tooling - -This section covers "build-time" tooling, or code that is run prior to compiling a crate's source code. -Conventionally, build-time code lives in a **build.rs** file and is commonly referred to as a "build script". -Common use cases include rust code generation and compilation of bundled C/C++/asm code. -See crates.io's [documentation on the matter][build-script-docs] for more information. - - -| Recipe | Crates | Categories | -|--------|--------|------------| -| [Compile and link statically to a bundled C library][ex-cc-static-bundled] | [![cc-badge]][cc] | [![cat-development-tools-badge]][cat-development-tools] | -| [Compile and link statically to a bundled C++ library][ex-cc-static-bundled-cpp] | [![cc-badge]][cc] | [![cat-development-tools-badge]][cat-development-tools] | -| [Compile a C library while setting custom defines][ex-cc-custom-defines] | [![cc-badge]][cc] | [![cat-development-tools-badge]][cat-development-tools] | - -[ex-cc-static-bundled]: #ex-cc-static-bundled - -## Compile and link statically to a bundled C library - -[![cc-badge]][cc] [![cat-development-tools-badge]][cat-development-tools] - -To accommodate scenarios where additional C, C++, or assembly is required in a project, the [**cc**][cc] crate -offers a simple api for compiling bundled C/C++/asm code into static libraries (**.a**) that can be statically linked to by **rustc**. - -The following example has some bundled C code (**src/hello.c**) that will be used from rust. -Before compiling our rust source code, the "build" file (**build.rs**) specified in **Cargo.toml** will run. -Using the [**cc**][cc] crate, a static library file will be produced (in this case, **libhello.a**, see -[`compile` docs][cc-build-compile]) which can then be used from rust by declaring the external function signatures in an `extern` block. - -Since the bundled C is very simple, only a single source file needs to be passed to [`cc::Build`][cc-build]. -For more complex build requirements, [`cc::Build`][cc-build] offers a full suite of builder methods for specifying -[`include`][cc-build-include] paths and extra compiler [`flag`][cc-build-flag]s. - -### `Cargo.toml` - -```toml -[package] -... -build = "build.rs" - -[build-dependencies] -cc = "1" - -[dependencies] -error-chain = "0.11" -``` - -### `build.rs` - -```rust,no_run -extern crate cc; - -fn main() { - cc::Build::new() - .file("src/hello.c") - .compile("hello"); // outputs `libhello.a` -} -``` - -### `src/hello.c` - -```c -#include - - -void hello() { - printf("Hello from C!\n"); -} - -void greet(const char* name) { - printf("Hello, %s!\n", name); -} -``` - -### `src/main.rs` - -```rust,ignore -# #[macro_use] extern crate error_chain; -use std::ffi::CString; -use std::os::raw::c_char; -# -# error_chain! { -# foreign_links { -# NulError(::std::ffi::NulError); -# Io(::std::io::Error); -# } -# } -# -# fn prompt(s: &str) -> Result { -# use std::io::Write; -# print!("{}", s); -# std::io::stdout().flush()?; -# let mut input = String::new(); -# std::io::stdin().read_line(&mut input)?; -# Ok(input.trim().to_string()) -# } - -extern { - fn hello(); - fn greet(name: *const c_char); -} - -fn run() -> Result<()> { - unsafe { hello() } - let name = prompt("What's your name? ")?; - let c_name = CString::new(name)?; - unsafe { greet(c_name.as_ptr()) } - Ok(()) -} -# -# quick_main!(run); -``` - - - -[ex-cc-static-bundled-cpp]: #ex-cc-static-bundled-cpp - -## Compile and link statically to a bundled C++ library - -[![cc-badge]][cc] [![cat-development-tools-badge]][cat-development-tools] - -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. - - -### `Cargo.toml` - -```toml -[package] -... -build = "build.rs" - -[build-dependencies] -cc = "1" -``` - -### `build.rs` - -```rust,no_run -extern crate cc; - -fn main() { - cc::Build::new() - .cpp(true) - .file("src/foo.cpp") - .compile("foo"); -} -``` - -### `src/foo.cpp` - -```cpp -extern "C" { - int multiply(int x, int y); -} - -int multiply(int x, int y) { - return x*y; -} -``` - -### `src/main.rs` - -```rust,ignore -extern { - fn multiply(x : i32, y : i32) -> i32; -} - -fn main(){ - unsafe { - println!("{}", multiply(5,7)); - } -} -``` - -[ex-cc-custom-defines]: #ex-cc-custom-defines - -## Compile a C library while setting custom defines - -[![cc-badge]][cc] [![cat-development-tools-badge]][cat-development-tools] - -It is simple to build bundled C code with custom defines using [`cc::Build::define`]. -It takes an [`Option`] value, so it is possible to create defines such as `#define APP_NAME "foo"` -as well as `#define WELCOME` (pass `None` as the value for a value-less defne). This example builds -a bundled C file with dynamic defines set in `build.rs` and prints "**Welcome to foo - version 1.0.2**" -when run. Cargo sets some [environment variables][cargo-env] which may be useful for some custom defines. - - -### `Cargo.toml` - -```toml -[package] -... -version = "1.0.2" -build = "build.rs" - -[build-dependencies] -cc = "1" -``` - -### `build.rs` - -```rust,no_run -extern crate cc; - -fn main() { - cc::Build::new() - .define("APP_NAME", "\"foo\"") - .define("VERSION", format!("\"{}\"", env!("CARGO_PKG_VERSION")).as_str()) - .define("WELCOME", None) - .file("src/foo.c") - .compile("foo"); -} -``` - -### `src/foo.c` - -```c -#include - -void print_app_info() { -#ifdef WELCOME - printf("Welcome to "); -#endif - printf("%s - version %s\n", APP_NAME, VERSION); -} -``` - -### `src/main.rs` - -```rust,ignore -extern { - fn print_app_info(); -} - -fn main(){ - unsafe { - print_app_info(); - } -} -``` - -{{#include links.md}} - - - -[`cc::Build::define`]: https://docs.rs/cc/*/cc/struct.Build.html#method.define -[`Option`]: https://doc.rust-lang.org/std/option/enum.Option.html -[build-script-docs]: http://doc.crates.io/build-script.html -[cargo-env]: http://doc.crates.io/environment-variables.html#environment-variables-cargo-sets-for-crates -[cc-build-compile]: https://docs.rs/cc/*/cc/struct.Build.html#method.compile -[cc-build-cpp]: https://docs.rs/cc/*/cc/struct.Build.html#method.cpp -[cc-build-flag]: https://docs.rs/cc/*/cc/struct.Build.html#method.flag -[cc-build-include]: https://docs.rs/cc/*/cc/struct.Build.html#method.include -[cc-build]: https://docs.rs/cc/*/cc/struct.Build.html -[playground]: https://play.rust-lang.org diff --git a/src/development_tools.md b/src/development_tools.md new file mode 100644 index 00000000..94b167ab --- /dev/null +++ b/src/development_tools.md @@ -0,0 +1,33 @@ +# Development Tools + +{{#include development_tools/debugging.md}} + +## Versioning + +| Recipe | Crates | Categories | +|--------|--------|------------| +| [Parse and increment a version string][ex-semver-increment] | [![semver-badge]][semver] | [![cat-config-badge]][cat-config] | +| [Parse a complex version string][ex-semver-complex] | [![semver-badge]][semver] | [![cat-config-badge]][cat-config] | +| [Check if given version is pre-release][ex-semver-prerelease] | [![semver-badge]][semver] | [![cat-config-badge]][cat-config] | +| [Find the latest version satisfying given range][ex-semver-latest] | [![semver-badge]][semver] | [![cat-config-badge]][cat-config] | +| [Check external command version for compatibility][ex-semver-command] | [![semver-badge]][semver] | [![cat-text-processing-badge]][cat-text-processing] [![cat-os-badge]][cat-os] + +## Build Time + +| Recipe | Crates | Categories | +|--------|--------|------------| +| [Compile and link statically to a bundled C library][ex-cc-static-bundled] | [![cc-badge]][cc] | [![cat-development-tools-badge]][cat-development-tools] | +| [Compile and link statically to a bundled C++ library][ex-cc-static-bundled-cpp] | [![cc-badge]][cc] | [![cat-development-tools-badge]][cat-development-tools] | +| [Compile a C library while setting custom defines][ex-cc-custom-defines] | [![cc-badge]][cc] | [![cat-development-tools-badge]][cat-development-tools] | + +[ex-semver-increment]: development_tools/versioning.html#parse-and-increment-a-version-string +[ex-semver-complex]: development_tools/versioning.html#parse-a-complex-version-string +[ex-semver-prerelease]: development_tools/versioning.html#check-if-given-version-is-pre-release +[ex-semver-latest]: development_tools/versioning.html#find-the-latest-version-satisfying-given-range +[ex-semver-command]: development_tools/versioning.html#check-external-command-version-for-compatibility + +[ex-cc-static-bundled]: development_tools/build_tools.html#compile-and-link-statically-to-a-bundled-c-library +[ex-cc-static-bundled-cpp]: development_tools/build_tools.html#compile-and-link-statically-to-a-bundled-c-library-1 +[ex-cc-custom-defines]: development_tools/build_tools.html#compile-a-c-library-while-setting-custom-defines + +{{#include links.md}} diff --git a/src/development_tools/build_tools.md b/src/development_tools/build_tools.md new file mode 100644 index 00000000..f889692b --- /dev/null +++ b/src/development_tools/build_tools.md @@ -0,0 +1,32 @@ +# Build Time Tooling + +This section covers "build-time" tooling, or code that is run prior to compiling a crate's source code. +Conventionally, build-time code lives in a **build.rs** file and is commonly referred to as a "build script". +Common use cases include rust code generation and compilation of bundled C/C++/asm code. +See crates.io's [documentation on the matter][build-script-docs] for more information. + + + + +{{#include build_tools/cc-bundled-static.md}} + + + +{{#include build_tools/cc-bundled-cpp.md}} + +{{#include build_tools/cc-defines.md}} + +{{#include ../links.md}} + + + +[`cc::Build::define`]: https://docs.rs/cc/*/cc/struct.Build.html#method.define +[`Option`]: https://doc.rust-lang.org/std/option/enum.Option.html +[build-script-docs]: http://doc.crates.io/build-script.html +[cargo-env]: http://doc.crates.io/environment-variables.html#environment-variables-cargo-sets-for-crates +[cc-build-compile]: https://docs.rs/cc/*/cc/struct.Build.html#method.compile +[cc-build-cpp]: https://docs.rs/cc/*/cc/struct.Build.html#method.cpp +[cc-build-flag]: https://docs.rs/cc/*/cc/struct.Build.html#method.flag +[cc-build-include]: https://docs.rs/cc/*/cc/struct.Build.html#method.include +[cc-build]: https://docs.rs/cc/*/cc/struct.Build.html +[playground]: https://play.rust-lang.org diff --git a/src/development_tools/build_tools/cc-bundled-cpp.md b/src/development_tools/build_tools/cc-bundled-cpp.md new file mode 100644 index 00000000..7d273846 --- /dev/null +++ b/src/development_tools/build_tools/cc-bundled-cpp.md @@ -0,0 +1,58 @@ +[ex-cc-static-bundled-cpp]: #ex-cc-static-bundled-cpp + +## Compile and link statically to a bundled C++ library + +[![cc-badge]][cc] [![cat-development-tools-badge]][cat-development-tools] + +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. + + +### `Cargo.toml` + +```toml +[package] +... +build = "build.rs" + +[build-dependencies] +cc = "1" +``` + +### `build.rs` + +```rust,no_run +extern crate cc; + +fn main() { + cc::Build::new() + .cpp(true) + .file("src/foo.cpp") + .compile("foo"); +} +``` + +### `src/foo.cpp` + +```cpp +extern "C" { + int multiply(int x, int y); +} + +int multiply(int x, int y) { + return x*y; +} +``` + +### `src/main.rs` + +```rust,ignore +extern { + fn multiply(x : i32, y : i32) -> i32; +} + +fn main(){ + unsafe { + println!("{}", multiply(5,7)); + } +} +``` diff --git a/src/development_tools/build_tools/cc-bundled-static.md b/src/development_tools/build_tools/cc-bundled-static.md new file mode 100644 index 00000000..e2590ab4 --- /dev/null +++ b/src/development_tools/build_tools/cc-bundled-static.md @@ -0,0 +1,97 @@ +[ex-cc-static-bundled]: #ex-cc-static-bundled + +## Compile and link statically to a bundled C library + +[![cc-badge]][cc] [![cat-development-tools-badge]][cat-development-tools] + +To accommodate scenarios where additional C, C++, or assembly is required in a project, the [**cc**][cc] crate +offers a simple api for compiling bundled C/C++/asm code into static libraries (**.a**) that can be statically linked to by **rustc**. + +The following example has some bundled C code (**src/hello.c**) that will be used from rust. +Before compiling our rust source code, the "build" file (**build.rs**) specified in **Cargo.toml** will run. +Using the [**cc**][cc] crate, a static library file will be produced (in this case, **libhello.a**, see +[`compile` docs][cc-build-compile]) which can then be used from rust by declaring the external function signatures in an `extern` block. + +Since the bundled C is very simple, only a single source file needs to be passed to [`cc::Build`][cc-build]. +For more complex build requirements, [`cc::Build`][cc-build] offers a full suite of builder methods for specifying +[`include`][cc-build-include] paths and extra compiler [`flag`][cc-build-flag]s. + +### `Cargo.toml` + +```toml +[package] +... +build = "build.rs" + +[build-dependencies] +cc = "1" + +[dependencies] +error-chain = "0.11" +``` + +### `build.rs` + +```rust,no_run +extern crate cc; + +fn main() { + cc::Build::new() + .file("src/hello.c") + .compile("hello"); // outputs `libhello.a` +} +``` + +### `src/hello.c` + +```c +#include + + +void hello() { + printf("Hello from C!\n"); +} + +void greet(const char* name) { + printf("Hello, %s!\n", name); +} +``` + +### `src/main.rs` + +```rust,ignore +# #[macro_use] extern crate error_chain; +use std::ffi::CString; +use std::os::raw::c_char; +# +# error_chain! { +# foreign_links { +# NulError(::std::ffi::NulError); +# Io(::std::io::Error); +# } +# } +# +# fn prompt(s: &str) -> Result { +# use std::io::Write; +# print!("{}", s); +# std::io::stdout().flush()?; +# let mut input = String::new(); +# std::io::stdin().read_line(&mut input)?; +# Ok(input.trim().to_string()) +# } + +extern { + fn hello(); + fn greet(name: *const c_char); +} + +fn run() -> Result<()> { + unsafe { hello() } + let name = prompt("What's your name? ")?; + let c_name = CString::new(name)?; + unsafe { greet(c_name.as_ptr()) } + Ok(()) +} +# +# quick_main!(run); +``` diff --git a/src/development_tools/build_tools/cc-defines.md b/src/development_tools/build_tools/cc-defines.md new file mode 100644 index 00000000..fc92ba91 --- /dev/null +++ b/src/development_tools/build_tools/cc-defines.md @@ -0,0 +1,66 @@ +[ex-cc-custom-defines]: #ex-cc-custom-defines + +## Compile a C library while setting custom defines + +[![cc-badge]][cc] [![cat-development-tools-badge]][cat-development-tools] + +It is simple to build bundled C code with custom defines using [`cc::Build::define`]. +It takes an [`Option`] value, so it is possible to create defines such as `#define APP_NAME "foo"` +as well as `#define WELCOME` (pass `None` as the value for a value-less defne). This example builds +a bundled C file with dynamic defines set in `build.rs` and prints "**Welcome to foo - version 1.0.2**" +when run. Cargo sets some [environment variables][cargo-env] which may be useful for some custom defines. + + +### `Cargo.toml` + +```toml +[package] +... +version = "1.0.2" +build = "build.rs" + +[build-dependencies] +cc = "1" +``` + +### `build.rs` + +```rust,no_run +extern crate cc; + +fn main() { + cc::Build::new() + .define("APP_NAME", "\"foo\"") + .define("VERSION", format!("\"{}\"", env!("CARGO_PKG_VERSION")).as_str()) + .define("WELCOME", None) + .file("src/foo.c") + .compile("foo"); +} +``` + +### `src/foo.c` + +```c +#include + +void print_app_info() { +#ifdef WELCOME + printf("Welcome to "); +#endif + printf("%s - version %s\n", APP_NAME, VERSION); +} +``` + +### `src/main.rs` + +```rust,ignore +extern { + fn print_app_info(); +} + +fn main(){ + unsafe { + print_app_info(); + } +} +``` diff --git a/src/development_tools/debugging.md b/src/development_tools/debugging.md new file mode 100644 index 00000000..d4ce3c03 --- /dev/null +++ b/src/development_tools/debugging.md @@ -0,0 +1,25 @@ +# Debugging + +| Recipe | Crates | Categories | +|--------|--------|------------| +| [Log a debug message to the console][ex-log-debug] | [![log-badge]][log] [![env_logger-badge]][env_logger] | [![cat-debugging-badge]][cat-debugging] | +| [Log an error message to the console][ex-log-error] | [![log-badge]][log] [![env_logger-badge]][env_logger] | [![cat-debugging-badge]][cat-debugging] | +| [Log to stdout instead of stderr][ex-log-stdout] | [![log-badge]][log] [![env_logger-badge]][env_logger] | [![cat-debugging-badge]][cat-debugging] | +| [Log messages with a custom logger][ex-log-custom-logger] | [![log-badge]][log] | [![cat-debugging-badge]][cat-debugging] | +| [Log to the Unix syslog][ex-log-syslog] | [![log-badge]][log] [![syslog-badge]][syslog] | [![cat-debugging-badge]][cat-debugging] | +| [Enable log levels per module][ex-log-mod] | [![log-badge]][log] [![env_logger-badge]][env_logger] | [![cat-debugging-badge]][cat-debugging] | +| [Use a custom environment variable to set up logging][ex-log-env-variable] | [![log-badge]][log] [![env_logger-badge]][env_logger] | [![cat-debugging-badge]][cat-debugging] | +| [Include timestamp in log messages][ex-log-timestamp] | [![log-badge]][log] [![env_logger-badge]][env_logger] [![chrono-badge]][chrono] | [![cat-debugging-badge]][cat-debugging] | +| [Log messages to a custom location][ex-log-custom] | [![log-badge]][log] [![log4rs-badge]][log4rs] | [![cat-debugging-badge]][cat-debugging] | + +[ex-log-debug]: development_tools/debugging/log.html#log-a-debug-message-to-the-console +[ex-log-error]: development_tools/debugging/log.html#log-an-error-message-to-the-console +[ex-log-stdout]: development_tools/debugging/log.html#log-to-stdout-instead-of-stderr +[ex-log-custom-logger]: development_tools/debugging/log.html#log-messages-with-a-custom-logger +[ex-log-syslog]: development_tools/debugging/log.html#log-to-the-unix-syslog +[ex-log-mod]: development_tools/debugging/config_log.html#enable-log-levels-per-module +[ex-log-env-variable]: development_tools/debugging/config_log.html#use-a-custom-environment-variable-to-set-up-logging +[ex-log-timestamp]: development_tools/debugging/config_log.html#include-timestamp-in-log-messages +[ex-log-custom]: development_tools/debugging/config_log.html#log-messages-to-a-custom-location + +{{#include ../links.md}} diff --git a/src/development_tools/debugging/config_log.md b/src/development_tools/debugging/config_log.md new file mode 100644 index 00000000..26069a1e --- /dev/null +++ b/src/development_tools/debugging/config_log.md @@ -0,0 +1,11 @@ +# Configure Logging + +{{#include config_log/log-mod.md}} + +{{#include config_log/log-env-variable.md}} + +{{#include config_log/log-timestamp.md}} + +{{#include config_log/log-custom.md}} + +{{#include ../../links.md}} diff --git a/src/development_tools/debugging/config_log/log-custom.md b/src/development_tools/debugging/config_log/log-custom.md new file mode 100644 index 00000000..1a3d0dc1 --- /dev/null +++ b/src/development_tools/debugging/config_log/log-custom.md @@ -0,0 +1,57 @@ + +## Log messages to a custom location + +[![log-badge]][log] [![log4rs-badge]][log4rs] [![cat-debugging-badge]][cat-debugging] + +Configures log to be output into custom location with [log4rs]. [log4rs] can use either an external YAML file or a programmatically constructed configuration. + +Firstly creates the log configuration with [`log4rs::append::file::FileAppender`] +using a custom pattern from [`log4rs::encode::pattern`]. + +Secondly assigns it to the [`log4rs::config::Config`] which has a root appender that uses the previously created `logfile` appender. Subsequently sets the default [`log::LogLevelFilter`] so that any logs with `Info` level or higher will be sent to the logger. + +```rust,no_run +# #[macro_use] +# extern crate error_chain; +#[macro_use] +extern crate log; +extern crate log4rs; + +use log::LogLevelFilter; +use log4rs::append::file::FileAppender; +use log4rs::encode::pattern::PatternEncoder; +use log4rs::config::{Appender, Config, Root}; +# +# error_chain! { +# foreign_links { +# Io(std::io::Error); +# LogConfig(log4rs::config::Errors); +# SetLogger(log::SetLoggerError); +# } +# } + +fn run() -> Result<()> { + let logfile = FileAppender::builder() + .encoder(Box::new(PatternEncoder::new("{l} - {m}\n"))) + .build("log/output.log")?; + + let config = Config::builder() + .appender(Appender::builder().build("logfile", Box::new(logfile))) + .build(Root::builder() + .appender("logfile") + .build(LogLevelFilter::Info))?; + + log4rs::init_config(config)?; + + info!("Hello, world!"); + + Ok(()) +} +# +# quick_main!(run); +``` + +[`log4rs::append::file::FileAppender`]: https://docs.rs/log4rs/*/log4rs/append/file/struct.FileAppender.html +[`log4rs::config::Config`]: https://docs.rs/log4rs/*/log4rs/config/struct.Config.html +[`log4rs::encode::pattern`]: https://docs.rs/log4rs/*/log4rs/encode/pattern/index.html +[`log::LogLevelFilter`]: https://doc.rust-lang.org/log/log/enum.LogLevelFilter.html diff --git a/src/development_tools/debugging/config_log/log-env-variable.md b/src/development_tools/debugging/config_log/log-env-variable.md new file mode 100644 index 00000000..50721e9c --- /dev/null +++ b/src/development_tools/debugging/config_log/log-env-variable.md @@ -0,0 +1,48 @@ + +## Use a custom environment variable to set up logging + +[![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] + +Logging is configured with [`LogBuilder`]. + +[`LogBuilder::parse`] parses `MY_APP_LOG` +environmental variable contents in the form of [`RUST_LOG`] syntax. +Then [`LogBuilder::init`] initializes the logger. +All these steps are normally done internally by [`env_logger::init`]. + +```rust +# #[macro_use] +# extern crate error_chain; +#[macro_use] +extern crate log; +extern crate env_logger; + +use std::env; +use env_logger::LogBuilder; + +# error_chain! { +# foreign_links { +# EnvLogger(log::SetLoggerError); +# } +# } +# +fn run() -> Result<()> { + LogBuilder::new() + .parse(&env::var("MY_APP_LOG").unwrap_or_default()) + .init()?; + + info!("informational message"); + warn!("warning message"); + error!("this is an error {}", "message"); + + Ok(()) +} +# +# quick_main!(run); +``` + +[`env_logger::init`]: https://doc.rust-lang.org/log/env_logger/fn.init.html +[`LogBuilder`]: https://doc.rust-lang.org/log/env_logger/struct.Builder.html +[`LogBuilder::init`]: https://doc.rust-lang.org/log/env_logger/struct.LogBuilder.html#method.init +[`LogBuilder::parse`]: https://doc.rust-lang.org/log/env_logger/struct.LogBuilder.html#method.parse +[`RUST_LOG`]: https://doc.rust-lang.org/log/env_logger/#enabling-logging diff --git a/src/development_tools/debugging/config_log/log-mod.md b/src/development_tools/debugging/config_log/log-mod.md new file mode 100644 index 00000000..09eae690 --- /dev/null +++ b/src/development_tools/debugging/config_log/log-mod.md @@ -0,0 +1,73 @@ + +## Enable log levels per module + +[![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] + +Creates two modules `foo` and nested `foo::bar` with logging directives +controlled separately with [`RUST_LOG`] environmental variable. + +```rust +# #[macro_use] +# extern crate error_chain; +#[macro_use] +extern crate log; +extern crate env_logger; + +mod foo { + mod bar { + pub fn run() { + warn!("[bar] warn"); + info!("[bar] info"); + debug!("[bar] debug"); + } + } + + pub fn run() { + warn!("[foo] warn"); + info!("[foo] info"); + debug!("[foo] debug"); + bar::run(); + } +} +# +# error_chain! { +# foreign_links { +# SetLogger(log::SetLoggerError); +# } +# } + +fn run() -> Result<()> { + env_logger::init()?; + warn!("[root] warn"); + info!("[root] info"); + debug!("[root] debug"); + foo::run(); + + Ok(()) +} +# +# quick_main!(run); +``` + +[`env_logger`][env_logger] output is controlled by [`RUST_LOG`] environmental +variable on per module basis with comma separated entries in format `path::to::module=log_level`. +Running the `test` application as follows: + +```bash +RUST_LOG="warn,test::foo=info,test::foo::bar=debug" ./test +``` + +Sets the default [`log::LogLevel`] to `warn`, module's `foo` and module's `foo::bar` +respectively to `info` and `debug`. The output is: + +```bash +WARN:test: [root] warn +WARN:test::foo: [foo] warn +INFO:test::foo: [foo] info +WARN:test::foo::bar: [bar] warn +INFO:test::foo::bar: [bar] info +DEBUG:test::foo::bar: [bar] debug +``` + +[`log::LogLevel`]: https://doc.rust-lang.org/log/log/enum.LogLevel.html +[`RUST_LOG`]: https://doc.rust-lang.org/log/env_logger/#enabling-logging diff --git a/src/development_tools/debugging/config_log/log-timestamp.md b/src/development_tools/debugging/config_log/log-timestamp.md new file mode 100644 index 00000000..6c497859 --- /dev/null +++ b/src/development_tools/debugging/config_log/log-timestamp.md @@ -0,0 +1,63 @@ + +## Include timestamp in log messages + +[![log-badge]][log] [![env_logger-badge]][env_logger] [![chrono-badge]][chrono] [![cat-debugging-badge]][cat-debugging] + +Creates a custom logger configuration with [`LogBuilder`]. +Each log entry calls [`Local::now`] to get the current [`DateTime`] in local timezone and uses [`DateTime::format`] with [`strftime::specifiers`] to format a timestamp used in the final log. + +The example calls [`LogBuilder::format`] to set a closure which formats each +message text with timestamp, [`LogRecord::level`] and body ([`LogRecord::args`]). + +```rust +# #[macro_use] +# extern crate error_chain; +#[macro_use] +extern crate log; +extern crate env_logger; +extern crate chrono; + +use std::env; +use env_logger::LogBuilder; +use chrono::Local; + +# +# error_chain! { +# foreign_links { +# SetLogger(log::SetLoggerError); +# } +# } + +fn run() -> Result<()> { + LogBuilder::new() + .format(|record| { + format!("{} [{}] - {}", + Local::now().format("%Y-%m-%dT%H:%M:%S"), + record.level(), + record.args()) + }) + .parse(&env::var("MY_APP_LOG").unwrap_or_default()) + .init()?; + + warn!("warn"); + info!("info"); + debug!("debug"); + Ok(()) +} +# +# quick_main!(run); +``` +Calling `MY_APP_LOG="info" cargo run` will result in similar output: +``` +2017-05-22T21:57:06 [WARN] - warn +2017-05-22T21:57:06 [INFO] - info +``` + +[`DateTime::format`]: https://docs.rs/chrono/*/chrono/datetime/struct.DateTime.html#method.format +[`DateTime`]: https://docs.rs/chrono/*/chrono/datetime/struct.DateTime.html +[`Local::now`]: https://docs.rs/chrono/*/chrono/offset/local/struct.Local.html#method.now +[`LogBuilder`]: https://doc.rust-lang.org/log/env_logger/struct.Builder.html +[`LogBuilder::format`]: https://doc.rust-lang.org/log/env_logger/struct.LogBuilder.html#method.format +[`LogRecord::args`]: https://doc.rust-lang.org/log/log/struct.LogRecord.html#method.args +[`LogRecord::level`]: https://doc.rust-lang.org/log/log/struct.LogRecord.html#method.level +[`strftime::specifiers`]: https://docs.rs/chrono/*/chrono/format/strftime/index.html#specifiers diff --git a/src/development_tools/debugging/log.md b/src/development_tools/debugging/log.md new file mode 100644 index 00000000..f2ba9665 --- /dev/null +++ b/src/development_tools/debugging/log.md @@ -0,0 +1,13 @@ +# Log Messages + +{{#include log/log-debug.md}} + +{{#include log/log-error.md}} + +{{#include log/log-stdout.md}} + +{{#include log/log-custom-logger.md}} + +{{#include log/log-syslog.md}} + +{{#include ../../links.md}} diff --git a/src/development_tools/debugging/log/log-custom-logger.md b/src/development_tools/debugging/log/log-custom-logger.md new file mode 100644 index 00000000..5fde48da --- /dev/null +++ b/src/development_tools/debugging/log/log-custom-logger.md @@ -0,0 +1,54 @@ + +## Log messages with a custom logger + +[![log-badge]][log] [![cat-debugging-badge]][cat-debugging] + +Implements a custom logger `ConsoleLogger` which prints to stdout. +In order to use the logging macros, `ConsoleLogger` implements +the [`log::Log`] trait and has to be installed via [`log::set_logger`]. + +```rust +# #[macro_use] +# extern crate error_chain; +#[macro_use] +extern crate log; + +use log::{LogRecord, LogLevel, LogMetadata, LogLevelFilter}; + +struct ConsoleLogger; + +impl log::Log for ConsoleLogger { + fn enabled(&self, metadata: &LogMetadata) -> bool { + metadata.level() <= LogLevel::Info + } + + fn log(&self, record: &LogRecord) { + if self.enabled(record.metadata()) { + println!("Rust says: {} - {}", record.level(), record.args()); + } + } +} +# +# error_chain! { +# foreign_links { +# SetLogger(log::SetLoggerError); +# } +# } + +fn run() -> Result<()> { + log::set_logger(|max_log_level| { + max_log_level.set(LogLevelFilter::Info); + Box::new(ConsoleLogger) + })?; + + info!("hello log"); + warn!("warning"); + error!("oops"); + Ok(()) +} +# +# quick_main!(run); +``` + +[`log::Log`]: https://doc.rust-lang.org/log/log/trait.Log.html +[`log::set_logger`]: https://doc.rust-lang.org/log/log/fn.set_logger.html diff --git a/src/development_tools/debugging/log/log-debug.md b/src/development_tools/debugging/log/log-debug.md new file mode 100644 index 00000000..85b620d8 --- /dev/null +++ b/src/development_tools/debugging/log/log-debug.md @@ -0,0 +1,53 @@ + +## Log a debug message to the console + +[![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] + +The `log` crate provides logging utilities. The `env_logger` crate configures +logging via an environment variable. + +```rust +#[macro_use] +extern crate log; +extern crate env_logger; +# +# #[macro_use] +# extern crate error_chain; +# +# error_chain! { +# foreign_links { +# SetLogger(log::SetLoggerError); +# } +# } + +fn execute_query(query: &str) { + debug!("Executing query: {}", query); + +} + +fn run() -> Result<()> { + env_logger::init()?; + + execute_query("DROP TABLE students"); + + Ok(()) +} +# +# quick_main!(run); +``` + +If you run this code, you'll notice that no output is printed. By default, the +log level is `error`, and any lower levels are dropped. + +We can change that easily by setting the `RUST_LOG` environment variable: + +``` +$ RUST_LOG=debug cargo run +``` + +After running this, you'll likely see a pile of logs from cargo, as well as the +following line at the very end of the output: + +``` +DEBUG:main: Executing query: DROP TABLE students +``` diff --git a/src/development_tools/debugging/log/log-error.md b/src/development_tools/debugging/log/log-error.md new file mode 100644 index 00000000..5c186f7f --- /dev/null +++ b/src/development_tools/debugging/log/log-error.md @@ -0,0 +1,45 @@ + +## Log an error message to the console + +[![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] + +```rust +#[macro_use] +extern crate log; +extern crate env_logger; +# +# #[macro_use] +# extern crate error_chain; +# +# error_chain! { +# foreign_links { +# SetLogger(log::SetLoggerError); +# } +# } + +fn execute_query(_query: &str) -> Result<()> { + // Do the thing, or maybe not + + bail!("I'm afraid I can't do that") +} + +fn run() -> Result<()> { + env_logger::init()?; + + let response = execute_query("DROP TABLE students"); + if let Err(err) = response { + // Log the error message and continue + error!("Failed to execute query: {}", err); + } + + Ok(()) +} +# +# quick_main!(run); +``` + +Run this code with `cargo run` and you should see the following line: + +``` +DEBUG:main: Failed to execute query: I'm afraid I can't do that +``` diff --git a/src/development_tools/debugging/log/log-stdout.md b/src/development_tools/debugging/log/log-stdout.md new file mode 100644 index 00000000..1d325066 --- /dev/null +++ b/src/development_tools/debugging/log/log-stdout.md @@ -0,0 +1,36 @@ + +## Log to stdout instead of stderr + +[![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] + +Creates a custom logger configuration using the [`LogBuilder::target`] to set the target of the log output to [`Target::Stdout`]. + +```rust +# #[macro_use] +# extern crate error_chain; +#[macro_use] +extern crate log; +extern crate env_logger; + +use env_logger::{LogBuilder, LogTarget}; +# +# error_chain! { +# foreign_links { +# SetLogger(log::SetLoggerError); +# } +# } + +fn run() -> Result<()> { + LogBuilder::new() + .target(LogTarget::Stdout) + .init()?; + + error!("This error has been printed to Stdout"); + Ok(()) +} +# +# quick_main!(run); +``` + +[`LogBuilder::target`]: https://doc.rust-lang.org/log/env_logger/struct.Builder.html#method.target +[`Target::Stdout`]: https://doc.rust-lang.org/log/env_logger/enum.Target.html diff --git a/src/development_tools/debugging/log/log-syslog.md b/src/development_tools/debugging/log/log-syslog.md new file mode 100644 index 00000000..a62e1fea --- /dev/null +++ b/src/development_tools/debugging/log/log-syslog.md @@ -0,0 +1,54 @@ + +## Log to the Unix syslog + +[![log-badge]][log] [![syslog-badge]][syslog] [![cat-debugging-badge]][cat-debugging] + +Logs messages to [UNIX syslog]. Initializes logger backend +with [`syslog::init`]. [`syslog::Facility`] indicates type of program submitting log, [`log::LogLevelFilter`] denotes allowed log verbosity +and `Option<&str>` holds optional application name. + +```rust,no_run +# #![allow(unused_imports)] +# #[macro_use] +# extern crate error_chain; +#[macro_use] +extern crate log; +# #[cfg(target_os = "linux")] +extern crate syslog; + +use log::LogLevelFilter; +# #[cfg(target_os = "linux")] +use syslog::Facility; +# +# #[cfg(target_os = "linux")] +# error_chain! { +# foreign_links { +# SetLogger(syslog::SyslogError); +# } +# } + +# #[cfg(target_os = "linux")] +fn run() -> Result<()> { + syslog::init(Facility::LOG_USER, + LogLevelFilter::Debug, + Some("My app name"))?; + debug!("this is a debug {}", "message"); + error!("this is an error!"); + Ok(()) +} +# +# #[cfg(not(target_os = "linux"))] +# error_chain! {} +# #[cfg(not(target_os = "linux"))] +# fn run() -> Result<()> { +# Ok(()) +# } +# +# quick_main!(run); +``` + +[`log::LogLevelFilter`]: https://doc.rust-lang.org/log/log/enum.LogLevelFilter.html +[`syslog::Facility`]: https://docs.rs/syslog/*/syslog/enum.Facility.html +[`syslog::init`]: https://docs.rs/syslog/*/syslog/fn.init.html + +[UNIX syslog]: https://www.gnu.org/software/libc/manual/html_node/Overview-of-Syslog.html diff --git a/src/development_tools/versioning.md b/src/development_tools/versioning.md new file mode 100644 index 00000000..90f87f6a --- /dev/null +++ b/src/development_tools/versioning.md @@ -0,0 +1,13 @@ +# Versioning + +{{#include versioning/semver-increment.md}} + +{{#include versioning/semver-complex.md}} + +{{#include versioning/semver-prerelease.md}} + +{{#include versioning/semver-latest.md}} + +{{#include versioning/semver-command.md}} + +{{#include ../links.md}} diff --git a/src/development_tools/versioning/semver-command.md b/src/development_tools/versioning/semver-command.md new file mode 100644 index 00000000..e7330d8c --- /dev/null +++ b/src/development_tools/versioning/semver-command.md @@ -0,0 +1,57 @@ +[ex-semver-command]: #ex-semver-command + +## Check external command version for compatibility +[![semver-badge]][semver] [![cat-text-processing-badge]][cat-text-processing] [![cat-os-badge]][cat-os] + +Runs `git --version` using [`Command`], then parses the version number into a [`semver::Version`] +using [`Version::parse`]. A [`semver::VersionReq`] is used to compare the parsed version to a +minimum version requirement. + +```rust,no_run +# #[macro_use] +# extern crate error_chain; +extern crate semver; + +use std::process::Command; +use semver::{Version, VersionReq}; +# +# error_chain! { +# foreign_links { +# Io(std::io::Error); +# Utf8(std::string::FromUtf8Error); +# SemVer(semver::SemVerError); +# SemVerReq(semver::ReqParseError); +# } +# } + +fn run() -> Result<()> { + let version_constraint = "> 1.12.0"; + let version_test = VersionReq::parse(version_constraint)?; + let output = Command::new("git").arg("--version").output()?; + + if !output.status.success() { + bail!("Command executed with failing error code"); + } + + let stdout = String::from_utf8(output.stdout)?; + // `git --version` output: "git version x.y.z" + let version = stdout.split(" ").last().ok_or_else(|| { + "Invalid command output" + })?; + let parsed_version = Version::parse(version)?; + + if !version_test.matches(&parsed_version) { + bail!("Command version lower than minimum supported version (found {}, need {})", + parsed_version, version_constraint); + } + + Ok(()) +} +# +# quick_main!(run); +``` + +[`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html +[`semver::Version`]: https://docs.rs/semver/*/semver/struct.Version.html +[`semver::VersionReq`]: https://docs.rs/semver/*/semver/struct.VersionReq.html +[`Version::parse`]: https://docs.rs/semver/*/semver/struct.Version.html#method.parse diff --git a/src/development_tools/versioning/semver-complex.md b/src/development_tools/versioning/semver-complex.md new file mode 100644 index 00000000..16b4d43d --- /dev/null +++ b/src/development_tools/versioning/semver-complex.md @@ -0,0 +1,57 @@ +[ex-semver-complex]: #ex-semver-complex + +## Parse a complex version string. + +[![semver-badge]][semver] [![cat-config-badge]][cat-config] + +Constructs a [`semver::Version`] from a complex version string using [`Version::parse`]. The string +contains pre-release and build metadata as defined in the [Semantic Versioning Specification]. + +Note that, in accordance with the Specification, build metadata is parsed but not considered when +comparing versions. In other words, two versions may be equal even if their build strings differ. + +```rust +# #[macro_use] +# extern crate error_chain; +extern crate semver; + +use semver::{Identifier, Version}; +# +# error_chain! { +# foreign_links { +# SemVer(semver::SemVerError); +# } +# } + +fn run() -> Result<()> { + let version_str = "1.0.49-125+g72ee7853"; + let parsed_version = Version::parse(version_str)?; + + assert_eq!( + parsed_version, + Version { + major: 1, + minor: 0, + patch: 49, + pre: vec![Identifier::Numeric(125)], + build: vec![], + } + ); + assert_eq!( + parsed_version.build, + vec![Identifier::AlphaNumeric(String::from("g72ee7853"))] + ); + + let serialized_version = parsed_version.to_string(); + assert_eq!(&serialized_version, version_str); + + Ok(()) +} +# +# quick_main!(run); +``` + +[`semver::Version`]: https://docs.rs/semver/*/semver/struct.Version.html +[`Version::parse`]: https://docs.rs/semver/*/semver/struct.Version.html#method.parse + +[Semantic Versioning Specification]: http://semver.org/ diff --git a/src/development_tools/versioning/semver-increment.md b/src/development_tools/versioning/semver-increment.md new file mode 100644 index 00000000..d3629289 --- /dev/null +++ b/src/development_tools/versioning/semver-increment.md @@ -0,0 +1,59 @@ +[ex-semver-increment]: #ex-semver-increment + +## Parse and increment a version string. + +[![semver-badge]][semver] [![cat-config-badge]][cat-config] + +Constructs a [`semver::Version`] from a string literal using [`Version::parse`], then increments it by patch, minor, and major version number one by one. + +Note that in accordance with the [Semantic Versioning Specification], incrementing the minor version number resets the patch version number to 0 and incrementing the major version number resets both the minor and patch version numbers to 0. + +```rust +# #[macro_use] +# extern crate error_chain; +extern crate semver; + +use semver::Version; +# +# error_chain! { +# foreign_links { +# SemVer(semver::SemVerError); +# } +# } + +fn run() -> Result<()> { + let mut parsed_version = Version::parse("0.2.6")?; + + assert_eq!( + parsed_version, + Version { + major: 0, + minor: 2, + patch: 6, + pre: vec![], + build: vec![], + } + ); + + parsed_version.increment_patch(); + assert_eq!(parsed_version.to_string(), "0.2.7"); + println!("New patch release: v{}", parsed_version); + + parsed_version.increment_minor(); + assert_eq!(parsed_version.to_string(), "0.3.0"); + println!("New minor release: v{}", parsed_version); + + parsed_version.increment_major(); + assert_eq!(parsed_version.to_string(), "1.0.0"); + println!("New major release: v{}", parsed_version); + + Ok(()) +} +# +# quick_main!(run); +``` + +[`semver::Version`]: https://docs.rs/semver/*/semver/struct.Version.html +[`Version::parse`]: https://docs.rs/semver/*/semver/struct.Version.html#method.parse + +[Semantic Versioning Specification]: http://semver.org/ diff --git a/src/development_tools/versioning/semver-latest.md b/src/development_tools/versioning/semver-latest.md new file mode 100644 index 00000000..2f717a91 --- /dev/null +++ b/src/development_tools/versioning/semver-latest.md @@ -0,0 +1,66 @@ +[ex-semver-latest]: #ex-semver-latest + +## Find the latest version satisfying given range +[![semver-badge]][semver] [![cat-config-badge]][cat-config] + +Given a list of version &strs, finds the latest [`semver::Version`] that satisfying a given [`semver::VersionReq`] using [`VersionReq::matches`]. + +```rust +# #[macro_use] +# extern crate error_chain; +extern crate semver; + +use semver::{Version, VersionReq}; +# +# error_chain! { +# foreign_links { +# SemVer(semver::SemVerError); +# SemVerReq(semver::ReqParseError); +# } +# } + +fn find_max_matching_version<'a, I>(version_req_str: &str, iterable: I) -> Result> +where + I: IntoIterator, +{ + let vreq = VersionReq::parse(version_req_str)?; + + Ok( + iterable + .into_iter() + .filter_map(|s| Version::parse(s).ok()) + .filter(|s| vreq.matches(s)) + .max(), + ) +} + +fn run() -> Result<()> { + assert_eq!( + find_max_matching_version("<= 1.0.0", vec!["0.9.0", "1.0.0", "1.0.1"])?, + Some(Version::parse("1.0.0")?) + ); + + // Shows Semver precedence for pre-release tags + assert_eq!( + find_max_matching_version( + ">1.2.3-alpha.3", + vec![ + "1.2.3-alpha.3", + "1.2.3-alpha.4", + "1.2.3-alpha.10", + "1.2.3-beta.4", + "3.4.5-alpha.9", + ] + )?, + Some(Version::parse("1.2.3-beta.4")?) + ); + + Ok(()) +} +# +# quick_main!(run); +``` + +[`semver::Version`]: https://docs.rs/semver/*/semver/struct.Version.html +[`semver::VersionReq`]: https://docs.rs/semver/*/semver/struct.VersionReq.html +[`VersionReq::matches`]: https://docs.rs/semver/*/semver/struct.VersionReq.html#method.matches diff --git a/src/development_tools/versioning/semver-prerelease.md b/src/development_tools/versioning/semver-prerelease.md new file mode 100644 index 00000000..eafeac5b --- /dev/null +++ b/src/development_tools/versioning/semver-prerelease.md @@ -0,0 +1,35 @@ +[ex-semver-prerelease]: #ex-semver-prerelease + +## Check if given version is pre-release. + +[![semver-badge]][semver] [![cat-config-badge]][cat-config] + +Given two versions, we assert (by using [`is_prerelease`]) that one is pre-release and that the other is not. + +```rust +# #[macro_use] +# extern crate error_chain; +extern crate semver; + +use semver::Version; +# +# error_chain! { +# foreign_links { +# SemVer(semver::SemVerError); +# } +# } + +fn run() -> Result<()> { + let version_1 = Version::parse("1.0.0-alpha")?; + let version_2 = Version::parse("1.0.0")?; + + assert!(version_1.is_prerelease()); + assert!(!version_2.is_prerelease()); + + Ok(()) +} +# +# quick_main!(run); +``` + +[`is_prerelease`]: https://docs.rs/semver/*/semver/struct.Version.html#method.is_prerelease diff --git a/src/intro.md b/src/intro.md index e9bfdc54..9be5db9f 100644 --- a/src/intro.md +++ b/src/intro.md @@ -26,6 +26,10 @@ community. It needs and welcomes help. For details see {{#include cryptography.md}} +{{#include data_structures.md}} + +{{#include development_tools.md}} + ## [Basics](basics.html) | Recipe | Crates | Categories | diff --git a/src/logging.md b/src/logging.md index 69eeda62..9e70a8f5 100644 --- a/src/logging.md +++ b/src/logging.md @@ -13,471 +13,31 @@ | [Log messages to a custom location][ex-log-custom] | [![log-badge]][log] [![log4rs-badge]][log4rs] | [![cat-debugging-badge]][cat-debugging] | [ex-log-debug]: #ex-log-debug - -## Log a debug message to the console - -[![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] - -The `log` crate provides logging utilities. The `env_logger` crate configures -logging via an environment variable. - -```rust -#[macro_use] -extern crate log; -extern crate env_logger; -# -# #[macro_use] -# extern crate error_chain; -# -# error_chain! { -# foreign_links { -# SetLogger(log::SetLoggerError); -# } -# } - -fn execute_query(query: &str) { - debug!("Executing query: {}", query); - - // then do the thing -} - -fn run() -> Result<()> { - env_logger::init()?; - - execute_query("DROP TABLE students"); - - Ok(()) -} -# -# quick_main!(run); -``` - -If you run this code, you'll notice that no output is printed. By default, the -log level is `error`, and any lower levels are dropped. - -We can change that easily by setting the `RUST_LOG` environment variable: - -``` -$ RUST_LOG=debug cargo run -``` - -After running this, you'll likely see a pile of logs from cargo, as well as the -following line at the very end of the output: - -``` -DEBUG:main: Executing query: DROP TABLE students -``` +{{#include development_tools/debugging/log/log-debug.md}} [ex-log-error]: #ex-log-error - -## Log an error message to the console - -[![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] - -```rust -#[macro_use] -extern crate log; -extern crate env_logger; -# -# #[macro_use] -# extern crate error_chain; -# -# error_chain! { -# foreign_links { -# SetLogger(log::SetLoggerError); -# } -# } - -fn execute_query(_query: &str) -> Result<()> { - // Do the thing, or maybe not - - bail!("I'm afraid I can't do that") -} - -fn run() -> Result<()> { - env_logger::init()?; - - let response = execute_query("DROP TABLE students"); - if let Err(err) = response { - // Log the error message and continue - error!("Failed to execute query: {}", err); - } - - Ok(()) -} -# -# quick_main!(run); -``` - -Run this code with `cargo run` and you should see the following line: - -``` -DEBUG:main: Failed to execute query: I'm afraid I can't do that -``` +{{#include development_tools/debugging/log/log-error.md}} [ex-log-mod]: #ex-log-mod - -## Enable log levels per module - -[![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] - -Creates two modules `foo` and nested `foo::bar` with logging directives -controlled separately with [`RUST_LOG`] environmental variable. - -```rust -# #[macro_use] -# extern crate error_chain; -#[macro_use] -extern crate log; -extern crate env_logger; - -mod foo { - mod bar { - pub fn run() { - warn!("[bar] warn"); - info!("[bar] info"); - debug!("[bar] debug"); - } - } - - pub fn run() { - warn!("[foo] warn"); - info!("[foo] info"); - debug!("[foo] debug"); - bar::run(); - } -} -# -# error_chain! { -# foreign_links { -# SetLogger(log::SetLoggerError); -# } -# } - -fn run() -> Result<()> { - env_logger::init()?; - warn!("[root] warn"); - info!("[root] info"); - debug!("[root] debug"); - foo::run(); - - Ok(()) -} -# -# quick_main!(run); -``` - -[`env_logger`][env_logger] output is controlled by [`RUST_LOG`] environmental -variable on per module basis with comma separated entries in format `path::to::module=log_level`. -Running the `test` application as follows: - -```bash -RUST_LOG="warn,test::foo=info,test::foo::bar=debug" ./test -``` - -Sets the default [`log::LogLevel`] to `warn`, module's `foo` and module's `foo::bar` -respectively to `info` and `debug`. The output is: - -```bash -WARN:test: [root] warn -WARN:test::foo: [foo] warn -INFO:test::foo: [foo] info -WARN:test::foo::bar: [bar] warn -INFO:test::foo::bar: [bar] info -DEBUG:test::foo::bar: [bar] debug -``` +{{#include development_tools/debugging/config_log/log-mod.md}} [ex-log-stdout]: #ex-log-stdout - -## Log to stdout instead of stderr - -[![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] - -Creates a custom logger configuration using the [`LogBuilder::target`] to set the target of the log output to [`Target::Stdout`]. - -```rust -# #[macro_use] -# extern crate error_chain; -#[macro_use] -extern crate log; -extern crate env_logger; - -use env_logger::{LogBuilder, LogTarget}; -# -# error_chain! { -# foreign_links { -# SetLogger(log::SetLoggerError); -# } -# } - -fn run() -> Result<()> { - LogBuilder::new() - .target(LogTarget::Stdout) - .init()?; - - error!("This error has been printed to Stdout"); - Ok(()) -} -# -# quick_main!(run); -``` +{{#include development_tools/debugging/log/log-stdout.md}} [ex-log-custom-logger]: #ex-log-custom-logger - -## Log messages with a custom logger - -[![log-badge]][log] [![cat-debugging-badge]][cat-debugging] - -Implements a custom logger `ConsoleLogger` which prints to stdout. -In order to use the logging macros, `ConsoleLogger` implements -the [`log::Log`] trait and has to be installed via [`log::set_logger`]. - -```rust -# #[macro_use] -# extern crate error_chain; -#[macro_use] -extern crate log; - -use log::{LogRecord, LogLevel, LogMetadata, LogLevelFilter}; - -struct ConsoleLogger; - -impl log::Log for ConsoleLogger { - fn enabled(&self, metadata: &LogMetadata) -> bool { - metadata.level() <= LogLevel::Info - } - - fn log(&self, record: &LogRecord) { - if self.enabled(record.metadata()) { - println!("Rust says: {} - {}", record.level(), record.args()); - } - } -} -# -# error_chain! { -# foreign_links { -# SetLogger(log::SetLoggerError); -# } -# } - -fn run() -> Result<()> { - log::set_logger(|max_log_level| { - max_log_level.set(LogLevelFilter::Info); - Box::new(ConsoleLogger) - })?; - - info!("hello log"); - warn!("warning"); - error!("oops"); - Ok(()) -} -# -# quick_main!(run); -``` +{{#include development_tools/debugging/log/log-custom-logger.md}} [ex-log-env-variable]: #ex-log-env-variable - -## Use a custom environment variable to set up logging - -[![log-badge]][log] [![env_logger-badge]][env_logger] [![cat-debugging-badge]][cat-debugging] - -Logging is configured with [`LogBuilder`]. - -[`LogBuilder::parse`] parses `MY_APP_LOG` -environmental variable contents in the form of [`RUST_LOG`] syntax. -Then [`LogBuilder::init`] initializes the logger. -All these steps are normally done internally by [`env_logger::init`]. - -```rust -# #[macro_use] -# extern crate error_chain; -#[macro_use] -extern crate log; -extern crate env_logger; - -use std::env; -use env_logger::LogBuilder; - -# error_chain! { -# foreign_links { -# EnvLogger(log::SetLoggerError); -# } -# } -# -fn run() -> Result<()> { - LogBuilder::new() - .parse(&env::var("MY_APP_LOG").unwrap_or_default()) - .init()?; - - info!("informational message"); - warn!("warning message"); - error!("this is an error {}", "message"); - - Ok(()) -} -# -# quick_main!(run); -``` +{{#include development_tools/debugging/config_log/log-env-variable.md}} [ex-log-timestamp]: #ex-log-timestamp - -## Include timestamp in log messages - -[![log-badge]][log] [![env_logger-badge]][env_logger] [![chrono-badge]][chrono] [![cat-debugging-badge]][cat-debugging] - -Creates a custom logger configuration with [`LogBuilder`]. -Each log entry calls [`Local::now`] to get the current [`DateTime`] in local timezone and uses [`DateTime::format`] with [`strftime::specifiers`] to format a timestamp used in the final log. - -The example calls [`LogBuilder::format`] to set a closure which formats each -message text with timestamp, [`LogRecord::level`] and body ([`LogRecord::args`]). - -```rust -# #[macro_use] -# extern crate error_chain; -#[macro_use] -extern crate log; -extern crate env_logger; -extern crate chrono; - -use std::env; -use env_logger::LogBuilder; -use chrono::Local; - -# -# error_chain! { -# foreign_links { -# SetLogger(log::SetLoggerError); -# } -# } - -fn run() -> Result<()> { - LogBuilder::new() - .format(|record| { - format!("{} [{}] - {}", - Local::now().format("%Y-%m-%dT%H:%M:%S"), - record.level(), - record.args()) - }) - .parse(&env::var("MY_APP_LOG").unwrap_or_default()) - .init()?; - - warn!("warn"); - info!("info"); - debug!("debug"); - Ok(()) -} -# -# quick_main!(run); -``` -Calling `MY_APP_LOG="info" cargo run` will result in similar output: -``` -2017-05-22T21:57:06 [WARN] - warn -2017-05-22T21:57:06 [INFO] - info -``` +{{#include development_tools/debugging/config_log/log-timestamp.md}} [ex-log-syslog]: #ex-log-syslog - -## Log to the Unix syslog - -[![log-badge]][log] [![syslog-badge]][syslog] [![cat-debugging-badge]][cat-debugging] - -Logs messages to [UNIX syslog]. Initializes logger backend -with [`syslog::init`]. [`syslog::Facility`] indicates type of program submitting log, [`log::LogLevelFilter`] denotes allowed log verbosity -and `Option<&str>` holds optional application name. - -```rust,no_run -# #![allow(unused_imports)] -# #[macro_use] -# extern crate error_chain; -#[macro_use] -extern crate log; -# #[cfg(target_os = "linux")] -extern crate syslog; - -use log::LogLevelFilter; -# #[cfg(target_os = "linux")] -use syslog::Facility; -# -# #[cfg(target_os = "linux")] -# error_chain! { -# foreign_links { -# SetLogger(syslog::SyslogError); -# } -# } - -# #[cfg(target_os = "linux")] -fn run() -> Result<()> { - syslog::init(Facility::LOG_USER, - LogLevelFilter::Debug, - Some("My app name"))?; - debug!("this is a debug {}", "message"); - error!("this is an error!"); - Ok(()) -} -# -# #[cfg(not(target_os = "linux"))] -# error_chain! {} -# #[cfg(not(target_os = "linux"))] -# fn run() -> Result<()> { -# Ok(()) -# } -# -# quick_main!(run); -``` +{{#include development_tools/debugging/log/log-syslog.md}} [ex-log-custom]: #ex-log-custom - -## Log messages to a custom location - -[![log-badge]][log] [![log4rs-badge]][log4rs] [![cat-debugging-badge]][cat-debugging] - -Configures log to be output into custom location with [log4rs]. [log4rs] can use either an external YAML file or a programmatically constructed configuration. - -Firstly creates the log configuration with [`log4rs::append::file::FileAppender`] -using a custom pattern from [`log4rs::encode::pattern`]. - -Secondly assigns it to the [`log4rs::config::Config`] which has a root appender that uses the previously created `logfile` appender. Subsequently sets the default [`log::LogLevelFilter`] so that any logs with `Info` level or higher will be sent to the logger. - -```rust,no_run -# #[macro_use] -# extern crate error_chain; -#[macro_use] -extern crate log; -extern crate log4rs; - -use log::LogLevelFilter; -use log4rs::append::file::FileAppender; -use log4rs::encode::pattern::PatternEncoder; -use log4rs::config::{Appender, Config, Root}; -# -# error_chain! { -# foreign_links { -# Io(std::io::Error); -# LogConfig(log4rs::config::Errors); -# SetLogger(log::SetLoggerError); -# } -# } - -fn run() -> Result<()> { - let logfile = FileAppender::builder() - .encoder(Box::new(PatternEncoder::new("{l} - {m}\n"))) - .build("log/output.log")?; - - let config = Config::builder() - .appender(Appender::builder().build("logfile", Box::new(logfile))) - .build(Root::builder() - .appender("logfile") - .build(LogLevelFilter::Info))?; - - log4rs::init_config(config)?; - - info!("Hello, world!"); - - Ok(()) -} -# -# quick_main!(run); -``` +{{#include development_tools/debugging/config_log/log-custom.md}} {{#include links.md}}