# Build a CLI Application | Deno By Example

If you have work for big organization, You must have built reusable and robust tool. That includes web, mobile and CLI Applications. A CLI will not only reduce the effort. It is also improve your CI/CD. Good CI/CD practice helps developer to achieve their goal in simple and convenient way


We have seen how to create a basic [Greeting CLI](https://decipher.dev/deno-by-example/02-greet-from-cli/) in another tutorial. Now we will extend our knowledge and create a `full-fledged` CLI which will be partially clone of Mac/Unix `find`.

<noscript><img alt="Image for post" src="https://miro.medium.com/max/9792/1*6IOv24zYgd_Zt5iqYGridg.jpeg" width="4896" height="3264" srcSet="https://miro.medium.com/max/552/1*6IOv24zYgd_Zt5iqYGridg.jpeg 276w, https://miro.medium.com/max/1104/1*6IOv24zYgd_Zt5iqYGridg.jpeg 552w, https://miro.medium.com/max/1280/1*6IOv24zYgd_Zt5iqYGridg.jpeg 640w, https://miro.medium.com/max/1400/1*6IOv24zYgd_Zt5iqYGridg.jpeg 700w" sizes="700px"/></noscript>

Photo by [Alex Knight](https://unsplash.com/@agkdesign?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)

Creating CLI required below mentioned features:

1. Input command arguments parser
2. Traverse files and directory trees
3. Filter files/directory based on the arguments
4. Logger, better logging information

## 1. Input command arguments parser

```ts
async function main(args: string[]) {
  console.log(args);
}
main(Deno.args);
```

Taking arguments in `Deno`, It is very easy. Every process has `Deno.args`, which returns arguments passed to the program.

_Run:_

`deno run examples/minifind.ts param1 param2`

_Output:_

`[ “param1”, “param2” ]`

`Deno.args` returns array of the string passed to the program(examples/minifind.ts).

Our CLI expects params like `type`, `name`, and `help`. To get the value of these parameters. We need to parse arguments. Deno has `flags` [module](https://deno.land/std/flags/mod.ts) which helps to parse and collect parameters. Let's add `parser`.

```ts
async function main(args: string[]) {
  const {
    type,
    name,
    not,
    help,
    _: [dir = "."]
  } = parse(args);
  console.log({
    type,
    name,
    not,
    help,
    dir
  });
}
main(Deno.args);
```

_Run:_

```ts
deno run examples/minifind.ts --help --type=f --type=d --name=".*\.ts" examples
```

_Output:_

```ts
{ type: [ "f", "d" ], name: ".*\.ts", not: undefined, help: true, dir: "examples" }
```

When you run the program with a given example, You will see the output as above. Deno parse helps you to collect all the arguments.

I have used the ES6 de-structuring feature to assign default values.

Deno parse automatically tries to collect and combine params based on patterns. Any argument passed as prefixing ( — ), considered as arguments with value. If you don't pass value next to it. It will become boolean.

_Example 1:_

```ts
console.log(parse(["--test", "t"])); // { _: [], test: "t" }
console.log(parse(["--test"])); // { _: [], test: true }
```

Things to be noted: If you pass an argument with the same param more than once. `parse` combine them in `array`. In the above example type is passed twice. That is why `type` has value `[ "f", "d" ]`.

_Example 2:_

```ts
console.log(parse(["--test", "t", "--test", "t2"]));

// { _: [], test: [ "t", "t2" ] }
```

underscore(`_`) here is like a collection of rest params. If arguments do not follow the standard `--` or `-` prefix. All arguments collected in `_` as an array of data. We are extracting `dir` as the directory name from rest `_`.

_Example 3:_

```ts
const { _ } = parse(["--test", "t", "examples"]);
console.log(_); // _ == [ "examples" ]
const [dir = "."] = _;
console.log(dir);

// examples
```

**For more info read:** [https://deno.land/std/flags](https://deno.land/std/flags)

## 2. Traverse files and directory trees

Since now we have arguments parsed, let’s add some logic to read the directory.

The first thing we can do, We can resolve the `path` or `directory` where files need to be searched. We can use the resolve method from the [path module](https://deno.land/std/path).

```ts
import { parse } from "https://deno.land/std/flags/mod.ts";
import { resolve } from "https://deno.land/std/path/mod.ts";

async function main(args: string[]) {
  const {
    type,
    name,
    not,
    help,
    _: [dir = "."]
  } = parse(args);
  const dirFullPath = resolve(Deno.cwd(), String(dir));
  console.log(dirFullPath);
}
main(Deno.args);
```

_Run:_

`deno run -A examples/minifind.ts examples`

_Output:_

`_/Users/xdeepakv/github/deno-by-example/examples_`

`resolve` require `--allow-read` permission. For the time being, I have given all permission passing flag `-A`. you can read more about [permissions](https://deno.land/manual/getting_started/permissions)

`Deno.cwd()` is used to get current running path. We had to convert `dir` as a string. Since `parse` can convert it to `string | number` based on the input type.

Reading a directory can be done using `Deno.readDir`. But we are traversing the entire tree of directories and files. Writing the traverse method can be tricky. You can try by yourself.

Here, I will take the help of `walk` function from [https://deno.land/std/fs/mod.ts](https://deno.land/std/fs/mod.ts).

```ts
import { parse } from "https://deno.land/std/flags/mod.ts";
import { resolve } from "https://deno.land/std/path/mod.ts";
import { walk } from "https://deno.land/std/fs/mod.ts";

async function main(args: string[]) {
  const {
    type,
    name,
    not,
    help,
    _: [dir = "."]
  } = parse(args);
  const dirFullPath = resolve(Deno.cwd(), String(dir));
  for await (let entry of walk(dirFullPath)) {
    console.log(entry);
  }
}
main(Deno.args);
```

_Run:_

`deno run -A --unstable examples/minifind.ts examples`

_Output_**_:_**

```ts
{
 path: "/Users/xdeepakv/github/deno-by-example/examples/sample_employee.csv",
 name: "sample_employee.csv",
 isFile: true,
 isDirectory: false,
 isSymlink: false
}
{
 path: "/Users/xdeepakv/github/deno-by-example/examples/06_readfile_chunk.ts",
 name: "06_readfile_chunk.ts",
 isFile: true,
 isDirectory: false,
 isSymlink: false
}
```

Since `walk` function is not a stable function. We have to use `--unstable` flag while running the example.

Walk function returns an async generator of `entries`. Each entries have `name` and `path` along with other flags like `isDirectory` and `isFile`.

**Nice:** The toughest part has been done. Now we can read entire directories along with files in it.

## 3. Filter files/directory based on the arguments

Walk function accepts `WalkOptions` as the second argument. We can use this option to add our logic.

_Interface:_

```ts
export interface WalkOptions {
  maxDepth?: number;
  includeFiles?: boolean;
  includeDirs?: boolean;
  followSymlinks?: boolean;
  exts?: string[];
  match?: RegExp[];
}
```

_Code:_

```ts
// rest of the code
async function main(args: string[]) {
  // rest of the code
  const dirFullPath = resolve(Deno.cwd(), String(dir));
  let includeFiles = true;
  let includeDirs = true;
  let types = type ? (Array.isArray(type) ? type : [type]) : ["f", "d"];
  if (!types.includes("f")) {
    includeFiles = false;
  }
  if (!types.includes("d")) {
    includeDirs = false;
  }
  const options = {
    maxDepth: 2,
    includeFiles,
    includeDirs,
    followSymlinks: false,
    skip: [/node_modules/g]
  };
  for await (const entry of walk(dirFullPath, options)) {
    console.log(entry.path);
  }
}
main(Deno.args);
```

_Run:_

`deno run -A --unstable examples/minifind.ts examples`

_Output:_

```bash
/Users/xdeepakv/github/deno-by-example/examples
/Users/xdeepakv/github/deno-by-example/examples/subfolder
/Users/xdeepakv/github/deno-by-example/examples/subfolder/dummy.ts
```

The default type would include both `file` and `dir` ["f","d"] . Users can pass the flag `--type=f` and `--type=d` to override behavior.

**_Run- Dirs only:_**

`deno run -A --unstable examples/minifind.ts --type=d examples`

**_Run- Files only:_**

`deno run -A --unstable examples/minifind.ts --type=f examples`

`WalkOptions` supports regexp to include and exclude patterns. We can use this to filter entries by name.

```ts
async function main(args: string[]) {
  /// rest of the code
  let matchRegexps: RegExp[] | undefined = name
    ? (Array.isArray(name) ? name : [name]).map(
        (reg: string) => new RegExp(reg)
      )
    : undefined;
  const options = {
    maxDepth: 2,
    includeFiles,
    includeDirs,
    followSymlinks: false,
    match: matchRegexps,
    skip: [/node_modules/g]
  };
  for await (const entry of walk(dirFullPath, options)) {
    console.log(entry.path);
  }
}
main(Deno.args);
```

**Run- Get all file name have logger in it:**

`deno run -A --unstable examples/minifind.ts --type=f --name=”.*logger.*” examples`

Now we have working `minifind`. **Noice**!

## 4. Logger, better logging information

```ts
/// rest of the code
import { Logger } from "https://deno.land/x/deno_util/logger.ts";
const usesFormat = `Uses:\n\n  minifind %s`;
const logger = new Logger();
function printHelp(command: string) {
  logger.info(`Welcome to minifind [v%s]`, "1.0.0");
  logger.warn(usesFormat, command);
}
async function main(args: string[]) {
  /// rest of the code
  if (help) {
    printHelp(`--type=f --name=".*logger.*" --help examples`);
    Deno.exit(0);
  }

  /// rest of the code
  for await (const entry of walk(dirFullPath, options)) {
    logger.inverse(entry.path);
  }
}
main(Deno.args);
```

The last missing piece is to tell your user about your CLI. For that, we have add helping messages for users. I am using `logger-util` created by me. You can read more here [https://deno.land/x/deno_util](https://deno.land/x/deno_util).

_Run with help:_

`deno run -A --unstable examples/minifind.ts --help`

_Output:_

![Image for post](https://miro.medium.com/max/60/0*CbyaCwfMZQGLpBLN.png?q=20)

<noscript><img alt="Image for post" class="t u v if aj" src="https://miro.medium.com/max/1600/0*CbyaCwfMZQGLpBLN.png" width="800" height="153" srcSet="https://miro.medium.com/max/552/0*CbyaCwfMZQGLpBLN.png 276w, https://miro.medium.com/max/1104/0*CbyaCwfMZQGLpBLN.png 552w, https://miro.medium.com/max/1280/0*CbyaCwfMZQGLpBLN.png 640w, https://miro.medium.com/max/1400/0*CbyaCwfMZQGLpBLN.png 700w" sizes="700px"/></noscript>

_Run with other options:_

`deno run -A --unstable examples/minifind.ts --help`

_Output:_

![Image for post](https://miro.medium.com/max/60/0*C0gh1DXteaV1fFU4.png?q=20)

<noscript><img alt="Image for post" class="t u v if aj" src="https://miro.medium.com/max/1600/0*C0gh1DXteaV1fFU4.png" width="800" height="140" srcSet="https://miro.medium.com/max/552/0*C0gh1DXteaV1fFU4.png 276w, https://miro.medium.com/max/1104/0*C0gh1DXteaV1fFU4.png 552w, https://miro.medium.com/max/1280/0*C0gh1DXteaV1fFU4.png 640w, https://miro.medium.com/max/1400/0*C0gh1DXteaV1fFU4.png 700w" sizes="700px"/></noscript>

TaDa! 👏👏 Now you know how to create a CLI.

## Bonus:

Now we have working `minifind` CLI. However, we had to use `deno run` and `filename` to run the command, which is not _intended/feasible_. Deno provides `install` the command. Using that, We can convert any program to an `executable` tool.

Let’s convert our minifind to `executable`. It is very simple.

```bash
deno install -f --allow-read --unstable examples/minifind.ts
```

Once you run above command you will see output like:

```bash
# Add /Users/xdeepakv/.deno/bin to PATH

export PATH="/Users/xdeepakv/.deno/bin:$PATH"
```

If you see that, just add `export PATH="/Users/xdeepakv/.deno/bin:$PATH"` this line to your `.bashrc` or `.bash_profile`(Depending upon your OS type). Once you add `.deno/bin` in PATH. Open a new terminal and try below-mentioned command.

```bash
minifind --type=f --name=".*logger.*" examples
```

Now your **minifind** is ready to use for production(CLI). :-)

_I hope you like this tutorial. Please have a look at some other tutorials on our website:_ [https://decipher.dev/deno-by-example/](https://decipher.dev/deno-by-example/)

All working examples can be found in my Github: [https://github.com/deepakshrma/deno-by-example/tree/master/examples](https://github.com/deepakshrma/deno-by-example/tree/master/examples)

