diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..3c1f41bd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +[*.rs] +indent_style = space +indent_size = 4 + +[*.toml] +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7585238e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +book diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..730a9747 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +sudo: false +dist: trusty +language: rust + +before_install: + - cargo install mdbook --vers '0.1.8' --debug --force + +script: + - mdbook build + +deploy: + provider: pages + skip-cleanup: true + github-token: $GITHUB_TOKEN # Set in travis-ci.org dashboard, marked secure + local-dir: book + keep-history: false + on: + branch: master diff --git a/book.toml b/book.toml new file mode 100644 index 00000000..c3b7fe0f --- /dev/null +++ b/book.toml @@ -0,0 +1,9 @@ +[book] +authors = ["The Rust CLI Working Group"] +multilingual = false +src = "src" +title = "Rust CLI WG" + +[output.html] +curly-quotes = true +additional-css = ["src/special-content.css"] diff --git a/src/SUMMARY.md b/src/SUMMARY.md new file mode 100644 index 00000000..1a1f7a04 --- /dev/null +++ b/src/SUMMARY.md @@ -0,0 +1,20 @@ +# Summary + +[Getting started](./getting_started.md) + +- [A command line app in 15 minutes](./tutorial/intro.md) + - [Project setup](./tutorial/setup.md) + - [Parsing command line arguments](./tutorial/cli-args.md) + - [First implementation of our example tool](./tutorial/impl-draft.md) + - [Nicer error reporting](./tutorial/errors.md) + - [Output for humans and machines](./tutorial/output.md) + - [Testing](./tutorial/testing.md) + - [Packaging and documentation](./tutorial/packaging.md) +- [In-depth topics](./in-depth/intro.md) + - [Signal handling](./in-depth/signals.md) + - [Using config files](./in-depth/config-files.md) + - [Exit codes](./in-depth/exit-code.md) + - [Communicating with humans](./in-depth/human-communication.md) + - [Communicating with machines](./in-depth/machine-communication.md) + - [Rendering documentation for you CLI apps](./in-depth/docs.md) + - [Packaging apps and distributing them for different platforms](./in-depth/packaging-distribution.md) diff --git a/src/getting_started.md b/src/getting_started.md new file mode 100644 index 00000000..3f203b75 --- /dev/null +++ b/src/getting_started.md @@ -0,0 +1,30 @@ +# Command line apps in Rust + +Rust is a statically compiled, fast language with great tooling and a rapidly growing ecosystem. +That makes it a great fit for writing command line applications: +They should be small, portable, and quick to run. +Command line applications are also a great way to get started with learning Rust; +or if you want to introduce Rust to your team! + +Writing a program with a simple command line interface (CLI) +is great a exercise for a beginner +who is new to the language and wants to get a feel for it. +There are many aspects to this topic, though, +that often only reveal themselves later on. + +This book is structure like this: +We start with a quick tutorial, +after which you'll end up with a working CLI tool. +You'll be exposed to a few of the core concepts of Rust +as well as the main aspects of CLI applications. +What follows are chapters that go into more detail +on some of these aspects. + +One last thing before we dive right into CLI applications: +If you found an error in this book +or want to help us write more content for it, +you can find its source [in the CLI WG repository][book-src]. +We'd love to year your feedback! +Thank you! + +[book-src]: https://github.com/rust-lang-nursery/cli-wg diff --git a/src/in-depth/config-files.md b/src/in-depth/config-files.md new file mode 100644 index 00000000..1d295fa1 --- /dev/null +++ b/src/in-depth/config-files.md @@ -0,0 +1 @@ +# Using config files diff --git a/src/in-depth/docs.md b/src/in-depth/docs.md new file mode 100644 index 00000000..fd05044a --- /dev/null +++ b/src/in-depth/docs.md @@ -0,0 +1 @@ +# Rendering documentation for you CLI apps diff --git a/src/in-depth/exit-code.md b/src/in-depth/exit-code.md new file mode 100644 index 00000000..4f13cfab --- /dev/null +++ b/src/in-depth/exit-code.md @@ -0,0 +1 @@ +# Exit codes diff --git a/src/in-depth/human-communication.md b/src/in-depth/human-communication.md new file mode 100644 index 00000000..687fdad8 --- /dev/null +++ b/src/in-depth/human-communication.md @@ -0,0 +1 @@ +# Communicating with humans diff --git a/src/in-depth/intro.md b/src/in-depth/intro.md new file mode 100644 index 00000000..467d41cf --- /dev/null +++ b/src/in-depth/intro.md @@ -0,0 +1 @@ +# In-depth topics diff --git a/src/in-depth/machine-communication.md b/src/in-depth/machine-communication.md new file mode 100644 index 00000000..f685448d --- /dev/null +++ b/src/in-depth/machine-communication.md @@ -0,0 +1 @@ +# Communicating with machines diff --git a/src/in-depth/packaging-distribution.md b/src/in-depth/packaging-distribution.md new file mode 100644 index 00000000..5ee5e758 --- /dev/null +++ b/src/in-depth/packaging-distribution.md @@ -0,0 +1 @@ +# Packaging apps and distributing them for different platforms diff --git a/src/in-depth/signals.md b/src/in-depth/signals.md new file mode 100644 index 00000000..dc42837f --- /dev/null +++ b/src/in-depth/signals.md @@ -0,0 +1 @@ +# Signal handling diff --git a/src/special-content.css b/src/special-content.css new file mode 100644 index 00000000..3d94850e --- /dev/null +++ b/src/special-content.css @@ -0,0 +1,20 @@ +.content aside { + padding-top: 0.1em; + margin-left: 0.1em; + padding-left: 1em; + border-left: 3px solid #ccc; + padding-bottom: 0.1em; + margin-bottom: 0.6em; +} + +.content aside *:first-child { + margin-top: 0.2em; +} + +.content aside *:last-child { + margin-bottom: 0.2em; +} + +.content aside.todo { + border-left-color: yellow; +} diff --git a/src/tutorial/cli-args.md b/src/tutorial/cli-args.md new file mode 100644 index 00000000..6f420525 --- /dev/null +++ b/src/tutorial/cli-args.md @@ -0,0 +1,58 @@ +## Parsing command line arguments + +A typical invocation of our CLI tool will look like this: +`grrs foobar test.txt`. +You can think of CLI arguments as a data type. +In our case, we have two fields, +`pattern` (the string to look for), +and `path` (the file to look in). +In Rust, it is very common to try and structure programs around the data they deal with. +This is a good start: + +```rust +struct Cli { + pattern: String, + path: std::path::PathBuf, +} +``` + + + +Now, we still need to actually get the arguments the user passed into this form. +One option would be manually parse the list of strings we get from the operating system, +but a much nicer way is to use one of the many available libraries. +As you can see in the `src/main.rs` file, +our templates already contains some code using `clap`, +and in particular use it’s “derive” feature. +This is quite nice: +All we have to do is annotate a struct and it’ll generate the code that parses the arguments into the fields. +Let’s add our fields to the `Cli` struct in the template +and also some documentation comments along the way. +It’ll look like this: + +```rust +/// Search for a pattern in a file and display the lines that contain it. +#[derive(Clap)] +struct Cli { + /// The pattern to look for + pattern: String, + /// The path to the file to read + path: std::path::PathBuf, +} +``` + +Right below the `Cli` struct our template contains its `main` function. +When the program starts, it will call this function. +The first line is + +```rust +let args = Cli::parse(); +``` + +This will try to parse the arguments the user gave when executing the program into our `Cli` struct. +You might be wondering what happens if this fails. +Give it a try! diff --git a/src/tutorial/errors.md b/src/tutorial/errors.md new file mode 100644 index 00000000..c1807d8a --- /dev/null +++ b/src/tutorial/errors.md @@ -0,0 +1,7 @@ +## Nicer error reporting + + diff --git a/src/tutorial/impl-draft.md b/src/tutorial/impl-draft.md new file mode 100644 index 00000000..e4154cb1 --- /dev/null +++ b/src/tutorial/impl-draft.md @@ -0,0 +1,35 @@ +## First implementation of `grrs` + +Right, now that we have our input data, +we can start to write our actual tool. +Let’s start by opening the file: + +```rust +let content = std::fs::read_to_string(&args.path)?; +``` + + + +Now, let’s iterate over the lines and print each one that contains our pattern: + +```rust +for line in content.lines() { + if line.contains(args.pattern) { + println!("{}", line); + } +} +``` + +Give it a try: `cargo run -- main src/main.rs` should work now! + + diff --git a/src/tutorial/intro.md b/src/tutorial/intro.md new file mode 100644 index 00000000..c3e5836f --- /dev/null +++ b/src/tutorial/intro.md @@ -0,0 +1,12 @@ +# Learning Rust by Writing a Command Line App in 15 Minutes + +This short tutorial will guide you through writing +a CLI (command line interface) application +in [Rust]. +It will take you roughly fifteen minutes; +but feel free to skip parts you don't need to know right now +or jump in at any point. +You’ll learn all the essentials about how to get going, +and where to find more information. + +[Rust]: https://rust-lang.org/ diff --git a/src/tutorial/output.md b/src/tutorial/output.md new file mode 100644 index 00000000..75496b66 --- /dev/null +++ b/src/tutorial/output.md @@ -0,0 +1,15 @@ +## Output + +### Logging + +To make it easier to understand what is happening in our program, +we might want to add some log statements. +This is usually easy while writing your application. +But it will become super helpful when running this program again in half a year. + + diff --git a/src/tutorial/packaging.md b/src/tutorial/packaging.md new file mode 100644 index 00000000..f275e04c --- /dev/null +++ b/src/tutorial/packaging.md @@ -0,0 +1,15 @@ +## Packaging and rendering documentation + +If you feel confident that your program is ready to for other people to use, +it is time to package it! + + + diff --git a/src/tutorial/setup.md b/src/tutorial/setup.md new file mode 100644 index 00000000..ae4f17fc --- /dev/null +++ b/src/tutorial/setup.md @@ -0,0 +1,27 @@ +## Project setup + +If you haven’t already, +[install Rust](https://www.rust-lang.org/install.html) on your computer (it should only take a few minutes). +After that, open a terminal and navigate to the directory you want to put your application code into. + +What kind of project do you want to write? +How about we start with something simple: +Let’s write a small `grep` clone. +That is a tool that we can give a string and a path and it’ll tell us which lines contain the string. +Let’s call it `grrs` (pronounced “grass”). + +If you’ve already seen the basic Rust tutorials, +you might be inclined to start with `cargo new --bin my-cool-app`. +To save us some time, +we’ll instead start with a CLI-specific template: +`cargo generate --git https://github.com/rust-clique/cargo-template-cli`. +When you run this, it’ll ask you for a project name. + +If look at the newly created `grrs` directory, +you’ll find a typical setup for a Rust project: + +- A `Cargo.toml` file that contains metadata for our project, incl. a list of dependencies/external libraries we use. +- A `src/main.rs` file that is the entry point for our (main) binary. +- A `tests/` directory that will contain integration tests for our tool. + +If you can execute `cargo run` in the `grrs` directory and see it greet you, you’re all set up. diff --git a/src/tutorial/testing.md b/src/tutorial/testing.md new file mode 100644 index 00000000..9e68f1f1 --- /dev/null +++ b/src/tutorial/testing.md @@ -0,0 +1,12 @@ +## Testing + + +