Мы видели, как создать Приветствие CLI в другом руководстве. Теперь мы расширим наши знания и создадим full-fledged CLI, который будет частично клонировать Mac / Unix find.

Для создания интерфейса командной строки необходимы следующие функции:

  1. Парсер аргументов входной команды
  2. Просматривать файлы и деревья каталогов
  3. Фильтровать файлы / каталог на основе аргументов
  4. Регистратор, лучшая информация для регистрации

1. Парсер аргументов входной команды

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

Принимая аргументы в Deno, это очень просто. У каждого процесса есть Deno.args, который возвращает аргументы, переданные программе.

Выполнить:

deno run examples/minifind.ts param1 param2

Вывод:

[ “param1”, “param2” ]

Deno.args возвращает массив строки, переданной программе (examples / minifind.ts).

Наш CLI ожидает такие параметры, как type, name и help. Чтобы получить значение этих параметров. Нам нужно разобрать аргументы. В Deno есть flags модуль, который помогает анализировать и собирать параметры. Добавим parser.

import { parse } from "https://deno.land/std/flags/mod.ts";
async function main(args: string[]) {
  const {
    type,
    name,
    not,
    help,
    _: [dir = "."],
  } = parse(args);
console.log({
    type,
    name,
    not,
    help,
    dir,
  });
}
main(Deno.args);

Выполнить:

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

Вывод:

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

Когда вы запустите программу с данным примером, вы увидите результат, как указано выше. Deno parse помогает собрать все аргументы.

Я использовал функцию деструктуризации ES6, чтобы присвоить значения по умолчанию.

Deno parse автоматически пытается собрать и объединить параметры на основе шаблонов. Любой аргумент, переданный с префиксом (-), считается аргументом со значением. Если вы не передаете значение рядом с ним. Он станет логическим.

Пример 1:

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

На что следует обратить внимание: если вы передаете аргумент с одним и тем же параметром более одного раза. parse объедините их в array. В приведенном выше примере type передается дважды. Вот почему type имеет значение [ "f", "d" ].

Пример 2:

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

подчеркивание (_) здесь похоже на набор параметров отдыха. Если аргументы не соответствуют стандартному префиксу -- или -. Все аргументы собраны в _ как массив данных. Мы извлекаем dir как имя каталога из остальных _.

Пример 3:

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

Подробнее читайте: https://deno.land/std/flags

2. Просматривайте файлы и деревья каталогов.

Поскольку теперь у нас есть проанализированные аргументы, давайте добавим логику чтения каталога.

Первое, что мы можем сделать, это разрешить path или directory, в которых нужно искать файлы. Мы можем использовать метод разрешения из модуля пути.

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);

Выполнить:

deno run -A examples/minifind.ts examples

Вывод:

/Users/xdeepakv/github/deno-by-example/examples

resolve требуется --allow-read разрешение. На данный момент я предоставил всем разрешение на передачу флага -A. вы можете узнать больше о разрешениях

Deno.cwd() используется для получения текущего рабочего пути. Нам пришлось преобразовать dir в строку. Поскольку parse может преобразовать его в string | number в зависимости от типа ввода.

Чтение каталога может быть выполнено с помощью Deno.readDir. Но мы обходим все дерево каталогов и файлов. Написание метода траверса может быть непростым делом. Вы можете попробовать сами.

Здесь я воспользуюсь функцией walk из https://deno.land/std/fs/mod.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);

Выполнить:

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

Вывод :

{
  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
}

Поскольку walk функция не является стабильной функцией. Мы должны использовать флаг --unstable при запуске примера.

Функция Walk возвращает асинхронный генератор entries. У каждой записи есть name и path вместе с другими флагами, такими как isDirectory и isFile.

Хорошо: самая сложная часть уже сделана. Теперь мы можем читать целые каталоги вместе с файлами в них.

3. Отфильтруйте файлы / каталог на основе аргументов.

Функция Walk принимает в качестве второго аргумента WalkOptions. Мы можем использовать эту опцию, чтобы добавить нашу логику.

Интерфейс:

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

Код:

// 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);

Выполнить:

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

Вывод:

/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

Тип по умолчанию будет включать как file, так и dir ['f', 'd']. Пользователи могут передать флаг --type=f и --type=d, чтобы переопределить поведение.

Только Run- Dirs:

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

Только файлы запуска:

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

WalkOptions поддерживает регулярное выражение для включения и исключения шаблонов. Мы можем использовать это для фильтрации записей по имени.

/// rest of the code
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);

Выполнить - получить все имена файлов, в которых есть регистратор:

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

Теперь у нас работает minifind. Шум!

4. Регистратор, лучшая информация для регистрации

/// 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);

Последний недостающий элемент - сообщить пользователю о вашем интерфейсе командной строки. Для этого мы добавили сообщения помощи для пользователей. Я использую созданные мной logger-util. Подробнее читайте здесь https://deno.land/x/deno_util.

Беги с помощью:

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

Вывод:

Выполнить с другими параметрами:

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

Вывод:

ТаДа! 👏👏 Теперь вы знаете, как создать интерфейс командной строки.

Бонус:

Теперь у нас есть рабочий minifind CLI. Однако нам пришлось использовать deno run и filename для выполнения команды, что не предназначено / выполнимо. Deno предоставляет install команду. Используя это, мы можем преобразовать любую программу в executable инструмент.

Давайте преобразуем наш мини-поиск в executable. Все очень просто.

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

После того, как вы запустите указанную выше команду, вы увидите следующий результат:

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

Если вы это видите, просто добавьте export PATH="/Users/xdeepakv/.deno/bin:$PATH" эту строку в свой .bashrc или .bash_profile (в зависимости от типа вашей ОС). Как только вы добавите .deno/bin в PATH. Откройте новый терминал и попробуйте следующую команду.

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

Теперь ваш minifind готов к использованию в рабочей среде (CLI). :-)

Надеюсь, вам понравился этот урок. Пожалуйста, ознакомьтесь с некоторыми другими руководствами на нашем веб-сайте: https://deepakshrma.github.io/deno-by-example/

Все рабочие примеры можно найти в моем Github: https://github.com/deepakshrma/deno-by-example/tree/master/examples