В начале статьи хочу признаться, что я не верстальщик и не владею PHP, JavaScript или CSS на должном уровне, поэтому всё, что написано ниже, используйте на свой страх и риск. Возможно, это неправильно и надо делать не так. Я не знаю, как надо правильно, учусь самостоятельно, поэтому пишу, как умею хрум-хрум-хрум.
Решил привести в порядок свою главную страницу блога. Не нравятся мне длинные посты, которые нужно долго листать, чтобы перейти к следующему. Изначально рассматривал два варианта:
- Кнопка
[Пропустить пост]для быстрой перемотки к следующему посту и возвращению к предыдущему - Кнопка
[Развернуть]для динамической загрузки остальной части поста после тега<!--more-->.
В итоге выбрал третий путь: весь пост загружается сразу, но часть после тега <!--more--> показывается или скрывается по клику на кнопку [развернуть/свернуть].
Я выбрал этот вариант по двум причинам:
- Скрытая часть поста должна корректно обрабатываться скриптами по событию
onload/onready. - Размер постов у меня небольшой.
Реализация
Для начала нужно выбрать способ обработки поста. Если вызвать get_the_content(), то возвращается только часть до тега. Можно обойти это поведение, используя глобальную переменную $more, но такое решение выглядит как костыль:
function my_the_content() {
global $more;
$prev_more = $more;
$more = 1;
$content = apply_filters('the_content', get_the_content());
$more = $prev_more;
}
Я решил брать текст поста из поля post_content объекта $post и делить его с помощью функции get_extended().
В итоге, я заменил вызов the_content() в своей теме на my_the_content($post) такого вида (код нужно добавить в functions.php вашей темы WordPress):
function my_the_content($post) {
// На странице поста стандартное поведение
if (is_singular()) {
the_content();
return;
}
// Получаем текст целиком из поля post_content и применяем фильтры
$content = apply_filters('the_content', $post->post_content);
// Разбиваем на несколько частей по тегу <!--more-->
$parts = get_extended($content);
// Часть после тега
$extended = $parts['extended'];
// После тега ничего нет, заканчиваем работу
if (empty($extended)) {
return;
}
// Поддержка роботов и тех, кому отключили JavaScript за неуплату
echo '<noscript>';
echo '<div>'.$extended.'</div>';
echo '<style>.post-body {display: none; }</style>';
echo '</noscript>';
// Храним всю дополнительную часть в секции post-body,
// классы expanded/collapsed используются для регулировки видимости элементов
echo '<div class="post-body">';
// Прячем кнопки в отдельном контейнере для удобства
echo '<div class="toggle-buttons">';
// Две кнопки, одна видна, другая спрятана
echo '<div class="toggle-post-body expanded">';
echo '<div class="label expand">развернуть</div>';
echo '</div>';
echo '<div class="toggle-post-body collapsed">';
echo '<div class="label collapse">свернуть</div>';
echo '</div>';
echo '</div>';
// Тело поста спрятано по умолчанию
echo '<div class="post-body-content collapsed">'.$extended.'</div>';
echo '</div>';
}
Стилизация
Теперь стили для блока .post-body. Все изменения затрагивают только элементы внутри него:
/* Элементы с классом collapsed внутри post-body скрыты */
.post-body .collapsed {
display: none;
}
/* Элементы с классом expanded внутри post-body видны */
.post-body .expanded {
display: block;
}
/* Общие настройки для кнопок-переключателей */
.post-body .toggle-post-body .label {
color: #cc6633;
cursor: pointer;
margin-bottom: 0.5em;
border-bottom: 1px dashed;
font-size: 90%;
}
/* Треугольник для кнопки "свернуть" */
.post-body .toggle-post-body .label.collapse:before {
content: '\25BC';/* ▼ */
margin-right: 5px;
}
/* Треугольник для кнопки "развернуть" */
.post-body .toggle-post-body .label.expand:before {
content: '\25B6'; /* ▶ */
margin-right: 5px;
}
Логика на JavaScript:
И, наконец, скрипт, который переключает видимость:
// Добавляем обработку клику по элементам с классом "toggle-post-body"
jQuery('.toggle-post-body').click(function(event){
var nextButton = jQuery(this)
.toggleClass('expanded')
.toggleClass('collapsed')
.siblings('.toggle-post-body');
nextButton
.toggleClass("expanded")
.toggleClass("collapsed");
var contentBlock = nextButton
.closest(".toggle-buttons")
.next('.post-body-content');
contentBlock
.toggleClass("expanded")
.toggleClass("collapsed");
});
Заключение
Подводя итог, логика работы выглядит следующим образом:
- Расширенная часть поста по умолчанию скрыта (класс .collapsed).
- При клике на видимую кнопку (например, «развернуть») запускается скрипт.
- Скрипт переключает видимость у нажатой кнопки (скрывая её) и у соседней (показывая её).
- Одновременно скрипт меняет видимость у блока с контентом.
Недостатки
- Пользователь не может перейти на страницу с самим постом, кроме как кликнув на заголовок.
- Пришлось убрать блок кнопки Social Likes с главной страницы для того, чтобы люди могли делиться контентом в социальных сетях. Но они этого и так не делают, так что невелика потеря.
- Некоторые мои записи используют канвас для рисования с помощью WebGL или JavaScript. Для них пришлось делать уникальный идентификатор канвасов для каждого поста.
- Страница грузит содержимое всех постов, что может быть накладно, если все посты на странице рисуют графику, например.
- Пришлось переписать каждый пост с меткой «Интерактив», чтобы каждый скрипт писал в свой уникальный canvas плюс немного исправить вызов
requestAnimationFrame. Зато выложил все скрипты на GitHub и добавил каждому демонстрационныйindex.html.
UPD: Немного упростил логику. Теперь у меня два элемента типа toggle-post-body. По кнопке их видимость меняется местами. Можете посмотреть исходники плагина b2k-tools с новейшей реализацией на (GitHub)[https://github.com/jirnov/b2k-tools].
Раз вы дочитали до конца, как считаете: нужно ли добавить кнопку «сверхнуть» в конец поста для быстрой навигации?
Вопросы и ответы для собеседования и подготовки Senior C++ разработчика
Всем привет! Решил систематизировать и записать актуальные на 2025 год вопросы и ответы программисту в позиции Senior C++. Вы можете это использовать как для проведения собеседований в качестве интервьюера, так и для подготовки к ним. Считаю, это самая скучная статья на моём сайте, но лучший способ что-то запомнить — это записать и систематизировать свои знания.
Немного о себе: пишу на C++ уже восемнадцатый год и немножко понимаю в программировании.
Кстати, недавно узнал, что человек запоминает 25% любой информации, но надолго. И вот смотрю я на эту прорву текста и думаю — если это всего лишь четверть, то ни фига себе — насколько огромная часть знаний прошла мимо меня.
std::shared_mutex.
Где располагаются переменные, чем инициализированы, область видимости:
int a;
static int b;
int c = 30;
const int d = 20; // без слова "extern" переменная d видна только в этом файле
void function() {
int fa;
int fb = 10;
const int fc = 5;
static int fd; // инициализируется нулём при первом вызове функции
static int fe = 1; // инициализируется единичкой при первом вызове функции
}
Если мы объявляем переменную в глобальном пространстве, то указание static делает видимость этой переменной только в этом файле. Указание константности приводит (в зависимости от желания компилятора) помещение его значения по умолчанию в секцию .rodata исполняемого файла или сразу встраивание этого значения в код вместо переменной. Все переменные объявленные вне функций инициализированы нулём по умолчанию и живут в сегменте памяти BSS, который выделяется и заполняется нулями при запуске программы, его размер записан в исполняемом файле. Не const переменные сохраняют своё значение в сегменте памяти DATA исполняемого файла. Этот сегмент копируется в память компьютера при запуске программы. Все глобальные переменные живут в статической памяти (BSS/DATA/.rodata). Статическая память выделятся при старте программы, имеет постоянный размер и существует в течение всей жизни программы. Размещение переменных по сегментам статической памяти — это поведение на этапе компоновки, описанное в стандартных соглашениях (Application Binary Interface, ABI). Сводная таблица всех переменных из примера выше:
| Переменная | Инициализация | Сегмент памяти | Видимость |
|---|---|---|---|
int a; |
Неявная (0) | BSS | Глобальная |
static int b; |
Неявная (0) | BSS | Файловая |
int c = 30; |
Явная (30) | DATA | Глобальная |
const int d = 20; |
Явная (20) | .rodata или inline | Файловая |
int fa; |
Мусор | стек | Блочная |
int fb = 10; |
Явная (10) | стек | Блочная |
const int fc = 5; |
Явная (5) | стек или inline | Блочная |
static int fd; |
Неявная (0) | BSS | Блочная |
static int fe = 1; |
Явная (1) | DATA | Блочная |
Правила вывода типа для auto
auto по умолчанию создает копию без ссылок и квалификаторов (const, volatile) верхнего уровня. auto сохраняет низкоуровневые квалификаторы (касающиеся указываемых данных), но отбрасывает высокоуровневые (касающиеся самой переменной-указателя).
Пример:
int x = 42;
const int *p1 = &x; // нельзя менять данные, но можно указатель
int *const p2 = &x; // нельзя менять указатель, но можно менять данные
const int *const p3 = &x; // нельзя менять указатель, нельзя менять данные
const int &p4 = x; // ссылка на const int
auto a1 = p1; // const int * - оставили const для данных
auto a2 = p2; // int * - убрали const для указателя
auto a3 = p3; // const int * - оставили const для данных, убрали const для указателя
auto a4 = p4; // int - убрали const, убрали ссылку
В итоге, правила для auto:
- указатели: сохраняют квалификаторы низкого уровня
- ссылки: отбрасывает ссылку, отбрасывает
const
Что такое RAII?
Resource Acquisition Is Initialization — дословно «захват ресурса есть инициализация». В конструкторе захватываем ресурс, в деструкторе освобождаем.
Какие бывают умные указатели?
std::shared_ptr— RAII указатель на объект со счётчиком ссылок. Копирование увеличивает счётчик ссылок, в деструкторе счётчик уменьшается. При достижении нуля освобождает память.std::weak_ptr— слабая ссылка на объект, управляемыйstd::shared_ptr. Не увеличивает счётчик ссылок. Для использования нужно преобразовать вstd::shared_ptrс помощью методаlock(). Полезен, чтобы избежать циклических ссылок междуstd::shared_ptr.std::unique_ptr— указатель с исключительным владением объектом. Запрещено копирование, разрешено только перемещение. Для передачи владения используйтеstd::move. Имеет специализацию для массивов:std::unique_ptr<T[]>.
Почему лучше использовать std::make_shared и std::make_unique, когда возможно?
Во время вычисления параметров конструктора возможно исключение, которое приведёт к утечке памяти. Также std::make_shared выделяет память для объекта и контрольного блока за одну аллокацию. В конце концов, читать удобнее.
Что происходит при возникновении исключения.
Выполнение функции прерывается, идёт поиск подходящего catch. Сначала в текущей функции, потом идёт последовательно выход наверх по стеку. При выходе вызываются все деструкторы локальных объектов, которые покидают текущую область видимости. Если подходящий catch не найден, то std::terminate(). Если во время раскрутки стека вызовов происходит повторное исключение, то снова std::terminate().
Что происходит при возникновении исключения в конструкторе/деструкторе?
- Если исключение происходит в конструкторе, для уже созданных членов класса вызываются деструкторы, деструктор этого объекта не вызывается, а дальше всё как обычно
- Деструктор неявно объявлен как
noexceptсо стандарта C++11, поэтому любое исключение, покидающее деструктор вызываетstd::terminate(). Также может возникнуть ситуация, если при обработке исключения в деструкторе во время раскрутки стека вызовов произойдёт исключение — то будетstd::terminate()
Почему память выделенная new/malloc будет утечкой, а умный указатель корректно освободит память при вызове исключения?
Потому что происходит раскрутка стека и очистка локальных переменных, выделенных на стеке, плюс вызываются деструкторы этих переменных. Сырой указатель от new/malloc не имеет деструктора и выделен в куче.
В чём особенность placement new?
При использовании placement new деструктор нужно вызывать вручную. Правильный порядок действий:
// 1. Выделили память
void* memory = ::operator new(sizeof(MyClass));
// 2. Создали объект (placement new)
MyClass* obj = new (memory) MyClass();
// 3. Использовали объект
obj->method();
// 4. Вызвали деструктор
obj->~MyClass();
// 5. Освободили память
::operator delete(memory);
Что такое срез исключения? Когда происходит? Как избежать?
Это происходит когда мы пытаемся перехватить исключение по значению, когда тип исключения в catch-блоке является базовым к брошенному исключению, например вот так:
try {
throw MyException(what, extended_data); // наследник std::exception
}
catch (std::exception e) { // тут происходит СРЕЗ
// Значение extended_data утеряно
}
Поэтому лучший путь — это перехватывать исключение по константной ссылке.
try {
throw MyException(what, extended_data); // наследник std::exception
}
catch (const std::exception &e) {
// Сохраняется полная информация об исключении
}
Что происходит при бросании исключения из noexcept метода?
Немедленный вызов std::terminate() и аварийное завершение программы.
Сколько создаётся vtable и vptr в классах и экземплярах классов?
- vtable — создаётся во время компиляции одна для каждого полиморфного класса, хранится в статической памяти
- vptr — создаётся в конструкторе в одном экземпляре в каждом объекте полиморфного класса, хранится внутри объекта. Сначала vptr указывает на таблицу базового класса, потом уже текущего.
Что происходит при вызове виртуального метода в конструкторе?
В конструкторе таблица виртуальных методов (vtable) указывает на методы текущего класса, значит вызываются методы текущего класса, а не производного. Вызов чисто виртуального метода приведёт к неопределённому поведению или ошибке компиляции.
struct Base {
public:
Base() {
print(); // Вызывается Base::print()
}
virtual void print() { ... }
};
struct Derived : Base {
void print() override { ... }
}
Что происходит при вызове виртуального метода в деструкторе?
То же самое, что в вопросе выше. Только в конструкторе производный класс ещё не построен, а в деструкторе производный класс уже разрушен.
Какие бывают касты в C++.
Целую заметку написал, не буду повторяться: Различные касты в C++.
Что такое семантика перемещения (move semantic)?
Механизм, который, начиная с C++11, позволяет эффективно передавать ресурсы между объектами без дорогостоящего копирования. Использование выглядит так:
std::vector<int> a;
a.resize(1000000); // выделяем много памяти
std::vector<int> b = std::move(a); // перемещаем данные, исходный объект в неопределённом состоянии
После выполнения кода объект b владеет данными, объект a находится в валидном, но неопределённом состоянии. Полезно знать, что std::move только преобразует тип в r-value ссылку и ничего никуда не перемещает, реальное перемещение выполняет конструктор перемещения или оператор перемещения.
Что такое RVO и NRVO
Это оптимизации компилятора под названием Copy elision (пропуск копий), которые позволяют избежать лишнего копирования/перемещения при возврате объектов из функций:
- RVO(Return Value Optimization) — оптимизация безымянного возвращаемого значения, гарантировано стандартом C++17:
HeavyObject create() {
return HeavyObject();
}
- NRVO(Named Return Value Optimization) — оптимизация именованного возвращаемого значения, когда возвращается именованная локальная переменная. Обратите внимание — использование
std::moveпри возврате значения ломает эту оптимизацию!
HeavyObject create() {
HeavyObject a;
return a;
}
Что такое и как использовать std::future/std::promise? В каких случаях std::future бросает ошибку?
future/promise — это механизм передачи данных или исключений между потоками. Одному std::promise соответствует один std::future.
std::promise— устанавливает значение (set_value()) или исключение (set_exception()), которое извлекается с помощьюstd::future.std::future— предоставляет доступ к результату (get()), записанному черезstd::promise. Методget()вызывает исключение, еслиstd::promiseуничтожен без установки значения или исключения. Повторный вызовget()также вызовет ошибку.
Пример для наглядности:
#include <iostream>
#include <future>
#include <thread>
int main() {
auto worker = [](auto p) {
try {
p.set_value("Hello world!");
// Делаем что-то вызывающее исключение
}
catch(...) {
// Передаём исключение
p.set_exception(std::current_exception());
}
};
std::promise<std::string> promise;
auto f = promise.get_future();
// Передаём promise по значению в поток с помощью перемещения
std::thread t(worker, std::move(promise));
try {
std::cout << f.get() << std::endl;
}
catch (const std::exception &e) {
std::cerr << "Exception : " << e.what() << std::endl;
}
t.join();
}
std::future можно использовать для получения значения только один раз и нельзя копировать, если хотите это изменить, создайте std::shared_future с помощью метода std::future::share() — этот класс лишён таких недостатков.
Что такое deadlock и как происходит?
Блокирование потоков в ожидании друг друга. Происходит, когда в потоке A блокируется мьютекс a, потом b. А в потоке B сначала мьютекс b, потом a. Исправляется использованием std::scoped_lock или одинаковым порядком блокирования мьютексов.
Зачем нужен std::shared_mutex?
Вместе с std::shared_lock и std::unique_lock std::shared_mutex обеспечивает множественный доступ (на чтение) при использовании std::shared_lock и эксклюзивный доступ (на запись) при использовании std::unique_lock соответственно.
Пример для наглядности:
std::shared_mutex mutex;
std::vector<int> data;
void write(int value) {
std::unique_lock lock(mutex); // эксклюзивная блокировка
data.push_back(value);
}
int read(size_t index) {
std::shared_lock lock(mutex); // разделяемая блокировка
return data.at(index);
}
Зачем нужно указание memory_order в объектах типа std::atomic? Какие бывают memory_order?
Если вкратце, то memory_order позволяет контролировать модель памяти для атомарных операций. Модель памяти определяет, как операции чтения и записи памяти могут быть переупорядочены компилятором и процессором для оптимизации. Начиная от самой слабой гарантии std::memory_order_relaxed и заканчивая самой строгой std::memory_order_seq_cst (по умолчанию). Если хотите подробнее вникнуть в тему, то вот сорокаминутное видео с объяснением от разработчика из Яндекса:
Чем процесс отличается от потока?
Изолированностью в первую очередь. Процессы имеют свои ресурсы, потоки делят ресурсы одного процесса. Падение одного потока приводит к завершению всего процесса. Завершение процесса никак не затрагивает другие процессы.
Что будет если вызвать new с огромным куском памяти?
Произойдёт вызов исключения std::bad_alloc, можно использовать new (std::nothrow) если хочется получить nullptr в случае неудачи.
Как работает алгоритм std::remove/std::remove_if
Пробегаемся по контейнеру слева направо двумя итераторами:
- первый итератор (
first) указывает на текущий элемент - второй (
result) указывает на позицию куда сохранять подходящие элементы.
Реализация алгоритма для наглядности:
template<typename ForwardIt, typename UnaryPredicate>
ForwardIt remove_if(ForwardIt first, ForwardIt last, UnaryPredicate p) {
ForwardIt result = first;
for (; first != last; ++first) {
if (!p(*first)) {
if (result != first) {
*result = std::move(*first);
}
++result;
}
}
return result;
}
Какие бывают контейнеры STL. Устройство и сложность операций поиска/вставки/удаления.
std::vector— выделяет непрерывный кусок памяти, если не хватает места, то выделяется бОльший кусок, старые данные копируются туда, доступ имеет сложность O(1), вставка в конец имеет амортизированную сложность O(1), вставка в начало/середину требует сдвига всех данных вправо и имеет сложность O(n). Эффективно использует кеш процессора. Имеет возможность доступа по индексу.std::list— двусвязный список, поиск O(n), доступ по индексу O(n), вставка O(1) при наличии итератора, удаление O(1) при наличии итератора, доступ к элементам по итератору.std::forward_list— односвязный список, аналогиченstd::list, но занимает меньше памяти. Может вставлять/удалять только после известного элемента.std::deque— двусторонняя очередь, реализуется как массив указателей на блоки, доступ O(1), вставка/удаление с начала/конца O(1), в середине O(n).std::unordered_set/std::unordered_map— хеш-таблица из бакетов, поиск, вставка, удаление O(1) в среднем случае, O(n) в худшем (при коллизиях), элементы без сортировки.std::set/std::map— красно-черные деревья, поиск/вставка/удаление O(log n). Элементы отсортированы.std::array— аналог сишного массива фиксированного размера с плюшками STL в виде итераторов и деструктораstd::span— интерфейс для работы с непрерывными последовательностями данных без владения этими даннымиstd::string_view— аналогstd::spanдляstd::string, доступ к данным строки без владения
Что такое универсальная ссылка, что такое perfect forwarding?
Perfect forwarding — это способ передать аргумент в другую функцию точно так, как он был получен:
lvalueостаётсяlvaluervalueостаётсяrvalueconstостаётсяconst
Выглядит так:
template<typename T>
void foo(T&&value) // T&& является универсальной ссылкой
{
bar(std::forward<T>(value));
}
Тут есть важный нюанс для шаблонных классов, легко ошибиться:
template<typename T>
class A {
void foo(T&&value) // это не универсальная ссылка
{
bar(std::forward<T>(value));
}
template<typename U>
void foo2(U&&value) // а здесь всё работает правильно
{
bar(std::forward<U>(value));
}
}
Требований для perfect forwarding два:
- Это должна быть шаблонная функция с универсальной ссылкой
- использование
std::forwardдля передачи параметров дальше
Почему так сделано? Очевидно, потому, что создатели C++ ненавидят людей и желают им всего плохого, а если без шуток, то, чтобы избежать проблем при инстанцировании шаблона класса ну и щепотка ненависти к людям, конечно. Кстати T&& становится универсальной ссылкой, только в контексте вывода параметров шаблона.
P.S. Благодарю создателей Markdown за синтаксис языка разметки, всё форматирование сделано с помощью него.
Обновляем краткое содержания статей в блоге с помощью ИИ, денег, bash и wp-cli
Всем привет! Если вы не первый раз на моём сайте, то могли заметить, что краткое содержание заметок содержит первые n слов (вроде, 22) из заметки безо всякого осмысления. По крайней мере, так было до недавнего времени.
Поскольку сейчас наступил XXI век, то пора передать формирование краткого содержания заметок на откуп искусственному интеллекту.
Делать плагин мне откровенно не хочется, поэтому я обновил все заметки с помощью bash-скрипта, запрашивая краткое содержание статьи через curl у Yandex GPT через их API.
Алгоритм такой:
- получаю ID всех статей блога с помощью wp-cli
- для каждой статьи блога, отправляю её содержимое в Yandex GPT с помощью API
- получаю краткую выжимку статьи и показываю пользователю
- если он согласен с содержимым, то обновляю поле
post_excerptу статьи - вывожу
post_excerptв мета полеdescriptionпри формировании статьи в разделе - ???
- PROFIT
Для регистрации и работы с Yandex GPT нужно немного денег, я потратил 50 рублей. Регистрация и получение идентификаторов каталога (folder_id) и API ключа (api_key) найдёте в этой статье на «Хабре»: (Как подключить Yandex GPT к своему проекту на Python)[https://habr.com/ru/articles/780008/].
Добавил в скрипт подтверждение каждого шага от пользователя, потому что иногда ИИ выдаёт какую-то политкорректную дичь типа «Я не могу обсуждать эту тему. Давайте поговорим о чём-нибудь ещё» на простые запросы, а также он не умеет работать с видео. И добавил проверку, что цитата не заполнена для поста, чтобы лишний раз не тратить запросы к ИИ.
#!/usr/bin/env bash
# Конфигурация
FOLDER_ID="folder_id"
API_KEY="api_key"
API_URL="https://llm.api.cloud.yandex.net/foundationModels/v1/completion"
wrap_for_yandexgpt() {
local text="$1"
jq -n \
--arg text "$text" \
--arg folder "$FOLDER_ID" \
'{
"modelUri": "gpt://\($folder)/yandexgpt-lite",
"completionOptions": {
"stream": false,
"temperature": 0.3,
"maxTokens": "2000"
},
"messages": [
{
"role": "user",
"text": $text
}
]
}'
}
# Функция для отправки запроса
create_post_excerpt() {
local content="$1"
local prompt="Сделай краткое метаописание статьи (до 60 слов):\n\n$content"
local json_payload
json_payload=$(wrap_for_yandexgpt "$prompt")
# Выполняем запрос и сохраняем ответ в переменную
local response
response=$(curl -s -w "\nHTTPSTATUS:%{http_code}" -X POST -H "Content-Type: application/json" -H "Authorization: Api-Key $API_KEY" -H "x-folder-id: $FOLDER_ID" -d "$json_payload" $API_URL)
# Проверяем, что curl выполнился успешно
if [ $? -ne 0 ]; then
echo "Ошибка выполнения curl" >&2
return 1
fi
# Извлекаем HTTP код и тело ответа
local http_code
local body
http_code=$(echo "$response" | sed -n 's/HTTPSTATUS://p')
body=$(echo "$response" | sed '$d')
# Проверяем HTTP-код
if [ "$http_code" -ne 200 ]; then
echo "ошибка HTTP: $http_code" >&2
return 1
fi
# Проверяем наличие ошибки в теле ответа
if echo "$body" | jq -e '.error' >/dev/null 2>&1; then
local error_msg
error_msg=$(echo "$body" | jq -r '.error.message // .error')
echo "ошибка API: $error_msg" >&2
return 1
fi
# Извлекаем текст
local result
result=$(echo "$body" | jq -r '.result.alternatives[0].message.text // empty')
if [ -z "$result" ]; then
echo "пустой ответ от API" >&2
return 1
fi
local status
status=$(echo "$body" | jq -r '.result.alternatives[0].status // empty')
if [ "$status" == "ALTERNATIVE_STATUS_CONTENT_FILTER" ]; then
echo "[cencored]"
return 1
fi
echo "$result"
return 0
}
update_post_excerpt() {
local post_id=$1
local excerpt="$2"
wp --allow-root post update "$post_id" --post_excerpt="$excerpt"
}
get_post_excerpt() {
local post_id=$1
wp --allow-root post get "$post_id" --field=post_excerpt
}
get_post_title() {
local posts_data="$1"
local post_id="$2"
echo "$posts_data" | jq ".[] | select(.ID==$post_id) | .post_title"
}
get_post_content() {
local posts_data="$1"
local post_id="$2"
echo "$posts_data" | jq ".[] | select(.ID==$post_id) | .post_content"
}
cd wordpress_dir || exit
posts_data=$(wp post list --allow-root --post_type=post --fields=ID,post_title,post_content,post_excerpt --format=json --post_status=publish | jq 'map(select(.post_excerpt=="" or .post_excerpt==null))')
post_ids=$(echo "$posts_data" | jq .[].ID)
for post_id in $post_ids; do
title=$(get_post_title "$posts_data" "$post_id")
echo "ЗАГОЛОВОК: $title"
content=$(get_post_content "$posts_data" "$post_id")
post_excerpt=$(create_post_excerpt "$content")
exit_code=$?
if [ $exit_code -ne 0 ]; then
echo "ОШИБКА ПОЛУЧЕНИЯ ЦИТАТЫ: $post_excerpt"
echo
continue
fi
echo "ЦИТАТА: \"$post_excerpt\""
while true; do
read -r -n1 -p "Обновить цитату? (y - да, n - пропустить, q - выход) " choice
choice=$(echo "$choice" | tr '[:upper:]' '[:lower:]')
echo
case $choice in
y)
update_post_excerpt "$post_id" "$post_excerpt"
break
;;
n)
break
;;
q)
exit 0
;;
*)
continue
;;
esac
done
echo
done
cd - || exit
Всё, что делает этот скрипт он делает только под вашу личную ответственность. Я никаких гарантий не даю и ответственности за результат не несу. Примерно половину скрипта написал DeepSeek, претензии к нему.
UPD. Выложил скрипты на GitHub.
Добавляем подсветку нового языка в плагин Prismatic+Prism.js
Всем привет! Понадобилось мне вставить CMakeLists.txt в мою заметку с подсветкой кода. В блоге она включена с помощью плагина Prismatic и библиотеки Prism.js c темой «Tomorow Night», если вам интересно. И так родилась эта инструкция по добавлению нового языка (в нашем случае CMake) в плагин. Все работы проходят в папке плагина wp-content/plugins/prismatic, порядок действий следующий:
- Переходим на страницу скачивания Prism. Выбираем minified версию и только тот язык, который вам нужен, у меня был язык cmake и получился файл на 18 килобайт.
- Вставляем по адресу
./lib/prism/js/lang-cmake.jsкусок кода сPrism.language.cmake. Пример lang-cmake.js - Открываем
./inc/resources-enqueue.phpв функцииprismatic_prism_classesв первом массиве после'language-bash'вставляем'language-cmake', во втором после'lang-bash'вставляем'lang-cmake'.
Мне кажется, первый массив отвечает за название CSS класса, а второй — название JS файла, но я не уверен, потому что я ненастоящий PHP программист.
Подключение git репозитория в CMake
Всем привет, недавно изучал либу SFML и с удивлением обнаружил, что её можно подключить в CMakeLists.txt с помощью команды FetchContent следующим образом:
cmake_minimum_required(VERSION 3.28)
project(main LANGUAGES CXX)
include(FetchContent)
FetchContent_Declare(SFML
GIT_REPOSITORY https://github.com/SFML/SFML.git
GIT_TAG 3.0.2
GIT_SHALLOW ON
EXCLUDE_FROM_ALL
SYSTEM)
FetchContent_MakeAvailable(SFML)
add_executable(main src/main.cpp)
target_compile_features(main PRIVATE cxx_std_17)
target_link_libraries(main PRIVATE SFML::Graphics)
И при создании файлов для сборки, CMake самостоятельно скачает и подключит SFML к вашему проекту — чистая магия, на мой взгляд.