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 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
- const code = this.generate(ast, target)
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
@@ -6,7 +6,7 @@ const http = require('http')
6
6
  const { EtherCompiler } = require('./compiler')
7
7
  const { Watcher } = require('./watcher')
8
8
 
9
- const VERSION = '0.5.1'
9
+ const VERSION = '0.5.4'
10
10
 
11
11
  const COLORS = {
12
12
  reset: '\x1b[0m',
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(ast)
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 result = this.compile(source, targetLang)
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'