Github Actions deno version Published on nest.land

Denomander is a solution for Deno command-line interfaces. It is inspired from commander.js by tj which is the node’s version.

Lizard 🦎: There is a new, much simpler and much cleaner, way to define you cli commands and options. It is called Lizard 🦎 and it is inspired by Laravel’s Artisan commands. For more, follow the instructions… .

Denomander is a Deno project so it needs to have deno installed in your system. If you don’t there is a Dockerfile in the root of the project to create an image running deno To use it just build the Docker file docker build -t deno . Now you can run all the deno commands docker run --rm -v $PWD:/app/ deno test

Installation

Using Nest Land

import Denomander from "https://x.nest.land/denomander@0.7.0/mod.ts";

Using Deno Land

import Denomander from "https://deno.land/x/denomander/mod.ts";

Usage example

At first initialize the app and optionally you may pass the name, description and version of the app. If not you can change them afterwards by setting the app_name, app_description and app_version variables.

const program = new Denomander(
  {
    app_name: "My MY App",
    app_description: "My MY Description",
    app_version: "1.0.1"
  }
);

There are three option types: commands, options and required options.

Options

To set an option just call the option() method passing a) the sort and the long flag seperated by space and b) the description. The value can be accessed as properties.

program
  .command("serve", "Simple Server")
  .option("-a --address", "Define the address")
  .option("-p --port", "Define the port")
  .parse(Deno.args);

  if(program.address){
    const port = program.port || "8000";
    console.log(`Server is running on <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>p</mi><mi>r</mi><mi>o</mi><mi>g</mi><mi>r</mi><mi>a</mi><mi>m</mi><mi mathvariant="normal">.</mi><mi>a</mi><mi>d</mi><mi>d</mi><mi>r</mi><mi>e</mi><mi>s</mi><mi>s</mi></mrow><mo>:</mo></mrow><annotation encoding="application/x-tex">{program.address}:</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="mord mathnormal">ro</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">am</span><span class="mord">.</span><span class="mord mathnormal">a</span><span class="mord mathnormal">dd</span><span class="mord mathnormal">ress</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>{port}`);
  }

You may define the option’s short and long flags by seperating them with either with a) space, b) comma, or c) | (vertical bar or “pipe”)

program
  .command("serve", "Start up the server")
  .option("-a, --address", "Define the address")
  .option("-p | --port", "Define the port")
  .parse(Deno.args);

console.log(`Server is running on <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>p</mi><mi>r</mi><mi>o</mi><mi>g</mi><mi>r</mi><mi>a</mi><mi>m</mi><mi mathvariant="normal">.</mi><mi>a</mi><mi>d</mi><mi>d</mi><mi>r</mi><mi>e</mi><mi>s</mi><mi>s</mi></mrow><mo>:</mo></mrow><annotation encoding="application/x-tex">{program.address}:</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="mord mathnormal">ro</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">am</span><span class="mord">.</span><span class="mord mathnormal">a</span><span class="mord mathnormal">dd</span><span class="mord mathnormal">ress</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>{program.port}`);

Required Options

The implementation of required option is exactly same as the optional option but you have to call the requiredOption() method instead.

program
  .command("serve", "Start up the server")
  .requiredOption("-p --port", "Define the port")
  .option("-a --address", "Define the address")
  .parse(Deno.args);

  // The port is required so it must have a value
  let address = program.address || "localhost";
  console.log(`Server run on <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>a</mi><mi>d</mi><mi>d</mi><mi>r</mi><mi>e</mi><mi>s</mi><mi>s</mi></mrow><mo>:</mo></mrow><annotation encoding="application/x-tex">{address}:</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord"><span class="mord mathnormal">a</span><span class="mord mathnormal">dd</span><span class="mord mathnormal">ress</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>{program.port}`);

Global Options and Base Command Options

You have the option to define options which belong to all commands (global option) and options which belong to no command (base command option ex. –help, –version)

program
  .baseOption("-q --quiet", "Do not output any message")
  .globalOption("-c --color", "Define the output color")
  .parse(Deno.args);

Custom option processing

You may specify a function to do custom processing of option values. The callback function recieves a parameter of the pre-processed value.

function parseInteger(value: string): number {
  return parseInt(value);
}

function upercase(text: string): string {
  return text.toUpperCase();
}

program
  .command("multiply", "Multiply x and y options")
  .option("-x --xnumber", "First Number", parseInteger)
  .option("-y --ynumber", "First Number", parseInteger)
  .action(() => {
    console.log(program.xnumber * program.ynumber);
  });

program
  .command("commit", "Commit Description")
  .requiredOption("-m --message", "Commit Message", upercase)
  .action(() => {
    console.log(program.message);
  });

Commands

There are two ways to implement the commands. The first is to use an action handler by calling the action() method immediately after the command definition passing the callback function and the second is with custom one-line implementation. Multiple command arguments are now supported!

To define a command just call the .command() method and pass the command name (optionally you may also pass the description and a callback function but if not you may define them afterwards in their own methods). After the command you have the option to declare argument(s) inside brackets []. If you want a not required argument just append a question mark (?) after the name of the argument.

program
 .command("mv [from] [to] [message?]", "Start the server")
 .action(({from, to, message}:any)=>{
   // Do your actions here
   console.log(`File is moved from <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>f</mi><mi>r</mi><mi>o</mi><mi>m</mi></mrow><mi>t</mi><mi>o</mi></mrow><annotation encoding="application/x-tex">{from} to </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal">ro</span><span class="mord mathnormal">m</span></span><span class="mord mathnormal">t</span><span class="mord mathnormal">o</span></span></span></span>{to}`);
   if(message){
     console.log("message")
   }
 });

program.parse(Deno.args);

// Command action calback is called in all 3 command names (actual command and two aliases)

Action Handler

The argument(s) passed in the callback function is now an object so you may destructure the object and take your variable which has the same name with your command declaration!

program
  .command("clone [foldername]")
  .description("clone a repo")
  .action(({foldername}:any) => {
    console.log("The repo is cloned into: " + foldername);
  });

program.parse(Deno.args);

Custom Implementation

program.command("serve", "Start the server");

if(program.serve){
  console.log("The server has started...");
}

program.parse(Deno.args);

Alias

After the command declaration you have the option to declare as many aliases as you want for this spesific command.

program
 .command("serve", "Start the server")
 .alias("server", "start-server")
 .action(()=>{
   console.log("the server is started");
 });

program.parse(Deno.args);

// Command action calback is called in all 3 command names (actual command and two aliases)

Option to change default commands (help, version)

In order to change the default commands (help, version) just call the corresponding method. In case of help pass the command and the description but in case of version you may also pass the actual version of the app and after that the command and the description.

 program.setVersion(
    "1.8.1",
    "-x --xversion",
    "Display the version of the app"
  );
  
  program.parse(args);

Customize error messages

There are two ways to change the error messages. You may pass a fourth argument in new Denomander() constructor (errors object) or you may call the .errorMessages() method again passing the error messages in object. 1.

const program = new Denomander(
  {
    app_name: "My MY App",
    app_description: "My MY Description",
    app_version: "1.0.1",
    errors: {
      INVALID_RULE: "Invalid Rule",
      OPTION_NOT_FOUND: "Option not found!",
      COMMAND_NOT_FOUND: "Command not found!",
      REQUIRED_OPTION_NOT_FOUND: "Required option is not specified!",
      REQUIRED_VALUE_NOT_FOUND: "Required command value is not specified!",
      TOO_MANY_PARAMS: "You have passed too many parameters",
    }
  },
);
program.errorMessages({
  INVALID_RULE: "Invalid Rule",
  OPTION_NOT_FOUND: "Option not found!",
  COMMAND_NOT_FOUND: "Command not found!",
  REQUIRED_OPTION_NOT_FOUND: "Required option is not specified!",
  REQUIRED_VALUE_NOT_FOUND: "Required command value is not specified!",
  TOO_MANY_PARAMS: "You have passed too many parameters",
});

ToDo

  • Custom option processing
  • More examples
  • More tests
  • Easy Error Customization
  • Documentation
  • Chanage –help default output
  • Command with multiple arguments

Used

Meta

Apostolos Siokas – @siokas_apostolossiokas@gmail.com

Contributing

Any kind of contribution is welcome!

License

Distributed under the MIT License.

https://github.com/siokas/denomander