478 lines
12 KiB
JavaScript
478 lines
12 KiB
JavaScript
/*
|
|
* 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;
|
|
});
|