Compare commits
1 Commits
master
...
restructur
Author | SHA1 | Date | |
---|---|---|---|
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
|
# ---> Hugo
|
||||||
# Generated files by hugo
|
# Generated files by hugo
|
||||||
/public/
|
/public/
|
||||||
/resources/_gen/
|
/resources/_gen/
|
||||||
|
/assets/jsconfig.json
|
||||||
|
hugo_stats.json
|
||||||
|
|
||||||
# Executable may be added to repository
|
# Executable may be added to repository
|
||||||
hugo.exe
|
hugo.exe
|
||||||
hugo.darwin
|
hugo.darwin
|
||||||
hugo.linux
|
hugo.linux
|
||||||
|
|
||||||
node_modules/
|
# Temporary lock file while building
|
||||||
.hugo_build.lock
|
/.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