ether-code 0.1.0

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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +130 -0
  3. package/cli/compiler.js +298 -0
  4. package/cli/ether.js +532 -0
  5. package/cli/watcher.js +106 -0
  6. package/generators/css-generator.js +583 -0
  7. package/generators/graphql-generator.js +868 -0
  8. package/generators/html-generator.js +745 -0
  9. package/generators/js-generator.js +909 -0
  10. package/generators/node-generator.js +467 -0
  11. package/generators/php-generator.js +706 -0
  12. package/generators/python-generator.js +913 -0
  13. package/generators/react-generator.js +599 -0
  14. package/generators/ruby-generator.js +904 -0
  15. package/generators/sql-generator.js +988 -0
  16. package/generators/ts-generator.js +569 -0
  17. package/i18n/i18n-css.json +743 -0
  18. package/i18n/i18n-graphql.json +1531 -0
  19. package/i18n/i18n-html.json +572 -0
  20. package/i18n/i18n-js.json +2790 -0
  21. package/i18n/i18n-node.json +2442 -0
  22. package/i18n/i18n-php.json +4306 -0
  23. package/i18n/i18n-python.json +3080 -0
  24. package/i18n/i18n-react.json +1784 -0
  25. package/i18n/i18n-ruby.json +1858 -0
  26. package/i18n/i18n-sql.json +3466 -0
  27. package/i18n/i18n-ts.json +442 -0
  28. package/lexer/ether-lexer.js +728 -0
  29. package/lexer/tokens.js +292 -0
  30. package/package.json +45 -0
  31. package/parsers/ast-css.js +545 -0
  32. package/parsers/ast-graphql.js +424 -0
  33. package/parsers/ast-html.js +886 -0
  34. package/parsers/ast-js.js +750 -0
  35. package/parsers/ast-node.js +2440 -0
  36. package/parsers/ast-php.js +957 -0
  37. package/parsers/ast-react.js +580 -0
  38. package/parsers/ast-ruby.js +895 -0
  39. package/parsers/ast-ts.js +1352 -0
  40. package/parsers/css-parser.js +1981 -0
  41. package/parsers/graphql-parser.js +2011 -0
  42. package/parsers/html-parser.js +1181 -0
  43. package/parsers/js-parser.js +2564 -0
  44. package/parsers/node-parser.js +2644 -0
  45. package/parsers/php-parser.js +3037 -0
  46. package/parsers/react-parser.js +1035 -0
  47. package/parsers/ruby-parser.js +2680 -0
  48. package/parsers/ts-parser.js +3881 -0
package/cli/ether.js ADDED
@@ -0,0 +1,532 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const { EtherCompiler } = require('./compiler')
6
+ const { Watcher } = require('./watcher')
7
+
8
+ const VERSION = '0.1.0'
9
+
10
+ const COLORS = {
11
+ reset: '\x1b[0m',
12
+ bright: '\x1b[1m',
13
+ dim: '\x1b[2m',
14
+ red: '\x1b[31m',
15
+ green: '\x1b[32m',
16
+ yellow: '\x1b[33m',
17
+ blue: '\x1b[34m',
18
+ magenta: '\x1b[35m',
19
+ cyan: '\x1b[36m'
20
+ }
21
+
22
+ function log(msg, color = '') {
23
+ console.log(`${color}${msg}${COLORS.reset}`)
24
+ }
25
+
26
+ function logSuccess(msg) {
27
+ log(`✓ ${msg}`, COLORS.green)
28
+ }
29
+
30
+ function logError(msg) {
31
+ log(`✗ ${msg}`, COLORS.red)
32
+ }
33
+
34
+ function logInfo(msg) {
35
+ log(`ℹ ${msg}`, COLORS.cyan)
36
+ }
37
+
38
+ function logWarning(msg) {
39
+ log(`⚠ ${msg}`, COLORS.yellow)
40
+ }
41
+
42
+ function showBanner() {
43
+ console.log(`
44
+ ${COLORS.cyan}${COLORS.bright}
45
+ ███████╗████████╗██╗ ██╗███████╗██████╗
46
+ ██╔════╝╚══██╔══╝██║ ██║██╔════╝██╔══██╗
47
+ █████╗ ██║ ███████║█████╗ ██████╔╝
48
+ ██╔══╝ ██║ ██╔══██║██╔══╝ ██╔══██╗
49
+ ███████╗ ██║ ██║ ██║███████╗██║ ██║
50
+ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
51
+ ${COLORS.reset}
52
+ ${COLORS.dim}Le langage intentionnel - v${VERSION}${COLORS.reset}
53
+ `)
54
+ }
55
+
56
+ function showHelp() {
57
+ showBanner()
58
+ console.log(`
59
+ ${COLORS.bright}UTILISATION${COLORS.reset}
60
+ ether <commande> [options]
61
+
62
+ ${COLORS.bright}COMMANDES${COLORS.reset}
63
+ ${COLORS.cyan}init${COLORS.reset} Initialiser un nouveau projet Ether
64
+ ${COLORS.cyan}build${COLORS.reset} Compiler les fichiers .eth
65
+ ${COLORS.cyan}dev${COLORS.reset} Mode développement (watch + compilation auto)
66
+ ${COLORS.cyan}check${COLORS.reset} Vérifier la syntaxe sans compiler
67
+ ${COLORS.cyan}help${COLORS.reset} Afficher cette aide
68
+ ${COLORS.cyan}version${COLORS.reset} Afficher la version
69
+
70
+ ${COLORS.bright}OPTIONS${COLORS.reset}
71
+ -c, --config Chemin vers le fichier de configuration
72
+ -o, --output Dossier de sortie
73
+ -w, --watch Surveiller les changements (alias de dev)
74
+ -v, --verbose Mode verbeux
75
+ -q, --quiet Mode silencieux
76
+ --no-color Désactiver les couleurs
77
+
78
+ ${COLORS.bright}EXEMPLES${COLORS.reset}
79
+ ${COLORS.dim}# Initialiser un projet${COLORS.reset}
80
+ ether init
81
+
82
+ ${COLORS.dim}# Compiler le projet${COLORS.reset}
83
+ ether build
84
+
85
+ ${COLORS.dim}# Mode développement${COLORS.reset}
86
+ ether dev
87
+
88
+ ${COLORS.dim}# Compiler avec config personnalisée${COLORS.reset}
89
+ ether build -c ./mon-config.js
90
+
91
+ ${COLORS.bright}DOCUMENTATION${COLORS.reset}
92
+ https://ether-code.com/docs
93
+ `)
94
+ }
95
+
96
+ function showVersion() {
97
+ console.log(`Ether v${VERSION}`)
98
+ }
99
+
100
+ function loadConfig(configPath) {
101
+ const defaultConfig = {
102
+ src: './src',
103
+ out: './dist',
104
+ targets: {},
105
+ i18n: 'fr',
106
+ watch: {
107
+ ignored: ['node_modules', '.git', 'dist']
108
+ }
109
+ }
110
+
111
+ const configFile = configPath || path.join(process.cwd(), 'ether.config.js')
112
+
113
+ if (fs.existsSync(configFile)) {
114
+ try {
115
+ const userConfig = require(configFile)
116
+ return { ...defaultConfig, ...userConfig }
117
+ } catch (err) {
118
+ logError(`Erreur de configuration: ${err.message}`)
119
+ return defaultConfig
120
+ }
121
+ }
122
+
123
+ const jsonConfig = path.join(process.cwd(), 'ether.config.json')
124
+ if (fs.existsSync(jsonConfig)) {
125
+ try {
126
+ const content = fs.readFileSync(jsonConfig, 'utf-8')
127
+ const userConfig = JSON.parse(content)
128
+ return { ...defaultConfig, ...userConfig }
129
+ } catch (err) {
130
+ logError(`Erreur de configuration: ${err.message}`)
131
+ return defaultConfig
132
+ }
133
+ }
134
+
135
+ return defaultConfig
136
+ }
137
+
138
+ async function cmdInit() {
139
+ showBanner()
140
+ logInfo('Initialisation du projet Ether...')
141
+
142
+ const cwd = process.cwd()
143
+
144
+ const srcDir = path.join(cwd, 'src')
145
+ if (!fs.existsSync(srcDir)) {
146
+ fs.mkdirSync(srcDir, { recursive: true })
147
+ logSuccess('Dossier src/ créé')
148
+ }
149
+
150
+ const distDir = path.join(cwd, 'dist')
151
+ if (!fs.existsSync(distDir)) {
152
+ fs.mkdirSync(distDir, { recursive: true })
153
+ logSuccess('Dossier dist/ créé')
154
+ }
155
+
156
+ const configContent = `module.exports = {
157
+ src: './src',
158
+ out: './dist',
159
+
160
+ i18n: 'fr',
161
+
162
+ targets: {
163
+ css: {
164
+ extension: '.css',
165
+ minify: false
166
+ },
167
+ html: {
168
+ extension: '.html',
169
+ minify: false
170
+ },
171
+ js: {
172
+ extension: '.js',
173
+ minify: false
174
+ }
175
+ },
176
+
177
+ watch: {
178
+ ignored: ['node_modules', '.git', 'dist']
179
+ }
180
+ }
181
+ `
182
+
183
+ const configPath = path.join(cwd, 'ether.config.js')
184
+ if (!fs.existsSync(configPath)) {
185
+ fs.writeFileSync(configPath, configContent)
186
+ logSuccess('Fichier ether.config.js créé')
187
+ } else {
188
+ logWarning('ether.config.js existe déjà')
189
+ }
190
+
191
+ const exampleEth = `// Exemple de fichier Ether
192
+ // Cible: html
193
+
194
+ page "Ma première page Ether"
195
+ entête
196
+ titre "Bienvenue"
197
+
198
+ corps
199
+ section #hero
200
+ titre "Ether"
201
+ paragraphe "Le langage intentionnel"
202
+ bouton .primary "Commencer"
203
+ `
204
+
205
+ const examplePath = path.join(srcDir, 'index.eth')
206
+ if (!fs.existsSync(examplePath)) {
207
+ fs.writeFileSync(examplePath, exampleEth)
208
+ logSuccess('Fichier exemple src/index.eth créé')
209
+ }
210
+
211
+ console.log('')
212
+ logSuccess('Projet initialisé avec succès!')
213
+ console.log('')
214
+ logInfo('Prochaines étapes:')
215
+ console.log(` 1. Éditer ${COLORS.cyan}src/index.eth${COLORS.reset}`)
216
+ console.log(` 2. Lancer ${COLORS.cyan}ether dev${COLORS.reset} pour le mode développement`)
217
+ console.log(` 3. Ou ${COLORS.cyan}ether build${COLORS.reset} pour compiler`)
218
+ console.log('')
219
+ }
220
+
221
+ async function cmdBuild(options) {
222
+ const startTime = Date.now()
223
+
224
+ if (!options.quiet) {
225
+ showBanner()
226
+ logInfo('Compilation en cours...')
227
+ console.log('')
228
+ }
229
+
230
+ const config = loadConfig(options.config)
231
+ const srcDir = path.resolve(process.cwd(), config.src)
232
+ const outDir = path.resolve(process.cwd(), options.output || config.out)
233
+
234
+ if (!fs.existsSync(srcDir)) {
235
+ logError(`Dossier source introuvable: ${srcDir}`)
236
+ process.exit(1)
237
+ }
238
+
239
+ if (!fs.existsSync(outDir)) {
240
+ fs.mkdirSync(outDir, { recursive: true })
241
+ }
242
+
243
+ const compiler = new EtherCompiler(config)
244
+
245
+ const files = findEthFiles(srcDir)
246
+
247
+ if (files.length === 0) {
248
+ logWarning('Aucun fichier .eth trouvé')
249
+ return
250
+ }
251
+
252
+ let successCount = 0
253
+ let errorCount = 0
254
+
255
+ for (const file of files) {
256
+ try {
257
+ const relativePath = path.relative(srcDir, file)
258
+ const result = await compiler.compileFile(file)
259
+
260
+ for (const output of result.outputs) {
261
+ const outPath = path.join(outDir, output.path)
262
+ const outDirPath = path.dirname(outPath)
263
+
264
+ if (!fs.existsSync(outDirPath)) {
265
+ fs.mkdirSync(outDirPath, { recursive: true })
266
+ }
267
+
268
+ fs.writeFileSync(outPath, output.content)
269
+
270
+ if (options.verbose) {
271
+ logSuccess(`${relativePath} → ${output.path}`)
272
+ }
273
+ }
274
+
275
+ successCount++
276
+ } catch (err) {
277
+ errorCount++
278
+ logError(`${path.relative(srcDir, file)}: ${err.message}`)
279
+
280
+ if (options.verbose) {
281
+ console.error(err.stack)
282
+ }
283
+ }
284
+ }
285
+
286
+ const duration = Date.now() - startTime
287
+
288
+ console.log('')
289
+ if (errorCount === 0) {
290
+ logSuccess(`${successCount} fichier(s) compilé(s) en ${duration}ms`)
291
+ } else {
292
+ logWarning(`${successCount} succès, ${errorCount} erreur(s) en ${duration}ms`)
293
+ }
294
+ }
295
+
296
+ async function cmdDev(options) {
297
+ showBanner()
298
+
299
+ const config = loadConfig(options.config)
300
+ const srcDir = path.resolve(process.cwd(), config.src)
301
+ const outDir = path.resolve(process.cwd(), options.output || config.out)
302
+
303
+ if (!fs.existsSync(srcDir)) {
304
+ logError(`Dossier source introuvable: ${srcDir}`)
305
+ process.exit(1)
306
+ }
307
+
308
+ logInfo(`Surveillance de ${srcDir}`)
309
+ logInfo(`Sortie vers ${outDir}`)
310
+ console.log('')
311
+ logInfo('En attente de modifications... (Ctrl+C pour arrêter)')
312
+ console.log('')
313
+
314
+ await cmdBuild({ ...options, quiet: true })
315
+
316
+ const watcher = new Watcher(srcDir, config.watch)
317
+ const compiler = new EtherCompiler(config)
318
+
319
+ watcher.on('change', async (filePath) => {
320
+ if (!filePath.endsWith('.eth')) return
321
+
322
+ const relativePath = path.relative(srcDir, filePath)
323
+ logInfo(`Modification: ${relativePath}`)
324
+
325
+ try {
326
+ const result = await compiler.compileFile(filePath)
327
+
328
+ for (const output of result.outputs) {
329
+ const outPath = path.join(outDir, output.path)
330
+ const outDirPath = path.dirname(outPath)
331
+
332
+ if (!fs.existsSync(outDirPath)) {
333
+ fs.mkdirSync(outDirPath, { recursive: true })
334
+ }
335
+
336
+ fs.writeFileSync(outPath, output.content)
337
+ logSuccess(`→ ${output.path}`)
338
+ }
339
+ } catch (err) {
340
+ logError(err.message)
341
+ }
342
+ })
343
+
344
+ watcher.on('add', async (filePath) => {
345
+ if (!filePath.endsWith('.eth')) return
346
+
347
+ const relativePath = path.relative(srcDir, filePath)
348
+ logInfo(`Nouveau fichier: ${relativePath}`)
349
+
350
+ try {
351
+ const result = await compiler.compileFile(filePath)
352
+
353
+ for (const output of result.outputs) {
354
+ const outPath = path.join(outDir, output.path)
355
+ const outDirPath = path.dirname(outPath)
356
+
357
+ if (!fs.existsSync(outDirPath)) {
358
+ fs.mkdirSync(outDirPath, { recursive: true })
359
+ }
360
+
361
+ fs.writeFileSync(outPath, output.content)
362
+ logSuccess(`→ ${output.path}`)
363
+ }
364
+ } catch (err) {
365
+ logError(err.message)
366
+ }
367
+ })
368
+
369
+ watcher.on('unlink', (filePath) => {
370
+ if (!filePath.endsWith('.eth')) return
371
+ logWarning(`Fichier supprimé: ${path.relative(srcDir, filePath)}`)
372
+ })
373
+
374
+ watcher.start()
375
+
376
+ process.on('SIGINT', () => {
377
+ console.log('')
378
+ logInfo('Arrêt du mode développement')
379
+ watcher.stop()
380
+ process.exit(0)
381
+ })
382
+ }
383
+
384
+ async function cmdCheck(options) {
385
+ showBanner()
386
+ logInfo('Vérification de la syntaxe...')
387
+ console.log('')
388
+
389
+ const config = loadConfig(options.config)
390
+ const srcDir = path.resolve(process.cwd(), config.src)
391
+
392
+ if (!fs.existsSync(srcDir)) {
393
+ logError(`Dossier source introuvable: ${srcDir}`)
394
+ process.exit(1)
395
+ }
396
+
397
+ const compiler = new EtherCompiler(config)
398
+ const files = findEthFiles(srcDir)
399
+
400
+ if (files.length === 0) {
401
+ logWarning('Aucun fichier .eth trouvé')
402
+ return
403
+ }
404
+
405
+ let validCount = 0
406
+ let errorCount = 0
407
+
408
+ for (const file of files) {
409
+ const relativePath = path.relative(srcDir, file)
410
+
411
+ try {
412
+ await compiler.check(file)
413
+ logSuccess(relativePath)
414
+ validCount++
415
+ } catch (err) {
416
+ logError(`${relativePath}: ${err.message}`)
417
+ errorCount++
418
+ }
419
+ }
420
+
421
+ console.log('')
422
+ if (errorCount === 0) {
423
+ logSuccess(`${validCount} fichier(s) valide(s)`)
424
+ } else {
425
+ logError(`${errorCount} erreur(s) sur ${files.length} fichier(s)`)
426
+ process.exit(1)
427
+ }
428
+ }
429
+
430
+ function findEthFiles(dir) {
431
+ const files = []
432
+
433
+ function scan(currentDir) {
434
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true })
435
+
436
+ for (const entry of entries) {
437
+ const fullPath = path.join(currentDir, entry.name)
438
+
439
+ if (entry.isDirectory()) {
440
+ if (!['node_modules', '.git', 'dist'].includes(entry.name)) {
441
+ scan(fullPath)
442
+ }
443
+ } else if (entry.name.endsWith('.eth')) {
444
+ files.push(fullPath)
445
+ }
446
+ }
447
+ }
448
+
449
+ scan(dir)
450
+ return files
451
+ }
452
+
453
+ function parseArgs(args) {
454
+ const options = {
455
+ command: null,
456
+ config: null,
457
+ output: null,
458
+ verbose: false,
459
+ quiet: false,
460
+ watch: false
461
+ }
462
+
463
+ let i = 0
464
+ while (i < args.length) {
465
+ const arg = args[i]
466
+
467
+ if (arg === '-c' || arg === '--config') {
468
+ options.config = args[++i]
469
+ } else if (arg === '-o' || arg === '--output') {
470
+ options.output = args[++i]
471
+ } else if (arg === '-v' || arg === '--verbose') {
472
+ options.verbose = true
473
+ } else if (arg === '-q' || arg === '--quiet') {
474
+ options.quiet = true
475
+ } else if (arg === '-w' || arg === '--watch') {
476
+ options.watch = true
477
+ } else if (arg === '--no-color') {
478
+ for (const key in COLORS) {
479
+ COLORS[key] = ''
480
+ }
481
+ } else if (!arg.startsWith('-')) {
482
+ if (!options.command) {
483
+ options.command = arg
484
+ }
485
+ }
486
+
487
+ i++
488
+ }
489
+
490
+ return options
491
+ }
492
+
493
+ async function main() {
494
+ const args = process.argv.slice(2)
495
+ const options = parseArgs(args)
496
+
497
+ if (options.watch && !options.command) {
498
+ options.command = 'dev'
499
+ }
500
+
501
+ switch (options.command) {
502
+ case 'init':
503
+ await cmdInit()
504
+ break
505
+ case 'build':
506
+ await cmdBuild(options)
507
+ break
508
+ case 'dev':
509
+ case 'watch':
510
+ await cmdDev(options)
511
+ break
512
+ case 'check':
513
+ await cmdCheck(options)
514
+ break
515
+ case 'version':
516
+ case '-V':
517
+ case '--version':
518
+ showVersion()
519
+ break
520
+ case 'help':
521
+ case '-h':
522
+ case '--help':
523
+ default:
524
+ showHelp()
525
+ break
526
+ }
527
+ }
528
+
529
+ main().catch(err => {
530
+ logError(err.message)
531
+ process.exit(1)
532
+ })
package/cli/watcher.js ADDED
@@ -0,0 +1,106 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const EventEmitter = require('events')
4
+
5
+ class Watcher extends EventEmitter {
6
+ constructor(dir, options = {}) {
7
+ super()
8
+ this.dir = dir
9
+ this.options = {
10
+ ignored: ['node_modules', '.git', 'dist'],
11
+ debounce: 100,
12
+ ...options
13
+ }
14
+ this.watchers = new Map()
15
+ this.debounceTimers = new Map()
16
+ }
17
+
18
+ start() {
19
+ this.watchDir(this.dir)
20
+ }
21
+
22
+ stop() {
23
+ for (const watcher of this.watchers.values()) {
24
+ watcher.close()
25
+ }
26
+ this.watchers.clear()
27
+
28
+ for (const timer of this.debounceTimers.values()) {
29
+ clearTimeout(timer)
30
+ }
31
+ this.debounceTimers.clear()
32
+ }
33
+
34
+ watchDir(dir) {
35
+ if (this.isIgnored(dir)) return
36
+
37
+ try {
38
+ const watcher = fs.watch(dir, { persistent: true }, (eventType, filename) => {
39
+ if (!filename) return
40
+
41
+ const fullPath = path.join(dir, filename)
42
+ this.handleEvent(eventType, fullPath)
43
+ })
44
+
45
+ watcher.on('error', (err) => {
46
+ this.emit('error', err)
47
+ })
48
+
49
+ this.watchers.set(dir, watcher)
50
+
51
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
52
+ for (const entry of entries) {
53
+ if (entry.isDirectory() && !this.isIgnored(entry.name)) {
54
+ this.watchDir(path.join(dir, entry.name))
55
+ }
56
+ }
57
+ } catch (err) {
58
+ this.emit('error', err)
59
+ }
60
+ }
61
+
62
+ handleEvent(eventType, filePath) {
63
+ const existing = this.debounceTimers.get(filePath)
64
+ if (existing) {
65
+ clearTimeout(existing)
66
+ }
67
+
68
+ const timer = setTimeout(() => {
69
+ this.debounceTimers.delete(filePath)
70
+ this.processEvent(filePath)
71
+ }, this.options.debounce)
72
+
73
+ this.debounceTimers.set(filePath, timer)
74
+ }
75
+
76
+ processEvent(filePath) {
77
+ try {
78
+ const exists = fs.existsSync(filePath)
79
+
80
+ if (!exists) {
81
+ this.emit('unlink', filePath)
82
+ return
83
+ }
84
+
85
+ const stat = fs.statSync(filePath)
86
+
87
+ if (stat.isDirectory()) {
88
+ if (!this.watchers.has(filePath)) {
89
+ this.watchDir(filePath)
90
+ this.emit('addDir', filePath)
91
+ }
92
+ } else {
93
+ this.emit('change', filePath)
94
+ }
95
+ } catch (err) {
96
+ this.emit('error', err)
97
+ }
98
+ }
99
+
100
+ isIgnored(name) {
101
+ const baseName = path.basename(name)
102
+ return this.options.ignored.includes(baseName) || baseName.startsWith('.')
103
+ }
104
+ }
105
+
106
+ module.exports = { Watcher }