From 5a388d3d60653e4b48d22d597d207a56a3a74b48 Mon Sep 17 00:00:00 2001 From: kazerlelutin Date: Tue, 21 Jul 2026 22:12:43 +0200 Subject: [PATCH] Initial CookieCode MVP --- .gitignore | 4 + README.md | 137 +++++++++++ dist/cookiecode.js | 477 ++++++++++++++++++++++++++++++++++++ examples/demo.js | 19 ++ examples/index.html | 18 ++ features/cookiecode.feature | 26 ++ package.json | 18 ++ recette.lua | 37 +++ src/cookiecode.js | 477 ++++++++++++++++++++++++++++++++++++ test/cookiecode.test.js | 53 ++++ 10 files changed, 1266 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 dist/cookiecode.js create mode 100644 examples/demo.js create mode 100644 examples/index.html create mode 100644 features/cookiecode.feature create mode 100644 package.json create mode 100644 recette.lua create mode 100644 src/cookiecode.js create mode 100644 test/cookiecode.test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9eb15d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/*.map +.env +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..c8dbe97 --- /dev/null +++ b/README.md @@ -0,0 +1,137 @@ +# CookieCode + +CookieCode est une microlibrairie JavaScript educative pour apprendre les bases de l'algorithmique avec des recettes de cuisine. + +L'idee : + +| Cuisine | Programmation | +| --- | --- | +| Recette | Programme | +| Ingredients | Variables | +| Materiel | Constantes / ressources | +| Etapes | Instructions | +| Actions culinaires | Fonctions | +| `si` | Condition | +| `repeter` | Boucle | + +## Objectif du MVP + +- fonctionner directement dans une page HTML via une balise ` + +``` + +## API principale + +### Creation + +```js +CookieCode.recette("Cookies"); +CookieCode.ingredient("farine", 200, "g"); +CookieCode.materiel("saladier"); +CookieCode.constante("temperature", 180); +CookieCode.preparation("pate"); +``` + +### Methodes d'une recette + +```js +.prendre(nom, quantite, unite) +.utiliser(materiel) +.ajouter(element, quantite, unite) +.retirer(element) +.couper(element, forme) +.melanger() +.verser(contenu, contenant) +.chauffer(element, temperature) +.cuire(duree, unite) +.attendre(duree, unite) +.repeter(nombre, action) +.si(condition, action, sinonAction) +.servir(element) +.afficher() +.toText() +.toHTML() +``` + +## Exemple avec variables + +```js +const farine = CookieCode.ingredient("farine", 200, "g"); +const sucre = CookieCode.ingredient("sucre", 100, "g"); +const saladier = CookieCode.materiel("saladier"); + +const recette = CookieCode.recette("Cookies") + .utiliser(saladier) + .ajouter(farine) + .ajouter(sucre) + .melanger(); + +console.log(recette.toText()); +``` + +## Exemple avec boucle et condition + +```js +const pate = CookieCode.preparation("pate").definir("texture", "trop seche"); + +CookieCode.recette("Pate a cookies") + .ajouter("farine", 200, "g") + .si(function () { + return pate.est("trop seche"); + }, function (recette) { + recette.ajouter("lait", 20, "ml"); + }) + .repeter(3, function (recette) { + recette.melanger(); + }) + .servir(); +``` + +## Cartes pedagogiques conseillees + +| Type de carte | Mots cles | Notion | +| --- | --- | --- | +| Structure | `recette`, `servir` | programme / resultat | +| Donnee | `ingredient`, `quantite`, `unite` | variable / valeur | +| Materiel | `materiel`, `utiliser` | constante / ressource | +| Action | `prendre`, `ajouter`, `couper`, `melanger`, `cuire` | fonction | +| Logique | `si`, `sinon`, `repeter` | condition / boucle | + +## Developpement + +```sh +npm test +npm run build +``` + +Aucune dependance n'est necessaire pour lancer les tests du MVP. diff --git a/dist/cookiecode.js b/dist/cookiecode.js new file mode 100644 index 0000000..1f263a6 --- /dev/null +++ b/dist/cookiecode.js @@ -0,0 +1,477 @@ +/* + * CookieCode - apprendre l'algorithmique avec des recettes de cuisine. + * 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(); + } +})(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._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 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, + version: "0.1.0", + }; + + return CookieCode; +}); diff --git a/examples/demo.js b/examples/demo.js new file mode 100644 index 0000000..01e5cf0 --- /dev/null +++ b/examples/demo.js @@ -0,0 +1,19 @@ +CookieCode.configurer({ + sortie: "#app", + afficherNotions: true, +}); + +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(); diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 0000000..0d464e6 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,18 @@ + + + + + + CookieCode - Demo + + +
+

CookieCode

+

Une recette devient un programme : les ingredients sont des variables, les actions sont des fonctions.

+
+
+ + + + + diff --git a/features/cookiecode.feature b/features/cookiecode.feature new file mode 100644 index 0000000..ba653a1 --- /dev/null +++ b/features/cookiecode.feature @@ -0,0 +1,26 @@ +Feature: Apprendre l'algorithmique avec CookieCode + En tant qu'apprenant debutant + Je veux ecrire une recette comme un programme + Afin de comprendre les variables, fonctions, boucles et conditions + + Scenario: Afficher une recette simple avec les notions de base + Given une page integre CookieCode via une balise script + When l'apprenant cree une recette avec des ingredients et des actions + Then la recette affiche les etapes dans l'ordre + And la lecture algorithmique indique que la recette est un programme + And la lecture algorithmique indique que les ingredients sont des variables + And la lecture algorithmique indique que les actions sont des fonctions + + Scenario: Representer une boucle avec repeter + Given une recette CookieCode + When l'apprenant ajoute une etape repeter 2 fois + Then la recette affiche une etape de repetition + And les actions repetees sont affichees comme sous-etapes + And la lecture algorithmique indique que repeter est une boucle + + Scenario: Representer une condition avec si + Given une preparation avec un etat observable + When l'apprenant ajoute une condition vraie + Then la recette affiche l'etape conditionnelle + And les actions du bloc conditionnel sont affichees + And la lecture algorithmique indique que si est une condition diff --git a/package.json b/package.json new file mode 100644 index 0000000..08030d9 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "cookiecode", + "version": "0.1.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/recette.lua b/recette.lua new file mode 100644 index 0000000..07dc3b0 --- /dev/null +++ b/recette.lua @@ -0,0 +1,37 @@ +-- Script simple en Lua pour afficher une recette de cuisine + +local recette = { + nom = "Omelette nature", + personnes = 1, + ingredients = { + "2 oeufs", + "1 pincee de sel", + "1 pincee de poivre", + "1 noisette de beurre" + }, + etapes = { + "Casser les oeufs dans un bol.", + "Ajouter le sel et le poivre, puis battre avec une fourchette.", + "Faire fondre le beurre dans une poele chaude.", + "Verser les oeufs battus dans la poele.", + "Cuire 2 a 3 minutes, puis servir chaud." + } +} + +local function afficher_liste(titre, elements) + print(titre) + for index, element in ipairs(elements) do + print(index .. ". " .. element) + end + print("") +end + +print("=== Recette de cuisine ===") +print("Nom : " .. recette.nom) +print("Pour : " .. recette.personnes .. " personne") +print("") + +afficher_liste("Ingredients :", recette.ingredients) +afficher_liste("Etapes :", recette.etapes) + +print("Bon appetit !") diff --git a/src/cookiecode.js b/src/cookiecode.js new file mode 100644 index 0000000..1f263a6 --- /dev/null +++ b/src/cookiecode.js @@ -0,0 +1,477 @@ +/* + * CookieCode - apprendre l'algorithmique avec des recettes de cuisine. + * 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(); + } +})(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._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 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, + version: "0.1.0", + }; + + return CookieCode; +}); diff --git a/test/cookiecode.test.js b/test/cookiecode.test.js new file mode 100644 index 0000000..33a9888 --- /dev/null +++ b/test/cookiecode.test.js @@ -0,0 +1,53 @@ +const assert = require("assert"); +const CookieCode = require("../dist/cookiecode.js"); + +CookieCode.configurer({ sortie: null, 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(); + +assert.match(texteRecette, /Recette : Cookie au chocolat/); +assert.match(texteRecette, /1\. Prendre 200 g de farine\./); +assert.match(texteRecette, /2\. Prendre 100 g de sucre\./); +assert.match(texteRecette, /3\. Utiliser saladier\./); +assert.match(texteRecette, /6\. Repeter 2 fois :/); +assert.match(texteRecette, /6\.1\. Melanger\./); +assert.match(texteRecette, /Ingredients = variables : farine, sucre/); +assert.match(texteRecette, /Materiel \/ valeurs fixes = constantes : saladier/); +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(); + +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(); + +assert.ok(!html.includes("