Command line interface as a function.

Overview


Command line interface as a function.

fncmd

Crates.io Crates.io



fncmd is an opinionated command line parser frontend that wraps around clap. The functionality is mostly identical to clap, but provides much more automated and integrated experience.

Motivation

Imagine a command line program you want to create. Essentially, it can be abstracted as a simple function that takes command line options as arguments. Then there should be nothing to stop you from being able to write it literally as a function, without using structs or builders like today's Rustaceans do.

This concept is tremendously inspired by argopt, I really appreciate the work. However, it still requires a bit of cumbersome code, especially for handling subcommands. fncmd has been rewritten from scratch to get rid of all the complexities. Let's dig into Subcommands section to see how we can handle it.

Installation

This crate is nightly-only. Make sure you have set up your toolchain as nightly before using (e.g. having rust-toolchain file).

To install, if you use cargo-edit:

cargo add fncmd

Or you can manually edit Cargo.toml:

[dependencies]
fncmd = "1.0.0"

Basics

This crate exposes just a single attribute macro, [fncmd], which can only be attached to the main function:

// main.rs
use fncmd::fncmd;

/// Description of the command line tool
#[fncmd]
pub fn main(
  /// Argument foo
  #[opt(short, long)]
  foo: String,
  /// Argument bar
  #[opt(short, long)]
  bar: Option<String>,
) {
  println!("{:?} {:?}", foo, bar);
}

That's all, and now you got a command line program with options handled by clap. With above code, the help message will be like below:

crate-name 0.1.0

Description of the command line tool

USAGE:
    crate-name [OPTIONS] --foo <FOO>

OPTIONS:
    -b, --bar <BAR>    Argument bar
    -f, --foo <FOO>    Argument foo
    -h, --help         Print help information
    -V, --version      Print version information

The name and the version of your crate are automatically inferred from Cargo metadata.

The usage of the opt attribute is exactly the same as the underlying clap attribute on arguments, they're just passed as is, except that it appends (long) if no configuration was provided, i.e. #[opt] means #[opt(long)]. If you want to take the argument foo without --foo, just omit #[opt].

Subcommands

As you may know, in Cargo project you can put entrypoints for additional binaries into src/bin. If 1) their names are prefixed by crate-name and 2) their main functions are decorated with the #[fncmd] attribute and 3) exposed as pub, then those are automatically wrapped up as subcommands of the default binary target crate-name. Say you have the following directory structure:

src
├── main.rs
└── bin
    ├── crate-name-subcommand1.rs
    └── crate-name-subcommand2.rs

You'll get the following subcommand structure:

crate-name
└── crate-name subcommand1
└── crate-name subcommand2

Specifying entrypoint paths manually

Configuring binary targets in your Cargo.toml should work as usual, for example:

[[bin]]
name = "crate-name"
path = "src/clis/crate-name.rs"

[[bin]]
name = "crate-name-subcommand1"
path = "src/clis/crate-name-subcommand1.rs"

[[bin]]
name = "crate-name-subcommand2"
path = "src/clis/crate-name-subcommand2.rs"

The resulting subcommand structure of this configuration is equivalent to what you get in the Subcommands section above. If you want a binary target to be handled by fncmd but not to be a subcommand regardless of its target name, just omit pub. So for example, when crate-name-subcommand2 is not exposed as pub, it won't contained within crate-name.

Nested subcommands

Following is how #[fncmd] macro determines which targets are subcommands (roughly explained):

  1. Get the name of the call-site target itself
  2. Enumerate all possible targets (#[fncmd]-annotated entrypoints)
  3. Filter out inappropriate items (ones not prefixed by the name of the call-site target)
  4. Filter out inappropriate items (ones prefixed by any other target name)
  5. Filter out inappropriate items (non-pub targets)

These steps are done for each macroexpansion. So for example:

[[bin]]
name = "crate-name"
path = "src/clis/crate-name.rs"

[[bin]]
name = "another"
path = "src/clis/another.rs"

[[bin]]
name = "another-sub" # `pub`
path = "src/clis/another-sub.rs"

[[bin]]
name = "another-sub-subsub" # `pub`
path = "src/clis/another-sub-subsub.rs"

[[bin]]
name = "another-orphan" # non-`pub`
path = "src/clis/another-orphan.rs"

[[bin]]
name = "another-orphan-sub" # `pub`
path = "src/clis/another-orphan-sub.rs"

This configuration yields up into these commands:

crate-name

another
└── another sub
    └── another sub subsub

another-orphan
└── another-orphan sub

Of course the same structure can be achieved without manually editing Cargo.toml, by placing files into the default location:

src
├── main.rs
└── bin
    ├── another.rs
    ├── another-sub.rs
    ├── another-sub-subsub.rs
    ├── another-orphan.rs
    └── another-orphan-sub.rs

Restrictions

fncmd won't support following features by design:

  • Show authors on the help message
  • Change the name and the version of the command to arbitrary values
  • Attach #[fncmd] to functions other than main

That's why fncmd states “opinionated”. Showing authors on the help will simply be a noise from general user's point of view, and changing metadata such as name and version to different values from the ones defined in Cargo.toml can easily undermine maintainability and consistency of them. Attaching #[fncmd] to arbitrary functions can lead to a bloated single file codebase, which should be avoided in general.

You might also like...
Gix is a command-line interface (CLI) to access git repositories

gix is a command-line interface (CLI) to access git repositories. It's written to optimize the user-experience, and perform as good or better than the

The official command-line interface for the makedeb Package Repository

mpr-cli This is the repository for the MPR CLI, the official command-line interface for the makedeb Package Repository. Installation Users have a few

A command line interface for trash written in Rust (WIP)

trashctl A command line interface for trash Features Add file to trash List files Permanently delete a file Restore file Empty the trash Documentation

A command-line interface for Trane

trane-cli This repository contains the code for the command-line interface to Trane. Documentation The latest documentation for trane-cli can be found

Command-line interface to Microsoft To Do

⚠ This is a hackathon project with no official support or quality guarantee Hackathon 2022 tdi The command-line interface, for some, is the natural wa

The awesome-app Command Line Interface

Rust CLI to create Awesome Applications with Rust. More info at awesomeapp.org Install cargo install awesome-app Create your first app: # Create you

This is a simple command-line interface tool that allows you to interact with ChatGPT from OpenAI or Azure.

HeyGPT This is a simple command-line interface tool that allows you to interact with ChatGPT from OpenAI or Azure. You can use it to: Chat with ChatGP

A command-line interface for interacting with the ChatGPT API from OpenAI
A command-line interface for interacting with the ChatGPT API from OpenAI

cligpt cligpt is a command-line interface for interacting with the ChatGPT API from OpenAI. With cligpt, you can quickly and easily generate text by s

tmplt is a command-line interface tool that allows you to quickly and easily set up project templates for various programming languages and frameworks
tmplt is a command-line interface tool that allows you to quickly and easily set up project templates for various programming languages and frameworks

tmplt A User Friendly CLI Tool For Creating New Projects With Templates About tmplt is a command-line tool that lets users quickly create new projects

Comments
  • async fn main

    async fn main

    Hi,

    It's common for main functions to look like this

    #[tokio::main]
    async fn main() -> anyhow::Result<()> {}
    

    adding #[fncmd] to that doesn't work. is there planned support for that ?

    opened by midnightexigent 4
  • Fixing build that fails due to missing derive feature of clap

    Fixing build that fails due to missing derive feature of clap

    First, thanks for the beautiful yet simple library! I want to use it for a personal project but I stumble upon this subtle issue, I hope this will fix it!

    opened by mettz 2
  • fix: suppress unused attributes warning for subcommands

    fix: suppress unused attributes warning for subcommands

    Fixes the bug that unstable feature gates (i.e. #![feature(...)]) in subcommand binary files are reported as unused_attributes incorrectly, regardless of their usage.

    opened by yuhr 1
  • fix: fully qualify hidden traits

    fix: fully qualify hidden traits

    This will avoid conficts against user-defined return type that implements traits that have into_exit_code function. Note that it seems clap-originated traits cannot be used in fully qualified manner, maybe a bug or limitation in their derive macros?

    opened by yuhr 0
Releases(v1.2.5)
  • v1.2.5(Apr 14, 2022)

    What's Changed

    • Feature "termination_trait_lib" is now stable by @DhruvDh in https://github.com/yuhr/fncmd/pull/28

    New Contributors

    • @DhruvDh made their first contribution in https://github.com/yuhr/fncmd/pull/28

    Full Changelog: https://github.com/yuhr/fncmd/compare/v1.2.4...v1.2.5

    Source code(tar.gz)
    Source code(zip)
  • v1.2.4(Feb 9, 2022)

    What's Changed

    • fix: follow upstream change of std::process::Termination by @yuhr in https://github.com/yuhr/fncmd/pull/26

    Full Changelog: https://github.com/yuhr/fncmd/compare/v1.2.3...v1.2.4

    Source code(tar.gz)
    Source code(zip)
  • v1.2.3(Feb 9, 2022)

    What's Changed

    • fix: handle exotic main macros correctly by @yuhr in https://github.com/yuhr/fncmd/pull/24
    • fix: fully qualify hidden traits by @yuhr in https://github.com/yuhr/fncmd/pull/25

    Full Changelog: https://github.com/yuhr/fncmd/compare/v1.2.2...v1.2.3

    Source code(tar.gz)
    Source code(zip)
  • v1.2.2(Feb 8, 2022)

    What's Changed

    • refactor: make code clean by @yuhr in https://github.com/yuhr/fncmd/pull/21
    • fix: use fully qualified macro path for recursive macroexpansion by @yuhr in https://github.com/yuhr/fncmd/pull/22
    • fix: fix condition to collect subcommands with fully qualified macro path by @yuhr in https://github.com/yuhr/fncmd/pull/23

    Full Changelog: https://github.com/yuhr/fncmd/compare/v1.2.1...v1.2.2

    Source code(tar.gz)
    Source code(zip)
  • v1.2.1(Feb 8, 2022)

    What's Changed

    • fix: make Rust keywords available as command names by @yuhr in https://github.com/yuhr/fncmd/pull/20

    Full Changelog: https://github.com/yuhr/fncmd/compare/v1.2.0...v1.2.1

    Source code(tar.gz)
    Source code(zip)
  • v1.2.0(Jan 17, 2022)

    What's Changed

    • feat: support for example targets by @mettz in https://github.com/yuhr/fncmd/pull/18

    New Contributors

    • @mettz made their first contribution in https://github.com/yuhr/fncmd/pull/18

    Full Changelog: https://github.com/yuhr/fncmd/compare/v1.1.1...v1.2.0

    Source code(tar.gz)
    Source code(zip)
  • v1.1.1(Jan 15, 2022)

    What's Changed

    • fix: fix lack of doc comment when exotic attribute macro is used by @yuhr in https://github.com/yuhr/fncmd/pull/16
    • build: exclude justfile by @yuhr in https://github.com/yuhr/fncmd/pull/17

    Full Changelog: https://github.com/yuhr/fncmd/compare/v1.1.0...v1.1.1

    Source code(tar.gz)
    Source code(zip)
  • v1.1.0(Jan 14, 2022)

    What's Changed

    • Support exotic attribute macros and arbitrary return types with Termination trait by @midnightexigent in https://github.com/yuhr/fncmd/pull/12
    • fix: make subcommands optional by @yuhr in https://github.com/yuhr/fncmd/pull/14
    • chore: update release replacement by @yuhr in https://github.com/yuhr/fncmd/pull/15

    New Contributors

    • @midnightexigent made their first contribution in https://github.com/yuhr/fncmd/pull/12

    Full Changelog: https://github.com/yuhr/fncmd/compare/v1.0.2...v1.1.0

    Source code(tar.gz)
    Source code(zip)
  • v1.0.2(Dec 16, 2021)

    What's Changed

    • style: fix indentation by @yuhr in https://github.com/yuhr/fncmd/pull/6
    • chore: enable signing for version bump commits by @yuhr in https://github.com/yuhr/fncmd/pull/7
    • docs: add useful links to badges by @yuhr in https://github.com/yuhr/fncmd/pull/8
    • fix: pin clap version to exact one by @yuhr in https://github.com/yuhr/fncmd/pull/10

    Full Changelog: https://github.com/yuhr/fncmd/compare/v1.0.1...v1.0.2

    Source code(tar.gz)
    Source code(zip)
  • v1.0.1(Nov 28, 2021)

    What's Changed

    • refactor: remove unnecessary unstable feature flags by @yuhr in https://github.com/yuhr/fncmd/pull/1
    • refactor: remove unused dependency by @yuhr in https://github.com/yuhr/fncmd/pull/2
    • docs: update readme by @yuhr in https://github.com/yuhr/fncmd/pull/3
    • fix: assert attached function name to be main by @yuhr in https://github.com/yuhr/fncmd/pull/4
    • chore: configure release flow by @yuhr in https://github.com/yuhr/fncmd/pull/5

    New Contributors

    • @yuhr made their first contribution in https://github.com/yuhr/fncmd/pull/1

    Full Changelog: https://github.com/yuhr/fncmd/compare/v1.0.0...v1.0.1

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0(Nov 23, 2021)

Owner
Yu Shimura
❤️ TypeScript, Rust, Haskell
Yu Shimura
Small command-line tool to switch monitor inputs from command line

swmon Small command-line tool to switch monitor inputs from command line Installation git clone https://github.com/cr1901/swmon cargo install --path .

William D. Jones 5 Aug 20, 2022
Safe OCaml-Rust Foreign Function Interface

ocaml-rust This repo contains code for a proof of concept for a safe OCaml-Rust interop inspired by cxx. This is mostly optimized for calling Rust cod

Laurent Mazare 23 Dec 27, 2022
Low-level Rust library for implementing terminal command line interface, like in embedded systems.

Terminal CLI Need to build an interactive command prompt, with commands, properties and with full autocomplete? This is for you. Example, output only

HashMismatch 47 Nov 25, 2022
Crunch is a command-line interface (CLI) to claim staking rewards every X hours for Substrate-based chains

crunch · crunch is a command-line interface (CLI) to claim staking rewards every X hours for Substrate-based chains. Why use crunch To automate payout

null 39 Dec 8, 2022
Command line interface for Solana Metaplex programs.

Metaplex Command Line Interface This is a command line interface for creating and managing non-fungible tokens on the Solana blockchain through the Me

Caleb Everett 26 Jul 12, 2022
Railway CLI - This is the command line interface for Railway.

Railway CLI This is the command line interface for Railway. Use it to connect your code to Railways infrastructure without needing to worry about envi

Nebula 4 Mar 20, 2022
A silly program written in Rust to output nonsensical sentences in the command line interface.

A silly program written in Rust to output nonsensical sentences in the command line interface.

Rachael Ava 1 Dec 13, 2021
Scouty is a command-line interface (CLI) to keep an eye on substrate-based chains and hook things up

scouty is a command-line interface (CLI) to keep an eye on substrate-based chains and hook things up

TurboFlakes 15 Aug 6, 2022
A standalone Command Line Interface debugging tool for The Witcher 3 written in Rust

A standalone Command Line Interface debugging tool for The Witcher 3 written in Rust. This tool is intended for Witcher 3 modders who make mainly scri

Przemysław Cedro 5 Apr 20, 2022
A command line interface meant to bridge the gap between Rust and shell scripting

clawbang A command line interface meant to bridge the gap between Rust and shell scripting. Intended for use with HEREDOCs and shebangs: $ clawbang <<

Chris Dickinson 52 Mar 25, 2022