Создайте собственный инструмент командной строки с Dart для проекта Flutter

Создайте собственный инструмент командной строки с Dart для проекта Flutter

FlutterPulse

Эта статья переведена специально для канала FlutterPulse. В этом канале вы найдёте много интересных вещей, связанных с Flutter. Не забывайте подписываться! 🚀

Введение

Введение

Вы когда-нибудь думали о создании собственного инструмента CLI для вашего проекта Flutter?

Я думаю, у каждого есть своя архитектура проекта или используется какая-то общая архитектура. Если вы используете GetX то вы можете использовать get_cli для генерации архитектуры, это может помочь вам сократить работу и сэкономить время.

Например, структура одного из моих проектов на GetX приведена ниже

Проект на основе GetX

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

Поэтому я хочу создать инструмент CLI чтобы помочь в этом!

Создание консольного приложения на Dart

Используйте следующую команду для создания консольного проекта и назовите его CLI

dart create -t console-full cli

Это создаст следующие файлы в папке

Структура приведена ниже

Попробуйте запустить его

dart run bin/cli.dart

Вы получите следующий результат

Привет, мир: 42!

И вы можете скомпилировать это в исполняемый файл

dart compile exe bin/cli.dart

Хорошо, это основа для создания консольного приложения, но нам нужно больше. Я покажу вам, как сгенерировать простой шаблон, как я упоминал выше.

DCli

DCli — это консольный SDK для Dart. Используйте консольный SDK DCli для создания кроссплатформенных приложений командной строки (CLI) и скриптов с использованием языка программирования Dart. Консольный SDK DCli (произносится как d-kleye) включает инструменты командной строки и обширный API для создания приложений CLI.

Это мощный SDK для создания консольных приложений.

DCli имеет следующие цели:

  • сделать создание приложений CLI таким же простым, как прогулка.
  • полностью использовать выразительность Dart.
  • работает плавно с основными библиотеками Dart.
  • предоставляет кроссплатформенный API для Windows, OSx и Linux.
  • вызывать любое приложение CLI.
  • делает отладку приложений CLI простой.
  • генерация сообщений об ошибках, которые облегчают решение проблем.
  • предоставляет качественную документацию и примеры.

Вы можете установить его с помощью следующей команды

dart pub global activate dcli
dcli install

Вы можете найти подробное описание использования на их официальном сайте. Они предоставили всестороннюю документацию, и я покажу вам, как создать и сгенерировать коды в этой статье 😀.

Создание шаблона

Для примера выше нам нужно создать следующую структуру папок

Таким образом, мы можем создать шаблоны файлов и папок в папке lib

Для простого примера содержимое controller.template следующее

class {{className}}Controller extends GetxController with BaseControllerMixin {
  @override
  String get builderId => '{{pageName}}';

  {{className}}Controller();

  @override
  void onInit() {
    super.onInit();
  }

  /// Следует ли отслеживать события жизненного цикла
  @override
  bool get listenLifecycleEvent => true;

  /// Когда listenLifecycle Event установлен в true, будут вызваны следующие методы жизненного цикла
  @override
  void onDetached() {
    log('onDetached');
  }

  @override
  void onHidden() {
    log('onHidden');
  }

  @override
  void onInactive() {
    log('onInactive');
  }

  @override
  void onPaused() {
    log('onPaused');
  }

  @override
  void onResumed() {
    log('onResumed');
  }
}

Обратите внимание, что это только для демонстрации, поэтому код неполный, и в этом шаблоне нет необходимых библиотек для импорта.

Файл view.template

class {{className}}Page extends GetView<{{className}}Controller> {
  const {{className}}Page({super.key});
}

Файл index.template

library {{pageName}};

export 'controller.dart';
export 'view.dart';

Как видно, в шаблоне есть некоторые переменные, и мы будем заменять их динамически с помощью команды CLI.

Реализация функций CLI

Добавьте зависимость dcli в pubspec.yaml

dependencies:
  dcli: ^7.0.3  #use the latest version

Для общего инструмента CLI должны поддерживаться такие аргументы, как -help или -version чтобы пользователь знал, как его использовать и какая текущая версия.

Поэтому сначала нужно обработать эти аргументы:

final parser =
  ArgParser()
    ..addFlag(
      'help',
      abbr: 'h',
      help: 'Show usage information.',
      negatable: false,
    )
    ..addFlag(
      'version',
      abbr: 'v',
      help: 'Show version information.',
      negatable: false,
    );

    final results = parser.parse(arguments);

if (results['help']) {
  printUsage(parser);
  exit(0);
}

if (results['version']) {
  printVersion();
  exit(0);
}

Я хочу использовать этот CLI для создания новой страницы по команде на основе шаблонов файлов

cli create page:home

Предположим, после выполнения вышеуказанной команды будут созданы следующие файлы и папки

Теперь нужно обработать аргументы create page:home

final command = arguments.sublist(1).join(' ');

  if (!command.startsWith('page:')) {
    print('Invalid command format. Expected: create page:<pagename>');
    printUsage(parser);
    exit(1);
  }

 final pageName = command.split(':')[1];
 createPage(pageName); 

Создайте папку pages по команде и перезапишите, если она существует

final dirPath = path.join(Directory.current.path, 'pages', pageName);

  // Delete directory if it exists
  if (exists(dirPath)) {
    find(
      '*',
      workingDirectory: dirPath,
      recursive: true,
      types: [Find.file],
    ).forEach(delete);
    deleteDir(dirPath);
    print('Deleted existing directory $dirPath');
  }

  // Create directory
  createDir(dirPath, recursive: true);
  print('Created directory $dirPath');

Загрузите шаблоны файлов и динамически замените переменные

// Преобразование в PascalCase для имен классов
  final className = toPascalCase(pageName);

  // Загрузка шаблонов из пакета
  final templateDir = 'lib/templates/page';
  final controllerTemplatePath = path.join(templateDir, 'controller.template');
  final viewTemplatePath = path.join(templateDir, 'view.template');
  final indexTemplatePath = path.join(templateDir, 'index.template');

  final controllerTemplate = loadAsset(controllerTemplatePath);
  final viewTemplate = loadAsset(viewTemplatePath);
  final indexTemplate = loadAsset(indexTemplatePath);

  if (controllerTemplate == null ||
      viewTemplate == null ||
      indexTemplate == null) {
    print(
      'Один или несколько файлов шаблонов отсутствуют в директории lib/templates/page.',
    );
    exit(1);
  }

  // Замена заполнителей в шаблонах
  final controllerContent = controllerTemplate
      .replaceAll('{{className}}', className)
      .replaceAll('{{pageName}}', pageName);
  final viewContent = viewTemplate.replaceAll('{{className}}', className);
  final indexContent = indexTemplate.replaceAll('{{pageName}}', pageName);

  // Создание или перезапись controller.dart
  final controllerFilePath = path.join(dirPath, 'controller.dart');
  File(controllerFilePath).writeAsStringSync(controllerContent);
  print('Создан или перезаписан файл $controllerFilePath');

  // Создание или перезапись view.dart
  final viewFilePath = path.join(dirPath, 'view.dart');
  File(viewFilePath).writeAsStringSync(viewContent);
  print('Создан или перезаписан файл $viewFilePath');

  // Создание или перезапись index.dart
  final indexPath = path.join(dirPath, 'index.dart');
  File(indexPath).writeAsStringSync(indexContent);
  print('Создан или перезаписан файл $indexPath');

Функция ниже загрузит файлы шаблонов из базовой папки проекта

String? loadAsset(String assetPath) {
  final scriptFile = Platform.script.toFilePath();
  final scriptDir = path.dirname(scriptFile);
  final packageRoot = path.join(
    scriptDir,
    '..',
  ); // Предполагается, что bin находится на один уровень ниже корня
  final filePath = path.join(packageRoot, assetPath);
  // final filePath = path.join(scriptDir, assetPath);
  try {
    final content = File(filePath).readAsStringSync();
    return content;
  } catch (e) {
    print('Не удалось загрузить ресурс: $filePath');
    return null;
  }
}

Я не показал весь код выше, а только основной. Вы можете найти полный проект здесь.

Тестирование CLI

После завершения вы можете запустить CLI как консольное приложение ниже

dart bin/cli.dart -v

Вы должны получить следующий результат

cli version v1.0.0

И попробуйте создать элемент страницы

dart bin/cli.dart create page:home

Будет сгенерировано 3 файла на основе файлов шаблонов

pages/home.dart файл

class HomeController extends GetxController with BaseControllerMixin {
  @override
  String get builderId => 'home';

  HomeController();

  @override
  void onInit() {
    super.onInit();
  }

  /// Следить за событиями жизненного цикла
  @override
  bool get listenLifecycleEvent => true;

  /// Когда listenLifecycle Event установлен в true, будут вызваны следующие методы жизненного цикла
  @override
  void onDetached() {
    log('onDetached');
  }

  @override
  void onHidden() {
    log('onHidden');
  }

  @override
  void onInactive() {
    log('onInactive');
  }

  @override
  void onPaused() {
    log('onPaused');
  }

  @override
  void onResumed() {
    log('onResumed');
  }
}

pages/view.dart файл

class HomePage extends GetView<HomeController> {
  const HomePage({super.key});
}

pages/index.dart файл

library home;

export 'controller.dart';
export 'view.dart';

Отлично, мы почти закончили!

Активация глобальной команды

Потому что есть шаблонные файлы и папки с CLI , поэтому если вы скопируете исполняемый файл в папку вашего проекта и также нужно скопировать шаблонные файлы, это не то, что нам нужно. Я думаю, вы захотите использовать ваш инструмент CLI где угодно, верно?

Хорошо, давайте сделаем это!

Нам нужно активировать проект CLI как глобальный инструмент, а также нужно включить шаблонные файлы, чтобы мы могли обновить файл pubspec.yaml ниже

executables:
  cli: cli

# Include templates in the package
include:
  - lib/templates/page/

И выполните команду ниже в корневой папке проекта, чтобы активировать её как глобальную команду

dart pub global activate --source path .

Вы можете использовать команду ниже, чтобы увидеть, добавлено ли это в глобальную

dart pub global list

Если вы хотите удалить его, просто выполните ниже

dart pub global deactivate cli

Тестирование глобальной версии

Теперь вы можете использовать CLI где угодно

cli create page:home

Если у вас возникнут следующие проблемы

Failed to load asset: /Volumes/cli/.dart_tool/pub/bin/cli/../lib/templates/page/controller.template
Failed to load asset: /Volumes/cli/.dart_tool/pub/bin/cli/../lib/templates/page/view.template
Failed to load asset: /Volumes/cli/.dart_tool/pub/bin/cli/../lib/templates/page/index.template

Это происходит потому, что приложение не может получить текущую папку, оно скопирует её в папку .dart_tool после публикации в глобальную, решение заключается в том, что вы можете жестко закодировать папку проекта в loadAsset

 final packageRoot = '/Volumes/cli/'; //hardcode to your CLI project folder

 final filePath = path.join(packageRoot, assetPath);
...

Обновление новой версии

В конце, если вы отредактируете код CLI и захотите опубликовать его снова, вы обнаружите, что это не работает, вам нужно следовать следующим шагам:

  1. Удалите папку .dart_tool в корневой папке вашего проекта.
  2. Получите зависимости проекта снова. (сохраните файл pubspec.yaml снова)
  3. Не нужно запускать команду активации глобально, но нужно запустить ниже для активации снова:
cli -h

Вы можете найти полный проект ниже

github.com

Заключение

На самом деле, инструмент CLI не только для проектов Flutter, вы можете создавать любые инструменты, которые хотите, чтобы сэкономить время на повторяющихся задачах.

Инструмент DCli также очень мощный и позволяет делать много вещей, поэтому я рекомендую вам изучить и попробовать его, если вы хотите создать CLI 😁

Спасибо, что являетесь частью сообщества!

Report Page