# Nicoletta On Crystal ## What? A while back (10 YEARS???? WTH.) I wrote a static site generator. I mean, I wrote one that is large and somewhat popular, called [Nikola](https://github.com/getnikola/nikola) but I also wrote a tiny one called [Nicoletta](https://github.com/ralsina/nicoletta) Why? Because it's a nice little project and it shows the very basics of how to do a whole project. All it does is: * Find markdown files * Build them * Use templates to generate HTML files * Put those in an output folder And that's it, that's a SSG. So, if I wanted a "toy" project to practice new (to me) programming languages, why not rewrite that? And why not write about how it goes while I do it? Hence this. ## So, what's Crystal? It's (they say) "A language for humans and computers". In short: a compiled, statically typed language with a ruby flavoured syntax. And why? Again, why not? ## Getting started I installed it using [curl](https://crystal-lang.org/install/on_ubuntu/) and that got me version 1.8.2 which is the latest at the time of writing this. You can get your project started by running a command: ```shell nicoletta/crystal ✦ > crystal init app nicoletta . create /home/ralsina/zig/nicoletta/crystal/.gitignore create /home/ralsina/zig/nicoletta/crystal/.editorconfig create /home/ralsina/zig/nicoletta/crystal/LICENSE create /home/ralsina/zig/nicoletta/crystal/README.md create /home/ralsina/zig/nicoletta/crystal/shard.yml create /home/ralsina/zig/nicoletta/crystal/src/nicoletta.cr create /home/ralsina/zig/nicoletta/crystal/spec/spec_helper.cr create /home/ralsina/zig/nicoletta/crystal/spec/nicoletta_spec.cr Initialized empty Git repository in /home/ralsina/zig/nicoletta/crystal/.git/ ``` Some maybe interesting bits: * It inits a git repo, with a gitignore in it * Sets you up with a MIT license * Creates a reasonable README with nice placeholders * We get a `shard.yml`with metadata * Source code in `src/` * `spec/` seems to be for tests? Mind you, I still have zero idea about the language :-) This apparently compiles into a do-nothing program, which is ok. Surprisied to see [starship](https://starship.rs/) seems to support crystal in the prompt! ```shell crystal on main [?] is 📦 v0.1.0 via 🔮 v1.8.2 > crystal build src/nicoletta.cr crystal on main [?] is 📦 v0.1.0 via 🔮 v1.8.2 > ls -l total 1748 -rw-rw-r-- 1 ralsina ralsina 2085 may 31 18:15 journal.md -rw-r--r-- 1 ralsina ralsina 1098 may 31 18:08 LICENSE -rwxrwxr-x 1 ralsina ralsina 1762896 may 31 18:15 nicoletta* -rw-r--r-- 1 ralsina ralsina 604 may 31 18:08 README.md -rw-r--r-- 1 ralsina ralsina 167 may 31 18:08 shard.yml drwxrwxr-x 2 ralsina ralsina 4096 may 31 18:08 spec/ drwxrwxr-x 2 ralsina ralsina 4096 may 31 18:08 src/ ``` Perhaps a bit surprising that the do-nothing binary is 1.7MB tho (1.2MB stripped) but it's just 380KB in "release mode" which is nice. ## Learning a Bit of Crystal At this point I will stop and learn some syntax: * How to declare a variable / a literal / a constant * How to do an if / loop * How to define / call a function Because you know, one *has* to know at least that much 😁 There seems to be a decent set of [tutorials at this level.](https://crystal-lang.org/reference/1.8/tutorials/basics/index.html) let's see how it looks. Good thing: this is valid Crystal: ```crystal module Nicoletta VERSION = "0.1.0" 😀 = "Hello world" puts 😀 end ``` Also nice that variables can change type. Having the docs say integers are `int32` and anything else is "for special use cases" is not great. `int32` is small. Also not a huge fan of separate unsigned types. I *hate* the "spaceship operator" `<==>` which "compares its operands and returns a value that is either zero (both operands are equal), a positive value (the first operand is bigger), or a negative value (the second operand is bigger)" ... *hate* it. Numbers have named methods, which is nice. However it randomly shows some weird syntax that has not been seen before. One of these is not like the others: ```crystal p! -5.abs, # absolute value 4.3.round, # round to nearest integer 5.even?, # odd/even check 10.gcd(16) # greatest common divisor ``` Or maybe the `?` is just part of the method name? Who knows! Not me! Nice string interpolation thingie. ```crystal name = "Crystal" puts "Hello #{name}" ``` Why would anyone add an `underscore` method to strings? That's just weird. Slices are reasonable, `whatever[x..y]` uses negative indexes for "from the right". We have truthy values, 0 is truthy, only nil, false and null pointers are falsy. Ok. I *strongly dislike* using `unless` as a keyword instead of `if` with a negated condition. I consider that to be keyword proliferation and cutesy. Methods support overloading. Ok. Ok, I know just enough Crystal to be slightly dangerous. Those feel like **good** tutorials. Short, to the point, give you enough rope to ... make something with rope, or whatever. ## Learning a Bit More Crystal So: errors? Classes? Blocks? How? Classes are [pretty straightforward](https://crystal-lang.org/reference/1.8/syntax_and_semantics/new%2C_initialize_and_allocate.html) ... apparently they are a bit frowned upon for performance reasons because they are heap allocated, but whatevs. Inheritance with method overloading is not my cup of tea but 🤷 Exceptions are [pretty simple](https://crystal-lang.org/reference/1.8/syntax_and_semantics/exception_handling.html) but `begin / rescue / else / ensure / end`? Eek. Also, I find that variables have `nil` type in the `ensure` block confusing. [Requiring files](https://crystal-lang.org/reference/1.8/syntax_and_semantics/requiring_files.html) is not going to be a problem. [Blocks](https://crystal-lang.org/reference/1.8/syntax_and_semantics/blocks_and_procs.html) are interesting but I am not going to try to use them yet. ## Dinner Break I will grab dinner, and then try to implement Nicoletta, somehow. I'll probably fail 😅 ## Implementing Nicoletta The code for nicoletta [is not long](https://github.com/ralsina/nicoletta/blob/master/nicoletta.py) so this should be a piece of cake. No need to have a `main` in Crystal. Things just are executed. First, I need a way to read the configuration. It looks like this: ```yaml TITLE: "Nicoletta Test Blog" ``` That is *technically YAML* so surely there is a crystal thing to read it. In fact, it's in the standard library! This fragment works: ```crystal require "yaml" VERSION = "0.1.0" tpl_data = File.open("conf") do |file| YAML.parse(file) end p! tpl_data ``` And when executed does this, which is correct: ```sh crystal on main [!?] is 📦 v0.1.0 via 🔮 v1.8.2 > crystal run src/nicoletta.cr tpl_data # => {"TITLE" => "Nicoletta Test Blog"} ``` Looks like what I want to store this sort of data is a [Hash](https://crystal-lang.org/reference/1.8/syntax_and_semantics/literals/hash.html) Next step: read templates and put them in a hash indexed by path. Templates are files in `templates/` which look like this: ```