ether-code 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/compiler.js CHANGED
@@ -212,8 +212,8 @@ class EtherCompiler {
212
212
  inferTargetFromContent(content) {
213
213
  const patterns = {
214
214
  html: [
215
- /\b(page|document|entête|entete|corps|section|div|paragraphe|titre|lien|image|formulaire|bouton)\b/i,
216
- /\b(balise|élément|element|attribut)\b/i
215
+ /\b(page|document|entête|corps|section|div|paragraphe|titre|lien|image|formulaire|bouton)\b/i,
216
+ /\b(balise|élément|attribut)\b/i
217
217
  ],
218
218
  css: [
219
219
  /\b(style|couleur|taille|marge|bordure|fond|police|largeur|hauteur)\s*[:=]/i,
@@ -222,7 +222,7 @@ class EtherCompiler {
222
222
  ],
223
223
  js: [
224
224
  /\b(fonction|variable|constante|si|sinon|pour|tant que|retourner)\b/i,
225
- /\b(classe|méthode|methode|constructeur)\b/i,
225
+ /\b(classe|méthode|constructeur)\b/i,
226
226
  /=>\s*\{/
227
227
  ],
228
228
  php: [
@@ -230,7 +230,7 @@ class EtherCompiler {
230
230
  /\$\w+/
231
231
  ],
232
232
  sql: [
233
- /\b(sélectionner|selectionner|insérer|inserer|mettre à jour|mettre a jour|supprimer|créer table|creer table|depuis|où|ou)\b/i,
233
+ /\b(sélectionner|insérer|mettre à jour|supprimer|créer table|depuis|)\b/i,
234
234
  /\b(SELECT|INSERT|UPDATE|DELETE|CREATE|FROM|WHERE)\b/i
235
235
  ],
236
236
  python: [
@@ -238,7 +238,7 @@ class EtherCompiler {
238
238
  /:\s*$/m
239
239
  ],
240
240
  react: [
241
- /\b(composant|état|etat|effet|props|rendu)\b/i,
241
+ /\b(composant|état|effet|props|rendu)\b/i,
242
242
  /<[\w]+[^>]*\/>/
243
243
  ],
244
244
  graphql: [
@@ -295,4 +295,4 @@ class EtherCompiler {
295
295
  }
296
296
  }
297
297
 
298
- module.exports = { EtherCompiler, GENERATORS, TARGET_EXTENSIONS }
298
+ module.exports = { EtherCompiler, GENERATORS, TARGET_EXTENSIONS }
package/cli/ether.js CHANGED
@@ -2,10 +2,11 @@
2
2
 
3
3
  const fs = require('fs')
4
4
  const path = require('path')
5
+ const http = require('http')
5
6
  const { EtherCompiler } = require('./compiler')
6
7
  const { Watcher } = require('./watcher')
7
8
 
8
- const VERSION = '0.1.0'
9
+ const VERSION = '0.1.4'
9
10
 
10
11
  const COLORS = {
11
12
  reset: '\x1b[0m',
@@ -62,7 +63,7 @@ ${COLORS.bright}UTILISATION${COLORS.reset}
62
63
  ${COLORS.bright}COMMANDES${COLORS.reset}
63
64
  ${COLORS.cyan}init${COLORS.reset} Initialiser un nouveau projet Ether
64
65
  ${COLORS.cyan}build${COLORS.reset} Compiler les fichiers .eth
65
- ${COLORS.cyan}dev${COLORS.reset} Mode développement (watch + compilation auto)
66
+ ${COLORS.cyan}dev${COLORS.reset} Mode développement (serveur + watch + live reload)
66
67
  ${COLORS.cyan}check${COLORS.reset} Vérifier la syntaxe sans compiler
67
68
  ${COLORS.cyan}help${COLORS.reset} Afficher cette aide
68
69
  ${COLORS.cyan}version${COLORS.reset} Afficher la version
@@ -70,6 +71,7 @@ ${COLORS.bright}COMMANDES${COLORS.reset}
70
71
  ${COLORS.bright}OPTIONS${COLORS.reset}
71
72
  -c, --config Chemin vers le fichier de configuration
72
73
  -o, --output Dossier de sortie
74
+ -p, --port Port du serveur de développement (défaut: 3000)
73
75
  -w, --watch Surveiller les changements (alias de dev)
74
76
  -v, --verbose Mode verbeux
75
77
  -q, --quiet Mode silencieux
@@ -82,11 +84,11 @@ ${COLORS.bright}EXEMPLES${COLORS.reset}
82
84
  ${COLORS.dim}# Compiler le projet${COLORS.reset}
83
85
  ether build
84
86
 
85
- ${COLORS.dim}# Mode développement${COLORS.reset}
87
+ ${COLORS.dim}# Mode développement sur localhost:3000${COLORS.reset}
86
88
  ether dev
87
89
 
88
- ${COLORS.dim}# Compiler avec config personnalisée${COLORS.reset}
89
- ether build -c ./mon-config.js
90
+ ${COLORS.dim}# Mode développement sur un autre port${COLORS.reset}
91
+ ether dev -p 8080
90
92
 
91
93
  ${COLORS.bright}DOCUMENTATION${COLORS.reset}
92
94
  https://ether-code.com/docs
@@ -222,18 +224,20 @@ async function cmdInit() {
222
224
  logWarning('package.json existe déjà')
223
225
  }
224
226
 
225
- const exampleEth = `// Exemple de fichier Ether
226
- // Cible: html
227
-
228
- page "Ma première page Ether"
229
- entête
230
- titre "Bienvenue"
231
-
227
+ const exampleEth = `html
228
+ tête
229
+ titre "Ma première page Ether"
232
230
  corps
233
- section #hero
234
- titre "Ether"
231
+ entete
232
+ titre1 "Bienvenue sur Ether"
235
233
  paragraphe "Le langage intentionnel"
236
- bouton .primary "Commencer"
234
+ principal
235
+ section #hero
236
+ titre2 "Commencez maintenant"
237
+ paragraphe "Programmez dans votre langue naturelle."
238
+ bouton .primary "Démarrer"
239
+ pied
240
+ paragraphe "© 2025 Créé avec Ether"
237
241
  `
238
242
 
239
243
  const examplePath = path.join(srcDir, 'index.eth')
@@ -341,6 +345,7 @@ async function cmdDev(options) {
341
345
  const config = loadConfig(options.config)
342
346
  const srcDir = path.resolve(process.cwd(), config.src)
343
347
  const outDir = path.resolve(process.cwd(), options.output || config.out)
348
+ const port = options.port || config.port || 3000
344
349
 
345
350
  if (!fs.existsSync(srcDir)) {
346
351
  logError(`Dossier source introuvable: ${srcDir}`)
@@ -350,11 +355,105 @@ async function cmdDev(options) {
350
355
  logInfo(`Surveillance de ${srcDir}`)
351
356
  logInfo(`Sortie vers ${outDir}`)
352
357
  console.log('')
353
- logInfo('En attente de modifications... (Ctrl+C pour arrêter)')
354
- console.log('')
355
358
 
356
359
  await cmdBuild({ ...options, quiet: true })
357
360
 
361
+ const clients = []
362
+
363
+ const MIME_TYPES = {
364
+ '.html': 'text/html',
365
+ '.css': 'text/css',
366
+ '.js': 'application/javascript',
367
+ '.json': 'application/json',
368
+ '.png': 'image/png',
369
+ '.jpg': 'image/jpeg',
370
+ '.jpeg': 'image/jpeg',
371
+ '.gif': 'image/gif',
372
+ '.svg': 'image/svg+xml',
373
+ '.ico': 'image/x-icon',
374
+ '.webp': 'image/webp',
375
+ '.mp4': 'video/mp4',
376
+ '.webm': 'video/webm',
377
+ '.woff': 'font/woff',
378
+ '.woff2': 'font/woff2',
379
+ '.ttf': 'font/ttf'
380
+ }
381
+
382
+ const LIVE_RELOAD_SCRIPT = `
383
+ <script>
384
+ (function() {
385
+ const es = new EventSource('/__ether_reload');
386
+ es.onmessage = function(e) {
387
+ if (e.data === 'reload') {
388
+ window.location.reload();
389
+ }
390
+ };
391
+ es.onerror = function() {
392
+ es.close();
393
+ setTimeout(function() { window.location.reload(); }, 1000);
394
+ };
395
+ })();
396
+ </script>
397
+ </body>`
398
+
399
+ const server = http.createServer((req, res) => {
400
+ if (req.url === '/__ether_reload') {
401
+ res.writeHead(200, {
402
+ 'Content-Type': 'text/event-stream',
403
+ 'Cache-Control': 'no-cache',
404
+ 'Connection': 'keep-alive',
405
+ 'Access-Control-Allow-Origin': '*'
406
+ })
407
+ res.write('data: connected\n\n')
408
+ clients.push(res)
409
+ req.on('close', () => {
410
+ const index = clients.indexOf(res)
411
+ if (index > -1) clients.splice(index, 1)
412
+ })
413
+ return
414
+ }
415
+
416
+ let filePath = req.url === '/' ? '/index.html' : req.url
417
+ filePath = filePath.split('?')[0]
418
+ filePath = path.join(outDir, filePath)
419
+
420
+ const ext = path.extname(filePath).toLowerCase()
421
+ const contentType = MIME_TYPES[ext] || 'application/octet-stream'
422
+
423
+ fs.readFile(filePath, (err, content) => {
424
+ if (err) {
425
+ if (err.code === 'ENOENT') {
426
+ res.writeHead(404)
427
+ res.end('404 - Fichier non trouvé')
428
+ } else {
429
+ res.writeHead(500)
430
+ res.end('Erreur serveur')
431
+ }
432
+ return
433
+ }
434
+
435
+ if (ext === '.html') {
436
+ content = content.toString().replace('</body>', LIVE_RELOAD_SCRIPT)
437
+ }
438
+
439
+ res.writeHead(200, { 'Content-Type': contentType })
440
+ res.end(content)
441
+ })
442
+ })
443
+
444
+ server.listen(port, () => {
445
+ logSuccess(`Serveur démarré sur http://localhost:${port}`)
446
+ console.log('')
447
+ logInfo('En attente de modifications... (Ctrl+C pour arrêter)')
448
+ console.log('')
449
+ })
450
+
451
+ function notifyClients() {
452
+ clients.forEach(client => {
453
+ client.write('data: reload\n\n')
454
+ })
455
+ }
456
+
358
457
  const watcher = new Watcher(srcDir, config.watch)
359
458
  const compiler = new EtherCompiler(config)
360
459
 
@@ -378,6 +477,8 @@ async function cmdDev(options) {
378
477
  fs.writeFileSync(outPath, output.content)
379
478
  logSuccess(`→ ${output.path}`)
380
479
  }
480
+
481
+ notifyClients()
381
482
  } catch (err) {
382
483
  logError(err.message)
383
484
  }
@@ -403,6 +504,8 @@ async function cmdDev(options) {
403
504
  fs.writeFileSync(outPath, output.content)
404
505
  logSuccess(`→ ${output.path}`)
405
506
  }
507
+
508
+ notifyClients()
406
509
  } catch (err) {
407
510
  logError(err.message)
408
511
  }
@@ -417,7 +520,8 @@ async function cmdDev(options) {
417
520
 
418
521
  process.on('SIGINT', () => {
419
522
  console.log('')
420
- logInfo('Arrêt du mode développement')
523
+ logInfo('Arrêt du serveur de développement')
524
+ server.close()
421
525
  watcher.stop()
422
526
  process.exit(0)
423
527
  })
@@ -516,6 +620,7 @@ function parseArgs(args) {
516
620
  command: null,
517
621
  config: null,
518
622
  output: null,
623
+ port: null,
519
624
  verbose: false,
520
625
  quiet: false,
521
626
  watch: false
@@ -529,6 +634,8 @@ function parseArgs(args) {
529
634
  options.config = args[++i]
530
635
  } else if (arg === '-o' || arg === '--output') {
531
636
  options.output = args[++i]
637
+ } else if (arg === '-p' || arg === '--port') {
638
+ options.port = parseInt(args[++i], 10)
532
639
  } else if (arg === '-v' || arg === '--verbose') {
533
640
  options.verbose = true
534
641
  } else if (arg === '-q' || arg === '--quiet') {
@@ -590,4 +697,4 @@ async function main() {
590
697
  main().catch(err => {
591
698
  logError(err.message)
592
699
  process.exit(1)
593
- })
700
+ })
@@ -455,10 +455,16 @@ class HTMLGenerator {
455
455
  if (!node) return
456
456
 
457
457
  switch (node.type) {
458
+ case 'Document':
459
+ this.generateDocument(node)
460
+ break
458
461
  case 'Element':
459
462
  case 'élément':
460
463
  this.generateElement(node)
461
464
  break
465
+ case 'TextNode':
466
+ this.generateTextNode(node)
467
+ break
462
468
  case 'Text':
463
469
  case 'text':
464
470
  this.generateText(node)
@@ -474,14 +480,34 @@ class HTMLGenerator {
474
480
  default:
475
481
  if (node.tag || node.tagName) {
476
482
  this.generateElement(node)
477
- } else if (node.text || node.content) {
483
+ } else if (node.text || node.content || node.value) {
478
484
  this.generateText(node)
479
485
  }
480
486
  }
481
487
  }
482
488
 
489
+ generateDocument(node) {
490
+ this.writeLine('<!DOCTYPE html>')
491
+ if (node.html) {
492
+ this.generateNode(node.html)
493
+ }
494
+ }
495
+
496
+ generateTextNode(node) {
497
+ if (node.value || node.content) {
498
+ this.writeLine(this.escapeHtml(node.value || node.content))
499
+ }
500
+ }
501
+
483
502
  generateElement(node) {
484
- const tag = this.translateTag(node.tag || node.tagName || node.name)
503
+ let tag
504
+ if (node.htmlTag && !node.htmlTag.startsWith('<')) {
505
+ tag = node.htmlTag
506
+ } else {
507
+ const rawTag = node.tag || node.tagName || node.name
508
+ tag = this.translateTag(rawTag)
509
+ }
510
+
485
511
  const attributes = this.generateAttributes(node.attributes || node.attrs || {})
486
512
  const isVoid = this.voidElements.includes(tag.toLowerCase())
487
513
 
@@ -521,30 +547,46 @@ class HTMLGenerator {
521
547
  }
522
548
 
523
549
  generateAttributes(attrs) {
524
- if (!attrs || Object.keys(attrs).length === 0) {
550
+ if (!attrs || (Array.isArray(attrs) && attrs.length === 0) || Object.keys(attrs).length === 0) {
525
551
  return ''
526
552
  }
527
553
 
528
554
  const parts = []
529
- for (const [key, value] of Object.entries(attrs)) {
530
- let attrName = this.translateAttribute(key)
531
-
532
- if (attrName.startsWith('au ') || attrName.startsWith('a la ')) {
533
- attrName = this.translateEvent(key)
534
- }
535
-
536
- if (value === true || value === '') {
537
- parts.push(attrName)
538
- } else if (value === false || value === null || value === undefined) {
539
- continue
540
- } else {
541
- let attrValue = this.translateAttrValue(value)
555
+
556
+ if (Array.isArray(attrs)) {
557
+ for (const attr of attrs) {
558
+ const attrName = attr.htmlName || this.translateAttribute(attr.name)
559
+ const value = attr.value
542
560
 
543
- if (attrName === 'type' && this.isInputType(value)) {
544
- attrValue = this.translateInputType(value)
561
+ if (value === true || value === '') {
562
+ parts.push(attrName)
563
+ } else if (value === false || value === null || value === undefined) {
564
+ continue
565
+ } else {
566
+ parts.push(`${attrName}="${this.escapeAttr(value)}"`)
545
567
  }
568
+ }
569
+ } else {
570
+ for (const [key, value] of Object.entries(attrs)) {
571
+ let attrName = this.translateAttribute(key)
546
572
 
547
- parts.push(`${attrName}="${this.escapeAttr(attrValue)}"`)
573
+ if (attrName.startsWith('au ') || attrName.startsWith('a la ')) {
574
+ attrName = this.translateEvent(key)
575
+ }
576
+
577
+ if (value === true || value === '') {
578
+ parts.push(attrName)
579
+ } else if (value === false || value === null || value === undefined) {
580
+ continue
581
+ } else {
582
+ let attrValue = this.translateAttrValue(value)
583
+
584
+ if (attrName === 'type' && this.isInputType(value)) {
585
+ attrValue = this.translateInputType(value)
586
+ }
587
+
588
+ parts.push(`${attrName}="${this.escapeAttr(attrValue)}"`)
589
+ }
548
590
  }
549
591
  }
550
592
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ether-code",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Ether - Le langage intentionnel",
5
5
  "main": "cli/compiler.js",
6
6
  "bin": {
@@ -20,15 +20,15 @@
20
20
  "french",
21
21
  "multilingual"
22
22
  ],
23
- "author": "Stéphane LEGRAND",
23
+ "author": "Steve",
24
24
  "license": "MIT",
25
25
  "repository": {
26
26
  "type": "git",
27
- "url": "https://github.com/ether-code/ether"
27
+ "url": "https://github.com/ether-lang/ether"
28
28
  },
29
29
  "homepage": "https://ether-code.com",
30
30
  "bugs": {
31
- "url": "https://github.com/ether-code/ether/issues"
31
+ "url": "https://github.com/ether-lang/ether/issues"
32
32
  },
33
33
  "engines": {
34
34
  "node": ">=14.0.0"
@@ -551,6 +551,7 @@ class HTMLParser {
551
551
  const tagName = elemToken.value
552
552
  const htmlTag = this.translateElement(tagName)
553
553
  const loc = elemToken.loc
554
+ const elemIndent = loc.column - 1
554
555
 
555
556
  const attributes = this.parseAttributes()
556
557
 
@@ -567,7 +568,7 @@ class HTMLParser {
567
568
  children.push(new AST.TextNode(textContent))
568
569
  }
569
570
 
570
- const childElements = this.parseChildren()
571
+ const childElements = this.parseChildren(elemIndent)
571
572
  children = children.concat(childElements)
572
573
  }
573
574
 
@@ -575,6 +576,9 @@ class HTMLParser {
575
576
  }
576
577
 
577
578
  parseShorthandElement() {
579
+ const firstToken = this.peek()
580
+ const elemIndent = firstToken.loc ? firstToken.loc.column - 1 : 0
581
+
578
582
  const attributes = []
579
583
  let tagName = 'div'
580
584
  let htmlTag = 'div'
@@ -607,7 +611,7 @@ class HTMLParser {
607
611
  children.push(new AST.TextNode(textContent))
608
612
  }
609
613
 
610
- const childElements = this.parseChildren()
614
+ const childElements = this.parseChildren(elemIndent)
611
615
  children.push(...childElements)
612
616
 
613
617
  return new AST.Element(tagName, htmlTag, attributes, children, false)
@@ -664,16 +668,13 @@ class HTMLParser {
664
668
  return attributes
665
669
  }
666
670
 
667
- parseChildren() {
671
+ parseChildren(parentIndent = -1) {
668
672
  const children = []
669
673
 
670
- this.skipNewlines()
671
- const baseIndent = this.currentIndent
672
-
673
674
  while (!this.match('EOF')) {
674
675
  this.skipNewlines()
675
676
 
676
- if (this.currentIndent <= baseIndent && children.length > 0) {
677
+ if (this.currentIndent <= parentIndent) {
677
678
  break
678
679
  }
679
680
 
@@ -807,12 +808,12 @@ class HTMLParser {
807
808
  'tete': 'head', 'tête': 'head', 'head': 'head', 'cabeza': 'head', 'голова': 'head', '头部': 'head', 'ヘッド': 'head',
808
809
  'corps': 'body', 'body': 'body', 'cuerpo': 'body', 'тело': 'body', '主体': 'body', 'ボディ': 'body',
809
810
  'titre': 'title', 'title': 'title', 'título': 'title', 'titulo': 'title', 'заголовок': 'title', '标题': 'title', 'タイトル': 'title',
810
- 'titre 1': 'h1', 'heading 1': 'h1', 'título 1': 'h1', 'titulo 1': 'h1', 'заголовок 1': 'h1', '标题 1': 'h1', '見出し 1': 'h1',
811
- 'titre 2': 'h2', 'heading 2': 'h2', 'título 2': 'h2', 'titulo 2': 'h2', 'заголовок 2': 'h2', '标题 2': 'h2', '見出し 2': 'h2',
812
- 'titre 3': 'h3', 'heading 3': 'h3', 'título 3': 'h3', 'titulo 3': 'h3', 'заголовок 3': 'h3', '标题 3': 'h3', '見出し 3': 'h3',
813
- 'titre 4': 'h4', 'heading 4': 'h4', 'título 4': 'h4', 'titulo 4': 'h4', 'заголовок 4': 'h4', '标题 4': 'h4', '見出し 4': 'h4',
814
- 'titre 5': 'h5', 'heading 5': 'h5', 'título 5': 'h5', 'titulo 5': 'h5', 'заголовок 5': 'h5', '标题 5': 'h5', '見出し 5': 'h5',
815
- 'titre 6': 'h6', 'heading 6': 'h6', 'título 6': 'h6', 'titulo 6': 'h6', 'заголовок 6': 'h6', '标题 6': 'h6', '見出し 6': 'h6',
811
+ 'titre 1': 'h1', 'titre1': 'h1', 'heading 1': 'h1', 'título 1': 'h1', 'titulo 1': 'h1', 'заголовок 1': 'h1', '标题 1': 'h1', '見出し 1': 'h1',
812
+ 'titre 2': 'h2', 'titre2': 'h2', 'heading 2': 'h2', 'título 2': 'h2', 'titulo 2': 'h2', 'заголовок 2': 'h2', '标题 2': 'h2', '見出し 2': 'h2',
813
+ 'titre 3': 'h3', 'titre3': 'h3', 'heading 3': 'h3', 'título 3': 'h3', 'titulo 3': 'h3', 'заголовок 3': 'h3', '标题 3': 'h3', '見出し 3': 'h3',
814
+ 'titre 4': 'h4', 'titre4': 'h4', 'heading 4': 'h4', 'título 4': 'h4', 'titulo 4': 'h4', 'заголовок 4': 'h4', '标题 4': 'h4', '見出し 4': 'h4',
815
+ 'titre 5': 'h5', 'titre5': 'h5', 'heading 5': 'h5', 'título 5': 'h5', 'titulo 5': 'h5', 'заголовок 5': 'h5', '标题 5': 'h5', '見出し 5': 'h5',
816
+ 'titre 6': 'h6', 'titre6': 'h6', 'heading 6': 'h6', 'título 6': 'h6', 'titulo 6': 'h6', 'заголовок 6': 'h6', '标题 6': 'h6', '見出し 6': 'h6',
816
817
  'paragraphe': 'p', 'paragraph': 'p', 'párrafo': 'p', 'parrafo': 'p', 'параграф': 'p', '段落': 'p',
817
818
  'division': 'div', 'div': 'div', 'bloque': 'div', 'блок': 'div', '块': 'div', 'ディブ': 'div',
818
819
  'etendue': 'span', 'étendue': 'span', 'span': 'span', 'extensión': 'span', 'extension': 'span', 'спан': 'span', '跨度': 'span', 'スパン': 'span',