Initial CookieCode MVP

This commit is contained in:
2026-07-21 22:12:43 +02:00
commit 5a388d3d60
10 changed files with 1266 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
node_modules/
dist/*.map
.env
.DS_Store
+137
View File
@@ -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 `<script>` ;
- exposer une variable globale `CookieCode` ;
- proposer une API chainable proche du langage naturel ;
- afficher une trace de recette et une lecture algorithmique ;
- rester sans dependance.
## Utilisation dans une page
```html
<div id="app"></div>
<script src="dist/cookiecode.js"></script>
<script>
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, function (recette) {
recette.melanger();
})
.cuire(12, "minutes")
.servir();
</script>
```
## 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.
+477
View File
@@ -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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
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 = '<section class="cookiecode-recette">';
html += "<h2>Recette : " + echapperHTML(this.nom) + "</h2>";
html += "<h3>Etapes</h3>";
html += etapesHTML(this.etapes);
if (configuration.afficherNotions) {
html += "<h3>Notions utilisees</h3><ul>";
this._lignesNotions()
.slice(2)
.forEach((ligne) => {
html += "<li>" + echapperHTML(ligne.replace(/^- /, "")) + "</li>";
});
html += "</ul>";
}
html += "</section>";
return html;
};
function etapesHTML(etapes) {
var html = "<ol>";
etapes.forEach((etape) => {
html += "<li>" + echapperHTML(etape.texte);
if (etape.enfants && etape.enfants.length > 0) {
html += etapesHTML(etape.enfants);
}
html += "</li>";
});
html += "</ol>";
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;
});
+19
View File
@@ -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();
+18
View File
@@ -0,0 +1,18 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CookieCode - Demo</title>
</head>
<body>
<main>
<h1>CookieCode</h1>
<p>Une recette devient un programme : les ingredients sont des variables, les actions sont des fonctions.</p>
<div id="app" aria-live="polite"></div>
</main>
<script src="../dist/cookiecode.js"></script>
<script src="./demo.js"></script>
</body>
</html>
+26
View File
@@ -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
+18
View File
@@ -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"
}
+37
View File
@@ -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 !")
+477
View File
@@ -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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
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 = '<section class="cookiecode-recette">';
html += "<h2>Recette : " + echapperHTML(this.nom) + "</h2>";
html += "<h3>Etapes</h3>";
html += etapesHTML(this.etapes);
if (configuration.afficherNotions) {
html += "<h3>Notions utilisees</h3><ul>";
this._lignesNotions()
.slice(2)
.forEach((ligne) => {
html += "<li>" + echapperHTML(ligne.replace(/^- /, "")) + "</li>";
});
html += "</ul>";
}
html += "</section>";
return html;
};
function etapesHTML(etapes) {
var html = "<ol>";
etapes.forEach((etape) => {
html += "<li>" + echapperHTML(etape.texte);
if (etape.enfants && etape.enfants.length > 0) {
html += etapesHTML(etape.enfants);
}
html += "</li>";
});
html += "</ol>";
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;
});
+53
View File
@@ -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("<script>alert('xss')</script>")
.ajouter("<img src=x onerror=alert(1)>")
.toHTML();
assert.ok(!html.includes("<script>alert"));
assert.ok(!html.includes("<img src=x"));
assert.ok(html.includes("&lt;script&gt;alert"));
assert.ok(html.includes("&lt;img src=x"));
assert.strictEqual(CookieCode.version, "0.1.0");
console.log("CookieCode tests: OK");