Compare commits

...

1 Commits

Author SHA1 Message Date
06e0e6afe8 refactor: migrate from Hugo to Org-mode static site generator
- Remove Hugo configuration, themes, and Node.js dependencies
- Delete CI/CD files (.drone.yml, .fleek.json, Dockerfile)
- Convert Markdown content to Org-mode format in org/ directory
- Add Emacs Lisp publishing script with ox-publish configuration
- Create Makefile for build automation
- Update .gitignore for Emacs and macOS files
- Preserve media assets with Git LFS tracking
- Add RSS feed generation capability
- Remove package.json, package-lock.json, and markdownlint config
2025-09-23 17:07:02 +02:00
27 changed files with 691 additions and 1391 deletions

View File

@ -1,18 +0,0 @@
kind: pipeline
type: docker
name: build
steps:
- name: lint checks
image: node:14.16-alpine
commands:
- npm install
- npm run lint
- name: spellchecker checks
image: polpetta/spellchecker
commands:
- ./tools/spellchecker.sh en
- name: hugo build
image: klakegg/hugo:0.82.1-ext-alpine-ci
commands:
- hugo

View File

@ -1,10 +0,0 @@
{
"build": {
"image": "polpetta/fleek:latest",
"command": "git lfs install && git lfs pull && yarn && hugo",
"publicDir": "public",
"environment": {
"HUGO_DISABLELANGUAGES": "it"
}
}
}

86
.gitignore vendored
View File

@ -1,12 +1,94 @@
# ---> macOS
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# ---> Hugo
# Generated files by hugo
/public/
/resources/_gen/
/assets/jsconfig.json
hugo_stats.json
# Executable may be added to repository
hugo.exe
hugo.darwin
hugo.linux
node_modules/
.hugo_build.lock
# Temporary lock file while building
/.hugo_build.lock
# ---> Emacs
# -*- mode: gitignore; -*-
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
# Org-mode
.org-id-locations
*_archive
# flymake-mode
*_flymake.*
# eshell files
/eshell/history
/eshell/lastdir
# elpa packages
/elpa/
# reftex files
*.rel
# AUCTeX auto folder
/auto/
# cask packages
.cask/
dist/
# Flycheck
flycheck_*.el
# server auth directory
/server/
# projectiles files
.projectile
# directory configuration
.dir-locals.el
# network security
/network-security.data
/rss.xml

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "themes/hermit"]
path = themes/hermit
url = https://github.com/Track3/hermit.git

View File

@ -1,39 +0,0 @@
# Disable some built-in rules
config:
no-inline-html: false
MD013:
code_block_line_length: 120
MD024:
allow_different_nesting: true
# no-trailing-spaces: false
# no-multiple-blanks: false
# Include a custom rule package
#customRules:
# - markdownlint-rule-titlecase
# Fix any fixable errors
fix: false
# Define a custom front matter pattern
#frontMatter: "<head>[^]*<\/head>"
# Define glob expressions to ignore
ignores:
- "*.autogen.md"
# Use a plugin to recognize math
#markdownItPlugins:
# -
# - "@iktakahiro/markdown-it-katex"
# Disable inline config comments
noInlineConfig: true
# Disable progress on stdout
noProgress: true
# Use a specific formatter
outputFormatters:
-
- markdownlint-cli2-formatter-default

View File

@ -1,8 +0,0 @@
FROM fleek/hugo:node-16
LABEL MAINTAINER="Davide Polonio poloniodavide@gmail.com"
LABEL DESCRIPTION="Docker image for deploying website on IPFS via fleek"
RUN apt-get update \
&& apt-get install -y git-lfs \
&& rm -rf /var/lib/apt/lists/*

16
Makefile Normal file
View File

@ -0,0 +1,16 @@
.PHONY: all
.DEFAULT_GOAL := all
all: clean html
html:
@./publish.el
watch:
@fswatch -0 path | while read -d "" event \
do;
./publish.el "${event}";
done
clean:
@rm -rf ./dist/

View File

@ -1,6 +0,0 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

View File

@ -1,73 +0,0 @@
baseURL = "https://bitdispenser.dev/"
languageCode = "en-us"
defaultContentLanguage = "en"
title = "Bitdispenser"
theme = "hermit"
#enableGitInfo = true
pygmentsCodefences = true
pygmentsUseClasses = true
rssLimit = 15
copyright = "This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License."
enableEmoji = true
relativeUrls = true
[author]
name = "Davide Polonio"
[taxonomies]
tag = "tags"
[params]
dateform = "2 Jan 2006"
dateformShort = "2 Jan"
dateformNum = "02-01-2006"
dateformNumTime = "02-01-2006"
themeColor = "#494f5c"
homeSubtitle = "Yet another ordered dispenser of bits"
footerCopyright = ' &#183; <a href="http://creativecommons.org/licenses/by-sa/4.0/" target="_blank" rel="noopener">CC BY-SA 4.0</a>'
justifyContent = true
relatedPosts = true
code_copy_button = true
[[params.social]]
name = "email"
url = "mailto:davide+bitdispenser@poldebra.me"
[[params.social]]
name = "github"
url = "https://github.com/Polpetta"
[[params.social]]
name = "stackoverflow"
url = "https://stackoverflow.com/users/9306378/polpetta"
[[params.social]]
name = "linkedin"
url = "https://www.linkedin.com/in/davidepolonio"
[languages]
[languages.en]
languageName = "English"
contentDir = "content/english"
title = "Bitdispenser"
homeSubtitle = "Yet another ordered dispenser of bits"
weight = 0
[[languages.en.menu.main]]
name = "Posts"
url = "posts/"
weight = 10
[[languages.en.menu.main]]
name = "About me"
url = "about-me"
weight = 20
[languages.it]
languageName = "Italiano"
contentDir = "content/italian"
title = "Bitdispenser"
homeSubtitle = "L'ennesimo distributore ordinato di bit"
weight = 10
[[languages.it.menu.main]]
name = "Articoli"
url = "articoli/"
weight = 10
[[languages.it.menu.main]]
name = "Riguardo l'Autore"
url = "riguardo-l-autore"
weight = 20

View File

@ -1,56 +0,0 @@
---
title: "About me"
---
Hi all! I am Davide Polonio, a programmer and devOps engineer. I am born in 1994
and started messing with computers since I was 8 (my first OS was the only and
glorious Windows 95), and from there I continued my path to learn about
computers and how to ~~break~~ tweak them. I graduated at university of Padova
with a master degree in computer science in 2018.
I prefer back-end stuff (I usually end up using Java, Golang, Rust and Bash to
gluing stuff around), especially automating releases with Continuous Integration
and Continuous Delivery. I create and manage pipelines for Jenkins (and I
currently administer Jenkins as well), but I also know other CI systems like
Github Actions, Travis CI, Drone CI (or the now clone Woodpecker). I enjoy
container technologies, starting from Docker, Podman and going on to
orchestrators like Docker Swarm, Kubernetes and Nomad (which I would like to
learn more of the lasts two). If I can, I use Infrastructure as Code (IaC) tools
like Terraform and Packer to create and maintain infrastructure on cloud
providers, like AWS or Scaleway (I also maintain [Podman plugin for
Packer](https://github.com/Polpetta/packer-plugin-podman)). Automation is
usually my preferred way, since it helps developers focus only on the business
product instead of messing with tasks that can be done by a machine. Finally, I
am interested in development processes like (but not only) Agile, Scrum,
Waterfall. I think the right way to develop a product starts from how you
organize your workflow and what processes you put in place in order to keep the
_development machine_ going.
I am an open source enthusiast, so when I am able in my free time (or paid time
if it is an open source business project) I like to contribute to open source
projects and discover new ones. I believe in the added values that open source
software (and especially free software) can give respect to closed ones. My
dream job would be to contribute to one of the many open source I like full
time! 🤩
I'm not only into programming though! I like to practice sport. In particular, I
am a black belt Judo and I teach it at the local dojo were I live. I know how to
ski from when I was I child, and if possible I enjoy passing some time in the
middle of mountains to admire the beauty of nature in winter. Finally, I like to
play tabletops games and I enjoy things like table football, billiards, bowling
(although I suck at the last one 😅).
If you are interested in getting in touch with me (or to get my resume) you can
send me an e-mail (I prefer this way) at
[davide@poldebra.me](mailto:davide+bitdispenser@poldebra.me) or you can contact
me through my [LinkedIn](https://www.linkedin.com/in/davidepolonio/) profile.
## Why this website and this blog
As I have already written at the beginning of [my first blog post]({{< ref
"/hello-world.md" >}}) I liked the idea of a place where I can share my thoughts
regarding technologies, personal experiences and what not. This blog is open
source, and all my content is licensed under CC BY-SA 4.0. The repository can be
found either on [my personal Gitea
instance](https://git.poldebra.me/polpetta/bitdispenser.dev) or on [Github
(mirrored)](https://github.com/Polpetta/bitdispenser.dev).

View File

@ -1,150 +0,0 @@
---
title: Hello world!
description: The very first article of this blog
tags:
- blog
- tech
- hugo
date: 2021-05-18T22:22:00+02:00
---
If you are reading this it means either you are trying to understand who I am
(are you a recruiter by any chance?) or you are simply bored. In any way, this
is the very first blog post, and it means you have come to the end of the road
(since this is the beginning). I always loved to have a place where I can write
down my thoughts about a new technology, a framework or what I did in order to
achieve a goal in a hobby project. That is not all though: I was searching for a
place where I was also able to express my ideas regarding modern dilemmas like
privacy issues and decentralization. I am not the kind of guy who likes social
networks, so I never had the chance to express them. Until now.
## How this blog is built
### The website
This blog is a very simple [Hugo](https://gohugo.io/) website. If you do not
know what it is, its basically a static website generator. It takes Markdown
documents and converts them in HTML pages. If you applied a specific theme then
it builds the page according to that theme. Hugo has a huge selection of themes
in its [dedicated themes page](https://themes.gohugo.io/), so basically picking
up one and starting from there is very simple. If you are curious about how I
made it, you can check out the source at my [personal git server
instance](https://git.poldebra.me/polpetta/bitdispenser.dev), where I started
hosting my code when [Microsoft bought
Github](https://news.microsoft.com/announcement/microsoft-acquires-github/) (you
can find a copy of the repository on Github too, visiting [my
profile](https://github.com/Polpetta)). I have simply picked up the simpler and
cleanest blog theme out there, following Ludwig Mies van der Rohe's idea:
> less is more
It will be a success if more than two people actually starts reading what I
write here, at least I want them to read this blog without having their
eyes bleeding with an extravagant color combination.
Last but not least, the website icon is literally the Team Fortress 2 dispenser
(one of the games I love and one of the first game I started playing online as a
kid when a decent connection at home was finally available), taken from the RED
team and generated via a [favicon generator](https://realfavicongenerator.net/).
The name of the website partially came out from that, and from the fact that
basically every website is a dispenser of bits, and it is only thanks to our
beloved browsers we are able to actually "consume" what is distributed in the
first place.
{{< figure src="/content/hello-world/engiwithdispenser.png" alt=`Engineer with
his dispenser` caption=`Engineer class with a dispenser. Thanks to [Team
Fortress wiki](https://wiki.teamfortress.com) for providing the image I
shamelessly downloaded from them` >}}
I hope Valve will not sue me for taking that asset as my website favicon. I
swear I will change it, a day. Pinky promise.
### Hosting
The real problem of hosting a website now day is not how to build it (as you can
see) but _where_ you can host it. There are plenty of cloud provides: AWS,
Google Cloud, DigitalOcean, Scaleway, etc... every day there is a new one
popping up. They all offers the possibility to host your website pretty easily,
especially if the website is a static one like this (e.g. using a S3 bucket).
Since I like challenges and I also like to learn new stuff, I though that
hosting the website in this way was boring. At the same time, I wanted to have a
good uptime and to not meddle too much under the hood. My (dream) requirements
were:
* always up
* good response time
* possibility to host as much data as I want
* using a hosting free as in beer and (possibly) that could use free as in
freedom technologies
Now, reading this I imagine you are thinking I am going crazy, and maybe I am, but
that is not the case. In fact, multiple weeks prior to writing this article,
while trying to kill the boredom caused by COVID-19 lockdown, I discovered
[IPFS](https://ipfs.io/). I already heard of it at University, but I never
bothered too much to understand what was about. I though "well, it surely is
some sort of filesystem". I was somewhat right, but not the way I thought.
IPFS acts like a peer-to-peer network, where nodes hash the content they want to
share to let other nodes grab it. You can grab this content using your local
node or using one of the many available gateways. Nodes can "pin" a file too, in
order to keep it locally and to serve it to other nodes. If a file gets pinned
by different nodes and gains traction it basically can not be deleted from the
web.
#### Choosing the right tools
Surely, as [the IPFS documentation
describes](https://docs.ipfs.io/how-to/websites-on-ipfs/multipage-website/), I
could have done it by myself. But there is a but. Holding all the infrastructure
manually means sacrifice the "always up" and "good response time" thingy for two
simple motives:
1. the [server where I host my drafts & codebase](https://git.poldebra.me) is
hosted on a very small machine (2GB of RAM and 2vCPU), very easy to kill with
the slightest of loads
2. in order to achieve a good response time, a CDN or some sort of caching is
necessary (even if the application is stored in a distributed file system)
{{< figure src="/content/hello-world/fleek.png" caption=`Fleek website, that I
used to automate my deployment on IPFS and my DNS update` alt=`The fleek website
screenshot` class="right" >}}
Finally, in order to achieve full automation with DNS updates, I would have
needed to implement and use NameCheap APIs (currently it is the DNS provider I
use for most of my websites). "What's the difficulty?" one would ask. [Here is
the official documentation](https://www.namecheap.com/support/api/intro/), and
even if the APIs look promising, while studying them my will to live decreased a
little bit, and so I decided that if I wanted to get up and running with less
maintenance as possible, with a good uptime while having the maximum
automation possible I needed to rely on a dedicated service. Luckily
[fleek.co](https://fleek.co) was what I was searching for. They currently
provide the possibility to buy a domain from their website, giving them all the
hassle of updating the new website on IPFS, distributing it, refreshing a very
possible CDN and finally to update the DNS records accordingly. This, as you can
imagine, provides multiple benefits:
* I do not have to care about my very little dev machine getting hugged to death
by request in the remote case any of my posts get any attention
* I do not have to focus on automating the process someone else has already done
for me
* I can focus on writing posts after dinner instead of scratching my head trying
to understand why the website does not load/the DNS is not properly updated
The only downside to this approach is that Fleek provides limitations on how
much data and bandwidth you can host. At the time of writing, you can only host
up to 3GB (enough for this website) and have a 50GB bandwidth (that is fine for
now) for the free version. [Upgrading you account](https://fleek.co/pricing/) to
one of the available plans give you extra space and bandwidth.
## Future improvements
For sure, this is only the beginning. Having an automatic workflow of spell
check, deployment and release would be the first milestone. Future features for
this website could be an automatic posting of every new article on a dedicated
Mastodon bot, so that people can possibly discuss about my thoughts on the
fediverse, a decentralized network.
To conclude, the frequency of this blog will be...whenever I have time to post
:grin: Of course I need content before posting something, and this require some
time for myself for experimenting with new technologies and learning new stuff,
so I do not expect very much posting, but only time will tell!

View File

@ -1,255 +0,0 @@
---
title: "Building a Telegram bot in Rust: a journey through Songlify"
description: An adventure through Rust and Telegram
tags:
- blog
- tech
- rust
- telegram
- songlify
date: 2022-01-05T17:01:00+02:00
---
Some time passed since the last article I wrote there. A lot of stuff happened
meanwhile, especially with COVID, but here we are again. While busy dealing with
the mess of real life tasks, three months ago I started to write a little bot
for Telegram in Rust. It is a simple one, but I consider the journey interesting
and worth of writing it down. If I add new features worth mentioning I will
start a series about it, maybe.
## Telegram bots
Telegram bots are not something new to me and nowadays are pretty much easy to
make, so I consider them like a gym where to try out new technologies and
experiment with stuff. I wrote plentiful of them, some of those are open source
like for example <https://github.com/Augugrumi/TorreArchimedeBot> (which is
currently broken 😭) that was useful when going to University, because it
scraped the university free room web page and from there it was able to tell you
which rooms where without any lessons and for how much time, allowing you to
easily find a place where to study with your mates (yep, we didn't like library
too much).
{{< figure src="/content/songlify/telegramscreen.png" alt=`A screenshot of
TorreArchimedeBot in action.` caption=`A screenshot of TorreArchimedeBot in
action.` >}}
Also another one bot worthy of mention is
<https://github.com/Polpetta/RedditToTelegram>, that allowed our D&D group to
receive push notifications of our private Subreddit in our Telegram group.
As you can see, all of these bots are quite simple, but they have the added
value of teaching you some new programming concepts, technologies or frameworks
that can be later applied in something that can be more production environment.
## Rust
I started to approach Rust many years ago (I do not remember exactly when).
First interaction with it was quite interesting to say at least: there were way
less compiler features (for example now the compiler is able to understand
object lifetime at compile time most of the time alone, without specifying them)
that made it a... _not-so-pleasant programming experience_. It had potential
thought, so by following Rust news I picked it up last year again, noticing that
now it has improved a lot and it is more pleasant to write. Meanwhile, also
JetBrains developed a good support for IntelliJ, so now it is even possible to
debug and perform every operation directly from your IDE UI.
## Making the two worlds collide: Rust + Telegram
One of the features I wanted to learn this time regarding Rust was the
asynchronous support it offers. Rust started to have `async` support with
[Tokio](https://tokio.rs/) framework, and recently the Rust team started to
build the asynchronous functionality inside Rust itself. Even if in the first
steps, it looks promising and the idea of a low-level language, without GC, with
automatic memory management and so much safety having asynchronous support is
exciting to me! 🥳 So the only option left, at this point, was to start messing
around with it. I started by picking up one of the many frameworks that provides
a layer for the Telegram APIs, [Teloxide](https://github.com/teloxide/teloxide).
In particular, as you can see from its _README_, one of the examples starts by
using `#[tokio:main]` macro:
```rust
use teloxide::prelude::*;
#[tokio::main]
async fn main() {
teloxide::enable_logging!();
log::info!("Starting dices_bot...");
let bot = Bot::from_env().auto_send();
teloxide::repl(bot, |message| async move {
message.answer_dice().await?;
respond(())
})
.await;
}
```
This was the reason I picked it up, given that it looked the most promising by
the time I started the project.
### Building _Songlify_
So, after choosing what was going to use to build the bot, I needed a _reason_
to build it.
With my friends we usually share a lot of songs (via Spotify links), so I
thought it was a good idea to build a bot around it. I integrated a Spotify API
library in it and started hacking up a bot.
> ⚠ Note that at the time of writing I have just noticed that the library I use
> for speaking with Spotify, [aspotify](https://crates.io/crates/aspotify) has
> been deprecated in favour of [rspotify](https://crates.io/crates/rspotify)
The first bot version was something very simple, and it was a single-file
program with nothing very fancy (I have written it in a night):
```rust
use crate::SpotifyURL::Track;
use aspotify::{Client, ClientCredentials};
use teloxide::prelude::*;
enum SpotifyURL {
Track(String),
}
fn get_spotify_entry(url: &str) -> Option<SpotifyURL> {
if url.contains("https://open.spotify.com/track/") {
let track_id = url.rsplit('/').next().and_then(|x| x.split('?').next());
return match track_id {
Some(id) => Some(SpotifyURL::Track(id.to_string())),
None => None,
};
}
return None;
}
struct TrackInfo {
name: String,
artist: Vec<String>,
}
async fn get_spotify_track(spotify: Box<Client>, id: &String) -> Option<TrackInfo> {
match spotify.tracks().get_track(id.as_str(), None).await {
Ok(track) => Some(TrackInfo {
name: track.data.name,
artist: track.data.artists.iter().map(|x| x.name.clone()).collect(),
}),
Err(_e) => None,
}
}
#[tokio::main]
async fn main() {
teloxide::enable_logging!();
log::info!("Starting Songlify...");
let bot = Bot::from_env().auto_send();
teloxide::repl(bot, |message| async move {
let spotify_creds =
ClientCredentials::from_env().expect("CLIENT_ID and CLIENT_SECRET not found.");
let spotify_client = Box::new(Client::new(spotify_creds));
log::info!("Connected to Spotify");
let text = message.update.text().and_then(get_spotify_entry);
match text {
Some(spotify) => match spotify {
Track(id) => {
let track_info = get_spotify_track(spotify_client, &id).await;
match track_info {
Some(info) => {
let reply = format!(
"Track information:\n\
Track name: {}\n\
Artists: {}",
info.name,
info.artist.join(", ")
);
Some(message.reply_to(reply).await?)
}
None => None,
}
}
},
None => None,
};
respond(())
})
.await;
log::info!("Exiting...");
}
```
As you can see, basically every time a request arrived to the bot, login to
Spotify was performed and track information and name retrieved from there. Of
course this was only the beginning (also you can "appreciate" the number of
nested blocks there...). Now the bot supports albums and playlists too, with the
possibility to go through each song in the playlist and collect general
information such as how many artists are in that playlist, how many songs and
other little information like that. If you see the [bot
repository](https://git.poldebra.me/polpetta/Songlify) you can see now that
Spotify functions live in a separate module.
#### Packaging and distribution
The obvious choice for a software like that was to incorporate it into a OCI
image. I wrote a very simple Dockerfile that, once the program was built, took
the artifact and using the [multi-stage Docker build
functionality](https://docs.docker.com/develop/develop-images/multistage-build/)
and put it into a separate container, in order to avoid having build
dependencies inside the final image. I used the images distributed by the
[Distroless project](gcr.io/distroless/) (you can find the source on their
[Github repository](https://github.com/GoogleContainerTools/distroless)) in
order to obtain the smallest possible image. The final result?
```txt
λ ~/Desktop/git/songlify/ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test/test latest 8ac7a7018719 5 seconds ago 34MB
<none> <none> 4bc7fb0699e0 12 seconds ago 1.53GB
rust 1.56.1-slim-bullseye d3e070c5ffa7 6 weeks ago 667MB
gcr.io/distroless/base latest-amd64 24787c1cd2e4 52 years ago 20.2MB
```
A part from the 52 years old image pulled from `gcr`, you can see that
`test/test` (actually Songlify) is only of **34MB**. Not much if you consider
that inside that image there are shared dynamic libraries to make the executable
able to run, which by default weights 20MB. A plus of these images is that they
do not run as root user and they do not have any shell of bash integrated,
making a possible surface attack smaller (not that Docker is secure anyway...).
I upload the images on Docker Hub, where you can find them here
<https://hub.docker.com/r/polpetta/songlify>
Finally, to run the bot I use a very simple docker-compose definition, that can
be found in my [server-dotfiles
repository](https://git.poldebra.me/polpetta/server-dotfiles/src/commit/7e7e1780b2db45f475510c49bf1a2f9e76c4c166/songlify/docker-compose.yml).
This allows me to easily upgrade the bot by just changing the version and
running `docker-compose up -d`.
#### Plans for the future
Currently I work on the bot only when I feel like I wanna add something. An
interesting feature to add could be to insert a persistence layer (using a
database for instance) and add various stats (which is the most shared song? Who
_is that guy_ that shares the most songs in a group?). Persistence can be
achieved quite easily by using [Diesel](https://diesel.rs/), an ORM compatible
with various databases.
Another cool feature could be to add the _inline bot_ functionality, where you
can search for songs directly in Spotify. Since I currently have a domain
available for that I could set it up for receive web-hook notifications, instead
of performing polling like the bot is currently doing (one requisite for inline
bots is indeed to receive web-hooks). There are platforms like Heroku where you
could make the bot run, but currently I prefer to use my box since it gives me
more flexibility. Experimenting with Heroku could lead to cool results though
😏.
Finally, link translation could be something very useful. I have a friend that
does not use Spotify but prefers to listen to music via YouTube. So an
interesting feature would be, given a Spotify link, to "convert" it into a
YouTube link and, of course, _vice-versa_. This could lead to translate
playlists and albums too into YouTube-based playlists, which of course could be
very useful if you are trying to avoid the infamous _vendor lock-in_, in this
case being stuck with Spotify because all you music collection, saved songs, etc
is there.

View File

@ -1,5 +0,0 @@
---
Title: "Riguardo l'Autore"
---
Prova 😀

View File

@ -1,7 +0,0 @@
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">

60
org/about-me.org Normal file
View File

@ -0,0 +1,60 @@
Hi all! I am Davide Polonio, a programmer and devOps engineer. I am born
in 1994 and started messing with computers since I was 8 (my first OS
was the only and glorious Windows 95), and from there I continued my
path to learn about computers and how to +break+ tweak them. I graduated
at university of Padova with a master degree in computer science
in 2018.
I prefer back-end stuff (I usually end up using Java, Golang, Rust and
Bash to gluing stuff around), especially automating releases with
Continuous Integration and Continuous Delivery. I create and manage
pipelines for Jenkins (and I currently administer Jenkins as well), but
I also know other CI systems like Github Actions, Travis CI, Drone CI
(or the now clone Woodpecker). I enjoy container technologies, starting
from Docker, Podman and going on to orchestrators like Docker Swarm,
Kubernetes and Nomad (which I would like to learn more of the lasts
two). If I can, I use Infrastructure as Code (IaC) tools like Terraform
and Packer to create and maintain infrastructure on cloud providers,
like AWS or Scaleway (I also maintain
[[https://github.com/Polpetta/packer-plugin-podman][Podman plugin for Packer]]).
Automation is usually my preferred way, since it helps developers focus
only on the business product instead of messing with tasks that can be
done by a machine. Finally, I am interested in development processes
like (but not only) Agile, Scrum, Waterfall. I think the right way to
develop a product starts from how you organize your workflow and what
processes you put in place in order to keep the /development machine/
going.
I am an open source enthusiast, so when I am able in my free time (or
paid time if it is an open source business project) I like to contribute
to open source projects and discover new ones. I believe in the added
values that open source software (and especially free software) can give
respect to closed ones. My dream job would be to contribute to one of
the many open source I like full time! 🤩
I'm not only into programming though! I like to practice sport. In
particular, I am a black belt Judo and I teach it at the local dojo were
I live. I know how to ski from when I was I child, and if possible I
enjoy passing some time in the middle of mountains to admire the beauty
of nature in winter. Finally, I like to play tabletops games and I enjoy
things like table football, billiards, bowling (although I suck at the
last one 😅).
If you are interested in getting in touch with me (or to get my resume)
you can send me an e-mail (I prefer this way) at
[[mailto:davide+bitdispenser@poldebra.me][davide@poldebra.me]] or you
can contact me through my
[[https://www.linkedin.com/in/davidepolonio/][LinkedIn]] profile.
** Why this website and this blog
:PROPERTIES:
:CUSTOM_ID: why-this-website-and-this-blog
:END:
As I have already written at the beginning of [my first blog post]({{<
ref “/hello-world.md” >}}) I liked the idea of a place where I can share
my thoughts regarding technologies, personal experiences and what not.
This blog is open source, and all my content is licensed under CC BY-SA
4.0. The repository can be found either on
[[https://git.poldebra.me/polpetta/bitdispenser.dev][my personal Gitea instance]]
or on
[[https://github.com/Polpetta/bitdispenser.dev][Github (mirrored)]].

6
org/index.org Normal file
View File

@ -0,0 +1,6 @@
#+TITLE: Bitdispenser homepage
#+AUTHOR: Davide Polonio
Welcome
cmdfklvdfm

166
org/posts/hello-world.org Normal file
View File

@ -0,0 +1,166 @@
If you are reading this it means either you are trying to understand who
I am (are you a recruiter by any chance?) or you are simply bored. In
any way, this is the very first blog post, and it means you have come to
the end of the road (since this is the beginning). I always loved to
have a place where I can write down my thoughts about a new technology,
a framework or what I did in order to achieve a goal in a hobby project.
That is not all though: I was searching for a place where I was also
able to express my ideas regarding modern dilemmas like privacy issues
and decentralization. I am not the kind of guy who likes social
networks, so I never had the chance to express them. Until now.
** How this blog is built
:PROPERTIES:
:CUSTOM_ID: how-this-blog-is-built
:END:
*** The website
:PROPERTIES:
:CUSTOM_ID: the-website
:END:
This blog is a very simple [[https://gohugo.io/][Hugo]] website. If you
do not know what it is, its basically a static website generator. It
takes Markdown documents and converts them in HTML pages. If you applied
a specific theme then it builds the page according to that theme. Hugo
has a huge selection of themes in its
[[https://themes.gohugo.io/][dedicated themes page]], so basically
picking up one and starting from there is very simple. If you are
curious about how I made it, you can check out the source at my
[[https://git.poldebra.me/polpetta/bitdispenser.dev][personal git server instance]],
where I started hosting my code when
[[https://news.microsoft.com/announcement/microsoft-acquires-github/][Microsoft bought Github]]
(you can find a copy of the repository on Github too, visiting
[[https://github.com/Polpetta][my profile]]). I have simply picked up
the simpler and cleanest blog theme out there, following Ludwig Mies van
der Rohe's idea:
#+begin_quote
less is more
#+end_quote
It will be a success if more than two people actually starts reading
what I write here, at least I want them to read this blog without having
their eyes bleeding with an extravagant color combination.
Last but not least, the website icon is literally the Team Fortress 2
dispenser (one of the games I love and one of the first game I started
playing online as a kid when a decent connection at home was finally
available), taken from the RED team and generated via a
[[https://realfavicongenerator.net/][favicon generator]]. The name of
the website partially came out from that, and from the fact that
basically every website is a dispenser of bits, and it is only thanks to
our beloved browsers we are able to actually “consume” what is
distributed in the first place.
#+CAPTION: Engineer class with a dispenser. Thanks to [[https://wiki.teamfortress.com][Team Fortress Wiki]] for providing the image I shamelessly downloaded from them
#+NAME: fig:Engineer with his dispenser
[[../media/hello-world/engiwithdispenser.png]]
I hope Valve will not sue me for taking that asset as my website
favicon. I swear I will change it, a day. Pinky promise.
*** Hosting
:PROPERTIES:
:CUSTOM_ID: hosting
:END:
The real problem of hosting a website now day is not how to build it (as
you can see) but /where/ you can host it. There are plenty of cloud
provides: AWS, Google Cloud, DigitalOcean, Scaleway, etc... every day
there is a new one popping up. They all offers the possibility to host
your website pretty easily, especially if the website is a static one
like this (e.g. using a S3 bucket). Since I like challenges and I also
like to learn new stuff, I though that hosting the website in this way
was boring. At the same time, I wanted to have a good uptime and to not
meddle too much under the hood. My (dream) requirements were:
- always up
- good response time
- possibility to host as much data as I want
- using a hosting free as in beer and (possibly) that could use free as
in freedom technologies
Now, reading this I imagine you are thinking I am going crazy, and maybe
I am, but that is not the case. In fact, multiple weeks prior to writing
this article, while trying to kill the boredom caused by COVID-19
lockdown, I discovered [[https://ipfs.io/][IPFS]]. I already heard of it
at University, but I never bothered too much to understand what was
about. I though “well, it surely is some sort of filesystem”. I was
somewhat right, but not the way I thought.
IPFS acts like a peer-to-peer network, where nodes hash the content they
want to share to let other nodes grab it. You can grab this content
using your local node or using one of the many available gateways. Nodes
can “pin” a file too, in order to keep it locally and to serve it to
other nodes. If a file gets pinned by different nodes and gains traction
it basically can not be deleted from the web.
**** Choosing the right tools
:PROPERTIES:
:CUSTOM_ID: choosing-the-right-tools
:END:
Surely, as
[[https://docs.ipfs.io/how-to/websites-on-ipfs/multipage-website/][the IPFS documentation describes]],
I could have done it by myself. But there is a but. Holding all the
infrastructure manually means sacrifice the “always up” and “good
response time” thingy for two simple motives:
1. the
[[https://git.poldebra.me][server where I host my drafts & codebase]]
is hosted on a very small machine (2GB of RAM and 2vCPU), very easy
to kill with the slightest of loads
2. in order to achieve a good response time, a CDN or some sort of
caching is necessary (even if the application is stored in a
distributed file system)
#+CAPTION: Fleek website, that I used to automate my deployment on IPFS and my DNS update
#+NAME: fig:The fleek website screenshot
#+ATTR_HTML: :width 600px
[[../media/hello-world/fleek.png]]
Finally, in order to achieve full automation with DNS updates, I would
have needed to implement and use NameCheap APIs (currently it is the DNS
provider I use for most of my websites). “What's the difficulty?” one
would ask.
[[https://www.namecheap.com/support/api/intro/][Here is the official documentation]],
and even if the APIs look promising, while studying them my will to live
decreased a little bit, and so I decided that if I wanted to get up and
running with less maintenance as possible, with a good uptime while
having the maximum automation possible I needed to rely on a dedicated
service. Luckily [[https://fleek.co][fleek.co]] was what I was searching
for. They currently provide the possibility to buy a domain from their
website, giving them all the hassle of updating the new website on IPFS,
distributing it, refreshing a very possible CDN and finally to update
the DNS records accordingly. This, as you can imagine, provides multiple
benefits:
- I do not have to care about my very little dev machine getting hugged
to death by request in the remote case any of my posts get any
attention
- I do not have to focus on automating the process someone else has
already done for me
- I can focus on writing posts after dinner instead of scratching my
head trying to understand why the website does not load/the DNS is not
properly updated
The only downside to this approach is that Fleek provides limitations on
how much data and bandwidth you can host. At the time of writing, you
can only host up to 3GB (enough for this website) and have a 50GB
bandwidth (that is fine for now) for the free version.
[[https://fleek.co/pricing/][Upgrading you account]] to one of the
available plans give you extra space and bandwidth.
** Future improvements
:PROPERTIES:
:CUSTOM_ID: future-improvements
:END:
For sure, this is only the beginning. Having an automatic workflow of
spell check, deployment and release would be the first milestone. Future
features for this website could be an automatic posting of every new
article on a dedicated Mastodon bot, so that people can possibly discuss
about my thoughts on the fediverse, a decentralized network.
To conclude, the frequency of this blog will be...whenever I have time
to post :grin: Of course I need content before posting something, and
this require some time for myself for experimenting with new
technologies and learning new stuff, so I do not expect very much
posting, but only time will tell!

272
org/posts/songlify-1.org Normal file
View File

@ -0,0 +1,272 @@
Some time passed since the last article I wrote there. A lot of stuff
happened meanwhile, especially with COVID, but here we are again. While
busy dealing with the mess of real life tasks, three months ago I
started to write a little bot for Telegram in Rust. It is a simple one,
but I consider the journey interesting and worth of writing it down. If
I add new features worth mentioning I will start a series about it,
maybe.
** Telegram bots
:PROPERTIES:
:CUSTOM_ID: telegram-bots
:END:
Telegram bots are not something new to me and nowadays are pretty much
easy to make, so I consider them like a gym where to try out new
technologies and experiment with stuff. I wrote plentiful of them, some
of those are open source like for example
[[https://github.com/Augugrumi/TorreArchimedeBot]] (which is currently
broken 😭) that was useful when going to University, because it scraped
the university free room web page and from there it was able to tell you
which rooms where without any lessons and for how much time, allowing
you to easily find a place where to study with your mates (yep, we
didn't like library too much).
#+CAPTION: A screenshot of TorreArchimedeBot in action.
#+NAME: fig:A screenshot of TorreArchimedeBot in action.
#+ATTR_HTML: :width 600px
[[../media/songlify/telegramscreen.png]]
Also another one bot worthy of mention is
[[https://github.com/Polpetta/RedditToTelegram]], that allowed our D&D
group to receive push notifications of our private Subreddit in our
Telegram group.
As you can see, all of these bots are quite simple, but they have the
added value of teaching you some new programming concepts, technologies
or frameworks that can be later applied in something that can be more
production environment.
** Rust
:PROPERTIES:
:CUSTOM_ID: rust
:END:
I started to approach Rust many years ago (I do not remember exactly
when). First interaction with it was quite interesting to say at least:
there were way less compiler features (for example now the compiler is
able to understand object lifetime at compile time most of the time
alone, without specifying them) that made it a... /not-so-pleasant
programming experience/. It had potential thought, so by following Rust
news I picked it up last year again, noticing that now it has improved a
lot and it is more pleasant to write. Meanwhile, also JetBrains
developed a good support for IntelliJ, so now it is even possible to
debug and perform every operation directly from your IDE UI.
** Making the two worlds collide: Rust + Telegram
:PROPERTIES:
:CUSTOM_ID: making-the-two-worlds-collide-rust-telegram
:END:
One of the features I wanted to learn this time regarding Rust was the
asynchronous support it offers. Rust started to have =async= support
with [[https://tokio.rs/][Tokio]] framework, and recently the Rust team
started to build the asynchronous functionality inside Rust itself. Even
if in the first steps, it looks promising and the idea of a low-level
language, without GC, with automatic memory management and so much
safety having asynchronous support is exciting to me! 🥳 So the only
option left, at this point, was to start messing around with it. I
started by picking up one of the many frameworks that provides a layer
for the Telegram APIs,
[[https://github.com/teloxide/teloxide][Teloxide]]. In particular, as
you can see from its /README/, one of the examples starts by using
=#[tokio:main]= macro:
#+begin_src rust
use teloxide::prelude::*;
#[tokio::main]
async fn main() {
teloxide::enable_logging!();
log::info!("Starting dices_bot...");
let bot = Bot::from_env().auto_send();
teloxide::repl(bot, |message| async move {
message.answer_dice().await?;
respond(())
})
.await;
}
#+end_src
This was the reason I picked it up, given that it looked the most
promising by the time I started the project.
*** Building /Songlify/
:PROPERTIES:
:CUSTOM_ID: building-songlify
:END:
So, after choosing what was going to use to build the bot, I needed a
/reason/ to build it.
With my friends we usually share a lot of songs (via Spotify links), so
I thought it was a good idea to build a bot around it. I integrated a
Spotify API library in it and started hacking up a bot.
#+begin_quote
⚠ Note that at the time of writing I have just noticed that the library
I use for speaking with Spotify,
[[https://crates.io/crates/aspotify][aspotify]] has been deprecated in
favour of [[https://crates.io/crates/rspotify][rspotify]]
#+end_quote
The first bot version was something very simple, and it was a
single-file program with nothing very fancy (I have written it in a
night):
#+begin_src rust
use crate::SpotifyURL::Track;
use aspotify::{Client, ClientCredentials};
use teloxide::prelude::*;
enum SpotifyURL {
Track(String),
}
fn get_spotify_entry(url: &str) -> Option<SpotifyURL> {
if url.contains("https://open.spotify.com/track/") {
let track_id = url.rsplit('/').next().and_then(|x| x.split('?').next());
return match track_id {
Some(id) => Some(SpotifyURL::Track(id.to_string())),
None => None,
};
}
return None;
}
struct TrackInfo {
name: String,
artist: Vec<String>,
}
async fn get_spotify_track(spotify: Box<Client>, id: &String) -> Option<TrackInfo> {
match spotify.tracks().get_track(id.as_str(), None).await {
Ok(track) => Some(TrackInfo {
name: track.data.name,
artist: track.data.artists.iter().map(|x| x.name.clone()).collect(),
}),
Err(_e) => None,
}
}
#[tokio::main]
async fn main() {
teloxide::enable_logging!();
log::info!("Starting Songlify...");
let bot = Bot::from_env().auto_send();
teloxide::repl(bot, |message| async move {
let spotify_creds =
ClientCredentials::from_env().expect("CLIENT_ID and CLIENT_SECRET not found.");
let spotify_client = Box::new(Client::new(spotify_creds));
log::info!("Connected to Spotify");
let text = message.update.text().and_then(get_spotify_entry);
match text {
Some(spotify) => match spotify {
Track(id) => {
let track_info = get_spotify_track(spotify_client, &id).await;
match track_info {
Some(info) => {
let reply = format!(
"Track information:\n\
Track name: {}\n\
Artists: {}",
info.name,
info.artist.join(", ")
);
Some(message.reply_to(reply).await?)
}
None => None,
}
}
},
None => None,
};
respond(())
})
.await;
log::info!("Exiting...");
}
#+end_src
As you can see, basically every time a request arrived to the bot, login
to Spotify was performed and track information and name retrieved from
there. Of course this was only the beginning (also you can “appreciate”
the number of nested blocks there...). Now the bot supports albums and
playlists too, with the possibility to go through each song in the
playlist and collect general information such as how many artists are in
that playlist, how many songs and other little information like that. If
you see the
[[https://git.poldebra.me/polpetta/Songlify][bot repository]] you can
see now that Spotify functions live in a separate module.
**** Packaging and distribution
:PROPERTIES:
:CUSTOM_ID: packaging-and-distribution
:END:
The obvious choice for a software like that was to incorporate it into a
OCI image. I wrote a very simple Dockerfile that, once the program was
built, took the artifact and using the
[[https://docs.docker.com/develop/develop-images/multistage-build/][multi-stage Docker build functionality]]
and put it into a separate container, in order to avoid having build
dependencies inside the final image. I used the images distributed by
the [[file:gcr.io/distroless/][Distroless project]] (you can find the
source on their
[[https://github.com/GoogleContainerTools/distroless][Github repository]])
in order to obtain the smallest possible image. The final result?
#+begin_src txt
λ ~/Desktop/git/songlify/ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test/test latest 8ac7a7018719 5 seconds ago 34MB
<none> <none> 4bc7fb0699e0 12 seconds ago 1.53GB
rust 1.56.1-slim-bullseye d3e070c5ffa7 6 weeks ago 667MB
gcr.io/distroless/base latest-amd64 24787c1cd2e4 52 years ago 20.2MB
#+end_src
A part from the 52 years old image pulled from =gcr=, you can see that
=test/test= (actually Songlify) is only of *34MB*. Not much if you
consider that inside that image there are shared dynamic libraries to
make the executable able to run, which by default weights 20MB. A plus
of these images is that they do not run as root user and they do not
have any shell of bash integrated, making a possible surface attack
smaller (not that Docker is secure anyway...). I upload the images on
Docker Hub, where you can find them here
[[https://hub.docker.com/r/polpetta/songlify]]
Finally, to run the bot I use a very simple docker-compose definition,
that can be found in my
[[https://git.poldebra.me/polpetta/server-dotfiles/src/commit/7e7e1780b2db45f475510c49bf1a2f9e76c4c166/songlify/docker-compose.yml][server-dotfiles repository]].
This allows me to easily upgrade the bot by just changing the version
and running =docker-compose up -d=.
**** Plans for the future
:PROPERTIES:
:CUSTOM_ID: plans-for-the-future
:END:
Currently I work on the bot only when I feel like I wanna add something.
An interesting feature to add could be to insert a persistence layer
(using a database for instance) and add various stats (which is the most
shared song? Who /is that guy/ that shares the most songs in a group?).
Persistence can be achieved quite easily by using
[[https://diesel.rs/][Diesel]], an ORM compatible with various
databases.
Another cool feature could be to add the /inline bot/ functionality,
where you can search for songs directly in Spotify. Since I currently
have a domain available for that I could set it up for receive web-hook
notifications, instead of performing polling like the bot is currently
doing (one requisite for inline bots is indeed to receive web-hooks).
There are platforms like Heroku where you could make the bot run, but
currently I prefer to use my box since it gives me more flexibility.
Experimenting with Heroku could lead to cool results though 😏.
Finally, link translation could be something very useful. I have a
friend that does not use Spotify but prefers to listen to music via
YouTube. So an interesting feature would be, given a Spotify link, to
“convert” it into a YouTube link and, of course, /vice-versa/. This
could lead to translate playlists and albums too into YouTube-based
playlists, which of course could be very useful if you are trying to
avoid the infamous /vendor lock-in/, in this case being stuck with
Spotify because all you music collection, saved songs, etc is there.

0
org/rss.org Normal file
View File

739
package-lock.json generated
View File

@ -1,739 +0,0 @@
{
"name": "bitdispenser.dev",
"version": "0.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bitdispenser.dev",
"version": "0.0.1",
"license": "CC-BY-SA-4.0",
"devDependencies": {
"markdownlint-cli2": "0.0.10"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
"integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "2.0.4",
"run-parallel": "^1.1.9"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@nodelib/fs.stat": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz",
"integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/@nodelib/fs.walk": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz",
"integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==",
"dev": true,
"dependencies": {
"@nodelib/fs.scandir": "2.1.4",
"fastq": "^1.6.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
"dev": true,
"dependencies": {
"path-type": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/entities": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
"integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==",
"dev": true
},
"node_modules/fast-glob": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz",
"integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.0",
"merge2": "^1.3.0",
"micromatch": "^4.0.2",
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fastq": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz",
"integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==",
"dev": true,
"dependencies": {
"reusify": "^1.0.4"
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/globby": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz",
"integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==",
"dev": true,
"dependencies": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.1.1",
"ignore": "^5.1.4",
"merge2": "^1.3.0",
"slash": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ignore": {
"version": "5.1.8",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
"dev": true,
"engines": {
"node": ">= 4"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/linkify-it": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.2.tgz",
"integrity": "sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ==",
"dev": true,
"dependencies": {
"uc.micro": "^1.0.1"
}
},
"node_modules/markdown-it": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-11.0.0.tgz",
"integrity": "sha512-+CvOnmbSubmQFSA9dKz1BRiaSMV7rhexl3sngKqFyXSagoA3fBdJQ8oZWtRy2knXdpDXaBw44euz37DeJQ9asg==",
"dev": true,
"dependencies": {
"argparse": "^1.0.7",
"entities": "~2.0.0",
"linkify-it": "^3.0.1",
"mdurl": "^1.0.1",
"uc.micro": "^1.0.5"
},
"bin": {
"markdown-it": "bin/markdown-it.js"
}
},
"node_modules/markdownlint": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.21.1.tgz",
"integrity": "sha512-8kc88w5dyEzlmOWIElp8J17qBgzouOQfJ0LhCcpBFrwgyYK6JTKvILsk4FCEkiNqHkTxwxopT2RS2DYb/10qqg==",
"dev": true,
"dependencies": {
"markdown-it": "11.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/markdownlint-cli2": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.0.10.tgz",
"integrity": "sha512-tuWKncGgg0ic9YIWzPu1w2upoGNF/CQwb+CMzJmB8LMM2MhFKTUQSDyUt0kM022qs1R7KjxFXG7y5FBmqGVhUQ==",
"dev": true,
"dependencies": {
"globby": "~11.0.1",
"markdownlint": "~0.21.0",
"markdownlint-cli2-formatter-default": "~0.0.1",
"markdownlint-rule-helpers": "~0.12.0",
"micromatch": "~4.0.2",
"strip-json-comments": "~3.1.1",
"yaml": "~1.10.0"
},
"bin": {
"markdownlint-cli2": "markdownlint-cli2.js"
},
"engines": {
"node": ">=10.17.0"
}
},
"node_modules/markdownlint-cli2-formatter-default": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.2.tgz",
"integrity": "sha512-jIz1X3SIC8sX4NDFqQFUXL+JEtfnDoN4i+xocEu+etcxGX455pHb6sx86f/yVk4mKJ2o7aNe2ydSx9an22BfBg==",
"dev": true,
"peerDependencies": {
"markdownlint-cli2": ">=0.0.4"
}
},
"node_modules/markdownlint-rule-helpers": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.12.0.tgz",
"integrity": "sha512-Q7qfAk+AJvx82ZY52OByC4yjoQYryOZt6D8TKrZJIwCfhZvcj8vCQNuwDqILushtDBTvGFmUPq+uhOb1KIMi6A==",
"dev": true
},
"node_modules/mdurl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=",
"dev": true
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/micromatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"dev": true,
"dependencies": {
"braces": "^3.0.1",
"picomatch": "^2.2.3"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/picomatch": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz",
"integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==",
"dev": true,
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"dev": true,
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"queue-microtask": "^1.2.2"
}
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
"dev": true
},
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true,
"engines": {
"node": ">= 6"
}
}
},
"dependencies": {
"@nodelib/fs.scandir": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
"integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "2.0.4",
"run-parallel": "^1.1.9"
}
},
"@nodelib/fs.stat": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz",
"integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==",
"dev": true
},
"@nodelib/fs.walk": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz",
"integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==",
"dev": true,
"requires": {
"@nodelib/fs.scandir": "2.1.4",
"fastq": "^1.6.0"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
},
"array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
"dev": true,
"requires": {
"path-type": "^4.0.0"
}
},
"entities": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
"integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==",
"dev": true
},
"fast-glob": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz",
"integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.0",
"merge2": "^1.3.0",
"micromatch": "^4.0.2",
"picomatch": "^2.2.1"
}
},
"fastq": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz",
"integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==",
"dev": true,
"requires": {
"reusify": "^1.0.4"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
},
"globby": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz",
"integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==",
"dev": true,
"requires": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.1.1",
"ignore": "^5.1.4",
"merge2": "^1.3.0",
"slash": "^3.0.0"
}
},
"ignore": {
"version": "5.1.8",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
"dev": true
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
"is-glob": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"linkify-it": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.2.tgz",
"integrity": "sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ==",
"dev": true,
"requires": {
"uc.micro": "^1.0.1"
}
},
"markdown-it": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-11.0.0.tgz",
"integrity": "sha512-+CvOnmbSubmQFSA9dKz1BRiaSMV7rhexl3sngKqFyXSagoA3fBdJQ8oZWtRy2knXdpDXaBw44euz37DeJQ9asg==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"entities": "~2.0.0",
"linkify-it": "^3.0.1",
"mdurl": "^1.0.1",
"uc.micro": "^1.0.5"
}
},
"markdownlint": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.21.1.tgz",
"integrity": "sha512-8kc88w5dyEzlmOWIElp8J17qBgzouOQfJ0LhCcpBFrwgyYK6JTKvILsk4FCEkiNqHkTxwxopT2RS2DYb/10qqg==",
"dev": true,
"requires": {
"markdown-it": "11.0.0"
}
},
"markdownlint-cli2": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.0.10.tgz",
"integrity": "sha512-tuWKncGgg0ic9YIWzPu1w2upoGNF/CQwb+CMzJmB8LMM2MhFKTUQSDyUt0kM022qs1R7KjxFXG7y5FBmqGVhUQ==",
"dev": true,
"requires": {
"globby": "~11.0.1",
"markdownlint": "~0.21.0",
"markdownlint-cli2-formatter-default": "~0.0.1",
"markdownlint-rule-helpers": "~0.12.0",
"micromatch": "~4.0.2",
"strip-json-comments": "~3.1.1",
"yaml": "~1.10.0"
}
},
"markdownlint-cli2-formatter-default": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.2.tgz",
"integrity": "sha512-jIz1X3SIC8sX4NDFqQFUXL+JEtfnDoN4i+xocEu+etcxGX455pHb6sx86f/yVk4mKJ2o7aNe2ydSx9an22BfBg==",
"dev": true,
"requires": {}
},
"markdownlint-rule-helpers": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.12.0.tgz",
"integrity": "sha512-Q7qfAk+AJvx82ZY52OByC4yjoQYryOZt6D8TKrZJIwCfhZvcj8vCQNuwDqILushtDBTvGFmUPq+uhOb1KIMi6A==",
"dev": true
},
"mdurl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=",
"dev": true
},
"merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true
},
"micromatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"dev": true,
"requires": {
"braces": "^3.0.1",
"picomatch": "^2.2.3"
}
},
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
},
"picomatch": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz",
"integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==",
"dev": true
},
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true
},
"reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"dev": true
},
"run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
"requires": {
"queue-microtask": "^1.2.2"
}
},
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
},
"uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
"dev": true
},
"yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true
}
}
}

View File

@ -1,19 +0,0 @@
{
"name": "bitdispenser.dev",
"version": "0.0.1",
"description": "Personal Hugo website",
"main": "none.js",
"scripts": {
"lint": "markdownlint-cli2 content/",
"spellchecker": "bash tools/spellchecker.sh en"
},
"repository": {
"type": "git",
"url": "git+https://git.poldebra.me/polpetta/bitdispenser.dev.git"
},
"author": "Polonio Davide <davide@poldebra.me>",
"license": "CC-BY-SA-4.0",
"devDependencies": {
"markdownlint-cli2": "0.0.10"
}
}

80
publish.el Executable file
View File

@ -0,0 +1,80 @@
#!/usr/bin/env -S emacs -x
(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-initialize)
;; Install ox-rss if not present
(unless (package-installed-p 'ox-rss)
(package-refresh-contents)
(package-install 'ox-rss))
(require 'ox-publish)
(require 'ox-rss)
(setq org-publish-project-alist
'(("blog-posts"
:base-directory "org/posts/"
:base-extension "org"
:publishing-directory "dist/p/"
:recursive t
:publishing-function org-html-publish-to-html
:org-html-preamble nil
:html-self-link-headlines nil
:org-export-with-title nil
:org-export-with-toc nil
:org-export-with-section-numbers nil
:org-export-with-properties nil
:org-export-with-tags nil
:org-export-with-date t
:org-export-with-time-stamp-file t
:org-export-with-tables t
:org-html-table-use-header-tags-for-first-column nil
)
("blog-pages"
:base-directory "org/"
:base-extension "org"
:publishing-directory "dist/"
:exclude "posts/\\|rss\\.org"
:recursive t
:publishing-function org-html-publish-to-html
:org-html-preamble nil
:html-self-link-headlines nil
:org-export-with-title nil
:org-export-with-toc nil
:org-export-with-section-numbers nil
:org-export-with-tags nil
:org-html-table-use-header-tags-for-first-column nil
)
("blog-media"
:base-directory "org/media"
:base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf"
:publishing-directory "dist/media/"
:recursive t
:publishing-function org-publish-attachment
)
("blog-static"
:base-directory "static/"
:base-extension "*"
:publishing-directory "dist/media/"
:recursive nil
:publishing-function org-publish-attachment
)
("blog-rss"
:base-directory "org/posts/"
:base-extension "org"
:recursive nil
:exclude ".*"
:include ("../rss.org")
:publishing-function (org-rss-publish-to-rss)
:publishing-directory "dist/"
:with-toc nil
:section-numbers nil
:html-link-use-abs-url t
:html-link-home "https://bitdispenser.dev/"
:title "Bitdispenser RSS"
)
("blog"
:components ("blog-posts" "blog-pages" "blog-media" "blog-static" "blog-rss"))
))
(org-publish "blog" t)

7
templates/post.org Normal file
View File

@ -0,0 +1,7 @@
#+options: html-link-use-abs-url:nil html-postamble:nil html-preamble:nil
#+options: html-scripts:t html-style:nil html5-fancy:t tex:t
#+options: tags:t title:nil toc:nil num:0 date:t
#+html_doctype: html5
#+html_container: div
#+html_head: <link rel="stylesheet" type="text/css" href="css/style.css" />
#+creator: Davide Polonio

@ -1 +0,0 @@
Subproject commit 2dc35c5c6a52168a3a7b35c5ad51209f40a851cf