Русский
Jsonnet в trip2g
Три возможности trip2g принимают не готовую настройку, а короткую программу: frontmatter-патч решает, какие поля добавить, валидатор — что не так с заметкой, преобразование вебхука — что отправить. Такие программы пишутся на Jsonnet — небольшом языке, который выдаёт JSON. На этой странице — та часть Jsonnet, которая нужна для trip2g, и места, где trip2g его запускает.
Что такое Jsonnet
Любой JSON — уже правильный Jsonnet. Поверх JSON в нём есть переменные, условия, функции и циклы по спискам, а результат программы — всегда JSON: объект, список, строка или число. Побочных эффектов у Jsonnet нет. Программа не меняет файлы, не ходит в сеть и не читает часы, она только вычисляет значение.
trip2g запускает каждую программу в песочнице: import запрещён, а рекурсия глубже 500 вызовов останавливается с ошибкой, а не зависает.
Где trip2g его использует
| Возможность | Что получает программа | Что возвращает |
|---|---|---|
| Frontmatter-патчи | meta (frontmatter заметки), path |
объект, поля которого сливаются с frontmatter |
| Валидаторы заметок | meta (после патчей), path, nodes, std.native('regexMatch') |
список сообщений {level, msg, line?} |
| Преобразование тела вебхука | std.extVar('change'), std.extVar('attached_notes') — JSON-строками |
тело запроса, которое уйдёт вместо стандартного |
В патчах и валидаторах программа лежит в блоке ```jsonnet внутри обычной заметки. Преобразование вебхука — это поле transformJsonnet у change или cron webhook в GraphQL API. Оно заменяет всё тело запроса и не сочетается с Pass API key. attached_notes есть, только если вебхук прикладывает заметки; чтение отсутствующей переменной останавливает программу с ошибкой Undefined external variable.
Синтаксис за десять минут
Значения и комментарии
// Комментарий. Обычный JSON плюс запятые в конце и ключи без кавычек.
{
title: 'Hello',
"tags": ['a', 'b'],
draft: false,
views: 3,
}
// → {"draft": false, "tags": ["a", "b"], "title": "Hello", "views": 3}
Строки пишутся в одинарных или двойных кавычках. Ключам кавычки не нужны, если это простые слова.
Переменные
local даёт значению имя. Объявление заканчивается ;, и имя видно во всём, что идёт дальше:
local site = 'trip2g';
local year = 2026;
{ footer: site + ' © ' + year }
// → {"footer": "trip2g © 2026"}
+ склеивает строки, складывает числа, объединяет списки и сливает объекты. Число, прибавленное к строке, становится текстом.
Поля объекта
local meta = { title: 'Post', tags: ['go'] };
{
title: meta.title,
hasTags: std.objectHas(meta, 'tags'),
author: std.get(meta, 'author', 'unknown'),
}
// → {"author": "unknown", "hasTags": true, "title": "Post"}
meta.author падает, если поля нет. std.get(meta, 'author', default) вместо этого возвращает значение по умолчанию, а std.objectHas проверяет, есть ли поле. В патчах и валидаторах берите эти две функции для полей, которых у заметки может не быть.
Условия
local meta = { status: 'draft' };
{
visible: if meta.status == 'published' then true else false,
label: if meta.status == 'draft' then 'Draft' else '',
}
// → {"label": "Draft", "visible": false}
if — это выражение со значением, как тернарный оператор. Всегда пишите else: без него ложное условие даёт null. Валидатор, который вернул null вместо списка, получает сообщение CRITICAL.
Списки и генераторы
local tags = ['go', 'Draft', 'web'];
{
lower: [std.asciiLower(t) for t in tags],
noDrafts: [t for t in tags if t != 'Draft'],
count: std.length(tags),
}
// → {"count": 3, "lower": ["go", "draft", "web"], "noDrafts": ["go", "web"]}
[выражение for x in список if условие] строит новый список. Валидаторы в основном так и пишутся: одно сообщение на каждый элемент, который нарушил правило.
Текст
local name = 'Notes – 2026-10-18 – Cursor';
local parts = std.split(name, ' – ');
{
parts: parts,
msg: 'date %s, tool %s' % [parts[1], parts[2]],
isNotes: std.startsWith(name, 'Notes'),
}
// → {"isNotes": true, "msg": "date 2026-10-18, tool Cursor",
// "parts": ["Notes", "2026-10-18", "Cursor"]}
'…%s…' % значение подставляет значения, как printf: %s — для текста, %d — для целого числа. Если значений несколько, передайте список.
Функции
local err(msg) = { level: 'error', msg: msg };
local missing(meta, f) = !std.objectHas(meta, f);
local meta = { title: 'Post' };
[err('missing ' + f) for f in ['title', 'description'] if missing(meta, f)]
// → [{"level": "error", "msg": "missing description"}]
Функция — это local с параметрами. Маленькие помощники вроде err делают валидатор короче.
Обход списка с состоянием
Когда каждый элемент зависит от предыдущих, например «в каком разделе эта картинка», нужен std.foldl. Он проходит по списку и несёт с собой накопитель:
local nodes = [
{ type: 'heading', text: 'Intro' },
{ type: 'image' },
{ type: 'heading', text: 'Steps' },
];
std.foldl(
function(acc, n)
if n.type == 'heading' then acc + [{ title: n.text, images: 0 }]
else acc[:std.length(acc) - 1]
+ [acc[std.length(acc) - 1] + { images: super.images + 1 }],
nodes,
[],
)
// → [{"images": 1, "title": "Intro"}, {"images": 0, "title": "Steps"}]
acc[:n] — срез: все разделы, кроме последнего. obj + { images: super.images + 1 } копирует объект с одним изменённым полем.
Функции, которые нужны чаще всего
| Функция | Что делает |
|---|---|
std.length(x) |
длина списка, строки или объекта |
std.objectHas(obj, 'f'), std.get(obj, 'f', default) |
есть ли поле; поле или значение по умолчанию |
std.isString(x), std.isArray(x), std.isNumber(x) |
тип значения |
std.member(list, x) |
есть ли x в списке |
std.startsWith(s, p), std.endsWith(s, p) |
начинается или заканчивается ли текст на p |
std.split(s, sep), std.join(sep, list) |
разбить текст на список; собрать список в текст |
std.asciiLower(s), std.asciiUpper(s) |
сменить регистр (латиница) |
std.substr(s, from, len) |
часть текста |
std.toString(x) |
любое значение как текст |
std.foldl(f, list, init) |
обход списка с накопителем |
std.parseJson(s) |
превратить JSON-строку в значение |
Своих регулярных выражений в Jsonnet нет. В валидаторах trip2g добавляет std.native('regexMatch')(pattern, text). Обратные слэши в шаблоне удваиваются: '^\\d{4}$'.
Частые ошибки
ifбезelseдаётnull, если условие ложно.- Отсутствующее поле, прочитанное как
meta.x, останавливает программу с ошибкойField does not exist: x. Используйтеstd.get. localбез;. Каждыйlocalзаканчивается;(или,, если несколько объявлены подряд).- Одинарный обратный слэш в шаблоне.
'\d'— недопустимая последовательность, пишите'\\d'.
Где почитать больше
Полный справочник языка и онлайн-песочница — на jsonnet.org: учебник и стандартная библиотека. Чтобы проверить программу на своих заметках, оформите её как валидатор и запустите trip2g lint на папке: см. Линтер для хранилища.