Coder avec Crystal pour ne pas perdre la boule, première partie
2020-05-20
Dernière modification le 2022-04-01«Coder avec Crystal pour ne pas perdre la boule» est le titre d'une conférence que j'aurais eu l'honneur de présenter au BreizhCamp ainsi qu'à Devoxx France. Ces conférences ont malheureusement été annulées suite à la situation liée au Covid-19.
Voici donc la première partie d'une petite série sur ce que je souhaitais aborder.
Avant de démarrer...
Les choses comme ceci
ou
cela
sont des morceaux de code ou des commandes.
Quand vous voyez ❯
dans un bloc, c'est que la suite sur la même ligne est une commande à exécuter. Les lignes qui suivent sont le résultat.
Crystal ?
Crystal est un langage généraliste inspiré de Ruby et qui cible des performances proches du C. Il est bien placé dans les étoiles sur GitHub (14.8k) entre Scala (12.7k) et Elixir (16.9k) et est accompagné d'une petite communauté bien productive.
La première version officielle, 0.1.0, est sortie en juin 2014.
Nous en sommes actuellement à la version 0.34.0, sortie le 6 avril 2020. Nous nous rapprochons de la version 1.0.
Installation
Le site officiel contient tout ce qu'il faut pour installer Crystal sur plusieurs types de distributions Linux, sur MacOS, ainsi que Windows Subsystem for Linux.
J'utilise Crystal sur Archlinux et sur MacOS, sans problème.
Une fois installé, vous aurez accès à 2 commandes: crystal
et shards
.
Le wiki contient une partie sur le support des éditeurs. Des modules additionnels existent pour certains, regroupés dans l'organisation GitHub crystal-lang-tools.
J'utilise Sublime Text avec le module adéquat.
Les commandes
crystal
Voici quelques commandes disponibles avec crystal
:
crystal init TYPE (DIR | NAME DIR)
permet de démarrer un projet (on voit ça un tout petit peu plus loin).crystal spec
permet de lancer les tests.crystal docs
permet de générer la documentation.
Essayez crystal help
pour découvrir les autres possibilités.
shards
shards
permet d'aller un peu plus loin. On utilise shards
pour gérer les dépendances et d'autres choses.
Voici quelques commandes disponibles avec shards
:
shards build
permet de construire le projet en cours.shards install
permet d'installer les dépendances définies dans le fichier "shard.yml" du projet (on y vient).shards update
permet de mettre à jour les dépendances.
Essayez shards help
pour découvrir les autres possibilités.
Bonjour le monde
Aller, on code (un peu) ! C'est le moment du classique Hello, world!
Créez un fichier "hello.cr" et ajoutez-y le contenu suivant:
puts "Hello, world!"
Ensuite, un coup de crystal run hello.cr
et le tour est joué.
Félicitations, vous avez fait votre premier programme en Crystal !
Démarrer un vrai projet
Nous allons utiliser crystal init
. Cette commande permet de démarrer une application ou une librairie.
Voici un exemple pour démarrer une application: crystal init app myapp
. Le résultat est le suivant:
❯ crystal init app myapp
create /home/sweethome/myapp/.gitignore
create /home/sweethome/myapp/.editorconfig
create /home/sweethome/myapp/LICENSE
create /home/sweethome/myapp/README.md
create /home/sweethome/myapp/.travis.yml
create /home/sweethome/myapp/shard.yml
create /home/sweethome/myapp/src/myapp.cr
create /home/sweethome/myapp/spec/spec_helper.cr
create /home/sweethome/myapp/spec/myapp_spec.cr
Initialized empty Git repository in /home/sweethome/myapp/.git/
Crystal a créé pour nous un répertoire "myapp" avec tous les fichiers pour bien démarrer. Cerise sur le gâteau, c'est un dépôt git.
Jetons un œil au fichier "shard.yml", qui contient les métadonnées liées à l'application:
name: myapp
version: 0.1.0
authors:
- Your Name <email@example.com>
targets:
myapp:
main: src/myapp.cr
crystal: 0.34.0
license: MIT
Nous pouvons construire notre application avec shards build
dans le répertoire du projet, pour produire notre binaire myapp
dans le répertoire bin/.
Nous pouvons exécuter ce fichier
# Dans ~/myapp
❯ cd bin
❯ ./myapp
Il ne se passe rien... Pour le moment !
Les mains dans le camboui
Point d'entrée de l'application
Le code de l'application se trouve dans le dossier "src/", avec un unique fichier "myapp.cr":
# TODO: Write documentation for `Myapp`
module Myapp
VERSION = "0.1.0"
# TODO: Put your code here
end
C'est le point d'entrée de notre programme. Ce fichier reste souvent assez minimal.
Nous remarquons module Myapp
dans . Un module permet de regrouper un ensemble d'objets, fonctions... Un module peut contenir d'autres modules, des classes, des structures...
Un programme contient presque toujours une sous-commande pour afficher l'aide et la version. Commençons par ça!
Nous allons regrouper nos commandes dans un sous-module "Commands".
Afficher l'aide
Ainsi qu'une première commande pour l'aide, dans le fichier "src/commands/help.cr", avec le contenu suivant:
module Myapp
module Commands
class Help
def self.run
puts <<-HELP
myapp <command> [<options>]
TODO
HELP
exit
end
end
end
end
Nous créons sont une classe "Help" dans le module "Commands" dans le module "Myapp".
Cette classe contient une unique méthode run
. La déclaration d'une méthode dans une classe se fait avec le mot clé def
. Ici nous utilisons def self.run
et non def run
pour créer une méthode de classe et non d'instance.
Le contenu de l'aide est une chaine de caractères. Ici, on remarque des choses bizarres <<-HELP
et HELP
. C'est un type de chaîne spécial, pratique pour délimiter une chaîne sur plusieurs lignes et identifier le début et la fin facilement. Voici ce que ça donne dans mon éditeur:
Afficher la version
La suivante, pour afficher la version, dans le fichier "src/commands/version.cr", avec le contenu suivant:
module Myapp
module Commands
class Version
def self.run
puts "myapp v#{VERSION}"
exit
end
end
end
end
Nous avons ici de l'interpolation dans la chaîne de caractère avec l'utilisation de #{...}
.
VERSION
vient du module Myapp
englobant.
Nous ne connaissons pas exit
, qui permet de terminer l'exécution du programme.
Brancher le tout
Les fichiers que nous venons de créer sont bien jolis, mais ils ne servent à rien. Il faut les relier à notre programme.
Nous allons créer le fichier "src/cli.cr", avec le contenu suivant:
require "option_parser"
require "./commands/*"
module Myapp
DEFAULT_COMMAND = "help"
def self.run
OptionParser.parse(ARGV) do |opts|
opts.unknown_args do |args, options|
command = args[0]? || DEFAULT_COMMAND
case command
when "help"
Commands::Help.run
when "version"
Commands::Version.run
else
puts "Unknown command: #{command}"
end
end
end
end
end
Ici, pas de nouvelle classe ! On peut découper notre module en plusieurs fichiers et les inclure (require
) dans le module principal.
Nous utilisons donc require "./commands/*"
afin de charger toutes nos commandes.
Et nous utilisons notre premier module venant de la librairie standard Crystal option_parse
. Ce module permet de répondre aux arguments et options passées à notre programme. Rien de très spécial ici, shards utilise la même technique.
Nous voyons ici comment utiliser les classes du module Commands. Dans ce module, les classes Help
et Version
sont accessibles via Commands::Help
et Commands::Version
. Comme les méthodes run
sont des méthodes de classe et non d'instance, on peut les appeler directement. Pas besoin de parenthèse car pas d'arguments.
Enfin, nous devons modifier "src/myapp.cr", notre point d'entrée:
require "./cli"
# TODO: Write documentation for `Myapp`
module Myapp
VERSION = "0.1.0"
self.run
end
On remarque require "./cli"
qui permet d'inclure le fichier que nous avons créé juste avant et qui définissait une méthode run
au niveau du module Myapp
. Avec self.run
, nous exécutons notre programme !
On vérifie que ça fonctionne
Exécutez shards build
à la racine du projet. Ensuite, cd bin/
puis lançons notre application:
Affichons l'aide:
❯ ./myapp help
myapp <command> [<options>]
TODO
Affichons la version:
❯ ./myapp version
myapp v0.1.0
Avec la commande par défaut:
❯ ./myapp
myapp <command> [<options>]
TODO
Et avec une commande inconnue:
~/myapp/bin
❯ ./myapp pouet
Unknown command: pouet
À bientôt pour la suite
Nous avons fait nos premiers pas avec Crystal.
Rendez-vous bientôt pour la suite et démarrer réellement notre application !
Le code est disponible à cette adresse, au tag partie-01.
Références
- Le site officiel: crystal-lang.org.
- La référence pour démarrer ainsi que la documentation pour les API et la librairie standard.
- Shards (gestionnaire de dépendances) et une base de données de Shards.
- crystal-lang-tools avec des outils et support pour les éditeurs.
- Une "awesome list" de la communauté Crystal.
- Crystal sur Wikipedia
- Crystal sur "Apprendre X en Y minutes".
- Une vidéo de 2015 par 2 des créateurs de Crystal.
A bientôt pour de nouvelles aventures !