@vvscode
CLI для кодогенерации своими руками
PHP
Работал руками
Что-то не так ?
PHP base cli
Yii / Symfony
- Создание новых структур
- Работа с базой и миграциями
- Работа с переводами
- Работа с assets
- Запуск сервера
- ...
Frontend
Работать руками
Ember
ember-cli
- build / dev server
- Генерация моделей с полями и отношениями
- Генерация компонентов
- Генерация роутов с параметрами
- Генерация тестов
- Собственные blueprints
Иметь cli "модно"
- упрощает разработку
- снижает порог вхождения
- работает с четкой структурой
Что нужно от генератора кода?
Генерировать код
Сколько вешать ?
- Проект
- Функция/класс
- Компонент
cli генератор кода
- консольный интерфейс
- принимает параметры
- генерирует код
- умеет шаблоны
- умеет справку
- что душе угодно
Консольный интерфейс
{
"name": "acli",
"version": "0.0.1",
"bin": {
"acli": "./scripts/acli.js"
}
}
#!/usr/bin/env node
chmod +x scripts/acli.js
package.json:
добавить shebang в scripts/acli.js
сделать файл исполняемым
На что стоит обратить внимание
// package.json
{
// ...
"engines": {
"node": ">=9.11.1 <10.0",
"yarn": ">=1.6.0"
},
// ...
}
# during development
npm link
Ограничение версий
Упрощение разработки
Параметры командной строки
scripts/acli.js
#!/usr/bin/env node
console.log(process.argv);
acli --xxx -yyy 123
Run with params:
[
'/usr/local/bin/node',
'/usr/local/bin/acli',
'--xxx',
'-yyy',
'123'
]
Помощь по использованию
~ $ ls --help
ls: illegal option -- -
usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...]
~ $ acli
Usage: acli [options] [command]
Awesome cli tool 0.0.28
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
init|i [options] Initialize new project based on predefined template at Gitlabs's /templates subgroup
generate|g [options] Generate code from template
config|c [options] Configures cli tool
И дальше вот это все
- os / path / fs
- util
- readline
По-старинке
Валидация параметров
# Ковыряния в работе терминала
console.log('\x1b[36m%s\x1b[0m', 'I am cyan');
console.log('\x1b[33m%s\x1b[0m', 'I am yellow');
Вывод справки и сообщений об ошибках
Это не наш метод
Подбираем пакеты
Парсинг параметров
#!/usr/bin/env node
const program = require('commander');
const projectPackage = require('../package.json');
program
.version(`${projectPackage.version}`)
.description(`Awesome cli tool ${projectPackage.version}`);
require('./commands/init')(program);
require('./commands/generate')(program);
require('./commands/config')(program);
program
.allowUnknownOption()
.parse(process.argv);
// show help by default
if (!process.argv.slice(2).length) {
program.outputHelp();
}
scripts/acli.js
command
module.exports = program =>
program
.command('generate')
.alias('g')
.description('Generate code from template')
.option(
'-t, --template [template]',
'A template to generate new code'
)
.action(generateByTemplate);
Взаимодействие с пользователем
- Гибкая конфигурация
- Поддержка типов запросов
- текст
- пароль
- список
- чекбоксы
- подтверждения
- автоподсказка при вводе - Значения по-умолчанию
- асинхронные хуки
const generateByTemplate = options => {
// first we get list of available templates
// which stored in scripts/generator-templates
getTemplatesList()
// after that we ask user about target template
.then(templateNamesList => {
if (!templateNamesList.includes(options.template)) {
return prompt([
{
type: 'list',
name: 'templateName',
choices: templateNamesList,
default: options.template,
message: 'Choose template for a new code',
},
]).then(i => i.templateName);
}
return options.template;
})
.then(() => {
// ...
})
}
Украшения
Chalk
Chalk
Multispinner
Генерация кода
любой шаблонизатор
import React, { Component } from 'react';
export class ${camelizedComponentName} extends Component {
render() {
return <div className="${dasherizedComponentName}">
${camelizedComponentName}
</div>;
}
}
export default ${camelizedComponentName};
/*
File was generated with tamm-cli from next settings:
${JSON.stringify(_answers, null, 2)}
*/
console.log(
require('es6-template-strings')(
'Hello ${place.toUpperCase()}!',
{ place: "World" }
)
);
По-мелочи
Чего хотелось от шаблонизации?
-
гибкость и простота
- настройка под себя
-
многофайловость
3 уровня шаблонов
- На уровне cli
Шаблоны в комплекте с генератором
- На уровне проекта
Шаблоны из `.acli` директории проекта
- На уровне пользователя
Шаблоны из `~/.acli`
Структура шаблона
.
├── _blank-template
│ ├── _template-definition.js
│ └── index.js
├── client-component
│ ├── _template-definition.js
│ ├── component.spec.js
│ ├── index.connected.js
│ ├── index.js
│ ├── statefull-component.js
│ ├── stateless-component.js
│ └── styles.less
├── client-route
│ ├── Route.component.js
│ ├── Route.container.js
│ ├── Route.css
│ ├── Route.spec.js
│ ├── _template-definition.js
│ ├── index.js
│ └── index.spec.js
_template-definition.js
module.exports = {
prompts: (/* answers */) => [
{
type: 'input',
name: 'path',
message: 'Enter component path (name)',
},
],
// extendAnswers: answers => ({
// ...answers,
// random: Math.random(),
// }),
files: (/* answers */) => {
const files = {
'index.js': fs.readFileSync(`${__dirname}/index.js`),
};
return files;
},
// postGenerateMessage: (/* answers */) => `Well done`,
// pinToDirectory: (/* answers */) => 'src/client/redux',
};
files:
files(answers) {
let { camelizedComponentName, statefull, connectedToRedux } = answers;
const withRedux = connectedToRedux === 'Yes';
statefull = statefull === 'Yes';
const files = {
[`${camelizedComponentName}.js`]: readFile(
`${__dirname}/${statefull ? 'statefull' : 'stateless'}-component.js`,
),
'index.js': readFile(
`${__dirname}/index${withRedux ? '.connected' : ''}.js`,
),
};
answers.addStyles && files[`${camelizedComponentName}.less`] = readFile(
`${__dirname}/styles.less`,
);
return files;
},
Решили писать cli?
- подумайте про пользователя
- продумайте интерфейс
- напишите документацию
- обработайте ошибки
- сохраняйте данные
- подберите удобные инструменты
process.exit();
CLI для кодогенерации своими руками
By Vasiliy Vanchuk
CLI для кодогенерации своими руками
Паттерны написания cli приложений на примере реализации cli для нужд проекта.
- 1,418