Giter Club home page Giter Club logo

fncmd's Introduction



Command line interface as a function.

fncmd

#[fncmd::fncmd] pub fn main() { println!("Hello, World!"); }

crates.io docs.rs License



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. 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). You might be interested in Why nightly.

To install, run in your project directory:

cargo add fncmd

For those who prefer case studies: see examples.

Basics

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

// main.rs

/// Description of the command line tool
#[fncmd::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 --help
Description of the command line tool

Usage: crate-name[EXE] [OPTIONS] --foo <FOO>

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

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

The usage of the opt attribute is almost exactly the same as the underlying arg attribute. They're just passed as is, except that (long) is implied 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

Multiple commands and nested subcommands

Actually fncmd doesn't have any distinction between the “default” binary and “additional” binaries. It determines subcommand structure just based on prefix structure instead. Therefore, configuring binary targets in your Cargo.toml should work as intended, 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

Note that another-orphan is not contained within another, because it's not exposed as pub. As seen here, making the main non-pub is only meaningful when you want it to have a common prefix with others but not to be included by another command, so in most cases you can set pub without thinking.

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

Use with exotic attribute macros

Sometimes you may want to transform the main function with another attribute macro such as #[tokio::main] and #[async_std::main]. In such case you have to put #[fncmd] at the outmost level:

/// Description of the command line tool
#[fncmd]
#[tokio::main]
pub async fn main(hello: String) -> anyhow::Result<()> {
  ...
}

But not:

/// Description of the command line tool
#[tokio::main]
#[fncmd]
pub async fn main(hello: String) -> anyhow::Result<()> {
  ...
}

This is because the macros like #[tokio::main] do some assertions on their own, so we need to feed them a well-mannered version of main function, e.g. removing parameters.

Position of the doc comment doesn't matter.

Restrictions

fncmd won't support following features by design. That's why fncmd states “opinionated”.

Can't show authors in the help message

Showing authors in the help will simply be a noise from general user's point of view.

Can't change the name and the version of the commands to different values

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.

Can't attach #[fncmd] to functions other than main

Attaching #[fncmd] to arbitrary functions can lead to a bloated single file codebase, which should be avoided in general.

Why nightly

The way it automatically determines which targets are subcommands or not requires the #[fncmd] macro itself to know the name of the attached target, and thus the path of the file at which it has been called. This can be achieved by Span::source_file, which is behind an unstable feature flag proc_macro_span.

fncmd's People

Contributors

dhruvdh avatar mettz avatar midnightexigent avatar yuhr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

fncmd's Issues

Support workspaces

Currently it just panics:

custom attribute panicked
message: called `Option::unwrap()` on a `None` value

Subcommands should be optional

Currently it's not possible to run the parent command if it has subcommands. This is unwanted behavior and needs to be fixed.

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 ?

Support external subcommands

There can be seen a number of toolings that do support discovery of subcommands in the PATH, such as git and cargo. This enables not only foreign subcommands (i.e. maintained independently of the top-level command) but also “external mode” (i.e. #[fncmd::fncmd]-decorated but non-pub) subcommands to work.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.