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
This commit is contained in:
parent
5717b5be9b
commit
06e0e6afe8
18
.drone.yml
18
.drone.yml
@ -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
|
10
.fleek.json
10
.fleek.json
@ -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
86
.gitignore
vendored
@ -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
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "themes/hermit"]
|
||||
path = themes/hermit
|
||||
url = https://github.com/Track3/hermit.git
|
@ -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
|
@ -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
16
Makefile
Normal 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/
|
@ -1,6 +0,0 @@
|
||||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
---
|
||||
|
73
config.toml
73
config.toml
@ -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 = ' · <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
|
@ -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).
|
@ -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!
|
@ -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.
|
@ -1,5 +0,0 @@
|
||||
---
|
||||
Title: "Riguardo l'Autore"
|
||||
---
|
||||
|
||||
Prova 😀
|
@ -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
60
org/about-me.org
Normal 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
6
org/index.org
Normal file
@ -0,0 +1,6 @@
|
||||
#+TITLE: Bitdispenser homepage
|
||||
#+AUTHOR: Davide Polonio
|
||||
|
||||
Welcome
|
||||
|
||||
cmdfklvdfm
|
166
org/posts/hello-world.org
Normal file
166
org/posts/hello-world.org
Normal 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
272
org/posts/songlify-1.org
Normal 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
0
org/rss.org
Normal file
739
package-lock.json
generated
739
package-lock.json
generated
@ -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
|
||||
}
|
||||
}
|
||||
}
|
19
package.json
19
package.json
@ -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
80
publish.el
Executable 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
7
templates/post.org
Normal 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
|
Loading…
x
Reference in New Issue
Block a user