diff --git a/.dockerignore b/.dockerignore index eb2c3fd..9933c86 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ .git .gitea .webarchive +local-admin deploy web/.next web/node_modules diff --git a/README.md b/README.md index 08943fd..fc6a0f1 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,20 @@ Useful commands: ```bash cd ~/tmp/progcode/web npm install +ADMIN_PORT=3333 npm run admin:posts node scripts/buildDatabase.mjs npm run dev -- --port 3000 npm run build ``` +Local posts admin: + +- run from `web/` with `npm run admin:posts`; +- open `http://127.0.0.1:3333`; +- it edits posts as Markdown and saves generated `contentHtml` to `web/data/articles.json`; +- uploaded files are stored in `web/public/assets/uploads/`; +- it lives in `local-admin/`, outside the Next.js app, and `.dockerignore` excludes it from Docker builds. + Docker static deploy: ```bash diff --git a/local-admin/posts-admin.mjs b/local-admin/posts-admin.mjs new file mode 100644 index 0000000..79b66f2 --- /dev/null +++ b/local-admin/posts-admin.mjs @@ -0,0 +1,3 @@ +import { startServer } from './src/app/startServer.mjs'; + +startServer(); diff --git a/local-admin/public/index.html b/local-admin/public/index.html new file mode 100644 index 0000000..b9d7f24 --- /dev/null +++ b/local-admin/public/index.html @@ -0,0 +1,126 @@ + + +
+ + +Только localhost
+${code}`);
+ return token;
+ });
+
+ html = html
+ .replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, (match, alt, url) => renderImage(match, alt, url))
+ .replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (match, text, url) => renderLink(match, text, url))
+ .replace(/\*\*([^*]+)\*\*/g, '$1')
+ .replace(/\*([^*]+)\*/g, '$1');
+
+ codeTokens.forEach((code, index) => {
+ html = html.replace(`@@CODE${index}@@`, code);
+ });
+
+ return html;
+}
+
+function renderImage(match, alt, url) {
+ const safeUrl = normalizeAssetUrl(url);
+ return safeUrl ? `${inlineToHtml(paragraph.join(' '))}
`); + paragraph.length = 0; + }; + + const closeList = () => { + if (!listType) { + return; + } + + html.push(`${listType}>`); + listType = null; + }; + + for (const line of lines) { + if (/^```/.test(line)) { + codeLines = toggleCodeBlock({ codeLines, flushParagraph, closeList, html }); + continue; + } + + if (codeLines) { + codeLines.push(line); + continue; + } + + if (!line.trim()) { + flushParagraph(); + closeList(); + continue; + } + + if (renderHeading({ closeList, flushParagraph, html, line })) { + continue; + } + + if (renderQuote({ closeList, flushParagraph, html, line })) { + continue; + } + + const renderedListType = renderListItem({ closeList, flushParagraph, html, line, listType }); + if (renderedListType) { + listType = renderedListType; + continue; + } + + paragraph.push(line.trim()); + } + + if (codeLines) { + html.push(`${escapeHtml(codeLines.join('\n'))}`);
+ }
+
+ flushParagraph();
+ closeList();
+
+ return html.join('\n');
+}
+
+function toggleCodeBlock({ closeList, codeLines, flushParagraph, html }) {
+ if (codeLines) {
+ html.push(`${escapeHtml(codeLines.join('\n'))}`);
+ return null;
+ }
+
+ flushParagraph();
+ closeList();
+ return [];
+}
+
+function renderHeading({ closeList, flushParagraph, html, line }) {
+ const heading = line.match(/^(#{1,3})\s+(.+)$/);
+ if (!heading) {
+ return false;
+ }
+
+ flushParagraph();
+ closeList();
+ html.push(`${inlineToHtml(quote[1])}`); + return true; +} + +function renderListItem({ closeList, flushParagraph, html, line, listType }) { + const unordered = line.match(/^[-*]\s+(.+)$/); + const ordered = line.match(/^\d+\.\s+(.+)$/); + if (!unordered && !ordered) { + return null; + } + + flushParagraph(); + const nextListType = unordered ? 'ul' : 'ol'; + + if (listType !== nextListType) { + closeList(); + html.push(`<${nextListType}>`); + } + + html.push(`
Йоаким — интервьюер-резидент блога о D. Он также брал интервью у членов D-сообщества для This Week in D и ответственен за портирование LDC для Android.
\nГеральд Нанн — разработчик Tilix (ранее называвшийся Terminix). Tilix— продвинутый тайлинговый эмулятор терминалов с открытым исходным кодом, который является самым «звёздным» проектом на основе D на GitHub, недавно даже обогнавший стандартный компилятор D, DMD. В этом году на DConf в Берлине он рассказывал о том, как использует D. Имеются слайды и видео. В своей повседневной работе, которая не имеет ничего общего с настольными графическими приложениями, он является старшим разработчиком промежуточных решений в Red Hat. [Подробнее об истории Джеральда в расширенном интервью — Ред.]
\nЙоаким: — Что такое тайлинговый эмулятор терминала?
\nГеральд: — Тайлинговый эмулятор терминала позволяет разделить терминал на несколько частей и распределить их в удобном порядке и месте, что наиболее удобно при работе над конкретной задачей. Люди, которые работают на нескольких терминалах одновременно, как правило, находят такие инструменты наиболее полезными, особенно с постоянно увеличивающимися размерами мониторов и разрешений.
\nНесмотря на то, что Tilix очень хорош, основной причиной, по которой я создал его, было то, что мне нужен был эмулятор терминала, который следовал бы концепции Gnome HIG (Human Interface Guidelines — рекомендации по созданию интуитивных, легко изучаемых и логичных интерфейсов взаимодействия с пользователем) и использовал CSD (Client-Side Decorations — отрисовку на стороне клиента). Tilix следует Gnome HIG, опубликованным здесь, что означает соблюдение интервалов, макетов и других рекомендаций. После HIG важно, чтобы приложение соответствовало всему рабочему столу в целом.
\nCSD ссылается на заголовок окна, где не диспетчер дисплея, а пользователь берёт на себя ответственность за него и может заполнять панель заголовка кнопками и другими элементами управления. Это часть концепции Gnome HIG, и большинство приложений Gnome (gedit, файлы, видео) по умолчанию используют этот подход. Единственное исключением является gnome-terminal, который вообще не использует CSD.
\nЙ.: — Можете привести несколько примеров того, как вы используете концепцию Gnome HIG?
\nГ.: — Gnome HIG определяет конкретный язык дизайна в отношении того, как приложения, работающие в Gnome, должны выглядеть и показывать себя. Некоторые примеры Tilix, следующие концепции HIG, включают использование CSD и меню приложений по рекомендациям, таким как интервалы, макеты и т.д. Кроме того, разработчики Gnome собрали множество макетов, как они думают, как должны выглядеть различные приложения. Tilix использует макеты, разработанные дизайнерами Gnome для терминала, где это возможно. Например, в Tilix диалог предпочтений и профилей использовался отдельно, однако один из дизайнеров Gnome предложил этот макет для gnome-терминала. Я пошел вперед и реализовал его в Tilix, гораздо лучше, чем раньше.
\nВ результате использования CSD и концепции Gnome HIG, надеюсь, что использование Tilix в Gnome более органичено для пользователей.
\nИнтересная вещь — это напряженность между людьми, которые используют Tilix на Gnome и тех, кто использует его в других дистрибутивах. Хотя я не имею никаких сомнений в том, что разработка под Gnome является моей основной целью, я стараюсь сделать Tilix лучше и в других средах рабочего стола, разрешив пользователю отключить CSD в пользу обычного заголовка, если они того пожелают.
\nЙ.: — Вы попали в D из среды Java. Вы все еще пишете код в стиле Java на D? Это было легко, т.е. сколько вам пришлось привыкать, чтобы писать на D?
\nГ.: — Да, чаще всего работаю с Java. Я нашёл это довольно интересным на DConf, когда я спросил, как много людей пришли не из среды C/C++ , только один человек поднял руку.
\nЕсли вы посмотрите на мой код в Tilix, то он очень похож на Java-код. Некоторое из этого связано с моей обычное средой, в которой я работаю, а некоторое из-за того, что GtkD является оболочкой классов.
\nЯ считаю, что переключение между D и Java является довольно плавным по большей части; между ними гораздо меньше когнитивного трения, чем, скажем, между переключением между Java и Python.
\nСамая важная идиома D, которую мне пришлось изучить, — это диапазоны, поскольку они являются основополагающей особенностью D. Однако это не было сильно сложным. Выполнение функций времени компиляции (CTFE) по-прежнему остаются для меня неестественными. Когда я использую их, мне приходится каждый раз искать про них информацию, и мои текущие попытки использования CTFE с точки зрения кода довольны глупы. Я хотел бы больше использовать CTFE в Tilix, поскольку я получаю всё больший опыт использования D.
\nНаконец, мой недостаток опыта работы с C выявляет другую проблему, с которой я немного борюсь — это взаимодействие с кодом на C. Хотя по большей части это довольно просто, но, когда мне приходится расшифровывать что-то сложное, оно становится не таким простым. Поддержка FlatPak (система изолированных контейнеров для графических приложений) в настоящее время есть, так как мне не приходилось так сильно напрягаться по некоторым вопросам, с которыми я сталкивался на C.
\nСказав это, у меня есть некоторый опыт разработки собственного кода, поскольку много лет назад я потратил много времени на разработку кода на Delphi и Object Pascal. Именно на это и приходится большая часть моего опыта работы с графическим интерфейсом.
\nЙ.: — И из вашего выступления на DConf вы явное не беспокоились о сборщике мусора (GC). Вам приходилось думать о нём, когда вы разрабатывали Tilix? Имелись ли проблемы задержек с GUI, вызванные GC?
\nГ.: — Придя из Java, GC для меня довольно естественен, и я определенно не считаю его плохим для D. Я думаю, что GC в D получает много плохой прессы на основе опыта Java, но важно помнить, что GC в D сильно отличается от того, что в Java. Самое большое различие для меня в том, что в D есть больше возможностей для его управления, так как он хорошо понимает, когда он может начать цикл GC. Я был очень рад видеть, как больше людей поднимают различные темы на reddit и форумах, жалуются на использование GC.
\nУ меня не было никаких проблем с GC с точки зрения пауз, и ни один пользователь Tilix не сообщал об этом. У меня было несколько проблем, связанных с GC, в основном связанных с утечкой памяти из-за хранения ссылок, но все они были ошибками программного кода, а не проблемой с реализацией GC в D. У меня есть другое приложение на GTK D, Visual Grep, где я столкнулся с ужасающей эффективностью при обработке большого количества совпадений в tight-цикле (цикл, который содержит несколько инструкций и повторяется много раз.). Тем не менее, просто отключив GC для этого раздела кода, ускорилось все.
\nЙ.: — Репозиторий github для Tilix замечательно чист, нет открытых запросов «pull request» (PR) и низкий процент проблем, которые все еще открыты. Сколько времени вы еженедельно тратите на Tilix? Является ли Tilix только хобби или он стал чем-то большим?
\nГ.: — Tilix — это просто хобби. Я, вероятно, провожу от 5 до 10 часов в неделю. На данный момент — это зрелое приложение, следовательно, относительно небольшое количество проблем. Я также уделяю приоритетное внимание исправлению ошибок при добавлении новых функций, что помогает сохранить список управляемым.
\nЧто касается запросов на «pull request» (PR), я твердо убежден в том, что я должен быть отзывчивым, поэтому я, как правило, отвечаю на PR в течение дня или двух. В качестве участника разработок я знаю, что ничего не убивает интерес, как видеть, что ваш PR томится в течение нескольких недель, месяцев или даже лет. Если вы хотите, чтобы люди вносили свой вклад, что я определенно делаю, то я чувствую, что вы обязаны участникам разработки своевременно реагировать на их PR.
\nНа данный момент Tiltx — это относительно небольшой проект, поэтому мне легко принять этот подход. Я понимаю, почему более крупные проекты могут иметь больше проблем в этой области.
\nЙ.: — D имеет много особенностей, насколько хорошо вы их знаете? Вы упомянули, что хотите использовать некоторые из возможностей времени компиляции. Как вы думаете за счёт каких особенностей D и что Tilix выиграет в будущем и как?
\nГ.: — Я не думаю, что знаю это, если честно, помимо основного набора особенностей, которые я использую в Tilix. Я всегда удивляюсь ребятам на форуме, которые могут утверждать достоинства/недостатки низкоуровневых деталей языка. Это определенно не я. Я бы хотел поправиться, но на самом деле я использую D только как хобби, поэтому я не могу тратить столько же времени, сколько и на Java. Кроме того, моя работа в Red Hat имеет больше компонентов инфраструктуры, чем мои предыдущие рабочие места, поэтому большая часть моего времени расходуется дома на обучение в этом направлении.
\nЧто касается возможностей, от которых Tilix выиграет, я думаю, что использование CTFE и диапазонов было бы очень полезно для того, чтобы улучшить некоторые моменты в GtkD более идиоматичным способом. У меня есть достаточное количество кода, где он может быть намного более кратким при соответствующем использовании CTFE. В качестве простого примера можно использовать диапазоны для поддержки итераций по различным артефактам с использованием foreach, а не классическим циклом. Тем не менее, я думаю, что некоторые из более сложных вариантов использования, таких как поддержка D-Bus (система межпроцессного взаимодействия, которая позволяет приложениям в операционной системе сообщаться друг с другом) и GObject, будут очень полезны.
\nДля тех, кто не знаком с GObject — это объект базового уровня в GTK и является счётчиком ссылок. Возможность легко создавать GObjects в D, как можно и в Python, упростит взаимодействие с некоторыми из API; прямо сейчас это эквивалентно написанию его в сыром C, и это несколько трудоемко. Майк Вей, сопровождающий GtkD, начал делать некоторые работы над этим.
\nЙ.: — Вы упомянули на DConf, что D имеет быстрый цикл компиляции: как вы используете это, то есть какие IDE, компиляторы, toolchain (набор программ, необходимых для создания других программ) вы используете как для разработки, так и для выпуска релизов?
\nГ.: — Я использую MS Visual Studio Code на Linux с отличным плагином code-d, написанным Jan «WebFreak» Jurzitza. Это дает мне все необходимые функции (автозаполнение кода, подсказки, рекомендации и т. Д.). Единственное, чего я не вижу, что есть в Java IDE, — это возможность рефакторинга. Для разработки я использую DMD, поскольку он имеет самое быстрое время компиляции. Сборку версий выполняю с использованием LDC, компилятора D с бэкэном LLVM, поскольку он генерирует меньшие и более быстрые двоичные файлы. Мне редко приходится запускать отладчик, но, когда я это делаю, я просто использую GDB из командной строки.
\nЙ.: — Какие проблемы у вас были с D? Какие его особенности вам не нравятся?
\nГ.: — Никаких серьёзных проблем с моей точки зрения. Я в целом очень доволен этим языком и считаю, что он несёт правильный баланс между простотой использования и возможностями. Наибольшее неудобство мне доставляла стандартная библиотека Phobos, а не сам язык, и эти неудобства напрямую соотносятся с нехваткой времени.
\nНикаких серьёзных проблем с Phobos, а скорее кучей раздражителей. Например, нельзя легко использовать immutable для отправки в std.concurrency, std.experimental.logger являющиеся все ещё экспериментальными, парсер json имеет проблемы, если в локализации установлена запятая для отделения десятичных знаков чисел и т.д. и т.п. Ни один из данных недочётов по себе не является особо важным, и большинство из скорее всего связаны из-за нехватки трудовых ресурсов. Я на самом деле несколько неохотно жалуюсь на них, потому что я, наверное, мог исправить их сам и отправить PR.
\nМеня больше раздражает негативность на форумах в отношении GC. Я чувствую, что иногда люди так поворачиваются в сторону D, что хотят видеть его идеальным системным языком (т.е. без GC, без безопасности памяти и т.д.), упуская из виду, что он очень хороший язык для создания приложений на данный момент. Пока D сравнивают с Rust, в некотором смысле сравнение с Go для меня более интересно. Оба языка основаны на GC и оба начинали как системные языки, однако Go опирается на GC и «удваивается» (от переводчика: скорее всего имеется в виду рост популярности), добиваясь успеха. Один из продуктов Red Hat, который я поддерживаю, OpenShift, использует Kubernetes (проект Google) для управления кластером контейнеров Linux как единой системой, и он написан на Go.
\nЯ думаю, что как язык D намного превосходит Go, и мне хотелось, что мы бы заявляли об этом громче вместо постоянного отрицательного обсуждения системного программирования. На данный момент надо сказать ради справедливости, что Go имеет крупного корпоративного спонсора, в отличие от D, однако контраст в позиционировании по-прежнему интересен мне.
\nЙ.: — Каковы ваши будущие планы в отношении Tilix?
\nГ.: — Две самые большие функции, которые я хотел бы добавить, это поддержка режима управления tmux и добавление возможности отображения боковой панели popout.
\nДля тех, кто не знаком с tmux, это терминальный мультиплексор; это, по сути, терминальный разделитель, но внутри самого терминала. Он также поддерживает ряд других функций, но наиболее интересным является сохранение терминальных сеансов, находящихся вне терминала. Поскольку он работает в терминале, он немного ухудшает производительность, а с точки зрения графического интерфейса он не может использовать собственные виджеты, такие как полосы прокрутки. Чтобы смягчить это, он поддерживает так называемый режим управления, который позволяет ему интегрироваться с эмулятором тайлингово терминала для создания новых терминалов, т.е. подтерминалов внутри одного терминала, управляющихся за пределами tmux. Это значительно улучшает его производительность, позволяя пользователям использовать другие функции, поддерживаемые tmux. На данный момент поддерживается только iterm2 на OSX насколько мне известно.
\nБоковая панель в Tilix является одним из наиболее противоречивых элементов интерфейса. Я решил не реализовывать интерфейс с вкладками, потому что я счел их бесполезными с точки зрения поиска открытия необходимой вкладки; там просто недостаточно места для вкладок, чтобы отличить их, и переименовывать их вручную — это «боль». Боковая панель — это моя попытка альтернативы, она отображает миниатюру каждого сеанса (известную также как ярлык) на боковой панели, которая может быть убрана по мере необходимости для переключения между сеансами.
\n
\nБоковая панель Tilix в действии.
У некоторых людей есть сильное предпочтение к тому, что постоянно доступно, но, к сожалению, все, что видно на боковой панели, непросто, так как генерация эскизов занимает значительное количество времени из-за структуры GTK. Существуют потенциальные способы заставить это работать быстрее, но требуется время на то, чтобы попробовать разные варианты, чтобы увидеть, что как отображается, а затем, что наиболее эффективно.
\nВ мечтах, я бы хотел переключиться на использование эмулятора терминала, написанного изначально в D, а не GTK VTE (Virtual Terminal Emulator), который написан на C, и который я использую сейчас. Для тех, кто не знаком с ним, VTE — это виджет эмуляции терминала, используемый терминалом Gnome и доступен в виде многоразового виджета. Многие эмуляторы терминала в Linux используют этот виджет (gnome-terminal, guake, terminator, tilix и т.д.), поскольку он обеспечивает полностью готовый к работке эмулятор, который прошел через огромное количество тестов.
\nНедостатком этого является то, что любые пользовательские функции, которые вы хотите реализовать, которые включают фактический уровень эмуляции терминала, требуют модификации VTE и получения этих изменений вверх по потоку (*upstream). У меня есть несколько патчей, которые Tilix поддерживает (для триггеров и значков), но, честно говоря, я провёл плохую работу по внедрению вверх по потоку (*upstream). Частично это связано с тем, что VTE написано на C и сделать быстрый и качественный патч на C занимает достаточно много времени.
\nТаким образом, наличие эмуляции терминала, написанного на D, сделало бы это намного проще, однако это огромные инвестиции времени, поскольку люди недооценивают объем работы. В эмуляции терминала много сложных случаев, плюс добавление всего материала, чтобы сделать его удобным для пользователей (поиск, клики по ссылкам и т.д.), это много, чтобы взять на себя. У меня просто нет времени, чтобы сделать это реальностью, если только я не выиграю в лотерею. Если кто-то захочет взять на себя это роль и создать виджет эмуляции терминала GTK, который имеет все необходимые функции и поддерживал бы его в течение длительного времени, я был бы рад работать с ним, чтобы интегрировать его с Tilix. Адам Рапп уже создал один, который работает достаточно хорошо по моим тестам; если кто-то хочет работать над преобразованием его в виджет GTK, добавит необходимые улучшения и согласится его поддерживать, то пусть не стесняется писать и надоедать сообщениями (*ping) мне. ?
\nЙ.: — Пожалуйста, расскажите из своего опыта, как вы впервые обнаружили D и использовали его для написания Tilix?
\nГ.: — На протяжении всей моей карьеры я всегда имел хобби. Некоторые вещи, над которыми я работал, включали популярную надстройку для Delphi под названием Gexperts, замену проводника файлов Windows, Java IDE под названием Gel и популярное приложение для Android под названием OnTrack Diabetes, которое я продал несколько лет назад. Пару лет назад я искал что-то новое для работы в качестве моей программы в качества хобби и остановился на идее создания десктопного приложения для Linux. Я знал, что нужно использовать инструментарий GTK, так как Gnome — моя предпочтительная среда рабочего стола, и особенно с моим прошлым опытом Delphi в графических интерфейсах, которые я мог бы использовать. Я также знал, что меня не интересует разработка на C или C ++, поэтому я посмотрел, какие альтернативы были.
\nЯ начал с Python, так как у него отличная поддержка GTK, и я немного поработал над программированием Jython в WebLogic, так как я использовал Weblogic Scripting Tool (WLST). Однако большая часть моей предыдущей работы была небольшими сценариями, и я быстро понял, что в больших масштабах Python не для меня. Я вообще предпочитаю статически типизированные языки, и динамический набор текста на Python приводил меня в замешательство, особенно в качестве хобби, где я постоянно нуждался в использование справочных материалов, так как я работал на нем нечасто.
\nЯ также смотрел в сторону Rust и Go, но в то время ни у одного из них не было полнофункциональных GTK-привязок. Кроме того, в то время у Rust было много позитивной прессы, но у него был плохой материал для убеждения в правильности его подхода, и я был не так сильно убежден в том, что управление безопасностью памяти с помощью контролера заимствований было лучшим подходом, чем GC.
\nЯ знал о D, поскольку я посматривал на D много лет назад и полюбил этот язык, но экосистема была настолько слабой, что это было не сильно полезно для практической работы. Я взглянул на него снова и обнаружил, что он значительно улучшился и даже удивительно: были доступны полные привязки GtkD. Это также помогло тому, чтобы D и Java были достаточно похожи, и чтобы собирать на D было невероятно легко. Как только я узнал, как работают диапазоны, было легко начать кодить.
\nСначала я создал небольшое приложение под названием Visual Grep, которое переносит grep в графический интерфейс. Когда я консультировался, мне часто приходилось собирать большие базы кода, ищущие конкретные шаблоны, и графический интерфейс, который делал просмотр шаблонов, был абсолютной необходимостью. Это было отличное первое приложение, так как я узнал немало вещей о D. Производительность приложения была изначально плохой с большими наборами результатов, потому что GC постоянно был перегружен при загрузке результатов из-за постоянного распределения. Отключение GC во время этого tight-цикла улучшило производительность неизмеримо. Я также узнал об интеграции GTK с возможностями многопоточности D.
\nЯ был вдохновлен пользовательским интерфейсом от Gnome Builder IDE, и подумал, что он хорошо подойдёт для эмулятора терминала. Таким образом, Tilix родился. Ну, на самом деле сначала он назывался Terminix, но как только он начал получать популярность, я получил вежливую просьбу на переименование из-за компании Terminix, американской компании по борьбе с вредителями. Будучи канадцем, я не слишком хорошо их знал, поэтому я не много думал об этом имени. Извлеченный урок: потратьте время на выбор хорошего имени, если ваше приложение станет более популярным, чем вы ожидаете.
\nЯ наслаждаюсь временем, проведённым с D, и это отличный язык для создания настольных приложений в GTK. Во многих отношениях, я чувствую, что D является естественным преемником Vala, который был языком, созданным специально для создания приложений GTK, но постепенно умирающим, в основном из-за его узкой направленности. Если вы не создаёте приложения GTK, вы не используете Vala, что означает, что количество людей, использующих его и работающий над ним, по определению очень мало.
\nЯ также считаю, что D является естественным преемником Delphi, по крайней мере с GtkD, как мощным инструментом для создания настольных приложений. Благодаря быстрому времени компиляции и легкому изучению языка, он приносит множество лучших атрибутов Delphi в современную эпоху. Плюс я могу ввести {и} вместо Begin и End и увеличить свою эффективность на 75% или около того. ?
\nОригинал (англ.) http://dlang.org/blog/2017/08/11/on-tilix-and-d-an-interview-with-gerald-nunn/
\nГлавный практический вывод прост: D может быть не только системным языком, но и удобным языком для прикладных desktop-инструментов. Tilix показывает, что связка D, GtkD и аккуратного следования GNOME HIG способна дать зрелое приложение, которым пользуются каждый день.
\nВ интервью хорошо видно, что выбор языка был не религиозным, а практическим. Автору не хотелось писать GUI на C или C++, Python оказался неудобен для крупного статически проверяемого приложения, а D дал знакомую после Java модель, нативную скорость и нормальные GTK-привязки. Это важный критерий выбора технологии: не «самый модный язык», а язык, на котором конкретный автор может быстро и спокойно делать продукт.
\nЕсли хочется повторить такой путь, начинать стоит не с большого терминала, а с маленькой GTK-утилиты: окно, настройки, несколько действий, сборка через DUB, упаковка и обновления. На таком проекте сразу проявятся реальные вопросы: работа с GtkD, структура проекта, взаимодействие с C-библиотеками, обработка событий и паузы GC.
\nИменно поэтому интервью полезно не только поклонникам D. Оно показывает нормальный инженерный путь: выбрать задачу, подобрать инструмент, сделать небольшую работающую версию, а затем постепенно доводить приложение до зрелого состояния.
", + "contentHtml": "Йоаким — интервьюер-резидент блога о D. Он также брал интервью у членов D-сообщества для This Week in D и ответственен за портирование LDC для Android.
\nГеральд Нанн — разработчик Tilix (ранее называвшийся Terminix).
\nTilix— продвинутый тайлинговый эмулятор терминалов с открытым исходным кодом, который является самым «звёздным» проектом на основеD наGitHub, недавно даже обогнавший стандартный компилятор D, DMD. В этом году на DConf в Берлине он рассказывал о том, как использует D. Имеются слайды и видео. В своей повседневной работе, которая не имеет ничего общего с настольными графическими приложениями, он является старшим разработчиком промежуточных решений в Red Hat. Подробнее об истории Джеральда в [расширенном интервью— Ред.]
\nЙоаким: — Что такое тайлинговый эмулятор терминала?
\nГеральд:— Тайлинговый эмулятор терминала позволяет разделить терминал на несколько частей и распределить их в удобном порядке и месте, что наиболее удобно при работе над конкретной задачей. Люди, которые работают на нескольких терминалах одновременно, как правило, находят такие инструменты наиболее полезными, особенно с постоянно увеличивающимися размерами мониторов и разрешений.
\nНесмотря на то, что Tilix очень хорош, основной причиной, по которой я создал его, было то, что мне нужен был эмулятор терминала, который следовал бы концепции Gnome HIG (Human Interface Guidelines — рекомендации по созданию интуитивных, легко изучаемых и логичных интерфейсов взаимодействия с пользователем) и использовал CSD (Client-Side Decorations — отрисовку на стороне клиента). Tilix следует Gnome HIG, опубликованным здесь, что означает соблюдение интервалов, макетов и других рекомендаций. После HIG важно, чтобы приложение соответствовало всему рабочему столу в целом.
\nCSD ссылается на заголовок окна, где не диспетчер дисплея, а пользователь берёт на себя ответственность за него и может заполнять панель заголовка кнопками и другими элементами управления. Это часть концепции Gnome HIG, и большинство приложений Gnome (gedit, файлы, видео) по умолчанию используют этот подход. Единственное исключением является gnome-terminal, который вообще не использует CSD.
\nЙ.: — Можете привести несколько примеров того, как вы используете концепцию Gnome HIG?
\nГ.: — Gnome HIG определяет конкретный язык дизайна в отношении того, как приложения, работающие в Gnome, должны выглядеть и показывать себя. Некоторые примеры Tilix, следующие концепции HIG, включают использование CSD и меню приложений по рекомендациям, таким как интервалы, макеты и т.д. Кроме того, разработчики Gnome собрали множество макетов, как они думают, как должны выглядеть различные приложения. Tilix использует макеты, разработанные дизайнерами Gnome для терминала, где это возможно. Например, в Tilix диалог предпочтений и профилей использовался отдельно, однако один из дизайнеров Gnome предложил этот макет для gnome-терминала. Я пошел вперед и реализовал его в Tilix, гораздо лучше, чем раньше.
\nВ результате использования CSD и концепции Gnome HIG, надеюсь, что использование Tilix в Gnome более органичено для пользователей.
\nИнтересная вещь — это напряженность между людьми, которые используют Tilix на Gnome и тех, кто использует его в других дистрибутивах. Хотя я не имею никаких сомнений в том, что разработка под Gnome является моей основной целью, я стараюсь сделать Tilix лучше и в других средах рабочего стола, разрешив пользователю отключить CSD в пользу обычного заголовка, если они того пожелают.
\nЙ.: — Вы попали в D из среды Java. Вы все еще пишете код в стиле Java на D? Это было легко, т.е. сколько вам пришлось привыкать, чтобы писать на D?
\nГ.: — Да, чаще всего работаю с Java. Я нашёл это довольно интересным на DConf, когда я спросил, как много людей пришли не из среды C/C++ , только один человек поднял руку.
\nЕсли вы посмотрите на мой код в Tilix, то он очень похож на Java-код. Некоторое из этого связано с моей обычное средой, в которой я работаю, а некоторое из-за того, что GtkD является оболочкой классов.
\nЯ считаю, что переключение между D и Java является довольно плавным по большей части; между ними гораздо меньше когнитивного трения, чем, скажем, между переключением между Java и Python.
\nСамая важная идиома D, которую мне пришлось изучить, — это диапазоны, поскольку они являются основополагающей особенностью D. Однако это не было сильно сложным. Выполнение функций времени компиляции (CTFE) по-прежнему остаются для меня неестественными. Когда я использую их, мне приходится каждый раз искать про них информацию, и мои текущие попытки использования CTFE с точки зрения кода довольны глупы. Я хотел бы больше использовать CTFE в Tilix, поскольку я получаю всё больший опыт использования D.
\nНаконец, мой недостаток опыта работы с C выявляет другую проблему, с которой я немного борюсь — это взаимодействие с кодом на C. Хотя по большей части это довольно просто, но, когда мне приходится расшифровывать что-то сложное, оно становится не таким простым. Поддержка FlatPak (система изолированных контейнеров для графических приложений) в настоящее время есть, так как мне не приходилось так сильно напрягаться по некоторым вопросам, с которыми я сталкивался на C.
\nСказав это, у меня есть некоторый опыт разработки собственного кода, поскольку много лет назад я потратил много времени на разработку кода на Delphi и Object Pascal. Именно на это и приходится большая часть моего опыта работы с графическим интерфейсом.
\nЙ.: — И из вашего выступления на DConf вы явное не беспокоились о сборщике мусора (GC). Вам приходилось думать о нём, когда вы разрабатывали Tilix? Имелись ли проблемы задержек с GUI, вызванные GC?
\nГ.: — Придя из Java, GC для меня довольно естественен, и я определенно не считаю его плохим для D. Я думаю, что GC в D получает много плохой прессы на основе опыта Java, но важно помнить, что GC в D сильно отличается от того, что в Java. Самое большое различие для меня в том, что в D есть больше возможностей для его управления, так как он хорошо понимает, когда он может начать цикл GC. Я был очень рад видеть, как больше людей поднимают различные темы на reddit и форумах, жалуются на использование GC.
\nУ меня не было никаких проблем с GC с точки зрения пауз, и ни один пользователь Tilix не сообщал об этом. У меня было несколько проблем, связанных с GC, в основном связанных с утечкой памяти из-за хранения ссылок, но все они были ошибками программного кода, а не проблемой с реализацией GC в D. У меня есть другое приложение на GTK D, Visual Grep, где я столкнулся с ужасающей эффективностью при обработке большого количества совпадений в tight-цикле (цикл, который содержит несколько инструкций и повторяется много раз.). Тем не менее, просто отключив GC для этого раздела кода, ускорилось все.
\nЙ.: — Репозиторий github для Tilix замечательно чист, нет открытых запросов «pull request» (PR) и низкий процент проблем, которые все еще открыты. Сколько времени вы еженедельно тратите на Tilix? Является ли Tilix только хобби или он стал чем-то большим?
\nГ.: — Tilix — это просто хобби. Я, вероятно, провожу от 5 до 10 часов в неделю. На данный момент — это зрелое приложение, следовательно, относительно небольшое количество проблем. Я также уделяю приоритетное внимание исправлению ошибок при добавлении новых функций, что помогает сохранить список управляемым.
\nЧто касается запросов на «pull request» (PR), я твердо убежден в том, что я должен быть отзывчивым, поэтому я, как правило, отвечаю на PR в течение дня или двух. В качестве участника разработок я знаю, что ничего не убивает интерес, как видеть, что ваш PR томится в течение нескольких недель, месяцев или даже лет. Если вы хотите, чтобы люди вносили свой вклад, что я определенно делаю, то я чувствую, что вы обязаны участникам разработки своевременно реагировать на их PR.
\nНа данный момент Tiltx — это относительно небольшой проект, поэтому мне легко принять этот подход. Я понимаю, почему более крупные проекты могут иметь больше проблем в этой области.
\nЙ.: — D имеет много особенностей, насколько хорошо вы их знаете? Вы упомянули, что хотите использовать некоторые из возможностей времени компиляции. Как вы думаете за счёт каких особенностей D и что Tilix выиграет в будущем и как?
\nГ.: — Я не думаю, что знаю это, если честно, помимо основного набора особенностей, которые я использую в Tilix. Я всегда удивляюсь ребятам на форуме, которые могут утверждать достоинства/недостатки низкоуровневых деталей языка. Это определенно не я. Я бы хотел поправиться, но на самом деле я использую D только как хобби, поэтому я не могу тратить столько же времени, сколько и на Java. Кроме того, моя работа в Red Hat имеет больше компонентов инфраструктуры, чем мои предыдущие рабочие места, поэтому большая часть моего времени расходуется дома на обучение в этом направлении.
\nЧто касается возможностей, от которых Tilix выиграет, я думаю, что использование CTFE и диапазонов было бы очень полезно для того, чтобы улучшить некоторые моменты в GtkD более идиоматичным способом. У меня есть достаточное количество кода, где он может быть намного более кратким при соответствующем использовании CTFE. В качестве простого примера можно использовать диапазоны для поддержки итераций по различным артефактам с использованием foreach, а не классическим циклом. Тем не менее, я думаю, что некоторые из более сложных вариантов использования, таких как поддержка D-Bus (система межпроцессного взаимодействия, которая позволяет приложениям в операционной системе сообщаться друг с другом) и GObject, будут очень полезны.
\nДля тех, кто не знаком с GObject — это объект базового уровня в GTK и является счётчиком ссылок. Возможность легко создавать GObjects в D, как можно и в Python, упростит взаимодействие с некоторыми из API; прямо сейчас это эквивалентно написанию его в сыром C, и это несколько трудоемко. Майк Вей, сопровождающий GtkD, начал делать некоторые работы над этим.
\nЙ.: — Вы упомянули на DConf, что D имеет быстрый цикл компиляции: как вы используете это, то есть какие IDE, компиляторы, toolchain (набор программ, необходимых для создания других программ) вы используете как для разработки, так и для выпуска релизов?
\nГ.: — Я использую MS Visual Studio Code на Linux с отличным плагином code-d, написанным Jan «WebFreak» Jurzitza. Это дает мне все необходимые функции (автозаполнение кода, подсказки, рекомендации и т. Д.). Единственное, чего я не вижу, что есть в Java IDE, — это возможность рефакторинга. Для разработки я использую DMD, поскольку он имеет самое быстрое время компиляции. Сборку версий выполняю с использованием LDC, компилятора D с бэкэном LLVM, поскольку он генерирует меньшие и более быстрые двоичные файлы. Мне редко приходится запускать отладчик, но, когда я это делаю, я просто использую GDB из командной строки.
\nЙ.: — Какие проблемы у вас были с D? Какие его особенности вам не нравятся?
\nГ.: — Никаких серьёзных проблем с моей точки зрения. Я в целом очень доволен этим языком и считаю, что он несёт правильный баланс между простотой использования и возможностями. Наибольшее неудобство мне доставляла стандартная библиотека Phobos, а не сам язык, и эти неудобства напрямую соотносятся с нехваткой времени.
\nНикаких серьёзных проблем с Phobos, а скорее кучей раздражителей. Например, нельзя легко использовать immutable для отправки в std.concurrency, std.experimental.logger являющиеся все ещё экспериментальными, парсер json имеет проблемы, если в локализации установлена запятая для отделения десятичных знаков чисел и т.д. и т.п. Ни один из данных недочётов по себе не является особо важным, и большинство из скорее всего связаны из-за нехватки трудовых ресурсов. Я на самом деле несколько неохотно жалуюсь на них, потому что я, наверное, мог исправить их сам и отправить PR.
\nМеня больше раздражает негативность на форумах в отношении GC. Я чувствую, что иногда люди так поворачиваются в сторону D, что хотят видеть его идеальным системным языком (т.е. без GC, без безопасности памяти и т.д.), упуская из виду, что он очень хороший язык для создания приложений на данный момент. Пока D сравнивают с Rust, в некотором смысле сравнение с Go для меня более интересно. Оба языка основаны на GC и оба начинали как системные языки, однако Go опирается на GC и «удваивается» (от переводчика: скорее всего имеется в виду рост популярности), добиваясь успеха. Один из продуктов Red Hat, который я поддерживаю, OpenShift, использует Kubernetes (проект Google) для управления кластером контейнеров Linux как единой системой, и он написан на Go.
\nЯ думаю, что как язык D намного превосходит Go, и мне хотелось, что мы бы заявляли об этом громче вместо постоянного отрицательного обсуждения системного программирования. На данный момент надо сказать ради справедливости, что Go имеет крупного корпоративного спонсора, в отличие от D, однако контраст в позиционировании по-прежнему интересен мне.
\nЙ.: — Каковы ваши будущие планы в отношении Tilix?
\nГ.: — Две самые большие функции, которые я хотел бы добавить, это поддержка режима управления tmuxи добавление возможности отображения боковой панели popout.
\nДля тех, кто не знаком с tmux, это терминальный мультиплексор; это, по сути, терминальный разделитель, но внутри самого терминала. Он также поддерживает ряд других функций, но наиболее интересным является сохранение терминальных сеансов, находящихся вне терминала. Поскольку он работает в терминале, он немного ухудшает производительность, а с точки зрения графического интерфейса он не может использовать собственные виджеты, такие как полосы прокрутки. Чтобы смягчить это, он поддерживает так называемый режим управления, который позволяет ему интегрироваться с эмулятором тайлингово терминала для создания новых терминалов, т.е. подтерминалов внутри одного терминала, управляющихся за пределами tmux. Это значительно улучшает его производительность, позволяя пользователям использовать другие функции, поддерживаемые tmux. На данный момент поддерживается только iterm2 на OSX насколько мне известно.
\nБоковая панель в Tilix является одним из наиболее противоречивых элементов интерфейса. Я решил не реализовывать интерфейс с вкладками, потому что я счел их бесполезными с точки зрения поиска открытия необходимой вкладки; там просто недостаточно места для вкладок, чтобы отличить их, и переименовывать их вручную — это «боль». Боковая панель — это моя попытка альтернативы, она отображает миниатюру каждого сеанса (известную также как ярлык) на боковой панели, которая может быть убрана по мере необходимости для переключения между сеансами.
\n Боковая панель Tilix в действии
Боковая панель Tilix в действии.
\nУ некоторых людей есть сильное предпочтение к тому, что постоянно доступно, но, к сожалению, все, что видно на боковой панели, непросто, так как генерация эскизов занимает значительное количество времени из-за структуры GTK. Существуют потенциальные способы заставить это работать быстрее, но требуется время на то, чтобы попробовать разные варианты, чтобы увидеть, что как отображается, а затем, что наиболее эффективно.
\nВ мечтах, я бы хотел переключиться на использование эмулятора терминала, написанного изначально в D, а не GTK VTE (Virtual Terminal Emulator), который написан на C, и который я использую сейчас. Для тех, кто не знаком с ним, VTE — это виджет эмуляции терминала, используемый терминалом Gnome и доступен в виде многоразового виджета. Многие эмуляторы терминала в Linux используют этот виджет (gnome-terminal, guake, terminator, tilix и т.д.), поскольку он обеспечивает полностью готовый к работке эмулятор, который прошел через огромное количество тестов.
\nНедостатком этого является то, что любые пользовательские функции, которые вы хотите реализовать, которые включают фактический уровень эмуляции терминала, требуют модификации VTE и получения этих изменений вверх по потоку (upstream). У меня есть несколько патчей, которые Tilix поддерживает (для триггеров и значков), но, честно говоря, я провёл плохую работу по внедрению вверх по потоку (upstream). Частично это связано с тем, что VTE написано на C и сделать быстрый и качественный патч на C занимает достаточно много времени.
\nТаким образом, наличие эмуляции терминала, написанного на D, сделало бы это намного проще, однако это огромные инвестиции времени, поскольку люди недооценивают объем работы. В эмуляции терминала много сложных случаев, плюс добавление всего материала, чтобы сделать его удобным для пользователей (поиск, клики по ссылкам и т.д.), это много, чтобы взять на себя. У меня просто нет времени, чтобы сделать это реальностью, если только я не выиграю в лотерею. Если кто-то захочет взять на себя это роль и создать виджет эмуляции терминала GTK, который имеет все необходимые функции и поддерживал бы его в течение длительного времени, я был бы рад работать с ним, чтобы интегрировать его с Tilix. Адам Рапп уже создал один, который работает достаточно хорошо по моим тестам; если кто-то хочет работать над преобразованием его в виджет GTK, добавит необходимые улучшения и согласится его поддерживать, то пусть не стесняется писать и надоедать сообщениями (*ping) мне. ?
\nЙ.: — Пожалуйста, расскажите из своего опыта, как вы впервые обнаружили D и использовали его для написания Tilix?
\nГ.: — На протяжении всей моей карьеры я всегда имел хобби. Некоторые вещи, над которыми я работал, включали популярную надстройку для Delphi под названием Gexperts, замену проводника файлов Windows, Java IDE под названием Gel и популярное приложение для Android под названием OnTrack Diabetes, которое я продал несколько лет назад. Пару лет назад я искал что-то новое для работы в качестве моей программы в качества хобби и остановился на идее создания десктопного приложения для Linux. Я знал, что нужно использовать инструментарий GTK, так как Gnome — моя предпочтительная среда рабочего стола, и особенно с моим прошлым опытом Delphi в графических интерфейсах, которые я мог бы использовать. Я также знал, что меня не интересует разработка на C или C ++, поэтому я посмотрел, какие альтернативы были.
\nЯ начал с Python, так как у него отличная поддержка GTK, и я немного поработал над программированием Jython в WebLogic, так как я использовал Weblogic Scripting Tool (WLST). Однако большая часть моей предыдущей работы была небольшими сценариями, и я быстро понял, что в больших масштабах Python не для меня. Я вообще предпочитаю статически типизированные языки, и динамический набор текста на Python приводил меня в замешательство, особенно в качестве хобби, где я постоянно нуждался в использование справочных материалов, так как я работал на нем нечасто.
\nЯ также смотрел в сторону Rust и Go, но в то время ни у одного из них не было полнофункциональных GTK-привязок. Кроме того, в то время у Rust было много позитивной прессы, но у него был плохой материал для убеждения в правильности его подхода, и я был не так сильно убежден в том, что управление безопасностью памяти с помощью контролера заимствований было лучшим подходом, чем GC.
\nЯ знал о D, поскольку я посматривал на D много лет назад и полюбил этот язык, но экосистема была настолько слабой, что это было не сильно полезно для практической работы. Я взглянул на него снова и обнаружил, что он значительно улучшился и даже удивительно: были доступны полные привязки GtkD. Это также помогло тому, чтобы D и Java были достаточно похожи, и чтобы собирать на D было невероятно легко. Как только я узнал, как работают диапазоны, было легко начать кодить.
\nСначала я создал небольшое приложение под названием Visual Grep, которое переносит grep в графический интерфейс. Когда я консультировался, мне часто приходилось собирать большие базы кода, ищущие конкретные шаблоны, и графический интерфейс, который делал просмотр шаблонов, был абсолютной необходимостью. Это было отличное первое приложение, так как я узнал немало вещей о D. Производительность приложения была изначально плохой с большими наборами результатов, потому что GC постоянно был перегружен при загрузке результатов из-за постоянного распределения. Отключение GC во время этого tight-цикла улучшило производительность неизмеримо. Я также узнал об интеграции GTK с возможностями многопоточности D.
\nЯ был вдохновлен пользовательским интерфейсом от Gnome Builder IDE, и подумал, что он хорошо подойдёт для эмулятора терминала. Таким образом, Tilix родился. Ну, на самом деле сначала он назывался Terminix, но как только он начал получать популярность, я получил вежливую просьбу на переименование из-за компании Terminix, американской компании по борьбе с вредителями. Будучи канадцем, я не слишком хорошо их знал, поэтому я не много думал об этом имени. Извлеченный урок: потратьте время на выбор хорошего имени, если ваше приложение станет более популярным, чем вы ожидаете.
\nЯ наслаждаюсь временем, проведённым с D, и это отличный язык для создания настольных приложений в GTK. Во многих отношениях, я чувствую, что D является естественным преемником Vala, который был языком, созданным специально для создания приложений GTK, но постепенно умирающим, в основном из-за его узкой направленности. Если вы не создаёте приложения GTK, вы не используете Vala, что означает, что количество людей, использующих его и работающий над ним, по определению очень мало.
\nЯ также считаю, что D является естественным преемником Delphi, по крайней мере с GtkD, как мощным инструментом для создания настольных приложений. Благодаря быстрому времени компиляции и легкому изучению языка, он приносит множество лучших атрибутов Delphi в современную эпоху. Плюс я могу ввести {и} вместо Begin и End и увеличить свою эффективность на 75% или около того. ?
\nОригинал (англ.) http://dlang.org/blog/2017/08/11/on-tilix-and-d-an-interview-with-gerald-nunn/
\nГлавный практический вывод прост: D может быть не только системным языком, но и удобным языком для прикладных desktop-инструментов. Tilix показывает, что связка D, GtkD и аккуратного следования GNOME HIG способна дать зрелое приложение, которым пользуются каждый день.
\nВ интервью хорошо видно, что выбор языка был не религиозным, а практическим. Автору не хотелось писать GUI на C или C++, Python оказался неудобен для крупного статически проверяемого приложения, а D дал знакомую после Java модель, нативную скорость и нормальные GTK-привязки. Это важный критерий выбора технологии: не «самый модный язык», а язык, на котором конкретный автор может быстро и спокойно делать продукт.
\nЕсли хочется повторить такой путь, начинать стоит не с большого терминала, а с маленькой GTK-утилиты: окно, настройки, несколько действий, сборка через DUB, упаковка и обновления. На таком проекте сразу проявятся реальные вопросы: работа с GtkD, структура проекта, взаимодействие с C-библиотеками, обработка событий и паузы GC.
\nИменно поэтому интервью полезно не только поклонникам D. Оно показывает нормальный инженерный путь: выбрать задачу, подобрать инструмент, сделать небольшую работающую версию, а затем постепенно доводить приложение до зрелого состояния.
", "readingMinutes": 19 }, { @@ -51,7 +51,7 @@ ], "cover": "/assets/illustrations/bitrix-translit-api.svg", "excerpt": "Часто в Bitrix необходимо сгенерировать код элемента из имени. Привожу пример реализации именно такой функции с помощью функции Bitrix API для транслита CUtil::translit :", - "contentHtml": "Часто в Bitrix необходимо сгенерировать код элемента из имени.
\nПривожу пример реализации именно такой функции с помощью функции Bitrix API для транслита CUtil::translit:
function strToElementCode($str, $maxLength = 100) {\n\t$params = array(\n\t\t\t"max_len" => $maxLength,\n\t\t\t"change_case" => "L",\n\t\t\t"replace_space" => "_",\n\t\t\t"replace_other" => "_",\n\t\t\t"delete_repeat_replace" => "true",\n\t\t\t"use_google" => "false",\n\t\t);\n\treturn CUtil::translit($str, "ru", $params);\n}Транслитерация имени удобна, но код элемента должен оставаться уникальным. Поэтому после генерации проверяйте существующие символьные коды в инфоблоке и при совпадении добавляйте числовой суффикс. Иначе два товара с похожим названием могут получить один URL.
\ntelefon-samsung\ntelefon-samsung-2\ntelefon-samsung-3\nТакже стоит сразу нормализовать результат: привести к нижнему регистру, заменить пробелы на дефис, убрать повторяющиеся дефисы и обрезать дефис в начале или конце строки. Это мелочь, но именно такие мелочи потом портят адреса страниц.
\n$code = CUtil::translit($name, 'ru', [\n 'replace_space' => '-',\n 'replace_other' => '-',\n 'change_case' => 'L',\n]);\n\n$code = trim(preg_replace('/-+/', '-', $code), '-');\nПроверку уникальности лучше делать в том же инфоблоке, где будет создан элемент. Если сайт многоязычный или есть разделы с одинаковыми товарами, заранее решите правило: уникальность на весь инфоблок или только внутри раздела. Для SEO обычно проще и надёжнее уникальность на весь инфоблок.
\nВ итоге функция должна не просто транслитерировать строку, а возвращать готовый символьный код: чистый, нижнего регистра, без мусора и без совпадений с уже существующими элементами.
", + "contentHtml": "Часто в Bitrix необходимо сгенерировать код элемента из имени. Привожу пример реализации именно такой функции с помощью функции Bitrix API для транслита CUtil::translit:
\nfunction strToElementCode($str, $maxLength = 100) {\n\t$params = array(\n\t\t\t"max_len" => $maxLength,\n\t\t\t"change_case" => "L",\n\t\t\t"replace_space" => "_",\n\t\t\t"replace_other" => "_",\n\t\t\t"delete_repeat_replace" => "true",\n\t\t\t"use_google" => "false",\n\t\t);\n\treturn CUtil::translit($str, "ru", $params);\n}\nТранслитерация имени удобна, но код элемента должен оставаться уникальным. Поэтому после генерации проверяйте существующие символьные коды в инфоблоке и при совпадении добавляйте числовой суффикс. Иначе два товара с похожим названием могут получить один URL.
\ntelefon-samsung\ntelefon-samsung-2\ntelefon-samsung-3\nТакже стоит сразу нормализовать результат: привести к нижнему регистру, заменить пробелы на дефис, убрать повторяющиеся дефисы и обрезать дефис в начале или конце строки. Это мелочь, но именно такие мелочи потом портят адреса страниц.
\n$code = CUtil::translit($name, 'ru', [\n 'replace_space' => '-',\n 'replace_other' => '-',\n 'change_case' => 'L',\n]);\n\n$code = trim(preg_replace('/-+/', '-', $code), '-');\nПроверку уникальности лучше делать в том же инфоблоке, где будет создан элемент. Если сайт многоязычный или есть разделы с одинаковыми товарами, заранее решите правило: уникальность на весь инфоблок или только внутри раздела. Для SEO обычно проще и надёжнее уникальность на весь инфоблок.
\nВ итоге функция должна не просто транслитерировать строку, а возвращать готовый символьный код: чистый, нижнего регистра, без мусора и без совпадений с уже существующими элементами.
", "readingMinutes": 1 }, { @@ -65,7 +65,7 @@ ], "cover": "/assets/illustrations/bitrix-offer-api.svg", "excerpt": "Хороший пример создания (добавления) торгового предложения в Bitrix через API :", - "contentHtml": "Хороший пример создания (добавления) торгового предложения в Bitrix через API:
\n\nuse \\Bitrix\\Main\\Loader;\n \nif (!Loader::includeModule('iblock') || !Loader::includeModule('catalog'))\n{\n\tdie('Error loading module iblock or catalog');\n}\n \n$IBlockOffersCatalogId = 3; // ID инфоблока предложений (должен быть торговым каталогом)\n$productName = "Товар"; // наименование товара\n$offerName = "Торговое предложение"; // наименование торгового предложения\n$offerPrice = 100.50; // Цена торгового предложения\n \n \n$arCatalog = CCatalog::GetByID($IBlockOffersCatalogId);\n \n$IBlockCatalogId = $arCatalog['PRODUCT_IBLOCK_ID']; // ID инфоблока товаров\n$SKUPropertyId = $arCatalog['SKU_PROPERTY_ID']; // ID свойства в инфоблоке предложений типа "Привязка к товарам (SKU)"\n \n$obElement = new CIBlockElement();\n$arFields = array(\n 'NAME' => $productName,\n 'IBLOCK_ID' => $IBlockCatalogId,\n 'ACTIVE' => 'Y'\n);\n$productId = $obElement->Add($arFields); // добавили товар, получили ID\n \nif ($productId)\n{\n\t$obElement = new CIBlockElement();\n\t// свойства торгвоого предложения\n\t$arOfferProps = array(\n\t\t$SKUPropertyId => $productId,\n\t);\n\t$arOfferFields = array(\n\t\t'NAME' => $offerName,\n\t\t'IBLOCK_ID' => $IBlockOffersCatalogId,\n\t\t'ACTIVE' => 'Y',\n\t\t'PROPERTY_VALUES' => $arOfferProps\n\t);\n \n\t$offerId = $obElement->Add($arOfferFields); // ID торгового предложения\n \n\tif ($offerId)\n\t{\n\t\t// добавляем как товар и указываем цену\n\t\t$catalogProductAddResult =\tCCatalogProduct::Add(array(\n\t\t\t\t"ID" => $offersId,\n\t\t\t\t"VAT_INCLUDED" => "Y", //НДС входит в стоимость\n\t\t\t));\n\t\tif ($catalogProductAddResult && !CPrice::SetBasePrice($offerId, $offerPrice, "RUB"))\n\t\t\tthrow new Exception("Ошибка установки цены торгового предложения \\"{$offerId}\\"");\n\t\telse\n\t\t\tthrow new Exception("Ошибка добавления параметров торгового предложения \\"{$offerId}\\" в каталог товаров");\n\t}\n\telse\n\t{\n\t\tthrow new Exception("Ошибка добавления торгового предложения: " . $obElement->LAST_ERROR);\n\t}\n}\nelse\n{\n\tthrow new Exception("Ошибка добавления товара: " . $obElement->LAST_ERROR);\n}После добавления торгового предложения проверьте три связи: привязку к товару, цены и остатки. В Bitrix предложение может успешно создаться, но не появиться в публичной части, если не заполнены обязательные свойства SKU, не сохранён товарный каталог или не обновлены кеши.
\nМинимальная последовательность такая: создать элемент торгового предложения в инфоблоке SKU, записать связь с товаром, сохранить параметры товара, установить цену, установить количество. Если хотя бы один шаг пропущен, предложение может быть видно в админке, но не попадёт в публичный каталог.
\nCModule::IncludeModule('iblock');\nCModule::IncludeModule('catalog');\n\n$offerId = $el->Add($fields);\nCCatalogProduct::Add([\n 'ID' => $offerId,\n 'QUANTITY' => 10,\n]);\nCPrice::SetBasePrice($offerId, 990, 'RUB');\nНа боевом проекте обязательно логируйте результат Add и текст ошибки LAST_ERROR. Bitrix часто молча возвращает false, а настоящая причина находится именно там: обязательное поле, неправильный инфоблок, неверный код свойства или недостаточные права.
", + "contentHtml": "Хороший пример создания (добавления) торгового предложения в Bitrix через API:
\nuse \\Bitrix\\Main\\Loader;\n\nif (!Loader::includeModule('iblock') || !Loader::includeModule('catalog'))\n{\n\tdie('Error loading module iblock or catalog');\n}\n\n$IBlockOffersCatalogId = 3; // ID инфоблока предложений (должен быть торговым каталогом)\n$productName = "Товар"; // наименование товара\n$offerName = "Торговое предложение"; // наименование торгового предложения\n$offerPrice = 100.50; // Цена торгового предложения\n\n$arCatalog = CCatalog::GetByID($IBlockOffersCatalogId);\n\n$IBlockCatalogId = $arCatalog['PRODUCT_IBLOCK_ID']; // ID инфоблока товаров\n$SKUPropertyId = $arCatalog['SKU_PROPERTY_ID']; // ID свойства в инфоблоке предложений типа "Привязка к товарам (SKU)"\n\n$obElement = new CIBlockElement();\n$arFields = array(\n 'NAME' => $productName,\n 'IBLOCK_ID' => $IBlockCatalogId,\n 'ACTIVE' => 'Y'\n);\n$productId = $obElement->Add($arFields); // добавили товар, получили ID\n\nif ($productId)\n{\n\t$obElement = new CIBlockElement();\n\t// свойства торгвоого предложения\n\t$arOfferProps = array(\n\t\t$SKUPropertyId => $productId,\n\t);\n\t$arOfferFields = array(\n\t\t'NAME' => $offerName,\n\t\t'IBLOCK_ID' => $IBlockOffersCatalogId,\n\t\t'ACTIVE' => 'Y',\n\t\t'PROPERTY_VALUES' => $arOfferProps\n\t);\n\n\t$offerId = $obElement->Add($arOfferFields); // ID торгового предложения\n\n\tif ($offerId)\n\t{\n\t\t// добавляем как товар и указываем цену\n\t\t$catalogProductAddResult =\tCCatalogProduct::Add(array(\n\t\t\t\t"ID" => $offersId,\n\t\t\t\t"VAT_INCLUDED" => "Y", //НДС входит в стоимость\n\t\t\t));\n\t\tif ($catalogProductAddResult && !CPrice::SetBasePrice($offerId, $offerPrice, "RUB"))\n\t\t\tthrow new Exception("Ошибка установки цены торгового предложения \\"{$offerId}\\"");\n\t\telse\n\t\t\tthrow new Exception("Ошибка добавления параметров торгового предложения \\"{$offerId}\\" в каталог товаров");\n\t}\n\telse\n\t{\n\t\tthrow new Exception("Ошибка добавления торгового предложения: " . $obElement->LAST_ERROR);\n\t}\n}\nelse\n{\n\tthrow new Exception("Ошибка добавления товара: " . $obElement->LAST_ERROR);\n}\nПосле добавления торгового предложения проверьте три связи: привязку к товару, цены и остатки. В Bitrix предложение может успешно создаться, но не появиться в публичной части, если не заполнены обязательные свойства SKU, не сохранён товарный каталог или не обновлены кеши.
\nМинимальная последовательность такая: создать элемент торгового предложения в инфоблоке SKU, записать связь с товаром, сохранить параметры товара, установить цену, установить количество. Если хотя бы один шаг пропущен, предложение может быть видно в админке, но не попадёт в публичный каталог.
\nCModule::IncludeModule('iblock');\nCModule::IncludeModule('catalog');\n\n$offerId = $el->Add($fields);\nCCatalogProduct::Add([\n 'ID' => $offerId,\n 'QUANTITY' => 10,\n]);\nCPrice::SetBasePrice($offerId, 990, 'RUB');\nНа боевом проекте обязательно логируйте результат Add и текст ошибки LAST_ERROR. Bitrix часто молча возвращает false, а настоящая причина находится именно там: обязательное поле, неправильный инфоблок, неверный код свойства или недостаточные права.
", "readingMinutes": 2 }, { @@ -121,7 +121,7 @@ ], "cover": "/assets/illustrations/preg-match-all-d.svg", "excerpt": "В PHP есть очень удобная функция для глобального поиска шаблона регулярного выражения в строке preg_match_all . Давайте напишем аналогичный класс статических методов для реализации этой функции с разными флагами на языке программирования D. Описание функции: i", - "contentHtml": "В PHP есть очень удобная функция для глобального поиска шаблона регулярного выражения в строке preg_match_all. Давайте напишем аналогичный класс статических методов для реализации этой функции с разными флагами на языке программирования D.
\nОписание функции:
\n\nint preg_match_all ( string $pattern , string $subject [, array &$matches [, int $flags = PREG_PATTERN_ORDER [, int $offset = 0 ]]] )Функция ищет в строке subject все совпадения с шаблоном pattern и помещает результат в массив matches в порядке, определяемом комбинацией флагов flags.
\nПосле нахождения первого соответствия последующие поиски будут осуществляться не с начала строки, а от конца последнего найденного вхождения.
Данная функцию очень удобна и чаще всего она применяется с третьим параметром, чтобы обработать полученный массив совпадений шаблона. В D подобной регулярной функции нет. Мне вообще не сильно нравятся, как реализованы регулярные выражения в D. Ну да ладно.
\nПолную документацию по функции можете посмотреть здесь http://php.net/manual/ru/function.preg-match-all.php.
\nВозможные флаги функции preg_match_all:
\nНе будем заморачиваться с реализацией через передачу константы, как в PHP, или через шаблоны в D. Создадим для каждого флага PHP данной функции аналогичные статические методы. Параметр offset оставим за бортом. Класс давайте назовём PReg. Вырисовывается следующая структура:
\n\nclass PReg\n{\n\tpublic static:\n \n\t… matchAllPatternOrder (...) {...}\n\t… matchAllSetOrder (...){...}\n\t… matchAllOffsetCapture (…) {...}\n}Как параметры будем передавать строку для поиска (тип string), регулярное выражение (тип ???) и переменную для вывода количества совпадений шаблона регулярного выражения (тип out int). Тип регулярного выражения можно посмотреть, чтобы не капаться в модуле, применив такую хитрость:
\nwriteln(typeof(regex(`\\d+`)).stringof);\nТакже как аргумент вы без проблем сможете передавать compile-time регулярное выражение (ctRegex). Для данного типа создадим алиас типа, чтобы поудобнее было.
\nФункция matchAllPatternOrder и matchAllSetOrder будет возвращать двумерный массив строк, для них тоже создадим алиас типов (мне так кажется, что путаницы меньше). MatchAllOffsetCapture пока оставим на закуску.
В итоге у нас получается:
\n\nclass PReg\n{\n\tprivate static alias typeRegex = Regex!char;\n \n\tpublic static:\t\n\talias typePatternOrder = string[];\n\talias typeSetOrder = string[];\n \n\ttypePatternOrder[] matchAllPatternOrder (string subject, typeRegex obRegex, out int count) {..}\n\ttypeSetOrder[] matchAllSetOrder (string subject, typeRegex obRegex, out int count){...}\n\t… matchAllOffsetCapture (string subject, typeRegex obRegex, out int count)\t{...}\n}Привожу пример реализованной функции matchAllPatternOrder (основные моменты пояснены ниже):
\n\ntypePatternOrder[] matchAllPatternOrder (string subject, typeRegex obRegex, out int count)\n{\n\ttypePatternOrder[] matches;\n\tauto stdMatches = matchAll(subject, obRegex);\n \n\twhile (!stdMatches.empty) {\n\t\tmatches.length++;\n\t\tmatches[0] ~= stdMatches.front.hit;\n \n\t\tfor (int i = 1; i < stdMatches.front.length; i++) {\n\t\t\tmatches.length++;\n\t\t\tmatches[count+1] ~= stdMatches.front[i];\n\t\t}\n\t\tstdMatches.popFront();\n\t\tcount++;\n\t}\n\treturn matches;\n}Реализация функции matchAllSetOrder по сути ничем сильно не отличается кроме как позициями найденных строк в массиве (она даже проще). Приведу лишь реализованный вариант:
\n\ntypeSetOrder[] matchAllSetOrder (string subject, typeRegex obRegex, out int count)\n{\n\ttypeSetOrder[] matches;\n\tauto stdMatches = matchAll(subject, obRegex);\n \n\twhile (!stdMatches.empty) {\n\t\tfor (int i = 0; i < stdMatches.front.length; i++) {\n\t\t\tmatches.length++;\n\t\t\tmatches[count] ~= stdMatches.front[i];\n\t\t}\n\t\tstdMatches.popFront();\n\t\tcount++;\n\t}\n\treturn matches;\n}Реализация функции matchAllOffsetCapture имеет свои особенности. Найденные данные функции будем хранить в двумерном массиве картежа типов Tuple!(string, «text», int, «position»), по которому можно будет получить позицию и найденую строку. Реализация:
\n\nalias typeOffsetCapture = Tuple!(string, "text", int, "position")[];\n \ntypeOffsetCapture[] matchAllOffsetCapture (string subject, typeRegex obRegex, out int count)\n\t{\n\t\ttypeOffsetCapture[] matches;\n \n\t\tauto stdMatches = matchAll(subject, obRegex);\n\t\tmatches.length = stdMatches.front.length;\n \n\t\twhile (!stdMatches.empty) {\n\t\t\tTuple!(string, "text", int, "position") match;\n\t\t\tmatch.text = stdMatches.front.hit;\n\t\t\tmatch.position = cast(int)(match.text.ptr - subject.ptr);\n\t\t\tmatches[0].length = count+1;\n\t\t\tmatches[0][count] = match;\n\t\t\tfor (int i = 1; i < stdMatches.front.length; i++) {\n\t\t\t\tmatches[i].length = matches[0].length;\n\t\t\t\tmatch.text = stdMatches.front[i];\n\t\t\t\tmatch.position = cast(int)(stdMatches.front[i].ptr - subject.ptr);\n\t\t\t\tmatches[i][count] = match;\n\t\t\t}\n\t\t\tstdMatches.popFront();\n\t\t\tcount++;\n\t\t}\n\t\treturn matches;\n\t}Единственное, что здесь важно отметить, как определяются позиции, а именно:
\n\nmatch.position = cast(int)(match.text.ptr - subject.ptr);Позиция определяется через разность указателей позиций элементов массива. Также здесь выполняется приведение типа к int, так как многие процессоры уже использует 64-х битное представление, и указатели будут иметь тип long.
\nПолная реализация модуля с тестами представлена ниже:
\n\nmodule preg;\n \nimport std.string: format;\nimport std.typecons;\nimport std.regex: Regex, regex, ctRegex, matchFirst, matchAll;\n \n// analog of preg regex in php\n \nclass PReg\n{\n\tprivate static alias typeRegex = Regex!char;\n \n\tpublic static:\n \n\talias typePatternOrder = string[];\n\talias typeSetOrder = string[];\n\talias typeOffsetCapture = Tuple!(string, "text", int, "position")[];\n \n\ttypePatternOrder[] matchAllPatternOrder (string subject, typeRegex obRegex, out int count)\n\t{\n\t\ttypePatternOrder[] matches;\n\t\tauto stdMatches = matchAll(subject, obRegex);\n \n\t\twhile (!stdMatches.empty) {\n\t\t\tmatches.length++;\n\t\t\tmatches[0] ~= stdMatches.front.hit;\n \n\t\t\tfor (int i = 1; i < stdMatches.front.length; i++) {\n\t\t\t\tmatches.length++;\n\t\t\t\tmatches[count+1] ~= stdMatches.front[i];\n\t\t\t}\n\t\t\tstdMatches.popFront();\n\t\t\tcount++;\n\t\t}\n\t\treturn matches;\n\t}\n \n \n\ttypeSetOrder[] matchAllSetOrder (string subject, typeRegex obRegex, out int count)\n\t{\n\t\ttypeSetOrder[] matches;\n \n\t\tauto stdMatches = matchAll(subject, obRegex);\n \n\t\twhile (!stdMatches.empty) {\n\t\t\tfor (int i = 0; i < stdMatches.front.length; i++) {\n\t\t\t\tmatches.length++;\n\t\t\t\tmatches[count] ~= stdMatches.front[i];\n\t\t\t}\n\t\t\tstdMatches.popFront();\n\t\t\tcount++;\n\t\t}\n\t\treturn matches;\n\t}\n \n\ttypeOffsetCapture[] matchAllOffsetCapture (string subject, typeRegex obRegex, out int count)\n\t{\n\t\ttypeOffsetCapture[] matches;\n \n\t\tauto stdMatches = matchAll(subject, obRegex);\n\t\tmatches.length = stdMatches.front.length;\n \n\t\twhile (!stdMatches.empty) {\n\t\t\tTuple!(string, "text", int, "position") match;\n\t\t\tmatch.text = stdMatches.front.hit;\n\t\t\tmatch.position = cast(int)(match.text.ptr - subject.ptr);\n\t\t\tmatches[0].length = count+1;\n\t\t\tmatches[0][count] = match;\n\t\t\tfor (int i = 1; i < stdMatches.front.length; i++) {\n\t\t\t\tmatches[i].length = matches[0].length;\n\t\t\t\tmatch.text = stdMatches.front[i];\n\t\t\t\tmatch.position = cast(int)(stdMatches.front[i].ptr - subject.ptr);\n\t\t\t\tmatches[i][count] = match;\n\t\t\t}\n\t\t\tstdMatches.popFront();\n\t\t\tcount++;\n\t\t}\n\t\treturn matches;\n\t}\n}\n \nunittest\n{\n\timport std.stdio;\n\twriteln("test PReg.matchAll");\n \n\tint count;\n \n\tauto matches1 = PReg.matchAllPatternOrder(`one two`, regex(`(\\w)(\\w)\\w+`), count);\n\tassert(matches1[0][0] == `one`);\n\tassert(matches1[0][1] == `two`);\n\tassert(matches1[1][0] == `o`);\n\tassert(matches1[1][1] == `n`);\n\tassert(matches1[2][0] == `t`);\n\tassert(matches1[2][1] == `w`);\n\tassert(count == 2);\n \n\tauto matches2 = PReg.matchAllSetOrder(`one two`, regex(`(\\w)(\\w)\\w+`), count);\n\tassert(matches2[0][0] == `one`);\n\tassert(matches2[0][1] == `o`);\n\tassert(matches2[0][2] == `n`);\n\tassert(matches2[1][0] == `two`);\n\tassert(matches2[1][1] == `t`);\n\tassert(matches2[1][2] == `w`);\n\tassert(count == 2);\n \n\tauto matches3 = PReg.matchAllOffsetCapture(`one two`, ctRegex!(`(\\w)(\\w)\\w+`), count);\n\tassert(matches3[0][0].text == "one");\n\tassert(matches3[0][0].position == 0);\n\tassert(matches3[0][1].text == "two");\n\tassert(matches3[0][1].position == 4);\n \n\tassert(matches3[1][0].text == "o");\n\tassert(matches3[1][0].position == 0);\n\tassert(matches3[1][1].text == "t");\n\tassert(matches3[1][1].position == 4);\n \n\tassert(matches3[2][0].text == "n");\n\tassert(matches3[2][0].position == 1);\n\tassert(matches3[2][1].text == "w");\n\tassert(matches3[2][1].position == 5);\n\tassert(count == 2);\n}Надеюсь, что из этой статьи вы подчеркнули для себя что-то полезное и интересное. Всем спасибо!
\nПосле реализации флагов обязательно добавьте тесты на три случая: нет совпадений, одно совпадение с группами и несколько совпадений с позициями. Именно на позициях чаще всего появляются ошибки, потому что PHP возвращает смещения в исходной строке, а не в подстроке после очередного поиска.
\nassert(matchAll(r\"(w+)=(d+)\", \"a=1 b=22\").length == 2);\nassert(matchAll(r\"z+\", \"abc\").empty);\nВ D стоит явно решить, какой формат результата нужен. PHP поддерживает несколько режимов: PREG_PATTERN_ORDER, PREG_SET_ORDER и вариант с PREG_OFFSET_CAPTURE. Если пытаться сделать всё сразу, код быстро станет запутанным. Лучше сначала реализовать простой режим, затем режим группировки по совпадениям, и только после этого добавить позиции.
\nstruct MatchPart\n{\n string text;\n ptrdiff_t offset;\n}\n\nalias MatchSet = MatchPart[][];\nОтдельно проверьте Unicode. Если шаблон и строка содержат кириллицу, важно понимать, что именно возвращается в offset: позиция в байтах или позиция в символах. PHP для PREG_OFFSET_CAPTURE возвращает байтовое смещение. Если в D вы хотите повторить поведение PHP, нужно документировать именно байтовый offset, иначе результаты будут отличаться.
\nФинальный класс должен иметь маленькую поверхность API: метод без offset, метод с offset и понятный enum для порядка результата. Тогда аналог preg_match_all будет не просто копией PHP-функции, а удобным D-инструментом, который предсказуемо работает в тестах.
", + "contentHtml": "В PHP есть очень удобная функция для глобального поиска шаблона регулярного выражения в строке preg_match_all. Давайте напишем аналогичный класс статических методов для реализации этой функции с разными флагами на языке программирования D.
\nОписание функции:
\n``` int preg_match_all ( string $pattern , string $subject [, array &$matches [, int $flags = PREG_PATTERN_ORDER [, int $offset = 0 ]]] )
\n\nФункция ищет в строке *subject* все совпадения с шаблоном pattern и помещает результат в массив *matches* в порядке, определяемом комбинацией флагов *flags*.\n После нахождения первого соответствия последующие поиски будут осуществляться не с начала строки, а от конца последнего найденного вхождения.\n\nДанная функцию очень удобна и чаще всего она применяется с третьим параметром, чтобы обработать полученный массив совпадений шаблона. В *D* подобной регулярной функции нет. Мне вообще не сильно нравятся, как реализованы регулярные выражения в *D*. Ну да ладно.\n\nПолную документацию по функции можете посмотреть здесь [http://php.net/manual/ru/function.preg-match-all.php](http://php.net/manual/ru/function.preg-match-all.php).\n\nВозможные флаги функции *preg_match_all*:\n\n- *PREG_PATTERN_ORDER* – упорядочивает результаты так, что элемент *$matches[0]* содержит массив полных вхождений шаблона, элемент *$matches[1]* содержит массив вхождений первой подмаски, и так далее;\n- *PREG_SET_ORDER* – упорядочивает результаты так, что элемент *$matches[0]* содержит первый набор вхождений, элемент *$matches[1]* содержит второй набор вхождений, и так далее;\n- *PREG_OFFSET_CAPTURE* – в случае, если этот флаг указан, для каждой найденной подстроки будет указана ее позиция в исходной строке. Необходимо помнить, что этот флаг меняет формат возвращаемого массива matches в массив, каждый элемент которого содержит массив, содержащий в индексе с номером *0* найденную подстроку, а смещение этой подстроки в параметре *subject* — в индексе *1*.\n\nНе будем заморачиваться с реализацией через передачу константы, как в *PHP,* или через шаблоны в *D*. Создадим для каждого флага *PHP*данной функции аналогичные статические методы. Параметр *offset* оставим за бортом. Класс давайте назовём *PReg*. Вырисовывается следующая структура:\n\nclass PReg { public static:
\n… matchAllPatternOrder (...) {...} … matchAllSetOrder (...){...} … matchAllOffsetCapture (…) {...} }
\n\nКак параметры будем передавать строку для поиска (тип *string*), регулярное выражение (тип *???*) и переменную для вывода количества совпадений шаблона регулярного выражения (тип *out int*). Тип регулярного выражения можно посмотреть, чтобы не капаться в модуле, применив такую хитрость:\n\nwriteln(typeof(regex(\\d+)).stringof);
\nТакже как аргумент вы без проблем сможете передавать compile-time регулярное выражение (*ctRegex*). Для данного типа создадим алиас типа, чтобы поудобнее было.\n Функция *matchAllPatternOrde*r и *matchAllSetOrde*r будет возвращать двумерный массив строк, для них тоже создадим алиас типов (мне так кажется, что путаницы меньше). *MatchAllOffsetCapture* пока оставим на закуску.\n\nВ итоге у нас получается:\n\nclass PReg { private static alias typeRegex = Regex!char;
\npublic static: alias typePatternOrder = string[]; alias typeSetOrder = string[];
\ntypePatternOrder[] matchAllPatternOrder (string subject, typeRegex obRegex, out int count) {..} typeSetOrder[] matchAllSetOrder (string subject, typeRegex obRegex, out int count){...} … matchAllOffsetCapture (string subject, typeRegex obRegex, out int count)\t{...} }
\n\nПривожу пример реализованной функции *matchAllPatternOrder* (основные моменты пояснены ниже):\n\ntypePatternOrder[] matchAllPatternOrder (string subject, typeRegex obRegex, out int count) { typePatternOrder[] matches; auto stdMatches = matchAll(subject, obRegex);
\nwhile (!stdMatches.empty) { matches.length++; matches[0] ~= stdMatches.front.hit;
\nfor (int i = 1; i < stdMatches.front.length; i++) { matches.length++; matches[count+1] ~= stdMatches.front[i]; } stdMatches.popFront(); count++; } return matches; }
\n- Функция *matchAll* осуществляет глобальный поиск шаблона регулярного выражения obRegex.\n- Цикл *while* файл перебирает найденные совпадения пока они не закончатся (проверка с помомощью *stdMatches.empty*);\n- *stdMatches.front* хранит строку и найденные подстроки;\n- *stdMatches.front.hit* возвращает всё строку входящую в найденный шаблон;\n- *stdMatches.front[i]* возвращает найденные части по позиции маски;\n- *stdMatches.popFront(*) переход к следующей найденной строке.\n\nРеализация функции *matchAllSetOrder* по сути ничем сильно не отличается кроме как позициями найденных строк в массиве (она даже проще). Приведу лишь реализованный вариант:\n\ntypeSetOrder[] matchAllSetOrder (string subject, typeRegex obRegex, out int count) { typeSetOrder[] matches; auto stdMatches = matchAll(subject, obRegex);
\nwhile (!stdMatches.empty) { for (int i = 0; i < stdMatches.front.length; i++) { matches.length++; matches[count] ~= stdMatches.front[i]; } stdMatches.popFront(); count++; } return matches; }
\n\nРеализация функции *matchAllOffsetCapture* имеет свои особенности. Найденные данные функции будем хранить в двумерном массиве картежа типов *Tuple!(string, «text», int, «position»)*, по которому можно будет получить позицию и найденую строку. Реализация:\n\nalias typeOffsetCapture = Tuple!(string, "text", int, "position")[];
\ntypeOffsetCapture[] matchAllOffsetCapture (string subject, typeRegex obRegex, out int count) { typeOffsetCapture[] matches;
\nauto stdMatches = matchAll(subject, obRegex); matches.length = stdMatches.front.length;
\nwhile (!stdMatches.empty) { Tuple!(string, "text", int, "position") match; match.text = stdMatches.front.hit; match.position = cast(int)(match.text.ptr - subject.ptr); matches[0].length = count+1; matches[0][count] = match; for (int i = 1; i < stdMatches.front.length; i++) { matches[i].length = matches[0].length; match.text = stdMatches.front[i]; match.position = cast(int)(stdMatches.front[i].ptr - subject.ptr); matches[i][count] = match; } stdMatches.popFront(); count++; } return matches; }
\n\nЕдинственное, что здесь важно отметить, как определяются позиции, а именно:\n\nmatch.position = cast(int)(match.text.ptr - subject.ptr);
\n\nПозиция определяется через разность указателей позиций элементов массива. Также здесь выполняется приведение типа к *int*, так как многие процессоры уже использует 64-х битное представление, и указатели будут иметь тип*long*.\n\nПолная реализация модуля с тестами представлена ниже:\n\nmodule preg;
\nimport std.string: format; import std.typecons; import std.regex: Regex, regex, ctRegex, matchFirst, matchAll;
\n// analog of preg regex in php
\nclass PReg { private static alias typeRegex = Regex!char;
\npublic static:
\nalias typePatternOrder = string[]; alias typeSetOrder = string[]; alias typeOffsetCapture = Tuple!(string, "text", int, "position")[];
\ntypePatternOrder[] matchAllPatternOrder (string subject, typeRegex obRegex, out int count) { typePatternOrder[] matches; auto stdMatches = matchAll(subject, obRegex);
\nwhile (!stdMatches.empty) { matches.length++; matches[0] ~= stdMatches.front.hit;
\nfor (int i = 1; i < stdMatches.front.length; i++) { matches.length++; matches[count+1] ~= stdMatches.front[i]; } stdMatches.popFront(); count++; } return matches; }
\ntypeSetOrder[] matchAllSetOrder (string subject, typeRegex obRegex, out int count) { typeSetOrder[] matches;
\nauto stdMatches = matchAll(subject, obRegex);
\nwhile (!stdMatches.empty) { for (int i = 0; i < stdMatches.front.length; i++) { matches.length++; matches[count] ~= stdMatches.front[i]; } stdMatches.popFront(); count++; } return matches; }
\ntypeOffsetCapture[] matchAllOffsetCapture (string subject, typeRegex obRegex, out int count) { typeOffsetCapture[] matches;
\nauto stdMatches = matchAll(subject, obRegex); matches.length = stdMatches.front.length;
\nwhile (!stdMatches.empty) { Tuple!(string, "text", int, "position") match; match.text = stdMatches.front.hit; match.position = cast(int)(match.text.ptr - subject.ptr); matches[0].length = count+1; matches[0][count] = match; for (int i = 1; i < stdMatches.front.length; i++) { matches[i].length = matches[0].length; match.text = stdMatches.front[i]; match.position = cast(int)(stdMatches.front[i].ptr - subject.ptr); matches[i][count] = match; } stdMatches.popFront(); count++; } return matches; } }
\nunittest { import std.stdio; writeln("test PReg.matchAll");
\nint count;
\nauto matches1 = PReg.matchAllPatternOrder(one two, regex((\\w)(\\w)\\w+), count); assert(matches1[0][0] == one); assert(matches1[0][1] == two); assert(matches1[1][0] == o); assert(matches1[1][1] == n); assert(matches1[2][0] == t); assert(matches1[2][1] == w); assert(count == 2);
auto matches2 = PReg.matchAllSetOrder(one two, regex((\\w)(\\w)\\w+), count); assert(matches2[0][0] == one); assert(matches2[0][1] == o); assert(matches2[0][2] == n); assert(matches2[1][0] == two); assert(matches2[1][1] == t); assert(matches2[1][2] == w); assert(count == 2);
auto matches3 = PReg.matchAllOffsetCapture(one two, ctRegex!((\\w)(\\w)\\w+), count); assert(matches3[0][0].text == "one"); assert(matches3[0][0].position == 0); assert(matches3[0][1].text == "two"); assert(matches3[0][1].position == 4);
assert(matches3[1][0].text == "o"); assert(matches3[1][0].position == 0); assert(matches3[1][1].text == "t"); assert(matches3[1][1].position == 4);
\nassert(matches3[2][0].text == "n"); assert(matches3[2][0].position == 1); assert(matches3[2][1].text == "w"); assert(matches3[2][1].position == 5); assert(count == 2); }
\n\nНадеюсь, что из этой статьи вы подчеркнули для себя что-то полезное и интересное. Всем спасибо!\n\n## Как довести класс до рабочего состояния\n\nПосле реализации флагов обязательно добавьте тесты на три случая: нет совпадений, одно совпадение с группами и несколько совпадений с позициями. Именно на позициях чаще всего появляются ошибки, потому что PHP возвращает смещения в исходной строке, а не в подстроке после очередного поиска.\n\nassert(matchAll(r"(w+)=(d+)", "a=1 b=22").length == 2); assert(matchAll(r"z+", "abc").empty);
\n\nВ D стоит явно решить, какой формат результата нужен. PHP поддерживает несколько режимов: *PREG_PATTERN_ORDER*, *PREG_SET_ORDER* и вариант с *PREG_OFFSET_CAPTURE*. Если пытаться сделать всё сразу, код быстро станет запутанным. Лучше сначала реализовать простой режим, затем режим группировки по совпадениям, и только после этого добавить позиции.\n\nstruct MatchPart { string text; ptrdiff_t offset; }
\nalias MatchSet = MatchPart[][];
\n\nОтдельно проверьте Unicode. Если шаблон и строка содержат кириллицу, важно понимать, что именно возвращается в offset: позиция в байтах или позиция в символах. PHP для *PREG_OFFSET_CAPTURE* возвращает байтовое смещение. Если в D вы хотите повторить поведение PHP, нужно документировать именно байтовый offset, иначе результаты будут отличаться.\n\nФинальный класс должен иметь маленькую поверхность API: метод без offset, метод с offset и понятный enum для порядка результата. Тогда аналог *preg_match_all* будет не просто копией PHP-функции, а удобным D-инструментом, который предсказуемо работает в тестах.",
"readingMinutes": 7
},
{
@@ -135,7 +135,7 @@
],
"cover": "/assets/illustrations/ctfe-engine.svg",
"excerpt": "В течение последних 9 месяцев велась работа над проектом под названием NewCTFE, в котором переписываются методы выполнения функций времени компиляции (СTFE) . СTFE считается одной из технологий способных изменить D. Как следует из названия, CTFE позволяет комп",
- "contentHtml": "В течение последних 9 месяцев велась работа над проектом под названием NewCTFE, в котором переписываются методы выполнения функций времени компиляции (СTFE). СTFE считается одной из технологий способных изменить D.
\nКак следует из названия, CTFE позволяет компилятору выполнять некоторые функции, когда он компилирует исходный код, в котором реализованы функции. Пока все аргументы функции доступны во время компиляции, а функция чиста (не имеет побочных эффектов), тогда функция квалифицируется как CTFE, и компилятор заменяет вызов функции результатом.
\n\nПоскольку это неотъемлемая часть языка, чистые функции могут быть вычислены везде, где может находиться константа времени компиляции. Простой пример можно найти в стандартном модуле std.uri, где CTFE используется для вычисления таблицы поиска. Это выглядит так:
\n\nprivate immutable ubyte[128] uri_flags = // indexed by character\n({\nubyte[128] uflags;\n// Compile time initialize\nuflags['#'] |= URI_Hash;\nforeach (c; 'A' .. 'Z' + 1)\n{\nuflags[c] |= URI_Alpha;\nuflags[c + 0x20] |= URI_Alpha; // lowercase letters\n}\nforeach (c; '0' .. '9' + 1) uflags[c] |= URI_Digit;\nforeach (c; ";/?:@&=+$,") uflags[c] |= URI_Reserved;\nforeach (c; "-_.!~*'()") uflags[c] |= URI_Mark;\nreturn uflags;\n})();Вместо заполнения таблицы магическими значениями используется простой экспрессивный литерал функции. Это намного проще понять и отладить, чем некоторые непрозрачные статические массивы. ({ запускает функцию-литерал, а }) закрывает ее. () в конце говорит компилятору немедленно вызвать этот литерал, чтобы uri_flags стал результатом литерала.
\nФункции выполняются только во время компиляции, если они необходимы. Uri_flags в приведенном выше фрагменте объявляется в области видимости модуля. Когда переменная области видимости модуля инициализируется таким образом, инициализатор должен быть доступен во время компиляции. В этом случае, поскольку инициализатор является функциональным литералом, будет предпринята попытка выполнить CTFE. Этот конкретный литерал не имеет аргументов и является чистым, поэтому попытка выполнена успешно.
\nБолее подробное обсуждение CTFE смотрите в статье H. S. Teoh D Wiki.
\nКонечно, подобный метод может быть применен и к более сложным проблемам. Например, std.regex можно использовать со специализированным автоматом для выполнения регулярного выражения во время компиляции с использованием CTFE. Однако, как только std.regex используется с CTFE для нетривиальных паттернов, то время компиляции может стать чрезвычайно длительным (в D все, что занимает больше секунды при компиляции — избыточность :)). В конце концов, по мере усложнения паттернов, у компилятора появится нехватка памяти, и, возможно, произойдёт крах всей системы.
\nПричина этого может крыться в текущей архитектуре интерпретатора CTFE. Это интерпретатор AST (абстрактного синтаксического дерева) — это означает, что он интерпретирует AST во время его обхода. Чтобы представить результат интерпретируемых выражений, он использует классы узлов DMD в AST. Это означает, что на каждое вновь встреченное выражение будет выделено один или несколько узлов AST. В ограниченном цикле интерпретатор может легко создать более 100.000.000 узлов и использовать несколько гигабайт оперативной памяти. Это может быстро израсходовать память.
\nВ issue 12844 имеет место проблема, что std.regex занимает более 16 ГБ ОЗУ для одного шаблона. Также есть issue 6498 , которая показывает, что выполняется простой от 0 до 10.000.000 узлов во время выполнения CTFE и что приводит к критичной нехватки памяти.
\nПростое освобождение узлов не устраняет проблему, так мы точно не знаем, какие узлы необходимо освободить, и что cделает весь компилятор очень медленным из-за сборщика мусора. К счастью, есть еще один подход, который не выделяет память для каждого вновь встреченного выражения. Он включает в себя компиляцию функции в виртуальную ISA (архитектуру набора инструкций). Эта виртуальная ISA, также известная как байт-код, затем передается выделенному интерпретатору для этой ISA (в случае, когда виртуальный ISA совпадает с ISA хоста, мы называем его JIT (Just in Time) интерпретатором).
\nПроект NewCTFE занимается реализацией такого интерпретатора байт-кода. Написание фактического интерпретатора (эмулятора CPU для виртуального CPU/ISA) достаточно просто. Однако компиляция кода для виртуального ISA выполняется в точности так же, как и при компиляции его в реальном ISA (хотя виртуальная ISA имеет дополнительное преимущество, которое может быть расширено для индивидуальных потребностей, но это затруднит выполнение JIT позже). Вот почему потребовался всего месяц, чтобы пучить первые простые примеры, работающие на новом движке CTFE, и почему немного более сложные из них все еще не работают даже после 9 месяцев разработки. В конце статьи вы найдете примерный график выполненной к настоящему времени работы (см. оригинал).
\nЯ буду выступать с презентацией на DConf 2017, где я расскажу о своем опыте внедрения движка и объясню некоторые технические детали, особенно относительно компромиссов и архитектурных решений, которые я применил. Текущая оценка состоит в том, что версия 1.0 не будет реализована к тому времени, но я буду заниматься разработкой, пока не закончу данный проект.
\nТе, кто хочет отслеживать разработку, могут сделать это на форуме D. В будущем я планирую написать еще одну статью о некоторых технических деталях реализации. К тому времени, я надеюсь, что следующий список поможет пролить свет на то, как много работы по реализации NewCTFE.
\nДанная статья является переводом статьи Штефана Коха, который является разработчиком sqlite-d, встроенного пакета в D для работы с sqlite, также он внес свой вклад в такие проекты, как SDC (Stupid D Compiler) и vibe.d. Он также был ответственен за 10% -ное повышение производительности в текущей реализации CTFE в D и в настоящее время пишет новый движок CTFE.
\nОригинал статьи смотрите по ссылке The D Blog/The New CTFE Engine.
\nCTFE полезен там, где результат можно посчитать один раз во время компиляции: таблицы констант, парсинг небольших DSL, генерация однотипного кода, предварительная подготовка строк и проверка инвариантов. Но не стоит превращать компиляцию в полноценный runtime. Чем проще входные данные и чем меньше побочных эффектов, тем легче будет сопровождать проект.
\nenum table = buildLookupTable();\n\nstatic assert(table.length == 256);\nХороший кандидат для CTFE — функция, которая зависит только от литералов, типов и конфигурации сборки. Плохой кандидат — код, которому нужен ввод-вывод, сеть, текущее время, состояние окружения или большая внешняя база данных. Такой код лучше оставить в runtime или вынести в отдельный генератор, который запускается до сборки.
\nДля контроля качества CTFE-кода полезно держать рядом static assert. Он сразу показывает, что вычисление действительно произошло на этапе компиляции и вернуло ожидаемый результат. Если compile-time функция стала слишком сложной, её стоит разбить на маленькие чистые функции: так компилятору проще, и человеку проще понять, что происходит.
\nПрактический вывод такой: CTFE в D — мощный инструмент, но сила его не в магии, а в переносе предсказуемых вычислений из runtime в compile-time. Используем его для констант, генерации и проверок. Не используем его как замену обычной программе.
", + "contentHtml": "В течение последних 9 месяцев велась работа над проектом под названием NewCTFE, в котором переписываются методы выполненияфункций времени компиляции (СTFE). СTFE считается одной из технологий способных изменить D.
\nКак следует из названия, CTFE позволяет компилятору выполнять некоторые функции, когда он компилирует исходный код, в котором реализованы функции. Пока все аргументы функции доступны во время компиляции, а функция чиста (не имеет побочных эффектов), тогда функция квалифицируется как CTFE, и компилятор заменяет вызов функции результатом.
\nПоскольку это неотъемлемая часть языка, чистые функции могут быть вычислены везде, где может находиться константа времени компиляции. Простой пример можно найти в стандартном модуле std.uri, где CTFE используется для вычисления таблицы поиска. Это выглядит так:
\nprivate immutable ubyte[128] uri_flags = // indexed by character\n({\nubyte[128] uflags;\n// Compile time initialize\nuflags['#'] |= URI_Hash;\nforeach (c; 'A' .. 'Z' + 1)\n{\nuflags[c] |= URI_Alpha;\nuflags[c + 0x20] |= URI_Alpha; // lowercase letters\n}\nforeach (c; '0' .. '9' + 1) uflags[c] |= URI_Digit;\nforeach (c; ";/?:@&=+$,") uflags[c] |= URI_Reserved;\nforeach (c; "-_.!~*'()") uflags[c] |= URI_Mark;\nreturn uflags;\n})();\nВместо заполнения таблицы магическими значениями используется простой экспрессивный литерал функции. Это намного проще понять и отладить, чем некоторые непрозрачные статические массивы. ({ запускает функцию-литерал, а }) закрывает ее. () в конце говорит компилятору немедленно вызвать этот литерал, чтобы uri_flags стал результатом литерала.
\nФункции выполняются только во время компиляции, если они необходимы. Uri_flags в приведенном выше фрагменте объявляется в области видимости модуля. Когда переменная области видимости модуля инициализируется таким образом, инициализатор должен быть доступен во время компиляции. В этом случае, поскольку инициализатор является функциональным литералом, будет предпринята попытка выполнить CTFE. Этот конкретный литерал не имеет аргументов и является чистым, поэтому попытка выполнена успешно.
\nБолее подробное обсуждение CTFE смотрите в статье H. S. Teoh D Wiki.
\nКонечно, подобный метод может быть применен и к более сложным проблемам. Например, std.regex можно использовать со специализированным автоматом для выполнения регулярного выражения во время компиляции с использованием CTFE. Однако, как только std.regex используется с CTFE для нетривиальных паттернов, то время компиляции может стать чрезвычайно длительным (в D все, что занимает больше секунды при компиляции — избыточность :)). В конце концов, по мере усложнения паттернов, у компилятора появится нехватка памяти, и, возможно, произойдёт крах всей системы.
\nПричина этого может крыться в текущей архитектуре интерпретатора CTFE. Это интерпретатор AST (абстрактного синтаксического дерева) — это означает, что он интерпретирует AST во время его обхода. Чтобы представить результат интерпретируемых выражений, он использует классы узлов DMD в AST. Это означает, что на каждое вновь встреченное выражение будет выделено один или несколько узлов AST. В ограниченном цикле интерпретатор может легко создать более 100.000.000 узлов и использовать несколько гигабайт оперативной памяти. Это может быстро израсходовать память.
\nВ issue 12844 имеет место проблема, что std.regex занимает более 16 ГБ ОЗУ для одного шаблона. Также есть issue 6498 , которая показывает, что выполняется простой от 0 до 10.000.000 узлов во время выполнения CTFE и что приводит к критичной нехватки памяти.
\nПростое освобождение узлов не устраняет проблему, так мы точно не знаем, какие узлы необходимо освободить, и что cделает весь компилятор очень медленным из-за сборщика мусора. К счастью, есть еще один подход, который не выделяет память для каждого вновь встреченного выражения. Он включает в себя компиляцию функции в виртуальную ISA (архитектуру набора инструкций). Эта виртуальная ISA, также известная как байт-код, затем передается выделенному интерпретатору для этой ISA (в случае, когда виртуальный ISA совпадает с ISA хоста, мы называем его JIT (Just in Time) интерпретатором).
\nПроект NewCTFE занимается реализацией такого интерпретатора байт-кода. Написание фактического интерпретатора (эмулятора CPU для виртуального CPU/ISA) достаточно просто. Однако компиляция кода для виртуального ISA выполняется в точности так же, как и при компиляции его в реальном ISA (хотя виртуальная ISA имеет дополнительное преимущество, которое может быть расширено для индивидуальных потребностей, но это затруднит выполнение JIT позже). Вот почему потребовался всего месяц, чтобы пучить первые простые примеры, работающие на новом движке CTFE, и почему немного более сложные из них все еще не работают даже после 9 месяцев разработки. В конце статьи вы найдете примерный график выполненной к настоящему времени работы (см. оригинал).
\nЯ буду выступать с презентацией на DConf 2017, где я расскажу о своем опыте внедрения движка и объясню некоторые технические детали, особенно относительно компромиссов и архитектурных решений, которые я применил. Текущая оценка состоит в том, что версия 1.0 не будет реализована к тому времени, но я буду заниматься разработкой, пока не закончу данный проект.
\nТе, кто хочет отслеживать разработку, могут сделать это на форуме D. В будущем я планирую написать еще одну статью о некоторых технических деталях реализации. К тому времени, я надеюсь, что следующий список поможет пролить свет на то, как много работы по реализации NewCTFE.
\nДанная статья является переводом статьи Штефана Коха, который является разработчиком sqlite-d, встроенного пакета в D для работы с sqlite, также он внес свой вклад в такие проекты, как SDC (Stupid D Compiler) и vibe.d. Он также был ответственен за 10% -ное повышение производительности в текущей реализации CTFE в D и в настоящее время пишет новый движок CTFE.
\nОригинал статьи смотрите по ссылке The D Blog/The New CTFE Engine.
\nCTFE полезен там, где результат можно посчитать один раз во время компиляции: таблицы констант, парсинг небольших DSL, генерация однотипного кода, предварительная подготовка строк и проверка инвариантов. Но не стоит превращать компиляцию в полноценный runtime. Чем проще входные данные и чем меньше побочных эффектов, тем легче будет сопровождать проект.
\nenum table = buildLookupTable();\n\nstatic assert(table.length == 256);\nХороший кандидат для CTFE — функция, которая зависит только от литералов, типов и конфигурации сборки. Плохой кандидат — код, которому нужен ввод-вывод, сеть, текущее время, состояние окружения или большая внешняя база данных. Такой код лучше оставить в runtime или вынести в отдельный генератор, который запускается до сборки.
\nДля контроля качества CTFE-кода полезно держать рядом static assert. Он сразу показывает, что вычисление действительно произошло на этапе компиляции и вернуло ожидаемый результат. Если compile-time функция стала слишком сложной, её стоит разбить на маленькие чистые функции: так компилятору проще, и человеку проще понять, что происходит.
\nПрактический вывод такой: CTFE в D — мощный инструмент, но сила его не в магии, а в переносе предсказуемых вычислений из runtime в compile-time. Используем его для констант, генерации и проверок. Не используем его как замену обычной программе.
", "readingMinutes": 6 }, { diff --git a/web/package.json b/web/package.json index 658053b..6ca30da 100644 --- a/web/package.json +++ b/web/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "dev": "next dev", + "admin:posts": "node ../local-admin/posts-admin.mjs", "build": "next build", "start": "next start" },