Использование Github Actions и Bash для создания динамического профиля Github

Использование Github Actions и Bash для создания динамического профиля Github

4 июня 2022 г.

Около года назад Github запустил функцию, которая позволяет добавлять README в профиль пользователя. Чтобы добавить README в свой профиль, вам необходимо:


  • создайте общедоступный репозиторий с именем, совпадающим с вашим именем пользователя Github.

  • поместите README.md в корень репозитория

Вы можете узнать больше об этом в документации Github.


Что такое динамический профиль Github?


Динамический профиль Github обновляется автоматически по какому-либо внешнему событию или по расписанию. Это возможно с использованием Github Actions. Github Actions — еще одна недавно выпущенная функция Github. Github Actions — это, по сути, система CI/CD, которая позволяет создавать и запускать настраиваемые рабочие процессы.


Впервые я узнал о профиле README в этой статье на Hackernoon. Парень использовал PHP для получения и обновления списка последних сообщений в своем блоге. Хотя я эксперт по PHP, я хотел сделать его более сложным. Я понял, что синтаксический анализ XML и замена текста в файле достижимы только с помощью собственных инструментов Bash.


Парсинг RSS-канала


RSS-канал — это обычный XML-файл с простой схемой. Вот пример из моего только что запущенного блога:


```разметка




<канал>


Сообщения в техническом блоге Александра Табакова | атабаков


<ссылка>https://atabakoff.com/posts/


Недавний контент в сообщениях технического блога Александра Табакова | атабаков


<изображение>


https://atabakoff.com/aleksandr_tabakov.jpeg


<ссылка>https://atabakoff.com/aleksandr_tabakov.jpeg



Хьюго -- gohugo.io


Пт, 27 мая 2022 г., 21:33:51 +0200


<элемент>


Как запустить Vaultwarden в Docker/Podman как службу systemd


https://atabakoff.com/how-to-run-vaultwarden-in-podman-as-a-systemd-service/


Пт, 27 мая 2022 г., 21:33:51 +0200


https://atabakoff.com/how-to-run-vaultwarden-in-podman-as-a-systemd-service/


Запуск Vaultwarden в контейнере в качестве службы systemd с использованием Podman. Как установить Podman, запустить Vaultwarden в контейнере, создать конфигурацию systemd для службы Vaultwarden и управлять ею с помощью systemctl.





Каждое сообщение представлено элементом item, где нам нужны title, link и pubDate.


Парсинг RSS-канала с помощью grep


Наивный подход состоит в том, чтобы использовать grep, а затем построить уценку в цикле bash. Давайте сначала попробуем grep:


``` ударить


wget --quiet -O rss.xml https://atabakoff.com/posts/index.xml


кот rss.xml | grep -Po '<(название|ссылка|дата публикации)>[^<]+'


Сообщения в техническом блоге Александра Табакова | атабаков</p><br><p><ссылка>https://atabakoff.com/posts/</p><br><p><ссылка>https://atabakoff.com/aleksandr_tabakov.jpeg</p><br><p><title>Как запустить Vaultwarden в Docker/Podman в качестве службы systemd</p><br><p><ссылка>https://atabakoff.com/how-to-run-vaultwarden-in-podman-as-a-systemd-service/</p><br><p><pubDate>Пт, 27 мая 2022 г. 21:33:51 +0200</p><br><p>Неплохо, но нам нужно избавиться от первых трех строк и открывающего тега:</p><br><p>``` ударить</p><br><p>кот rss.xml | grep -Po '<(title|link|pubDate)>[^<]+' | хвост -n +4 \</p><br><p>| grep -oE '>([^>]+)' | grep -oE '([^>]+)'</p><br><p>Как запустить Vaultwarden в Docker/Podman в качестве службы systemd</p><br><p>https://atabakoff.com/how-to-run-vaultwarden-in-podman-as-a-systemd-service/</p><br><p>Пт, 27 мая 2022 21:33:51 +0200</p><br><p>Просто для проверки grep</p><br><p>https://atabakoff.com/testing-grep/</p><br><p>Пт, 31 мая 2022 18:33:51 +0200</p><br><p>Я добавил один дополнительный элемент, чтобы проверить, работает ли мое выражение с несколькими сообщениями. В этот момент я начинаю думать, что <code>grep</code> может быть не лучшим вариантом. Я быстро написал конвертер для преобразования RSS в уценку, прежде чем исследовать другие варианты:</p><br><p>``` ударить</p><br><h1>!/бин/баш</h1><br><p>items=$( cat rss.xml | grep -Po '<(title|link|pubDate)>[^<]+' | tail -n +4 \</p><br><p>| grep -oE '>([^>]+)' | grep -oE '([^>]+)' )</p><br><p>ЕСЛИ=$'<br>'</p><br><p>количество = 0</p><br><p>для товара в $items</p><br><p>делать</p><br><p>case $(expr $count % 3) в</p><br><p>'0')</p><br><p>название = $ элемент</p><br><p>ссылка = ''</p><br><p>дата публикации = ''</p><br><p>'1')</p><br><p>ссылка=$элемент</p><br><p>'2')</p><br><p>pubDate=$(дата -d "$item" +'%d/%m/%Y' )</p><br><p>кошка<<EOF</p><br><ul> <li>$pubDate <a href="$ссылка">$название</a></li> </ul><br><p>EOF</p><br><p>эсак</p><br><p>количество = $ (($ количество + 1))</p><br><p>Выполнено</p><br><p>Запустите его для проверки:</p><br><p>``` ударить</p><br><p>./test.sh</p><br><ul> <li>05.07.2022 <a href="https://atabakoff.com/how-to-run-vaultwarden-in-podman-as-a-systemd-service/">Как запустить Vaultwarden в Docker/Podman в качестве службы systemd</a></li> </ul><br><ul> <li>05.03.2022 <a href="https://atabakoff.com/testing-grep/">Только для проверки grep</a></li> </ul><br><h3>Проверка производительности</h3><br><p>Моя RSS-лента крошечная. Чтобы измерить производительность, нам нужно запустить парсер много раз. Я создал файл <code>test.sh</code>:</p><br><p>``` ударить</p><br><h1>!/бин/баш</h1><br><p>х=100</p><br><p>в то время как [ "$x" -gt "0" ] ; делать</p><br><p>$(/bin/bash $1) 2>/dev/null</p><br><p>х=$((х-1))</p><br><p>Выполнено</p><br><p>Он принимает файл сценария в качестве параметра и запускает его 100 раз в цикле. Давайте запустим его с <code>time</code>, чтобы увидеть, сколько времени требуется для анализа фида:</p><br><blockquote> <p>время ./test.sh grep-rss.sh</p> </blockquote><br><p>./test.sh grep.sh 1,87 с пользователь 0,72 с система 137% ЦП 1,883 всего</p><br><p>Не очень впечатляюще, но ожидаемо из-за использования регулярных выражений.</p><br><h3>Парсинг RSS-канала в Bash</h3><br><p>Я начал гуглить, есть ли способ разобрать XML в Bash, и нашел это [отличное решение] (https://www.linuxjournal.com/content/parsing-rss-news-feed-bash-script). Здесь описана та же проблема парсинга RSS-канала. Я изменил код для своих нужд и сохранил его в файле <code>parse-rss.sh</code>:</p><br><p>``` ударить</p><br><h1>!/бин/баш</h1><br><p>xmlgetnext () {</p><br><p>местный IFS='>'</p><br><p>read -d '<' ЗНАЧЕНИЕ ТЕГА</p><br><p>кот $1 | пока xmlgetnext ; делать</p><br><p>случай $TAG в</p><br><p>'вещь')</p><br><p>название = ''</p><br><p>ссылка = ''</p><br><p>дата публикации = ''</p><br><p>'заглавие')</p><br><p>название=$ЗНАЧЕНИЕ</p><br><p>'ссылка на сайт')</p><br><p>ссылка=$ЗНАЧЕНИЕ</p><br><p>'дата публикации')</p><br><p>pubDate=$(дата -d "$VALUE" +'%d/%m/%Y' )</p><br><p>'/вещь')</p><br><p>кошка<<EOF</p><br><ul> <li>$pubDate <a href="$ссылка">$название</a></li> </ul><br><p>EOF</p><br><p>эсак</p><br><p>Выполнено</p><br><p>Я провел тот же тест, чтобы сравнить производительность:</p><br><p>``` ударить</p><br><p>время ./test.sh parse-rss.sh</p><br><p>./test.sh parse.sh 0,81 с пользователь 0,33 с система 109% ЦП 1,042 всего</p><br><p>Почти <em>в два раза</em> быстрее: «1042» против «1883». Это последний подход, который я выбрал для обработки RSS-канала.</p><br><h2>Обновление README.md</h2><br><p>Обновление списка постов — это просто замена. Поскольку уценка позволяет использовать HTML-код, мы можем использовать HTML-комментарии, чтобы пометить заполнитель для сообщений:</p><br><p>```разметка</p><br><!--блог:старт--><br><!--блог:конец--><br><p>Стандартным инструментом для замены текста в Bash является <code>sed</code>, но у него есть одно ограничение. Это строковый редактор, обрабатывающий только одну строку за один шаг. В нашем случае и заполнитель, и список постов представляют собой многострочный текст. Вот как я это решил:</p><br><p>``` ударить</p><br><h1>!/бин/баш</h1><br><p>ЧИСЛО=$(($2*3))</p><br><p>POSTS=$(cat $1 | head $NUM | tr '<br>' '\t')</p><br><p>кот README.md | тр '<br>' '\t' \</p><br><p>| sed -E "s#(<!--блог:начало-->).*(<!--блог:конец-->)#\1\t${Публикации}\2#g" \</p><br><p>| tr '\t' '<br>' > README.tmp</p><br><p>mv README.tmp README.md</p><br><p>rm -f rss.xml posts.md</p><br><p>Некоторые вещи, которые стоит пояснить:</p><br><ul> <li><code>NUM=$(($2*3))</code> — количество строк для указанного количества постов; в моем случае я хочу показать пять постов по три строки каждый (название, ссылка, дата)</li> </ul><br><ul> <li><code>tr '<br>' '\t'</code> должен преобразовать текст в одну строку для обработки с помощью <code>sed</code></li> </ul><br><ul> <li><code>tr '\t' '<br>'</code> возвращает символы новой строки</li> </ul><br><h2>Конвейер действий Github</h2><br><p>Теперь у нас есть наши сценарии, и нам нужно поместить их в конвейер. Действия Github просматривают специальный каталог <code>.gihub/workflows</code> и обрабатывают там каждый файл <code>.yaml</code>. Я создал там файл <code>posts.yml</code> со следующим содержимым:</p><br><p>``ямл</p><br><p>name: Обновление сообщений в блоге</p><br><p>на:</p><br><p>толкать:</p><br><p>workflow_dispatch:</p><br><p>расписание:</p><br><ul> <li>cron: '0 0 * <em> </em>'</li> </ul><br><p>вакансии:</p><br><p>обновить-readme-с-последними-сообщениями:</p><br><p>запуски: ubuntu-последняя</p><br><p>шаги:</p><br><ul> <li>имя: Клонировать репозиторий</li> </ul><br><p>использует: action/checkout@v2</p><br><p>с:</p><br><p>глубина выборки: 1</p><br><ul> <li>имя: Получить RSS-канал</li> </ul><br><p>выполнить: wget --quiet -O rss.xml https://atabakoff.com/posts/index.xml</p><br><ul> <li>название: Разобрать RSS-канал</li> </ul><br><p>запустить: |</p><br><p>компакт-диск ${GITHUB_WORKSPACE}</p><br><p>./src/parse-rss.sh rss.xml > posts.md</p><br><ul> <li>название: Обновление README.md</li> </ul><br><p>запустить: |</p><br><p>компакт-диск ${GITHUB_WORKSPACE}</p><br><p>./src/update-readme.sh posts.md 5</p><br><ul> <li>имя: Нажмите изменения</li> </ul><br><p>запустить: |</p><br><p>git config --global user.name "${GITHUB_ACTOR}"</p><br><p>git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com"</p><br><p>git commit -am "Обновленные сообщения в блоге" | выход 0</p><br><p>git push</p><br><p>Вот что нужно пояснить:</p><br><ul> <li><code>push</code> - запускать по нажатию</li> </ul><br><ul> <li><code>cron: '0 0 * * *'</code> - это расписание, в моем случае каждый день в полночь</li> </ul><br><ul> <li><code>uses: action/checkout@v2</code> клонирует репозиторий</li> </ul><br><p>Затем я разделил получение, синтаксический анализ и обновление на отдельные этапы. Это позволяет мне быстро локализовать проблему, если что-то пойдет не так. Стоит отметить:</p><br><ul> <li><code>cd ${GITHUB_WORKSPACE}</code> должен перейти в текущий рабочий каталог, который является недавно клонированным репозиторием.</li> </ul><br><ul> <li><code>${GITHUB_ACTOR}</code> – ваше имя пользователя</li> </ul><br><ul> <li><code>${GITHUB_ACTOR}@users.noreply.github.com</code> — это специальный адрес электронной почты Github, который можно использовать для отправки изменений в репозиторий.</li> </ul><br><h2>Вывод</h2><br><p>Вы можете найти полное решение в <a href="https://github.com/daydiff/daydiff/">репозиторий моего профиля</a>. Было очень весело решать эту проблему с помощью чистого Bash.</p><br><p>При этом на Github существует множество действий сообщества. Они позволяют создать динамический профиль без написания кода. Все, что вам нужно сделать, это написать немного YAML. Но в этом мало сложностей. Это не путь воина.</p><br><p>Также опубликовано [здесь] (https://atabakoff.com/creating-a-dynamic-github-profile-with-github-actions-and-bash/).</p><br> <hr> <a href="https://hackernoon.com/using-github-actions-and-bash-to-create-dynamic-github-profile" target="_blank" rel="nofollow">Оригинал</a> <!-- Yandex.RTB R-A-1755394-6 --> <div class="row"> <div id="yandex_rtb_R-A-1755394-6" class="col-12"></div> </div> <script>window.yaContextCb.push(() => { Ya.Context.AdvManager.render({ "blockId": "R-A-1755394-6", "renderTo": "yandex_rtb_R-A-1755394-6", "type": "feed" }) }) </script> </div> <div class="col-lg-3 sidebar"> <div class="recent-post"> <h3 class="widget-title">Recent Post</h3> <hr> <!-- Yandex Native Ads C-A-1755394-2 --> <div id="id-C-A-1755394-2"></div> <script>window.yaContextCb.push(() => { Ya.Context.AdvManager.renderWidget({ renderTo: 'id-C-A-1755394-2', blockId: 'C-A-1755394-2' }) })</script> <ul class="top_15"> <li> <a href="/blog/laravel-under-the-hood-the-magic-of-macros/"> <h4 class="title">Laravel Under The Hood - Магия макросов</h4> <span>5 ноября 2024 г.</span> </a> </li> <li> <a href="/blog/claude-ai-runs-javascript-python-beats-javascript-on-github-node-js-updates-this-week-in-js/"> <h4 class="title">Claude AI запускает JavaScript, Python опережает JavaScript на GitHub и обновления Node.js - Эта неделя в JS</h4> <span>4 ноября 2024 г.</span> </a> </li> <li> <a href="/blog/here-s-why-owning-a-directory-owning-a-free-infinite-marketing-channel/"> <h4 class="title">Вот почему владение каталогом = владение бесплатным бесконечным маркетинговым каналом</h4> <span>4 ноября 2024 г.</span> </a> </li> <li> <a href="/blog/avkit-integration-in-ios-18-30daysofswift/"> <h4 class="title">Интеграция AVKit в iOS 18 - #30DaysOfSwift</h4> <span>2 ноября 2024 г.</span> </a> </li> <li> <a href="/blog/forms-and-input-handling-in-ios-18-30daysofswift/"> <h4 class="title">Формы и обработка ввода в iOS 18 - #30DaysOfSwift</h4> <span>2 ноября 2024 г.</span> </a> </li> </ul> </div> <div class="categories top_45"> <h3 class="widget-title">Categories</h3> <ul class="top_30"> <li><a href="http://coffee-web.ru/blog/category/python/">Python</a></li> <li><a href="http://coffee-web.ru/blog/category/blockchain/">blockchain</a></li> <li><a href="http://coffee-web.ru/blog/category/web/">web</a></li> <li><a href="http://coffee-web.ru/blog/category/hackernoon/">hackernoon</a></li> <li><a href="http://coffee-web.ru/blog/category/computing/">вычисления</a></li> <li><a href="http://coffee-web.ru/blog/category/computing-components/">вычислительные компоненты</a></li> <li><a href="http://coffee-web.ru/blog/category/digital-home/">цифровой дом</a></li> <li><a href="http://coffee-web.ru/blog/category/gaming/">игры</a></li> <li><a href="http://coffee-web.ru/blog/category/audio/">аудио</a></li> <li><a href="http://coffee-web.ru/blog/category/home-cinema/">домашний кинотеатр</a></li> <li><a href="http://coffee-web.ru/blog/category/internet/">Интернет</a></li> <li><a href="http://coffee-web.ru/blog/category/mobile-computing/">Мобильные вычисления</a></li> <li><a href="http://coffee-web.ru/blog/category/networking/">сеть</a></li> <li><a href="http://coffee-web.ru/blog/category/photography-video-capture/">фотосъемка видео</a></li> <li><a href="http://coffee-web.ru/blog/category/portable-devices/">портативные устройства</a></li> <li><a href="http://coffee-web.ru/blog/category/software/">программного обеспечения</a></li> <li><a href="http://coffee-web.ru/blog/category/phone-and-communications/">телефон и связь</a></li> <li><a href="http://coffee-web.ru/blog/category/television/">телевидение</a></li> <li><a href="http://coffee-web.ru/blog/category/video/">видео</a></li> <li><a href="http://coffee-web.ru/blog/category/world-of-tech/">мир технологий</a></li> <li><a href="http://coffee-web.ru/blog/category/smart-persons-guides/">умные гиды</a></li> <li><a href="http://coffee-web.ru/blog/category/cloud/">облако</a></li> <li><a href="http://coffee-web.ru/blog/category/artificial-intelligence/">искусственный интеллект</a></li> <li><a href="http://coffee-web.ru/blog/category/ces/">се</a></li> <li><a href="http://coffee-web.ru/blog/category/samsung/">Samsung</a></li> <li><a href="http://coffee-web.ru/blog/category/smart-cities/">умные города</a></li> <li><a href="http://coffee-web.ru/blog/category/digitaltrends/">digitaltrends</a></li> <li><a href="http://coffee-web.ru/blog/category/security/">безопасность</a></li> <li><a href="http://coffee-web.ru/blog/category/tech-and-work/">техника и работа</a></li> <li><a href="http://coffee-web.ru/blog/category/cxo/">cxo</a></li> <li><a href="http://coffee-web.ru/blog/category/mobility/">мобильность</a></li> <li><a href="http://coffee-web.ru/blog/category/developer/">разработчик</a></li> <li><a href="http://coffee-web.ru/blog/category/5g/">5г</a></li> <li><a href="http://coffee-web.ru/blog/category/microsoft/">майкрософт</a></li> <li><a href="http://coffee-web.ru/blog/category/innovation/">инновации</a></li> </ul> </div> <!-- Yandex.RTB R-A-1755394-7 --> <div id="yandex_rtb_R-A-1755394-7"></div> <script>window.yaContextCb.push(() => { Ya.Context.AdvManager.render({ "blockId": "R-A-1755394-7", "renderTo": "yandex_rtb_R-A-1755394-7" }) }) </script> </div> </div> </div> <div class="col-lg-6 offset-lg-3 top_30"> </div> </div> <div class="left-prev project-nav blog"> <a href="/blog/smart-contract-security-is-complicated-but-totally-worth-it/">PREVIOUS ARTICLE</a> </div> <div class="right-next project-nav blog"> <a href="/blog/uk-us-eu-authorities-gather-in-san-francisco-to-discuss-ai-safety/">NEXT ARTICLE</a> </div> <footer> <div class="copyright float-left"> <p>COPYRIGHT 2020 COFFEE-WEB.RU</p> </div> <!-- Social Links --> <ul class="social float-right"> <li><a href="#"><i class="fab fa-facebook-f"></i> </a></li> <li><a href="#"><i class="fab fa-twitter" aria-hidden="true"></i> </a></li> <li><a href="#"><i class="fab fa-dribbble" aria-hidden="true"></i> </a></li> <li><a href="#"><i class="fab fa-behance" aria-hidden="true"></i> </a></li> </ul> </footer> <div class="container-fluid page-grid page-bg"> <div class="row"> <div class="col-lg-3 line"></div> <div class="col-lg-3 line"></div> <div class="col-lg-3 line"></div> <div class="col-lg-3 line"></div> </div> </div> </div> <!-- Javascripts --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <script src="/static/js/jquery.fullpage.js"></script> <script src="/static/js/animsition.min.js"></script> <script src="/static/js/bootstrap.min.js"></script> <script src="/static/js/modernizr.custom.js"></script> <script src="/static/js/jquery.dlmenu.js"></script> <script src="/static/js/jquery.mb.YTPlayer.min.js"></script> <script src="/static/js/main.js"></script> <script type="text/javascript"> jQuery(function () { jQuery("#bgndVideo").YTPlayer(); }); </script> <script src="/static/js/comments.js"></script> <!-- Yandex.Metrika counter --> <script type="text/javascript"> (function (m, e, t, r, i, k, a) { m[i] = m[i] || function () { (m[i].a = m[i].a || []).push(arguments) }; m[i].l = 1 * new Date(); k = e.createElement(t), a = e.getElementsByTagName(t)[0], k.async = 1, k.src = r, a.parentNode.insertBefore(k, a) }) (window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym"); ym(50511013, "init", { clickmap: true, trackLinks: true, accurateTrackBounce: true, webvisor: true }); <!-- Yandex.RTB R-A-1755394-3 --> window.yaContextCb.push(() => { Ya.Context.AdvManager.render({ type: 'fullscreen', platform: 'touch', blockId: 'R-A-1755394-3' }) }) window.yaContextCb.push(()=>{ Ya.Context.AdvManager.render({ "blockId": "R-A-1755394-8", "type": "fullscreen", "platform": "desktop" }) }) </script> <noscript> <div><img src="https://mc.yandex.ru/watch/50511013" style="position:absolute; left:-9999px;" alt=""/></div> </noscript> <!-- /Yandex.Metrika counter --> </body> </html>