Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni
10.01.2025
Спостереження за станом збірок на GitHub Actions
Маю таку проблему, що коли працюю з купою гілок на GitHub (тобто майже завжди), то важко встежити за результатами збірок — які можуть тривати до 20 хвилин. Хотів зробити віджет, який мені б сповіщав про завершення моїх збірок.
Інтерфейсна частина вирішується дуже легко з xbar, який я всім рекомендую — там можна написати скрипт улюбленою мовою та перетворити його текстовий вихід у віджет панелі меню macOS. Але ще залишається отримувати інформацію.
Для того в нас є два методи API. Перелічити запуски збірок для репозиторію — вертає всю потрібну інформацію. Їх навіть можна відфільтрувати за автором.
Але є нюанс. Для отримання інформації потрібний “класичний” токен авторизації з повними правами до всіх репозиторіїв — не хотілося б такий тримати відкритим. Або “fine-grained” токен — там є права тільки на читання GH Actions та тільки на конкретні репозиторії — що гарно. Але щоб бачити робочі репозиторії, він повинен належати робочій організації, на що треба ще отримати дозвіл від адміністратора. Що, як на мене, шкереберть — бо ж на класичний токен з повними привілеями дозволу не потрібно.
Друга проблема — що цей API обмежений одним репозиторієм, а в мене їх принаймні 6. Хоча 6 - це не 100, та можна всі їх опитувати по черзі. Є інший API - для сповіщень. Навіть доступний із класичним токеном з точно обмеженими правами. Але… сповіщення про збірки мають сміховинний мінімум інформації: репозиторій та текстовий опис — навіть не структурований. Навіть посилання на збірку в них немає, тому інтерфейс буде на рівні “щось десь впало, піди подивись.” (Краще, ніж нічого, звісно.)
Одним словом, наче й можна зібрати потрібний мені звіт (а саме — де тести впали, а де пройшли) - але виглядає ненадійно.
До речі, а не знаєте такого ПЗ, щоб в локальному браузері, з моєю сесією перевіряло зміст сторінки, а краще — дозволяло періодично виконувати скрипти? Бо можна було б ще з такого боку зайти.
09.01.2025
Син попросив приготувати: сніданки
В неділю син щось чи нафантазував, чи приколовся і каже: хочу на сніданок фотосинтез! Ну можна було посміятися та забути, а можна прийняти цей творчий кулінарний виклик. Не знав я тоді, що це тільки початок…
🥬 Понеділок: фотосинтез. Подолавши перше бажання дати лист салату та посвітити в очі ліхтариком, пішов з іншого боку. Ну власне “світлом” не наїсися… що може замінити світло? Як щодо жовтка яйця? Поклав на салат яйце пашот; яйце розрізаємо, жовток розтікається по салату, фотосинтез зараховано. 🌞
😶🌫️ Вівторок: аерозоль. Їстівних аерозолів теж не дуже багато. (Ну хіба оті цукерки-спреї… брр.) Але можна ще згадати, що пара — теж аерозоль. (От зараз ще подумав про дим — можна було б теж якийсь елемент включити.) В мене на пару асоціація конкретно з картоплею — як ото дихають картопляною парою від застуди. В середину тарілки йде піала з гарячою вареною картоплею з водичкою, а задля ефекту все це накрите кришкою. До речі, щоб пара дожила до столу, посуд повинен бути гарячим заздалегідь. Що взагалі хороша ідея, коли подаєш щось невелике — наприклад, яєшню. 🥔
🥗 Середа: емульсія. О, це вже значно легше. Бо більшість соусів — це емульсії. Можна було б обмежитися майонезом, але я заколотив прямо за столом айолі. Підсушив в аерогрилі шматочки хліба, порізав печену курячу грудку — салат “Цезар” готовий! (Не дуже автентичний, але де ви бачили автентичний “Цезар”?) 🧄
🥞 Четвер: піна. Тут теж складнощів немає. По-перше, всі хлібобулочні продукти — це тверда піна. По-друге, дві класичні кухонні піни — збиті білки та збиті вершки. Беремо перше, беремо друге… разом будуть панкейки зі збитими вершками зверху! Щоб було щось здорове, посередині кладу притушене яблуко зі спеціями. 🍎
🌯 Завтра беру перерву, бо досить. Взагалі в нас типова страва дитині на сніданок — це мʼясо з овочами в лаваші. І поживно, і здорово, і всередину можна багато всього покласти.
08.01.2025
Генерація JSON на Hugo
Мій бот скрипт для каналу — який бере зміст з файлів блогу на Hugo - був з самого початку збудований як повністю окреме рішення: він сам знаходить пости, сам їх парсить, сам їх надсилає в Telegram. З Hugo в нього спільні тільки домовленості про структуру тек та формат файлів.
Нещодавно мені спало на думку, що взагалі-то перші два кроки Hugo теж робить, та незрівнянно вправніше. Тому гарно було б якось включитися в Hugo та залишити на власний скрипт тільки публікацію. Зокрема, це розблокувало б інші напрямки публікації, бо наразі скрипт дуже завʼязаний на Telegram та розділити його мені поки не вдалося.
Механізму плагінів в Hugo немає, тож що я придумав: Hugo під час збірки буде генерувати мені JSON з всіма даними, що потрібні для публікації, а скрипту залишиться зчитати JSON та, так би мовити, “синхронізувати” його в канал.
…Механізму “згенерувати JSON” в Hugo теж немає. Зате ми можемо створити окрему сторінку, а в її шаблоні назбирати даних, викликати для них jsonify
та надрукувати — от і буде JSON. На щастя, принаймні з кожної сторінки є необмежений доступ до даних сайту.
Величезний мінус — збирати дані доведеться всередині шаблону. Hugo використовує звичайні шаблони Go, але ніколи в Go мені не спадало на думку, скажімо, перетворювати в шаблоні масиви даних. Бо мова шаблонів незручна, не має підказок IDE, та ще й документація Hugo неповноцінна (в документації функції називаються, як в Go, а в шаблонах пишуться за псевдонімами, яких у переліках функцій та навіть в пошуці не видно).
Але, попри все це, мені вдалося! Найбільше часу витратив на спроби функціонального перетворення масивів — бо там наче є функція apply - аналог map
- але виявилося, що дуже обмежена. А мова шаблонів — все ж імперативна. Тому, наприклад, щоб перетворити масив тегів-обʼєктів у масив назв, я оголошую новий масив та циклом складаю в нього назви. Коли це збагнеш, решта вже не так складно.
Так що чекайте в наступні тижні покращень по скрипту публікації, а може й публікації самого скрипту.
07.01.2025
Міграції в SQLite
🗑️ Стикнувся сьогодні з класичним багом в Ping: видалення тегу звалювалося через порушення FOREIGN KEY
. Бо ключ я додати згадав, а ON DELETE CASCADE
забув. Почав було виправляти на рівні застосунку (тобто видаляти всі відмітки, а потім сам тег), та й думаю… хіба це не робота для бази даних?
🪨 Але ось що виявилося: в SQLite команда ALTER TABLE
здатна тільки на найпростіші зміни схеми: додати / видалити / перейменувати стовпчик, або перейменувати саму таблицю, і все. Що мене, звиклого до PostgreSQL, ввело в ступор: невже я застряг з поганою таблицею?
✂️ Знайшов відповідь на StackOverflow з цікавою порадою: оскільки схема таблиць в SQLite зберігається в спеціальній табличці, то достатньо зняти спеціальний запобігач PRAGMA writable_schema
та можна просто замінити схему та додати до неї ON DELETE CASCADE
. Ясно, що це виглядає максимально сумнівно.
💣 Сумнівно чи ні, але спробував; виявилося, що на iOS цей метод взагалі не працює, бо там підключення відбувається в особливому defensive mode, де абсолютно заборонені такі “сумнівні дії”. (Якщо в схемі змінити таким чином щось суттєве — типи чи порядок стовпчиків, наприклад — то база гепнеться.)
📖 Тоді почитав трохи документацію, та виявилося, що рекомендований підхід дуже простий: створити другу, “правильну” таблицю, наповнити її даними, а потім підмінити стару таблицю новою. Мінуси цього рішення: не відчуєш себе містером роботом. Плюси: все пройшло успішно, без втрати даних та цілісності.
❓ До речі, причина цього (яка теж є на тій же ж сторінці документації), прагматична. SQLite не зберігає схему в структурній формі, а тільки в текстовій (власне, зберігається команда CREATE TABLE
.) Тому будь-які зміни в схемі повинні були б перекладатися назад в текст CREATE TABLE
- ще й гарантовано без втрат змісту. Творці SQLite вирішили не ускладнювати цим проєкт та залишили зміни схеми в такому ручному, явному режимі.
06.01.2025
Робота без відволікань з Bunch
Взявся за те, щоб обмежити відволікання навколо робочого місця. Та, якщо на столі можна прибрати, двері зачинити, а телефон — приховати в шухлядку (до речі, всім раджу!), то з відкритими на компʼютері вікнами не так легко. Погодьтеся, зазвичай вікна не хочеться зачиняти, бо потім доведеться відкривати наново, налаштовувати стан… краще нехай всі будуть відкриті! Отримуємо цифровий аналог робочого стола, заваленого папірцями та матеріалами всіх активних проєктів.
Щоб спростити підготовку робочого місця на macOS є застосунок Bunch. Він почався як “ярлик до групи програм” (звідси назва), але з часом виріс в цілий інтерпретатор — заточений під керування програмами, вікнами, режимами ОС, та здатний на будь-які інші задачі.
Я Bunch використав, щоб закривати всі вікна та відкривати мені потрібні. Відповідно на кожну задачу є окремий Bunch-скрипт. Можливостей там безліч. Мій скрипт виглядає приблизно так:
# закриваю все, окрім "системних" застосунків
(quit all except Drafts, Obsidian, Music)
# VSCode в AppleScript не вміє, тому відкриваю воркспейс командою терміналу
$ code ~/projects/ping/ping.code-workspace
# запускаю XCode з файлом проєкту
XCode
- ~/projects/ping/ping.xcodeproj
# ...запускаю проєкт комбінацією Cmd+R
- {@r}
# роблю переднім вікном VSCode
Visual Studio Code^
Це тільки разове налаштування стану, воно не забороняє потім просто відкривати будь-які вікна тощо. Я ставлюся до нього саме так, як до прибирання фізичного столу: підготувався до роботи, а далі роблю те, що зручно.
Закривати всі вікна спочатку страшно, а потім тренує залишати роботу в завершеному стані, зокрема в браузері.
05.01.2025
JSON-бази для простих застосунків
Я якось писав, що використовую JSON для зберігання даних мого застосунку для GTD. Застосунок досі живий та я ним користуюся постійно, тож JSON виріс та займає цілих… 120 кб!
А от на тому тижні натрапив на пост, де згадувалась “база” pickleDB для Python (та ще декілька альтернатив.) Це фактично той самий єдиний JSON, але з накладеною зверху абстракцією “документів”, “списків”, та “словників” - певно, щоб код хоч трохи наближався до такого, що буде працювати зі звичайною базою. (До речі, подивився навколо, та здається, що на Python такі мікробази взагалі популярні — а на Ruby нічого схожого не бачив. Цікаво)
Хотілося додати про досвід своєї “JSON-бази” - зокрема, де я бачу її межі. Головне те, що JSON можна читати та писати тільки цілим документом. Це наче і не проблема, бо є бібліотеки, які здатні читати та писати гігабайти JSON в секунду. Але ж нам також доведеться писати його на диск, а це вже повільніше. Плюс не забути увімкнути атомарний запис, інакше ризикуємо через форс-мажор записати половину файлу та втратити все. Плюс я б не хотів робити синхронізацію доступу через файл, якщо база потрібна більш ніж в одному процесі.
Також доведеться читати та тримати в памʼяті весь зміст разом — це відразу накладає ціну, особливо в мовах, де обʼєкти в пам’яті займатимуть більше місця, ніж сам JSON. Я з таким стикнувся навіть в Go. Але, це стане помітно на сотнях мегабайтів даних.
Гадаю, все залежить від того, як часто вам потрібно читати чи писати в базу. Якщо читання відбувається під час запуску застосунку, а запис — на дії користувача, то JSON вам (як і мені) вистачатиме надовго.
04.01.2025
Моя спроба зробити Undo/Redo
Був в мене проєкт застосунку для самоменеджменту… який не досяг успіху через відсутність бачення. Втім, рушій я для нього нагородив досить потужний. Застосунок був на React Native, а стан зберігався у базі PouchDB, яка синхронізувалася з сервером CouchDB. Для мене, як я вже писав, CouchDB досі залишається потужною, але нішевою технологією без зрозумілого застосування — приблизно як Clojure.
Отже, механізм Undo/Redo, тобто “скасувати/повторити”. Зазвичай його складність полягає в неоднорідності операцій зі станом. Так, в багатьох (чи усіх) застосунках текстові операції є скасованими, але спробуй змінити блоки, що містять той текст — наприклад, задачі — та можливість скасування втрачається.
CouchDB тут має корисну властивість. Документи в ній мають ревізії, та старі ревізії зберігаються поруч з новими. Це потрібно для реплікації. Є схеми, де реплікуються зміни, а ось в CouchDB реплікуються цілі ревізії. Таким чином, досить довго можна прочитати стару версію документа. Навіть видалення відбувається через нову ревізію з маркером _deleted
. Тому: всі дії в моєму проєкті проходили через єдину операцію put
, а також — я завжди мав доступ до старих ревізій всіх документів.
Але очевидно, для користувача інтерфейс повинен бути вище за скасування “документ за документом, ревізія за ревізією”. Тому всі функції-дії, що потребували скасування, загорталися в функцію withUndo
та отримували всередині особливу реалізацію інтерфейсу DBEffects
, яку потім передавали усім операціям з базою. А інтерфейс DBEffects
складався з єдиної функції put
… з Undo вона не тільки писала в базу, а й складала перелік документів та їх ревізій до об’єкта, створеного в функції withUndo
. (Звісно, через put
були реалізовані звичайні create
, update
, delete
і так далі.)
Потім, щоб скасувати останню дію, я брав крайній обʼєкт з переліку дій, та вертав назад попередній стан порушених документів. Ось так просто. (Ревізії утворювались нові, до речі, бо як і в Git, старі дані не можна змінити.) Та це дійсно працювало з будь-якими змінами, від примітивних до складних.
03.01.2025
Що робить інтерфейси "дорогими"?
✨ Короткий списочок того, що я б хотів бачити у застосунках, над якими працюю — але на це ніколи немає часу. (Найближче була Сінтра, оскільки пан Олександр Зайцев максимально прискіпливо ставиться до деталей.)
-
🍃 Дрібні анімації. Практично кожний елемент інтерфейсу може мати анімації переходу, але робити їх зазвичай важко.
-
⚠️ Змістовні повідомлення про помилки. Не “невірна сума”, а “сума повинна не перебільшувати 66.00”, а ще краще “у вас в гаманці залишилось 66.00 на витрати”.
-
⏳ Видимий стан внутрішньої активності. Коли застосунок робить запити до сервера, або щось обчислює, зробити це помітним та зрозумілим. Як бонус: результат на завершення — теж. Від операцій, що тривають секунди, до багатогодинних.
-
🔙 Можливість скасувати зміни. Навіть найкрутіші застосунки не можуть цього собі дозволити в повному обсязі. Між іншим, я колись намагався це зробити з Redux/CouchDB - та щось навіть виходило. От тільки проєкт не злетів. Треба буде про це написати.
-
🐣 Обробка виключних ситуацій. Найперша з них — порожній стан. Але інтерфейси просто складаються з виключних ситуацій. Наприклад, коли предмет один, або коли їх значно більше, ніж заплановано. Коли назви довгі, а статті короткі. Коли світлина прямокутна, а не квадратна. Далі та далі.
-
🇺🇦 Переклад. Потребує величезних інженерних витрат, а потім ще й власне перекладацьких. А ще й локалізації — правильного відображення кількостей та значень. А потім ще й підтримки. Втім, це найбільш практична “розкіш” з переліченого.
🥳 PS: стохастичний таймтрекер вже доступний для бета-тестування, доєднатися можна за посиланням.
02.01.2025
Мій власний Steam Replay, або в що я грав у 2024 році
Як я обіцяв собі зробити звіт на кшталт Steam Replay, так і зробив. Причому навіть як функціональність застосунку, тобто можна такий саме звіт зібрати з будь-чого. Обираєш тег-“всесвіт” (бо якщо не обмежувати вибірку, то вийде повна каша) - тут це тег gaming
. А потім набір тегів-категорій - з тих, що з ним перетинаються. Отримуєш ось такий цікавий графік.
…Насправді зі звітом все складніше. Те, що ви бачите — це я, після першого поганого досвіду, обрав топ-10 ігор. Бо там ще купа дрібних записів на 1-2 години, які вже перетворюють графік на шум. В кольорах розібратися неможливо. (Гадаю, треба відразу автоматично залишати на графіку топ-10.)
Технічно цей функціонал включав купу рефакторингу коду деревної мапи — бо там сидить обчислення “покривної” статистики. А ще форми пінга — бо там є UI вибору тегів. В цілому у Swift це приємно робиться, але: він любить кидатися помилкою про “не можу розібратися в типах”, та далі ми залишаємося на самоті: або розпилюй на менші функції — без допомоги IDE, або шукай помилку — теж наосліп. Вихід примітивний: коментую шматки коду, поки не скомпілюється; тоді поламане місце десь в коментарях.
…А тепер, до результатів. BG3 я так і не пройшов; досяг level cap та стало якось не цікаво. Сталкер здивував; але його поки теж закинув. Evil Within 2 - нудьгуватий, але компетентний. Hades - екшн-Roguelike з безліччю цікавих комбінацій. Echo Night Beyond на PS2 - гра випередила час, вигадлива, але логічна адвенчура від першого лиця. Ashes of the Apocalypse - вже писав, бумер-шутер з мистецьки зробленим світом.
Dread Delusion - не закінчив, але все ще планую, це як King’s Field з відкритим світом. Morrowind - цього року досліджував Tamriel Rebuilt. Rule of Rose - ще одна гра на PS2, SIlent Hill 2-like в цікавому сетінгу. Cyberpunk 2077 - а ви знаєте, що з виходом доповнення вони повністю переробили всю рольову систему та лут? Я таке вперше бачу.
Нагадаю, що вся ця статистика зібрана моїм стохастичним таймтрекером, який ось-ось можна буде завантажити в TestFlight… як тільки відділ підтримки Apple виспиться.
01.01.2025
Застосунки за замовчуванням: 2025
Почнемо рік з встановлення цікавої традиції: визначення застосунків, якими ти користуєшся “за замовчуванням”. Це цілий рух, ось його домашня сторінка, а нижче — мої застосунки. Тицяй у 📝, щоб перейти на відповідний пост.
- 📨 Поштовий клієнт: MailMate
- 📮 Поштовий сервер: Fastmail
- 📝 Нотатки: Obsidian 📝 та Drafts 📝
- ✅ Задачі: мій власний застосунок CoreGTD
- 📷 Фотографування айфоном: звичайна камера, але в складних ситуаціях (як ручний фокус) також Halide.
- 🟦 Керування світлинами: Apple Photos
- 📆 Календар: Apple Calendar та Dato
- 📁 Хмарне сховище файлів: Apple iCloud
- 📖 RSS: Reeder Classic та Miniflux. 📝
- 🙍🏻♂️ Контакти: Apple Contacts
- 🌐 Браузер: Safari :)
- 💬 Спілкування: там, де контакти. Але будь моя воля, певно залишив би тільки Apple Messages.
- 🔖 Закладки: Obsidian. Просто закладки “окремо” я не люблю.
- 📑 Список читання: Reeder Classic 📝
- 📜 Текстовий процесор: Apple Pages, я не вибагливий :)
- 📈 Табличний редактор: Apple Numbers :)
- 📊 Презентації: давно не робив, але Deckset.
- 🛒 Список покупок: Apple Reminders
- 🍴 Планування їжі: рецепти мої у Paprika, а не мої — в NYT Cooking. 📝
- 💰 Особисті фінанси: якби вів, то в нашій Сінтрі. 📝.
- 📰 Новини: знову Reeder та RSS 📝
- 🎵 Музика: Apple Music
- 🎤 Подкасти: Overcast
- 🔐 Паролі: 1Password 📝
- 👨💻 Редактор коду: VSCode
- 🔣 Термінал: Fish, а емулятор - в тому ж VSCode
- 🎨 Графічний редактор: Pixelmator.