diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 923641b..0000000 --- a/.drone.yml +++ /dev/null @@ -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 diff --git a/.fleek.json b/.fleek.json deleted file mode 100644 index d0ba21d..0000000 --- a/.fleek.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "build": { - "image": "polpetta/fleek:latest", - "command": "git lfs install && git lfs pull && yarn && hugo", - "publicDir": "public", - "environment": { - "HUGO_DISABLELANGUAGES": "it" - } - } -} diff --git a/.gitignore b/.gitignore index d881df6..9fb1c68 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,94 @@ +# ---> macOS +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + # ---> Hugo # Generated files by hugo /public/ /resources/_gen/ +/assets/jsconfig.json +hugo_stats.json # Executable may be added to repository hugo.exe hugo.darwin hugo.linux -node_modules/ -.hugo_build.lock +# Temporary lock file while building +/.hugo_build.lock + +# ---> Emacs +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +# network security +/network-security.data +/rss.xml diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index ff46467..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "themes/hermit"] - path = themes/hermit - url = https://github.com/Track3/hermit.git diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml deleted file mode 100644 index 8d46f62..0000000 --- a/.markdownlint-cli2.yaml +++ /dev/null @@ -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>" - -# 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 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index a88615e..0000000 --- a/Dockerfile +++ /dev/null @@ -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/* \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2b8abdd --- /dev/null +++ b/Makefile @@ -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/ diff --git a/archetypes/default.md b/archetypes/default.md deleted file mode 100644 index 00e77bd..0000000 --- a/archetypes/default.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ .Date }} -draft: true ---- - diff --git a/config.toml b/config.toml deleted file mode 100644 index 64ed254..0000000 --- a/config.toml +++ /dev/null @@ -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 = ' · CC BY-SA 4.0' - 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 \ No newline at end of file diff --git a/content/english/about-me.md b/content/english/about-me.md deleted file mode 100644 index 4b1a9c9..0000000 --- a/content/english/about-me.md +++ /dev/null @@ -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). diff --git a/content/english/posts/hello-world.md b/content/english/posts/hello-world.md deleted file mode 100644 index 41cf0f0..0000000 --- a/content/english/posts/hello-world.md +++ /dev/null @@ -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! diff --git a/content/english/posts/songlify-1.md b/content/english/posts/songlify-1.md deleted file mode 100644 index 9840ffb..0000000 --- a/content/english/posts/songlify-1.md +++ /dev/null @@ -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 (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 -, 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 { - 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, -} - -async fn get_spotify_track(spotify: Box, id: &String) -> Option { - 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 - 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 - - -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. diff --git a/content/italian/riguardo-l-autore.md b/content/italian/riguardo-l-autore.md deleted file mode 100644 index a3ac27c..0000000 --- a/content/italian/riguardo-l-autore.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -Title: "Riguardo l'Autore" ---- - -Prova 😀 diff --git a/layouts/partials/header.html b/layouts/partials/header.html deleted file mode 100644 index 7bfd670..0000000 --- a/layouts/partials/header.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/org/about-me.org b/org/about-me.org new file mode 100644 index 0000000..9141c8e --- /dev/null +++ b/org/about-me.org @@ -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)]]. diff --git a/org/index.org b/org/index.org new file mode 100644 index 0000000..2c44181 --- /dev/null +++ b/org/index.org @@ -0,0 +1,6 @@ +#+TITLE: Bitdispenser homepage +#+AUTHOR: Davide Polonio + +Welcome + +cmdfklvdfm diff --git a/static/content/hello-world/engiwithdispenser.png b/org/media/hello-world/engiwithdispenser.png similarity index 100% rename from static/content/hello-world/engiwithdispenser.png rename to org/media/hello-world/engiwithdispenser.png diff --git a/static/content/hello-world/fleek.png b/org/media/hello-world/fleek.png similarity index 100% rename from static/content/hello-world/fleek.png rename to org/media/hello-world/fleek.png diff --git a/static/content/songlify/telegramscreen.png b/org/media/songlify/telegramscreen.png similarity index 100% rename from static/content/songlify/telegramscreen.png rename to org/media/songlify/telegramscreen.png diff --git a/org/posts/hello-world.org b/org/posts/hello-world.org new file mode 100644 index 0000000..c064903 --- /dev/null +++ b/org/posts/hello-world.org @@ -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! diff --git a/org/posts/songlify-1.org b/org/posts/songlify-1.org new file mode 100644 index 0000000..9733cf2 --- /dev/null +++ b/org/posts/songlify-1.org @@ -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 { + 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, +} + +async fn get_spotify_track(spotify: Box, id: &String) -> Option { + 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 + 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. diff --git a/org/rss.org b/org/rss.org new file mode 100644 index 0000000..e69de29 diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 18e6c8f..0000000 --- a/package-lock.json +++ /dev/null @@ -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 - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 4ad5974..0000000 --- a/package.json +++ /dev/null @@ -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 ", - "license": "CC-BY-SA-4.0", - "devDependencies": { - "markdownlint-cli2": "0.0.10" - } -} diff --git a/publish.el b/publish.el new file mode 100755 index 0000000..6c666ac --- /dev/null +++ b/publish.el @@ -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) diff --git a/templates/post.org b/templates/post.org new file mode 100644 index 0000000..1f3198a --- /dev/null +++ b/templates/post.org @@ -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: +#+creator: Davide Polonio diff --git a/themes/hermit b/themes/hermit deleted file mode 160000 index 2dc35c5..0000000 --- a/themes/hermit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2dc35c5c6a52168a3a7b35c5ad51209f40a851cf