Skip to content

Commit

Permalink
Prompting the user for input with rprompt and loop
Browse files Browse the repository at this point in the history
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
ChristopherBiscardi committed Jan 16, 2021
1 parent d9dcc0e commit 7d45753
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 1 deletion.
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Expand Up @@ -17,6 +17,8 @@ path = "src/main.rs"
color-eyre = "0.5.10"
directories = "3.0.1"
edit = "0.1.2"
rprompt = "1.0.5"
slug = "0.1.4"
structopt = "0.3.21"

[dev-dependencies]
Expand Down
45 changes: 44 additions & 1 deletion src/write.rs
Expand Up @@ -34,6 +34,49 @@ pub fn write(garden_path: PathBuf, title: Option<String>) -> Result<()> {
.map(|maybe_line| maybe_line.trim_start_matches("# ").to_string())
});

dbg!(contents, document_title);
// get the filename to use for the file
let filename = match document_title {
Some(raw_title) => confirm_filename(&raw_title),
None => ask_for_filename(),
}?;

dbg!(contents, filename);
todo!()
}

fn ask_for_filename() -> Result<String> {
rprompt::prompt_reply_stderr(
"\
Enter filename
> ",
)
.wrap_err("Failed to get filename")
.map(|title| slug::slugify(title))
}

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
}
};
}
}

0 comments on commit 7d45753

Please sign in to comment.