From 80b4220d985d8e7808e5f655ee4457887ce5da61 Mon Sep 17 00:00:00 2001 From: kazerlelutin Date: Tue, 21 Jul 2026 22:30:36 +0200 Subject: [PATCH] Fix Biome warnings --- dist/cookiecode.js | 1310 +++++++++++++++++++-------------------- examples/demo.js | 40 +- package.json | 32 +- src/cookiecode.js | 1310 +++++++++++++++++++-------------------- test/cookiecode.test.js | 98 +-- 5 files changed, 1401 insertions(+), 1389 deletions(-) diff --git a/dist/cookiecode.js b/dist/cookiecode.js index 7f5a5c4..15ab0d3 100644 --- a/dist/cookiecode.js +++ b/dist/cookiecode.js @@ -3,660 +3,660 @@ * Microlibrairie sans dépendance, compatible navigateur, CDN et Node/CommonJS. */ ((root, factory) => { - if (typeof module === "object" && module.exports) { - module.exports = factory(); - } else { - root.CookieCode = factory(); - } + if (typeof module === "object" && module.exports) { + module.exports = factory(); + } else { + root.CookieCode = factory(); + } })(typeof globalThis !== "undefined" ? globalThis : this, () => { - var configuration = { - sortie: "console", - afficherNotions: true, - }; - - function configurer(options) { - options = options || {}; - if (Object.hasOwn(options, "sortie")) { - configuration.sortie = options.sortie; - } - if (Object.hasOwn(options, "afficherNotions")) { - configuration.afficherNotions = Boolean(options.afficherNotions); - } - return CookieCode; - } - - function texte(valeur, defaut) { - if (valeur === undefined || valeur === null || valeur === "") { - return defaut || ""; - } - return String(valeur); - } - - function echapperHTML(valeur) { - return texte(valeur) - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); - } - - function creerElement(type, nom, quantite, unite, valeur) { - if (!nom && nom !== 0) { - throw new Error( - "CookieCode : un nom est obligatoire pour creer un " + type + ".", - ); - } - - return { - type: type, - nom: texte(nom), - quantite: quantite, - unite: unite, - valeur: valeur, - }; - } - - function ingredient(nom, quantite, unite) { - return creerElement("ingredient", nom, quantite, unite); - } - - function materiel(nom) { - return creerElement("materiel", nom); - } - - function constante(nom, valeur) { - return creerElement("constante", nom, undefined, undefined, valeur); - } - - function preparation(nom) { - return { - type: "preparation", - nom: texte(nom, "preparation"), - etat: {}, - definir: function (cle, valeur) { - this.etat[cle] = valeur; - return this; - }, - est: function (valeur) { - return ( - this.etat.texture === valeur || - this.etat.etat === valeur || - this.etat[valeur] === true - ); - }, - }; - } - - function nomDe(element) { - if (element && typeof element === "object" && element.nom !== undefined) { - return texte(element.nom); - } - return texte(element); - } - - function quantiteDe(element, quantite, unite) { - var q = quantite !== undefined ? quantite : element?.quantite; - var u = unite !== undefined ? unite : element?.unite; - - if (q === undefined || q === null || q === "") { - return ""; - } - - return texte(q) + (u ? " " + texte(u) : ""); - } - - function formatElement(element, quantite, unite) { - var quantiteTexte = quantiteDe(element, quantite, unite); - var nom = nomDe(element); - return quantiteTexte ? quantiteTexte + " de " + nom : nom; - } - - function conditionLisible(condition) { - if (typeof condition === "string") { - return condition; - } - if (typeof condition === "boolean") { - return condition ? "condition vraie" : "condition fausse"; - } - if (typeof condition === "function") { - return "condition fournie"; - } - return "condition"; - } - - function evaluerCondition(condition) { - if (typeof condition === "function") { - return Boolean(condition()); - } - if (typeof condition === "boolean") { - return condition; - } - return true; - } - - function creerEtape(texteEtape, notion, enfants) { - return { - texte: texteEtape, - notion: notion, - enfants: enfants || [], - }; - } - - function Recette(nom) { - if (!nom && nom !== 0) { - throw new Error("CookieCode : une recette doit avoir un nom."); - } - - this.nom = texte(nom); - this.etapes = []; - this.notions = { - programme: true, - variables: [], - constantes: [], - fonctions: [], - boucles: false, - conditions: false, - sequence: true, - }; - } - - Recette.prototype._noterFonction = function (nom) { - if (this.notions.fonctions.indexOf(nom) === -1) { - this.notions.fonctions.push(nom); - } - }; - - Recette.prototype._noterVariable = function (nom) { - if (nom && this.notions.variables.indexOf(nom) === -1) { - this.notions.variables.push(nom); - } - }; - - Recette.prototype._noterConstante = function (nom) { - if (nom && this.notions.constantes.indexOf(nom) === -1) { - this.notions.constantes.push(nom); - } - }; - - Recette.prototype._ajouterEtape = function (texteEtape, notion, enfants) { - this.etapes.push(creerEtape(texteEtape, notion, enfants)); - return this; - }; - - Recette.prototype.prendre = function (nom, quantite, unite) { - this._noterVariable(nomDe(nom)); - this._noterFonction("prendre"); - return this._ajouterEtape( - "Prendre " + formatElement(nom, quantite, unite) + ".", - "variable", - ); - }; - - Recette.prototype.utiliser = function (outil) { - this._noterConstante(nomDe(outil)); - this._noterFonction("utiliser"); - return this._ajouterEtape("Utiliser " + nomDe(outil) + ".", "constante"); - }; - - Recette.prototype.ajouter = function (element, quantite, unite) { - this._noterFonction("ajouter"); - if (element && element.type === "ingredient") { - this._noterVariable(element.nom); - } - return this._ajouterEtape( - "Ajouter " + formatElement(element, quantite, unite) + ".", - "fonction", - ); - }; - - Recette.prototype.retirer = function (element) { - this._noterFonction("retirer"); - return this._ajouterEtape("Retirer " + nomDe(element) + ".", "fonction"); - }; - - Recette.prototype.couper = function (element, forme) { - this._noterFonction("couper"); - return this._ajouterEtape( - "Couper " + nomDe(element) + (forme ? " en " + texte(forme) : "") + ".", - "fonction", - ); - }; - - Recette.prototype.melanger = function () { - this._noterFonction("melanger"); - return this._ajouterEtape("Melanger.", "fonction"); - }; - - Recette.prototype.verser = function (contenu, contenant) { - this._noterFonction("verser"); - return this._ajouterEtape( - "Verser " + - nomDe(contenu) + - (contenant ? " dans " + nomDe(contenant) : "") + - ".", - "fonction", - ); - }; - - Recette.prototype.chauffer = function (element, temperature) { - this._noterFonction("chauffer"); - return this._ajouterEtape( - "Chauffer " + - nomDe(element) + - (temperature ? " a " + nomDe(temperature) : "") + - ".", - "fonction", - ); - }; - - Recette.prototype.cuire = function (duree, unite) { - this._noterFonction("cuire"); - return this._ajouterEtape( - "Cuire" + - (duree - ? " pendant " + texte(duree) + (unite ? " " + texte(unite) : "") - : "") + - ".", - "fonction", - ); - }; - - Recette.prototype.attendre = function (duree, unite) { - this._noterFonction("attendre"); - return this._ajouterEtape( - "Attendre " + texte(duree) + (unite ? " " + texte(unite) : "") + ".", - "fonction", - ); - }; - - Recette.prototype.repeter = function (nombre, action) { - this.notions.boucles = true; - this._noterFonction("repeter"); - - var bloc = new Recette("bloc repeter"); - if (typeof action === "function") { - action(bloc); - } - - this._fusionnerNotions(bloc); - return this._ajouterEtape( - "Repeter " + texte(nombre) + " fois :", - "boucle", - bloc.etapes, - ); - }; - - Recette.prototype.si = function (condition, action, sinonAction) { - this.notions.conditions = true; - this._noterFonction("si"); - - var bloc = new Recette("bloc si"); - var blocSinon = new Recette("bloc sinon"); - var conditionVraie = evaluerCondition(condition); - - if (conditionVraie && typeof action === "function") { - action(bloc); - } - - if (!conditionVraie && typeof sinonAction === "function") { - sinonAction(blocSinon); - } - - this._fusionnerNotions(bloc); - this._fusionnerNotions(blocSinon); - - var enfants = bloc.etapes; - if (blocSinon.etapes.length > 0) { - enfants = enfants.concat([ - creerEtape("Sinon :", "condition", blocSinon.etapes), - ]); - } - - return this._ajouterEtape( - "Si " + conditionLisible(condition) + " :", - "condition", - enfants, - ); - }; - - Recette.prototype.servir = function (element) { - this._noterFonction("servir"); - this._ajouterEtape( - "Servir" + (element ? " " + nomDe(element) : "") + ".", - "fonction", - ); - return this.afficher(); - }; - - Recette.prototype.afficher = function () { - var cible; - - if (configuration.sortie === "console" || !configuration.sortie) { - if (typeof console !== "undefined" && console.log) { - console.log(this.toText()); - } - return this; - } - - if ( - typeof configuration.sortie === "string" && - typeof document !== "undefined" - ) { - cible = document.querySelector(configuration.sortie); - if (cible) { - cible.innerHTML = this.toHTML(); - } - } - - return this; - }; - - Recette.prototype._fusionnerNotions = function (autreRecette) { - autreRecette.notions.variables.forEach((nom) => { - this._noterVariable(nom); - }); - autreRecette.notions.constantes.forEach((nom) => { - this._noterConstante(nom); - }); - autreRecette.notions.fonctions.forEach((nom) => { - this._noterFonction(nom); - }); - this.notions.boucles = this.notions.boucles || autreRecette.notions.boucles; - this.notions.conditions = - this.notions.conditions || autreRecette.notions.conditions; - }; - - Recette.prototype._lignesEtapes = function (etapes, prefixe, indentation) { - var lignes = []; - etapes.forEach(function (etape, index) { - var numero = prefixe ? prefixe + "." + (index + 1) : String(index + 1); - lignes.push(indentation + numero + ". " + etape.texte); - if (etape.enfants && etape.enfants.length > 0) { - lignes = lignes.concat( - this._lignesEtapes(etape.enfants, numero, indentation + " "), - ); - } - }, this); - return lignes; - }; - - Recette.prototype.analyser = function () { - var actions = this.notions.fonctions.slice(); - var etapesAPlates = aplatirEtapes(this.etapes); - var derniereAction = - etapesAPlates.length > 0 - ? actionDeTexte(etapesAPlates[etapesAPlates.length - 1].texte) - : ""; - var erreurs = []; - - if (this.etapes.length === 0) { - erreurs.push("Ta recette est vide : ajoute au moins une etape."); - } - - if (this.etapes.length > 0 && derniereAction !== "servir") { - erreurs.push("Ta recette ne se termine pas par servir."); - } - - etapesAPlates.forEach((etape, index) => { - if ( - actionDeTexte(etape.texte) === "servir" && - index !== etapesAPlates.length - 1 - ) { - erreurs.push("Tu as servi avant la fin de la recette."); - } - }); - - return { - nom: this.nom, - ingredients: this.notions.variables.slice(), - variables: this.notions.variables.slice(), - materiels: this.notions.constantes.slice(), - constantes: this.notions.constantes.slice(), - actions: actions, - nombreEtapes: etapesAPlates.length, - utiliseBoucle: this.notions.boucles, - utiliseCondition: this.notions.conditions, - termineParServir: derniereAction === "servir", - erreurs: erreurs, - }; - }; - - Recette.prototype._lignesNotions = function () { - var lignes = [ - "", - "Notions utilisees :", - "- Recette = programme", - "- Sequence = les etapes sont executees dans l'ordre", - ]; - - if (this.notions.variables.length > 0) { - lignes.push( - "- Ingredients = variables : " + this.notions.variables.join(", "), - ); - } - if (this.notions.constantes.length > 0) { - lignes.push( - "- Materiel / valeurs fixes = constantes : " + - this.notions.constantes.join(", "), - ); - } - if (this.notions.fonctions.length > 0) { - lignes.push( - "- Actions = fonctions : " + this.notions.fonctions.join(", "), - ); - } - if (this.notions.boucles) { - lignes.push("- Repeter = boucle"); - } - if (this.notions.conditions) { - lignes.push("- Si = condition"); - } - - return lignes; - }; - - Recette.prototype.toText = function () { - var lignes = ["Recette : " + this.nom, "", "Etapes :"]; - lignes = lignes.concat(this._lignesEtapes(this.etapes, "", "")); - - if (configuration.afficherNotions) { - lignes = lignes.concat(this._lignesNotions()); - } - - return lignes.join("\n"); - }; - - Recette.prototype.toHTML = function () { - var html = '
'; - html += "

Recette : " + echapperHTML(this.nom) + "

"; - html += "

Etapes

"; - html += etapesHTML(this.etapes); - - if (configuration.afficherNotions) { - html += "

Notions utilisees

"; - } - - html += "
"; - return html; - }; - - function aplatirEtapes(etapes) { - var resultat = []; - etapes.forEach((etape) => { - resultat.push(etape); - if (etape.enfants && etape.enfants.length > 0) { - resultat = resultat.concat(aplatirEtapes(etape.enfants)); - } - }); - return resultat; - } - - function actionDeTexte(texteEtape) { - var index; - var valeur = texte(texteEtape).toLowerCase(); - var actions = [ - "prendre", - "utiliser", - "ajouter", - "retirer", - "couper", - "melanger", - "verser", - "chauffer", - "cuire", - "attendre", - "repeter", - "si", - "servir", - ]; - - for (index = 0; index < actions.length; index += 1) { - if (valeur.indexOf(actions[index]) === 0) { - return actions[index]; - } - } - - return ""; - } - - function defi(nom, regles) { - if (!nom && nom !== 0) { - throw new Error("CookieCode : un defi doit avoir un nom."); - } - - return { - type: "defi", - nom: texte(nom), - regles: regles || {}, - }; - } - - function verifier(recetteAVerifier, defiAVerifier) { - var rapport = recetteAVerifier.analyser(); - var regles = defiAVerifier?.regles ? defiAVerifier.regles : {}; - var messages = []; - var reussi = true; - - function valider(condition, succes, echec) { - if (condition) { - messages.push("✅ " + succes); - } else { - reussi = false; - messages.push("❌ " + echec); - } - } - - if (regles.ingredientsMinimum !== undefined) { - valider( - rapport.ingredients.length >= regles.ingredientsMinimum, - "Tu as utilise " + rapport.ingredients.length + " ingredient(s).", - "Il faut au moins " + regles.ingredientsMinimum + " ingredient(s).", - ); - } - - if (regles.materielsMinimum !== undefined) { - valider( - rapport.materiels.length >= regles.materielsMinimum, - "Tu as utilise " + rapport.materiels.length + " materiel(s).", - "Il faut au moins " + regles.materielsMinimum + " materiel(s).", - ); - } - - (regles.actionsObligatoires || []).forEach((action) => { - valider( - rapport.actions.indexOf(action) !== -1, - "Action trouvee : " + action + ".", - "Action manquante : " + action + ".", - ); - }); - - if (regles.boucleObligatoire) { - valider( - rapport.utiliseBoucle, - "Tu as utilise une boucle avec repeter.", - "Il manque une boucle : essaie avec .repeter().", - ); - } - - if (regles.conditionObligatoire) { - valider( - rapport.utiliseCondition, - "Tu as utilise une condition avec si.", - "Il manque une condition : essaie avec .si().", - ); - } - - if (regles.terminerParServir) { - valider( - rapport.termineParServir, - "Ta recette se termine par servir.", - "Ta recette doit se terminer par .servir().", - ); - } - - if (regles.interdireErreurs) { - valider( - rapport.erreurs.length === 0, - "Aucune erreur logique detectee.", - rapport.erreurs.join(" ") || "Une erreur logique a ete detectee.", - ); - } - - if (messages.length === 0) { - messages.push("ℹ️ Aucun critere de defi a verifier."); - } - - return { - reussi: reussi, - nom: defiAVerifier ? defiAVerifier.nom : "Defi", - messages: messages, - rapport: rapport, - toText: function () { - return [ - "Defi : " + this.nom, - this.reussi ? "Resultat : reussi" : "Resultat : a ameliorer", - ] - .concat(this.messages) - .join("\n"); - }, - }; - } - - function etapesHTML(etapes) { - var html = "
    "; - etapes.forEach((etape) => { - html += "
  1. " + echapperHTML(etape.texte); - if (etape.enfants && etape.enfants.length > 0) { - html += etapesHTML(etape.enfants); - } - html += "
  2. "; - }); - html += "
"; - return html; - } - - function recette(nom) { - return new Recette(nom); - } - - var CookieCode = { - configurer: configurer, - recette: recette, - ingredient: ingredient, - materiel: materiel, - constante: constante, - preparation: preparation, - defi: defi, - verifier: verifier, - version: "0.2.0", - }; - - return CookieCode; + var configuration = { + sortie: "console", + afficherNotions: true, + }; + + function configurer(options) { + options = options || {}; + if (Object.hasOwn(options, "sortie")) { + configuration.sortie = options.sortie; + } + if (Object.hasOwn(options, "afficherNotions")) { + configuration.afficherNotions = Boolean(options.afficherNotions); + } + return CookieCode; + } + + function texte(valeur, defaut) { + if (valeur === undefined || valeur === null || valeur === "") { + return defaut || ""; + } + return String(valeur); + } + + function echapperHTML(valeur) { + return texte(valeur) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + } + + function creerElement(type, nom, quantite, unite, valeur) { + if (!nom && nom !== 0) { + throw new Error( + `CookieCode : un nom est obligatoire pour creer un ${type}.`, + ); + } + + return { + type: type, + nom: texte(nom), + quantite: quantite, + unite: unite, + valeur: valeur, + }; + } + + function ingredient(nom, quantite, unite) { + return creerElement("ingredient", nom, quantite, unite); + } + + function materiel(nom) { + return creerElement("materiel", nom); + } + + function constante(nom, valeur) { + return creerElement("constante", nom, undefined, undefined, valeur); + } + + function preparation(nom) { + return { + type: "preparation", + nom: texte(nom, "preparation"), + etat: {}, + definir: function (cle, valeur) { + this.etat[cle] = valeur; + return this; + }, + est: function (valeur) { + return ( + this.etat.texture === valeur || + this.etat.etat === valeur || + this.etat[valeur] === true + ); + }, + }; + } + + function nomDe(element) { + if (element && typeof element === "object" && element.nom !== undefined) { + return texte(element.nom); + } + return texte(element); + } + + function quantiteDe(element, quantite, unite) { + var q = quantite !== undefined ? quantite : element?.quantite; + var u = unite !== undefined ? unite : element?.unite; + + if (q === undefined || q === null || q === "") { + return ""; + } + + return texte(q) + (u ? ` ${texte(u)}` : ""); + } + + function formatElement(element, quantite, unite) { + var quantiteTexte = quantiteDe(element, quantite, unite); + var nom = nomDe(element); + return quantiteTexte ? `${quantiteTexte} de ${nom}` : nom; + } + + function conditionLisible(condition) { + if (typeof condition === "string") { + return condition; + } + if (typeof condition === "boolean") { + return condition ? "condition vraie" : "condition fausse"; + } + if (typeof condition === "function") { + return "condition fournie"; + } + return "condition"; + } + + function evaluerCondition(condition) { + if (typeof condition === "function") { + return Boolean(condition()); + } + if (typeof condition === "boolean") { + return condition; + } + return true; + } + + function creerEtape(texteEtape, notion, enfants) { + return { + texte: texteEtape, + notion: notion, + enfants: enfants || [], + }; + } + + function Recette(nom) { + if (!nom && nom !== 0) { + throw new Error("CookieCode : une recette doit avoir un nom."); + } + + this.nom = texte(nom); + this.etapes = []; + this.notions = { + programme: true, + variables: [], + constantes: [], + fonctions: [], + boucles: false, + conditions: false, + sequence: true, + }; + } + + Recette.prototype._noterFonction = function (nom) { + if (this.notions.fonctions.indexOf(nom) === -1) { + this.notions.fonctions.push(nom); + } + }; + + Recette.prototype._noterVariable = function (nom) { + if (nom && this.notions.variables.indexOf(nom) === -1) { + this.notions.variables.push(nom); + } + }; + + Recette.prototype._noterConstante = function (nom) { + if (nom && this.notions.constantes.indexOf(nom) === -1) { + this.notions.constantes.push(nom); + } + }; + + Recette.prototype._ajouterEtape = function (texteEtape, notion, enfants) { + this.etapes.push(creerEtape(texteEtape, notion, enfants)); + return this; + }; + + Recette.prototype.prendre = function (nom, quantite, unite) { + this._noterVariable(nomDe(nom)); + this._noterFonction("prendre"); + return this._ajouterEtape( + `Prendre ${formatElement(nom, quantite, unite)}.`, + "variable", + ); + }; + + Recette.prototype.utiliser = function (outil) { + this._noterConstante(nomDe(outil)); + this._noterFonction("utiliser"); + return this._ajouterEtape(`Utiliser ${nomDe(outil)}.`, "constante"); + }; + + Recette.prototype.ajouter = function (element, quantite, unite) { + this._noterFonction("ajouter"); + if (element && element.type === "ingredient") { + this._noterVariable(element.nom); + } + return this._ajouterEtape( + `Ajouter ${formatElement(element, quantite, unite)}.`, + "fonction", + ); + }; + + Recette.prototype.retirer = function (element) { + this._noterFonction("retirer"); + return this._ajouterEtape(`Retirer ${nomDe(element)}.`, "fonction"); + }; + + Recette.prototype.couper = function (element, forme) { + this._noterFonction("couper"); + return this._ajouterEtape( + `Couper ${nomDe(element)}${forme ? ` en ${texte(forme)}` : ""}.`, + "fonction", + ); + }; + + Recette.prototype.melanger = function () { + this._noterFonction("melanger"); + return this._ajouterEtape("Melanger.", "fonction"); + }; + + Recette.prototype.verser = function (contenu, contenant) { + this._noterFonction("verser"); + return this._ajouterEtape( + "Verser " + + nomDe(contenu) + + (contenant ? ` dans ${nomDe(contenant)}` : "") + + ".", + "fonction", + ); + }; + + Recette.prototype.chauffer = function (element, temperature) { + this._noterFonction("chauffer"); + return this._ajouterEtape( + "Chauffer " + + nomDe(element) + + (temperature ? ` a ${nomDe(temperature)}` : "") + + ".", + "fonction", + ); + }; + + Recette.prototype.cuire = function (duree, unite) { + this._noterFonction("cuire"); + return this._ajouterEtape( + "Cuire" + + (duree + ? ` pendant ${texte(duree)}${unite ? ` ${texte(unite)}` : ""}` + : "") + + ".", + "fonction", + ); + }; + + Recette.prototype.attendre = function (duree, unite) { + this._noterFonction("attendre"); + return this._ajouterEtape( + `Attendre ${texte(duree)}${unite ? ` ${texte(unite)}` : ""}.`, + "fonction", + ); + }; + + Recette.prototype.repeter = function (nombre, action) { + this.notions.boucles = true; + this._noterFonction("repeter"); + + var bloc = new Recette("bloc repeter"); + if (typeof action === "function") { + action(bloc); + } + + this._fusionnerNotions(bloc); + return this._ajouterEtape( + `Repeter ${texte(nombre)} fois :`, + "boucle", + bloc.etapes, + ); + }; + + Recette.prototype.si = function (condition, action, sinonAction) { + this.notions.conditions = true; + this._noterFonction("si"); + + var bloc = new Recette("bloc si"); + var blocSinon = new Recette("bloc sinon"); + var conditionVraie = evaluerCondition(condition); + + if (conditionVraie && typeof action === "function") { + action(bloc); + } + + if (!conditionVraie && typeof sinonAction === "function") { + sinonAction(blocSinon); + } + + this._fusionnerNotions(bloc); + this._fusionnerNotions(blocSinon); + + var enfants = bloc.etapes; + if (blocSinon.etapes.length > 0) { + enfants = enfants.concat([ + creerEtape("Sinon :", "condition", blocSinon.etapes), + ]); + } + + return this._ajouterEtape( + `Si ${conditionLisible(condition)} :`, + "condition", + enfants, + ); + }; + + Recette.prototype.servir = function (element) { + this._noterFonction("servir"); + this._ajouterEtape( + `Servir${element ? ` ${nomDe(element)}` : ""}.`, + "fonction", + ); + return this.afficher(); + }; + + Recette.prototype.afficher = function () { + var cible; + + if (configuration.sortie === "console" || !configuration.sortie) { + if (typeof console !== "undefined" && console.log) { + console.log(this.toText()); + } + return this; + } + + if ( + typeof configuration.sortie === "string" && + typeof document !== "undefined" + ) { + cible = document.querySelector(configuration.sortie); + if (cible) { + cible.innerHTML = this.toHTML(); + } + } + + return this; + }; + + Recette.prototype._fusionnerNotions = function (autreRecette) { + autreRecette.notions.variables.forEach((nom) => { + this._noterVariable(nom); + }); + autreRecette.notions.constantes.forEach((nom) => { + this._noterConstante(nom); + }); + autreRecette.notions.fonctions.forEach((nom) => { + this._noterFonction(nom); + }); + this.notions.boucles = this.notions.boucles || autreRecette.notions.boucles; + this.notions.conditions = + this.notions.conditions || autreRecette.notions.conditions; + }; + + Recette.prototype._lignesEtapes = function (etapes, prefixe, indentation) { + var lignes = []; + etapes.forEach(function (etape, index) { + var numero = prefixe ? `${prefixe}.${index + 1}` : String(index + 1); + lignes.push(`${indentation + numero}. ${etape.texte}`); + if (etape.enfants && etape.enfants.length > 0) { + lignes = lignes.concat( + this._lignesEtapes(etape.enfants, numero, `${indentation} `), + ); + } + }, this); + return lignes; + }; + + Recette.prototype.analyser = function () { + var actions = this.notions.fonctions.slice(); + var etapesAPlates = aplatirEtapes(this.etapes); + var derniereAction = + etapesAPlates.length > 0 + ? actionDeTexte(etapesAPlates[etapesAPlates.length - 1].texte) + : ""; + var erreurs = []; + + if (this.etapes.length === 0) { + erreurs.push("Ta recette est vide : ajoute au moins une etape."); + } + + if (this.etapes.length > 0 && derniereAction !== "servir") { + erreurs.push("Ta recette ne se termine pas par servir."); + } + + etapesAPlates.forEach((etape, index) => { + if ( + actionDeTexte(etape.texte) === "servir" && + index !== etapesAPlates.length - 1 + ) { + erreurs.push("Tu as servi avant la fin de la recette."); + } + }); + + return { + nom: this.nom, + ingredients: this.notions.variables.slice(), + variables: this.notions.variables.slice(), + materiels: this.notions.constantes.slice(), + constantes: this.notions.constantes.slice(), + actions: actions, + nombreEtapes: etapesAPlates.length, + utiliseBoucle: this.notions.boucles, + utiliseCondition: this.notions.conditions, + termineParServir: derniereAction === "servir", + erreurs: erreurs, + }; + }; + + Recette.prototype._lignesNotions = function () { + var lignes = [ + "", + "Notions utilisees :", + "- Recette = programme", + "- Sequence = les etapes sont executees dans l'ordre", + ]; + + if (this.notions.variables.length > 0) { + lignes.push( + `- Ingredients = variables : ${this.notions.variables.join(", ")}`, + ); + } + if (this.notions.constantes.length > 0) { + lignes.push( + "- Materiel / valeurs fixes = constantes : " + + this.notions.constantes.join(", "), + ); + } + if (this.notions.fonctions.length > 0) { + lignes.push( + `- Actions = fonctions : ${this.notions.fonctions.join(", ")}`, + ); + } + if (this.notions.boucles) { + lignes.push("- Repeter = boucle"); + } + if (this.notions.conditions) { + lignes.push("- Si = condition"); + } + + return lignes; + }; + + Recette.prototype.toText = function () { + var lignes = [`Recette : ${this.nom}`, "", "Etapes :"]; + lignes = lignes.concat(this._lignesEtapes(this.etapes, "", "")); + + if (configuration.afficherNotions) { + lignes = lignes.concat(this._lignesNotions()); + } + + return lignes.join("\n"); + }; + + Recette.prototype.toHTML = function () { + var html = '
'; + html += `

Recette : ${echapperHTML(this.nom)}

`; + html += "

Etapes

"; + html += etapesHTML(this.etapes); + + if (configuration.afficherNotions) { + html += "

Notions utilisees

"; + } + + html += "
"; + return html; + }; + + function aplatirEtapes(etapes) { + var resultat = []; + etapes.forEach((etape) => { + resultat.push(etape); + if (etape.enfants && etape.enfants.length > 0) { + resultat = resultat.concat(aplatirEtapes(etape.enfants)); + } + }); + return resultat; + } + + function actionDeTexte(texteEtape) { + var index; + var valeur = texte(texteEtape).toLowerCase(); + var actions = [ + "prendre", + "utiliser", + "ajouter", + "retirer", + "couper", + "melanger", + "verser", + "chauffer", + "cuire", + "attendre", + "repeter", + "si", + "servir", + ]; + + for (index = 0; index < actions.length; index += 1) { + if (valeur.indexOf(actions[index]) === 0) { + return actions[index]; + } + } + + return ""; + } + + function defi(nom, regles) { + if (!nom && nom !== 0) { + throw new Error("CookieCode : un defi doit avoir un nom."); + } + + return { + type: "defi", + nom: texte(nom), + regles: regles || {}, + }; + } + + function verifier(recetteAVerifier, defiAVerifier) { + var rapport = recetteAVerifier.analyser(); + var regles = defiAVerifier?.regles ? defiAVerifier.regles : {}; + var messages = []; + var reussi = true; + + function valider(condition, succes, echec) { + if (condition) { + messages.push(`✅ ${succes}`); + } else { + reussi = false; + messages.push(`❌ ${echec}`); + } + } + + if (regles.ingredientsMinimum !== undefined) { + valider( + rapport.ingredients.length >= regles.ingredientsMinimum, + `Tu as utilise ${rapport.ingredients.length} ingredient(s).`, + `Il faut au moins ${regles.ingredientsMinimum} ingredient(s).`, + ); + } + + if (regles.materielsMinimum !== undefined) { + valider( + rapport.materiels.length >= regles.materielsMinimum, + `Tu as utilise ${rapport.materiels.length} materiel(s).`, + `Il faut au moins ${regles.materielsMinimum} materiel(s).`, + ); + } + + (regles.actionsObligatoires || []).forEach((action) => { + valider( + rapport.actions.indexOf(action) !== -1, + `Action trouvee : ${action}.`, + `Action manquante : ${action}.`, + ); + }); + + if (regles.boucleObligatoire) { + valider( + rapport.utiliseBoucle, + "Tu as utilise une boucle avec repeter.", + "Il manque une boucle : essaie avec .repeter().", + ); + } + + if (regles.conditionObligatoire) { + valider( + rapport.utiliseCondition, + "Tu as utilise une condition avec si.", + "Il manque une condition : essaie avec .si().", + ); + } + + if (regles.terminerParServir) { + valider( + rapport.termineParServir, + "Ta recette se termine par servir.", + "Ta recette doit se terminer par .servir().", + ); + } + + if (regles.interdireErreurs) { + valider( + rapport.erreurs.length === 0, + "Aucune erreur logique detectee.", + rapport.erreurs.join(" ") || "Une erreur logique a ete detectee.", + ); + } + + if (messages.length === 0) { + messages.push("ℹ️ Aucun critere de defi a verifier."); + } + + return { + reussi: reussi, + nom: defiAVerifier ? defiAVerifier.nom : "Defi", + messages: messages, + rapport: rapport, + toText: function () { + return [ + `Defi : ${this.nom}`, + this.reussi ? "Resultat : reussi" : "Resultat : a ameliorer", + ] + .concat(this.messages) + .join("\n"); + }, + }; + } + + function etapesHTML(etapes) { + var html = "
    "; + etapes.forEach((etape) => { + html += `
  1. ${echapperHTML(etape.texte)}`; + if (etape.enfants && etape.enfants.length > 0) { + html += etapesHTML(etape.enfants); + } + html += "
  2. "; + }); + html += "
"; + return html; + } + + function recette(nom) { + return new Recette(nom); + } + + var CookieCode = { + configurer: configurer, + recette: recette, + ingredient: ingredient, + materiel: materiel, + constante: constante, + preparation: preparation, + defi: defi, + verifier: verifier, + version: "0.2.0", + }; + + return CookieCode; }); diff --git a/examples/demo.js b/examples/demo.js index 66e23ba..fc6048b 100644 --- a/examples/demo.js +++ b/examples/demo.js @@ -1,29 +1,29 @@ CookieCode.configurer({ - sortie: "#app", - afficherNotions: true, + sortie: "#app", + afficherNotions: true, }); const recette = CookieCode.recette("Cookie au chocolat") - .prendre("farine", 200, "g") - .prendre("sucre", 100, "g") - .prendre("chocolat", 150, "g") - .utiliser("saladier") - .ajouter("farine") - .ajouter("sucre") - .ajouter("chocolat") - .melanger() - .repeter(2, (recette) => { - recette.melanger(); - }) - .cuire(12, "minutes") - .servir(); + .prendre("farine", 200, "g") + .prendre("sucre", 100, "g") + .prendre("chocolat", 150, "g") + .utiliser("saladier") + .ajouter("farine") + .ajouter("sucre") + .ajouter("chocolat") + .melanger() + .repeter(2, (recette) => { + recette.melanger(); + }) + .cuire(12, "minutes") + .servir(); const defi = CookieCode.defi("Cookie avec boucle", { - ingredientsMinimum: 3, - actionsObligatoires: ["melanger", "cuire", "servir"], - boucleObligatoire: true, - terminerParServir: true, - interdireErreurs: true + ingredientsMinimum: 3, + actionsObligatoires: ["melanger", "cuire", "servir"], + boucleObligatoire: true, + terminerParServir: true, + interdireErreurs: true, }); console.log(CookieCode.verifier(recette, defi).toText()); diff --git a/package.json b/package.json index a568e59..b03b2d9 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,18 @@ { - "name": "cookiecode", - "version": "0.2.0", - "description": "Microlibrairie educative pour apprendre l'algorithmique avec des recettes de cuisine.", - "main": "dist/cookiecode.js", - "scripts": { - "test": "node test/cookiecode.test.js", - "build": "cp src/cookiecode.js dist/cookiecode.js" - }, - "keywords": [ - "education", - "algorithmique", - "cuisine", - "javascript", - "cdn" - ], - "license": "MIT" + "name": "cookiecode", + "version": "0.2.0", + "description": "Microlibrairie educative pour apprendre l'algorithmique avec des recettes de cuisine.", + "main": "dist/cookiecode.js", + "scripts": { + "test": "node test/cookiecode.test.js", + "build": "cp src/cookiecode.js dist/cookiecode.js" + }, + "keywords": [ + "education", + "algorithmique", + "cuisine", + "javascript", + "cdn" + ], + "license": "MIT" } diff --git a/src/cookiecode.js b/src/cookiecode.js index 7f5a5c4..15ab0d3 100644 --- a/src/cookiecode.js +++ b/src/cookiecode.js @@ -3,660 +3,660 @@ * Microlibrairie sans dépendance, compatible navigateur, CDN et Node/CommonJS. */ ((root, factory) => { - if (typeof module === "object" && module.exports) { - module.exports = factory(); - } else { - root.CookieCode = factory(); - } + if (typeof module === "object" && module.exports) { + module.exports = factory(); + } else { + root.CookieCode = factory(); + } })(typeof globalThis !== "undefined" ? globalThis : this, () => { - var configuration = { - sortie: "console", - afficherNotions: true, - }; - - function configurer(options) { - options = options || {}; - if (Object.hasOwn(options, "sortie")) { - configuration.sortie = options.sortie; - } - if (Object.hasOwn(options, "afficherNotions")) { - configuration.afficherNotions = Boolean(options.afficherNotions); - } - return CookieCode; - } - - function texte(valeur, defaut) { - if (valeur === undefined || valeur === null || valeur === "") { - return defaut || ""; - } - return String(valeur); - } - - function echapperHTML(valeur) { - return texte(valeur) - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); - } - - function creerElement(type, nom, quantite, unite, valeur) { - if (!nom && nom !== 0) { - throw new Error( - "CookieCode : un nom est obligatoire pour creer un " + type + ".", - ); - } - - return { - type: type, - nom: texte(nom), - quantite: quantite, - unite: unite, - valeur: valeur, - }; - } - - function ingredient(nom, quantite, unite) { - return creerElement("ingredient", nom, quantite, unite); - } - - function materiel(nom) { - return creerElement("materiel", nom); - } - - function constante(nom, valeur) { - return creerElement("constante", nom, undefined, undefined, valeur); - } - - function preparation(nom) { - return { - type: "preparation", - nom: texte(nom, "preparation"), - etat: {}, - definir: function (cle, valeur) { - this.etat[cle] = valeur; - return this; - }, - est: function (valeur) { - return ( - this.etat.texture === valeur || - this.etat.etat === valeur || - this.etat[valeur] === true - ); - }, - }; - } - - function nomDe(element) { - if (element && typeof element === "object" && element.nom !== undefined) { - return texte(element.nom); - } - return texte(element); - } - - function quantiteDe(element, quantite, unite) { - var q = quantite !== undefined ? quantite : element?.quantite; - var u = unite !== undefined ? unite : element?.unite; - - if (q === undefined || q === null || q === "") { - return ""; - } - - return texte(q) + (u ? " " + texte(u) : ""); - } - - function formatElement(element, quantite, unite) { - var quantiteTexte = quantiteDe(element, quantite, unite); - var nom = nomDe(element); - return quantiteTexte ? quantiteTexte + " de " + nom : nom; - } - - function conditionLisible(condition) { - if (typeof condition === "string") { - return condition; - } - if (typeof condition === "boolean") { - return condition ? "condition vraie" : "condition fausse"; - } - if (typeof condition === "function") { - return "condition fournie"; - } - return "condition"; - } - - function evaluerCondition(condition) { - if (typeof condition === "function") { - return Boolean(condition()); - } - if (typeof condition === "boolean") { - return condition; - } - return true; - } - - function creerEtape(texteEtape, notion, enfants) { - return { - texte: texteEtape, - notion: notion, - enfants: enfants || [], - }; - } - - function Recette(nom) { - if (!nom && nom !== 0) { - throw new Error("CookieCode : une recette doit avoir un nom."); - } - - this.nom = texte(nom); - this.etapes = []; - this.notions = { - programme: true, - variables: [], - constantes: [], - fonctions: [], - boucles: false, - conditions: false, - sequence: true, - }; - } - - Recette.prototype._noterFonction = function (nom) { - if (this.notions.fonctions.indexOf(nom) === -1) { - this.notions.fonctions.push(nom); - } - }; - - Recette.prototype._noterVariable = function (nom) { - if (nom && this.notions.variables.indexOf(nom) === -1) { - this.notions.variables.push(nom); - } - }; - - Recette.prototype._noterConstante = function (nom) { - if (nom && this.notions.constantes.indexOf(nom) === -1) { - this.notions.constantes.push(nom); - } - }; - - Recette.prototype._ajouterEtape = function (texteEtape, notion, enfants) { - this.etapes.push(creerEtape(texteEtape, notion, enfants)); - return this; - }; - - Recette.prototype.prendre = function (nom, quantite, unite) { - this._noterVariable(nomDe(nom)); - this._noterFonction("prendre"); - return this._ajouterEtape( - "Prendre " + formatElement(nom, quantite, unite) + ".", - "variable", - ); - }; - - Recette.prototype.utiliser = function (outil) { - this._noterConstante(nomDe(outil)); - this._noterFonction("utiliser"); - return this._ajouterEtape("Utiliser " + nomDe(outil) + ".", "constante"); - }; - - Recette.prototype.ajouter = function (element, quantite, unite) { - this._noterFonction("ajouter"); - if (element && element.type === "ingredient") { - this._noterVariable(element.nom); - } - return this._ajouterEtape( - "Ajouter " + formatElement(element, quantite, unite) + ".", - "fonction", - ); - }; - - Recette.prototype.retirer = function (element) { - this._noterFonction("retirer"); - return this._ajouterEtape("Retirer " + nomDe(element) + ".", "fonction"); - }; - - Recette.prototype.couper = function (element, forme) { - this._noterFonction("couper"); - return this._ajouterEtape( - "Couper " + nomDe(element) + (forme ? " en " + texte(forme) : "") + ".", - "fonction", - ); - }; - - Recette.prototype.melanger = function () { - this._noterFonction("melanger"); - return this._ajouterEtape("Melanger.", "fonction"); - }; - - Recette.prototype.verser = function (contenu, contenant) { - this._noterFonction("verser"); - return this._ajouterEtape( - "Verser " + - nomDe(contenu) + - (contenant ? " dans " + nomDe(contenant) : "") + - ".", - "fonction", - ); - }; - - Recette.prototype.chauffer = function (element, temperature) { - this._noterFonction("chauffer"); - return this._ajouterEtape( - "Chauffer " + - nomDe(element) + - (temperature ? " a " + nomDe(temperature) : "") + - ".", - "fonction", - ); - }; - - Recette.prototype.cuire = function (duree, unite) { - this._noterFonction("cuire"); - return this._ajouterEtape( - "Cuire" + - (duree - ? " pendant " + texte(duree) + (unite ? " " + texte(unite) : "") - : "") + - ".", - "fonction", - ); - }; - - Recette.prototype.attendre = function (duree, unite) { - this._noterFonction("attendre"); - return this._ajouterEtape( - "Attendre " + texte(duree) + (unite ? " " + texte(unite) : "") + ".", - "fonction", - ); - }; - - Recette.prototype.repeter = function (nombre, action) { - this.notions.boucles = true; - this._noterFonction("repeter"); - - var bloc = new Recette("bloc repeter"); - if (typeof action === "function") { - action(bloc); - } - - this._fusionnerNotions(bloc); - return this._ajouterEtape( - "Repeter " + texte(nombre) + " fois :", - "boucle", - bloc.etapes, - ); - }; - - Recette.prototype.si = function (condition, action, sinonAction) { - this.notions.conditions = true; - this._noterFonction("si"); - - var bloc = new Recette("bloc si"); - var blocSinon = new Recette("bloc sinon"); - var conditionVraie = evaluerCondition(condition); - - if (conditionVraie && typeof action === "function") { - action(bloc); - } - - if (!conditionVraie && typeof sinonAction === "function") { - sinonAction(blocSinon); - } - - this._fusionnerNotions(bloc); - this._fusionnerNotions(blocSinon); - - var enfants = bloc.etapes; - if (blocSinon.etapes.length > 0) { - enfants = enfants.concat([ - creerEtape("Sinon :", "condition", blocSinon.etapes), - ]); - } - - return this._ajouterEtape( - "Si " + conditionLisible(condition) + " :", - "condition", - enfants, - ); - }; - - Recette.prototype.servir = function (element) { - this._noterFonction("servir"); - this._ajouterEtape( - "Servir" + (element ? " " + nomDe(element) : "") + ".", - "fonction", - ); - return this.afficher(); - }; - - Recette.prototype.afficher = function () { - var cible; - - if (configuration.sortie === "console" || !configuration.sortie) { - if (typeof console !== "undefined" && console.log) { - console.log(this.toText()); - } - return this; - } - - if ( - typeof configuration.sortie === "string" && - typeof document !== "undefined" - ) { - cible = document.querySelector(configuration.sortie); - if (cible) { - cible.innerHTML = this.toHTML(); - } - } - - return this; - }; - - Recette.prototype._fusionnerNotions = function (autreRecette) { - autreRecette.notions.variables.forEach((nom) => { - this._noterVariable(nom); - }); - autreRecette.notions.constantes.forEach((nom) => { - this._noterConstante(nom); - }); - autreRecette.notions.fonctions.forEach((nom) => { - this._noterFonction(nom); - }); - this.notions.boucles = this.notions.boucles || autreRecette.notions.boucles; - this.notions.conditions = - this.notions.conditions || autreRecette.notions.conditions; - }; - - Recette.prototype._lignesEtapes = function (etapes, prefixe, indentation) { - var lignes = []; - etapes.forEach(function (etape, index) { - var numero = prefixe ? prefixe + "." + (index + 1) : String(index + 1); - lignes.push(indentation + numero + ". " + etape.texte); - if (etape.enfants && etape.enfants.length > 0) { - lignes = lignes.concat( - this._lignesEtapes(etape.enfants, numero, indentation + " "), - ); - } - }, this); - return lignes; - }; - - Recette.prototype.analyser = function () { - var actions = this.notions.fonctions.slice(); - var etapesAPlates = aplatirEtapes(this.etapes); - var derniereAction = - etapesAPlates.length > 0 - ? actionDeTexte(etapesAPlates[etapesAPlates.length - 1].texte) - : ""; - var erreurs = []; - - if (this.etapes.length === 0) { - erreurs.push("Ta recette est vide : ajoute au moins une etape."); - } - - if (this.etapes.length > 0 && derniereAction !== "servir") { - erreurs.push("Ta recette ne se termine pas par servir."); - } - - etapesAPlates.forEach((etape, index) => { - if ( - actionDeTexte(etape.texte) === "servir" && - index !== etapesAPlates.length - 1 - ) { - erreurs.push("Tu as servi avant la fin de la recette."); - } - }); - - return { - nom: this.nom, - ingredients: this.notions.variables.slice(), - variables: this.notions.variables.slice(), - materiels: this.notions.constantes.slice(), - constantes: this.notions.constantes.slice(), - actions: actions, - nombreEtapes: etapesAPlates.length, - utiliseBoucle: this.notions.boucles, - utiliseCondition: this.notions.conditions, - termineParServir: derniereAction === "servir", - erreurs: erreurs, - }; - }; - - Recette.prototype._lignesNotions = function () { - var lignes = [ - "", - "Notions utilisees :", - "- Recette = programme", - "- Sequence = les etapes sont executees dans l'ordre", - ]; - - if (this.notions.variables.length > 0) { - lignes.push( - "- Ingredients = variables : " + this.notions.variables.join(", "), - ); - } - if (this.notions.constantes.length > 0) { - lignes.push( - "- Materiel / valeurs fixes = constantes : " + - this.notions.constantes.join(", "), - ); - } - if (this.notions.fonctions.length > 0) { - lignes.push( - "- Actions = fonctions : " + this.notions.fonctions.join(", "), - ); - } - if (this.notions.boucles) { - lignes.push("- Repeter = boucle"); - } - if (this.notions.conditions) { - lignes.push("- Si = condition"); - } - - return lignes; - }; - - Recette.prototype.toText = function () { - var lignes = ["Recette : " + this.nom, "", "Etapes :"]; - lignes = lignes.concat(this._lignesEtapes(this.etapes, "", "")); - - if (configuration.afficherNotions) { - lignes = lignes.concat(this._lignesNotions()); - } - - return lignes.join("\n"); - }; - - Recette.prototype.toHTML = function () { - var html = '
'; - html += "

Recette : " + echapperHTML(this.nom) + "

"; - html += "

Etapes

"; - html += etapesHTML(this.etapes); - - if (configuration.afficherNotions) { - html += "

Notions utilisees

"; - } - - html += "
"; - return html; - }; - - function aplatirEtapes(etapes) { - var resultat = []; - etapes.forEach((etape) => { - resultat.push(etape); - if (etape.enfants && etape.enfants.length > 0) { - resultat = resultat.concat(aplatirEtapes(etape.enfants)); - } - }); - return resultat; - } - - function actionDeTexte(texteEtape) { - var index; - var valeur = texte(texteEtape).toLowerCase(); - var actions = [ - "prendre", - "utiliser", - "ajouter", - "retirer", - "couper", - "melanger", - "verser", - "chauffer", - "cuire", - "attendre", - "repeter", - "si", - "servir", - ]; - - for (index = 0; index < actions.length; index += 1) { - if (valeur.indexOf(actions[index]) === 0) { - return actions[index]; - } - } - - return ""; - } - - function defi(nom, regles) { - if (!nom && nom !== 0) { - throw new Error("CookieCode : un defi doit avoir un nom."); - } - - return { - type: "defi", - nom: texte(nom), - regles: regles || {}, - }; - } - - function verifier(recetteAVerifier, defiAVerifier) { - var rapport = recetteAVerifier.analyser(); - var regles = defiAVerifier?.regles ? defiAVerifier.regles : {}; - var messages = []; - var reussi = true; - - function valider(condition, succes, echec) { - if (condition) { - messages.push("✅ " + succes); - } else { - reussi = false; - messages.push("❌ " + echec); - } - } - - if (regles.ingredientsMinimum !== undefined) { - valider( - rapport.ingredients.length >= regles.ingredientsMinimum, - "Tu as utilise " + rapport.ingredients.length + " ingredient(s).", - "Il faut au moins " + regles.ingredientsMinimum + " ingredient(s).", - ); - } - - if (regles.materielsMinimum !== undefined) { - valider( - rapport.materiels.length >= regles.materielsMinimum, - "Tu as utilise " + rapport.materiels.length + " materiel(s).", - "Il faut au moins " + regles.materielsMinimum + " materiel(s).", - ); - } - - (regles.actionsObligatoires || []).forEach((action) => { - valider( - rapport.actions.indexOf(action) !== -1, - "Action trouvee : " + action + ".", - "Action manquante : " + action + ".", - ); - }); - - if (regles.boucleObligatoire) { - valider( - rapport.utiliseBoucle, - "Tu as utilise une boucle avec repeter.", - "Il manque une boucle : essaie avec .repeter().", - ); - } - - if (regles.conditionObligatoire) { - valider( - rapport.utiliseCondition, - "Tu as utilise une condition avec si.", - "Il manque une condition : essaie avec .si().", - ); - } - - if (regles.terminerParServir) { - valider( - rapport.termineParServir, - "Ta recette se termine par servir.", - "Ta recette doit se terminer par .servir().", - ); - } - - if (regles.interdireErreurs) { - valider( - rapport.erreurs.length === 0, - "Aucune erreur logique detectee.", - rapport.erreurs.join(" ") || "Une erreur logique a ete detectee.", - ); - } - - if (messages.length === 0) { - messages.push("ℹ️ Aucun critere de defi a verifier."); - } - - return { - reussi: reussi, - nom: defiAVerifier ? defiAVerifier.nom : "Defi", - messages: messages, - rapport: rapport, - toText: function () { - return [ - "Defi : " + this.nom, - this.reussi ? "Resultat : reussi" : "Resultat : a ameliorer", - ] - .concat(this.messages) - .join("\n"); - }, - }; - } - - function etapesHTML(etapes) { - var html = "
    "; - etapes.forEach((etape) => { - html += "
  1. " + echapperHTML(etape.texte); - if (etape.enfants && etape.enfants.length > 0) { - html += etapesHTML(etape.enfants); - } - html += "
  2. "; - }); - html += "
"; - return html; - } - - function recette(nom) { - return new Recette(nom); - } - - var CookieCode = { - configurer: configurer, - recette: recette, - ingredient: ingredient, - materiel: materiel, - constante: constante, - preparation: preparation, - defi: defi, - verifier: verifier, - version: "0.2.0", - }; - - return CookieCode; + var configuration = { + sortie: "console", + afficherNotions: true, + }; + + function configurer(options) { + options = options || {}; + if (Object.hasOwn(options, "sortie")) { + configuration.sortie = options.sortie; + } + if (Object.hasOwn(options, "afficherNotions")) { + configuration.afficherNotions = Boolean(options.afficherNotions); + } + return CookieCode; + } + + function texte(valeur, defaut) { + if (valeur === undefined || valeur === null || valeur === "") { + return defaut || ""; + } + return String(valeur); + } + + function echapperHTML(valeur) { + return texte(valeur) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + } + + function creerElement(type, nom, quantite, unite, valeur) { + if (!nom && nom !== 0) { + throw new Error( + `CookieCode : un nom est obligatoire pour creer un ${type}.`, + ); + } + + return { + type: type, + nom: texte(nom), + quantite: quantite, + unite: unite, + valeur: valeur, + }; + } + + function ingredient(nom, quantite, unite) { + return creerElement("ingredient", nom, quantite, unite); + } + + function materiel(nom) { + return creerElement("materiel", nom); + } + + function constante(nom, valeur) { + return creerElement("constante", nom, undefined, undefined, valeur); + } + + function preparation(nom) { + return { + type: "preparation", + nom: texte(nom, "preparation"), + etat: {}, + definir: function (cle, valeur) { + this.etat[cle] = valeur; + return this; + }, + est: function (valeur) { + return ( + this.etat.texture === valeur || + this.etat.etat === valeur || + this.etat[valeur] === true + ); + }, + }; + } + + function nomDe(element) { + if (element && typeof element === "object" && element.nom !== undefined) { + return texte(element.nom); + } + return texte(element); + } + + function quantiteDe(element, quantite, unite) { + var q = quantite !== undefined ? quantite : element?.quantite; + var u = unite !== undefined ? unite : element?.unite; + + if (q === undefined || q === null || q === "") { + return ""; + } + + return texte(q) + (u ? ` ${texte(u)}` : ""); + } + + function formatElement(element, quantite, unite) { + var quantiteTexte = quantiteDe(element, quantite, unite); + var nom = nomDe(element); + return quantiteTexte ? `${quantiteTexte} de ${nom}` : nom; + } + + function conditionLisible(condition) { + if (typeof condition === "string") { + return condition; + } + if (typeof condition === "boolean") { + return condition ? "condition vraie" : "condition fausse"; + } + if (typeof condition === "function") { + return "condition fournie"; + } + return "condition"; + } + + function evaluerCondition(condition) { + if (typeof condition === "function") { + return Boolean(condition()); + } + if (typeof condition === "boolean") { + return condition; + } + return true; + } + + function creerEtape(texteEtape, notion, enfants) { + return { + texte: texteEtape, + notion: notion, + enfants: enfants || [], + }; + } + + function Recette(nom) { + if (!nom && nom !== 0) { + throw new Error("CookieCode : une recette doit avoir un nom."); + } + + this.nom = texte(nom); + this.etapes = []; + this.notions = { + programme: true, + variables: [], + constantes: [], + fonctions: [], + boucles: false, + conditions: false, + sequence: true, + }; + } + + Recette.prototype._noterFonction = function (nom) { + if (this.notions.fonctions.indexOf(nom) === -1) { + this.notions.fonctions.push(nom); + } + }; + + Recette.prototype._noterVariable = function (nom) { + if (nom && this.notions.variables.indexOf(nom) === -1) { + this.notions.variables.push(nom); + } + }; + + Recette.prototype._noterConstante = function (nom) { + if (nom && this.notions.constantes.indexOf(nom) === -1) { + this.notions.constantes.push(nom); + } + }; + + Recette.prototype._ajouterEtape = function (texteEtape, notion, enfants) { + this.etapes.push(creerEtape(texteEtape, notion, enfants)); + return this; + }; + + Recette.prototype.prendre = function (nom, quantite, unite) { + this._noterVariable(nomDe(nom)); + this._noterFonction("prendre"); + return this._ajouterEtape( + `Prendre ${formatElement(nom, quantite, unite)}.`, + "variable", + ); + }; + + Recette.prototype.utiliser = function (outil) { + this._noterConstante(nomDe(outil)); + this._noterFonction("utiliser"); + return this._ajouterEtape(`Utiliser ${nomDe(outil)}.`, "constante"); + }; + + Recette.prototype.ajouter = function (element, quantite, unite) { + this._noterFonction("ajouter"); + if (element && element.type === "ingredient") { + this._noterVariable(element.nom); + } + return this._ajouterEtape( + `Ajouter ${formatElement(element, quantite, unite)}.`, + "fonction", + ); + }; + + Recette.prototype.retirer = function (element) { + this._noterFonction("retirer"); + return this._ajouterEtape(`Retirer ${nomDe(element)}.`, "fonction"); + }; + + Recette.prototype.couper = function (element, forme) { + this._noterFonction("couper"); + return this._ajouterEtape( + `Couper ${nomDe(element)}${forme ? ` en ${texte(forme)}` : ""}.`, + "fonction", + ); + }; + + Recette.prototype.melanger = function () { + this._noterFonction("melanger"); + return this._ajouterEtape("Melanger.", "fonction"); + }; + + Recette.prototype.verser = function (contenu, contenant) { + this._noterFonction("verser"); + return this._ajouterEtape( + "Verser " + + nomDe(contenu) + + (contenant ? ` dans ${nomDe(contenant)}` : "") + + ".", + "fonction", + ); + }; + + Recette.prototype.chauffer = function (element, temperature) { + this._noterFonction("chauffer"); + return this._ajouterEtape( + "Chauffer " + + nomDe(element) + + (temperature ? ` a ${nomDe(temperature)}` : "") + + ".", + "fonction", + ); + }; + + Recette.prototype.cuire = function (duree, unite) { + this._noterFonction("cuire"); + return this._ajouterEtape( + "Cuire" + + (duree + ? ` pendant ${texte(duree)}${unite ? ` ${texte(unite)}` : ""}` + : "") + + ".", + "fonction", + ); + }; + + Recette.prototype.attendre = function (duree, unite) { + this._noterFonction("attendre"); + return this._ajouterEtape( + `Attendre ${texte(duree)}${unite ? ` ${texte(unite)}` : ""}.`, + "fonction", + ); + }; + + Recette.prototype.repeter = function (nombre, action) { + this.notions.boucles = true; + this._noterFonction("repeter"); + + var bloc = new Recette("bloc repeter"); + if (typeof action === "function") { + action(bloc); + } + + this._fusionnerNotions(bloc); + return this._ajouterEtape( + `Repeter ${texte(nombre)} fois :`, + "boucle", + bloc.etapes, + ); + }; + + Recette.prototype.si = function (condition, action, sinonAction) { + this.notions.conditions = true; + this._noterFonction("si"); + + var bloc = new Recette("bloc si"); + var blocSinon = new Recette("bloc sinon"); + var conditionVraie = evaluerCondition(condition); + + if (conditionVraie && typeof action === "function") { + action(bloc); + } + + if (!conditionVraie && typeof sinonAction === "function") { + sinonAction(blocSinon); + } + + this._fusionnerNotions(bloc); + this._fusionnerNotions(blocSinon); + + var enfants = bloc.etapes; + if (blocSinon.etapes.length > 0) { + enfants = enfants.concat([ + creerEtape("Sinon :", "condition", blocSinon.etapes), + ]); + } + + return this._ajouterEtape( + `Si ${conditionLisible(condition)} :`, + "condition", + enfants, + ); + }; + + Recette.prototype.servir = function (element) { + this._noterFonction("servir"); + this._ajouterEtape( + `Servir${element ? ` ${nomDe(element)}` : ""}.`, + "fonction", + ); + return this.afficher(); + }; + + Recette.prototype.afficher = function () { + var cible; + + if (configuration.sortie === "console" || !configuration.sortie) { + if (typeof console !== "undefined" && console.log) { + console.log(this.toText()); + } + return this; + } + + if ( + typeof configuration.sortie === "string" && + typeof document !== "undefined" + ) { + cible = document.querySelector(configuration.sortie); + if (cible) { + cible.innerHTML = this.toHTML(); + } + } + + return this; + }; + + Recette.prototype._fusionnerNotions = function (autreRecette) { + autreRecette.notions.variables.forEach((nom) => { + this._noterVariable(nom); + }); + autreRecette.notions.constantes.forEach((nom) => { + this._noterConstante(nom); + }); + autreRecette.notions.fonctions.forEach((nom) => { + this._noterFonction(nom); + }); + this.notions.boucles = this.notions.boucles || autreRecette.notions.boucles; + this.notions.conditions = + this.notions.conditions || autreRecette.notions.conditions; + }; + + Recette.prototype._lignesEtapes = function (etapes, prefixe, indentation) { + var lignes = []; + etapes.forEach(function (etape, index) { + var numero = prefixe ? `${prefixe}.${index + 1}` : String(index + 1); + lignes.push(`${indentation + numero}. ${etape.texte}`); + if (etape.enfants && etape.enfants.length > 0) { + lignes = lignes.concat( + this._lignesEtapes(etape.enfants, numero, `${indentation} `), + ); + } + }, this); + return lignes; + }; + + Recette.prototype.analyser = function () { + var actions = this.notions.fonctions.slice(); + var etapesAPlates = aplatirEtapes(this.etapes); + var derniereAction = + etapesAPlates.length > 0 + ? actionDeTexte(etapesAPlates[etapesAPlates.length - 1].texte) + : ""; + var erreurs = []; + + if (this.etapes.length === 0) { + erreurs.push("Ta recette est vide : ajoute au moins une etape."); + } + + if (this.etapes.length > 0 && derniereAction !== "servir") { + erreurs.push("Ta recette ne se termine pas par servir."); + } + + etapesAPlates.forEach((etape, index) => { + if ( + actionDeTexte(etape.texte) === "servir" && + index !== etapesAPlates.length - 1 + ) { + erreurs.push("Tu as servi avant la fin de la recette."); + } + }); + + return { + nom: this.nom, + ingredients: this.notions.variables.slice(), + variables: this.notions.variables.slice(), + materiels: this.notions.constantes.slice(), + constantes: this.notions.constantes.slice(), + actions: actions, + nombreEtapes: etapesAPlates.length, + utiliseBoucle: this.notions.boucles, + utiliseCondition: this.notions.conditions, + termineParServir: derniereAction === "servir", + erreurs: erreurs, + }; + }; + + Recette.prototype._lignesNotions = function () { + var lignes = [ + "", + "Notions utilisees :", + "- Recette = programme", + "- Sequence = les etapes sont executees dans l'ordre", + ]; + + if (this.notions.variables.length > 0) { + lignes.push( + `- Ingredients = variables : ${this.notions.variables.join(", ")}`, + ); + } + if (this.notions.constantes.length > 0) { + lignes.push( + "- Materiel / valeurs fixes = constantes : " + + this.notions.constantes.join(", "), + ); + } + if (this.notions.fonctions.length > 0) { + lignes.push( + `- Actions = fonctions : ${this.notions.fonctions.join(", ")}`, + ); + } + if (this.notions.boucles) { + lignes.push("- Repeter = boucle"); + } + if (this.notions.conditions) { + lignes.push("- Si = condition"); + } + + return lignes; + }; + + Recette.prototype.toText = function () { + var lignes = [`Recette : ${this.nom}`, "", "Etapes :"]; + lignes = lignes.concat(this._lignesEtapes(this.etapes, "", "")); + + if (configuration.afficherNotions) { + lignes = lignes.concat(this._lignesNotions()); + } + + return lignes.join("\n"); + }; + + Recette.prototype.toHTML = function () { + var html = '
'; + html += `

Recette : ${echapperHTML(this.nom)}

`; + html += "

Etapes

"; + html += etapesHTML(this.etapes); + + if (configuration.afficherNotions) { + html += "

Notions utilisees

"; + } + + html += "
"; + return html; + }; + + function aplatirEtapes(etapes) { + var resultat = []; + etapes.forEach((etape) => { + resultat.push(etape); + if (etape.enfants && etape.enfants.length > 0) { + resultat = resultat.concat(aplatirEtapes(etape.enfants)); + } + }); + return resultat; + } + + function actionDeTexte(texteEtape) { + var index; + var valeur = texte(texteEtape).toLowerCase(); + var actions = [ + "prendre", + "utiliser", + "ajouter", + "retirer", + "couper", + "melanger", + "verser", + "chauffer", + "cuire", + "attendre", + "repeter", + "si", + "servir", + ]; + + for (index = 0; index < actions.length; index += 1) { + if (valeur.indexOf(actions[index]) === 0) { + return actions[index]; + } + } + + return ""; + } + + function defi(nom, regles) { + if (!nom && nom !== 0) { + throw new Error("CookieCode : un defi doit avoir un nom."); + } + + return { + type: "defi", + nom: texte(nom), + regles: regles || {}, + }; + } + + function verifier(recetteAVerifier, defiAVerifier) { + var rapport = recetteAVerifier.analyser(); + var regles = defiAVerifier?.regles ? defiAVerifier.regles : {}; + var messages = []; + var reussi = true; + + function valider(condition, succes, echec) { + if (condition) { + messages.push(`✅ ${succes}`); + } else { + reussi = false; + messages.push(`❌ ${echec}`); + } + } + + if (regles.ingredientsMinimum !== undefined) { + valider( + rapport.ingredients.length >= regles.ingredientsMinimum, + `Tu as utilise ${rapport.ingredients.length} ingredient(s).`, + `Il faut au moins ${regles.ingredientsMinimum} ingredient(s).`, + ); + } + + if (regles.materielsMinimum !== undefined) { + valider( + rapport.materiels.length >= regles.materielsMinimum, + `Tu as utilise ${rapport.materiels.length} materiel(s).`, + `Il faut au moins ${regles.materielsMinimum} materiel(s).`, + ); + } + + (regles.actionsObligatoires || []).forEach((action) => { + valider( + rapport.actions.indexOf(action) !== -1, + `Action trouvee : ${action}.`, + `Action manquante : ${action}.`, + ); + }); + + if (regles.boucleObligatoire) { + valider( + rapport.utiliseBoucle, + "Tu as utilise une boucle avec repeter.", + "Il manque une boucle : essaie avec .repeter().", + ); + } + + if (regles.conditionObligatoire) { + valider( + rapport.utiliseCondition, + "Tu as utilise une condition avec si.", + "Il manque une condition : essaie avec .si().", + ); + } + + if (regles.terminerParServir) { + valider( + rapport.termineParServir, + "Ta recette se termine par servir.", + "Ta recette doit se terminer par .servir().", + ); + } + + if (regles.interdireErreurs) { + valider( + rapport.erreurs.length === 0, + "Aucune erreur logique detectee.", + rapport.erreurs.join(" ") || "Une erreur logique a ete detectee.", + ); + } + + if (messages.length === 0) { + messages.push("ℹ️ Aucun critere de defi a verifier."); + } + + return { + reussi: reussi, + nom: defiAVerifier ? defiAVerifier.nom : "Defi", + messages: messages, + rapport: rapport, + toText: function () { + return [ + `Defi : ${this.nom}`, + this.reussi ? "Resultat : reussi" : "Resultat : a ameliorer", + ] + .concat(this.messages) + .join("\n"); + }, + }; + } + + function etapesHTML(etapes) { + var html = "
    "; + etapes.forEach((etape) => { + html += `
  1. ${echapperHTML(etape.texte)}`; + if (etape.enfants && etape.enfants.length > 0) { + html += etapesHTML(etape.enfants); + } + html += "
  2. "; + }); + html += "
"; + return html; + } + + function recette(nom) { + return new Recette(nom); + } + + var CookieCode = { + configurer: configurer, + recette: recette, + ingredient: ingredient, + materiel: materiel, + constante: constante, + preparation: preparation, + defi: defi, + verifier: verifier, + version: "0.2.0", + }; + + return CookieCode; }); diff --git a/test/cookiecode.test.js b/test/cookiecode.test.js index 7ddabda..dd93160 100644 --- a/test/cookiecode.test.js +++ b/test/cookiecode.test.js @@ -1,19 +1,19 @@ -const assert = require("assert"); +const assert = require("node:assert"); const CookieCode = require("../dist/cookiecode.js"); CookieCode.configurer({ sortie: "test", afficherNotions: true }); const texteRecette = CookieCode.recette("Cookie au chocolat") - .prendre("farine", 200, "g") - .prendre("sucre", 100, "g") - .utiliser("saladier") - .ajouter("farine") - .melanger() - .repeter(2, (recette) => { - recette.melanger(); - }) - .cuire(12, "minutes") - .toText(); + .prendre("farine", 200, "g") + .prendre("sucre", 100, "g") + .utiliser("saladier") + .ajouter("farine") + .melanger() + .repeter(2, (recette) => { + recette.melanger(); + }) + .cuire(12, "minutes") + .toText(); assert.match(texteRecette, /Recette : Cookie au chocolat/); assert.match(texteRecette, /1\. Prendre 200 g de farine\./); @@ -27,21 +27,21 @@ assert.match(texteRecette, /Repeter = boucle/); const pate = CookieCode.preparation("pate").definir("texture", "trop seche"); const texteCondition = CookieCode.recette("Pate a cookies") - .si( - () => pate.est("trop seche"), - (recette) => { - recette.ajouter("lait", 20, "ml"); - }, - ) - .toText(); + .si( + () => pate.est("trop seche"), + (recette) => { + recette.ajouter("lait", 20, "ml"); + }, + ) + .toText(); assert.match(texteCondition, /Si condition fournie :/); assert.match(texteCondition, /1\.1\. Ajouter 20 ml de lait\./); assert.match(texteCondition, /Si = condition/); const html = CookieCode.recette("") - .ajouter("") - .toHTML(); + .ajouter("") + .toHTML(); assert.ok(!html.includes("