ether-code 0.1.9 → 0.2.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.
- package/LICENSE +1 -1
- package/README.md +27 -89
- package/cli/compiler.js +11 -25
- package/cli/ether.js +534 -0
- package/cli/watcher.js +106 -0
- package/ether-compiler.js +1 -1
- package/ether-parser.js +1 -1
- package/i18n/i18n-css.json +743 -0
- package/i18n/i18n-graphql.json +1531 -0
- package/i18n/i18n-html.json +572 -0
- package/i18n/i18n-js.json +2790 -0
- package/i18n/i18n-node.json +2442 -0
- package/i18n/i18n-php.json +4306 -0
- package/i18n/i18n-python.json +3080 -0
- package/i18n/i18n-react.json +1784 -0
- package/i18n/i18n-ruby.json +1858 -0
- package/i18n/i18n-sql.json +3466 -0
- package/i18n/i18n-ts.json +442 -0
- package/lexer/ether-lexer.js +869 -0
- package/lexer/tokens.js +292 -0
- package/package.json +1 -1
package/cli/ether.js
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
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.2.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 = `// cible: html
|
|
192
|
+
|
|
193
|
+
document
|
|
194
|
+
tete
|
|
195
|
+
titre "Ma première page Ether"
|
|
196
|
+
|
|
197
|
+
corps
|
|
198
|
+
entete
|
|
199
|
+
titre1 "Bienvenue sur Ether"
|
|
200
|
+
|
|
201
|
+
principal
|
|
202
|
+
section
|
|
203
|
+
titre2 "Le langage intentionnel"
|
|
204
|
+
paragraphe "Ether permet d'écrire du code comme on pense."
|
|
205
|
+
|
|
206
|
+
pied
|
|
207
|
+
paragraphe "Créé avec Ether"
|
|
208
|
+
`
|
|
209
|
+
|
|
210
|
+
const examplePath = path.join(srcDir, 'index.eth')
|
|
211
|
+
if (!fs.existsSync(examplePath)) {
|
|
212
|
+
fs.writeFileSync(examplePath, exampleEth)
|
|
213
|
+
logSuccess('Fichier exemple src/index.eth créé')
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.log('')
|
|
217
|
+
logSuccess('Projet initialisé avec succès!')
|
|
218
|
+
console.log('')
|
|
219
|
+
logInfo('Prochaines étapes:')
|
|
220
|
+
console.log(` 1. Éditer ${COLORS.cyan}src/index.eth${COLORS.reset}`)
|
|
221
|
+
console.log(` 2. Lancer ${COLORS.cyan}ether dev${COLORS.reset} pour le mode développement`)
|
|
222
|
+
console.log(` 3. Ou ${COLORS.cyan}ether build${COLORS.reset} pour compiler`)
|
|
223
|
+
console.log('')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function cmdBuild(options) {
|
|
227
|
+
const startTime = Date.now()
|
|
228
|
+
|
|
229
|
+
if (!options.quiet) {
|
|
230
|
+
showBanner()
|
|
231
|
+
logInfo('Compilation en cours...')
|
|
232
|
+
console.log('')
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const config = loadConfig(options.config)
|
|
236
|
+
const srcDir = path.resolve(process.cwd(), config.src)
|
|
237
|
+
const outDir = path.resolve(process.cwd(), options.output || config.out)
|
|
238
|
+
|
|
239
|
+
if (!fs.existsSync(srcDir)) {
|
|
240
|
+
logError(`Dossier source introuvable: ${srcDir}`)
|
|
241
|
+
process.exit(1)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!fs.existsSync(outDir)) {
|
|
245
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const compiler = new EtherCompiler(config)
|
|
249
|
+
|
|
250
|
+
const files = findEthFiles(srcDir)
|
|
251
|
+
|
|
252
|
+
if (files.length === 0) {
|
|
253
|
+
logWarning('Aucun fichier .eth trouvé')
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let successCount = 0
|
|
258
|
+
let errorCount = 0
|
|
259
|
+
|
|
260
|
+
for (const file of files) {
|
|
261
|
+
try {
|
|
262
|
+
const relativePath = path.relative(srcDir, file)
|
|
263
|
+
const result = await compiler.compileFile(file)
|
|
264
|
+
|
|
265
|
+
for (const output of result.outputs) {
|
|
266
|
+
const outPath = path.join(outDir, output.path)
|
|
267
|
+
const outDirPath = path.dirname(outPath)
|
|
268
|
+
|
|
269
|
+
if (!fs.existsSync(outDirPath)) {
|
|
270
|
+
fs.mkdirSync(outDirPath, { recursive: true })
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
fs.writeFileSync(outPath, output.content)
|
|
274
|
+
|
|
275
|
+
if (options.verbose) {
|
|
276
|
+
logSuccess(`${relativePath} → ${output.path}`)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
successCount++
|
|
281
|
+
} catch (err) {
|
|
282
|
+
errorCount++
|
|
283
|
+
logError(`${path.relative(srcDir, file)}: ${err.message}`)
|
|
284
|
+
|
|
285
|
+
if (options.verbose) {
|
|
286
|
+
console.error(err.stack)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const duration = Date.now() - startTime
|
|
292
|
+
|
|
293
|
+
console.log('')
|
|
294
|
+
if (errorCount === 0) {
|
|
295
|
+
logSuccess(`${successCount} fichier(s) compilé(s) en ${duration}ms`)
|
|
296
|
+
} else {
|
|
297
|
+
logWarning(`${successCount} succès, ${errorCount} erreur(s) en ${duration}ms`)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function cmdDev(options) {
|
|
302
|
+
showBanner()
|
|
303
|
+
|
|
304
|
+
const config = loadConfig(options.config)
|
|
305
|
+
const srcDir = path.resolve(process.cwd(), config.src)
|
|
306
|
+
const outDir = path.resolve(process.cwd(), options.output || config.out)
|
|
307
|
+
|
|
308
|
+
if (!fs.existsSync(srcDir)) {
|
|
309
|
+
logError(`Dossier source introuvable: ${srcDir}`)
|
|
310
|
+
process.exit(1)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
logInfo(`Surveillance de ${srcDir}`)
|
|
314
|
+
logInfo(`Sortie vers ${outDir}`)
|
|
315
|
+
|
|
316
|
+
await cmdBuild({ ...options, quiet: true })
|
|
317
|
+
|
|
318
|
+
const watcher = new Watcher(srcDir, config.watch)
|
|
319
|
+
const compiler = new EtherCompiler(config)
|
|
320
|
+
|
|
321
|
+
watcher.on('change', async (filePath) => {
|
|
322
|
+
if (!filePath.endsWith('.eth')) return
|
|
323
|
+
|
|
324
|
+
const relativePath = path.relative(srcDir, filePath)
|
|
325
|
+
logInfo(`Modification: ${relativePath}`)
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
const result = await compiler.compileFile(filePath)
|
|
329
|
+
|
|
330
|
+
for (const output of result.outputs) {
|
|
331
|
+
const outPath = path.join(outDir, output.path)
|
|
332
|
+
const outDirPath = path.dirname(outPath)
|
|
333
|
+
|
|
334
|
+
if (!fs.existsSync(outDirPath)) {
|
|
335
|
+
fs.mkdirSync(outDirPath, { recursive: true })
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
fs.writeFileSync(outPath, output.content)
|
|
339
|
+
logSuccess(`→ ${output.path}`)
|
|
340
|
+
}
|
|
341
|
+
} catch (err) {
|
|
342
|
+
logError(err.message)
|
|
343
|
+
}
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
watcher.on('add', async (filePath) => {
|
|
347
|
+
if (!filePath.endsWith('.eth')) return
|
|
348
|
+
|
|
349
|
+
const relativePath = path.relative(srcDir, filePath)
|
|
350
|
+
logInfo(`Nouveau fichier: ${relativePath}`)
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const result = await compiler.compileFile(filePath)
|
|
354
|
+
|
|
355
|
+
for (const output of result.outputs) {
|
|
356
|
+
const outPath = path.join(outDir, output.path)
|
|
357
|
+
const outDirPath = path.dirname(outPath)
|
|
358
|
+
|
|
359
|
+
if (!fs.existsSync(outDirPath)) {
|
|
360
|
+
fs.mkdirSync(outDirPath, { recursive: true })
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
fs.writeFileSync(outPath, output.content)
|
|
364
|
+
logSuccess(`→ ${output.path}`)
|
|
365
|
+
}
|
|
366
|
+
} catch (err) {
|
|
367
|
+
logError(err.message)
|
|
368
|
+
}
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
watcher.on('unlink', (filePath) => {
|
|
372
|
+
if (!filePath.endsWith('.eth')) return
|
|
373
|
+
logWarning(`Fichier supprimé: ${path.relative(srcDir, filePath)}`)
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
watcher.start()
|
|
377
|
+
|
|
378
|
+
process.on('SIGINT', () => {
|
|
379
|
+
console.log('')
|
|
380
|
+
logInfo('Arrêt du mode développement')
|
|
381
|
+
watcher.stop()
|
|
382
|
+
process.exit(0)
|
|
383
|
+
})
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async function cmdCheck(options) {
|
|
387
|
+
showBanner()
|
|
388
|
+
logInfo('Vérification de la syntaxe...')
|
|
389
|
+
console.log('')
|
|
390
|
+
|
|
391
|
+
const config = loadConfig(options.config)
|
|
392
|
+
const srcDir = path.resolve(process.cwd(), config.src)
|
|
393
|
+
|
|
394
|
+
if (!fs.existsSync(srcDir)) {
|
|
395
|
+
logError(`Dossier source introuvable: ${srcDir}`)
|
|
396
|
+
process.exit(1)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const compiler = new EtherCompiler(config)
|
|
400
|
+
const files = findEthFiles(srcDir)
|
|
401
|
+
|
|
402
|
+
if (files.length === 0) {
|
|
403
|
+
logWarning('Aucun fichier .eth trouvé')
|
|
404
|
+
return
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
let validCount = 0
|
|
408
|
+
let errorCount = 0
|
|
409
|
+
|
|
410
|
+
for (const file of files) {
|
|
411
|
+
const relativePath = path.relative(srcDir, file)
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
await compiler.check(file)
|
|
415
|
+
logSuccess(relativePath)
|
|
416
|
+
validCount++
|
|
417
|
+
} catch (err) {
|
|
418
|
+
logError(`${relativePath}: ${err.message}`)
|
|
419
|
+
errorCount++
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
console.log('')
|
|
424
|
+
if (errorCount === 0) {
|
|
425
|
+
logSuccess(`${validCount} fichier(s) valide(s)`)
|
|
426
|
+
} else {
|
|
427
|
+
logError(`${errorCount} erreur(s) sur ${files.length} fichier(s)`)
|
|
428
|
+
process.exit(1)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function findEthFiles(dir) {
|
|
433
|
+
const files = []
|
|
434
|
+
|
|
435
|
+
function scan(currentDir) {
|
|
436
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true })
|
|
437
|
+
|
|
438
|
+
for (const entry of entries) {
|
|
439
|
+
const fullPath = path.join(currentDir, entry.name)
|
|
440
|
+
|
|
441
|
+
if (entry.isDirectory()) {
|
|
442
|
+
if (!['node_modules', '.git', 'dist'].includes(entry.name)) {
|
|
443
|
+
scan(fullPath)
|
|
444
|
+
}
|
|
445
|
+
} else if (entry.name.endsWith('.eth')) {
|
|
446
|
+
files.push(fullPath)
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
scan(dir)
|
|
452
|
+
return files
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function parseArgs(args) {
|
|
456
|
+
const options = {
|
|
457
|
+
command: null,
|
|
458
|
+
config: null,
|
|
459
|
+
output: null,
|
|
460
|
+
verbose: false,
|
|
461
|
+
quiet: false,
|
|
462
|
+
watch: false
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
let i = 0
|
|
466
|
+
while (i < args.length) {
|
|
467
|
+
const arg = args[i]
|
|
468
|
+
|
|
469
|
+
if (arg === '-c' || arg === '--config') {
|
|
470
|
+
options.config = args[++i]
|
|
471
|
+
} else if (arg === '-o' || arg === '--output') {
|
|
472
|
+
options.output = args[++i]
|
|
473
|
+
} else if (arg === '-v' || arg === '--verbose') {
|
|
474
|
+
options.verbose = true
|
|
475
|
+
} else if (arg === '-q' || arg === '--quiet') {
|
|
476
|
+
options.quiet = true
|
|
477
|
+
} else if (arg === '-w' || arg === '--watch') {
|
|
478
|
+
options.watch = true
|
|
479
|
+
} else if (arg === '--no-color') {
|
|
480
|
+
for (const key in COLORS) {
|
|
481
|
+
COLORS[key] = ''
|
|
482
|
+
}
|
|
483
|
+
} else if (!arg.startsWith('-')) {
|
|
484
|
+
if (!options.command) {
|
|
485
|
+
options.command = arg
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
i++
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return options
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function main() {
|
|
496
|
+
const args = process.argv.slice(2)
|
|
497
|
+
const options = parseArgs(args)
|
|
498
|
+
|
|
499
|
+
if (options.watch && !options.command) {
|
|
500
|
+
options.command = 'dev'
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
switch (options.command) {
|
|
504
|
+
case 'init':
|
|
505
|
+
await cmdInit()
|
|
506
|
+
break
|
|
507
|
+
case 'build':
|
|
508
|
+
await cmdBuild(options)
|
|
509
|
+
break
|
|
510
|
+
case 'dev':
|
|
511
|
+
case 'watch':
|
|
512
|
+
await cmdDev(options)
|
|
513
|
+
break
|
|
514
|
+
case 'check':
|
|
515
|
+
await cmdCheck(options)
|
|
516
|
+
break
|
|
517
|
+
case 'version':
|
|
518
|
+
case '-V':
|
|
519
|
+
case '--version':
|
|
520
|
+
showVersion()
|
|
521
|
+
break
|
|
522
|
+
case 'help':
|
|
523
|
+
case '-h':
|
|
524
|
+
case '--help':
|
|
525
|
+
default:
|
|
526
|
+
showHelp()
|
|
527
|
+
break
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
main().catch(err => {
|
|
532
|
+
logError(err.message)
|
|
533
|
+
process.exit(1)
|
|
534
|
+
})
|
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 }
|
package/ether-compiler.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
|
-
const { EtherLexer, TokenType } = require('./ether-lexer')
|
|
3
|
+
const { EtherLexer, TokenType } = require('./lexer/ether-lexer')
|
|
4
4
|
const { EtherParser } = require('./ether-parser')
|
|
5
5
|
const { CSSGenerator } = require('./generators/css-generator')
|
|
6
6
|
const { HTMLGenerator } = require('./generators/html-generator')
|
package/ether-parser.js
CHANGED