ether-code 0.5.1 → 0.5.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 +86 -1
- package/cli/ether.js +1 -1
- package/ether-compiler.js +85 -4
- package/ether-parser.js +74 -0
- package/generators/html-generator.js +13 -0
- package/generators/php-generator.js +221 -124
- package/i18n/i18n-css.json +702 -4871
- package/i18n/i18n-html.json +2410 -572
- package/i18n/i18n-js.json +418 -2781
- package/i18n/i18n-php.json +4414 -4305
- package/package.json +1 -1
package/cli/compiler.js
CHANGED
|
@@ -68,6 +68,7 @@ class EtherCompiler {
|
|
|
68
68
|
|
|
69
69
|
this.generators = {}
|
|
70
70
|
this.parser = null
|
|
71
|
+
this.resolvedIncludes = new Set()
|
|
71
72
|
|
|
72
73
|
this.initGenerators()
|
|
73
74
|
this.initParser()
|
|
@@ -110,10 +111,14 @@ class EtherCompiler {
|
|
|
110
111
|
compileFile(filePath) {
|
|
111
112
|
const content = fs.readFileSync(filePath, 'utf-8')
|
|
112
113
|
const fileName = path.basename(filePath, '.eth')
|
|
114
|
+
const basePath = path.dirname(path.resolve(filePath))
|
|
113
115
|
|
|
114
116
|
const target = this.detectTarget(content, filePath)
|
|
115
117
|
const ast = this.parse(content, target)
|
|
116
|
-
|
|
118
|
+
|
|
119
|
+
const resolvedAst = this.resolveIncludes(ast, basePath, target)
|
|
120
|
+
|
|
121
|
+
const code = this.generate(resolvedAst, target)
|
|
117
122
|
|
|
118
123
|
const extension = this.getExtension(target)
|
|
119
124
|
const outputPath = fileName + extension
|
|
@@ -128,6 +133,86 @@ class EtherCompiler {
|
|
|
128
133
|
}
|
|
129
134
|
}
|
|
130
135
|
|
|
136
|
+
resolveIncludes(ast, basePath, targetLang) {
|
|
137
|
+
const self = this
|
|
138
|
+
|
|
139
|
+
const visit = (node) => {
|
|
140
|
+
if (!node || typeof node !== 'object') return node
|
|
141
|
+
|
|
142
|
+
if (node.type === 'Include' && node.path) {
|
|
143
|
+
let includePath = node.path
|
|
144
|
+
|
|
145
|
+
if (!includePath.endsWith('.eth') && !includePath.endsWith('.ether')) {
|
|
146
|
+
includePath = includePath.replace(/\.(html|php|js|css)$/i, '.eth')
|
|
147
|
+
if (!includePath.endsWith('.eth')) {
|
|
148
|
+
includePath += '.eth'
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const fullPath = path.resolve(basePath, includePath)
|
|
153
|
+
|
|
154
|
+
if (self.resolvedIncludes.has(fullPath)) {
|
|
155
|
+
console.warn(`Inclusion circulaire détectée: ${fullPath}`)
|
|
156
|
+
return null
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!fs.existsSync(fullPath)) {
|
|
160
|
+
console.warn(`Fichier inclus non trouvé: ${fullPath}`)
|
|
161
|
+
return {
|
|
162
|
+
type: 'Comment',
|
|
163
|
+
content: `Fichier non trouvé: ${node.path}`
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
self.resolvedIncludes.add(fullPath)
|
|
168
|
+
|
|
169
|
+
const includeSource = fs.readFileSync(fullPath, 'utf-8')
|
|
170
|
+
const includeDir = path.dirname(fullPath)
|
|
171
|
+
|
|
172
|
+
const normalizedTarget = self.normalizeTarget(targetLang)
|
|
173
|
+
const includeAst = self.parser.parse(includeSource, normalizedTarget)
|
|
174
|
+
|
|
175
|
+
const resolvedIncludeAst = self.resolveIncludes(includeAst, includeDir, targetLang)
|
|
176
|
+
|
|
177
|
+
self.resolvedIncludes.delete(fullPath)
|
|
178
|
+
|
|
179
|
+
if (resolvedIncludeAst.children && Array.isArray(resolvedIncludeAst.children)) {
|
|
180
|
+
return resolvedIncludeAst.children
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (resolvedIncludeAst.body && Array.isArray(resolvedIncludeAst.body)) {
|
|
184
|
+
return resolvedIncludeAst.body
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return resolvedIncludeAst
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (node.children && Array.isArray(node.children)) {
|
|
191
|
+
node.children = node.children.flatMap(child => {
|
|
192
|
+
const result = visit(child)
|
|
193
|
+
if (Array.isArray(result)) {
|
|
194
|
+
return result
|
|
195
|
+
}
|
|
196
|
+
return result ? [result] : []
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (node.body && Array.isArray(node.body)) {
|
|
201
|
+
node.body = node.body.flatMap(child => {
|
|
202
|
+
const result = visit(child)
|
|
203
|
+
if (Array.isArray(result)) {
|
|
204
|
+
return result
|
|
205
|
+
}
|
|
206
|
+
return result ? [result] : []
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return node
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return visit(ast)
|
|
214
|
+
}
|
|
215
|
+
|
|
131
216
|
parse(content, target) {
|
|
132
217
|
const normalizedTarget = this.normalizeTarget(target)
|
|
133
218
|
return this.parser.parse(content, normalizedTarget)
|
package/cli/ether.js
CHANGED
package/ether-compiler.js
CHANGED
|
@@ -17,8 +17,10 @@ const { GraphQLGenerator } = require('./generators/graphql-generator')
|
|
|
17
17
|
class EtherCompiler {
|
|
18
18
|
constructor(options = {}) {
|
|
19
19
|
this.i18nDir = options.i18nDir || path.join(__dirname, 'i18n')
|
|
20
|
+
this.basePath = options.basePath || process.cwd()
|
|
20
21
|
this.parser = new EtherParser({ i18nDir: this.i18nDir })
|
|
21
22
|
this.generators = {}
|
|
23
|
+
this.resolvedIncludes = new Set()
|
|
22
24
|
this.initGenerators()
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -59,16 +61,94 @@ class EtherCompiler {
|
|
|
59
61
|
this.generators.gql = this.generators.graphql
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
compile(source, targetLang = 'auto') {
|
|
64
|
+
compile(source, targetLang = 'auto', basePath = null) {
|
|
63
65
|
const ast = this.parser.parse(source, targetLang)
|
|
64
66
|
const lang = this.detectLanguage(ast, targetLang, source)
|
|
67
|
+
|
|
68
|
+
const resolvedAst = this.resolveIncludes(ast, basePath || this.basePath, lang)
|
|
69
|
+
|
|
65
70
|
const generator = this.generators[lang]
|
|
66
71
|
|
|
67
72
|
if (!generator) {
|
|
68
73
|
throw new Error(`Générateur non disponible pour: ${lang}`)
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
return generator.generate(
|
|
76
|
+
return generator.generate(resolvedAst)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
resolveIncludes(ast, basePath, targetLang) {
|
|
80
|
+
const self = this
|
|
81
|
+
|
|
82
|
+
const visit = (node) => {
|
|
83
|
+
if (!node || typeof node !== 'object') return node
|
|
84
|
+
|
|
85
|
+
if (node.type === 'Include' && node.path) {
|
|
86
|
+
let includePath = node.path
|
|
87
|
+
|
|
88
|
+
if (!includePath.endsWith('.eth') && !includePath.endsWith('.ether')) {
|
|
89
|
+
includePath = includePath.replace(/\.(html|php|js|css)$/i, '.eth')
|
|
90
|
+
if (!includePath.endsWith('.eth')) {
|
|
91
|
+
includePath += '.eth'
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const fullPath = path.resolve(basePath, includePath)
|
|
96
|
+
|
|
97
|
+
if (self.resolvedIncludes.has(fullPath)) {
|
|
98
|
+
console.warn(`Inclusion circulaire détectée: ${fullPath}`)
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!fs.existsSync(fullPath)) {
|
|
103
|
+
console.warn(`Fichier inclus non trouvé: ${fullPath}`)
|
|
104
|
+
return {
|
|
105
|
+
type: 'Comment',
|
|
106
|
+
content: `Fichier non trouvé: ${node.path}`
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
self.resolvedIncludes.add(fullPath)
|
|
111
|
+
|
|
112
|
+
const includeSource = fs.readFileSync(fullPath, 'utf-8')
|
|
113
|
+
const includeDir = path.dirname(fullPath)
|
|
114
|
+
|
|
115
|
+
const includeAst = self.parser.parse(includeSource, targetLang)
|
|
116
|
+
|
|
117
|
+
const resolvedIncludeAst = self.resolveIncludes(includeAst, includeDir, targetLang)
|
|
118
|
+
|
|
119
|
+
self.resolvedIncludes.delete(fullPath)
|
|
120
|
+
|
|
121
|
+
if (resolvedIncludeAst.children && Array.isArray(resolvedIncludeAst.children)) {
|
|
122
|
+
return resolvedIncludeAst.children
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return resolvedIncludeAst
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (node.children && Array.isArray(node.children)) {
|
|
129
|
+
node.children = node.children.flatMap(child => {
|
|
130
|
+
const result = visit(child)
|
|
131
|
+
if (Array.isArray(result)) {
|
|
132
|
+
return result
|
|
133
|
+
}
|
|
134
|
+
return result ? [result] : []
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (node.body && Array.isArray(node.body)) {
|
|
139
|
+
node.body = node.body.flatMap(child => {
|
|
140
|
+
const result = visit(child)
|
|
141
|
+
if (Array.isArray(result)) {
|
|
142
|
+
return result
|
|
143
|
+
}
|
|
144
|
+
return result ? [result] : []
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return node
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return visit(ast)
|
|
72
152
|
}
|
|
73
153
|
|
|
74
154
|
detectLanguage(ast, targetLang, source) {
|
|
@@ -87,7 +167,8 @@ class EtherCompiler {
|
|
|
87
167
|
|
|
88
168
|
compileFile(inputPath, outputPath = null, targetLang = 'auto') {
|
|
89
169
|
const source = fs.readFileSync(inputPath, 'utf-8')
|
|
90
|
-
const
|
|
170
|
+
const basePath = path.dirname(path.resolve(inputPath))
|
|
171
|
+
const result = this.compile(source, targetLang, basePath)
|
|
91
172
|
|
|
92
173
|
if (outputPath) {
|
|
93
174
|
const dir = path.dirname(outputPath)
|
|
@@ -187,4 +268,4 @@ module.exports = {
|
|
|
187
268
|
compileFile,
|
|
188
269
|
parse,
|
|
189
270
|
tokenize
|
|
190
|
-
}
|
|
271
|
+
}
|
package/ether-parser.js
CHANGED
|
@@ -1179,6 +1179,43 @@ class EtherParser {
|
|
|
1179
1179
|
let tagName = tagToken.value.toLowerCase()
|
|
1180
1180
|
this.advance()
|
|
1181
1181
|
|
|
1182
|
+
const includeKeywords = ['inclure', 'requiert', 'include', 'require', 'importer']
|
|
1183
|
+
if (includeKeywords.includes(tagName)) {
|
|
1184
|
+
let includePath = null
|
|
1185
|
+
|
|
1186
|
+
if (this.current() && this.current().type === TokenType.COLON) {
|
|
1187
|
+
this.advance()
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
const pathToken = this.current()
|
|
1191
|
+
if (pathToken && pathToken.type === TokenType.STRING) {
|
|
1192
|
+
includePath = pathToken.value.replace(/^["']|["']$/g, '')
|
|
1193
|
+
this.advance()
|
|
1194
|
+
} else if (pathToken && pathToken.type === TokenType.IDENTIFIER) {
|
|
1195
|
+
let pathParts = [pathToken.value]
|
|
1196
|
+
this.advance()
|
|
1197
|
+
while (this.current() && (this.current().type === TokenType.SLASH || this.current().type === TokenType.DOT || this.current().type === TokenType.IDENTIFIER)) {
|
|
1198
|
+
if (this.current().type === TokenType.SLASH) {
|
|
1199
|
+
pathParts.push('/')
|
|
1200
|
+
} else if (this.current().type === TokenType.DOT) {
|
|
1201
|
+
pathParts.push('.')
|
|
1202
|
+
} else {
|
|
1203
|
+
pathParts.push(this.current().value)
|
|
1204
|
+
}
|
|
1205
|
+
this.advance()
|
|
1206
|
+
}
|
|
1207
|
+
includePath = pathParts.join('')
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
this.skipNewlines()
|
|
1211
|
+
|
|
1212
|
+
return {
|
|
1213
|
+
type: 'Include',
|
|
1214
|
+
path: includePath,
|
|
1215
|
+
resolved: false
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1182
1219
|
const nextToken = this.current()
|
|
1183
1220
|
if ((tagName === 'titre' || tagName === 'heading') && nextToken && (nextToken.type === TokenType.NUMBER || nextToken.type === TokenType.INTEGER)) {
|
|
1184
1221
|
const num = parseInt(nextToken.value)
|
|
@@ -2245,8 +2282,31 @@ class EtherParser {
|
|
|
2245
2282
|
}
|
|
2246
2283
|
|
|
2247
2284
|
parseImport(lang) {
|
|
2285
|
+
const importToken = this.current()
|
|
2286
|
+
const importKeyword = importToken ? importToken.value.toLowerCase() : ''
|
|
2248
2287
|
this.advance()
|
|
2249
2288
|
|
|
2289
|
+
const includeKeywords = ['inclure', 'requiert', 'include', 'require']
|
|
2290
|
+
|
|
2291
|
+
if (this.current() && this.current().type === TokenType.COLON) {
|
|
2292
|
+
this.advance()
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
const nextToken = this.current()
|
|
2296
|
+
if (includeKeywords.includes(importKeyword) && nextToken && nextToken.type === TokenType.STRING) {
|
|
2297
|
+
const includePath = nextToken.value.replace(/^["']|["']$/g, '')
|
|
2298
|
+
this.advance()
|
|
2299
|
+
|
|
2300
|
+
if (includePath.endsWith('.eth') || includePath.endsWith('.ether') ||
|
|
2301
|
+
!includePath.includes('.') || includePath.match(/\.(html|php|js|css)$/i)) {
|
|
2302
|
+
return {
|
|
2303
|
+
type: 'Include',
|
|
2304
|
+
path: includePath,
|
|
2305
|
+
resolved: false
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2250
2310
|
const imports = []
|
|
2251
2311
|
let source = null
|
|
2252
2312
|
let defaultImport = null
|
|
@@ -2277,6 +2337,20 @@ class EtherParser {
|
|
|
2277
2337
|
} else if (token && token.type === TokenType.IDENTIFIER) {
|
|
2278
2338
|
defaultImport = token.value
|
|
2279
2339
|
this.advance()
|
|
2340
|
+
} else if (token && token.type === TokenType.STRING) {
|
|
2341
|
+
source = token.value.replace(/^["']|["']$/g, '')
|
|
2342
|
+
this.advance()
|
|
2343
|
+
|
|
2344
|
+
if (includeKeywords.includes(importKeyword)) {
|
|
2345
|
+
if (source.endsWith('.eth') || source.endsWith('.ether') ||
|
|
2346
|
+
!source.includes('.') || source.match(/\.(html|php|js|css)$/i)) {
|
|
2347
|
+
return {
|
|
2348
|
+
type: 'Include',
|
|
2349
|
+
path: source,
|
|
2350
|
+
resolved: false
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2280
2354
|
}
|
|
2281
2355
|
|
|
2282
2356
|
if (this.matchValue('depuis') || this.matchValue('de') || this.matchValue('from')) {
|
|
@@ -414,6 +414,9 @@ class HTMLGenerator {
|
|
|
414
414
|
case 'doctype':
|
|
415
415
|
this.generateDoctype(node)
|
|
416
416
|
break
|
|
417
|
+
case 'Include':
|
|
418
|
+
this.generateInclude(node)
|
|
419
|
+
break
|
|
417
420
|
default:
|
|
418
421
|
if (node.tag || node.tagName) {
|
|
419
422
|
this.generateElement(node)
|
|
@@ -591,6 +594,16 @@ class HTMLGenerator {
|
|
|
591
594
|
this.output += '<!DOCTYPE html>\n'
|
|
592
595
|
}
|
|
593
596
|
|
|
597
|
+
generateInclude(node) {
|
|
598
|
+
if (node.resolved && node.children) {
|
|
599
|
+
for (const child of node.children) {
|
|
600
|
+
this.generateNode(child)
|
|
601
|
+
}
|
|
602
|
+
} else {
|
|
603
|
+
this.writeLine(`<!-- Include non résolu: ${node.path || 'inconnu'} -->`)
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
594
607
|
writeLine(text) {
|
|
595
608
|
const indentation = ' '.repeat(this.indent)
|
|
596
609
|
this.output += indentation + text + '\n'
|