# rust-petname

Generate human readable random names.

> **🚨 Upgrading?** <br>
> Check the notes on switching [**from 2.x to 3.x**](#upgrading-from-2x) and
> [**from 1.x to 2.x**](#upgrading-from-1x).

[Petnames][petname-intro] are useful when you need to name a large number of
resources – like servers, services, perhaps bicycles for hire – and you want
those names to be easy to recall and communicate unambiguously. For example,
over a telephone compare saying "please restart remarkably-striking-cricket"
with "please restart s01O97i4": the former is easier to say and less likely to
be misunderstood. Avoiding sequential names adds confidence too: petnames have a
greater lexical distance between them, so errors in transcription can be more
readily detected.

This crate is both a command-line tool and a [Rust][rust-lang] library. Dustin
Kirkland's [petname][] project is the inspiration for this project. The word
lists and the basic command-line UX here are taken from there. Check it out!
Dustin maintains packages for [Python][petname-py], and [Golang][petname-go]
too.

Notable features:

- Choose from 3 built-in word lists, or provide your own.
- `english!` (aliased as `petnames!`) and `turkish!` (with feature
  `lang-turkish`) macros to statically embed word lists at compile-time.
- Alliterative names, like _viable-vulture_, _proper-pony_, ...
- Build names with 1-255 components (adjectives, adverbs, nouns).
- Name components can be unseparated, or joined by any character or string.
- Generate 1..n names, or stream names continuously.
- **`no_std` support** (see [later section](#features--no_std-support)).
- Compile without built-in dictionaries to reduce library/binary size.

[rust-lang]: https://www.rust-lang.org/
[petname-intro]: https://blog.dustinkirkland.com/2015/01/introducing-petname-libraries-for.html
[petname]: https://github.com/dustinkirkland/petname
[petname-py]: https://pypi.org/project/petname/
[petname-go]: https://github.com/dustinkirkland/golang-petname

## Command-line utility

If you have [installed Cargo][install-cargo], you can install rust-petname with
`cargo install petname`. This puts a `petname` binary in `~/.cargo/bin`, which
the Cargo installation process will probably have added to your `PATH`.

> **🍺 [Homebrew][homebrew]** – You can instead install it from the
> [`allenap/utils`][allenap-utils] tap. The formula is named `rust-petname` and
> provides the same `petname` command:
>
> ```shellsession
> $ brew install allenap/utils/rust-petname
> ```

[homebrew]: https://brew.sh/
[allenap-utils]: https://github.com/allenap/homebrew-utils

The `petname` binary from rust-petname is _mostly_ drop-in compatible with the
original `petname`. It has more options and it's stricter when validating
arguments, but for most uses it should behave the same[^differences].

[^differences]:
    When using the `--dir` option, Dustin Kirkland's _petname_ looks for a
    file named `names.txt` whereas this looks for `nouns.txt` first before
    checking for `names.txt`.

```shellsession
$ petname -h
Generate human readable random names

Usage: petname [OPTIONS] [COMMAND]

Commands:
  completions  Print a shell completion script to standard output
  help         Print this message or the help of the given subcommand(s)

Options:
  -w, --words <WORDS>             Number of words in name [default: 2]
  -s, --separator <SEP>           Separator between words [default: -]
      --language <LANG>           Language to generate names in [default: english] [aliases: --lang] [possible values: english, turkish]
      --lists <LIST>              Use the built-in word lists with small, medium, or large words [default: medium] [possible values: small, medium, large]
  -c, --complexity <NUM>          Alias for compatibility with upstream; prefer --lists instead
  -d, --dir <DIR>                 Use custom word lists by specifying a directory containing `adjectives.txt`, `adverbs.txt`, and `nouns.txt`
      --count <COUNT>             Generate multiple names; or use --stream to generate continuously [default: 1]
      --stream                    Stream names continuously
  -l, --letters <LETTERS>         Maximum number of letters in each word; 0 for unlimited [default: 0]
  -a, --alliterate                Generate names where each word begins with the same letter
  -A, --alliterate-with <LETTER>  Generate names where each word begins with the given letter
  -u, --ubuntu                    Alias for compatibility with upstream; prefer --alliterate instead
      --seed <SEED>               Seed the RNG with this value (unsigned 64-bit integer in base-10)
  -h, --help                      Print help (see more with '--help')
  -V, --version                   Print version

Based on Dustin Kirkland's petname project <https://github.com/dustinkirkland/petname>.

$ petname
unified-platypus

$ petname -s _ -w 3
lovely_notable_rooster
```

### Languages

Beyond the default English word lists, rust-petname can generate names in other
languages with their own grammar-aware generators. These are gated behind
per-language features (so the default build stays small) and selected with
`--language`.

Turkish is available via the `lang-turkish` feature. It is grammatically simple
for this purpose – no gender, no adjective agreement, adjective-before-noun
order – and it models Turkish's distinctive emphatic reduplication
(_pekiştirme_), so a two-word name may intensify the adjective morphologically:

```console
$ petname --language turkish --words 2
kıpkırmızı-kedi

$ petname --language turkish --words 3
çok-güzel-yıldız
```

More languages (Luxembourgish, French, German, …) are planned. Each is a
distinct generator, so languages with grammatical gender, agreement, or
word-order rules can be modelled properly rather than approximated.

### Shell completions

`petname completions <SHELL>` prints a completion script to standard output, for
`bash`, `zsh`, `fish`, `elvish`, or `powershell`. Redirect it to the location
your shell loads completions from, for example:

```shellsession
$ petname completions zsh > ~/.zfunc/_petname
$ petname completions bash > /etc/bash_completion.d/petname
$ petname completions fish > ~/.config/fish/completions/petname.fish
```

This is an ordinary subcommand, so `petname` on its own still generates names as
usual. If you installed via Homebrew, the completions are set up for you.

### Performance

This implementation is considerably faster than the upstream `petname`:

```shellsession
$ time /usr/bin/petname
fit-lark

real    0m0.038s
user    0m0.032s
sys     0m0.008s

$ time target/release/petname
contiguous-seriema

real    0m0.004s
user    0m0.001s
sys     0m0.002s
```

These timings are irrelevant if you only need to name a single thing, but if you
need to generate 100s or 1000s of names then rust-petname is handy:

```shellsession
$ time { for i in $(seq 1000); do /usr/bin/petname; done; } > /dev/null

real    0m32.058s
user    0m29.360s
sys     0m5.163s

$ time { for i in $(seq 1000); do target/release/petname; done; } > /dev/null

real    0m2.293s
user    0m1.044s
sys     0m1.003s
```

To be fair, `/usr/bin/petname` is a shell script. The Go command-line version
(available from the golang-petname package on Ubuntu) is comparable to the Rust
version for speed, but has very limited options compared to its shell-script
ancestor and to rust-petname.

Lastly, rust-petname has a `--count` option that speeds up generation of names
considerably:

```shellsession
$ time target/release/petname --count=10000000 > /dev/null

real    0m0.785s
user    0m0.767s
sys     0m0.016s
```

That's ~408,000 (four hundred and eight thousand) times faster, for about 12.7
million petnames a second on this hardware. This is useful if you want to apply
an external filter to the names being generated:

```shellsession
$ petname --words=3 --stream | grep 'love.*\bsalmon$'
```

## Library

You can use rust-petname in your own Rust projects with `cargo add petname`.

## Features & `no_std` support

There are features that can be selected, and many than can be _deselected_
(since they're enabled by default):

- `default-rng` enables `std` and `std_rng` in [rand][]. A couple of convenience
  functions depend on this for a default RNG.
- `default-words` enables the default word lists. Deselecting this will reduce
  the size of compiled artifacts.
- `clap` enables the [clap][] command-line argument parser, which is needed to
  build the `petname` binary.
  - **NOTE** that `clap` is **not** necessary for the library at all, and you
    can deselect it, but it is presently a default feature since otherwise it's
    inconvenient to build the binary. This will probably change in the future.
- `macros` enables the `english!` macro (and its `petnames!` alias). It's
  required for the `default-words` feature, but otherwise it can be deselected.
- `lang-turkish` (not a default) compiles the Turkish generator and enables
  `--language turkish`. Like the English lists, the built-in Turkish word lists
  are embedded only when `default-words` is also enabled. See
  [Languages](#languages).

All of the default features are required to build the command-line utility.

The library can be built without any default features, and it will work in a
[`no_std`][no_std] environment, like [Wasm][]. You'll need to figure out a
source of randomness, but [SmallRng::seed_from_u64][smallrng::seed_from_u64] may
be a good starting point.

[rand]: https://crates.io/crates/rand
[clap]: https://crates.io/crates/clap
[no_std]: https://doc.rust-lang.org/reference/crates-and-source-files.html#preludes-and-no_std
[wasm]: https://webassembly.org/
[smallrng::seed_from_u64]: https://docs.rs/rand/latest/rand/rngs/struct.SmallRng.html#method.seed_from_u64

## Upgrading

### <a id="upgrading-from-30x"></a>From 3.0.0 to 3.0.1

3.0.1 is purely additive – nothing is required to upgrade, and the default build
is unchanged. It adds non-English generators (see [Languages](#languages)). The
new `--language` flag (alias `--lang`) selects the language; the `lang-turkish`
feature compiles a Turkish generator and makes `--language turkish` available.
The word-list macro is now `english!`, with `petnames!` kept as a
backwards-compatible alias, and a `turkish!` macro is available when the
`lang-turkish` feature is enabled.

### <a id="upgrading-from-2x"></a>From 2.x to 3.x

Version 3.0 brings a few breaking changes to the API, but the command-line is
largely unchanged.

#### Command-line

- One subtle change: if you use `--seed`, the generated names will differ from
  2.x, since `rand` 0.10 produces different output for the same seed.

#### Library

- The `rand` dependency has been bumped from 0.9 to 0.10. If you depend on
  `rand` types (e.g. `RngCore`, `SmallRng`) directly in your own code, you will
  need to upgrade your `rand` dependency to match.
- The `Generator` trait has changed significantly:
  - The `generate` and `generate_one` methods are gone.
  - The trait no longer has a lifetime parameter; `Generator<'a>` is now just
    `Generator`.
  - The one required method is `generate_into`.
  - The `iter` method has been renamed to `namer`. It now returns a [`Namer`]
    directly instead of `Box<dyn Iterator<Item = String>>`, so there is no heap
    allocation for the iterator itself.
- [`Namer`] is a new public type (an iterator over generated petnames). It is
  generic over the generator type, so `Namer<Petnames>` and
  `Namer<Alliterations>` are both valid.
- The built-in word lists are now compiled into the library via the `petnames!`
  proc macro rather than via `build.rs`. This is mostly an internal change, but
  it does mean that the `petname-macros` crate is a new compile-time dependency
  when the `default-words` or `macros` features are enabled.
- A new `macros` feature flag exposes the `petnames!` proc macro as public API.
  It is enabled by default. You can use it to embed custom word lists at compile
  time:
  ```rust
  let p = petname::petnames!("path/to/my/words");
  ```

### <a id="upgrading-from-1x"></a>From 1.x to 2.x

Version 2.0 brought several breaking changes to both the API and the
command-line too. Below are the most important:

#### Command-line

- The `--complexity <COMPLEXITY>` option has been replaced by `--lists <LISTS>`.
  - For compatibility, `--complexity [0,1,2]` will still work, but its
    availability is not shown in the `-h|--help` text.
  - The default is now "medium" (equivalent to `--complexity 1`). Previously it
    was "small" (`--complexity 0`).
- When using custom word lists with `--dir <DIR>`, nouns are now found in a file
  named appropriately `DIR/nouns.txt`. Previously this was `names.txt` but this
  was confusing; the term "names" is overloaded enough already.
  - For compatibility, if `nouns.txt` is not found, an attempt will be made to
    load nouns from `names.txt`.
- The option `--count 0` is no longer a synonym for `--stream`. Use `--stream`
  instead. It's not an error to pass `--count 0`, but it will result in zero
  names being generated.
- The `--non-repeating` flag is no longer recognised ([#101]).

#### Library

- Feature flags have been renamed:
  - `std_rng` is now `default-rng`,
  - `default_dictionary` is now `default-words`.
- The `names` field on the `Petnames` struct has been renamed to `nouns`.
- `Petnames::new()` is now `Petnames::default()`.
- `Petnames::new(…)` now accepts word lists as strings.
- `Names` is no longer public. This served as the iterator struct returned by
  `Petnames::iter(…)`, but this now hides the implementation details by
  returning `impl Iterator<Item = String>` instead. This also means that
  `Names::cardinality(&self)` is no longer available; use
  `Petnames::cardinality(&self, words: u8)` instead.
- `Petnames::iter_non_repeating` has been removed ([#101]).
- `Petnames::generate`, `Petnames::generate_one`, and `Petnames::iter` have been
  extracted into a `Generator` trait. This must be in scope in order to call
  those methods ([#102]).
- The default word lists are now the "medium" lists.

[#101]: https://github.com/allenap/rust-petname/pull/101
[#102]: https://github.com/allenap/rust-petname/pull/102

## Developing & Contributing

To hack the source, [install Cargo][install-cargo], [install
`cargo-hack`][install-cargo-hack], clone this repository, then:

```shellsession
# Build with every combination of feature flags:
$ cargo hack --workspace --feature-powerset build

# Optionally, hide noise when using `git blame`:
$ git config blame.ignoreRevsFile .git-blame-ignore-revs
```

[install-cargo]: https://crates.io/install
[install-cargo-hack]: https://crates.io/crates/cargo-hack#installation

### Making changes & running tests

After installing the source (see above), make changes and:

```shellsession
# Run tests with all feature flag combinations:
cargo hack --workspace --feature-powerset test

# Run Clippy from time to time too:
$ cargo clippy

# Before committing, format:
$ cargo fmt
```

### Making a release

1. Bump version in [`Cargo.toml`](Cargo.toml).
1. Update the dependency on `petnames-macros` too.
1. Paste updated `-h` output into [`README.md`](README.md) (this file; see near
   the top). On macOS the command `cargo run --all-features -- -h | pbcopy` is
   helpful. **Note** that `--help` output is not the same as `-h` output: it's
   more verbose and too much for an overview.
1. Build **and** test all crates in the workspace. Testing on its own does build
   code, but a test build can hide warnings about dead code, so do both. To test
   feature combinations, install [cargo-hack][] first, then:
   - `cargo hack --workspace --feature-powerset build`
   - `cargo hack --workspace --feature-powerset test`
1. Commit with message "Bump version to `$VERSION`."
1. Tag with "v`$VERSION`", e.g. `git tag v1.0.10`.
1. Push: `git push && git push --tags`.
1. Publish: `cargo publish --workspace`.

[cargo-hack]: https://crates.io/crates/cargo-hack

## License

This project is licensed under the Apache 2.0 License. See the
[LICENSE](LICENSE) file for details.
