Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Prompting the user for input with rprompt and loop
Given the potential `document_title` we now want to ask the user if the title is still what they want to use so that we can write the file to disk. This will involve two paths depending on if we have an existing potential option or not. We will need to use `ask_for_filename` in `confirm_filename`, so we'll write that branch first. ```rust let filename = match document_title { Some(raw_title) => { // confirm_filename()? todo!() } None => ask_for_filename(), }; ``` `rprompt` allows us to ask for a small amount of input from the user (compared to the much larger input possible by passing control to the user's editor). We'll use `rprompt::prompt_reply_stderr` to get a response from the user, and `.wrap_err` to add context to the error if anything goes wrong. ```rust fn ask_for_filename() -> Result<String> { rprompt::prompt_reply_stderr( "\ Enter filename > ", ) .wrap_err("Failed to get filename") } ``` We'll use the same behavior for the first part of `confirm_filename`. We'll also need to use our first lifetime to account for the shared reference argument. `match` can match against multiple values for the same branch, so we'll take advantage of that to handle branches for Ns and Ys, as well as the default case. If anything goes wrong, such as someone inputting an "a", we'll fall through using `loop` and ask again until we get a usable answer ```rust fn confirm_filename(raw_title: &str) -> Result<String> { loop { // prompt defaults to uppercase character in question // this is a convention, not a requirement enforced by // the code let result = rprompt::prompt_reply_stderr(&format!( "\ current title: `{}` Do you want a different title? (y/N): ", raw_title, )) .wrap_err("Failed to get input for y/n question")?; match result.as_str() { "y" | "Y" => break ask_for_filename(), "n" | "N" | "" => { // the capital N in the prompt means "default", // so we handle "" as input here break Ok(slug::slugify(raw_title)); } _ => { // ask again because something went wrong } }; } } ``` While filenames can technically have spaces in them, we're going to slugify our filenames like urls for a few reasons. One is that when starting out, many programmers fail to quote filename arguments in their bash scripts, which results in filenames with spaces being treated as separate arguments. Another is that this is a digital garden CLI, and digital gardens are often stored in git, accessed from multiple file systems, as well as from URLs. While strictly speaking we don't need to slugify our filenames, we will here so as to adhere to a safer set of characters. ``` cargo add slug ``` We'll map over the `Result` from `rprompt` which allows us to operate on the internal value if it's `Ok`. ```rust fn ask_for_filename() -> Result<String> { rprompt::prompt_reply_stderr( "\ Enter filename > ", ) .wrap_err("Failed to get filename") .map(|title| slug::slugify(title)) } ``` We'll also use slugify in `confirm_filename` ```rust match result.as_str() { "y" | "Y" => break ask_for_filename(), "n" | "N" | "" => { // the capital N in the prompt means "default", // so we handle "" as input here break Ok(slug::slugify(raw_title)); } _ => { // ask again because something went wrong } }; ``` The fact that `confirm_filename` and `ask_for_filename` have the same return type is important because the branches of a `match` need to return the same type.
- Loading branch information