Перейти к основному содержимому

Справочник по CLI

Берёт .tdc-конфиг, генерирует данные и записывает результат в файл или stdout — без единой строчки кода.

tdcv2 <input.tdc> [options]
Откуда берётся tdcv2

npm install -D tdcv2, pip install tdcv2 и cargo install tdcv2 кладут команду tdcv2 в PATH из того же пакета, что несёт библиотеку. В Maven и NuGet аналога npm-ного bin нет, поэтому у Java и C# командная строка — отдельный артефакт; вкладка для каждого языка есть на странице Установка. Алиас делает все команды на этой странице одинаковыми. Всё, что ниже, работает одинаково в любой реализации.

Кроме генерации у CLI есть команды tdcv2 init и tdcv2 pack для настройки и данных — см. Установку пакетов данных — а также tdcv2 check (ниже) и tdcv2 format (ниже).

Опции

ОпцияЧто делает
-o, --output <path>Пишет результат в файл. Без этой опции выводит в stdout. Путь, заканчивающийся на .parquet, включает писателя Parquet — другого способа получить его нет
--seed <seed>Переопределяет seed из <env>
--count <n>Переопределяет count из <env> — неотрицательное целое
--locale <loc>Переопределяет локаль (по умолчанию en)
--now <date>Фиксирует часы, по которым читают today, now и b_day
--data-path <dir>Добавляет папку данных для @data/… (можно повторять)
--jobs <n>Число рабочих потоков, положительное целое (по умолчанию TDC решает сам)
--mode <memory|disk>Движок: disk (по умолчанию) или memory
--engine <1|2|3>Форсировать конкретный движок (для продвинутых)
--diskТо же, что --mode disk — и так по умолчанию
--progressПишет <output>.progress — небольшой JSON-файл состояния (нужен -o)
--streamЛегаси-алиас для --engine 2
-h, --helpПоказывает помощь
-v, --versionПоказывает версию

Длинные опции можно писать и через =: tdcv2 demo.tdc --output=out.csv --count=100.

В примерах ниже используется вот такой demo.tdc:

<tdc>
<env count="10" seed="demo" local="en">
<sequence name="Id"><gen type="increment" value="1"/></sequence>
<sequence name="City"><gen type="text" value="Moscow,Berlin,Paris" order="sequential"/></sequence>
<sequence name="Status"><gen type="text" value="new,active,closed"/></sequence>
<before><line><data>Id,City,Status</data></line></before>
</env>
<block><line><data>${{Id}},${{City}},${{Status}}</data></line></block>
</tdc>
./run demo.tdc
Id,City,Status
1,Moscow,closed
2,Berlin,new
3,Paris,closed
4,Moscow,new
5,Berlin,closed
6,Paris,new
7,Moscow,new
8,Berlin,active
9,Paris,active
10,Moscow,active

--seed — переопределить случайность

В конфиг зашит один сид, а вам нужен другой набор значений — не трогая файл. --seed переопределяет его: столбцы со счётчиком (Id) и с перебором по кругу (City) от сида не зависят, поэтому меняется только Status.

--count — сколько строк

--count 4 отрисует четыре строки. Позиционные столбцы (счётчик, текст по кругу) — это префикс; столбцы с точными долями (percent, <mix>) и с уникальностью (uniq) пересчитываются от нового общего количества. См. Детерминизм и пропорции.

--output — записать в файл

-o (или --output) пишет результат в файл; в stdout при этом ничего не выводится:

tdcv2 demo.tdc -o out.csv

--locale — язык шаблонных данных

Шаблонные генераторы (имена, города) по умолчанию выдают английские данные; --locale ru переключает весь файл на русский, позиция в позицию.

--now — зафиксировать часы

Часть генераторов смотрит на часы: value="today", value="now", person.b_day (возрастное окно, отсчитанное назад от сегодня) и генератор date, которому не задали границ. Так и задумано — день рождения, который едет вслед за сегодняшним днём, в этом и смысл. Но часы становятся входом прогона наравне с конфигом и сидом — и единственным входом, который нельзя записать. Тот же файл с тем же сидом завтра выдаст другие строки.

--now записывает их:

tdcv2 people.tdc --seed demo --now 2026-04-23 -o out.csv

Запустите это через год — получите те же байты. Уберите флаг — и прогон возьмёт реальные часы, что и нужно в продакшене и не нужно в тесте.

Значение — дата в том же синтаксисе, который принимает <gen type="date" value="…">: 2026-04-23 или 2026-04-23T09:30:00, когда важен час. Часового пояса нет: все даты в TDC — UTC. Значение, которое TDC прочитать не может, — это ошибка, а не молчаливый возврат к реальным часам:

tdcv2: invalid --now "yesterday" — expected YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss (UTC)

--data-path — внешние данные

Когда конфиг читает src="@data/…", CLI нужно знать, где лежит папка data/. Она передаётся через --data-path (опцию можно повторять — папки просматриваются по порядку):

tdcv2 demo.tdc --data-path ./data --data-path ./private-data -o out.csv

Обычный относительный путь src="names.txt" сначала ищется рядом с .tdc-файлом, затем в папках из --data-path.

Скорость и движки — --jobs, --mode, --engine

Обычно ни один из этих флагов не нужен: TDC сам выбирает движок по конфигу и сам решает, распараллеливать ли генерацию. Коротко:

  • --jobs N — задать число рабочих потоков вручную. Это только про скорость: вывод байт-в-байт совпадает с однопоточным прогоном.

  • --mode memory — маленький in-RAM движок (аварийный выход для небольших данных и объектного API). Значения он даёт те же самые, что и остальные движки (детерминированность); он просто держит каждую колонку в памяти, а не выдаёт её потоком.

  • --engine 1|2|3 — форсировать конкретный движок; --stream — легаси-алиас для --engine 2. --mode описывает стоимость, и такой прогон по-прежнему может уйти на другой движок.

    --engine 2 отказывается от всего, что не может считать потоком, — поэтому замер меряет то, что называет. --engine 3 отказывается ровно в одном случае — когда uniq теснее, чем вытягивает его ограниченная починка, — а в остальных откатывается на движок в памяти и печатает его байты: код 0 и ни слова. Это сужено намеренно: формы, на которых движок 3 откатывается, — ровно те, которых ленивый путь вообще не выражает, и покрывать их и есть его работа. Но значит и вот что: замер памяти с --engine 3 на stat, running-сумме или простом uniq — это замер движка 1. Какой движок запустит ваш конфиг перечисляет формы.

TDC сам прикидывает, сколько потоков поместится в оперативку этой машины, и берёт столько — на слабой машине запуск просто пойдёт медленнее, но не упадёт на середине. Подробнее — в разделе Большие объёмы.

--progress — наблюдать за долгим прогоном

Прогон на сто миллионов строк долго молчит, а молчание выглядит ровно как зависание. --progress пишет рядом с выводом небольшой JSON-файл — <output>.progress — и переписывает его примерно раз в секунду:

tdcv2 demo.tdc -o out.csv --progress
{
"phase": "render",
"done": 4200000,
"total": 10000000,
"percent": 42,
"startedAt": 1787871050458,
"updatedAt": 1787871083822,
"pid": 51234
}

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

Каждое обновление пишет <output>.progress.tmp и переименовывает поверх <output>.progress, так что читатель никогда не застанет наполовину записанный файл. .tmp виден рядом с выводом, пока идёт прогон. tdcv2 format -w делает так же с <file>.tmp.

Первая запись — {"phase":"starting"}, ещё до того, как у работы появится хоть одно число. Она нужна, чтобы файл СУЩЕСТВОВАЛ с первого мгновения: тот, кто файла не нашёл, не отличит «ещё не начали» от «умерло», а запуск десятка воркеров на большом конфиге занимает секунды.

Дальше фазы — в этом порядке, когда случаются: uniq-scan (хешируется набор значений каждой строки), uniq-sort (сортируются кучи), uniq-repair (проверяются и переставляются повторившиеся наборы) и render (пишутся строки).

Какие из них сообщит прогон — зависит от движка и заранее не известно. Замерено на одном конфиге с <uniq>: движок в памяти сообщает только render; потоковый на 300 000 строк — uniq-repair, затем render; тот же конфиг на 1 500 000 строк, где прогон разбивается по воркерам, — все четыре. И план не закреплён даже после старта: потоковый движок может встретить конфиг, который ему не выразить, сдаться на полпути и отдать весь прогон движку в памяти, а тот сообщит render и больше ничего.

Поэтому в файле нет ЧИСЛА фаз. «Фаза 2 из 4», объявленная в начале, была бы числом, до которого этот прогон может и не дойти, а полоса, построенная на нём, прыгала бы — чего полосе делать нельзя. Рисуйте фазу и её собственные числа: они верны всегда.

У uniq-repair на параллельном прогоне нет done/total — расстановка там считается одним вызовом, а не шагами, которые можно пересчитать, — поэтому сообщается одна фаза. На большом uniq-прогоне ни одна из них не перевешивает: на 6 000 000 строк по 900 000 000 возможных пар запись строк заняла 17 секунд, хеширование каждого набора — 12, сортировка куч — 3, а ремонт — 7, всего около 40.

Внутри фазы числа только растут, и фаза заканчивается на своём же итоге — поэтому полоса, нарисованная по ним, не прыгает назад и не замирает, не дойдя до конца. uniq-repair — это несколько разных по смыслу шагов на одной растущей шкале, поэтому её итог — это то, сколько работы ремонт набрал на себя к этому моменту, а не заранее известное число.

Последняя запись — {"phase":"done","percent":100,...} с числом секунд, которое занял прогон.

Опрашивать файл безопасно по двум причинам. Он заменяется атомарно, поэтому читающий никогда не увидит половину JSON. И он переписывается не реже раза в секунду — то же состояние заново, со свежим updatedAt, — независимо от того, есть ли работе что сказать. Поэтому файл, который не двигается несколько минут, означает, что процесс умер, что бы ни было написано внутри.

Этот пульс — по мере сил, и об этом стоит знать точно. Таймер живёт в том же процессе, поэтому непрерывный кусок вычислений может его придержать: на прогоне в 1 500 000 строк с <uniq> самая долгая тишина составила 10.9 секунды, во время ремонта. До того, как таймер появился, такой же прогон молчал 2 минуты 16 секунд, будучи совершенно здоровым, — этого хватило бы, чтобы читатель этой страницы объявил его мёртвым. Судить о жизни по минутам, а не по секундам, — и правило работает.

Нужен -o: файл состояния лежит рядом с выводом, а без вывода его некуда положить — и команда говорит об этом, а не принимает флаг и молча его теряет.

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

Вывод в Parquet тоже отчитывается — раз на группу строк, то есть раз на пятьдесят тысяч. Грубее, чем в текстовом пути, и намеренно: группа строк — это единица, которой работает тот писатель, и внутри неё нет момента, где незаконченная группа что-то значит. Если процент дойдёт до конца и начнётся заново — значит прогон считается дважды; это стоит завести как ошибку, а не терпеть.

Тот же канал есть в библиотеке во всех реализациях — как обратный вызов с аргументами (phase, done, total).

tdcv2 check

Читает конфиг, проверяет его и ничего не генерирует. То, что нужно в pre-commit-хуке или в CI: отвечает на вопрос «а запустится ли это?», не тратя времени на сам запуск.

tdcv2 check demo.tdc

Всё уходит в stderr — на верный конфиг одна строка, на неверный те же диагностики, что напечатала бы обычная генерация. В stdout не уходит ничего, и это намеренно: stdout хука — это шум, а тот, кому нужны данные, запускает генератор.

tdcv2 check demo.tdc
tdcv2: demo.tdc is valid

Предупреждения проверку не заваливают — они печатаются, а код выхода остаётся 0, потому что предупреждение описывает то, что работает, но, скорее всего, задумывалось иначе. Кодом 1 завершается только ошибка.

--brief — единственный флаг у check. Он печатает по одной строке на диагностику (код, позиция, сообщение, подсказка) без выдержки из исходника — для редакторов, CI и всего остального, что вывод читает, а не разглядывает:

tdcv2 check --brief demo.tdc
TDC041 1:70 unknown gen type "nosuch" :: Allowed types: text, file, template, number, regex, advanced_regex, … (11 more).

tdcv2 format

Приводит .tdc в аккуратный вид — отступы, пробелы в атрибутах, выровненные таблицы <map> — тот же форматтер, что и в редакторе.

tdcv2 format demo.tdc # печатает отформатированный конфиг в stdout
tdcv2 format -w demo.tdc # переписывает файл на месте (-w / --write)

Форматирование никогда не меняет то, что генерирует конфиг. Синтаксическая ошибка будет показана, а файл останется нетронутым (код выхода 1).

Коды выхода

КодЗначение
0Успешная генерация, --help или --version
1Ошибка чтения, парсинга, валидации или выполнения
2Неправильные аргументы CLI — а также любой сбой pack или init (скачивание, контрольная сумма, уже существующий конфиг)

См. также