tui_playground_x_feuf_2024_ustensilesnum▔▜▚▘▘▟_erg_┆_wiki

Introduction

Pour ce cours, je voulais être capable de créer une application en ligne de commande, en m’inspirant de programmes comme figlet, cowsay ou encore cmatrix, qui sont des applications graphiques, possédant comme simple but d’être joli et amusant à regarder.

cmatrix

cowsay

figlet

Je m’intéresse aux applications en ligne de commande depuis que j’ai découvert musikcube, en fin 2022 ; c’est un lecteur de musique qui fonctionne intégralement dans le terminal, un des seuls qui est disponible et parfaitement fonctionnel sur windows. Il n’est pas du tout plus pratique qu’un lecteur de musique standard avec une interface graphique, mais pour je ne sait quelle raison, son esthétique m’a tout de suite parlé.

musikcube

Cette fascination soudaine pour les applications en lignes de commandes est liée à ma passion de longue date pour les typographies mono et l’ascii art, ainsi que les regroupements de symbole comme la table unicode et les sites comme emojicombos.com.

ascii art d’hatsune miku

Il y a quelque chose qui me plait graphiquement dans ces glyphes un peu délaissés à l’époque des emojis, dans leur variété et dans leur forme fine. J’ai toujours eu une attirance pour les visuels monochromes ou comportant peu de couleurs, et souvent très peu épais, car utilisant des petits symboles ou des typos de petite taille.

Bash scripting

En partant de ce constat et de ces aspirations graphiques, j’ai alors du penser à l’approche que j’allais prendre en matière de programmation. Pour créer une application simple dans le terminal, la première solution vers laquelle je me suis penché était alors de faire un script en bash.

Bash n’est pas un langage de programmation en soi ; il s’agit du shell (Programme permettant de faire fonctionner l’invite de commande/terminal, d’interpreter les différentes commandes de l’utilisateurice) qui vient par défaut sur l’immense majorité des distributions linux et sur beaucoup d’anciennes versions de MacOS. Aujourd’hui, beaucoup de shells alternatifs ont été développés pour répondre à des besoins d’utilisation variés, mais tous ou presque gardent les mêmes commandes en commun.

En se basant sur la syntaxe bash et ses commandes, n’importe qui peut donc écrire un script bash, en .sh : Il faut entendre un script comme une suite déterminée de commandes qui seront exécutées une à une dans le terminal. L’utilisateurice pourrait reproduire le même résultat en lançant les commandes une à une, mais tout regrouper dans un même script permet de créer des suites de commandes plus complexes.

N’ayant jamais fait de script bash par moi-même et ne sachant pas par où commencer, j’ai alors demandé à chatgpt de me faire une base pour le code, avec le prompt suivant :

how to make a bash script that allows me to display an image of my choice in ascii

ChatGPT m’a alors créé un script très simple, qui utilise une application déjà existante intitulée jp2a pour convertir des images en ascii dans l’invite de commande:

#!/bin/bash

# Check if the correct number of arguments is provided
if [ "$#" -ne 1 ]; then
  echo "Usage: $0 <image_path>"
  exit 1
fi

# Check if the image file exists
if [ ! -f "$1" ]; then
  echo "Error: Image file not found!"
  exit 1
fi

# Use jp2a to convert and display the image in ASCII
jp2a "$1"

En recopiant le script et en changeant la configuration de mon shell pour pouvoir donner le nom de mon choix à ma commande, j’ai ainsi pu la tester et me rendre compte qu’elle fonctionnait parfaitement bien :

# Commandes mises dans ma configuration shell ".zshrc"
export PATH="$PATH:~/bin"
alias imagepixel="~/bin/imagepixel.sh"

image de vache en ascii (imagepixel + “lien de l’image”)

L’utilisation telle quelle de cette commande n’était pas très intéressante, étant donné que l’outil utilisé est déjà complètement fonctionnel, et n’a pas du tout été façonné par moi. Cependant, en demandant une base à chatgpt, je me suis rendu compte qu’il n’était pas si compliqué que ça d’afficher de l’ascii dans le terminal, et encore moins de faire son propre script bash : j’allais alors pouvoir essayer de créer mon propre outil. Ainsi, j’ai demandé à ChatGPT de me faire un autre script, cette fois si en implémentant du contexte pour programme se baser sur le programme cowsay, comme je l’ai montré plus haut. Je voulais voir si j’étais capable de plus ou moins refaire cowsay en script bash, étant donné que le programme de base est écrit en perl, langage composé de beaucoup de parties mathématiques que je ne comprends pas.

Je trouve que l’approche de demander à chatgpt de créer des bases pour apprendre à faire des programmes est intéressante, car elle permet de se lancer sans avoir besoin d’apprendre toute la syntaxe d’un coup, et elle donne la liberté d’analyser tout le code après qu’il ait été généré. Cela ne m’intéresse pas d’avoir un programme qui marche sans que je me sois renseigné sur son fonctionnement, cela me permet donc d’avancer à mon propre ryhtme.

Le premier script que chatgpt m’a donné me proposait une base intéressante sur laquelle j’allais pouvoir construire la suite de mon code. Je ne comprenais pas encore bien ce que les différentes parties du code faisaient, mais la fonction de base était solide, et me permettait d’afficher un panneau qui s’étend en fonction de la longueur de mon message. En reprenant l’illustration de base de la vache de cowsay dans les fichiers sources, et en appelant la fonction “display_ascii_art”, je suis alors arrivé à un résultat satisfaisant, qui me permettait d’afficher cette image :

#!/bin/bash

function display_sign() {
    local message="$1"
    local length=${#message}
    local border=""

    for (( i=0; i<(length+0); i++ )); do
        border="${border}_"
    done

    echo " $border "
    echo "/ $message \\"
    echo "\\ $border /"
}

function display_ascii_art() {
    echo "        \   ^__^             "
    echo "         \  (oo)\_______     "
    echo "            (__)\       )\/\ "
    echo "                ||----w |    "
    echo "                ||     ||    "
}

if [ $# -eq 0 ]; then
    echo "Usage: $0 <message>"
    exit 1
fi

message="$*"
display_sign "$message"
display_ascii_art

Super, ça marche. Maintenant je dois essayer de changer quelques trucs.

Tout d’abord, je me suis dit que c’était dommage que l’image utilisée pour le programme soit toujours la même, car directement inscrite dans le code. Pour remédier à ce problème, j’ai changé la structure de mon projet, et j’ai commencé à inscrire les petits personnages dans des fichiers textes que je pourrais réutiliser par la suite à ma guise.

vachedit
 ├── ascii
 │   └── vache.txt
 └── vachedit.sh

(J’ai tout bêtement décidé d’appeler le programme vachedit, vu que je refaisais le code de cowsay.)

Ainsi, je peux maintenant afficher mon personnage en le supprimant dans mon script et en changeant la ligne de fin :

message="$*"
display_sign "$message"
cat ~/bin/vachedit/ascii/vache.txt
# Au lieu d'appeler directement la fonction, j'execute la commande cat, qui me permets d'afficher l'ascii art de mon choix

Pour l’instant, ça ne change pas du tout la forme de mon programme, mais cela va me permettre de spécifier, à l’aide d’un attribut, quel personnage je veux utiliser au moment de run la commande.

Pour arriver à ce résultat, cela demanda un peu plus d’efforts que prévu… Voici les lignes de code que j’ai rajouté :

default_ascii_file="vache.txt"
ascii_file="$default_ascii_file"

while getopts "a:" opt; do
  case $opt in
    a)
      ascii_file="$OPTARG.txt"
      ;;
    \?)
      echo "Invalid option: -$OPTARG" >&2
      exit 1
      ;;
    :)
      echo "L'option -$OPTARG requiert un argument." >&2
      exit 1
      ;;
  esac
done

shift $((OPTIND-1)) #Ligne necessaire pour écrire sans guillemets 

ascii_path=~/bin/vachedit/ascii/"$ascii_file"
if [ ! -f "$ascii_path" ]; then
  echo "Erreur: le fichier '$ascii_file' n'existe pas." >&2
  exit 1
fi

Reste du code...

message="$*"
display_sign "$message"
cat "$ascii_path"

J’ai utilisé un script de base que j’ai demandé à chatgpt, puis j’ai modifié les valeurs moi même pour les adapter au projet, ce qui veut dire qu’avec ma structure de fichiers, je peux faire ça :

.
├── ascii
│   ├── hellokitty.txt
│   └── vache.txt
└── vachedit.sh

A l’intérieur du code, j’ai fait en sorte que le personnage utilisé par défaut (si aucun argument n’est apporté) soit la vache, comme sur cowsay.

Il ne me reste plus qu’a rajouter tout un tas de personnages rigolos… Mais comment peut-on faire pour connaitre la liste de tous les personnages ? Encore une autre fonction.

ascii_dir=~/bin/vachedit/ascii

list_ascii_files() {
  local ascii_dir=~/bin/vachedit/ascii
  local files=("$ascii_dir"/*.txt)
  local file_names=()

  for file in "${files[@]}"; do
    file_names+=("$(basename "$file" .txt)")
  done

  echo "Personnages disponibles :"
  echo "${file_names[*]}"
}

Ce qui me donne ce résultat :

Finalement, pour garder trace de toutes les nouvelles commandes qui s’ajoutent au fur et à mesure du code, j’ai décidé de faire une fonction d’aide simple, qui s’affiche quand on utilise la commande vachedit sans préciser aucun arguments.

Je l’ai fait avec le code suivant :

if [ $# -eq 0 ]; then
  echo -e "\033[1m\033[4mUtilisation\033[0m: "
  echo "    vachedit [OPTIONS] 'Texte' "
  echo ""
  echo -e "\033[1m\033[4mOptions\033[0m:"
  echo "    -l           Liste tous les personnages disponibles"
  echo "    -a [option]  Spécifie un personnage à utiliser"
  exit 1
fi

Ce qui nous donne :

Bon. J’ai déjà une base de code assez solide, et un programme qui marche. Mais comme prévu, j’ai tout fait en faisant un collage de plein d’aide que j’ai trouvé sur internet, car ça allais me prendre trop de temps d’apprendre toutes les fonctions par moi même. A ce stade, je ne comprenais pas encore bien ce que toutes les fonctions faisaient individuellement. Pour remédier à ça, j’ai alors passé en revue tout le code que j’ai fait jusqu’à présent, et j’ai commenté chaque section importante du code, pour bien comprendre ce que chaque partie faisait, et pourquoi le résultat marchait à la fin.

Voici le code dans son état actuel, avec les commentaires que j’ai rajouté :

#!/bin/bash

# La première ligne d'un script bash s'appelle un shebang : 
# elle sert simplement à indiquer l'endroit ou se trouve le shell pour executer le script.

# Gère l'épaisseur et la construction du panneau, en fonction du nombre de charactères.
function display_sign() {
  local message="$1"
  local length=${#message}
  local border=""

  for (( i=0; i<(length+0); i++ )); do
    border="${border}_"
  done

  echo " $border "
  echo "/$message\\"
  echo "\\$border/"
}

# Fichier Ascii utilisé si aucun argument n'est donné à la fonction.
default_ascii_file="vache.txt"
ascii_file="$default_ascii_file"
ascii_dir=~/bin/vachedit/ascii

# Fonction pour lister tous les personnages ascii
list_ascii_files() {
  local ascii_dir=~/bin/vachedit/ascii
  local files=("$ascii_dir"/*.txt)
  local file_names=()

  for file in "${files[@]}"; do
    file_names+=("$(basename "$file" .txt)")
  done

  echo "Personnages disponibles :"
  echo "${file_names[*]}"
}

# Executer la fonction
while getopts "a:l" opt; do
  case $opt in
    a)
      ascii_file="$OPTARG.txt"
      ;;
    l)
      list_ascii_files
      exit 0
      ;;
    \?)
      echo "Invalid option: -$OPTARG" >&2
      exit 1
      ;;
    :)
      echo "L'option -$OPTARG requiert un argument." >&2
      exit 1
      ;;
  esac
done

shift $((OPTIND-1)) #Ligne necessaire pour écrire du texte sans guillemets après cowsay

# Vérifie si le fichier existe. Si non, renvoie une erreur.
ascii_path=~/bin/vachedit/ascii/"$ascii_file"
if [ ! -f "$ascii_path" ]; then
  echo "Erreur: le fichier '$ascii_file' n'existe pas." >&2
  exit 1
fi

# Âffiche un message d'aide si la commande est utilisée sans arguments
if [ $# -eq 0 ]; then
  echo -e "\033[1m\033[4mUtilisation\033[0m: "
  echo "    vachedit [OPTIONS] 'Texte' "
  echo ""
  echo -e "\033[1m\033[4mOptions\033[0m:"
  echo "    -l           Liste tous les personnages disponibles"
  echo "    -a [option]  Spécifie un personnage à utiliser"
  exit 1
fi

# Affiche le message
message="$*"
display_sign "$message"
cat "$ascii_path"

Mon programme est donc dans une position qui me plait bien : je peux rajouter des nouveaux personnages si je veux, et j’ai à peu près compris comment la syntaxe marchait.

Démonstration du programme

J’ai donc déjà pas mal avancé, et en soi je suis arrivé à l’objectif que je m’étais fixé : faire une application dans le terminal. En chemin, j’ai appris beaucoup de choses qui n’ont pas eu un lien directe avec le programme que je faisais, mais qui m’ont permis d’en connaitre un peu plus sur l’environnement linux et les invites de commande en général.

Python - Asciimatics

A ce moment, il me reste quelques semaines avant la fin du cours, et j’ai donc décidé de poursuivre sur la même voie, mais en partant sur un autre programme, en me disant qu’il n’y avait plus grand chose à rajouter sur vachedit. Comme montré plus haut, je suis toujours autant passionné d’ascii art et d’animations, et je trouvais que la suite logique était donc d’apprendre à faire des animations en ascii, toujours dans le terminal. J’avais aussi envie d’explorer autre chose que le script bash, car bien pratique à lui seul, il ne permets pas non plus de faire grand chose de très technique.

Je me suis donc dirigé vers python pour faire des animations dans le terminal, en essayant de partir sur quelque chose de peu complexe pour ne pas passer trop de temps à apprendre la syntaxe, mais pour quand même être capable de faire de choses graphiquement intéressantes.

Toujours en recherchant à l’aide d’internet et de ChatGPT, je me suis mis en quête de trouver une librairie qui me permettrait de faire exactement ça ; afficher des animations chouettes à regarder dans le terminal, sans me demander énormément de connaissances.

Je suis donc tombé sur la librarie Asciimatics :

Asciimatics est une librairie python qui permet de faire tout un tas d’animations, partant de visuels simples comme afficher du texte avec des typos personnalisées, ou bien des formules plus avancées, créant des paternes générés de manière procédurale, ou alors de la conversion de gifs en symboles colorés. Il s’agit donc d’une sorte de boîte à outil qui permet de faire exactement ce dont j’avais envie.

Les développeurs d’Asciimatics propose un répertoire github avec tout un tas d’exemples pour se faire une idée de l’utilisation de la librairie. En ne sachant pas trop d’où partir, je l’ai téléchargé, et j’ai lancé plusieurs scripts aléatoires. J’ai alors été épaté de ce que la librairie proposait de faire :

Pour créer des effets avec asciimatics, on doit éditer des scripts python. Il faut entendre “script” comme dans une pièce de théâtre : l’utilisateur.ice énonce étape par étape les effets, leur spécificités, et ils seront exécutés un à uns.

Voici par exemple le script pour l’effet “plasma” dans les exemples fournis :

#!/usr/bin/env python3

from asciimatics.effects import Julia
from asciimatics.scene import Scene
from asciimatics.screen import Screen
from asciimatics.exceptions import ResizeScreenError
import sys


def demo(screen):
    scenes = []
    effects = [
        Julia(screen),
    ]
    scenes.append(Scene(effects, -1))
    screen.play(scenes, stop_on_resize=True)


while True:
    try:
        Screen.wrapper(demo)
        sys.exit(0)
    except ResizeScreenError:
        pass

Un petit peu comme en bash, on indique d’abord l’emplacement de l’environnement de code (ici python), puis on appelle les différents effets qui seront utilisés dans le script. Ensuite, on spécifie les options pour les effets appelés, et finalement on spécifie les options pour l’écran, qui sont des options spécifiques à asciimatics, pour permettre d’afficher les effets visuels dans une fenêtre de terminal.

La syntaxe est un peu complexe, mais pas non plus extrêmement avancée, ce qui me permets de lire les scripts pour les éditer à ma sauce et en faire des effets personnalisés. Je ne m’y connais pas beaucoup en python, mais je sais à peu près comprendre une syntaxe et la reproduire.

En regardant le script images.py toujours dans le répertoire d’examples, j’ai trouvé l’effet ColourImageFile, qui permets de convertir des gifs en images ascii animées, tout ça en couleurs. Cet effet est donc très intéressant, et comme tous les autres effets, il peut être combiné à d’autres effets. Par exemple, sur le gif montré plus haut, en bas à droite on peut voir un effet feu d’artifices être combiné à un texte, qui affiche “happy”.

En jouant un peu avec le script de base, en modifiant certaines entrées et en en rajoutant de nouvelles, je suis alors arrivé à ce script :

#!/usr/bin/env python3

from asciimatics.effects import BannerText, Print, Scroll
from asciimatics.renderers import ColourImageFile, FigletText, Rainbow
from asciimatics.scene import Scene
from asciimatics.screen import Screen
from asciimatics.exceptions import ResizeScreenError
from asciimatics.effects import RandomNoise
import sys


def demo(screen):
    scenes = []
    effects = [
        Print(screen,
              ColourImageFile(screen, "gifs/data_matrix.gif", screen.height-2,
                              uni=screen.unicode_aware,
                              dither=screen.unicode_aware),
              1,
              stop_frame=200),
        Print(screen,
              FigletText("idontevenknow",
                         font='small'),
              screen.height//1-1,
              colour=7, bg=7),
        Print(screen,
              FigletText("bababababababababababababababababababababababab",
                         font='mini'),
              screen.height//2-1,
              colour=7, bg=7),
        Print(screen,
              FigletText("103804833cx;c,",
                         font='lean'),
              screen.width//5-1,
              colour=7, bg=7),
    ]
    scenes.append(Scene(effects))

    screen.play(scenes, stop_on_resize=True)


if __name__ == "__main__":
    while True:
        try:
            Screen.wrapper(demo)
            sys.exit(0)
        except ResizeScreenError:
            pass

Ici, j’importe tout un tas d’effets dans les premières lignes, et je rajoute du texte sur un gif que j’ai sélectionné auparavant, ce qui me donne ce résultat :

Même si ce résultat est assez simple, j’ai selon moi atteint mon objectif. J’ai réussi, pendant toutes ces semaines de cours, à me plonger en profondeur dans des outils en ligne de commande, je suis arrivé à en créer un par moi même, et j’ai exploré plusieurs nouveaux langages de programmation, pour au final réussir à créer des effets visuels comme je l’avais imaginé.

Je vais réutiliser ces connaissances pour créer une installation vidéo pendant mes jurys, et je vais, je l’espère, continuer à l’intéresser à la programmation et à l’art digital dans le futur.


projets/tui_playground_x_feuf_2024_ustensilesnum▔▜▚▘▘▟_erg_┆_wiki.txt · Dernière modification : 2024/05/11 11:59 de etudiant_e
CC Attribution-Noncommercial-Share Alike 4.0 International Sauf mention contraire, le contenu de ce wiki est placé sous les termes de la licence suivante : CC Attribution-Noncommercial-Share Alike 4.0 International