Создай себе инструмент. Часть 1: скрипт на Dart

от автора

С хорошим инструментом и работа в радость. Зачастую количество и качество тулинга влияет на развитие сообщества вокруг технологии, для которой он создан. Но «человеку всегда мало того, что он имеет. Не будь этого, мир перестал бы двигаться вперед…» (С) Евсей Баренбойм. 

Меня зовут Алексей Букин, я Flutter-разработчик в Surf. Расскажу способ сделать жизнь программиста проще и разберу этот способ на примере.

Если у вас много рутинных действий — автоматизируйте их

Постепенно вы замечаете, что вам приходится повторять множество действий: небольших и длинных, одному и в коллективе. Они не сложные, но утомляют и тратят ресурсы на синхронизацию с командой. Логичным шагом будет попытка от них избавиться, но за вас их пока никто не станет делать. Вы нуждаетесь в помощнике, дополнительном инструменте, который облегчит жизнь. 

Варианты реализации: найти готовый инструмент, написать скрипт, написать плагин

Пробуем найти готовое решение. Если ваша проблема не уникальна, то с большой вероятностью её уже кто-то решил. В противном случае переходим к самостоятельной разработке (и не забываем делиться результатом с миром). 

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

  • консольной утилитой (скриптом, программой), 

  • плагином, 

  • полноценной программой. 

Консольная утилита. Инструменты для работы в терминале не требуют особых навыков, а разработка не занимает много времени. Это отличный вариант для небольших действий. Но чем шире становится функциональность, тем больше они требуют внимания к архитектуре. 

С помощью даже несложного скрипта команда может договориться о стиле, нейминге и структуре файлов и папок. Строгость в этом вопросе позволяет проще ориентироваться в проекте и уменьшает время ревью. Но теперь вам не нужен строгий ревьюер, чтобы за этим следить: есть строгий скрипт!

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

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

Лично для меня наличие плагинов показывает, что авторы технологии заботятся о потребителях: с таким подходом намного приятнее работать. Дух захватывает от мысли, что я могу нажать красивую кнопочку, и оп — всё готово! 

Полноценная программа. Сложно писать, поддерживать совместимость и переносимость. На такой шаг нужно идти в крайнем случае, оценив все риски. Мы этот вариант здесь не рассмотрим, но если вы уверены — дерзайте! Кто-то же создал Vim и VS Code…

Отдельно хочется упомянуть алиасы и горячие клавиши. Пользуйтесь! Назначить их — простое действие, но это тоже шаг на пути к автоматизации. Даже такие небольшие изменения влияют на продуктивность: экономят секунды и увеличивают удовольствие от работы.

CLI тулза на Dart

Представим базовую задачу: нужно написать генератор бойлерплейта — шаблонного кода. Времени у нас немного, и мы хотим что-то простое: соответственно, выбираем консольную утилиту. Такой сценарий был в нашей практике (и не раз), поэтому для наглядности предлагаю ознакомиться с процессом создания на примере. 

Недавно мы готовили тулинг для Flutter-пакета Elementary, а поставлять его решили как исполняемый пакет на Dart. 

  • Опираемся на пакет для создания консольного приложения.

  • Добавляем «команды-ветви» и «команды-листья».

  • Реализуем необходимую функциональность. 

Создадим Dart-проект. Для простоты обработки аргументов подтянем пакет args. Создадим команду-разветвление generate и команду-лист module. Тогда синтаксис вызова будет такой: 

generate module [args]
class GenerateCommand extends Command<void> {   static const templatesUnreachable =       FileSystemException('Generator misses template files');    @override   String get description => 'Generates template files';    @override   String get name => 'generate';    @override   bool get takesArguments => false;    GenerateCommand() {     addSubcommand(GenerateModuleCommand());   } }

«Команда-лист» тоже должна переопределить description и name, но нужно ещё позаботиться о параметре argParser. В нём необходимо перечислить все аргументы, которые команда хочет получить:

  static const pathOption = 'path';   static const nameOption = 'name';   static const isSubdirNeededFlag = 'create-subdirectory';  @override   ArgParser get argParser {     return ArgParser()       ..addOption(         nameOption,         abbr: 'n',         mandatory: true,         help: 'Name of module in snake case',         valueHelp: 'my_cool_module',       )       ..addOption(         pathOption,         abbr: 'p',         defaultsTo: '.',         help: 'Path to directory where module files will be generated',         valueHelp: 'dir1/dir2',       )       ..addFlag(         isSubdirNeededFlag,         abbr: 's',         help: 'Should we generate subdirectory for module?',       );   } 

В данном случае мы явно хотим получить три параметра: 

  • название модуля, 

  • папку, куда сложить сгенерированные файлы и флажок, 

  • стоит ли для файлов создать папку с названием модуля. 

Параметр имени делаем обязательным, передавая mandatory: true. Для пути по умолчанию назначим текущую папку. Примечательно, что есть возможность задать сокращённые версии опций и флагов: например, для имени применимо –name и -n. Также автоматически соберётся справка, которую можно дополнять описаниями флагов и опций.

Ну и место, где будет выполняться работа, — метод run:

@override   Future<void> run() async {     final parsed = argResults!;     final pathRaw = parsed[pathOption] as String;     final fileNameBase = parsed[nameOption] as String;     final isSubdirNeeded = parsed[isSubdirNeededFlag] as bool; ………… }

Получаем аргументы из поля argResults, приводим к нужному типу и можем использовать по своему усмотрению! Если интересно, как усмотрели мы, — полный код доступен на github

Осталось поместить в папку bin файл с функцией main:

import 'dart:io';  import 'package:args/command_runner.dart'; import 'package:elementary_cli/generate/generate.dart';  Future<void> main(List<String> arguments) async {    const commandName = 'elementary_tools';   const commandDescription = 'CLI utilities for Elementary';      final runner = CommandRunner<void>(commandName, commandDescription)     ..addCommand(GenerateCommand());   await runner.run(arguments); } 

Теперь пакет можно опубликовать: любой пользователь сможет активировать его командой dart pub global activate и запустить с помощью dart pub global run.


Написать себе в помощь инструмент несложно. Намного сложнее качественно подсветить проблему, чтобы решение было полезным. А если скрипт уже пользуется популярностью, стоит задуматься о его интеграции в рабочий процесс.

Это была первая статья из цикла про тулинг. В следующей части разберём процесс создания плагина для VS Code, обсудим нюансы и посмотрим, можно ли подружить IDE с консольной утилитой.


ссылка на оригинал статьи https://habr.com/ru/company/surfstudio/blog/670244/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *