Initial CookieCode MVP
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/*.map
|
||||||
|
.env
|
||||||
|
.DS_Store
|
||||||
@@ -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.
|
||||||
Vendored
+477
@@ -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, """)
|
||||||
|
.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 = '<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;
|
||||||
|
});
|
||||||
@@ -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();
|
||||||
@@ -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>
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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 !")
|
||||||
@@ -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, """)
|
||||||
|
.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 = '<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;
|
||||||
|
});
|
||||||
@@ -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("<script>alert"));
|
||||||
|
assert.ok(html.includes("<img src=x"));
|
||||||
|
|
||||||
|
assert.strictEqual(CookieCode.version, "0.1.0");
|
||||||
|
|
||||||
|
console.log("CookieCode tests: OK");
|
||||||
Reference in New Issue
Block a user