WordPress: Интерактивный тег <!—more—> без перезагрузки страницы

В начале статьи хочу признаться, что я не верстальщик и не владею PHP, JavaScript или CSS на должном уровне, поэтому всё, что написано ниже, используйте на свой страх и риск. Возможно, это неправильно и надо делать не так. Я не знаю, как надо правильно, учусь самостоятельно, поэтому пишу, как умею хрум-хрум-хрум.

Решил привести в порядок свою главную страницу блога. Не нравятся мне длинные посты, которые нужно долго листать, чтобы перейти к следующему. Изначально рассматривал два варианта:

  1. Кнопка [Пропустить пост] для быстрой перемотки к следующему посту и возвращению к предыдущему
  2. Кнопка [Развернуть] для динамической загрузки остальной части поста после тега <!--more-->.

В итоге выбрал третий путь: весь пост загружается сразу, но часть после тега <!--more--> показывается или скрывается по клику на кнопку [развернуть/свернуть].

Я выбрал этот вариант по двум причинам:

  1. Скрытая часть поста должна корректно обрабатываться скриптами по событию onload/onready.
  2. Размер постов у меня небольшой.
развернуть

Вопросы и ответы для собеседования и подготовки Senior C++ разработчика

Всем привет! Решил систематизировать и записать актуальные на 2025 год вопросы и ответы программисту в позиции Senior C++. Вы можете это использовать как для проведения собеседований в качестве интервьюера, так и для подготовки к ним. Считаю, это самая скучная статья на моём сайте, но лучший способ что-то запомнить — это записать и систематизировать свои знания.

Немного о себе: пишу на C++ уже восемнадцатый год и немножко понимаю в программировании.

Кстати, недавно узнал, что человек запоминает 25% любой информации, но надолго. И вот смотрю я на эту прорву текста и думаю — если это всего лишь четверть, то ни фига себе — насколько огромная часть знаний прошла мимо меня.

развернуть

Обновляем краткое содержания статей в блоге с помощью ИИ, денег, 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/].

Добавил в скрипт подтверждение каждого шага от пользователя, потому что иногда ИИ выдаёт какую-то политкорректную дичь типа «Я не могу обсуждать эту тему. Давайте поговорим о чём-нибудь ещё» на простые запросы, а также он не умеет работать с видео. И добавил проверку, что цитата не заполнена для поста, чтобы лишний раз не тратить запросы к ИИ.

развернуть

Добавляем подсветку нового языка в плагин Prismatic+Prism.js

Всем привет! Понадобилось мне вставить CMakeLists.txt в мою заметку с подсветкой кода. В блоге она включена с помощью плагина Prismatic и библиотеки Prism.js c темой «Tomorow Night», если вам интересно. И так родилась эта инструкция по добавлению нового языка (в нашем случае CMake) в плагин. Все работы проходят в папке плагина wp-content/plugins/prismatic, порядок действий следующий:

  1. Переходим на страницу скачивания Prism. Выбираем minified версию и только тот язык, который вам нужен, у меня был язык cmake и получился файл на 18 килобайт.
  2. Вставляем по адресу ./lib/prism/js/lang-cmake.js кусок кода с Prism.language.cmake. Пример lang-cmake.js
  3. Открываем ./inc/resources-enqueue.php в функции prismatic_prism_classes в первом массиве после 'language-bash' вставляем 'language-cmake', во втором после 'lang-bash' вставляем 'lang-cmake'.

Мне кажется, первый массив отвечает за название CSS класса, а второй — название JS файла, но я не уверен, потому что я ненастоящий PHP программист.

Патч под спойлером к с версии 3.6:

Подсветка синтаксиса CMake в Prismatic + Prism

Index: inc/resources-enqueue.php
===================================================================
--- inc/resources-enqueue.php   (revision 3415040)
+++ inc/resources-enqueue.php   (working copy)
@@ -458,6 +458,7 @@ function prismatic_prism_classes() {
            'language-batch', 
            'language-c', 
            'language-clike', 
+           'language-cmake', 
            'language-coffeescript', 
            'language-cpp', 
            'language-csharp', 
@@ -542,6 +543,7 @@ function prismatic_prism_classes() {
            'lang-batch', 
            'lang-c', 
            'lang-clike', 
+           'lang-cmake', 
            'lang-coffeescript', 
            'lang-cpp', 
            'lang-csharp', 
@@ -651,4 +653,4 @@ function prismatic_get_current_screen_id() {
    
    return false;
    
-}
\ No newline at end of file
+}
Index: lib/prism/js/lang-cmake.js
===================================================================
--- lib/prism/js/lang-cmake.js  (nonexistent)
+++ lib/prism/js/lang-cmake.js  (working copy)
@@ -0,0 +1 @@
+Prism.languages.cmake={comment:/#.*/,string:{pattern:/"(?:[^\\"]|\\.)*"/,greedy:!0,inside:{interpolation:{pattern:/\$\{(?:[^{}$]|\$\{[^{}$]*\})*\}/,inside:{punctuation:/\$\{|\}/,variable:/\w+/}}}},variable:/\b(?:CMAKE_\w+|\w+_(?:(?:BINARY|SOURCE)_DIR|DESCRIPTION|HOMEPAGE_URL|ROOT|VERSION(?:_MAJOR|_MINOR|_PATCH|_TWEAK)?)|(?:ANDROID|APPLE|BORLAND|BUILD_SHARED_LIBS|CACHE|CPACK_(?:ABSOLUTE_DESTINATION_FILES|COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY|ERROR_ON_ABSOLUTE_INSTALL_DESTINATION|INCLUDE_TOPLEVEL_DIRECTORY|INSTALL_DEFAULT_DIRECTORY_PERMISSIONS|INSTALL_SCRIPT|PACKAGING_INSTALL_PREFIX|SET_DESTDIR|WARN_ON_ABSOLUTE_INSTALL_DESTINATION)|CTEST_(?:BINARY_DIRECTORY|BUILD_COMMAND|BUILD_NAME|BZR_COMMAND|BZR_UPDATE_OPTIONS|CHANGE_ID|CHECKOUT_COMMAND|CONFIGURATION_TYPE|CONFIGURE_COMMAND|COVERAGE_COMMAND|COVERAGE_EXTRA_FLAGS|CURL_OPTIONS|CUSTOM_(?:COVERAGE_EXCLUDE|ERROR_EXCEPTION|ERROR_MATCH|ERROR_POST_CONTEXT|ERROR_PRE_CONTEXT|MAXIMUM_FAILED_TEST_OUTPUT_SIZE|MAXIMUM_NUMBER_OF_(?:ERRORS|WARNINGS)|MAXIMUM_PASSED_TEST_OUTPUT_SIZE|MEMCHECK_IGNORE|POST_MEMCHECK|POST_TEST|PRE_MEMCHECK|PRE_TEST|TESTS_IGNORE|WARNING_EXCEPTION|WARNING_MATCH)|CVS_CHECKOUT|CVS_COMMAND|CVS_UPDATE_OPTIONS|DROP_LOCATION|DROP_METHOD|DROP_SITE|DROP_SITE_CDASH|DROP_SITE_PASSWORD|DROP_SITE_USER|EXTRA_COVERAGE_GLOB|GIT_COMMAND|GIT_INIT_SUBMODULES|GIT_UPDATE_CUSTOM|GIT_UPDATE_OPTIONS|HG_COMMAND|HG_UPDATE_OPTIONS|LABELS_FOR_SUBPROJECTS|MEMORYCHECK_(?:COMMAND|COMMAND_OPTIONS|SANITIZER_OPTIONS|SUPPRESSIONS_FILE|TYPE)|NIGHTLY_START_TIME|P4_CLIENT|P4_COMMAND|P4_OPTIONS|P4_UPDATE_OPTIONS|RUN_CURRENT_SCRIPT|SCP_COMMAND|SITE|SOURCE_DIRECTORY|SUBMIT_URL|SVN_COMMAND|SVN_OPTIONS|SVN_UPDATE_OPTIONS|TEST_LOAD|TEST_TIMEOUT|TRIGGER_SITE|UPDATE_COMMAND|UPDATE_OPTIONS|UPDATE_VERSION_ONLY|USE_LAUNCHERS)|CYGWIN|ENV|EXECUTABLE_OUTPUT_PATH|GHS-MULTI|IOS|LIBRARY_OUTPUT_PATH|MINGW|MSVC(?:10|11|12|14|60|70|71|80|90|_IDE|_TOOLSET_VERSION|_VERSION)?|MSYS|PROJECT_NAME|UNIX|WIN32|WINCE|WINDOWS_PHONE|WINDOWS_STORE|XCODE))\b/,property:/\b(?:cxx_\w+|(?:ARCHIVE_OUTPUT_(?:DIRECTORY|NAME)|COMPILE_DEFINITIONS|COMPILE_PDB_NAME|COMPILE_PDB_OUTPUT_DIRECTORY|EXCLUDE_FROM_DEFAULT_BUILD|IMPORTED_(?:IMPLIB|LIBNAME|LINK_DEPENDENT_LIBRARIES|LINK_INTERFACE_LANGUAGES|LINK_INTERFACE_LIBRARIES|LINK_INTERFACE_MULTIPLICITY|LOCATION|NO_SONAME|OBJECTS|SONAME)|INTERPROCEDURAL_OPTIMIZATION|LIBRARY_OUTPUT_DIRECTORY|LIBRARY_OUTPUT_NAME|LINK_FLAGS|LINK_INTERFACE_LIBRARIES|LINK_INTERFACE_MULTIPLICITY|LOCATION|MAP_IMPORTED_CONFIG|OSX_ARCHITECTURES|OUTPUT_NAME|PDB_NAME|PDB_OUTPUT_DIRECTORY|RUNTIME_OUTPUT_DIRECTORY|RUNTIME_OUTPUT_NAME|STATIC_LIBRARY_FLAGS|VS_CSHARP|VS_DOTNET_REFERENCEPROP|VS_DOTNET_REFERENCE|VS_GLOBAL_SECTION_POST|VS_GLOBAL_SECTION_PRE|VS_GLOBAL|XCODE_ATTRIBUTE)_\w+|\w+_(?:CLANG_TIDY|COMPILER_LAUNCHER|CPPCHECK|CPPLINT|INCLUDE_WHAT_YOU_USE|OUTPUT_NAME|POSTFIX|VISIBILITY_PRESET)|ABSTRACT|ADDITIONAL_MAKE_CLEAN_FILES|ADVANCED|ALIASED_TARGET|ALLOW_DUPLICATE_CUSTOM_TARGETS|ANDROID_(?:ANT_ADDITIONAL_OPTIONS|API|API_MIN|ARCH|ASSETS_DIRECTORIES|GUI|JAR_DEPENDENCIES|NATIVE_LIB_DEPENDENCIES|NATIVE_LIB_DIRECTORIES|PROCESS_MAX|PROGUARD|PROGUARD_CONFIG_PATH|SECURE_PROPS_PATH|SKIP_ANT_STEP|STL_TYPE)|ARCHIVE_OUTPUT_DIRECTORY|ATTACHED_FILES|ATTACHED_FILES_ON_FAIL|AUTOGEN_(?:BUILD_DIR|ORIGIN_DEPENDS|PARALLEL|SOURCE_GROUP|TARGETS_FOLDER|TARGET_DEPENDS)|AUTOMOC|AUTOMOC_(?:COMPILER_PREDEFINES|DEPEND_FILTERS|EXECUTABLE|MACRO_NAMES|MOC_OPTIONS|SOURCE_GROUP|TARGETS_FOLDER)|AUTORCC|AUTORCC_EXECUTABLE|AUTORCC_OPTIONS|AUTORCC_SOURCE_GROUP|AUTOUIC|AUTOUIC_EXECUTABLE|AUTOUIC_OPTIONS|AUTOUIC_SEARCH_PATHS|BINARY_DIR|BUILDSYSTEM_TARGETS|BUILD_RPATH|BUILD_RPATH_USE_ORIGIN|BUILD_WITH_INSTALL_NAME_DIR|BUILD_WITH_INSTALL_RPATH|BUNDLE|BUNDLE_EXTENSION|CACHE_VARIABLES|CLEAN_NO_CUSTOM|COMMON_LANGUAGE_RUNTIME|COMPATIBLE_INTERFACE_(?:BOOL|NUMBER_MAX|NUMBER_MIN|STRING)|COMPILE_(?:DEFINITIONS|FEATURES|FLAGS|OPTIONS|PDB_NAME|PDB_OUTPUT_DIRECTORY)|COST|CPACK_DESKTOP_SHORTCUTS|CPACK_NEVER_OVERWRITE|CPACK_PERMANENT|CPACK_STARTUP_SHORTCUTS|CPACK_START_MENU_SHORTCUTS|CPACK_WIX_ACL|CROSSCOMPILING_EMULATOR|CUDA_EXTENSIONS|CUDA_PTX_COMPILATION|CUDA_RESOLVE_DEVICE_SYMBOLS|CUDA_SEPARABLE_COMPILATION|CUDA_STANDARD|CUDA_STANDARD_REQUIRED|CXX_EXTENSIONS|CXX_STANDARD|CXX_STANDARD_REQUIRED|C_EXTENSIONS|C_STANDARD|C_STANDARD_REQUIRED|DEBUG_CONFIGURATIONS|DEFINE_SYMBOL|DEFINITIONS|DEPENDS|DEPLOYMENT_ADDITIONAL_FILES|DEPLOYMENT_REMOTE_DIRECTORY|DISABLED|DISABLED_FEATURES|ECLIPSE_EXTRA_CPROJECT_CONTENTS|ECLIPSE_EXTRA_NATURES|ENABLED_FEATURES|ENABLED_LANGUAGES|ENABLE_EXPORTS|ENVIRONMENT|EXCLUDE_FROM_ALL|EXCLUDE_FROM_DEFAULT_BUILD|EXPORT_NAME|EXPORT_PROPERTIES|EXTERNAL_OBJECT|EchoString|FAIL_REGULAR_EXPRESSION|FIND_LIBRARY_USE_LIB32_PATHS|FIND_LIBRARY_USE_LIB64_PATHS|FIND_LIBRARY_USE_LIBX32_PATHS|FIND_LIBRARY_USE_OPENBSD_VERSIONING|FIXTURES_CLEANUP|FIXTURES_REQUIRED|FIXTURES_SETUP|FOLDER|FRAMEWORK|Fortran_FORMAT|Fortran_MODULE_DIRECTORY|GENERATED|GENERATOR_FILE_NAME|GENERATOR_IS_MULTI_CONFIG|GHS_INTEGRITY_APP|GHS_NO_SOURCE_GROUP_FILE|GLOBAL_DEPENDS_DEBUG_MODE|GLOBAL_DEPENDS_NO_CYCLES|GNUtoMS|HAS_CXX|HEADER_FILE_ONLY|HELPSTRING|IMPLICIT_DEPENDS_INCLUDE_TRANSFORM|IMPORTED|IMPORTED_(?:COMMON_LANGUAGE_RUNTIME|CONFIGURATIONS|GLOBAL|IMPLIB|LIBNAME|LINK_DEPENDENT_LIBRARIES|LINK_INTERFACE_(?:LANGUAGES|LIBRARIES|MULTIPLICITY)|LOCATION|NO_SONAME|OBJECTS|SONAME)|IMPORT_PREFIX|IMPORT_SUFFIX|INCLUDE_DIRECTORIES|INCLUDE_REGULAR_EXPRESSION|INSTALL_NAME_DIR|INSTALL_RPATH|INSTALL_RPATH_USE_LINK_PATH|INTERFACE_(?:AUTOUIC_OPTIONS|COMPILE_DEFINITIONS|COMPILE_FEATURES|COMPILE_OPTIONS|INCLUDE_DIRECTORIES|LINK_DEPENDS|LINK_DIRECTORIES|LINK_LIBRARIES|LINK_OPTIONS|POSITION_INDEPENDENT_CODE|SOURCES|SYSTEM_INCLUDE_DIRECTORIES)|INTERPROCEDURAL_OPTIMIZATION|IN_TRY_COMPILE|IOS_INSTALL_COMBINED|JOB_POOLS|JOB_POOL_COMPILE|JOB_POOL_LINK|KEEP_EXTENSION|LABELS|LANGUAGE|LIBRARY_OUTPUT_DIRECTORY|LINKER_LANGUAGE|LINK_(?:DEPENDS|DEPENDS_NO_SHARED|DIRECTORIES|FLAGS|INTERFACE_LIBRARIES|INTERFACE_MULTIPLICITY|LIBRARIES|OPTIONS|SEARCH_END_STATIC|SEARCH_START_STATIC|WHAT_YOU_USE)|LISTFILE_STACK|LOCATION|MACOSX_BUNDLE|MACOSX_BUNDLE_INFO_PLIST|MACOSX_FRAMEWORK_INFO_PLIST|MACOSX_PACKAGE_LOCATION|MACOSX_RPATH|MACROS|MANUALLY_ADDED_DEPENDENCIES|MEASUREMENT|MODIFIED|NAME|NO_SONAME|NO_SYSTEM_FROM_IMPORTED|OBJECT_DEPENDS|OBJECT_OUTPUTS|OSX_ARCHITECTURES|OUTPUT_NAME|PACKAGES_FOUND|PACKAGES_NOT_FOUND|PARENT_DIRECTORY|PASS_REGULAR_EXPRESSION|PDB_NAME|PDB_OUTPUT_DIRECTORY|POSITION_INDEPENDENT_CODE|POST_INSTALL_SCRIPT|PREDEFINED_TARGETS_FOLDER|PREFIX|PRE_INSTALL_SCRIPT|PRIVATE_HEADER|PROCESSORS|PROCESSOR_AFFINITY|PROJECT_LABEL|PUBLIC_HEADER|REPORT_UNDEFINED_PROPERTIES|REQUIRED_FILES|RESOURCE|RESOURCE_LOCK|RULE_LAUNCH_COMPILE|RULE_LAUNCH_CUSTOM|RULE_LAUNCH_LINK|RULE_MESSAGES|RUNTIME_OUTPUT_DIRECTORY|RUN_SERIAL|SKIP_AUTOGEN|SKIP_AUTOMOC|SKIP_AUTORCC|SKIP_AUTOUIC|SKIP_BUILD_RPATH|SKIP_RETURN_CODE|SOURCES|SOURCE_DIR|SOVERSION|STATIC_LIBRARY_FLAGS|STATIC_LIBRARY_OPTIONS|STRINGS|SUBDIRECTORIES|SUFFIX|SYMBOLIC|TARGET_ARCHIVES_MAY_BE_SHARED_LIBS|TARGET_MESSAGES|TARGET_SUPPORTS_SHARED_LIBS|TESTS|TEST_INCLUDE_FILE|TEST_INCLUDE_FILES|TIMEOUT|TIMEOUT_AFTER_MATCH|TYPE|USE_FOLDERS|VALUE|VARIABLES|VERSION|VISIBILITY_INLINES_HIDDEN|VS_(?:CONFIGURATION_TYPE|COPY_TO_OUT_DIR|DEBUGGER_(?:COMMAND|COMMAND_ARGUMENTS|ENVIRONMENT|WORKING_DIRECTORY)|DEPLOYMENT_CONTENT|DEPLOYMENT_LOCATION|DOTNET_REFERENCES|DOTNET_REFERENCES_COPY_LOCAL|INCLUDE_IN_VSIX|IOT_STARTUP_TASK|KEYWORD|RESOURCE_GENERATOR|SCC_AUXPATH|SCC_LOCALPATH|SCC_PROJECTNAME|SCC_PROVIDER|SDK_REFERENCES|SHADER_(?:DISABLE_OPTIMIZATIONS|ENABLE_DEBUG|ENTRYPOINT|FLAGS|MODEL|OBJECT_FILE_NAME|OUTPUT_HEADER_FILE|TYPE|VARIABLE_NAME)|STARTUP_PROJECT|TOOL_OVERRIDE|USER_PROPS|WINRT_COMPONENT|WINRT_EXTENSIONS|WINRT_REFERENCES|XAML_TYPE)|WILL_FAIL|WIN32_EXECUTABLE|WINDOWS_EXPORT_ALL_SYMBOLS|WORKING_DIRECTORY|WRAP_EXCLUDE|XCODE_(?:EMIT_EFFECTIVE_PLATFORM_NAME|EXPLICIT_FILE_TYPE|FILE_ATTRIBUTES|LAST_KNOWN_FILE_TYPE|PRODUCT_TYPE|SCHEME_(?:ADDRESS_SANITIZER|ADDRESS_SANITIZER_USE_AFTER_RETURN|ARGUMENTS|DISABLE_MAIN_THREAD_CHECKER|DYNAMIC_LIBRARY_LOADS|DYNAMIC_LINKER_API_USAGE|ENVIRONMENT|EXECUTABLE|GUARD_MALLOC|MAIN_THREAD_CHECKER_STOP|MALLOC_GUARD_EDGES|MALLOC_SCRIBBLE|MALLOC_STACK|THREAD_SANITIZER(?:_STOP)?|UNDEFINED_BEHAVIOUR_SANITIZER(?:_STOP)?|ZOMBIE_OBJECTS))|XCTEST)\b/,keyword:/\b(?:add_compile_definitions|add_compile_options|add_custom_command|add_custom_target|add_definitions|add_dependencies|add_executable|add_library|add_link_options|add_subdirectory|add_test|aux_source_directory|break|build_command|build_name|cmake_host_system_information|cmake_minimum_required|cmake_parse_arguments|cmake_policy|configure_file|continue|create_test_sourcelist|ctest_build|ctest_configure|ctest_coverage|ctest_empty_binary_directory|ctest_memcheck|ctest_read_custom_files|ctest_run_script|ctest_sleep|ctest_start|ctest_submit|ctest_test|ctest_update|ctest_upload|define_property|else|elseif|enable_language|enable_testing|endforeach|endfunction|endif|endmacro|endwhile|exec_program|execute_process|export|export_library_dependencies|file|find_file|find_library|find_package|find_path|find_program|fltk_wrap_ui|foreach|function|get_cmake_property|get_directory_property|get_filename_component|get_property|get_source_file_property|get_target_property|get_test_property|if|include|include_directories|include_external_msproject|include_guard|include_regular_expression|install|install_files|install_programs|install_targets|link_directories|link_libraries|list|load_cache|load_command|macro|make_directory|mark_as_advanced|math|message|option|output_required_files|project|qt_wrap_cpp|qt_wrap_ui|remove|remove_definitions|return|separate_arguments|set|set_directory_properties|set_property|set_source_files_properties|set_target_properties|set_tests_properties|site_name|source_group|string|subdir_depends|subdirs|target_compile_definitions|target_compile_features|target_compile_options|target_include_directories|target_link_directories|target_link_libraries|target_link_options|target_sources|try_compile|try_run|unset|use_mangled_mesa|utility_source|variable_requires|variable_watch|while|write_file)(?=\s*\()\b/,boolean:/\b(?:FALSE|OFF|ON|TRUE)\b/,namespace:/\b(?:INTERFACE|PRIVATE|PROPERTIES|PUBLIC|SHARED|STATIC|TARGET_OBJECTS)\b/,operator:/\b(?:AND|DEFINED|EQUAL|GREATER|LESS|MATCHES|NOT|OR|STREQUAL|STRGREATER|STRLESS|VERSION_EQUAL|VERSION_GREATER|VERSION_LESS)\b/,inserted:{pattern:/\b\w+::\w+\b/,alias:"class-name"},number:/\b\d+(?:\.\d+)*\b/,function:/\b[a-z_]\w*(?=\s*\()\b/i,punctuation:/[()>}]|\$[<{]/};
\ No newline at end of file

UPD: Послал патч автору плагина Prismatic, так что есть вероятность, что подсветка cmake появится в следующих версиях, но это не точно.

Подключение 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 к вашему проекту — чистая магия, на мой взгляд.

Как я тестовое задание делал

В конце сентября сходил на собеседование в некую компанию и получил тестовое задание сроком на семь дней. При этом работу предлагали в офисе. Но есть нюансы!

Во-первых, за время работы в «Газпром-Медиа» я отвык работать в офисе. Во-вторых, я уверен, что выполнение тестового задания должно быть оплачено, тем более это целый проект. В-третьих, что-то мне подсказывало, что тестовое задание не примут в любом случае, потому что я не сказал способ нахождения пересечения луча со сферой, да и манера общения в целом мне не понравилась. Тем не менее, с целью узнать что-то новое и в качестве разминки для мозгов, тестовое задание таки взялся выполнять. Ниже расскажу, как это было.

Если описать общими словами задание, то оно звучит так: сделать приложение, имитирующее джекпот автомат с пятью барабанами, которые должны вращаться при нажатии анимированной кнопки «START» и замедляться по очереди спустя 4-5 секунд.

развернуть

Текирова, Кемер, Турция — поездка на неделю

Писать или нет?, — думал я. По дороге в аэропорт провожающий гид «обрадовал», что рейс будет «Анталия-Махачкала», а не «Анталия-Петербург». И это ещё везение — кому-то до нас достался Новосибирск или Самара. «Точно писать!» — это сигнал свыше. Махачкала — это как будто Россия, но Дагестан, а достаточна ли у меня длина рукавов и штанин для посещения данной местности — я не очень уверен.

Овчарка Белла

Итак, мы купили относительно недорогой тур в трехзвездочный отель Турции на семь дней. Что сказать, отель так себе, но радушие хозяев и местная живность в лице немецкой овчарки Беллы, рыжего кота, желтоглазой кошки и четырёх озорных котят подкупают, поэтому о минусах отеля писать не буду. Мы славно ели, крепко спали, подкармливали зверушек на завтрак и ужин. В отеле у нас всё прошло хорошо.

развернуть

Небольшая новость

Всем привет! Всякому программисту положено иметь pet-проект, который он холит, лелеет и делает в свободное время.

Когда я задумался о таком, оказалось, что мой pet-проект — это собственно сайт blog2k.ru, для которого я написал свою тему для WordPress и поддерживаю собственный WordPress плагин со всякими полезными плюшками.

Буквально сегодня выложил в открытый доступ на github исходники своих наработок:

  • тема для WordPress этого сайта: b2k-theme
  • плагин с полезным функционалом: b2k-tools
  • виджет, который при добавлении в область title sidebar, показывает список постов c текущей категорией или тегом: wp-index-widget
  • плагин для виджета отображения погоды: yowindow-widget

Работу плагина wp-index-widget можно увидеть, например, здесь.

Всё выложено под лицензией MIT, можно модифицировать, изменять, брать любую часть и даже использовать в коммерческих целях.

Коммиты и форки всячески приветствуются и не осуждаются. Пул реквесты также, возможно, будут приняты. Enjoy!

Пул потоков с запуском функций с переменным количеством параметров

Всем привет! Внезапно оказался без определённого места работы и в свободное время мучаю ИИ по поводу возможных задач и вопросов на собеседованиях по C++, и некоторые задачи делаю самостоятельно.

Недавно я попросил ИИ сделать пул потоков. Я его написал заранее, но захотелось проверить, где я мог ошибиться.

ИИ написал нерабочий код, но с интересной идеей: запускать вместо стандартного std::function<void()> функции с параметрами с помощью std::packaged_task и variadic templates. Меня этот так вдохновило, что я решил таки сделать этот код рабочим. И сделал!

Кстати, мой код не нравится ИИ — он предлагает его переделать на std::condition_variable и, возможно, он прав, а я написал ерунду, таков путь!

Мой вариант на C++:

#include <vector>
#include <atomic>
#include <thread>
#include <mutex>
#include <functional>
#include <deque>
#include <optional>
#include <future>
#include <iostream>

class ThreadPool {
public:
    /// По умолчанию количество потоков равно количеству аппаратных потоков
    ThreadPool(size_t numThreads=std::jthread::hardware_concurrency()) {
        m_threads.reserve(numThreads);
        for (size_t i = 0; i < numThreads; ++i) {
            m_threads.emplace_back([this](){ runLoop(); });
        }
    }

    // Принимаем на входе функцию и её аргументы, на выходе автоматически
    // оборачиваем результат работы функции в std::future
    template<typename... Args>
    auto runTaskAsync(auto f, Args&&...args) {
        // Выясняем возращаемый тип у функции
        using return_type = std::invoke_result_t<decltype(f), Args...>;

        // Заворачиваем функцию с переменным количеством аргументов в
        // std::packaged_task, у которого можно получить std::future<return_type>
        auto task = std::make_shared<std::packaged_task<return_type()>>(
            std::bind(std::forward<decltype(f)>(f), std::forward<Args>(args)...)
            );

        // Получаем std::future
        auto res = task->get_future();

        if (m_Running) {
            std::unique_lock lock(m_guard);
            // Добавляем задачу в очередь на выполнение
            m_tasks.push_back([task](){(*task)();});

            // Уведомляем ожидающие потоки
            m_hasTask = true;
            // Может тут надо использовать notify_one (?)
            m_hasTask.notify_all();
        }

        return res;
    }

    // Вызываем функцию синхронно и возвращаем результат
    template<typename...Args>
    auto runTaskSync(auto f, Args &&...args) {
        return runTaskAsync(f, std::forward<Args>(args)...).get();
    }

    ~ThreadPool()
    {
        // Устанавливаем флаг остановки потоков
        m_Running = false;
        // Очищаем очередь задач
        {
            std::unique_lock lock(m_guard);
            m_tasks.clear();
        }
        // Обманом :-) выводим потоки из режима ожидания
        m_hasTask = true;
        m_hasTask.notify_all();

        // Для каждого потока
        for (auto &thread : m_threads) {
            if (thread.joinable()) {
                // Дожидаемся завершения работы
                thread.join();
            }
        }
    }
private:
    using Task = std::function<void()>;

    void runLoop() {
        while (m_Running) {
            // Ждём задачу
            m_hasTask.wait(false);

            std::optional<Task> task;

            // Забираем задачу из начала списка
            {
                std::unique_lock lock(m_guard);
                if (!m_tasks.empty()) {
                    task = m_tasks.front();
                    m_tasks.pop_front();
                }
            }

            // Задача есть, исполняем
            if (task) {
                (*task)();
            }
            else {
                // Отдаём процессорное время другим потокам
                std::this_thread::yield();
            }
        }
    }

    // Вектор потоков
    std::vector<std::thread> m_threads;
    // Атомарный флаг остановки потоков
    std::atomic_bool m_Running{ true };
    // Атомарный флаг наличия задачи
    std::atomic_bool m_hasTask{ false };
    // Очередь задач
    std::deque<Task> m_tasks;
    // Мьютекс для защиты очереди задач
    std::mutex m_guard;
};


int main()
{
    // Создаём пул потоков
    ThreadPool mgr;

    // Первая задача на две секунды, которая возвращает строку
    auto func = [](const std::string &s) {
        std::cout << "Long task1 begin..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(2));
        std::cout << "Long task1 end..." << std::endl;
        return s;
    };

    // Вторая задача на пять секунд, которая ничего не возвращает
    auto func2 = []() {
        std::cout << "Long task2 begin..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(5));
        std::cout << "Long task2 end..." << std::endl;
    };

    // Запускаем первую задачу с параметром
    auto result1 = mgr.runTaskAsync(func, "task1 result");
    // Запускаем вторую задачу без параметров
    auto result2 = mgr.runTaskAsync(func2);

    // Получаем результат выполнения первой задачи и выводим в консоль
    std::cout << "Task1 result = " << result1.get() << std::endl;
    // Ждём вторую задачу
    std::cout << "Wait task2 result" << std::endl;
    result2.wait();
}

Исходник от ИИ для сравнения:

#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>
#include <memory>
#include <stdexcept>

class ThreadPool {
public:
    // Конструктор, создающий указанное количество потоков
    ThreadPool(size_t threads) : stop(false) {
        for(size_t i = 0; i < threads; ++i) {
            workers.emplace_back([this] {
                for(;;) {
                    std::function<void()> task;
                    
                    {
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        this->condition.wait(lock, 
                            [this]{ return this->stop || !this->tasks.empty(); });
                        if(this->stop && this->tasks.empty())
                            return;
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }
                    
                    task();
                }
            });
        }
    }
    
    // Добавление задачи в пул
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) 
        -> std::future<typename std::result_of<F(Args...)>::type> {
        using return_type = typename std::result_of<F(Args...)>::type;
        
        auto task = std::make_shared<std::packaged_task<return_type()>>(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
            
        std::future<return_type> res = task->get_future();
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            
            // Не добавляем новые задачи после остановки пула
            if(stop)
                throw std::runtime_error("enqueue on stopped ThreadPool");
                
            tasks.emplace([task](){ (*task)(); });
        }
        condition.notify_one();
        return res;
    }
    
    // Деструктор, останавливающий все потоки
    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            stop = true;
        }
        condition.notify_all();
        for(std::thread &worker: workers)
            worker.join();
    }

private:
    std::vector<std::thread> workers;       // Рабочие потоки
    std::queue<std::function<void()>> tasks;// Очередь задач
    
    std::mutex queue_mutex;                 // Мьютекс для синхронизации доступа к очереди
    std::condition_variable condition;      // Условная переменная для уведомлений
    bool stop;                              // Флаг остановки пула
};

Буду признателен, если вы укажете на возможные ошибки и недочёты в моём коде.

Летний гастротур по России

Зеленоградск

Изначально мы прилетели в Калининград и взяли такси до Зеленоградска. Вдоль трассы полно зелени и полей, ехать 20 минут, такси стоит недорого относительно Петербурга.

Заселились в отель «Кранц», немецкое название Зеленоградска именно такое. Отель не понравился. Стоит в два раза дороже, чем должен. Единственный плюс отеля — его расположение недалеко от берега, остальное плохо — туалет и ванная одна на два номера, завтрак советский из полуфабрикатов, кофе растворимый, вешалок в номере нет, подниматься по крутой лестнице на третий этаж сложно с пустыми руками, с чемоданами вообще шею можно сломать, по уборке в номере возникли вопросы.

Но город, в отличие от отеля, оставил самые приятные впечатления, несмотря на сильный ветер и температуру в 12-16 градусов. Так получилось, что поездка была больше гастротуром, чем туризмом, не успевали испытывать голод. Перепробовали всё, привезли лишнего весу килограмм шесть на двоих.

Напробовался рыбы разных видов и способов приготовления. Тут тебе и строганина из пеламиды, и тартар из лосося, и шаверма из тунца. Так-то рыбу я не ем особо, а тут прям ушёл в отрыв.

После приезда, отдохнув от поездки, вечером посетили колесо обозрения «Глаз Балтики» и обозрели весь Зеленоградск сверху.

Церкви в Зеленоградске получаются из католических костёлов: добавляют костёлу золотой купол и получается цыганский дворец с элементами готики. Выглядит не к месту и чужеродно на мой непритязательный взгляд. Зачем так сделано — лично мне непонятно.

развернуть
Блог Евгения Жирнова