Skip to content

Commit

Permalink
Extend CLI functionality (#91)
Browse files Browse the repository at this point in the history
* [raphael-cli] Add search + solve subcommands

Move to a git-style subcommand interface and add basic support
for item searching.

   raphael-cli solve --item-id XYZ ...
   raphael-cli search Archeo

* [raphael-cli] Support OFS output in CLI

* [raphael-cli] Do not print number of found matches

* [raphael-cli] Move commands to their own files

* [raphael-cli] Move arg structs to command files

* Add food & potion selection to cli

* Add combined stats argument to cli

* Add initial-/target-quality arguments and HQ ingredient selection to cli

* Add argument for custom output format to cli

* Fix output specified using output-format not being valid CSV data

* Streamline solver argument parsing

* Improve readablility

* Use `Arg::env` instead of manual implementation

* Add language option to search

* Add ability to search for item by `item_id` to cli

* [raphael-cli] Run rustfmt

* Update README to include more info about CLI

* Make CLI documentation clearer on possible arguments

* Trim spaces and CL char for search in cli

---------

Co-authored-by: Jesse Farmer <[email protected]>
  • Loading branch information
augenfrosch and jfarmer authored Nov 22, 2024
1 parent dc6f538 commit e3c5a5f
Show file tree
Hide file tree
Showing 6 changed files with 444 additions and 106 deletions.
45 changes: 33 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ Raphael is a crafting rotation solver for the online game Final Fantasy XIV.
* Produces optimal solutions.
* Short solve time (20-60 seconds) and reasonable memory usage (300-1000 MB) for most configurations.

## Contents <!-- omit in toc -->

* [How does it work?](#how-does-it-work)
* [Building from source](#building-from-source)
* [Native app](#native-app)
* [Web app (WASM)](#web-app-wasm)
* [Native CLI](#native-cli)

## How does it work?

* Short answer: [A* search](https://en.wikipedia.org/wiki/A*_search_algorithm) + [Pareto optimization](https://en.wikipedia.org/wiki/Multi-objective_optimization) + [Dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming).
Expand All @@ -23,32 +31,45 @@ To build and run the application:
cargo run --release
```

### Native CLI
### Web app (WASM)

To build and run the command-line interface (CLI):
[Trunk](https://trunkrs.dev/) is required to bundle and host the website and can be installed via the Rust toolchain:

```
cargo run --release --package raphael-cli -- <cli-args>
cargo install --locked trunk
```

The CLI can also be installed so that it can be called from anywhere:
To build and host the application locally:

```
cargo install --path raphael-cli
export RANDOM_SUFFIX=""
export RUSTFLAGS="--cfg=web_sys_unstable_apis"
trunk serve --release --dist distrib
```

### Web app (WASM)
### Native CLI

[Trunk](https://trunkrs.dev/) is required to bundle and host the website and can be installed via the Rust toolchain:
To build and run the command-line interface (CLI):

```
cargo install --locked trunk
cargo run --release --package raphael-cli -- <cli-args>
```

To build and host the application locally:
The CLI currently supports searching for items and solving for crafting rotations. Run the following to see the relevant help messages:
```
cargo run --release --package raphael-cli -- --help
cargo run --release --package raphael-cli -- search --help
cargo run --release --package raphael-cli -- solve --help
```

Some basic examples:
```
export RANDOM_SUFFIX=""
export RUSTFLAGS="--cfg=web_sys_unstable_apis"
trunk serve --release --dist distrib
cargo run --release --package raphael-cli -- search "Archeo Fending"
cargo run --release --package raphael-cli -- solve --item-id 8548 --stats 5000 4000 500
```

The CLI can also be installed so that it can be called from anywhere:

```
cargo install --path raphael-cli
```
2 changes: 1 addition & 1 deletion raphael-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ simulator = { path = "../simulator" }
solvers = { path = "../solvers" }
game-data = { path = "../game_data" }

clap = { version = "4.4.11", features = ["derive", "wrap_help"] }
clap = { version = "4.4.11", features = ["derive", "wrap_help", "env"] }

log = "0.4"
env_logger = "0.11.5"
2 changes: 2 additions & 0 deletions raphael-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod search;
pub mod solve;
67 changes: 67 additions & 0 deletions raphael-cli/src/commands/search.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use clap::{Args, ValueEnum};
use game_data::{get_item_name, Locale, RECIPES};

#[derive(Args, Debug)]
pub struct SearchArgs {
/// Search pattern, <PATTERN> can be a string or an item ID
pub pattern: String,

/// The delimiter the output uses between fields
#[arg(long, alias = "OFS", default_value = " ", env = "OFS")]
output_field_separator: String,

/// The language the input pattern and output use
#[arg(short, long, alias = "locale", value_enum, ignore_case = true, default_value_t = SearchLanguage::EN)]
language: SearchLanguage,
}

#[derive(Copy, Clone, ValueEnum, Debug)]
pub enum SearchLanguage {
EN,
DE,
FR,
JP,
}

impl Into<Locale> for SearchLanguage {
fn into(self) -> Locale {
match self {
SearchLanguage::EN => Locale::EN,
SearchLanguage::DE => Locale::DE,
SearchLanguage::FR => Locale::FR,
SearchLanguage::JP => Locale::JP,
}
}
}

pub fn execute(args: &SearchArgs) {
let locale = args.language.into();
let matches: Vec<usize>;
if let Ok(item_id) = u32::from_str_radix(&args.pattern, 10) {
match &RECIPES
.iter()
.enumerate()
.find(|(_, recipe)| recipe.item_id == item_id)
{
Some((index, _)) => matches = Vec::from([*index]),
None => matches = Vec::new(),
}
} else {
matches = game_data::find_recipes(&args.pattern, locale);
}
if matches.is_empty() {
println!("No matches found");
return;
}

for recipe_idx in matches {
let recipe = &RECIPES[recipe_idx];
let name = get_item_name(recipe.item_id, false, locale);
println!(
"{item_id}{separator}{name}",
item_id = recipe.item_id,
separator = args.output_field_separator,
name = name.trim_end_matches(&[' ', game_data::CL_ICON_CHAR])
);
}
}
Loading

0 comments on commit e3c5a5f

Please sign in to comment.