henry_flower: A melancholy wolf (Default)
2017-02-21 02:18 am

[sticky entry] Sticky: 2 іграшки для DreamWidth

Іграшка 1-ша: CLI кліент для DW. Користуюсь десь з місяць, ніби працює.

Read more... )

Іграшка 2-га: транслітератор з російської в українську

Read more... )
henry_flower: A melancholy wolf (Default)
2017-02-11 12:08 am

[sticky entry] Sticky: П'єси Подерв'янського у Standard Stage Play Format

https://sigwait.gitlab.io/les_podervyansky--plays/

Коли 2 роки тому, мені закортіло перечитати п'єси ЛП, я не спромігся їх знайти у нормальному epub. Тоді я зробив свою компіляцію і розповів про це десь у LJ. Не знаю чому, але the resulting epub/pdf найбільше скачували з Росії. Навіщо їм там Подерв'янський?

На останній сторінці epub'у я попрохав надсилати мені erratum. За 2 роки це наважилася зробити аж 1 людина з Чехії. Сьогодні я, нарешті, знайшов сили epub обновити.

Read more... )
henry_flower: A melancholy wolf (Default)
2025-05-11 07:33 pm

Безвідповідальна модифікація інсталяції

Майкрософт вперше додали механізма autounattend.xml за часів захоплюючих краєвидів Вісти. Користувач завантажував те що зараз називається ADK віндюковий, який містив графічну ютіліту WSIM, яка випльовувала xml-файла. Останній клавсь до кореня usb-драйву і віндюка (ув ідеалі) інсталювався без втручання хомо сапієнса.

Нічого революційно нового ув тому не було--лайнакси схожі механізми мали багато років, наприклад kickstart ув Федорі.

Прублема ADK є ув тому, що він потребує інстальованої ОС, а способу генерації autounattend.xml з-під ОС конкурентів Майкрософт не надає, окрім цінних порад писати його ув текстовому редакторі вручну (дуже дякую).

Не слідкуючи за їх цирком на дроті, деякий час назад я почув, що з винахідливим змістом того .xml можна отримати інсталяцію віндюка без сучасних та корисних аплікації типу коупайлота або ноутпеда (модерн) чи аутлука (н'ю), та без необхідності ув танцюванні гопака для створювання 'офлайнового' користувацького акавнту.

Різноманітні онлайнові генератóри autounattend.xml змінюються як пори року. Поточним, працюючим, популярним (ППП) залишається дойчландський unbeaufsichtigter (вимовляйте це самі) generator.

На цьому пригоди лише починаються:

  1. Якщо потрібен .iso з доданим autounattend.xml для створення мошини віртуальної, то як модифікувати оригінальний .iso? Всередені там є хфайлова система udf, яку змаунтувати можна тільки read-only.

  2. Якщо треба інсталювати віндюка на справжнього комп'ютера (ви не повірите, але такі випадки трапляються непоодиноко), то як створити з оригінального .iso завантажувальний usb?

Нагадаю: ми ув лайнаксі, де жодні rufus'и працюють не тут. Різноманітні ютіліти різної ступені занедбаності або підтримують лише .iso лайнаксних дістро, або написані ув найкращому випадку школярами, ув найгіршому--вініпухами чи руснею (маґа-патріот-джон с тексастской області лєнінґрадскоґо района). Ув 2х останніх різновидах є гарний шанс отримати гарного .iso с безоплатним подарунком всередині.

З найсмішніших варіянтів що я бачив, кількість залежностей є така, що аплікацію, разом з залежностями, завертають ув appimage як ковбасу.

.iso → usb

Rufus'и та друзі для режиму UEFI роблять нічого складного: на usb-дівайсі створюється дві GPT партішони:

  1. невелика fat32;
  2. ntfs (рідше--exfat), яка займає решту дівайса.

До 1ї переписують бутлоадера (зазвичай 'універсального', який може вантажити декілька різних ОС). До ntfs копіюють вміст .iso.

Ув нашому варіянті майкрософтський .iso має свого бутлоадера, тому скопіювати можна його. Головний трюка є ув правільному створені партішонів: (1) fat32 не повинен бути з позначками esp та boot, інакше віндюковий інсталятора вирішить що саме на usb-драйві буде EFI розділ для ОС, хоча останню він ставить до геть іншого драйва; (2) без позначки msftdata на ntfs партішоні віндюка кидає "Install driver to show hardware" даялога, а її diskpart малює його як "Unknown". Вкрай неввічливо.

$ parted test.img print | grep -v ^Disk
WARNING: You are not superuser.  Watch out for permissions.
Model:  (file)
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End     Size    File system  Name     Flags
 1      1049kB  1074MB  1073MB  fat32        BOOT     msftdata
 2      1074MB  7516MB  6442MB  ntfs         INSTALL  msftdata

Наївний скрипта, який це втомлююче робить, на жаль, вимагає рута через створення loop devices, але не чіпає хадварних драйвів:

$ sudo ./winiso2img Win11_24H2_English_x64.iso test.img

test.img потім можна перенести на usb-драйв будь-яким улюбленим способом, наприклад:

$ sudo dd if=test.img of=/dev/XXX status=progress bs=1M oflag=direct,sync

Ув принципі, щоб згенерувати хфайла з потрібною таблицею партішонів можна обійтися без losetup+parted, а взяти sfdisk, але як потім змонтувати партішони всередині test.img звичайним користувачем я не знаю.

Модифікація .iso

Майкрософтські .iso зроблені з хфайлової системи udf, а крихітний шар iso9660 там містить лише хфайла README.TXT з повідомленям про skill issue читача.

Лайнакс знає про udf, але маунтить оті .iso лише ув read-only.

Спочатку я хотів взяти 7-zip. Розаркування великого аркайву (> 5GB) займає час навіть на сучасних мошинах, тому бажано повідомляти користувачу що коїться. 7-zip друкує лінію стану розаркування, але робить це ув найворожіший спосіб. По-1ше, автор 7-zip'у вважає що \r (carriage return) то є буржуазноє ізлішєство. Ось фрагмент збереженого аутпуту:

00000000: 2020 304d 2053 6361 6e20 2f68 6f6d 652f    0M Scan /home/
00000010: 616c 6578 2f44 6f77 6e6c 6f61 6473 2f77  alex/Downloads/w
00000020: 696e 2f69 736f 2f77 3131 2f08 0808 0808  in/iso/w11/.....
00000030: 0808 0808 0808 0808 0808 0808 0808 0808  ................
00000040: 0808 0808 0808 0808 0808 0808 0808 0808  ................
00000050: 0808 0808 0808 2020 2020 2020 2020 2020  ......
00000060: 2020 2020 2020 2020 2020 2020 2020 2020
00000070: 2020 2020 2020 2020 2020 2020 2020 2020
00000080: 2008 0808 0808 0808 0808 0808 0808 0808   ...............
00000090: 0808 0808 0808 0808 0808 0808 0808 0808  ................
000000a0: 0808 0808 0808 0808 0808 0808 2020 3025  ............  0%
000000b0: 2031 3030 204f 7065 6e08 0808 0808 0808   100 Open.......
000000c0: 0808 0808 0808 2020                      ......

Воно видаляє рядок за допомогою циклу з \b (backspace), потім друкує пробіли щоб видалити їх знову.

По-2ге, воно відмовляється малювати лінію стану коли stdout не є терміналом. Зі script(1) це можна побороти і, з деяким зусиллям, я змусив аутпут від 7z бути зумісним з dialog(1) gauge, але після проглядання 7-zip src, огида до нього у мене переросла бажання його торкатися. Це 1 з найгірших src, які я бачив. То є диво що воно працює взагалі, а кількість CVE починаючи з 2022 наводить на конспірологічні думки які я тут писати не буду.

Якщо ігнорувати 7-zip, як тоді модифікувати змонтований read-only .iso? Можна скопіювати все ув іншу директорію, але існує більш шляхетний спосіб: overlayfs.

З ним гагібайти хфайлів не копіються, але метод вимагає рута. FreeBSD ніби мала раніше щось схоже (unionfs), але коли я користувався fbsd, воно часто працювало з помилками.

Після отримання директорії з autounattend.xml хфайлом, її завертають ув .iso знову. Скрипта, який це робить, є на 24 рядки більший за попередній winiso2img:

$ wc -l *
  67 mkwiniso
  91 winiso2img
 158 total
$ sudo ./mkwiniso add lol.xml Win11_24H2_English_x64.iso test.iso

Якщо просто треба з розаркованого Майкрософтського .iso створити новий, рута є непотрібний:

$ ./mkwiniso generate dir 1.iso
henry_flower: A melancholy wolf (Default)
2025-05-11 03:29 pm

Куток лузерів

712 безглуздих слів про "renewing the Brand".

Дякуючи Зевсу, Гері та іншим богам, ЕйАй, який нищить Stack Exchange, вміє перетворювати воду на сенс:

"Stack Exchange is launching a rebrand because they think their identity is a mess, and it’s hurting their business. Instead of fixing real problems, they’re wasting time and money on consultants and vague “brand strategy” to make everything look more polished while AI eats their lunch. They say they want community input, but this is corporate theater."

henry_flower: A melancholy wolf (Default)
2025-05-03 09:06 am

Why isn't the Golden Gate golden?

Понапридумували ґолдів:

colorsel screenshot
henry_flower: A melancholy wolf (Default)
2025-04-08 01:13 am

Лінки до віндюка ув 2025

З безтурботних часів кінця сивочолого криголама (що мені не подобалось?), для знаходження діректових лінків на .iso, Майкрософт кілька разів змінювали внутрішній ойпіай, загалом, потреба ув якому як була так і залишається незрозумілою, якщо не рахувати необхідність свіжих булітів кожного end-of-year рів'ю.

Щоб завантажити 24h2, треба отримати ойді сесії та список мов, за якими Майкрософт подарує згенерований url ув джейсоні. Як робити найостанніший ріквеста забагато разів на годину (наприклад, нахабно 5 разів), Майкрософт зобанить. Покласти всі .iso на статичного сервера не можна--це буде глузування над здоровий глуздом, потрібна Процедура.

$ grep -Ev '^#|^$' windows-iso-url | wc -l
28

Скрипта вимагає nokogiri.

henry_flower: A melancholy wolf (Default)
2025-03-22 09:55 pm

Respect my capabilitah

Спостерігаючи за бійкою між Поттерінґом та термінальними пуристами, я дізнався про таке собі XTGETTCAP. Це є механізма (відносно новий, ~2019?) для отримання можливостей терміналу без консалтінґу terminfo. Це означає, що брехати про тип терміналу з XTGETTCAP не вийде.

Наприклад, якщо хочеться намалювати користувачу саме монохроматичного інфобокса, зараз можна підсунути назву терміналу, для якого ув terminfo зазначено безбарвне життя:

$ TERM=vt100 dialog --infobox 'ну шо ти малá' 0 0

Це працює тому що даялоґа використовує ncurses, який дивиться ув terminfo. Якби даялоґа стріляв ув термінала XTGETTCAP'апом, замість читання змінної середовища TERM, такий хфокус би не спрацював:

$ echo $TERM
xterm-256color
$ TERM=vt100 ./XTGETTCAP TN
xterm-256color

Якщо ув теорії XTGETTCAP--майбутнє вже сьогодні, то на практиці цей механізма мені не подобається. Можливо in-band signalling було надією людства ув 1970ті, але зараз щоб імплементувати XTGETTCAP запита та прочитати результата, треба перемикати термінала ув raw режим, конвертувати ім'я capability ув hex строку і писати до tty обгорнувши її з \e (ascii 033 ув октал) з обох боків, поряд з іншим мотлохом:

printf '\033P+q%s\033'\\ "$capablity" > "`tty`"

Що для "TN" виглядає як

00000000: 1b50 2b71 3534 3465 1b5c                 .P+q544e.\

Потім треба обережно прочитати результата все ще знаходячись ув raw режимі. Якщо робити це з акуратністю, з якою нарід пише скрипти шелóві, вірогідність того, шо термінал буде заблоковано перманентно, наближається до 1.

Для wayland'у є емулятор терміналу foot, який має окрему C ютіліту для читання XTGETTCAP, але вона наглухо висне, якщо термінала не розуміє запиту. Як можна сподіватися на здібності гіпадріла звичайного, якщо це не можуть порядно зробити навіть люди, які пишуть термінального емулятора.

Окрім xterm (який вимагає увімкненого allowTcapOps, який вирублений по замовчуванню на всіх ОйБіЄм'івських дістро через цирка на дроті), найбільше просунулося у цьому напрямку індійське диво kitty (на яке я маю алергію після стількох років страждань з calibre (автор той самий)), та усілякі ghostly та ін. штукенції, якими користуються 12 людей на планеті земля. iterm2 та діфолтне еплівське одоробло також ніби має підтримку, але мені є лінь перевіряти.

Найсумніше є те, що XTGETTCAP наразі не працює через gnu screen чи tmux, що хоча є вина останніх, популярності XTGETTCAP не додає.

Скрипта, яким можна тестувати:

#!/bin/sh

set -e

eh() { echo "Error: $*" 1>&2; trap - 0; exit 1; }
text2hex() { od -A n -t x1 | tr -d ' \n'; }
hex2text() { sed 's/../0x& /g' | xargs printf '\\\\%03o\n' | xargs printf %b; }
stty_orig=`stty -g`
stty_restore() { stty "$stty_orig"; }

[ -n "$1" ] || eh Usage: XTGETTCAP capability

capablity=`printf '%s' "$1" | text2hex`
trap stty_restore 0

stty -echo raw time 1 min 0
printf '\033P+q%s\033'\\ "$capablity" > /dev/tty
buf=`dd status=none count=1`
stty_restore

[ -n "$buf" ] || eh unsupported terminal
[ "$V" ] && printf %s "$buf" | xxd

buf=`printf %s "$buf" | sed 's/[^a-zA-Z0-9+=]//g'`
[ P1 = "${buf%+*}" ] || eh unknown capabilitah
printf %s "${buf#*=}" | hex2text | xargs

Повинно працювати з пацаватими dash, busybox sh та ін.

hex2text() тут не фонтан, звичайно, але нопешіть як то можна інакше, щоб (а) було сумісно з fbsd, (б) не вимагало чогось ікстернального, як xxd.

$ ./XTGETTCAP TN
xterm-kitty
$ V=1 ./XTGETTCAP colors
00000000: 1b50 312b 7236 3336 6636 6336 6637 3237  .P1+r636f6c6f727
00000010: 333d 3332 3335 3336 1b5c                 3=323536.\
256
$ ./XTGETTCAP шо?
Error: unknown capabilitah
henry_flower: A melancholy wolf (Default)
2025-03-20 12:28 pm

Кольорологія терміналів

Ув вікіпідіа написано шо хадварні термінали VT100 (продавалися з 1978 року) та VT220 (1983) були монохромні (кольорові варіянти, напевно, лише для заможних верств гіпадрілів). Terminfo ув лайнаксі про ці термінали містить ту саму інформацію:

$ for i in dumb vt100 vt220 screen xterm $TERM; do printf "%-20s" $i; TERM=$i tput colors; done
dumb                -1
vt100               -1
vt220               -1
screen              8
xterm               8
xterm-256color      256

Кілька днів тому, нєкто Поттерінґ, мастодонів про діфолтне значення TERM, яке обирає systemd:

'The default fallback $TERM we so far picked has been "vt220" … This deemed us to be a good choice, since (unlike the better known vt100) these kind of terminals support PageUp/PageDown keys'

Загалом, ув terminfo дивляться зазвичай тільки ютіліти які для користувацького інтерхфейсу використовують ncurses; решта вгадує можливості терміналу шляхом слідкування за візерунками руху небесних тіл, e.g.:

  • чи є змінна середовища COLORTERM (нещодавно обраний шлях systemd);
  • змінна середовища NO_COLOR (я би віддав перевагу, навпаки, щось на кшталт COLOR=1, бо діфолтне плювання кольорами ув консолі є страшенний несмак);
  • дивитися на TERM і шукати збіг ув своїй вкомпайлений (не жарт) табличці, як то робить ls з coreutils;
  • механізма terminal-colors.d(5) з util-linux, яким користується ніхто, окрім деяких ютіліт з util-linux.

Лайнаксний ls, звичайно, перемагає ув конкурсі найдебільнішого рішення.

Воно друкує барвами для vt100, тому що протягом компіляції coreutils хапається текстовий хфайла з налаштунками для dircolors(1), який має ось такі рядки:

TERM vt100
TERM xterm*

Цей хфайл (його копія зазвичай є ув /etc/DIR_COLORS) скрипта перловий конвертує ув символьного масива

$ head -c7 src/dircolors.hin # ув ріпо coreutils
# Confi
$ (head -c61 && printf 🤡 && tail -c39) < <(src/dcgen src/dircolors.hin)
static char const G_line[] =
{
  '#',' ','C','o','n','f','i',🤡,'r','i','a','b','l','e','s','.',0,
};

який потім інклудиться як dircolors.h ув ls.c, де є хфункція

/* Check if the content of TERM is a valid name in dircolors.  */
static bool known_term_type(void) {
  char const *term = getenv("TERM");
  if (!term || !*term)
    return false;

  char const *line = G_line;
  while (line - G_line < sizeof(G_line)) {
    if (STRNCMP_LIT(line, "TERM ") == 0) {
      if (fnmatch(line + 5, term, 0) == 0)
        return true;
    }
    line += strlen(line) + 1;
  }

  return false;
}

dircolors(1) також парсить нещасну G_line та друкує 0 інструкцій для ls, коли TERM є відсутня чи несе невідоме значення:

$ env -i SHELL=/bin/sh dircolors
LS_COLORS='';
export LS_COLORS

У чому сенс тоді, навіть за відсутності LS_COLORS, розмальовуювати множинні character devices з директоріями (довгий масив color_indicator[] ув ls.c, якщо комусь цікаво), але полишати файли звичайні (кольори для яких G_line має), залишається невідомим.

henry_flower: A melancholy wolf (Default)
2025-03-01 07:45 am

Как он пасмєл наєзжать?

Найвизначнішою рисою істот з СНД, навіть якщо останнє вони покинули фізично, залишається гарантована compassion towards victims.

Приклад типовий: https://stas.dreamwidth.org/1468036.html разом з коментарями.

henry_flower: A melancholy wolf (Default)
2025-02-14 04:18 pm

Перевірка на хадварні відеокодувальники ув Кроумі

Бовзер Кроум має інтернáльну сторінку chrome://gpu/, яка малює, поміж інших речей, статуса хадварних відеокодувальників. Якщо драйвер gpu не опиняється ув блеклисті (це слово можна знову використовувати?), ближче до кінця там з'являється таблиця з рядками

Decode hevc main    64x64 to 8192x4352 pixels

З інтернáльною сторінкою пов'язано багато смішного. Наприклад, одного разу на безмежні узбережжя скелястих форумів гоогла викинуло монголку:

"Добрий дєнь! вопрос про страніцу chrome://gpu - у мєня поддєржка ЯндєксКарти запросіла пріслать содєржімоє єтой страніци. Єто что, вообщє? Єто бєзопасно? Я вставіла єє в адрєсную строку і получіла коллапс какой-то - цвєтниє полоси, клінья, сєґмєнти і прочую бєлібєрду, послє чєґо всє откритиє страніци інтєрнєта пошлі "плясать" - содєржімоє пєрєкорєжіло. Вірус, что лі?"

Раніше, якщо треба було дивитися на окремі відеофрейми, брався src відеокодеку на C та відчайдушно компілювався ув вебасемблі. Зараз це ліпше робити ні, т.я. бовзер розуміє вдосталь відеокодеків самотужки і має новітнього ойпіай (йому 5 років) для маніпулювання фреймами--WebCodecs.

Ув лайнаксі, Кроум використовує хадварні відеокодувальникі через завдяки за допомогою VAAPI. Якщо дістро має застарілі (або занадто нові) версії Mesa з їх DRI-драйверами, відеокодувальники ув Кроумі будуть софтварними, навіть якщо GPU підтримує їх хадварну акселерацію. З ідіотськими порадами "не викидайте старі комп'ютери, а інсталюйте на них лайнакса", наївний користувач отримує гул кулерів, який був відсутній ув віндюку, де Кроум дружив з драйверами GPU.

WebCodecs має метода статичного VideoDecoder.isConfigSupported(), яким можна перевірити чи використовує Кроум могутню акселерацію для конкретного кодека. З VideoEncoder це можна перевірити для конкретної роздільної здатності.

Наприклад, якщо пастнути текста нижче ув дівтулзову консоль, воно надрукує трохи більш зручну табличку, аніж простирадла з chrome://gpu/:

Promise.all([
    ['AV1',   'av01.0.08M.10'],
    ['H.264', 'avc1.640033'],
    ['H.265', 'hev1.1.6.L120.90'],
    ['VP8',   'vp8'],
    ['VP9',   'vp09.00.40.08']
].map( async v => ({
    name: v[0],
    codec: v[1],
    support: (await VideoDecoder.isConfigSupported({
        codec: v[1], hardwareAcceleration: "prefer-hardware",
    })).supported
}))).then(console.table)

(Звичайно шо isConfigSupported() є червона хфункція, т.я. потрібно нагадувати гіпадрілам постійно шо життя це є боротьба.)

На жаль, console.table() не вертає html, тому щоб написати уйоб-сторінку, яку зручно відкривати на SBC з ондроїдом або кетайських тівібоксах, треба малювати все самому.

Мінімальний приклад (працює лише ув Кроумі; Файрфокс видає галімат'ю):

$ wc -l *html
47 index.html

є отут (вибачте що не Реакт). Ув якості домашнього завдання, додайте вибір варіянтів baseline/main/high кодеків з <select>.

henry_flower: A melancholy wolf (Default)
2025-01-27 04:02 pm

Шльопання форм статичних

Я уявляю це собі так: ув старі добрі часи для швидкого анкетування можна було пнути хропучого сисадміна, який би написав html форму та елементарний сервер (на його улюбленій мові), що з'їдав би post ріквеста і складав результат ув якійсь директорії. Після чого сисадмін вертався до своєї комірки сопти далі, а ікзéк'ютів починав спамити колег адресою 172.20.1.15/surveys/job-satisfaction.html.

Тобто, як то воно була насправді, я гадки не маю. 10+ років тому, коли потрібні були анкети, я робив їх ув гоогл хформах і читав результати ув гоогл шітс. Зараз, звичайно, варіянти коливаються від $0/mo (вся дейта вільно продається кому завгодно) чи $199/mo за "1 active project" з "10K сабмітів" (вся дейта дістається русскім з рансомware угрупувань) до селф-хостинґу з хропучим дівопсом фултайм ув комплекті (вся дейта губиться, коли дівопс звільняється).

Чомусь останній варіянт з селф-хостинґом завжди виглядає як конкурс мошин Руба Голдберґа, хоча такий "проєкта" має бути на рівні можливостей будь-якого школяра. 2 файли ув найпростішому вигляді: form.html та server.js. Ні?

Орхітектура

server.js містить статичного http сервера. На GET /simplest шукає public_html/simplest/index.html та виставляє сесію ув cookie. Автор анкети пише simplest/index.html руками ув текстовому редакторі. Найпростіша анкета:

<form method="post">
Name? <input name="name" type="text" required>
<input type="submit">
</form>

Це є класичний postback, тобто POST ріквест форма відсилає на той самий pathname за яким було GET сторінки. Ув даному випадку це дозволяє з URL викопирсати ім'я анкети. Ув директорії public_html/simplest/ можуть бути будь-які файли, які треба для рендеренгу екстравагантної форми: світлини, відео, джаваскрипт і т.ін.

Число анкет обмежується лише лімітами файлової системи. Додавати анкети можна ув ріалтайм, перезапускати сервера є непотрібно.

Якщо POST був без помилок, application/x-www-form-urlencoded конвертується ув json і ✍️ ув окремій директорії.

Сервер

Бовзерна валідація хформ працює всюди > 10 років (окрім ондроїдного файрфоксу, тому що кожного року треба усім світом збирати мульйони долярів на зарплату CEO і часу на все не вистачає). Перевірку пейлоаду з post ріквеста можна робити загальнопролетарським json schema, який можна генерувати зі статичного form.html на льоту.

Є купа мотлоху який генерує html з json schema, але не навпаки: я знайшов нічого, що оналізує якийсь <input type="text" minlength="2" maxlength="50"> і випльовує скіму, але з ноудівським cheerio можна написати свій генератор за кілька годин. Форми не можуть бути вкладеними одна в одну, тому складність генератора буде рости лише з кількістю віджетів.

Наприклад, з

<input type="radio" name="os" value="linux" required> Linux
<input type="radio" name="os" value="macos"> macOS

генерується

{
  "type": "object",
  "properties": {
    "os": { "enum": [ "linux", "macos" ] }
    ...
  },
  "required": [ "os", ... ]
}

Ванільний фронтенд не був ви фронтендом, якщо би всі стандартні віджети працювали однаково. Якщо для множини radio кнопок підтримується валідація "required", то для множини чекбоксів немає ані "required", ані перевірки мінімальної кількості увімкнених прапорців (навіщо bother, як то кажуть ув Калґарі). Я виставляв data-required="true" та data-min="N" на парентному div.

Коли анкетування є номінально анонімним, auth можна пропустити і просто встановлювати cookie щоб користувач, який згадає шо він цейво, як його, забувсь, шо він хотів написати, міг якийсь час свою анкету редагувати. З

Set-Cookie: sid=2025/01/26/a8b92708-f1dc-4870-abee-907a50a10800; Max-Age=31536000; Path=/js101/
Set-Cookie: signature=3c564fa3f3b534b85fdab82deb575c2ab2af48b7; Max-Age=31536000; Path=/js101/

результати записуються ув db/js101/2025/01/26/XXXX/results.json, які ~легко трансформуються ув csv. Без couchbase, mongo чи mariadb. Жах.

(Здогадайтеся для чого потрібна "signature".)

Анкета стає автоматично 403, якщо хфайл з формою має mtime < зараз. Тобто, валідна анкета має обов'язково мати дату модифікації ув майбутньому, що є трохи незвично, але звільняє від необхідності тримати якогось конфігураційного хфайла.

Якщо над директорією з results.json запустити іншого статичного http сервера, то анкета, сімлінка якої пишеться поряд з results.json, може читати results.json і заповнювати ту саму форму результатами тяжкої роботи користувача.

Віджети

Стандартних має вистачити, але як тільки закортить мінімального дайґрешона, треба готуватися до ведмежих кутів бовзерних ойпіай.

Наприклад, я хотів вертикального слайдера з підписом рисок:

spartaforms-slider

Кастомний уйоб-компонента якого виглядає ось так:

<spartaforms-slider name="knowledge" min="0" max="4" value="0" required>
  <span data-value="4">I'm Douglas Crockford 🧙‍♂️</span>
  <span data-value="3"><i>Très bien</i></span>
  <span data-value="2">Can code with an LLM</span>
  <span data-value="1">Syntax only</span>
  <span data-value="0">Nil</span>
</spartaforms-slider>

Всередині то є звичайний <input type=range>, розвернутий на -90°; зі <span> генерується datalist, з якими слайдер стає схожим на словацького термометра. Можна було би писати datalist самотужки, та його відмовляється рендерити ie6 еплівський вебкіт.

Але <input type=range> сидить ув shadow dom уйоб-компонента, тому форма на сторінці його не бачить! Будь-які зміні ув атрибутах <spartaforms-slider> треба переносити до <input> (і навпаки) самому. Щоб дружити з формами існує спеціяльний ойпіай, який став з ~2023 таким популярним, що про нього користоґо є ~0, окрім скупих речень на whatwg.org.

Якоїсь підтримки ув збереженні інпуту, набраного користувачем ув елементах форми, від бовзера чекати є марно. Доводиться перехоплювати submit івента щоб зберігати їх десь (хоча би ув localStorage) і самотужки відновлювати, коли користувач вирішує ту форму відредагувати. Все це вміють робити різноманітні фреймвоки багато років, а ванільний джаваскрипта полишає нічого, окрім дратування через витрачання часу на цю ґілімат'ю.

Ін конклуз'йоне

Загалом, екперимента визнано невдалим. Що здавалося елементарним 1+N файловим рішенням (де N--кількість анкет), ув реальності з 3ма анкетами виросло на

$ tree --gitignore -I test --dirsfirst
.
├── public_html
│   ├── eng210
│   │   ├── form.css -> ../form.css
│   │   ├── form.js -> ../form.js
│   │   ├── index.html
│   │   └── widgets.js -> ../widgets.js
│   ├── js101
│   │   ├── form.css -> ../form.css
│   │   ├── form.js -> ../form.js
│   │   ├── index.html
│   │   └── widgets.js -> ../widgets.js
│   ├── simplest
│   │   └── index.html
│   ├── 60438.svg
│   ├── favicon.ico
│   ├── form.css
│   ├── form.js
│   ├── index.html
│   ├── posted.html
│   └── widgets.js
├── Makefile
├── mkschema.js
├── package.json
└── server.js

з малонадихаючим розміром джаваскрипта:

$ wc -lc *js public_html/*js
  196  5260 mkschema.js
  249  8322 server.js
  136  4193 public_html/form.js
  101  2906 public_html/widgets.js
  682 20681 total

Приклад анкети є ось тут. Чи має сенс цей ковгосп викладати на ґітгаб, я не знаю.

henry_flower: A melancholy wolf (Default)
2024-12-28 11:47 am

Експерименти з nolibc TCP-сервером

Such is human stupidity that whatever is difficult
to obtain is always thought to be better.
-- Peter Martyr d'Anghiera, 1524.

Колись давно, років 8 тому, ув часи сивочолого гетьмана і кволих реформ мусоріату, на HN з'явився лінка на http-сервера розміром 1KB. Не src того сервера був 1KB, а його статично зкомпільований байнарник. Все що той сервер вмів робити це відповідати на будь-який ріквест 1 файлом. Я проявив 0 інтересу, тому що написано воно було на asm/C ув пропорції 60/40.

Ідея такого розміру програм сподобалася французькому автору haproxy і він вмовив нарід ув LKML погодитися закомітити окрему бібліотеку для написання "nolibc" аплікацій. Прикладом такої аплікації є rcutorture--тестування кернела--де генерується невеличкий initrd, написаний на C без використання будь-якої libc.

Іронія ув необхідності спеціяльної бібліотеки щоб не використовувати стандартну бібліотеку нагадує спробу обійти коло, рухаючись по його контуру.

На тему < 1KB байнарників є класичний текста Really Teensy ELF Executables, але у ньому йде мова про асемблера, на який я не бажав звертати уваги ув минулому і на який не бажаю звертати уваги зараз.

Чи можна створювати крихітні (байнарі-вайз) аплікації без асемблеру на C без стандартної бібліотеки? Майже пусту main() лінкер--лінкує, але:

$ cat 42.v1.c
int main() { return 42; }

$ clang -Os 42.v1.c -o 42.v1.o -c
$ ld.lld -e main -o 42.v1 42.v1.o

$ ./42.v1
Segmentation fault (core dumped)

Виявляється, осемблер потрібен щоб кликати сисколи лайнакса:

$ cat 42.v2.c
static inline long syscall(long NR, long arg1) {
  long r;
  __asm__ volatile("syscall" : "=a" (r) : "a" (NR),
                   "D" (arg1) : "rcx", "r11", "memory");
  return r;
}
int main() { return 42; }
void _start() { syscall(60, main()); }

$ clang -Os 42.v2.c -o 42.v2.o -c
$ ld.lld -e _start -o 42.v2 42.v2.o
$ strip -s 42.v2

$ strace ./42.v2
execve("./42.v2", ["./42.v2"], 0x7ffc7190b9a0 /* 105 vars */) = 0
exit(42)                                = ?
+++ exited with 42 +++

Ага! Такий статичний байнарник має розмір 800 байт і залежить від 0 бібліотек. Номер сисколу (60) для exit можна подивитися ув файлі arch/x86/entry/syscalls/syscall_64.tbl кернельного src. На відміну від віндюка, де ABI змінюється з щорічними "feature updates", такий 800-байтовий ікзек'ютабл буде працювати доки є жива орхітектура x86_64.

Чи складно тоді, маючи обгортки для лайнаксних сисколів, написати елементарного TCP сервера для x86_64? Я вирішив спробувати:

$ _out/uptime-tcp-nolibc &
[1] 53161
$ nc --recv-only 127.0.0.1 1377
 07:16:29 up 1 day(s) 22:14,  load average: 0.19, 0.31, 0.30

Воно вертає шось схоже на команду uptime на ремоутній мошині, але не використовує ніяких екстернальних команд чи бібліотек. Якого розміру uptime-tcp-nolibc та скільки він їсть пам'яті написано ув кінці посту.

Якщо без libc немає жодного способу робити I/O чи відкривати сокета, тоді С стає фанкшонал мовою, про яку мріяли вчоні з 80х років, але, на їх невдачу, кернела набув IP стек та аналоги open(2) з write(2).

42.v2.c не вміє читати argv чи environ, а варіятивні хфункції у ньому роблять кордамп. Останне лікується

__attribute__((force_align_arg_pointer))
void _start() { ... }

З argv все є набагато гірше: якщо мета є саме отримувати поінтера на argv, потрібен знову осемблер (~14 рядків), який я тут постити не буду, щоб не лякати людей. Технічно, прочитати argv можна просто відкривши /proc/self/cmdline, ув якому \0 є роздільником аргументів:

$ tr \\0 \\n < /proc/self/cmdline
tr
\0
\n

але вас засміють ув інтервебах.

Перша каменюка на шляху до сокетів лежить ув man-сторінках відповідних libc враперів. Наприклад, bind(2) чекає на аргумента struct sockaddr *, опис якого ув man-сторінці ip(7) був скопійований з документації якоїсь 4.3BSD-Tahoe 1987 року і відповідає реальності нутрощів лайнуксного сьогодення з висоти дзвіниці Києво-Печерської лаври. (Потрібний додатковий паддінґ' unsigned char __pad[8].)

Якщо libc-врапери вертають -1 на помилку та виставляють errno, то сисколи вертають errno × -1. Ув справжніх libc errno є локальна для поточного треду. Для мікімаусного nolibc серверу писати свою версію позікс тредів це як збирати гелікоптер AK1-3 на подвір'ї поміж 2ма хрущовками, тому тут errno--глобальна змінна. Як зберігати інтерхфейса, до якого звикли всі, хто коли-небудь писав мережеві майкросервіси на C, то найпростіший враппер виглядає як

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
  int r = my_syscall(43, sockfd, (long)addr, (long)addrlen, 0, 0, 0);
  if (r < 0) {
    errno = -r;
    r = -1;
  }
  return r;
}

Сигнали

Ув класичних Unix-серверах, якщо вони писалися не для inetd, конкурентна модель є форкова модель, яка вимагає прислуховуватися до SIGCHLD і чекати доки child завершить роботу, інакше після кожного ріквесту буде залишатися зомбі.

Форкова модель була популярна, тому що дозволяла серверу не лякатися сіґфолта, якщо сервер мав бага на який натрапив клайент: зупинявся лише child-невдаха, всі інші продовжували працювати.

Для встановлення колбеку на окремий сигнал лайнакс має свій rt_sigaction сискола. Ув аргументах той має поінтера на struct sigaction з дуже підступним філдом

void (*sa_restorer)(void);

Ув man-сторінках про нього написано ось таке:

'This flag is used by C libraries [тобто, нами] to indicate that the sa_restorer field contains the address of a "signal trampoline".

'… the C library's sigaction(2) wrapper function informs the kernel of the location of the trampoline code by placing its address in the sa_restorer field of the sigaction structure.'

Шматок з мікімаусного врапера:

int sigaction(int sig, const struct sigaction *act, struct sigaction *oact) {
  // ...

  struct kernel_sigaction kact, koact;
  kact.k_sa_handler = act->sa_handler;
  memcpy(&kact.sa_mask, &act->sa_mask, sizeof(sigset_t));
  // ...
  kact.sa_restorer = &restore_rt; // THE MOST IMPORTANT STEP

  int r = my_syscall(13, sig,
                     (long)(act?&kact:NULL), (long)(oact?&koact:NULL),
                     _NSIG/8, 0, 0);
  // ...
}

Звідкіля береться адреса хфункції для kact.sa_restorer? Це є та сама "signal trampoline", про необхідність якої попереджає лайнаксна документація. Звичайно, можна її не виставляти, але тоді жоден з сигналів не буде пійманий і жоден з колбеків не буде виконаний.

Спочатку я наївно написав

void restore_rt() {
  my_syscall(15, 0, 0, 0, 0, 0, 0); // 15 це rt_sigreturn
  __builtin_trap();
}

сигнали перехоплювалися, колбеки виконувалися,

СЄЧЄНОВ

Цей гіпноз корисний дуже,
У хазяйстві вєрно служить,
Злакі мощно прорастають
І надої возрастають!

але після завершення останніх сервер гепався через сіґфолта. На жаль, тут був потрібен осемблер знову:

__asm__(
  ".globl restore_rt\n"
  "restore_rt:\n"
  "    movq $15, %rax\n"
  "    syscall\n"
  "    hlt\n"
);

Чому не працює С-версія я не знаю. Клод ЕйАй вважає вона "may modify registers or the stack layout."

snprintf та gettimeofday

Життя без snprintf--життя варварів.
-- Невідомий філософ.

Ув src кернелу є приклад мінімалістичної vfprintf на 101 рядок, але вона не підтримує floats, які має друкувати uptime. Замість додавання їх підтримки, ліпше пошукати справжню snprintf, яка не використовує malloc. Така є ув pkg Федори (із усіх можливих місць саме тут) і називається stb_sprintf-devel. Це самотній .h файла, який зкомпільовується ув 18168 байт. Жахливе марнування ресурсів, але нічого не вдіяти. Тій хфункції потрібні va_start з друзями, але gcc та clang мають __builtin_va_* ув комплекті:

#define va_start(v,l)   __builtin_va_start(v,l)

Після інкорпорації stb_sprintf життя заграло новими барвами.

Дізнатися час ув секундах з початку буту можна зі сисколом sysinfo, поточний час--зі gettimeofday. Останній провайдить навіть кількість хвилин на захід від Ґрінвічу! Така розкіш.

Фіналє

Розмір нестріпованого статичного байнарника: 21736 байт. (Я до сих пір не можу повірити як це можливо.)

$ make info
wc -lc *.[ch] | while read -r lines bytes file; do \
 elf=-;\
 echo $file | grep -q '\.c$' && elf=`stat -c%s _out/${file%.*}.o`;\
 echo $file $lines $bytes $elf;\
done | column -t -N\ ,LINES,BYTES,ELF
            LINES  BYTES  ELF
main.c      134    3657   5896
signal.c    76     1699   1704
signal.h    113    2649   -
snprintf.c  6      120    18168
snprintf.h  12     274    -
sockets.c   37     896    1728
sockets.h   45     1119   -
u.c         98     2152   4496
u.h         46     1353   -
uptime.c    67     2148   2128
uptime.h    6      79     -
total       640    16146  -

Якби не stb_sprintf, розмір був би < 10KB.

Але це є не найбільша цікавинка. Ось кількість пам'яті, яку їсть такий конкурентний tcp-сервер після відповіді на 100 одночасних ріквестів:

$ sudo ps_mem -p `pgrep -f uptime-tcp-nolibc`
 Private  +   Shared  =  RAM used       Program

 36.0 KiB +   4.5 KiB =  40.5 KiB       uptime-tcp-nolibc
---------------------------------
                         40.5 KiB

Дуже сміюся. Ув сучасному світі, якщо споживання серверу, який робить 0 складного, < 80 _мегабайт_, то це є видатний вчинок і безстрашний прояв волі, за який треба сертифікат хоноровий, медаль та грошовий бонус для походу ув підпільне казино з повіями.

henry_flower: A melancholy wolf (Default)
2024-12-16 03:43 pm

Reddit замість fortune(1)

Пан @vak нещодавно знайшов дотепну заміну fortune(1), де парсячи реддітовського джейсона окремого ком'юніті, можна собі ув терміналі друкувати ~свіжі жарти.

У коментарях там мументально переписали пáйфона на шелл, тому мінімальний скрипта, з доданими очікуваннями на деякі помилки, виглядає ось так:

lolreddit in xterm

Якщо реддіт вертає не зовсім того джейсона, який ми очікували, ми друкуємо нічого. w3m потрібен щоб перетворювати &amp; і друзів на відповідні символи.

Скрипта має 2 недоліки:

  1. джейсона завантажується кожного разу, а реддіт часто вертає 429;
  2. жарти беруться лише з 1го ком'юніті.

Завантажування можна закешити і перевіряти застарілість кешу на кожному запуску. jq вміє самотужки парсити окремо кожний файловий аргумента. Переписати скрипта ліпше за все як мейкфайла, тому шо лише дурень пише на баші те, шо може зробити Мейка (ніхто так не каже):

#!/usr/bin/make -sf

r := showerthoughts CrazyIdeas oneliners
update := 600

wits := /tmp/reddit-wisdom/all.txt
age := $(shell expr `date +%s` - `date -r $(wits) +%s 2>/dev/null || echo 0`)
$(shell [ $(age) -gt $(update) ] && rm -f /tmp/reddit-wisdom/*)

all: $(wits)
    sort -R < $< | head -1 | w3m -dump -T text/html

$(wits): $(patsubst %, /tmp/reddit-wisdom/%.json, $(r))
    jq -r '.data?.children[]?.data | "\"\(.title)\" -- \(.author)" | gsub("\\n"; " ")' $^ > $@

/tmp/reddit-wisdom/%.json:
    mkdir -p $(dir $@)
    echo Fetching r/$*
    curl -sf 'https://www.reddit.com/r/$*/top.json?sort=top&t=week&limit=100' > $@

.DELETE_ON_ERROR:

(Ув fbsd потрібно встановити gmake та відредагувати shebang.) cowsay з lolcat'ом я прибрав. Завантажений джейсона застаріває кожні 600 секунд, але IRL краще виставити 48 годин.

$ ./reddit-wisdom
Fetching r/showerthoughts
Fetching r/CrazyIdeas
Fetching r/oneliners
"We’re constantly playing a tug of war against companies by paying YouTube to
not show ads while they pay YT to show them. " -- SillySlothySlug
$ ./reddit-wisdom
"make a law where everybody gives money to me" -- Due_Intention_2473

$ wc -l < /tmp/reddit-wisdom/all.txt
186

Найскладніше тут знайти слушні ком'юніті, які мають цікаві тайтлс постів. showerthoughts інколи буває NSFW.

henry_flower: A melancholy wolf (Default)
2024-11-18 03:32 pm

/bin/sh errexit ув fbsd

FreeBSD is very compatible with standards such as POSIX.
-- FreeBSD Developer Handbook

На будь-якому лайнаксі з будь-яким sh-похідним шелом, скрипта нижче друкує hello world:

$ cat -n 1.sh
     1  set -e
     2
     3  hello() {
     4      false
     5      echo hello
     6  }
     7
     8  true | hello && echo world

$ sh 1.sh
hello
world

fbsd зі своїм /bin/sh робить потужний кєк на рядку 4:

$ sh 1.sh
$ echo $?
1
$ sh -x 1.sh
+ set -e
+ true
+ hello
+ false
$ uname -rs
FreeBSD 14.1-RELEASE-p5

Їхній sh(1) має ось таке речення ув роздлі про set -e:

"If a shell function is executed and its exit status is explicitly tested, all commands of the function are considered to be tested as well."

Я, звичайно, не є перший хто це випадково помітив: схожі 4 скарги ув їхній баґзилі date back to 1999-2005, всі обнадійливо позначені як FIXED.

Хто і навіщо fbsd використовує для роботи, окрім контори Соні та ображених на Торвальдса русскіх, залишається незрозумілим.

henry_flower: A melancholy wolf (Default)
2024-11-17 04:36 pm

llama3.2-vision ув ollama

Іграшка нова: маючи колекцію з NN тисяч хфото, локально описувати на лайнаксі що коїться на кожній світлині без допомоги Епла, Гоогла або распазнай-с-єйай.лох:

$ alias omglol='find -name \*txt | xargs -n50 grep -li'
$ omglol selfie
./10/IMG_20241019_204444.txt
./09/IMG_20240930_082108.txt
./09/IMG_20240930_082118.txt
./07/IMG_20240712_154559.txt
./07/IMG_20240712_154554.txt

кожний .txt файл лежить поряд з .jpg, який було відправлено до ollama.

Або для слайдшоу:

$ omglol selfie | sed s/txt/jpg/ | xargs feh

Опис можна пхати до exif UserComment, звичайно, але я не хотів модифікувати оригінали.

На жаль, з поточним ollama'овським cli прикріпляти світлини немає куди. Замість cli пропонують робити json пост-ріквести до ollama'овського ендпоінту, перед чим кожне зображення треба спочатку base64-конвертувати.

Другий недолік діфолтного cli--неможливість читати stdin. Я не розумію чому стандартний паттерна будь-якого текстового редактора 'селекнутий текста передати до stdin зовнішній ютіліти', є наразі бонусна фіча вчорашніх крипто-бро, сабскріпшона на яку вони залюбки продадуть простодушному користувачу.

Третій недолік--ігнорування ліміту вихідного тексту для деякого інпуту, для якого ніякі поради конструювання підказок не працюють, а num_predict ув api просто обрізає генерацію, що можна зробити без їх допомоги і так. Зі своїм враппером стає можливим підсумувати підсумки, на чому я і зупинився:

$ ./llm llama3.2 'summarize the following:' < VI.txt | ./llm llama3.2 'summarize the following in 40 words using the present tense:' | fmt -w50
Hernán Cortés and his men arrive on Cozumel
after rough seas delay them; they're rejoiced to
see Geronimo de Aguilar, a Christian prisoner
who's fluent in Yucatecan. Cortés sees great
potential for Aguilar as an interpreter and
prepares to proceed with his expedition.

Нещасний llm врапер--58-рядковий шелóвий скрипта (осьо є його svg), куди ув якості домашнього завдання можна додати async спінера.

Зі скриптом та невеликим мейкфайлом, процеса аналізу світлин виглядає як:

$ find . -iname \*.jpg | sed 's/\.[^.]*$/.txt/' | xargs -r -d\\n -n50 llm-image

де llm-image це:

#!/usr/bin/make -f
model := llama3.2-vision
define desc
@printf "%s\n\n" $(model) > $@
echo 'describe the image' | llm -i $< $(model) | tee -a $@
endef
%.txt: %.jpg; $(desc)
%.txt: %.JPG; $(desc)

Фасебооківська llama3.2-vision не є єдина модель, знатна описувати зображення (llava:13b, наприклад, вміє це також і працює ув 5 разів швидше), але вона здалася мені найбільш корисною ув виробництві keywords для пошуку.

henry_flower: A melancholy wolf (Default)
2024-10-04 11:39 am

Скріншоти XTerm ув SVG

Як нарід робить векторні скріншоти терміналу? Ось такі:

Я гадав є конвертори аутпуту зі script(1) ув SVG, але нічого такого не знайшов. Результат з asciinema нормально конвертується лише ув бітмапові зображення. Мотлох ansi-ту-шото на пáйфоні та расті або видає скуйовджені світлини або не розуміє ті ansi сіквенсес, що відповідають за рух курсору. Покинутим termtosvg можна генерувати купу svg, але він покладається на пáйфонівський in memory емулятора терміналу, який має купу багів.

Сучасний xterm вміє робити "SVG Screen Dump", але з наївним позіціонуванням літер. Наприклад, рядок '$ uname' він серіялізує як:

<g fill='rgb(60.00%, 60.00%, 60.00%)'>
 <text x='2' y='17'>$</text>
 <text x='22' y='17'>u</text>
 <text x='32' y='17'>n</text>
 <text x='42' y='17'>a</text>
 <text x='52' y='17'>m</text>
 <text x='62' y='17'>e</text>
</g>

Це означає, що кернінґа шрифта до уваги не береться і результат виглядає чудернацько.

Сучасний xterm також вміє робити "XHTML Screen Dump" (ув меню Ctrl + Миша-1), який гендериться ув бовзері майже ідеально (з замалим line-height). Додати необхідного стилю до .xhtml файлу ж нескладно, але як конвертнути той xhtml ув svg?

Пассо нумеро уно

Бовзери вміють друкувати ув pdf. Кроум має headless режима, а гооглова бібліотека puppeteer (з дебільним інтерфейсом), має Page#pdf() метода. Нам лише потрібно вибрати розмір в'юпорту та опцію 'друкувати background graphics'.

$ cat html2pdf
#!/usr/bin/env node

import util from 'util'
import fs from 'fs'
import puppeteer from 'puppeteer'

async function pdf(url, output, opt) {
    let bowser = await puppeteer.launch()
    let page = await bowser.newPage()

    let print_settings = Object.assign({
        path: output,
        printBackground: true,
        width: 1920,
        height: 1080,
    }, opt)

    await page.emulateMediaType('screen')
    await page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36')
    try {
        await page.goto(urlise(url), { waitUntil: 'networkidle0' })
        await page.pdf(print_settings)
    } catch (e) {
        err('Error:', e.message)
    }

    await bowser.close()
}

function urlise(s) {
    return (/^https?:/.test(s)) ? s : 'file://' + fs.realpathSync(s)
}

function err(...msg) { console.error(...msg); process.exit(1) }

let args = util.parseArgs({
    options: {
        width: { type: 'string', short: 'w' },
        height: { type: 'string', short: 'h' },
    },
    allowPositionals: true
})

if ( !(args.positionals[0] && args.positionals[1]))
    err('Usage: html2pdf URL output.pdf')

pdf(args.positionals[0], args.positionals[1], args.values)

Скрипта підтримує -w та -h опції і конвертує будь-яку уйоб-сторінку, а не тільки локальні файли. Якщо не виставити user-agent, по-замовчуванню буде HeadlessChrome, на що деякі сайти неприємно реагують.

Пассо нумеро дуе

PDF, на жаль, не буде мати розмір конь тенту, а буде містити borders (як це українською? межа?) Прибрати їх простіше всього Inkscape'ом:

$ inkscape --actions "select-all;fit-canvas-to-selection" 1.pdf -o 2.pdf

Пассо нумеро тре

$ pdf2svg 2.pdf 1.svg

Воно автоматом перетворює текст ув path. Зменшити розмір .svg можна svgcleaner'ом.

На кроці нумеро дуе можна було би зразу конвертнути рельтат ув svg, але він тоді виходить гаргантюажного розміру.

Енівей, фінальний мейкфайла:

$ cat xterm-xhtml-to-svg
#!/usr/bin/make -f

font := monospace

$(if $(and $(i),$(o)),,$(error Usage: xterm-xhtml-to-svg i=1.xhtml o=1.svg))
__dir__ := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))

$(o): $(i).dirty.svg; svgcleaner --quiet $< $@

%.nokogiri.xhtml: $(i)
    ruby -rnokogiri -e 'd = Nokogiri.XML(STDIN.read); d.css("#vt100 > pre").first["style"] = "font-family: '$(call se,$(font))'; line-height: 1.2"; puts d.to_xml' < $< > $@

%.uncropped.pdf: %.nokogiri.xhtml; $(__dir__)/html2pdf $< $@

%.pdf: %.uncropped.pdf
    inkscape --actions "select-all;fit-canvas-to-selection" $< -o $@

%.dirty.svg: %.pdf; pdf2svg $< $@

se = '$(subst ','\'',$1)'
.INTERMEDIATE: $(i).dirty.svg
henry_flower: A melancholy wolf (Default)
2024-09-03 01:50 pm

Emoji та юнікод ув динамічному меню

Коли рік тому до імаксу додали інтерактивну хфункцію emoji-insert, я подумав що hell froze over. Окремий emoji інпут, звичайно, мають всі ОС багато років. Ув лайнаксі це залежить від середовища стільниці, і якщо ними (середовищами стільниці) не користуватися, то невблаганну залізну хватку прогресу можна не помітити.

Нещодавно на хакір н'юз хтось презентував свого менхетнського проекту: emoji-клавіятуру написану на голому XCB. На що хтось ув коментарях зазначив що то є вражаюче досягнення, але маючи список пар

emoji1    опис1
emoji2    опис2
…

то emoji вставляються за допомогою будь-якого динамічного меню без спеціяльної клавіятури.

Раніше таким меню була аплікація dmenu (яку я колись ламав для інкрементного аналога alt-tab для FVWM), але зараз гіпадріли користуються rofi, який є ув пекеджах будь-якого дістро:

$ ls /etc | rofi -dmenu -i

Одного rofi буде замало, треба механізма вставки тексту, який друкує rofi на stdout, до активного вікна. Ув xorg це вміє робити xdotool:

$ ls /etc | rofi -dmenu -i | xdotool type --file -

Де взяти список пар? Федора має unicode-emoji пекеджа (unicode-data ув Деб'яні) з файлом emoji-test.txt:

Зліва від середника там список codepoints. Наприклад, щоб отримати чорного кота 🐈‍⬛, треба взяти звичайного кота (0x1F408, 🐈), додати ZERO WIDTH JOINER (0x200D) та чорного квадрату (0x2B1B, ⬛).

На жаль, %b та \Uxxxx ув printf є непортабельні:

$ e="printf '%b\n' '\U1F408\U200D\U2B1B'"
$ bash -c "$e"
🐈‍⬛
$ bash -c /bin/"$e"
/bin/printf: missing hexadecimal number in escape
$ dash -c "$e"
\U1F408\U200D\U2B1B
$ busybox sh -c "$e"
\U1F408\U200D\U2B1B
$ zsh -c "$e"
🐈‍⬛
$ ksh -c "$e"
\U1F408\U200D\U2B1B

Але якщо перевести всі hex до int та роздрукувати кожен байт з int ув octal, це буде працювати навіть ув busybox:

$ echo $'\360\237\220\210\342\200\215\342\254\233'
🐈‍⬛

Файл emoji-test.txt містить 3773 придатних для використання emojis. Щоб не генерувати статичні пари ув якомусь my-dull-list.txt, які будуть задавнюватися при кожному оновленні пекеджу, краще написати швидкого шелóвого фільтра.

Look, ma--no execve() after fork()s:

$ cat codepoint.sh
codepoint_to_oct() {
    local dec="`printf %d 0x${1:-0}`"
    local b1 b2 b3 b4

    if [ "$dec" -lt $((0x80)) ]; then    # 1-byte
        b1=$dec
    elif [ "$dec" -lt $((0x800)) ]; then # 2-byte
        b1=$((0xC0 | (dec >> 6)))
        b2=$((0x80 | (dec & 0x3F)))
    elif [ "$dec" -lt $((0x10000)) ]; then
        b1=$((0xE0 | (dec >> 12)))
        b2=$((0x80 | ((dec >> 6) & 0x3F)))
        b3=$((0x80 | (dec & 0x3F)))
    elif [ "$dec" -lt $((0x200000)) ]; then
        b1=$((0xF0 | (dec >> 18)))
        b2=$((0x80 | ((dec >> 12) & 0x3F)))
        b3=$((0x80 | ((dec >> 6) & 0x3F)))
        b4=$((0x80 | (dec & 0x3F)))
    else
        return 1                # out of range
    fi

    printf \\%03o "$b1" $b2 $b3 $b4
}

emoji_print() { printf %b "`for i; do codepoint_to_oct "$i"; done`"; }

Кросиві var assignments ув codepoint_to_oct() написав chatgpt, після чого Гостре Око помітив що генерувати взагалі потрібно нічого, тому що emoji-test.txt має для кожного codepint готовий emoji.

$ cat emoji-list.sh
: "${DB:=/usr/share/unicode/emoji/emoji-test.txt}"

grep '; fully-qualified ' "$DB" \
    | grep -v 'skin tone' \
    | sed -E 's/.+# ([^ ]+) E[^ ]+ (.+)/\1\t\2/'

Як відфільтровувати безцінні варіянти з кольором шкіри, список виходить дещо коротший:

$ ./emoji-list.sh | wc -l
1898
$ ./emoji-list.sh | tail -3
🏴󠁧󠁢󠁥󠁮󠁧󠁿    flag: England
🏴󠁧󠁢󠁳󠁣󠁴󠁿    flag: Scotland
🏴󠁧󠁢󠁷󠁬󠁳󠁿    flag: Wales

Щоб не викидати codepoint.sh, над яким тяжко пітнів чатбот, можна зробити додаткового хфінта: друкувати список не лише emojis, а велику частку utf8:

$ ./unidata-list.sh | grep TELEPHONE
℡    TELEPHONE SIGN
⌕    TELEPHONE RECORDER
☎    BLACK TELEPHONE
☏    WHITE TELEPHONE
✆    TELEPHONE LOCATION SIGN
📞    TELEPHONE RECEIVER
🕻    LEFT HAND TELEPHONE RECEIVER
🕼    TELEPHONE RECEIVER WITH PAGE
🕽    RIGHT HAND TELEPHONE RECEIVER
🕾    WHITE TOUCHTONE TELEPHONE
🕿    BLACK TOUCHTONE TELEPHONE
🖀    TELEPHONE ON TOP OF MODEM

Довжина списку таких пар є 34,856. Джерело є текстовий хфайл UnicodeData.txt з unicode-ucd пекеджу ув Федорі (unicode-data ув Деб'яні):

$ grep TELEPHONE /usr/share/unicode/ucd/UnicodeData.txt | head -3
2121;TELEPHONE SIGN;So;0;ON;<compat> 0054 0045 004C;;;;N;T E L SYMBOL;;;;
2315;TELEPHONE RECORDER;So;0;ON;;;;;N;;;;;
260E;BLACK TELEPHONE;So;0;ON;;;;;N;;;;;

Якщо генерувати списка лінійно, це буде займати кількадесят секунд. Хоча rofi вміє не чекати, а показувати рядки як вони з'являються на його stdin, процес ліпше пришвидшити:

$ cat unidata-list.sh
__dir__=$(dirname "$(readlink -f "$0")")

: "${DB:=/usr/share/unicode/ucd/UnicodeData.txt}"

sed 1,33d "$DB" | while IFS=\; read -r codepoint desc _ _ _ _ _ _ _ _ alt _; do
    [ -n "$alt" ] && [ '<' = "`printf %c "$desc"`" ] && desc=$alt
    printf '%s %s\n' "$codepoint" "$desc"
done | grep -Eav '^.+\s<' \
     | xargs -r -d \\n -n1 -P "`nproc`" "$__dir__/codepoint.sh"

до чатботівского codepoint.sh додати:

…
set -e
codepoint=${1%% *}; [ "$codepoint" ]
char=`emoji_print "$codepoint"`; [ "$char" ]
printf '%s\t%s\n' "$char" "${1#[0-9A-F]* }"

Якщо навіть з такою кенкаренсі все одно доводиться чекати, споглядаючи цифри прогресу ув rofi, рекомендую користуватися нормальними комп'ютерами, а не raspberry pi.

henry_flower: A melancholy wolf (Default)
2024-08-29 11:36 pm

Файерфокса лайнаксного періоду

Намагаючись приготуватися до неминучого колапсу Кроума, згадав шо є бовзер Файрфокс, фундацьоне якого минулого року збирало усім світом на зарплату своєї CEO.

Чому колапсу? Кроум переходить на "новий" API для ікстеншонів (моніфест 3.0), депрікуючи "старий". За винятком Ublock Origin, всі ікстеншони у мене є самописні (99% того що має гооглівський могозин є malware; я не перебільшую). Ublock Origin працювати з "новим" API не збирається, а я маю 0 стимулу та зиску ув портуванні самописного. 1 з ікстеншонів використовує той самий API що і Ublock Origin, але для додавання/редагування http headers.

З цим ікстеншоном були 2 смішні гішторії. Його статистика плаває від 30 користувачів до раптом 900, 1200, 1900 і знову вниз до 30, а т.я. ніякої телеметрії там немає, чому їм починають користуватися на короткий час ув Азії, я не знаю. Якось після такого бурсту активності мені прийшов листа від якогось кетайця, який чемно запропонував продати йому ікстеншона за €600 (я не відповів).

Іншого разу до мене приколупася гамериканська коб'єта, у якої не працювало замовлення з якогось локального склепу, тому що її бовзер видаляв referer і вона якимось незбагненним чином знайшла того ікстеншона та вкрай наполегливо у мене розпитувала як її referer повернути взад. Я чесно намагався їй допомогти, але ув кінці здався і порекомендував встановити Файрфокса, на що вона відповіла "Foxfire [sic] works, thank you!", а я перехрестився.

Чому колапсу Кроума? Я, звичайно, трохи перебільшую, але маю сумніви, що після стількох років з інтервебом без реклами, ореал ойті та їх сімей добровільно погодиться на, та буде сумлінно спостерігати, наприклад, рекламу ув ютубі. Скільки це додасть мульйонів користувачів ФФ це є цікаве питання, але я очікую невеличкого ренесансу.

Як справи ув ФФ на лайнаксі?

Для свого кроуму він використовує gtk3, який фундацьйоне Ґноума та контора Червоний Капелюх вважає депрікейтед. (Для тих хто живе під скелею та є не ув уйоб-дівелопмент дискурсі: кроум--специфічний крос-бовзерський термін, який означає будь-які елементи gui, які не стосуються DOM'у уйоб-сторінки. Наприклад, меню з букмарками або url-бар. Відповідно, кроумлес є вікно лише з уйоб-сторінкою. Фулскрін режим F11 є прикладом кроумлес.)

Gtk3 означає 0 покращень для кроуму, т.я. дівелопмент тулкіту багато років переїхав на gtk4 та майбутнього gtk5. Старий gtk3 бажають викинути геть, але наразі через аплікації типу ФФ це зробити не вдається, хоча люди намагаються.

Наприклад, одного дня ФФ перестав показувати pointer курсор (використовується при наведенні на лінка). Раніше мені було все одно--я пускав ФФ лише для перевірки рендеренґу. Виявляється це сталосі тому, що ув діфолтний gtk3 темі загубили той курсор для xorg. Хтось накричав на Ґноум, але їм було по-барабану, тому ФФ спеціяльно додав widget.gtk.legacy-cursors.enabled конфіґураційного параметра, який по-замовчуванню є false--доля лайнаксоїдів не ув wayland є страждати.

Але найгірша ситуація є зі шрифтами. Gtk3 підтримує fontconfig і ФФ'шний кроум рендерить заміни згідно користувацьких ~/.config/fontconfig/fonts.conf та налаштувань ув ~/.config/gtk-3.0/, як того юзер очікує. Механізма рендеренґу шрифтів ув бовзерному двигуні ФФ технічно питає fontconfig також, але за своїми правилами, тому на практиці результати відрізняються від Гоогл Кроуму та Майкрософтського Edge.

За часів w2k/winxp на fbsd/лайнакс копіювали віндюкові шрифти Arial, Times New Roman, Courier New, &c (така колекція називалася core fonts), і МС деякий проміжок часу дозволяла їх завантажувати легально. Копіювали всі: вільними лайнаксними шрифтами можна було катувати.

Деб'ян має core fonts ув своїх пекеджах (Федора--ні), але ті шрифти застигли ув версіях чи то w98 чи то w2k і відповідних версіях юнікоду. За цей час з'явилося вдосталь metric-compatible families і технічно навіть оновлена колекція core fonts стала непотрібна.

З /usr/share/fontconfig/conf.avail/30-metric-aliases.conf:

Microsoft Liberation Google CrOS core StarOffice
Arial Liberation Sans Arimo Albany
Arial Narrow Liberation Sans Narrow
Times New Roman Liberation Serif Tinos Thorndale
Courier New Liberation Mono Cousine Cumberland
Cambria Caladea
Calibri Carlito
Symbol SymbolNeu

Але я звик до core fonts. Не оригінальних, а свіжих, які я регулярно оновлював коли оновлювався віндюк. Ув fonts.conf достатньо було мати на кшталт

<alias>
  <family>sans-serif</family>
  <prefer><family>Arial</family></prefer>
</alias>

і гоогловський Кроум рендерував sans-serif Arial'ом замість лайнаксового Liberation Sans, Nimbus Sans чи DejaVu Sans.

Звісно з ФФ це не працює (навіщо). Той наполегливо ставить 1й ліпший sans-serif шрифта, який fontconfig знаходить ув /etc/fonts/conf.d/. Якщо того шрифта дісейблнути ув fonts.conf, ФФ бере наступного. На їхній баґзилі відкритий баґ висить 10й рік.

Подолати це можна тільки створивши ув /etc/fonts/conf.d/ хфайла з ім'ям, яке буде сортуватися (in numeric order) до хфайлів з "family substitution", наприклад, 12-msfonts.conf:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fontconfig SYSTEM "../fonts.dtd">
<fontconfig>

  <alias>
    <family>sans-serif</family>
    <prefer><family>Arial</family></prefer>
  </alias>

  <alias>
    <family>Arial</family>
    <default><family>sans-serif</family></default>
  </alias>

…
</fontconfig>

Це було 1не з 1ших речей, що було, з 1го погляду, безнадійно зломано на ФФ у порівнянні зі Кроумом. Як то буде далі, прогнозувати не хочу, можливо є крихітний шанс що гоогл передумає з моніфестом 3.0 і переїжджати на ФФ не доведеться (шансів немає).

henry_flower: A melancholy wolf (Default)
2024-08-04 05:53 pm

There were families present

Сміявсь

ОйБіЕм:

'In 1964, for example, one of our branch managers staged a burlesque show at a sales conference in the Midwest.

The skit was too vulgar to believe--about an Indian village, starring the manager himself as the chief and some scantily clad models as squaws. They even had live chickens running around on the stage. At the end of the skit the manager disappeared with one of these models into a tepee, and as he turned to go into the tent, the audience saw a sign on his back that said something like "Branch Manager: I do all things for all people." Then he and the girl pulled the tepee flap closed behind them.

There were families present, and somebody who witnessed the show wrote me a letter saying, "Is this what you call IBM dignity?" So I started a big inquiry.'

Father, Son & Co.: My Life at IBM and Beyond Томаса Ватсона молодшого.)

henry_flower: A melancholy wolf (Default)
2024-08-03 01:20 pm

Найпримітивніший link checker

Якщо набрати link checker ув гооглі, то виявиться що перевірка посилань є серйозний бізнес. Запускання скрипта, який пишуть на 1му курсі, коштує від $13 до $159 на місяць. За $13 дозволяється це робити кожні 2 тижні, а за $159 с барскоґо плєча до 1 разу на день.

Для тих хто готовий нарешті "стати професіоналом", контора зовсім іншого гатунку пропонує вдосконалені та ентерпрайзні рішення за $449 та $1249 на місяць відповідно. Тоді відкриваються потужні можливості відправляння csv-звіту до "колеги чи фрілансера".

Є статичний уйоб-сайта з 375ма сторінками, який має 241 ікстернальний лінка. Зазвичай подібні сайти перевіряють локально на рівні .md хфайлів, з яких він генерується, і ютіліти, які це роблять, працюють доволі швидко, т.я. їм не треба повзати по http зі сторінки до сторінки, створюючи графа. Про них ми говорити не будемо.

Щоб перевірити задіплойеного сайта, а не директорію з .md-хфайлами, додається прублема рекурсивного знаходження посилань та питання коли зупинятися. Спочатку, я вирішив пошукати щось готове ув пекеджах Федори: якийсь пáйфонівський скрипта linkchecker, який за майже 4 хв на локалхості і рівнем рекурсії 20, знайшов 60 лінків загалом, замість 241.

Потім я подивився на W3C'шний link-checker (категорія програм, що є сповнена різноманітністю імен). Окрім уйоб інтерхфейсу він має CLI компонента--перл-скрипта, який витратив 13 хв для народження лоґу з 27859 рядками, де посеред машино-нечитабельного мотлоху пропонується шукати зламані посилання. Можливо, як за таке чарджити клаентів по $1249, то це має сенс; найняти когось буде нескладно.

Ґітхаб є повний "fast" та "async" чекерів, найпопулярнійший з яких (1.9K зірочок) не підтримує рекурсії, але друкує затишні 🔍, ✅, 🚫, 💤 емоджі ув консолі та рожеву progress bar. Рисою професіоналізму посеред сучасних лікн-чекерів є порада використовувати їх з докер-контейнеру, тому що для відправляння http ріквестів наш час вимагає >= 216 бібліотек на будь-якій мові пограмування.

Чи складно написати свій, наприклад, на bash? Хоча він буде мати міцний вайб нульових, коли завантажена сторінка якимось wget майже відповідала сторінці яку рендерив бовзер, для перевірки сторінок, наприклад, документації, такий чекер все одно буде залишатися корисним. Назвемо його badlinks:

$ wc -l lib.bash url* badlinks
  21 lib.bash
  28 urlcheck
  14 urlextract.rb
  54 urlextract-recursive
  24 badlinks
 141 total

На чистому bash це зробити майже неможливо: переписування nokogiri на шелі я залишаю комусь іншому, а ліпшого парсера кострубатого html, аніж nokogiri, не існує ув природі.

badlinks складається з 3 компонентів:

  1. парсера html, що друкує всі посилання з нього (не рекурсивно);
  2. рекурсивного crawler'а, який знаходить посилання з конь тент тайпом text/html, та занурюється до них; на кожному етапі він друкує знайдені leafs;
  3. скрипта, який читає список посилань від п.2 та перевіряє їх валідність.

Парсінґ html

$ curl -sL google.com | ./urlextract.rb q:
https://www.google.com/imghp?hl=uk&tab=wi
http://maps.google.com.ua/maps?hl=uk&tab=wl
…
q:/intl/uk/about.html

q: тут означає будь-яку base, відносно якої друкуються релатівні посилання.

$ cat urlextract.rb
#!/usr/bin/env -S ruby -rnokogiri -raddressable/uri

include Addressable

def eh = abort "Usage: #{$0} base_url < html"
base = URI.parse $*[0] rescue eh
eh unless base&.scheme

Nokogiri::HTML(STDIN.read).css('links,a,img,iframe,script,video,audio').each {|n|
  u = URI.parse(n['src'] || n['href']) rescue next
  next if !u || (!u.scheme && u.path.strip == '')
  u.fragment = nil
  puts u.scheme ? u : base.join(u)
}

Щоб воно працювало, треба сказати gem install nokogiri addressable. Вбудований ув stdlib парсер url є не такий гнучкий як addressable.

Фрагмент (частина рядка після #) видаляється для простоти, хоча, звичайно, можна було би намагатися шукати його (фрагмент) потім ув html.

Філософське питання, яке може виникнути: чи має ютіліта друкувати унікальні лінки лише (тому що уйоб-сторінка може містити кілька геть однакових), чи залишати процес фільтрування downstream, так би мовити, процесам.

Crawler

… є найбільш неприємним компонентом. Шелóві мовні конструкції допомагають погано, і найпримітивніший пошук ув глибину з пародією на детектора циклів, це є все що вдалося втиснути ув 54 рядка коду.

./urlextract-recursive http://127.0.0.1/
0 Scanning http://127.0.0.1/ .
1 Scanning http://127.0.0.1/foo.html http://127.0.0.1/
2 Scanning http://127.0.0.1/bar.png http://127.0.0.1/foo.html
2 External http://example.com http://127.0.0.1/foo.html
1 Leaf http://127.0.0.1/baz.jpg http://127.0.0.1/
…

Алгоритма є вкрай простий:

  1. З отриманого стартового url автоматично дізнаємося про origin. Це можна зробити башом як це було продемонстровано ув пості HTTP get на баші за допомогою страхітливого regexp'у. Origin потрібен для розрізняння посилань на наш уйоб-сайт від посилань на ікстернальні ресурси.
  1. Для кожного лінка зі стартового url робимо HTTP HEAD ріквест. Якщо останній є text/html, занурюємося в, якщо ні--друкуємо його як leaf.

    Формат друку:

     level type url parent

    Щоб не сканувати лінка декілька разів, записуємо його ув хфайл history (1 лінк на рядок для макс швидкості grep'у). Рівень занурення регулюється користувачем і по замовчуванню == 20.

    parent потрібен лише для ютіліти ув наступному розділі.

$ cat urlextract-recursive
#!/usr/bin/env bash

set -e -o pipefail
__dir__=$(dirname "$(readlink -f "$0")")
. "$__dir__/lib.bash"

jn() { jobs -l | wc -l; }

scan() {
    local url="$1" level="$2" parent="$3"

    [ "$level" -ge "$LEVEL" ] && return 0
    echo "$level Scanning $url $parent"

    level=$((level+1))
    for u in `fetch "$url" | "$__dir__/urlextract.rb" "$url" | sort -u`; do
        search "$u" "$history" && continue
        echo "$u" >> "$history"

        if echo "$u" | grep -F -- "${URL[host]}" >/dev/null 2>&1; then
            if is_html "$u"; then
                local async=
                [ 1 == $level ] && [ "$JOBS" -gt 1 ] \
                    && [ `jn` -lt "$JOBS" ] && async=1
                if [ $async ] ; then
                    scan "$u" $level "$url" &
                else
                    scan "$u" $level "$url"
                fi
            else
                echo "$level Leaf $u $url"
            fi
        else
            echo "$level External $u" "$url"
        fi
    done
}

type curl > /dev/null
: "${LEVEL:=20}"
: "${JOBS:=`nproc`}"
url=${1:?Usage [LEVEL=$LEVEL] $0 url}

url_parse "$url"
history=${HISTORY:-`mktemp /tmp/urlextract-recursive.XXXXXX`}
trap 'rm -f $history; exit 130' 1 2 15
trap 'rm -f $history' 0

if is_html "$url"; then
    scan "$url" 0 .
    wait
else
    echo "0 Leaf $url ."
fi

(Вибачте за простирадло.)

Скрипта намагається робити конкурентні ріквести. Найнеприємніше тут є неможливість знати заздалегідь скільки посилань буде далі і складність створення фіксованої кількості worker'ів, перевірка яких була би можлива ув child процесах. Тому "паралелізм" використовується лише на рівні 1. Це означає якщо стартовий url мав рівно 1 text/html посилання, поралелізму не буде.

Якщо не робити ліміта на конкурентні ріквести, вбити мошину можна дуже легко. Я так тестував безлімітний варіянт з 6K gnu libstdc++ doxygen сторінками на локалхості: стався еквівалента форк бімби, лайнакс грохнув ікси та почав люто плюватися сторінками вбитих процесів.

Енівей, отримані рядки від urlextract-recursive легко фільтруються. Наприклад, щоб дізнатися який був макс рівень рекурсії і чи не треба його збільшити:

$ ./urllextract-recursive http://127.0.0.1/ | tee 1
$ awk '{print $1}' 1 | sort -un | tail -1
11

або показати лише ікстернальні посилання:

$ awk '$2 == "External"' 1

Нецікаві шматки з lib.bash:

fetch() { curl -sfL --connect-timeout 3 -m 3 -A 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36' -H 'accept-language: en-US,en;q=0.9' -H 'upgrade-insecure-requests: 1' "$@"; }

is_html() { fetch -I "$1" | grep -Ei '^content-type:\s+text/html' > /dev/null; }

search() { grep -Fx -- "$1" "$2" > /dev/null; }

Перевірка посилань

На відміну від попередньої ютіліти, ця чудово і елементарно паралелізується. Сам скрипта про це гадки не має, тому що оперує лише з 1 url, але конкурентна перевірка робиться ув фінальному врапері наступного розділу через xargs.

$ cat urlcheck
#!/usr/bin/env bash

set -e -o pipefail
__dir__=$(dirname "$(readlink -f "$0")")
. "$__dir__/lib.bash"

check_link() {
    local ec=0
    fetch "$1" | head -c1 > /dev/null || {
        ec=$?
        [ $ec == 23 ] && return 0 # pipe was closed by 'head'
        [ $ec -lt 100 ] && echo "Bad $1 in $2"
        return 101
    }
}

type curl > /dev/null
url=${1:?Usage: IGNORE=domains.txt $0 url parent}
parent=${2:-.}

url_parse "$url" || { echo "Invalid $url in $parent"; exit 100; }

if [ "$IGNORE" ] && search "${URL[host]}" "$IGNORE" ; then
    echo "Ignoring $url in $parent"
else
    [ 2 == $? ] && exit 100
    check_link "$url" "$parent"
fi

Єдина оптимізація гідна уваги це є check_link(), який читає лише 1 байт успішного ріспонзу і перевіряє статусного кода curl.

Деякі уйоб-сайти ітервебу (наприклад wsj, або припизджена квора) вважають curl ботом, ув незалежності від user agent, тому якщо вказати ув IGNORE env var шлях до хфайлу зі списком доменів, посилання з ними перевірятися не будуть.

Врапер

Фінальна обгортка:

$ cat badlinks
#!/usr/bin/env bash

set -e -o pipefail
__dir__=$(dirname "$(readlink -f "$0")")

usage="Usage: $0 [-l max level] [-j max jobs] [-e] [-i domains.txt] url"
link_type=Leaf
while getopts i:l:j:e opt; do
    case $opt in
        i) opt_i=$OPTARG ;;
        l) opt_l=$OPTARG ;;
        j) opt_j=$OPTARG ;;
        e) link_type="$link_type|External" ;;
        *) echo "$usage" 1>&2; exit 1
    esac
done
shift $((OPTIND-1))

: "${1:?$usage}"
export LEVEL=${opt_l:-20} JOBS=${opt_j:-`nproc`} IGNORE=${opt_i}

"$__dir__/urlextract-recursive" "$1" | \
    awk '$2 ~ /'"$link_type"'/ {print $3, $4}' | \
    xargs -r -n2 -P "$JOBS" "$__dir__/urlcheck"

Зі статичним уйоб-сайтом, на який попередньо знайдені готові пекеджи витрачали від 4 до 13 хв, набір скриптів шелóвих впорався за 16 секунд, відсканувавши 769 лінка.

henry_flower: A melancholy wolf (Default)
2024-07-19 04:08 am

Розум, тіло та духовне здоров'я

Ґноум втратив директора:

"Holly Million was named the GNOME Foundation Executive Director in late October. Part of her focus was going to be on helping the GNOME Foundation attain more funding. But coming today as a surprise, she will be departing her role with GNOME effective the end of July. She is stepping down on the basis of pursuing a PhD in psychology and dedicating herself to her own private practice."

Намагання знайти фінансування для Ґімпу викликає такий рівень психоемоційного стресу, який лікується лише отриманням PhD ув психології.

До погодження стати директоркою Ґноума, вона працювала "професійним шаманом, артистом, виробником трав'яних ліків та займалася мікро-гомстедингом". Останнє є не сексуальне збочення, а тип сільського господарства ув Гамериці.

"I do energy healing work on behalf of individuals, land, houses, and businesses.", розказувала про себе пані Мілліон, "I can do this work remotely because everything is connected. We live in a quantum universe."

Гомстединг був дуже ув нагоді: вирощування лікарських трав допомагало "with my shamanic work". Якщо у вас врожай з'їдали комахи, пані Мілліон проводила цілющі церемонії та ритуали по телефону: $250 за годину енергетичної роботи 1:1 з вирівнюванням чакри, або $500 за повне очищення енергії у вашому домі чи охфісі.

Наразі рада директорів Фундацьйоне Ґноума, запаливши пахощі та свічки, шукає нового директора.

henry_flower: A melancholy wolf (Default)
2024-07-10 04:50 pm

Візуалайзація curl

Як хутко довести дівопсу або хмарному провайдеру що завантаження з серверу є повільне? Можна заміряти час загальний, чи копіювати все що curl написав на stderr, або вислати кєно зроблене на телефоні (вертикальне, тремтячою рукою, під кутом > 10°, на відстані > 1м від монітора).

Не пам'ятаю ув якій версії віндюка його іксплорер почав малювати графіка під час копіювання хфайлів (Vista?), але він (віндюк) так і не почав цього робити ані ув класичних своїх бовзерах минулого, ані ув своєму поточному безглуздому різновиді кроума для блінка.

Найнахабнішим повідомленням дівопсу був би svg:

та/чи рядки

00:00:01 1000k
00:00:02 2000k
00:00:03 2500k

ув якості доказу.

2й варяінт є найпростіший; curl, наприклад, друкує

fprintf(tool_stderr,
        "\r"
        "%-3s " /* percent downloaded */
        "%-3s " /* percent uploaded */
        "%s " /* Dled */
        "%s " /* Uled */
        "%5" CURL_FORMAT_CURL_OFF_T " " /* Xfers */
        "%5" CURL_FORMAT_CURL_OFF_T " " /* Live */
        " %s "  /* Total time */
        "%s "  /* Current time */
        "%s "  /* Time left */
        "%s "  /* Speed */
        "%5s" /* final newline */,
        …

кожну секунду, тому лише замінивши \r на \n можна відправляти повного логу:

$ curl http://example.com/1.zip -o 1.zip 2>&1 | tr \\r \\n

та вимагати сатисфакції.

Щоб намалювати графіка з gnuplot, нам потрібні Current time та Speed. Останнє доведеться конвертувати з 1234k чи 4567M ув байти. Спочатку я думав малоелегантно замінювати суфікси:

awk 'function expr(s) {
  h["k"]=2**10; h["M"]=2**20; h["G"]=2**30; h["T"]=2**40; h["P"]=2**50
  for (k in h) sub(k, "*"h[k], s); return s
}
/[0-9.][kMGTP]?$/ { print expr($NF) | "bc"; close("bc") }'

але, виявляється, саме для цього coreutils має ютіліту numfmt(1), про яку я ніколи не чув (додали недавно, лише 12 років тому, не встигаєш слідкувати за всіма ціма новими штуками):

$ echo lol 10M | numfmt --from iec --field 2
lol 10485760

Так, звичайно, зараз не роблять, а кожного разу ретельно пишуть нудні, багатослівні простирадла на пáйфоні. Те що має фітнутися ув 5 рядків шелових макс + 6 рядків скрипта gnuplot'у, потрібно ретельно розмазувати на 70+, ніби працюєш на ойбіем ув році 1984 і тобі платять за кількість рядків коду.

Повна версія іграшкового візуалайзатора пускається як

$ ./curlbench http://example.com/1.zip | gnuplot --persist

де gnuplot дозволяє зберегти графіка ув потрібному форматі.

$ cat curlbench
#!/usr/bin/env -S stdbuf -o0 bash

set -e -o pipefail
numfmt=`type -p gnumfmt numfmt;:`; test "${numfmt:?}"

cat <<E
set xdata time
set timefmt "%H:%M:%S"
set xlabel "Time"
set format y "%.0s%cB"
set grid
plot "-" using 1:2 with lines title ""
E

curl "$@" -fL -o /dev/null 2>&1 | tr \\r \\n | awk '
/[0-9.][kMGTP]?$/ {
  time = index($10, ":") == 0 ? $11 : $10
  if (time != "--:--:--") print time, $NF
}' | tr k K | $numfmt --from iec --field 2

Має працювати також на fbsd та маку (як були встановлені coreutils), але мені є лінь перевіряти.