Skip to content
Snippets Groups Projects
Commit 51b870d5 authored by Millicent Billette's avatar Millicent Billette
Browse files

g1lib extracted from gsper

parents
No related branches found
No related tags found
No related merge requests found
*.log*
*generated*
node_modules/
.idea/
*.exe
stages:
- build
- test
- publish
build:
stage: build
image: node:latest
script:
- npm install
- npm run postinstall
- npm run build
artifacts:
untracked: true
paths:
- generated
expire_in: 10 minutes
only:
- master
- tags
pages:
stage: test
image: node:latest
script:
- npm run test:production
# coverage report publish in pages
artifacts:
untracked: true
paths:
- public
only:
- tags
##--------------------------------PUBLISH-------------------------------------------------------
npm:
stage: publish
image: node:latest
# dependencies: ["build"]
script:
- cd generated/npm
- echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}'>.npmrc
- npm publish
only:
- tags
[EN](CHANGELOG.md) | FR
# Changelog | liste des changements
Tous les changements notables de ce projet seront documenté dans ce fichier.
Le format est basé sur [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
et ce projet adhère au [versionnage sémantique](https://semver.org/spec/v2.0.0.html).
## Evolutions probable / Roadmap :
- GraphQL stuff
- ::: comme séparateur entre identifiant secret et mdp pour la génération de combinaison à tester (usage principal Gsper)
## [Non-publié/Non-Stabilisé] (par [1000i100])
### Ajouté
## [Version 2.1.0] - 2018-06-27 (par [1000i100])
### Ajouté
- syntaxe =référence> pour faire des références syncronisé et éviter de générer des variantes non souhaitées.
## [Version 2.0.0] - 2018-05-10 (par [1000i100])
### Ajouté
##### Générateur de variantes de mot de passe
- Déclinaisons avec Majuscules
- Désaccentuation
- Déclinaison avancées façon expression régulière
##### Documentation
- Rédaction d'une documentation des générateur de variante de mot de passe
##### Améliorations technique
- Ajout de test unitaire (meilleur fiabilité).
- Différentiation de la lib pour la partie crypto et de celle de génération de variantes (meilleur maintenabilité et évolutivité).
## [Version 1.0.1 (Proof of Concept)] - 2018-04-18 (par [1000i100])
### Ajouté
##### Algorithme
- intégration des librairies de crypto nécessaires
- calcul de la clef publique correspondant à chaque combinaison de secrets saisie, et comparaison à la clef publique de référence.
[Non-publié/Non-Stabilisé]: https://framagit.org/1000i100/gsper/-/compare/v2.1.0...master
[Version 2.1.0]: https://framagit.org/1000i100/gsper/-/compare/v2.0.0...v2.1.0
[Version 2.0.0]: https://framagit.org/1000i100/gsper/-/compare/v1.0.1...v2.0.0
[Version 1.0.1 (Proof of Concept)]: https://framagit.org/1000i100/gsper/-/tree/v1.0.1
[1000i100]: https://framagit.org/1000i100 "@1000i100"
EN | [FR](CHANGELOG.fr.md)
English translation not released yet, see up-to-date FR version.
(Pas de version anglaise pour l'instant, consultez la version française).
\ No newline at end of file
const fs = require("fs");
try{fs.writeFileSync("generated/vendors/nacl.js",(fs.readFileSync("node_modules/tweetnacl/nacl-fast.js","utf8"))
.replace("(function(nacl) {","var nacl = {};")
.replace("})(typeof module !== 'undefined' && module.exports ? module.exports : (self.nacl = self.nacl || {}));","export default nacl;")
,"utf8");} catch (e) {console.error(e);}
try{fs.writeFileSync("generated/vendors/scrypt.js",(fs.readFileSync("node_modules/scrypt-async-modern/dist/index.js","utf8"))
.replace("exports.default = scrypt;","export default scrypt;")
.replace(`Object.defineProperty(exports, "__esModule", { value: true });`,"")
,"utf8");} catch (e) {console.error(e);}
This diff is collapsed.
[![pipeline status](https://framagit.org/g1/g1lib.js/badges/main/pipeline.svg)](https://framagit.org/g1/g1lib.js/-/commits/main)
[![coverage report](https://framagit.org/g1/g1lib.js/badges/main/coverage.svg)](https://framagit.org/g1/g1lib.js/-/commits/main)
[![maintainability](https://mycelia.tools/ci/mycelia-front-app/plato/maintainability.svg)](https://mycelia.tools/ci/mycelia-front-app/plato/)
[![release](https://img.shields.io/npm/v/g1lib.svg)](https://www.npmjs.com/package/g1lib)
[![usage as download](https://img.shields.io/npm/dy/g1lib.svg)](https://www.npmjs.com/package/g1lib)
# G1lib.js
Une lib javascript (avec éventuellement du web assembly / wasm issue de rust) pour développer des clients Ǧ1 modulaire fiable et maintenable.
L'idée est de favoriser le développement d'outils serverless ou assimilé, dont l'essentiel du fonctionnement à lieu coté client (dans le navigateur).
Transformable en application mobile ou extension navigateur grace aux technos de PWA (Progressive Web App).
G1lib.js se veux utilisable aussi bien dans le navigateur que coté serveur (ubiquitous javascript),
sans dépendances (sauf lib de crypto actuellement), packagé sans aucune dépendance (static package) pour en faciliter l'usage.
G1lib.js est couvert par des tests unitaires avec une couverture de test aussi proche de 100% que possible.
G1lib.js est une bibliothèque de code dédié à l'écosystème Ǧ1 / Duniter.
Elle est destinée à être inclue dans des clients web, PWA ou ligne de commande (cli).
Sa responsabilité n'est pas d'être utilisable directement en tant que client mais d'être utilisé par ces derniers
pour mutualiser du code métier fiable, maintenable, facile à auditer et facilement accessible à la contribution.
## Usage
Installez nodejs puis en cli :
```
npm install --save g1lib
```
En js :
```
import * as g1lib from "g1lib"
```
Si vous souhaitez la lib en asm, cjs ou autre packaging, [demandez-le](https://framagit.org/g1/g1lib.js/-/issues)
## Contribuer
Vous pouvez proposer des évolutions sous forme de [ticket](https://framagit.org/g1/g1lib.js/-/issues) aussi bien que des demandes d'aide (en français ou en anglais).
Les merge-request sont bienvenue.
Elles seront acceptées si :
- elles respectent la philosophie de g1lib.js
- elles sont couvertes par des tests unitaires
- leur code respecte les bonnes pratiques décrites dans l'ouvrage [Clean Code / Coder proprement](https://dl.leneveu.fr/public/Coder_Proprement.pdf) ISBN : 978-2-7440-4104-4
Si vous avez besoin d'aide pour respecter les critères d'acceptation, n'hésitez pas à demander (par [ticket](https://framagit.org/g1/g1lib.js/-/issues) ou [email](https://1forma-tic.fr/#contact)).
## Financer le développement
Les dons sont bienvenus en Ǧ1 comme en UNL.
Actuellement L'auteur et mainteneur de G1Lib.js est [1000i100] Millicent Billette.
En fin de [CGV](https://1forma-tic.fr/#CGV) sur mon site pro, vous trouverez le nécessaire pour me soutenir.
{
"name": "g1lib",
"version": "3.0.0",
"description": "reliable toolbox for Ǧ1 ecosystem javascript developpement",
"main": "all.mjs",
"author": {
"name": "[1000i100] Millicent Billette",
"email": "git@1000i100.fr",
"url": "https://1forma-tic.fr/"
},
"funding": {
"url": "https://framagit.org/g1/g1lib.js"
},
"homepage": "https://framagit.org/g1/g1lib.js",
"repository": {
"type": "git",
"url": "https://framagit.org/g1/g1lib.js.git"
},
"bugs": {
"url": "https://framagit.org/g1/g1lib.js/-/issues"
},
"license": "AGPL-3.0",
"ava": {},
"nyc": {
"report-dir": "../coverage",
"temp-dir": "../nyc_output.temp",
"reporter": [
"text-summary",
"html"
]
}
}
{
"private": true,
"author": "[1000i100] Millicent Billette <git@1000i100.fr> (https://1forma-tic.fr/)",
"scripts": {
"postinstall": "run-s clean build",
"clean": "rm -rf generated*",
"build": "run-s build:**",
"build:mk:npm": "mkdirp generated/npm",
"build:mk:vendors": "mkdirp generated/vendors",
"build:vendors": "node CI/build.js",
"build:npm:all": "rollup src/all.mjs --format esm --file generated/npm/all.mjs",
"build:npm:crypto": "rollup src/crypto.mjs --format esm --file generated/npm/crypto.mjs",
"build:npm:dictionaryBuilder": "rollup src/dictionaryBuilder.mjs --format esm --file generated/npm/dictionaryBuilder.mjs",
"build:npm:cp": "cp npm/* generated/npm/",
"test": "run-s test:dev",
"test:dev": "run-p test:dev:**",
"test:dev:qualityCheck": "xo",
"test:dev:runTests": "ava",
"test:dev:duplication": "jscpd",
"test:production": "run-s test:production:**",
"test:production:qualityCheck": "xo",
"test:production:duplication": "jscpd",
"test:production:complexity-report": "./node_modules/.bin/es6-plato -r -d generated/complexity-report src",
"test:production:test2npm": "cp src/*.test.js generated/npm/",
"test:production:runTests": "cd generated/npm/ && nyc ava",
"test:production:clean": "rm generated/npm/*.test.js"
},
"dependencies": {
"scrypt-async-modern": "^3.0.12",
"tweetnacl": "^1.0.3"
},
"devDependencies": {
"ava": "^3.14.0",
"es6-plato": "^1.2.3",
"jscpd": "^3.3.22",
"mkdirp": "^1.0.4",
"npm-run-all": "^4.1.5",
"nyc": "^15.1.0",
"rollup": "^2.34.2",
"xo": "^0.36.1"
},
"ava": {},
"jscpd": {
"threshold": 0.1,
"reporters": ["html", "console", "badge"],
"gitignore": true,
"output": "generated/jscpd/"
}
}
import * as crypto from "./crypto.mjs";
import * as dictionaryBuilder from "./dictionaryBuilder.mjs";
export {crypto,dictionaryBuilder};
export {b58,b16, saltPass2seed, seed2keyPair,idSecPass2rawAll, raw2b58, idSecPass2cleanKeys}
import nacl from "../generated/vendors/nacl.js";
import scrypt from "../generated/vendors/scrypt.js";
async function idSecPass2rawAll(idSec,pass) {
const rawSeed = await saltPass2seed(idSec,pass);
const keyPair = seed2keyPair(rawSeed);
return {
seed:rawSeed,
publicKey:keyPair.publicKey,
secretKey:keyPair.secretKey
}
}
function raw2b58(raws){
const res = {};
for(let r in raws) res[r] = b58.encode(raws[r]);
return res;
}
async function idSecPass2cleanKeys(idSec,pass){
const raw = await idSecPass2rawAll(idSec,pass);
return Object.assign(raw2b58(raw),{idSec,password:pass});
}
function seed2keyPair(seed){
return nacl.sign.keyPair.fromSeed(seed);
}
async function saltPass2seed(idSec,pass) {
const options = {
logN: 12,
r: 16,
p: 1,
//dkLen: 32,
encoding: 'binary'
};
return await scrypt(pass.normalize('NFKC'), idSec.normalize('NFKC'), options);
}
//inspired by bs58 and base-x module
const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
const b58 = basex(ALPHABET);
const b16 = basex('0123456789abcdef');
function basex (ALPHABET) {
const ALPHABET_MAP = {};
const BASE = ALPHABET.length;
const LEADER = ALPHABET.charAt(0);
// pre-compute lookup table
for (let z = 0; z < ALPHABET.length; z++) {
let x = ALPHABET.charAt(z);
if (ALPHABET_MAP[x] !== undefined) throw new TypeError(x + ' is ambiguous');
ALPHABET_MAP[x] = z;
}
function encode (source) {
if (source.length === 0) return '';
const digits = [0];
for (let i = 0; i < source.length; ++i) {
let carry = source[i];
for (let j = 0; j < digits.length; ++j) {
carry += digits[j] << 8;
digits[j] = carry % BASE;
carry = (carry / BASE) | 0;
}
while (carry > 0) { digits.push(carry % BASE); carry = (carry / BASE) | 0; }
}
let string = '';
for (let k = 0; source[k] === 0 && k < source.length - 1; ++k) string += LEADER; // deal with leading zeros
for (let q = digits.length - 1; q >= 0; --q) string += ALPHABET[digits[q]]; // convert digits to a string
return string;
}
function decodeUnsafe (string) {
if (typeof string !== 'string') throw new TypeError('Expected String');
if (string.length === 0) return new Uint8Array(0);
const bytes = [0];
for (let i = 0; i < string.length; i++) {
const value = ALPHABET_MAP[string[i]];
if (value === undefined) return ;
let carry = value;
for (let j = 0; j < bytes.length; ++j) {
carry += bytes[j] * BASE;
bytes[j] = carry & 0xff;
carry >>= 8;
}
while (carry > 0) { bytes.push(carry & 0xff); carry >>= 8; }
}
for (let k = 0; string[k] === LEADER && k < string.length - 1; ++k) bytes.push(0); // deal with leading zeros
return new Uint8Array(bytes.reverse());
}
function decode (string) {
const buffer = decodeUnsafe(string);
if (buffer) return buffer;
throw new Error('Non-base' + BASE + ' character')
}
return { encode, decodeUnsafe, decode }
}
const idSec = "a";
const mdp = "b";
//base58
const pubKey = "AoxVA41dGL2s4ogMNdbCw3FFYjFo5FPK36LuiW1tjGbG";
const secretKey = "3ZsmZhnRv137dS1s7Q3jFGKLTDyhkwguPHfnWBxzDCTTHKWGnYw9zBk3gcCUJCc72TEUuyzM7cqpo7c5LYhs1Qtv";
const seed = "9eADqX8V6VcPdJCHCVYiE1Vnift9nFNrvr9aTaXA5RJc";
import * as app from "./crypto.mjs";
describe('crypto', () => {
it('b58 should decode/encode well', () => {
expect(app.b58.encode(app.b58.decode(pubKey))).toEqual(pubKey);
});
it('saltPass2seed should convert salt & password to seed with scrypt', async () => {
expect(app.b58.encode(await app.saltPass2seed(idSec,mdp))).toEqual(seed);
});
it('seed2keyPair should generate public and private key nacl/sodium way.', async () => {
const rawSeed = app.b58.decode(seed);
const rawKeyPair = app.seed2keyPair(rawSeed);
expect(app.b58.encode(rawKeyPair.publicKey)).toEqual(pubKey);
expect(app.b58.encode(rawKeyPair.secretKey)).toEqual(secretKey);
});
it('idSecPass2cleanKeys should output clean base58 keys and seed', async () => {
const res = await app.idSecPass2cleanKeys(idSec,mdp);
expect(res.publicKey).toEqual(pubKey);
expect(res.secretKey).toEqual(secretKey);
expect(res.seed).toEqual(seed);
expect(res.idSec).toEqual(idSec);
expect(res.password).toEqual(mdp);
});
});
export {dedup,noAccentVariant,casesVariants,regLikeVariants,resetCache}
function dedup(array) {
const res = {};
array.forEach(v=>res[v]=v);
return Object.keys(res);
}
function noAccentVariant(string) {
const conversionMap = {'Á':'A','Ă':'A','':'A','':'A','':'A','':'A','':'A','Ǎ':'A','Â':'A','':'A','':'A','':'A','':'A','':'A','Ä':'A','Ǟ':'A','Ȧ':'A','Ǡ':'A','':'A','Ȁ':'A','À':'A','':'A','Ȃ':'A','Ā':'A','Ą':'A','Å':'A','Ǻ':'A','':'A','Ⱥ':'A','Ã':'A','':'AA','Æ':'AE','Ǽ':'AE','Ǣ':'AE','':'AO','':'AU','':'AV','':'AV','':'AY','':'B','':'B','Ɓ':'B','':'B','Ƀ':'B','Ƃ':'B','Ć':'C','Č':'C','Ç':'C','':'C','Ĉ':'C','Ċ':'C','Ƈ':'C','Ȼ':'C','Ď':'D','':'D','':'D','':'D','':'D','Ɗ':'D','':'D','Dz':'D','Dž':'D','Đ':'D','Ƌ':'D','DZ':'DZ','DŽ':'DZ','É':'E','Ĕ':'E','Ě':'E','Ȩ':'E','':'E','Ê':'E','':'E','':'E','':'E','':'E','':'E','':'E','Ë':'E','Ė':'E','':'E','Ȅ':'E','È':'E','':'E','Ȇ':'E','Ē':'E','':'E','':'E','Ę':'E','Ɇ':'E','':'E','':'E','':'ET','':'F','Ƒ':'F','Ǵ':'G','Ğ':'G','Ǧ':'G','Ģ':'G','Ĝ':'G','Ġ':'G','Ɠ':'G','':'G','Ǥ':'G','':'H','Ȟ':'H','':'H','Ĥ':'H','':'H','':'H','':'H','':'H','Ħ':'H','Í':'I','Ĭ':'I','Ǐ':'I','Î':'I','Ï':'I','':'I','İ':'I','':'I','Ȉ':'I','Ì':'I','':'I','Ȋ':'I','Ī':'I','Į':'I','Ɨ':'I','Ĩ':'I','':'I','':'D','':'F','':'G','':'R','':'S','':'T','':'IS','Ĵ':'J','Ɉ':'J','':'K','Ǩ':'K','Ķ':'K','':'K','':'K','':'K','Ƙ':'K','':'K','':'K','':'K','Ĺ':'L','Ƚ':'L','Ľ':'L','Ļ':'L','':'L','':'L','':'L','':'L','':'L','':'L','Ŀ':'L','':'L','Lj':'L','Ł':'L','LJ':'LJ','':'M','':'M','':'M','':'M','Ń':'N','Ň':'N','Ņ':'N','':'N','':'N','':'N','Ǹ':'N','Ɲ':'N','':'N','Ƞ':'N','Nj':'N','Ñ':'N','NJ':'NJ','Ó':'O','Ŏ':'O','Ǒ':'O','Ô':'O','':'O','':'O','':'O','':'O','':'O','Ö':'O','Ȫ':'O','Ȯ':'O','Ȱ':'O','':'O','Ő':'O','Ȍ':'O','Ò':'O','':'O','Ơ':'O','':'O','':'O','':'O','':'O','':'O','Ȏ':'O','':'O','':'O','Ō':'O','':'O','':'O','Ɵ':'O','Ǫ':'O','Ǭ':'O','Ø':'O','Ǿ':'O','Õ':'O','':'O','':'O','Ȭ':'O','Ƣ':'OI','':'OO','Ɛ':'E','Ɔ':'O','Ȣ':'OU','':'P','':'P','':'P','Ƥ':'P','':'P','':'P','':'P','':'Q','':'Q','Ŕ':'R','Ř':'R','Ŗ':'R','':'R','':'R','':'R','Ȑ':'R','Ȓ':'R','':'R','Ɍ':'R','':'R','':'C','Ǝ':'E','Ś':'S','':'S','Š':'S','':'S','Ş':'S','Ŝ':'S','Ș':'S','':'S','':'S','':'S','Ť':'T','Ţ':'T','':'T','Ț':'T','Ⱦ':'T','':'T','':'T','Ƭ':'T','':'T','Ʈ':'T','Ŧ':'T','':'A','':'L','Ɯ':'M','Ʌ':'V','':'TZ','Ú':'U','Ŭ':'U','Ǔ':'U','Û':'U','':'U','Ü':'U','Ǘ':'U','Ǚ':'U','Ǜ':'U','Ǖ':'U','':'U','':'U','Ű':'U','Ȕ':'U','Ù':'U','':'U','Ư':'U','':'U','':'U','':'U','':'U','':'U','Ȗ':'U','Ū':'U','':'U','Ų':'U','Ů':'U','Ũ':'U','':'U','':'U','':'V','':'V','Ʋ':'V','':'V','':'VY','':'W','Ŵ':'W','':'W','':'W','':'W','':'W','':'W','':'X','':'X','Ý':'Y','Ŷ':'Y','Ÿ':'Y','':'Y','':'Y','':'Y','Ƴ':'Y','':'Y','':'Y','Ȳ':'Y','Ɏ':'Y','':'Y','Ź':'Z','Ž':'Z','':'Z','':'Z','Ż':'Z','':'Z','Ȥ':'Z','':'Z','Ƶ':'Z','IJ':'IJ','Œ':'OE','':'A','':'AE','ʙ':'B','':'B','':'C','':'D','':'E','':'F','ɢ':'G','ʛ':'G','ʜ':'H','ɪ':'I','ʁ':'R','':'J','':'K','ʟ':'L','':'L','':'M','ɴ':'N','':'O','ɶ':'OE','':'O','':'OU','':'P','ʀ':'R','':'N','':'R','':'S','':'T','':'E','':'R','':'U','':'V','':'W','ʏ':'Y','':'Z','á':'a','ă':'a','':'a','':'a','':'a','':'a','':'a','ǎ':'a','â':'a','':'a','':'a','':'a','':'a','':'a','ä':'a','ǟ':'a','ȧ':'a','ǡ':'a','':'a','ȁ':'a','à':'a','':'a','ȃ':'a','ā':'a','ą':'a','':'a','':'a','å':'a','ǻ':'a','':'a','':'a','ã':'a','':'aa','æ':'ae','ǽ':'ae','ǣ':'ae','':'ao','':'au','':'av','':'av','':'ay','':'b','':'b','ɓ':'b','':'b','':'b','':'b','ƀ':'b','ƃ':'b','ɵ':'o','ć':'c','č':'c','ç':'c','':'c','ĉ':'c','ɕ':'c','ċ':'c','ƈ':'c','ȼ':'c','ď':'d','':'d','':'d','ȡ':'d','':'d','':'d','ɗ':'d','':'d','':'d','':'d','':'d','đ':'d','ɖ':'d','ƌ':'d','ı':'i','ȷ':'j','ɟ':'j','ʄ':'j','dz':'dz','dž':'dz','é':'e','ĕ':'e','ě':'e','ȩ':'e','':'e','ê':'e','ế':'e','':'e','':'e','':'e','':'e','':'e','ë':'e','ė':'e','':'e','ȅ':'e','è':'e','':'e','ȇ':'e','ē':'e','':'e','':'e','':'e','ę':'e','':'e','ɇ':'e','':'e','':'e','':'et','':'f','ƒ':'f','':'f','':'f','ǵ':'g','ğ':'g','ǧ':'g','ģ':'g','ĝ':'g','ġ':'g','ɠ':'g','':'g','':'g','ǥ':'g','':'h','ȟ':'h','':'h','ĥ':'h','':'h','':'h','':'h','':'h','ɦ':'h','':'h','ħ':'h','ƕ':'hv','í':'i','ĭ':'i','ǐ':'i','î':'i','ï':'i','':'i','':'i','ȉ':'i','ì':'i','':'i','ȋ':'i','ī':'i','į':'i','':'i','ɨ':'i','ĩ':'i','':'i','':'d','':'f','':'g','':'r','':'s','':'t','':'is','ǰ':'j','ĵ':'j','ʝ':'j','ɉ':'j','':'k','ǩ':'k','ķ':'k','':'k','':'k','':'k','ƙ':'k','':'k','':'k','':'k','':'k','ĺ':'l','ƚ':'l','ɬ':'l','ľ':'l','ļ':'l','':'l','ȴ':'l','':'l','':'l','':'l','':'l','':'l','ŀ':'l','ɫ':'l','':'l','ɭ':'l','ł':'l','lj':'lj','ſ':'s','':'s','':'s','':'s','ḿ':'m','':'m','':'m','ɱ':'m','':'m','':'m','ń':'n','ň':'n','ņ':'n','':'n','ȵ':'n','':'n','':'n','ǹ':'n','ɲ':'n','':'n','ƞ':'n','':'n','':'n','ɳ':'n','ñ':'n','nj':'nj','ó':'o','ŏ':'o','ǒ':'o','ô':'o','':'o','':'o','':'o','':'o','':'o','ö':'o','ȫ':'o','ȯ':'o','ȱ':'o','':'o','ő':'o','ȍ':'o','ò':'o','':'o','ơ':'o','':'o','':'o','':'o','':'o','':'o','ȏ':'o','':'o','':'o','':'o','ō':'o','':'o','':'o','ǫ':'o','ǭ':'o','ø':'o','ǿ':'o','õ':'o','':'o','':'o','ȭ':'o','ƣ':'oi','':'oo','ɛ':'e','':'e','ɔ':'o','':'o','ȣ':'ou','':'p','':'p','':'p','ƥ':'p','':'p','':'p','':'p','':'p','':'p','':'q','ʠ':'q','ɋ':'q','':'q','ŕ':'r','ř':'r','ŗ':'r','':'r','':'r','':'r','ȑ':'r','ɾ':'r','':'r','ȓ':'r','':'r','ɼ':'r','':'r','':'r','ɍ':'r','ɽ':'r','':'c','':'c','ɘ':'e','ɿ':'r','ś':'s','':'s','š':'s','':'s','ş':'s','ŝ':'s','ș':'s','':'s','':'s','':'s','ʂ':'s','':'s','':'s','ȿ':'s','ɡ':'g','':'o','':'o','':'u','ť':'t','ţ':'t','':'t','ț':'t','ȶ':'t','':'t','':'t','':'t','':'t','ƭ':'t','':'t','':'t','ƫ':'t','ʈ':'t','ŧ':'t','':'th','ɐ':'a','':'ae','ǝ':'e','':'g','ɥ':'h','ʮ':'h','ʯ':'h','':'i','ʞ':'k','':'l','ɯ':'m','ɰ':'m','':'oe','ɹ':'r','ɻ':'r','ɺ':'r','':'r','ʇ':'t','ʌ':'v','ʍ':'w','ʎ':'y','':'tz','ú':'u','ŭ':'u','ǔ':'u','û':'u','':'u','ü':'u','ǘ':'u','ǚ':'u','ǜ':'u','ǖ':'u','':'u','':'u','ű':'u','ȕ':'u','ù':'u','':'u','ư':'u','':'u','':'u','':'u','':'u','':'u','ȗ':'u','ū':'u','':'u','ų':'u','':'u','ů':'u','ũ':'u','':'u','':'u','':'ue','':'um','':'v','':'v','ṿ':'v','ʋ':'v','':'v','':'v','':'v','':'vy','':'w','ŵ':'w','':'w','':'w','':'w','':'w','':'w','':'w','':'x','':'x','':'x','ý':'y','ŷ':'y','ÿ':'y','':'y','':'y','':'y','ƴ':'y','':'y','ỿ':'y','ȳ':'y','':'y','ɏ':'y','':'y','ź':'z','ž':'z','':'z','ʑ':'z','':'z','ż':'z','':'z','ȥ':'z','':'z','':'z','':'z','ʐ':'z','ƶ':'z','ɀ':'z','':'ff','':'ffi','':'ffl','':'fi','':'fl','ij':'ij','œ':'oe','':'st','':'a','':'e','':'i','':'j','':'o','':'r','':'u','':'v','':'x'};
return dedup([
string,
string.replace( /[^A-Za-z0-9]/g, (a) => conversionMap[a]||a )
]);
}
function casesVariants(string) {
return dedup([
string,
string.toLowerCase(),
string.split(" ").map(str=>str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()).join(" "),
string.toUpperCase()
]);
}
function regLikeVariants(theString,allStrings=[]) {
return _regLikeVariants(escape2utfSpecial(theString),allStrings.map(escape2utfSpecial)).map(utfSpecial2unEscaped);
}
function swapKeyVal(obj){
const ret = {};
for(let key in obj){
ret[obj[key]] = key;
}
return ret;
}
const specialMap = {
'(':String.fromCharCode(0x01),
')':String.fromCharCode(0x02),
'|':String.fromCharCode(0x03),
'{':String.fromCharCode(0x04),
'}':String.fromCharCode(0x05),
',':String.fromCharCode(0x06),
'[':String.fromCharCode(0x07),
']':String.fromCharCode(0x08),
'-':String.fromCharCode(0x09),
'<':String.fromCharCode(0x0a),
'>':String.fromCharCode(0x0b),
':':String.fromCharCode(0x0c),
'=':String.fromCharCode(0x0d)
};
const revertSpecial = swapKeyVal(specialMap);
function escape2utfSpecial(str) {
return str.replace(/\\(.)/g,(a,chr)=>specialMap[chr]?specialMap[chr]:chr);
}
function utfSpecial2unEscaped(str) {
return str.split('').map((chr)=>revertSpecial[chr]?revertSpecial[chr]:chr).join('');
}
function bracketsHandler(theString){
// handle []
const lower = "abcdefghijklmnopqrstuvwxyz";
const upper = lower.toUpperCase();
const number= "0123456789";
theString = theString.replace(/(\[[^\]]*)([a-z]-[a-z])([^\]]*\])/g,(osef,before,chrs,after)=> before+lower.slice(lower.indexOf(chrs.split('-')[0]),lower.indexOf(chrs.split('-')[1])+1)+after );
theString = theString.replace(/(\[[^\]]*)([A-Z]-[A-Z])([^\]]*\])/g,(osef,before,chrs,after)=> before+upper.slice(upper.indexOf(chrs.split('-')[0]),upper.indexOf(chrs.split('-')[1])+1)+after );
theString = theString.replace(/(\[[^\]]*)([0-9]-[0-9])([^\]]*\])/g,(osef,before,chrs,after)=> before+number.slice(number.indexOf(chrs.split('-')[0]),number.indexOf(chrs.split('-')[1])+1)+after );
theString = theString.replace(/\[([^\]]+)\]/g,(osef,chrs)=> `(${chrs.split('').join('|')})` );
return theString;
}
function resetCache(){cache();}
function cache(key,func,...args){
if(!cache.cached) cache.cached = {}; // init
if(arguments.length === 0) return cache.cached = {}; // reset cache
if(typeof cache.cached[key] === "undefined") cache.cached[key] = func(...args); // compute and cache
return cache.cached[key]; // answer from cache;
}
function labelExpressions(str) {
return str.slice( str.indexOf(`::`) + 2);
}
function computedLabel(label,allStrings) {
return cache(`label::${label}`,_computedLabel,label, allStrings);
}
function _computedLabel(label,allStrings) {
const matchingLabel = allStrings.filter(str=>str.trim().indexOf(`${label}::`)=== 0);
return flatten(matchingLabel.map(str=>_regLikeVariants(labelExpressions(str)),allStrings));
}
function refHandler(theString,allStrings) {
// handle <ref>
theString = theString.replace(/<([^>]+)>/g,(osef,ref)=> `(${computedLabel(ref,allStrings).join('|')})` );
return theString;
}
function syncRefHandler(theString,allStrings) {
// handle =ref>
const syncRef = dedup(theString.match(/=[^>]+>/g)||[]).map(str=>str.substring(1,str.length-1));
let results = [theString];
for(let ref of syncRef){
const variantes = computedLabel(ref,allStrings);
let tmpRes = [];
for(let v of variantes){
for(let r of results){
tmpRes.push(r.replace(new RegExp(`=${ref}>`,'g'),v));
}
}
results = tmpRes;
}
return results;
}
function qtyHandlerReplaceCallback(all,chr,qty){
const mm = qty.split(",").map(n=>n.trim()*1);
const min = mm[0];
const max = (mm.length === 2)?mm[1]:min;
const res = [];
for (let i = min; i<=max; i++) res.push( new Array(i + 1).join(chr) )
return `(${res.join('|')})`;
}
function qtyHandler(theString){
// handle {qty} and {min,max}
theString = theString.replace(/([^\)]){([^}]+)}/g,qtyHandlerReplaceCallback);
theString = theString.replace(/^(.*)\(([^)]*)\)\{([^}]+)\}(.*)$/,(all,before,choices,qty,after)=> before+qtyHandlerReplaceCallback('',`(${choices})`,qty)+after );
return theString;
}
function optionsHandler(theString,allStrings){
// handle (|)
let multiString = theString.replace(/^(.*)\(([^)]*)\)(.*)$/,(all,before,choices,after)=>choices.split('|').map(c=> before+c+after).join('=$##$=') ).split('=$##$=');
multiString = [].concat(...multiString.map(str => (str.indexOf('(')!== -1)?_regLikeVariants(str,allStrings):str));
return dedup(multiString);
}
function _regLikeVariants(theString,allStrings) {
if(theString.indexOf('::')>0) return []; // remove label definition lines.
theString = bracketsHandler(theString); // handle []
theString = refHandler(theString,allStrings);// handle <ref> and ref::
theString = qtyHandler(theString); // handle {qty} and {min,max}
const strings = optionsHandler(theString,allStrings); // handle (|)
return flatten(strings.map(str=>syncRefHandler(str,allStrings)));// handle =ref> and ref::
}
function flatten(arrayOfArray){
return dedup([].concat(...arrayOfArray));
}
\ No newline at end of file
import * as app from "./dictionaryBuilder.mjs";
describe('dictionaryBuilder', () => {
beforeEach(app.resetCache);
it('add no accents variant', () => {
expect(app.noAccentVariant("Ǧ1")).toEqual(["Ǧ1","G1"]);
});
it('add case variants', () => {
expect(app.casesVariants("moT")).toEqual(["moT","mot","Mot","MOT"]);
});
it('add multi word case variants', () => {
expect(app.casesVariants("autre mot")).toEqual(["autre mot","Autre Mot","AUTRE MOT"]);
});
it('regLikeVariants remove ref:: lines', () => {
expect(app.regLikeVariants("ref::truc")).toEqual([]);
});
it('regLikeVariants handle <ref>', () => {
expect(app.regLikeVariants("<ref> <ref>",["ref::truc","ref::bidule","<ref> <ref>"])).toEqual(["truc truc","bidule truc", "truc bidule", "bidule bidule"]);
expect(app.regLikeVariants("<ref> <ref>",["ref::(truc|bidule)","<ref> <ref>"])).toEqual(["truc truc","bidule truc", "truc bidule", "bidule bidule"]);
});
it('regLikeVariants handle =ref>', () => {
expect(app.regLikeVariants("=ref> =ref>",["ref::truc","ref::bidule","=ref> =ref>"])).toEqual(["truc truc","bidule bidule"]);
expect(app.regLikeVariants("=ref> =ref>",["ref::(truc|bidule)","=ref> =ref>"])).toEqual(["truc truc","bidule bidule"]);
});
it('regLikeVariants handle multiple =ref>', () => {
expect(app.regLikeVariants("=ref> =ref2> =ref> =ref2>",
["ref::(truc|bidule)","ref2::(machin|chose)","=ref> =ref>"]
)).toEqual(["truc machin truc machin","bidule machin bidule machin","truc chose truc chose","bidule chose bidule chose"]);
});
it('regLikeVariants handle (this|that)', () => {
expect(app.regLikeVariants("(this|that)")).toEqual(["this","that"]);
});
it('regLikeVariants handle [ -_]', () => {
expect(app.regLikeVariants("[ -_]")).toEqual([" ","-","_"]);
});
it('regLikeVariants handle [a-f]', () => {
expect(app.regLikeVariants("[a-f]")).toEqual(["a","b","c","d","e","f"]);
expect(app.regLikeVariants("[7-9]")).toEqual(["7","8","9"]);
expect(app.regLikeVariants("[C-F]")).toEqual(["C","D","E","F"]);
});
it('regLikeVariants handle [a-c-]', () => {
expect(app.regLikeVariants("[a-c-]")).toEqual(["a","b","c","-"]);
});
it('regLikeVariants handle {qty}', () => {
expect(app.regLikeVariants("a{5}")).toEqual(["aaaaa"]);
});
it('regLikeVariants handle {min,max}', () => {
expect(app.regLikeVariants("b{3,5}")).toEqual(["bbb","bbbb","bbbbb"]);
});
it('regLikeVariants handle (string){qty}', () => {
expect(app.regLikeVariants("c'est (toto|tata){0,2}")).toEqual(["c'est ","c'est toto","c'est totototo","c'est tata","c'est tatatoto","c'est tototata","c'est tatatata"]);
});
it('regLikeVariants handle nested -([a-f]|<ref>){0,1}', () => {
expect(app.regLikeVariants("-([B-D]|<ref>){0,1}",["ref::plop"])).toEqual(["-","-B","-plop","-C","-D"]);
});
it('regLikeVariants handle plop:\\:', () => {
expect(app.regLikeVariants("plop:\\:ici")).toEqual(["plop::ici"]);
expect(app.regLikeVariants("plop\\::ici")).toEqual(["plop::ici"]);
expect(app.regLikeVariants("plop::ici")).toEqual([]);
});
it('regLikeVariants handle [\\]*]', () => {
expect(app.regLikeVariants("[\\]*]")).toEqual(["]","*"]);
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment