Skip to content

Commit

Permalink
feat(plz): fully configurable plz menu
Browse files Browse the repository at this point in the history
BREAKING CHANGE: this PR introduces a bunch of breaking changes to both the configuration system and the command-line interface.

- Change the `plz` menu system to read the menu entries from the config file
   - Each entry contains its display string that will displayed in the menu and its operation/args
   - The `operation` is just a hash map that contains the operation key and its value and the rest of the arguments
   - Each operation has a specific set of pre-defined arguments that can be used to configure the `operation handler` and change its default behavior.
   -  The operation handler will ignore any argument that doesn't know about
   - However, all the argument values will be passed to the template engine to be parsed before they are passed to the operation handler, which means that if there's any `template error` in any of the arguments the program will exit even if the argument that have the error doesn't needed by the operation handler
   - The currently available operations is:
      -  `fetch` | `url`: fetch any page from the web(mainly) by HTTP
      - `run` | `command`: run command/s
      - `file`: just display the contents of any local file in the pager
- Remove the following CLI options:
   - `--cheat-sh-url`
   - `--eg-url`
   - `--man-cmd`
   - `--cheat-url`
 - Introduce the following CLI options:
    - `-s` | `--selected-position`: used to set the default selected position, the posable values are:
       - `start`:  The first item in the menu
       - `center`: The middle item in the menu (in case of an even number of items, the first item in the second half)
       - `end`:    The last item in the menu

COMMITS LIST:

da8bb01 fix: un-use the wildcard pattern and use `as` instead (2 weeks ago)
0fdc42e chore(git): merge main and resolve confilcts (2 weeks ago)
9fd106d docs(readme): update the outdated examples (3 weeks ago)
446efff chore(config): just update the config file (3 weeks ago)
a418fbd fix(test): fix the cli::update_config test and fi doc (3 weeks ago)
0187928 feat(file): create the file handler (3 weeks ago)
a08101f feat(cli): add the --selected-position option to plz subcommand (3 weeks ago)
6d07d80 refactor: refactor and and lint and re-format the whole proj (3 weeks ago)
4dd0702 feat(command): create the command operation handler (3 weeks ago)
3eb71a6 refactor(plz): improve the operation handleing system (3 weeks ago)
3bb10a6 refactor: remove all the deprecate code and re-format the code (3 weeks ago)
9bfc239 revert(idea): remove the `.idea` directory (3 weeks ago)
1babee0 feat(plz): use the new system (3 weeks ago)
f4d845e feat(cmd_parse): take a refrence to the hashmap (3 weeks ago)
0ed22dd feat(fetch): create the fetch handler and stabilize the `Handler` interface (3 weeks ago)
1646c14 feat(parser): cover more cases for the command parser (3 weeks ago)
5b054fb feat(parser): create the command parser (3 weeks ago)
399dfee feat(parser): create the template parser (3 weeks ago)
ad409ab feat(plz): make the defualt selected item configrable (4 weeks ago)
218316a feat(plz): improve the cheat.sh url template (4 weeks ago)
08e7867 feat(plz): create the handler trait (4 weeks ago)
bba0696 feat(plz): read the plz menu from the config file (4 weeks ago)
  • Loading branch information
0x61nas committed Sep 16, 2023
1 parent e35374e commit 148480b
Show file tree
Hide file tree
Showing 19 changed files with 993 additions and 335 deletions.
29 changes: 17 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,22 @@ halp [OPTIONS] plz <CMD>

```
Options:
-m, --man-cmd <MAN_CMD> Sets the manual page command to run
--cheat-sh-url <URL> Use a custom URL for cheat.sh [env: CHEAT_SH_URL=]
-p, --pager <PAGER> Sets the pager to use
--no-pager Disables the pager
-h, --help Print help
-p, --pager <PAGER>
Sets the pager to use
--no-pager
Disables the pager
-s, --selected-position <SELECTED_POSITION>
Sets the default selected position
Possible values:
- start: The first item in the menu
- center: The middle item in the menu (in case of even number of items, the first item in the second half)
- end: The last item in the menu
-h, --help
Print help (see a summary with '-h')
```

## Examples
Expand Down Expand Up @@ -313,12 +324,6 @@ To disable the pager:
halp plz --no-pager bat vim
```

##### Custom cheat.sh host URL

```sh
halp plz --cheat-sh-url https://cht.sh vim
```

## Configuration

`halp` can be configured with a configuration file that uses the [TOML](https://en.wikipedia.org/wiki/INI_file) format. It can be specified via `--config` or `HALP_CONFIG` environment variable. It can also be placed in one of the following global locations:
Expand All @@ -330,7 +335,7 @@ halp plz --cheat-sh-url https://cht.sh vim
`<config_dir>` depends on the platform as shown in the following table:

| Platform | Value | Example |
| -------- | ------------------------------------- | ---------------------------------------- |
|----------|---------------------------------------|------------------------------------------|
| Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/orhun/.config |
| macOS | `$HOME`/Library/Application Support | /Users/Orhun/Library/Application Support |
| Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Orhun\AppData\Roaming |
Expand Down
39 changes: 29 additions & 10 deletions config/halp.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
# configuration for https://github.com/orhun/halp

# check the version flag
check_version = true
# check the help flag
check_help = true
# arguments to check
check = [ [ "-v", "-V", "--version" ], [ "-h", "--help", "help", "-H" ] ]
# command to run for manual pages
check = [["-v", "-V", "--version", "version"], ["-h", "--help", "help", "-H"]]
man_command = "man"
# pager to use for command outputs
pager_command = "less -R"
# Cheat.sh URL
cheat_sh_url = "https://cheat.sh"

[plz_menu]
selected_pos = "Center"

[[plz_menu.entries]]
display_msg = "Show man page"

[plz_menu.entries.operation]
run = "man {cmd}"

[[plz_menu.entries]]
display_msg = "Show cheat.sh page"

[plz_menu.entries.operation]
user-agent = "fetch"
fetch = "https://cheat.sh/{cmd}{?/{subcommand}}{? {args}}"

[[plz_menu.entries]]
display_msg = "Show eg page"

[plz_menu.entries.operation]
fetch = "https://raw.githubusercontent.com/srsudar/eg/master/eg/examples/{cmd}.md"

[[plz_menu.entries]]
display_msg = "Show cheatsheets page"

[plz_menu.entries.operation]
fetch = "https://raw.githubusercontent.com/cheat/cheatsheets/master/{cmd}"
37 changes: 9 additions & 28 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::config::plz_menu::PlzMenuSelection;
use crate::config::Config;
use clap::{Parser, Subcommand};
use std::path::PathBuf;
Expand Down Expand Up @@ -45,24 +46,15 @@ pub enum CliCommands {
Plz {
/// Command or binary name.
cmd: String,
/// Sets the manual page command to run.
#[arg(short, long)]
man_cmd: Option<String>,
/// Use a custom URL for cheat.sh.
#[arg(long, env = "CHEAT_SH_URL", value_name = "URL")]
cheat_sh_url: Option<String>,
/// Use a custom provider URL for `eg` pages.
#[arg(long, env = "EG_PAGES_URL", value_name = "URL")]
eg_url: Option<String>,
/// Use a custom URL for cheat sheets.
#[arg(long, env = "CHEATSHEETS_URL", value_name = "URL")]
cheat_url: Option<String>,
/// Sets the pager to use.
#[arg(short, long)]
pager: Option<String>,
/// Disables the pager.
#[arg(long)]
no_pager: bool,
/// Sets the default selected position.
#[arg(long, short)]
selected_position: Option<PlzMenuSelection>,
},
}

Expand All @@ -80,28 +72,20 @@ impl CliArgs {
config.check_args = Some(args.iter().map(|s| vec![s.to_string()]).collect());
}
if let Some(CliCommands::Plz {
ref man_cmd,
ref cheat_sh_url,
ref eg_url,
no_pager,
ref pager,
ref selected_position,
..
}) = self.subcommand
{
if let Some(man_cmd) = man_cmd {
config.man_command = man_cmd.clone();
}
if let Some(cheat_sh_url) = cheat_sh_url {
config.cheat_sh_url = Some(cheat_sh_url.clone());
}
if let Some(eg_url) = eg_url {
config.eg_url = Some(eg_url.to_owned());
}
if no_pager {
config.pager_command = None;
} else if let Some(pager) = pager {
config.pager_command = Some(pager.clone());
}
if let Some(selected_position) = selected_position {
config.plz_menu.selected_pos = *selected_position;
}
}
}
}
Expand All @@ -125,10 +109,7 @@ mod tests {
subcommand: Some(CliCommands::Plz {
cmd: "ps".to_string(),
pager: Some("bat".to_string()),
cheat_sh_url: None,
cheat_url: None,
eg_url: None,
man_cmd: None,
selected_position: Some(PlzMenuSelection::Center),
no_pager: false,
}),
..Default::default()
Expand Down
24 changes: 7 additions & 17 deletions src/config.rs → src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/// The `plz` configuration stuff.
pub mod plz_menu;

use crate::config::plz_menu::PlzMenu;
use crate::error::Result;
use crate::helper::args::common::{HelpArg, VersionArg};
use crate::helper::args::FOUND_EMOTICON;
use crate::helper::docs::cheat_sh::DEFAULT_CHEAT_SHEET_PROVIDER;
use crate::helper::docs::cheatsheets::DEFAULT_CHEATSHEETS_PROVIDER;
use crate::helper::docs::eg::DEFAULT_EG_PAGES_PROVIDER;
use colored::*;
use serde::{Deserialize, Serialize};
use std::env;
Expand All @@ -25,12 +26,8 @@ pub struct Config {
pub man_command: String,
/// Pager to use for command outputs, None to disable.
pub pager_command: Option<String>,
/// Use a custom URL for cheat.sh.
pub cheat_sh_url: Option<String>,
/// Use a custom URL for `eg` pages provider.
pub eg_url: Option<String>,
/// Use a custom URL for cheatsheets provider.
pub cheatsheets_url: Option<String>,
/// Plz menu options.
pub plz_menu: PlzMenu,
}

impl Default for Config {
Expand All @@ -50,9 +47,7 @@ impl Default for Config {
]),
man_command: "man".to_string(),
pager_command: Some("less -R".to_string()),
cheat_sh_url: Some(DEFAULT_CHEAT_SHEET_PROVIDER.to_string()),
eg_url: Some(DEFAULT_EG_PAGES_PROVIDER.to_string()),
cheatsheets_url: Some(DEFAULT_CHEATSHEETS_PROVIDER.to_string()),
plz_menu: PlzMenu::default(),
}
}
}
Expand Down Expand Up @@ -122,7 +117,6 @@ impl Config {
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::path::PathBuf;

#[test]
Expand All @@ -136,10 +130,6 @@ mod tests {
let config = Config::parse(&path)?;
assert!(config.check_help);
assert!(config.check_version);
assert_eq!(
config.cheat_sh_url,
Some(DEFAULT_CHEAT_SHEET_PROVIDER.to_string())
);
Ok(())
}
}
83 changes: 83 additions & 0 deletions src/config/plz_menu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Default cheat sheet provider URL.
const CHEAT_SH_URL_TEMPLATE: &str = "https://cheat.sh/{cmd}{?/{subcommand}}{? {args}}";

/// User agent for the cheat sheet provider.
///
/// See <https://github.com/chubin/cheat.sh/blob/83bffa587b6c1048cbcc40ea8fa6af675203fd5f/bin/app.py#L76>
const CHEAT_SH_USER_AGENT: &str = "fetch";

/// EG page provider URL.
const EG_PAGES_URL_TEMPLATE: &str =
"https://raw.githubusercontent.com/srsudar/eg/master/eg/examples/{cmd}.md";

/// The default cheatsheets provider URL.
const CHEATSHEETS_URL_TEMPLATE: &str =
"https://raw.githubusercontent.com/cheat/cheatsheets/master/{cmd}";

/// Plz menu config.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PlzMenu {
/// The default selected poison in the menu.
pub selected_pos: PlzMenuSelection,
/// The menu entries.
pub entries: Vec<PlzMenuEntry>,
}

/// Plz menu selection position.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ValueEnum)]
pub enum PlzMenuSelection {
/// The first item in the menu.
Start,
/// The middle item in the menu (in case of even number of items, the first item in the second half).
Center,
/// The last item in the menu.
End,
}

/// Plz menu item.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PlzMenuEntry {
/// The string to display in the menu.
pub display_msg: String,
/// The operation to perform. and its arguments.
pub operation: HashMap<String, String>,
}

impl Default for PlzMenu {
fn default() -> Self {
PlzMenu {
selected_pos: PlzMenuSelection::Center,
entries: vec![
PlzMenuEntry {
display_msg: "Show man page".to_string(),
operation: HashMap::from([("run".to_string(), "man {cmd}".to_string())]),
},
PlzMenuEntry {
display_msg: "Show cheat.sh page".to_string(),
operation: HashMap::from([
("fetch".to_string(), CHEAT_SH_URL_TEMPLATE.to_string()),
("user-agent".to_string(), CHEAT_SH_USER_AGENT.to_string()),
]),
},
PlzMenuEntry {
display_msg: "Show eg page".to_string(),
operation: HashMap::from([(
"fetch".to_string(),
EG_PAGES_URL_TEMPLATE.to_string(),
)]),
},
PlzMenuEntry {
display_msg: "Show cheatsheets page".to_string(),
operation: HashMap::from([(
"fetch".to_string(),
CHEATSHEETS_URL_TEMPLATE.to_string(),
)]),
},
],
}
}
}
19 changes: 19 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::helper::docs::template::HalpTemplateError;
use thiserror::Error as ThisError;

/// Custom error type.
Expand All @@ -21,6 +22,24 @@ pub enum Error {
/// Error that might occur when tray to get help from an external provider.
#[error("External help provider error: `{0}`")]
ProviderError(String),
/// Error that might occur when trying to parse a template.
#[error("Template error: `{0}`")]
TemplateError(#[from] HalpTemplateError),
/// Error that might occur if the user provides invalid arguments for the operation handler.
#[error("Invalid argument: `{0}`")]
InvalidArgument(String),
/// Error that might occur if the user dosen't provide any operation handler.
#[error("No operation provided.")]
PlzMenuNoOperation,
/// Error that might occur if the user provides an invalid operation.
#[error("Invalid operation there is no operation named `{0}`.")]
PlzMenuInvalidOperation(String),
/// Error that might occur if the timeout is reached while executing a command.
#[error("Command timeout.")]
CommandTimeoutError,
/// Error that might occur when trying to execute a command or collect its output.
#[error("Command error: `{0}`")]
CommandError(String),
}

/// Type alias for the standard [`Result`] type.
Expand Down
8 changes: 4 additions & 4 deletions src/helper/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ fn check_args<'a, ArgsIter: Iterator<Item = &'a str>, Output: Write>(
/// Shows command-line help about the given command.
pub fn get_args_help<Output: Write>(
cmd: &str,
config: &Config,
config: Config,
verbose: bool,
output: &mut Output,
) -> Result<()> {
Expand Down Expand Up @@ -215,7 +215,7 @@ Options:
fn test_get_default_help() -> Result<()> {
let config = Config::default();
let mut output = Vec::new();
get_args_help(&get_test_bin(), &config, false, &mut output)?;
get_args_help(&get_test_bin(), config, false, &mut output)?;
println!("{}", String::from_utf8_lossy(&output));
assert_eq!(
r"(°ロ°) checking 'test -v'
Expand Down Expand Up @@ -250,7 +250,7 @@ Options:
..Default::default()
};
let mut output = Vec::new();
get_args_help(&get_test_bin(), &config, false, &mut output)?;
get_args_help(&get_test_bin(), config, false, &mut output)?;
println!("{}", String::from_utf8_lossy(&output));
assert_eq!(
r"(°ロ°) checking 'test -x'
Expand All @@ -277,7 +277,7 @@ halp 0.1.0
..Default::default()
};
let mut output = Vec::new();
get_args_help("", &config, false, &mut output)?;
get_args_help("", config, false, &mut output)?;
assert!(String::from_utf8_lossy(&output).is_empty());
Ok(())
}
Expand Down
Loading

0 comments on commit 148480b

Please sign in to comment.