ether-code 0.3.9 → 0.4.1

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/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.3.9'
9
+ const VERSION = '0.4.1'
10
10
 
11
11
  const COLORS = {
12
12
  reset: '\x1b[0m',
package/ether-parser.js CHANGED
@@ -219,11 +219,15 @@ class EtherParser {
219
219
  this.skipNewlines()
220
220
 
221
221
  while (!this.isAtEnd()) {
222
+ const startPos = this.pos
222
223
  const rule = this.parseCSSRule()
223
224
  if (rule) {
224
225
  stylesheet.rules.push(rule)
225
226
  }
226
227
  this.skipNewlines()
228
+ if (this.pos === startPos && !this.isAtEnd()) {
229
+ this.advance()
230
+ }
227
231
  }
228
232
 
229
233
  return stylesheet
@@ -298,11 +302,27 @@ class EtherParser {
298
302
  const token = this.current()
299
303
 
300
304
  if (!token || token.type === TokenType.NEWLINE || token.type === TokenType.INDENT ||
301
- token.type === TokenType.COLON || token.type === TokenType.EOF) {
305
+ token.type === TokenType.EOF) {
302
306
  break
303
307
  }
304
308
 
305
- if (token.type === TokenType.DOT) {
309
+ if (token.type === TokenType.COLON) {
310
+ this.advance()
311
+ const next = this.current()
312
+ if (next && next.type === TokenType.COLON) {
313
+ this.advance()
314
+ const pseudoName = this.current()
315
+ if (pseudoName && pseudoName.type === TokenType.IDENTIFIER) {
316
+ parts.push('::' + pseudoName.value)
317
+ this.advance()
318
+ }
319
+ } else if (next && next.type === TokenType.IDENTIFIER) {
320
+ parts.push(':' + next.value)
321
+ this.advance()
322
+ } else {
323
+ break
324
+ }
325
+ } else if (token.type === TokenType.DOT) {
306
326
  this.advance()
307
327
  const name = this.current()
308
328
  if (name && name.type === TokenType.IDENTIFIER) {
@@ -334,12 +354,28 @@ class EtherParser {
334
354
  } else if (token.type === TokenType.TILDE) {
335
355
  parts.push('~')
336
356
  this.advance()
357
+ } else if (token.type === TokenType.LBRACKET) {
358
+ let attrSelector = '['
359
+ this.advance()
360
+ while (!this.isAtEnd()) {
361
+ const attrToken = this.current()
362
+ if (!attrToken || attrToken.type === TokenType.RBRACKET) {
363
+ attrSelector += ']'
364
+ this.advance()
365
+ break
366
+ }
367
+ attrSelector += attrToken.value
368
+ this.advance()
369
+ }
370
+ parts.push(attrSelector)
371
+ } else if (token.type === TokenType.EQUALS) {
372
+ break
337
373
  } else {
338
374
  break
339
375
  }
340
376
  }
341
377
 
342
- return parts.join(' ').replace(/\s+([,>+~])\s+/g, ' $1 ').trim()
378
+ return parts.join(' ').replace(/\s+([,>+~])\s+/g, ' $1 ').replace(/\s+(::?)/g, '$1').trim()
343
379
  }
344
380
 
345
381
  translateCSSSelector(selector) {
@@ -400,6 +436,7 @@ class EtherParser {
400
436
  }
401
437
 
402
438
  const valueParts = []
439
+ let lastType = null
403
440
 
404
441
  while (!this.isAtEnd()) {
405
442
  const current = this.current()
@@ -409,21 +446,31 @@ class EtherParser {
409
446
  break
410
447
  }
411
448
 
449
+ let needsSpace = valueParts.length > 0
450
+
451
+ if (current.type === TokenType.LPAREN || current.type === TokenType.COMMA ||
452
+ current.type === TokenType.RPAREN || current.type === TokenType.PERCENT ||
453
+ lastType === TokenType.LPAREN || lastType === TokenType.MINUS ||
454
+ lastType === TokenType.HASH || lastType === TokenType.COLON) {
455
+ needsSpace = false
456
+ }
457
+
412
458
  if (current.type === TokenType.IDENTIFIER) {
459
+ if (needsSpace) valueParts.push(' ')
413
460
  valueParts.push(current.value)
414
461
  } else if (current.type === TokenType.NUMBER || current.type === TokenType.INTEGER ||
415
462
  current.type === TokenType.FLOAT) {
463
+ if (needsSpace && lastType !== TokenType.HASH) valueParts.push(' ')
416
464
  valueParts.push(current.value)
417
465
  } else if (current.type === TokenType.STRING) {
466
+ if (needsSpace) valueParts.push(' ')
467
+ valueParts.push('"' + current.value + '"')
468
+ } else if (current.type === TokenType.HEX) {
469
+ if (needsSpace) valueParts.push(' ')
418
470
  valueParts.push(current.value)
419
- } else if (current.type === TokenType.HEX || current.type === TokenType.HASH) {
420
- const next = this.peek(1)
421
- if (next && (next.type === TokenType.IDENTIFIER || next.type === TokenType.NUMBER)) {
422
- this.advance()
423
- valueParts.push('#' + next.value)
424
- } else {
425
- valueParts.push('#')
426
- }
471
+ } else if (current.type === TokenType.HASH) {
472
+ if (needsSpace) valueParts.push(' ')
473
+ valueParts.push('#')
427
474
  } else if (current.type === TokenType.COMMA) {
428
475
  valueParts.push(',')
429
476
  } else if (current.type === TokenType.LPAREN) {
@@ -433,25 +480,33 @@ class EtherParser {
433
480
  } else if (current.type === TokenType.PERCENT) {
434
481
  valueParts.push('%')
435
482
  } else if (current.type === TokenType.SLASH) {
483
+ if (needsSpace) valueParts.push(' ')
436
484
  valueParts.push('/')
437
485
  } else if (current.type === TokenType.MINUS) {
438
- valueParts.push('-')
486
+ if (lastType === TokenType.LPAREN || lastType === TokenType.MINUS) {
487
+ valueParts.push('-')
488
+ } else if (needsSpace) {
489
+ valueParts.push(' -')
490
+ } else {
491
+ valueParts.push('-')
492
+ }
493
+ } else if (current.type === TokenType.COLON) {
494
+ valueParts.push(':')
495
+ } else if (current.type === TokenType.DOT) {
496
+ valueParts.push('.')
439
497
  } else {
440
498
  break
441
499
  }
442
500
 
501
+ lastType = current.type
443
502
  this.advance()
444
503
  }
445
504
 
446
505
  return {
447
506
  type: 'Declaration',
448
507
  property: property,
449
- value: valueParts.join(' ')
450
- .replace(/\(\s+/g, '(')
451
- .replace(/\s+\)/g, ')')
452
- .replace(/\s+,/g, ',')
453
- .replace(/-\s+/g, '-')
454
- .replace(/(\d+)\s+(%|px|em|rem|vh|vw|vmin|vmax|fr|s|ms|deg)/gi, '$1$2')
508
+ value: valueParts.join('')
509
+ .replace(/,\s*/g, ', ')
455
510
  .replace(/\s+/g, ' ')
456
511
  .trim()
457
512
  }
@@ -3098,4 +3153,4 @@ class EtherParser {
3098
3153
 
3099
3154
  module.exports = {
3100
3155
  EtherParser
3101
- }
3156
+ }
@@ -103,210 +103,86 @@ class CSSGenerator {
103
103
 
104
104
  translateProperty(prop) {
105
105
  const lower = prop.toLowerCase()
106
- return this.propertyMap[lower] || this.translateGeneric(prop)
106
+ if (this.propertyMap && this.propertyMap[lower]) {
107
+ return this.propertyMap[lower]
108
+ }
109
+ return this.translateGeneric(prop)
107
110
  }
108
111
 
109
112
  translateValue(val) {
110
113
  if (typeof val !== 'string') return val
111
- const lower = val.toLowerCase()
112
114
 
113
- if (this.colorMap[lower]) {
114
- return this.colorMap[lower]
115
- }
115
+ let result = val
116
+ result = this.translateFunctions(result)
117
+ result = this.translateColors(result)
118
+ result = this.translateKeywords(result)
119
+
120
+ return result
121
+ }
122
+
123
+ translateFunctions(val) {
124
+ let result = val
125
+
126
+ result = result.replace(/utiliser:\s*([\w\u00C0-\u024F\-]+)/gi, (match, varName) => {
127
+ return `var(--${varName})`
128
+ })
116
129
 
117
- if (this.valueMap[lower]) {
118
- const mapped = this.valueMap[lower]
119
- if (typeof mapped === 'object' && mapped.type === 'animation') {
120
- return mapped
130
+ result = result.replace(/dégradé linéaire:\s*/gi, 'linear-gradient(')
131
+ result = result.replace(/degrade lineaire:\s*/gi, 'linear-gradient(')
132
+ result = result.replace(/dégradé radial:\s*/gi, 'radial-gradient(')
133
+ result = result.replace(/degrade radial:\s*/gi, 'radial-gradient(')
134
+ result = result.replace(/dégradé conique:\s*/gi, 'conic-gradient(')
135
+ result = result.replace(/degrade conique:\s*/gi, 'conic-gradient(')
136
+
137
+ result = result.replace(/rgba:\s*(\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)/gi, 'rgba($1, $2, $3, $4)')
138
+ result = result.replace(/rgb:\s*(\d+),\s*(\d+),\s*(\d+)/gi, 'rgb($1, $2, $3)')
139
+ result = result.replace(/hsl:\s*(\d+),\s*([\d.]+%?),\s*([\d.]+%?)/gi, 'hsl($1, $2, $3)')
140
+ result = result.replace(/hsla:\s*(\d+),\s*([\d.]+%?),\s*([\d.]+%?),\s*([\d.]+)/gi, 'hsla($1, $2, $3, $4)')
141
+
142
+ result = result.replace(/flou:\s*([\d.]+\w*)/gi, 'blur($1)')
143
+ result = result.replace(/luminosité:\s*([\d.]+%?)/gi, 'brightness($1)')
144
+ result = result.replace(/luminosite:\s*([\d.]+%?)/gi, 'brightness($1)')
145
+ result = result.replace(/contraste:\s*([\d.]+%?)/gi, 'contrast($1)')
146
+ result = result.replace(/niveaux-gris:\s*([\d.]+%?)/gi, 'grayscale($1)')
147
+ result = result.replace(/niveaux gris:\s*([\d.]+%?)/gi, 'grayscale($1)')
148
+ result = result.replace(/inverser:\s*([\d.]+%?)/gi, 'invert($1)')
149
+ result = result.replace(/saturation:\s*([\d.]+%?)/gi, 'saturate($1)')
150
+ result = result.replace(/sépia:\s*([\d.]+%?)/gi, 'sepia($1)')
151
+ result = result.replace(/sepia:\s*([\d.]+%?)/gi, 'sepia($1)')
152
+ result = result.replace(/teinte:\s*([\d.]+\w*)/gi, 'hue-rotate($1)')
153
+
154
+ result = result.replace(/déplacer-y:\s*([\d.\-]+\w*)/gi, 'translateY($1)')
155
+ result = result.replace(/deplacer-y:\s*([\d.\-]+\w*)/gi, 'translateY($1)')
156
+ result = result.replace(/déplacer-x:\s*([\d.\-]+\w*)/gi, 'translateX($1)')
157
+ result = result.replace(/deplacer-x:\s*([\d.\-]+\w*)/gi, 'translateX($1)')
158
+ result = result.replace(/déplacer:\s*([\d.\-]+\w*),?\s*([\d.\-]+\w*)?/gi, (match, x, y) => {
159
+ if (y) return `translate(${x}, ${y})`
160
+ return `translate(${x})`
161
+ })
162
+ result = result.replace(/rotation:\s*([\d.\-]+\w*)/gi, 'rotate($1)')
163
+ result = result.replace(/échelle:\s*([\d.]+)/gi, 'scale($1)')
164
+ result = result.replace(/echelle:\s*([\d.]+)/gi, 'scale($1)')
165
+
166
+ result = result.replace(/répéter:\s*([^,]+),\s*minmax:\s*([^,]+),\s*([^\s\)]+)/gi, 'repeat($1, minmax($2, $3))')
167
+ result = result.replace(/repeter:\s*([^,]+),\s*minmax:\s*([^,]+),\s*([^\s\)]+)/gi, 'repeat($1, minmax($2, $3))')
168
+ result = result.replace(/minmax:\s*([^,]+),\s*([^\s\)]+)/gi, 'minmax($1, $2)')
169
+
170
+ result = result.replace(/calcul:\s*(.+)/gi, 'calc($1)')
171
+ result = result.replace(/minimum:\s*([^,]+),\s*([^\s\)]+)/gi, 'min($1, $2)')
172
+ result = result.replace(/maximum:\s*([^,]+),\s*([^\s\)]+)/gi, 'max($1, $2)')
173
+ result = result.replace(/borner:\s*([^,]+),\s*([^,]+),\s*([^\s\)]+)/gi, 'clamp($1, $2, $3)')
174
+
175
+ if (result.includes('linear-gradient(') || result.includes('radial-gradient(') || result.includes('conic-gradient(')) {
176
+ if (!result.endsWith(')')) {
177
+ result = result + ')'
121
178
  }
122
- return mapped
123
179
  }
124
180
 
125
- return this.translateGeneric(val)
181
+ return result
126
182
  }
127
183
 
128
- translateGeneric(text) {
129
- const translations = {
130
- 'couleur-fond': 'background-color',
131
- 'couleur-texte': 'color',
132
- 'couleur-bordure': 'border-color',
133
- 'couleur-primaire': '--color-primary',
134
- 'couleur-secondaire': '--color-secondary',
135
- 'couleur-accent': '--color-accent',
136
- 'couleur-surface': '--color-surface',
137
- 'couleur-texte-pale': '--color-text-muted',
138
- 'famille-police': 'font-family',
139
- 'taille-police': 'font-size',
140
- 'poids-police': 'font-weight',
141
- 'style-police': 'font-style',
142
- 'hauteur-ligne': 'line-height',
143
- 'espacement-lettres': 'letter-spacing',
144
- 'espacement-mots': 'word-spacing',
145
- 'texte-aligne': 'text-align',
146
- 'alignement-texte': 'text-align',
147
- 'decoration-texte': 'text-decoration',
148
- 'transformation-texte': 'text-transform',
149
- 'retrait-texte': 'text-indent',
150
- 'ombre-texte': 'text-shadow',
151
- 'debordement-texte': 'text-overflow',
152
- 'coupure-mot': 'word-break',
153
- 'retour-ligne': 'word-wrap',
154
- 'espace-blanc': 'white-space',
155
- 'marge-haut': 'margin-top',
156
- 'marge-bas': 'margin-bottom',
157
- 'marge-gauche': 'margin-left',
158
- 'marge-droite': 'margin-right',
159
- 'marge-bloc': 'margin-block',
160
- 'marge-en-ligne': 'margin-inline',
161
- 'remplissage-haut': 'padding-top',
162
- 'remplissage-bas': 'padding-bottom',
163
- 'remplissage-gauche': 'padding-left',
164
- 'remplissage-droite': 'padding-right',
165
- 'remplissage-bloc': 'padding-block',
166
- 'remplissage-en-ligne': 'padding-inline',
167
- 'bordure-haut': 'border-top',
168
- 'bordure-bas': 'border-bottom',
169
- 'bordure-gauche': 'border-left',
170
- 'bordure-droite': 'border-right',
171
- 'largeur-bordure': 'border-width',
172
- 'style-bordure': 'border-style',
173
- 'rayon-bordure': 'border-radius',
174
- 'rayon-haut-gauche': 'border-top-left-radius',
175
- 'rayon-haut-droite': 'border-top-right-radius',
176
- 'rayon-bas-gauche': 'border-bottom-left-radius',
177
- 'rayon-bas-droite': 'border-bottom-right-radius',
178
- 'ombre-boite': 'box-shadow',
179
- 'modele-boite': 'box-sizing',
180
- 'max-largeur': 'max-width',
181
- 'min-largeur': 'min-width',
182
- 'max-hauteur': 'max-height',
183
- 'min-hauteur': 'min-height',
184
- 'ajustement-objet': 'object-fit',
185
- 'position-objet': 'object-position',
186
- 'debordement-x': 'overflow-x',
187
- 'debordement-y': 'overflow-y',
188
- 'comportement-defilement': 'scroll-behavior',
189
- 'justifier-contenu': 'justify-content',
190
- 'aligner-elements': 'align-items',
191
- 'aligner-contenu': 'align-content',
192
- 'aligner-soi': 'align-self',
193
- 'justifier-soi': 'justify-self',
194
- 'justifier-elements': 'justify-items',
195
- 'direction-flex': 'flex-direction',
196
- 'enveloppe-flex': 'flex-wrap',
197
- 'flux-flex': 'flex-flow',
198
- 'croissance-flex': 'flex-grow',
199
- 'retrecissement-flex': 'flex-shrink',
200
- 'base-flex': 'flex-basis',
201
- 'ordre': 'order',
202
- 'colonnes-grille': 'grid-template-columns',
203
- 'lignes-grille': 'grid-template-rows',
204
- 'zones-grille': 'grid-template-areas',
205
- 'colonne-grille': 'grid-column',
206
- 'ligne-grille': 'grid-row',
207
- 'zone-grille': 'grid-area',
208
- 'ecart-grille': 'grid-gap',
209
- 'ecart-colonnes': 'column-gap',
210
- 'ecart-lignes': 'row-gap',
211
- 'flux-auto-grille': 'grid-auto-flow',
212
- 'colonnes-auto-grille': 'grid-auto-columns',
213
- 'lignes-auto-grille': 'grid-auto-rows',
214
- 'z-index': 'z-index',
215
- 'index-z': 'z-index',
216
- 'liste-style': 'list-style',
217
- 'type-liste': 'list-style-type',
218
- 'position-liste': 'list-style-position',
219
- 'image-liste': 'list-style-image',
220
- 'duree-transition': 'transition-duration',
221
- 'delai-transition': 'transition-delay',
222
- 'propriete-transition': 'transition-property',
223
- 'fonction-transition': 'transition-timing-function',
224
- 'nom-animation': 'animation-name',
225
- 'duree-animation': 'animation-duration',
226
- 'delai-animation': 'animation-delay',
227
- 'fonction-animation': 'animation-timing-function',
228
- 'iteration-animation': 'animation-iteration-count',
229
- 'direction-animation': 'animation-direction',
230
- 'remplissage-animation': 'animation-fill-mode',
231
- 'etat-animation': 'animation-play-state',
232
- 'origine-transformation': 'transform-origin',
233
- 'style-transformation': 'transform-style',
234
- 'perspective': 'perspective',
235
- 'origine-perspective': 'perspective-origin',
236
- 'face-arriere': 'backface-visibility',
237
- 'flou-fond': 'backdrop-filter',
238
- 'mode-melange': 'mix-blend-mode',
239
- 'mode-melange-fond': 'background-blend-mode',
240
- 'image-fond': 'background-image',
241
- 'position-fond': 'background-position',
242
- 'taille-fond': 'background-size',
243
- 'repetition-fond': 'background-repeat',
244
- 'attachement-fond': 'background-attachment',
245
- 'origine-fond': 'background-origin',
246
- 'decoupe-fond': 'background-clip',
247
- 'couleur-remplissage-texte': '-webkit-text-fill-color',
248
- 'selection-utilisateur': 'user-select',
249
- 'evenements-pointeur': 'pointer-events',
250
- 'accrochage-defilement': 'scroll-snap-type',
251
- 'alignement-accrochage': 'scroll-snap-align',
252
- 'redimensionnement': 'resize',
253
- 'apparence': 'appearance',
254
- 'contour': 'outline',
255
- 'largeur-contour': 'outline-width',
256
- 'style-contour': 'outline-style',
257
- 'couleur-contour': 'outline-color',
258
- 'decalage-contour': 'outline-offset',
259
- 'compteur-reset': 'counter-reset',
260
- 'compteur-increment': 'counter-increment',
261
- 'guillemets': 'quotes',
262
- 'coupure-colonne': 'break-inside',
263
- 'orphelins': 'orphans',
264
- 'veuves': 'widows',
265
- 'fond': 'background',
266
- 'couleur': 'color',
267
- 'police': 'font-family',
268
- 'taille': 'font-size',
269
- 'poids': 'font-weight',
270
- 'style': 'font-style',
271
- 'alignement': 'text-align',
272
- 'decoration': 'text-decoration',
273
- 'transformation': 'transform',
274
- 'debordement': 'overflow',
275
- 'largeur': 'width',
276
- 'hauteur': 'height',
277
- 'marge': 'margin',
278
- 'remplissage': 'padding',
279
- 'bordure': 'border',
280
- 'arrondi': 'border-radius',
281
- 'ombre': 'box-shadow',
282
- 'affichage': 'display',
283
- 'position': 'position',
284
- 'haut': 'top',
285
- 'bas': 'bottom',
286
- 'gauche': 'left',
287
- 'droite': 'right',
288
- 'z': 'z-index',
289
- 'opacite': 'opacity',
290
- 'curseur': 'cursor',
291
- 'transition': 'transition',
292
- 'animation': 'animation',
293
- 'filtre': 'filter',
294
- 'ajustement': 'object-fit',
295
- 'selection': 'user-select',
296
- 'redimensionner': 'resize',
297
- 'visibilite': 'visibility',
298
- 'flex': 'flex',
299
- 'grille': 'grid',
300
- 'colonnes': 'grid-template-columns',
301
- 'lignes': 'grid-template-rows',
302
- 'zone': 'grid-area',
303
- 'ecart': 'gap',
304
- 'espace': 'gap',
305
- 'direction': 'flex-direction',
306
- 'envelopper': 'flex-wrap',
307
- 'justifier': 'justify-content',
308
- 'aligner': 'align-items',
309
- 'contenu': 'content',
184
+ translateColors(val) {
185
+ const colorMap = {
310
186
  'blanc': 'white',
311
187
  'noir': 'black',
312
188
  'rouge': 'red',
@@ -317,55 +193,301 @@ class CSSGenerator {
317
193
  'violet': 'purple',
318
194
  'rose': 'pink',
319
195
  'gris': 'gray',
320
- 'transparent': 'transparent',
321
- 'heriter': 'inherit',
322
- 'initial': 'initial',
196
+ 'cyan': 'cyan',
197
+ 'magenta': 'magenta',
198
+ 'turquoise': 'turquoise',
199
+ 'beige': 'beige',
200
+ 'or': 'gold',
201
+ 'argent': 'silver',
202
+ 'corail': 'coral',
203
+ 'saumon': 'salmon',
204
+ 'olive': 'olive',
205
+ 'marine': 'navy',
206
+ 'bordeaux': 'maroon',
207
+ 'kaki': 'khaki',
208
+ 'ivoire': 'ivory',
209
+ 'indigo': 'indigo',
210
+ 'lavande': 'lavender',
211
+ 'prune': 'plum',
212
+ 'chocolat': 'chocolate',
213
+ 'transparent': 'transparent'
214
+ }
215
+
216
+ let result = val
217
+ for (const [fr, en] of Object.entries(colorMap)) {
218
+ const regex = new RegExp(`\\b${fr}\\b`, 'gi')
219
+ result = result.replace(regex, en)
220
+ }
221
+ return result
222
+ }
223
+
224
+ translateKeywords(val) {
225
+ const keywords = {
323
226
  'aucun': 'none',
227
+ 'aucune': 'none',
324
228
  'auto': 'auto',
229
+ 'hériter': 'inherit',
230
+ 'heriter': 'inherit',
231
+ 'initial': 'initial',
232
+ 'caché': 'hidden',
325
233
  'cache': 'hidden',
326
234
  'visible': 'visible',
235
+ 'défiler': 'scroll',
327
236
  'defiler': 'scroll',
328
237
  'fixe': 'fixed',
238
+ 'fixée': 'fixed',
239
+ 'fixee': 'fixed',
240
+ 'absolue': 'absolute',
329
241
  'absolu': 'absolute',
242
+ 'relative': 'relative',
330
243
  'relatif': 'relative',
331
244
  'statique': 'static',
332
245
  'collant': 'sticky',
246
+ 'collante': 'sticky',
333
247
  'centre': 'center',
248
+ 'centré': 'center',
249
+ 'début': 'flex-start',
334
250
  'debut': 'flex-start',
335
251
  'fin': 'flex-end',
336
- 'entre': 'space-between',
337
- 'autour': 'space-around',
338
- 'egal': 'space-evenly',
252
+ 'espace-entre': 'space-between',
253
+ 'espace entre': 'space-between',
254
+ 'espace-autour': 'space-around',
255
+ 'espace autour': 'space-around',
256
+ 'espace-égal': 'space-evenly',
257
+ 'espace egal': 'space-evenly',
258
+ 'étirer': 'stretch',
339
259
  'etirer': 'stretch',
340
260
  'ligne': 'row',
341
261
  'colonne': 'column',
342
- 'inverser': 'reverse',
262
+ 'ligne-inverse': 'row-reverse',
263
+ 'ligne inverse': 'row-reverse',
264
+ 'colonne-inverse': 'column-reverse',
265
+ 'colonne inverse': 'column-reverse',
266
+ 'envelopper': 'wrap',
267
+ 'sans-retour': 'nowrap',
268
+ 'sans retour': 'nowrap',
343
269
  'gras': 'bold',
344
270
  'normal': 'normal',
345
271
  'italique': 'italic',
272
+ 'souligné': 'underline',
346
273
  'souligne': 'underline',
274
+ 'barré': 'line-through',
347
275
  'barre': 'line-through',
348
276
  'majuscules': 'uppercase',
349
277
  'minuscules': 'lowercase',
350
278
  'capitaliser': 'capitalize',
351
279
  'pointeur': 'pointer',
352
280
  'attente': 'wait',
353
- 'texte': 'text',
354
281
  'interdit': 'not-allowed',
355
282
  'couvrir': 'cover',
356
283
  'contenir': 'contain',
357
284
  'remplir': 'fill',
358
285
  'bloc': 'block',
359
286
  'en-ligne': 'inline',
287
+ 'en ligne': 'inline',
360
288
  'bloc-en-ligne': 'inline-block',
289
+ 'bloc en ligne': 'inline-block',
361
290
  'flex-en-ligne': 'inline-flex',
291
+ 'flex en ligne': 'inline-flex',
292
+ 'grille': 'grid',
362
293
  'grille-en-ligne': 'inline-grid',
363
- 'espace-blanc': 'white-space',
364
- 'blanc-espace': 'white-space',
365
- 'pre': 'pre',
294
+ 'grille en ligne': 'inline-grid',
295
+ 'préservé': 'pre',
296
+ 'preserve': 'pre',
297
+ 'pré-ligne': 'pre-line',
366
298
  'pre-ligne': 'pre-line',
299
+ 'pré-enveloppe': 'pre-wrap',
367
300
  'pre-enveloppe': 'pre-wrap',
368
- 'sans-retour': 'nowrap'
301
+ 'solide': 'solid',
302
+ 'pointillée': 'dotted',
303
+ 'pointillee': 'dotted',
304
+ 'tirets': 'dashed',
305
+ 'double': 'double',
306
+ 'tout': 'all',
307
+ 'ease': 'ease',
308
+ 'ease-in': 'ease-in',
309
+ 'ease-out': 'ease-out',
310
+ 'ease-in-out': 'ease-in-out',
311
+ 'linéaire': 'linear',
312
+ 'lineaire': 'linear'
313
+ }
314
+
315
+ let result = val
316
+
317
+ const varPlaceholders = []
318
+ result = result.replace(/var\(--[\w\u00C0-\u024F\-]+\)/gi, (match) => {
319
+ varPlaceholders.push(match)
320
+ return `__VAR_PLACEHOLDER_${varPlaceholders.length - 1}__`
321
+ })
322
+
323
+ for (const [fr, en] of Object.entries(keywords)) {
324
+ const regex = new RegExp(`\\b${fr}\\b`, 'gi')
325
+ result = result.replace(regex, en)
326
+ }
327
+
328
+ if (result === val) {
329
+ const lower = val.toLowerCase()
330
+ if (lower === 'texte') return 'text'
331
+ }
332
+
333
+ for (let i = 0; i < varPlaceholders.length; i++) {
334
+ result = result.replace(`__VAR_PLACEHOLDER_${i}__`, varPlaceholders[i])
335
+ }
336
+
337
+ return result
338
+ }
339
+
340
+ translateGeneric(text) {
341
+ const translations = {
342
+ 'couleur': 'color',
343
+ 'fond': 'background',
344
+ 'police': 'font-family',
345
+ 'taille police': 'font-size',
346
+ 'taille-police': 'font-size',
347
+ 'poids police': 'font-weight',
348
+ 'poids-police': 'font-weight',
349
+ 'style police': 'font-style',
350
+ 'style-police': 'font-style',
351
+ 'hauteur ligne': 'line-height',
352
+ 'hauteur-ligne': 'line-height',
353
+ 'espacement-lettres': 'letter-spacing',
354
+ 'espacement lettres': 'letter-spacing',
355
+ 'alignement-texte': 'text-align',
356
+ 'alignement texte': 'text-align',
357
+ 'décoration-texte': 'text-decoration',
358
+ 'décoration texte': 'text-decoration',
359
+ 'decoration-texte': 'text-decoration',
360
+ 'decoration texte': 'text-decoration',
361
+ 'transformation-texte': 'text-transform',
362
+ 'transformation texte': 'text-transform',
363
+ 'ombre-texte': 'text-shadow',
364
+ 'ombre texte': 'text-shadow',
365
+ 'marge': 'margin',
366
+ 'marge haut': 'margin-top',
367
+ 'marge-haut': 'margin-top',
368
+ 'marge bas': 'margin-bottom',
369
+ 'marge-bas': 'margin-bottom',
370
+ 'marge gauche': 'margin-left',
371
+ 'marge-gauche': 'margin-left',
372
+ 'marge droite': 'margin-right',
373
+ 'marge-droite': 'margin-right',
374
+ 'marge dedans': 'padding',
375
+ 'marge-dedans': 'padding',
376
+ 'marge dedans haut': 'padding-top',
377
+ 'marge-dedans-haut': 'padding-top',
378
+ 'marge dedans bas': 'padding-bottom',
379
+ 'marge-dedans-bas': 'padding-bottom',
380
+ 'marge dedans gauche': 'padding-left',
381
+ 'marge-dedans-gauche': 'padding-left',
382
+ 'marge dedans droite': 'padding-right',
383
+ 'marge-dedans-droite': 'padding-right',
384
+ 'bordure': 'border',
385
+ 'bordure haut': 'border-top',
386
+ 'bordure-haut': 'border-top',
387
+ 'bordure bas': 'border-bottom',
388
+ 'bordure-bas': 'border-bottom',
389
+ 'bordure gauche': 'border-left',
390
+ 'bordure-gauche': 'border-left',
391
+ 'bordure droite': 'border-right',
392
+ 'bordure-droite': 'border-right',
393
+ 'couleur-bordure': 'border-color',
394
+ 'couleur bordure': 'border-color',
395
+ 'arrondi': 'border-radius',
396
+ 'largeur': 'width',
397
+ 'hauteur': 'height',
398
+ 'largeur minimum': 'min-width',
399
+ 'largeur-minimum': 'min-width',
400
+ 'largeur maximum': 'max-width',
401
+ 'largeur-maximum': 'max-width',
402
+ 'hauteur minimum': 'min-height',
403
+ 'hauteur-minimum': 'min-height',
404
+ 'hauteur maximum': 'max-height',
405
+ 'hauteur-maximum': 'max-height',
406
+ 'affichage': 'display',
407
+ 'position': 'position',
408
+ 'haut': 'top',
409
+ 'bas': 'bottom',
410
+ 'gauche': 'left',
411
+ 'droite': 'right',
412
+ 'index-z': 'z-index',
413
+ 'z-index': 'z-index',
414
+ 'débordement': 'overflow',
415
+ 'debordement': 'overflow',
416
+ 'débordement-x': 'overflow-x',
417
+ 'debordement-x': 'overflow-x',
418
+ 'débordement-y': 'overflow-y',
419
+ 'debordement-y': 'overflow-y',
420
+ 'opacité': 'opacity',
421
+ 'opacite': 'opacity',
422
+ 'curseur': 'cursor',
423
+ 'transition': 'transition',
424
+ 'animation': 'animation',
425
+ 'transformation': 'transform',
426
+ 'filtre': 'filter',
427
+ 'filtre-fond': 'backdrop-filter',
428
+ 'filtre fond': 'backdrop-filter',
429
+ 'ombre-boîte': 'box-shadow',
430
+ 'ombre-boite': 'box-shadow',
431
+ 'ombre boîte': 'box-shadow',
432
+ 'ombre boite': 'box-shadow',
433
+ 'modèle-boîte': 'box-sizing',
434
+ 'modele-boite': 'box-sizing',
435
+ 'modèle boîte': 'box-sizing',
436
+ 'modele boite': 'box-sizing',
437
+ 'espace': 'gap',
438
+ 'espace ligne': 'row-gap',
439
+ 'espace-ligne': 'row-gap',
440
+ 'espace colonne': 'column-gap',
441
+ 'espace-colonne': 'column-gap',
442
+ 'justifier-contenu': 'justify-content',
443
+ 'justifier contenu': 'justify-content',
444
+ 'aligner-éléments': 'align-items',
445
+ 'aligner-elements': 'align-items',
446
+ 'aligner éléments': 'align-items',
447
+ 'aligner elements': 'align-items',
448
+ 'aligner-contenu': 'align-content',
449
+ 'aligner contenu': 'align-content',
450
+ 'direction-flex': 'flex-direction',
451
+ 'direction flex': 'flex-direction',
452
+ 'enveloppe-flex': 'flex-wrap',
453
+ 'enveloppe flex': 'flex-wrap',
454
+ 'flex': 'flex',
455
+ 'grille': 'display',
456
+ 'colonnes-grille': 'grid-template-columns',
457
+ 'colonnes grille': 'grid-template-columns',
458
+ 'lignes-grille': 'grid-template-rows',
459
+ 'lignes grille': 'grid-template-rows',
460
+ 'style-liste': 'list-style',
461
+ 'style liste': 'list-style',
462
+ 'contenu': 'content',
463
+ 'évènements-pointeur': 'pointer-events',
464
+ 'evenements-pointeur': 'pointer-events',
465
+ 'évènements pointeur': 'pointer-events',
466
+ 'evenements pointeur': 'pointer-events',
467
+ 'découpe-fond': 'background-clip',
468
+ 'decoupe-fond': 'background-clip',
469
+ 'découpe fond': 'background-clip',
470
+ 'decoupe fond': 'background-clip',
471
+ 'couleur-remplissage-texte': '-webkit-text-fill-color',
472
+ 'couleur remplissage texte': '-webkit-text-fill-color',
473
+ 'espace-blanc': 'white-space',
474
+ 'espace blanc': 'white-space',
475
+ 'taille': 'font-size',
476
+ 'poids': 'font-weight',
477
+ 'style': 'font-style',
478
+ 'alignement': 'text-align',
479
+ 'decoration': 'text-decoration',
480
+ 'ombre': 'box-shadow',
481
+ 'ecart': 'gap',
482
+ 'direction': 'flex-direction',
483
+ 'envelopper': 'flex-wrap',
484
+ 'justifier': 'justify-content',
485
+ 'aligner': 'align-items',
486
+ 'visibilite': 'visibility',
487
+ 'visibilité': 'visibility',
488
+ 'redimensionnement': 'resize',
489
+ 'apparence': 'appearance',
490
+ 'contour': 'outline'
369
491
  }
370
492
 
371
493
  const lower = text.toLowerCase()
@@ -375,30 +497,91 @@ class CSSGenerator {
375
497
  translateSelector(selector) {
376
498
  let result = selector
377
499
 
378
- result = result.replace(/au survol/gi, ':hover')
379
- result = result.replace(/au clic/gi, ':active')
380
- result = result.replace(/au focus/gi, ':focus')
381
- result = result.replace(/au focus dans/gi, ':focus-within')
382
- result = result.replace(/visite/gi, ':visited')
383
- result = result.replace(/premier enfant/gi, ':first-child')
384
- result = result.replace(/dernier enfant/gi, ':last-child')
385
- result = result.replace(/enfant impair/gi, ':nth-child(odd)')
386
- result = result.replace(/enfant pair/gi, ':nth-child(even)')
387
- result = result.replace(/avant/gi, '::before')
388
- result = result.replace(/apres/gi, '::after')
389
- result = result.replace(/selection/gi, '::selection')
390
- result = result.replace(/placeholder/gi, '::placeholder')
391
- result = result.replace(/premiere lettre/gi, '::first-letter')
392
- result = result.replace(/premiere ligne/gi, '::first-line')
393
- result = result.replace(/coche/gi, ':checked')
394
- result = result.replace(/desactive/gi, ':disabled')
395
- result = result.replace(/active/gi, ':enabled')
396
- result = result.replace(/requis/gi, ':required')
397
- result = result.replace(/optionnel/gi, ':optional')
398
- result = result.replace(/valide/gi, ':valid')
399
- result = result.replace(/invalide/gi, ':invalid')
400
- result = result.replace(/vide/gi, ':empty')
401
- result = result.replace(/racine/gi, ':root')
500
+ result = result.replace(/:racine\b/gi, ':root')
501
+ result = result.replace(/::avant\b/gi, '::before')
502
+ result = result.replace(/::après\b/gi, '::after')
503
+ result = result.replace(/::apres\b/gi, '::after')
504
+ result = result.replace(/::première-lettre\b/gi, '::first-letter')
505
+ result = result.replace(/::premiere-lettre\b/gi, '::first-letter')
506
+ result = result.replace(/::première-ligne\b/gi, '::first-line')
507
+ result = result.replace(/::premiere-ligne\b/gi, '::first-line')
508
+ result = result.replace(/::sélection\b/gi, '::selection')
509
+ result = result.replace(/::selection\b/gi, '::selection')
510
+ result = result.replace(/::marqueur\b/gi, '::marker')
511
+
512
+ result = result.replace(/\bau survol\b/gi, ':hover')
513
+ result = result.replace(/\bquand survolé\b/gi, ':hover')
514
+ result = result.replace(/\bquand survole\b/gi, ':hover')
515
+ result = result.replace(/\ben survol\b/gi, ':hover')
516
+ result = result.replace(/\bau clic\b/gi, ':active')
517
+ result = result.replace(/\bquand cliqué\b/gi, ':active')
518
+ result = result.replace(/\bquand clique\b/gi, ':active')
519
+ result = result.replace(/\ben clic\b/gi, ':active')
520
+ result = result.replace(/\bactif\b/gi, ':active')
521
+ result = result.replace(/\bau focus\b/gi, ':focus')
522
+ result = result.replace(/\bquand focalisé\b/gi, ':focus')
523
+ result = result.replace(/\bquand focalise\b/gi, ':focus')
524
+ result = result.replace(/\ben focus\b/gi, ':focus')
525
+ result = result.replace(/\bau focus dans\b/gi, ':focus-within')
526
+ result = result.replace(/\bvisité\b/gi, ':visited')
527
+ result = result.replace(/\bvisite\b/gi, ':visited')
528
+
529
+ result = result.replace(/\s+(:hover|:active|:focus|:focus-within|:visited|:checked|:disabled|:enabled|:required|:optional|:valid|:invalid|:empty|:target)/gi, '$1')
530
+ result = result.replace(/\bpremier enfant\b/gi, ':first-child')
531
+ result = result.replace(/\bdernier enfant\b/gi, ':last-child')
532
+ result = result.replace(/\benfant impair\b/gi, ':nth-child(odd)')
533
+ result = result.replace(/\benfant pair\b/gi, ':nth-child(even)')
534
+ result = result.replace(/\bpremier de type\b/gi, ':first-of-type')
535
+ result = result.replace(/\bdernier de type\b/gi, ':last-of-type')
536
+ result = result.replace(/\benfant unique\b/gi, ':only-child')
537
+ result = result.replace(/\bunique de type\b/gi, ':only-of-type')
538
+ result = result.replace(/\bvide\b/gi, ':empty')
539
+ result = result.replace(/\bcible\b/gi, ':target')
540
+ result = result.replace(/\bcoché\b/gi, ':checked')
541
+ result = result.replace(/\bcoche\b/gi, ':checked')
542
+ result = result.replace(/\bdésactivé\b/gi, ':disabled')
543
+ result = result.replace(/\bdesactive\b/gi, ':disabled')
544
+ result = result.replace(/\bactivé\b/gi, ':enabled')
545
+ result = result.replace(/\brequis\b/gi, ':required')
546
+ result = result.replace(/\boptionnel\b/gi, ':optional')
547
+ result = result.replace(/\bvalide\b/gi, ':valid')
548
+ result = result.replace(/\binvalide\b/gi, ':invalid')
549
+
550
+ result = result.replace(/\bcorps\b/gi, 'body')
551
+ result = result.replace(/\btête\b/gi, 'head')
552
+ result = result.replace(/\btete\b/gi, 'head')
553
+ result = result.replace(/\btitre1\b/gi, 'h1')
554
+ result = result.replace(/\btitre2\b/gi, 'h2')
555
+ result = result.replace(/\btitre3\b/gi, 'h3')
556
+ result = result.replace(/\btitre4\b/gi, 'h4')
557
+ result = result.replace(/\btitre5\b/gi, 'h5')
558
+ result = result.replace(/\btitre6\b/gi, 'h6')
559
+ result = result.replace(/\bparagraphe\b/gi, 'p')
560
+ result = result.replace(/\blien\b/gi, 'a')
561
+ result = result.replace(/\bimage\b/gi, 'img')
562
+ result = result.replace(/\bbouton\b/gi, 'button')
563
+ result = result.replace(/\bformulaire\b/gi, 'form')
564
+ result = result.replace(/\bchamp\b/gi, 'input')
565
+ result = result.replace(/\bliste\b/gi, 'ul')
566
+ result = result.replace(/\bliste-ordonnée\b/gi, 'ol')
567
+ result = result.replace(/\bliste-ordonnee\b/gi, 'ol')
568
+ result = result.replace(/\bélément-liste\b/gi, 'li')
569
+ result = result.replace(/\belement-liste\b/gi, 'li')
570
+ result = result.replace(/\btableau\b/gi, 'table')
571
+ result = result.replace(/\bligne-tableau\b/gi, 'tr')
572
+ result = result.replace(/\bcellule\b/gi, 'td')
573
+ result = result.replace(/\bentête\b/gi, 'header')
574
+ result = result.replace(/\bentete\b/gi, 'header')
575
+ result = result.replace(/\bpied\b/gi, 'footer')
576
+ result = result.replace(/\bnavigation\b/gi, 'nav')
577
+ result = result.replace(/\bsection\b/gi, 'section')
578
+ result = result.replace(/\barticle\b/gi, 'article')
579
+ result = result.replace(/\bcôté\b/gi, 'aside')
580
+ result = result.replace(/\bcote\b/gi, 'aside')
581
+ result = result.replace(/\bprincipal\b/gi, 'main')
582
+ result = result.replace(/\bdivision\b/gi, 'div')
583
+ result = result.replace(/\bportée\b/gi, 'span')
584
+ result = result.replace(/\bportee\b/gi, 'span')
402
585
 
403
586
  return result
404
587
  }
@@ -428,12 +611,13 @@ class CSSGenerator {
428
611
  'fr': 'fr'
429
612
  }
430
613
 
614
+ let result = value
431
615
  for (const [fr, css] of Object.entries(unitMap)) {
432
616
  const regex = new RegExp(`(\\d+(?:\\.\\d+)?)\\s*${fr}\\b`, 'gi')
433
- value = value.replace(regex, `$1${css}`)
617
+ result = result.replace(regex, `$1${css}`)
434
618
  }
435
619
 
436
- return value
620
+ return result
437
621
  }
438
622
 
439
623
  generate(ast) {
@@ -483,6 +667,10 @@ class CSSGenerator {
483
667
  case 'mediaQuery':
484
668
  this.generateMediaQuery(node)
485
669
  break
670
+ case 'Condition':
671
+ case 'condition':
672
+ this.generateCondition(node)
673
+ break
486
674
  case 'FontFace':
487
675
  case 'fontFace':
488
676
  this.generateFontFace(node)
@@ -538,7 +726,7 @@ class CSSGenerator {
538
726
  let value = decl.value
539
727
 
540
728
  if (isRoot) {
541
- property = '--' + property.toLowerCase()
729
+ property = '--' + property.toLowerCase().replace(/\s+/g, '-')
542
730
  } else {
543
731
  property = this.translateProperty(property)
544
732
  }
@@ -633,27 +821,79 @@ class CSSGenerator {
633
821
  generateMediaQuery(node) {
634
822
  let query = node.query || node.condition || ''
635
823
 
636
- query = query.replace(/ecran/gi, 'screen')
637
- query = query.replace(/imprimante/gi, 'print')
638
- query = query.replace(/tous/gi, 'all')
639
- query = query.replace(/largeur-min/gi, 'min-width')
640
- query = query.replace(/largeur min/gi, 'min-width')
641
- query = query.replace(/largeur-max/gi, 'max-width')
642
- query = query.replace(/largeur max/gi, 'max-width')
643
- query = query.replace(/max-largeur/gi, 'max-width')
644
- query = query.replace(/min-largeur/gi, 'min-width')
645
- query = query.replace(/hauteur-min/gi, 'min-height')
646
- query = query.replace(/hauteur min/gi, 'min-height')
647
- query = query.replace(/hauteur-max/gi, 'max-height')
648
- query = query.replace(/hauteur max/gi, 'max-height')
649
- query = query.replace(/max-hauteur/gi, 'max-height')
650
- query = query.replace(/min-hauteur/gi, 'min-height')
651
- query = query.replace(/orientation/gi, 'orientation')
652
- query = query.replace(/paysage/gi, 'landscape')
653
- query = query.replace(/portrait/gi, 'portrait')
654
- query = query.replace(/et/gi, 'and')
655
- query = query.replace(/ou/gi, 'or')
656
- query = query.replace(/pas/gi, 'not')
824
+ query = this.translateMediaQuery(query)
825
+
826
+ this.writeLine(`@media ${query} {`)
827
+ this.indent++
828
+
829
+ const rules = node.rules || node.children || []
830
+ for (const rule of rules) {
831
+ this.generateNode(rule)
832
+ }
833
+
834
+ this.indent--
835
+ this.writeLine('}')
836
+ this.writeLine('')
837
+ }
838
+
839
+ translateMediaQuery(query) {
840
+ let result = query
841
+
842
+ result = result.replace(/écran/gi, 'screen')
843
+ result = result.replace(/ecran/gi, 'screen')
844
+ result = result.replace(/imprimante/gi, 'print')
845
+ result = result.replace(/tous/gi, 'all')
846
+ result = result.replace(/largeur-min/gi, 'min-width')
847
+ result = result.replace(/largeur min/gi, 'min-width')
848
+ result = result.replace(/largeur-max/gi, 'max-width')
849
+ result = result.replace(/largeur max/gi, 'max-width')
850
+ result = result.replace(/max-largeur/gi, 'max-width')
851
+ result = result.replace(/min-largeur/gi, 'min-width')
852
+ result = result.replace(/hauteur-min/gi, 'min-height')
853
+ result = result.replace(/hauteur min/gi, 'min-height')
854
+ result = result.replace(/hauteur-max/gi, 'max-height')
855
+ result = result.replace(/hauteur max/gi, 'max-height')
856
+ result = result.replace(/max-hauteur/gi, 'max-height')
857
+ result = result.replace(/min-hauteur/gi, 'min-height')
858
+ result = result.replace(/orientation/gi, 'orientation')
859
+ result = result.replace(/paysage/gi, 'landscape')
860
+ result = result.replace(/portrait/gi, 'portrait')
861
+ result = result.replace(/\bet\b/gi, 'and')
862
+ result = result.replace(/\bou\b/gi, 'or')
863
+ result = result.replace(/\bpas\b/gi, 'not')
864
+
865
+ return result
866
+ }
867
+
868
+ generateCondition(node) {
869
+ let condition = node.condition || ''
870
+ let query = ''
871
+
872
+ const plusPetitMatch = condition.match(/plus petit que\s+([\d.]+\w*)/i)
873
+ const plusGrandMatch = condition.match(/plus grand que\s+([\d.]+\w*)/i)
874
+ const inferieurMatch = condition.match(/inférieur[e]?\s+à\s+([\d.]+\w*)/i) || condition.match(/inferieur[e]?\s+a\s+([\d.]+\w*)/i)
875
+ const superieurMatch = condition.match(/supérieur[e]?\s+à\s+([\d.]+\w*)/i) || condition.match(/superieur[e]?\s+a\s+([\d.]+\w*)/i)
876
+ const egalMatch = condition.match(/égal[e]?\s+à\s+([\d.]+\w*)/i) || condition.match(/egal[e]?\s+a\s+([\d.]+\w*)/i)
877
+ const simpleInfMatch = condition.match(/<\s*([\d.]+\w*)/i)
878
+ const simpleSupMatch = condition.match(/>\s*([\d.]+\w*)/i)
879
+
880
+ if (plusPetitMatch) {
881
+ query = `(max-width: ${plusPetitMatch[1]})`
882
+ } else if (plusGrandMatch) {
883
+ query = `(min-width: ${plusGrandMatch[1]})`
884
+ } else if (inferieurMatch) {
885
+ query = `(max-width: ${inferieurMatch[1]})`
886
+ } else if (superieurMatch) {
887
+ query = `(min-width: ${superieurMatch[1]})`
888
+ } else if (egalMatch) {
889
+ query = `(width: ${egalMatch[1]})`
890
+ } else if (simpleInfMatch) {
891
+ query = `(max-width: ${simpleInfMatch[1]})`
892
+ } else if (simpleSupMatch) {
893
+ query = `(min-width: ${simpleSupMatch[1]})`
894
+ } else {
895
+ query = this.translateMediaQuery(condition)
896
+ }
657
897
 
658
898
  this.writeLine(`@media ${query} {`)
659
899
  this.indent++
@@ -221,6 +221,11 @@ class EtherLexer {
221
221
  }
222
222
 
223
223
  if (char === '#') {
224
+ const next = this.peek(1)
225
+ if (next && (this.isHexDigit(next) || this.isAlpha(next))) {
226
+ this.scanHashToken()
227
+ return
228
+ }
224
229
  this.scanComment()
225
230
  return
226
231
  }
@@ -319,6 +324,42 @@ class EtherLexer {
319
324
  }
320
325
  }
321
326
 
327
+ scanHashToken() {
328
+ const startLine = this.line
329
+ const startCol = this.column
330
+
331
+ this.advance()
332
+
333
+ let value = ''
334
+ let isHexColor = true
335
+ let charCount = 0
336
+
337
+ while (!this.isAtEnd()) {
338
+ const char = this.peek()
339
+ if (this.isHexDigit(char)) {
340
+ value += this.advance()
341
+ charCount++
342
+ } else if (this.isAlpha(char) && !this.isHexDigit(char)) {
343
+ isHexColor = false
344
+ value += this.advance()
345
+ } else if (char === '-' || char === '_') {
346
+ isHexColor = false
347
+ value += this.advance()
348
+ } else {
349
+ break
350
+ }
351
+ }
352
+
353
+ if (isHexColor && (charCount === 3 || charCount === 4 || charCount === 6 || charCount === 8)) {
354
+ this.tokens.push(new Token(TokenType.HEX, '#' + value, startLine, startCol, this.currentIndent))
355
+ } else {
356
+ this.tokens.push(new Token(TokenType.HASH, '#', startLine, startCol, this.currentIndent))
357
+ if (value) {
358
+ this.tokens.push(new Token(TokenType.IDENTIFIER, value, startLine, startCol + 1, this.currentIndent))
359
+ }
360
+ }
361
+ }
362
+
322
363
  scanBlockComment() {
323
364
  const startLine = this.line
324
365
  const startCol = this.column
@@ -521,10 +562,42 @@ class EtherLexer {
521
562
  }
522
563
 
523
564
  if (type === TokenType.FLOAT) {
524
- this.tokens.push(new Token(type, parseFloat(value), startLine, startCol, this.currentIndent))
565
+ let numValue = String(parseFloat(value))
566
+ const unit = this.scanCSSUnit()
567
+ if (unit) numValue += unit
568
+ this.tokens.push(new Token(type, numValue, startLine, startCol, this.currentIndent))
525
569
  } else {
526
- this.tokens.push(new Token(type, parseInt(value, 10), startLine, startCol, this.currentIndent))
570
+ let numValue = String(parseInt(value, 10))
571
+ const unit = this.scanCSSUnit()
572
+ if (unit) numValue += unit
573
+ this.tokens.push(new Token(type, numValue, startLine, startCol, this.currentIndent))
574
+ }
575
+ }
576
+
577
+ scanCSSUnit() {
578
+ const units = ['px', 'em', 'rem', 'vh', 'vw', 'vmin', 'vmax', '%', 'fr', 's', 'ms', 'deg', 'rad', 'turn', 'ch', 'ex', 'cm', 'mm', 'in', 'pt', 'pc', 'cqw', 'cqh']
579
+
580
+ for (const unit of units) {
581
+ let matches = true
582
+ for (let i = 0; i < unit.length; i++) {
583
+ const char = this.peek(i)
584
+ if (!char || char.toLowerCase() !== unit[i].toLowerCase()) {
585
+ matches = false
586
+ break
587
+ }
588
+ }
589
+ if (matches) {
590
+ const afterUnit = this.peek(unit.length)
591
+ if (!afterUnit || !this.isAlpha(afterUnit)) {
592
+ let result = ''
593
+ for (let i = 0; i < unit.length; i++) {
594
+ result += this.advance()
595
+ }
596
+ return result
597
+ }
598
+ }
527
599
  }
600
+ return ''
528
601
  }
529
602
 
530
603
  scanIdentifier() {
@@ -693,7 +766,15 @@ class EtherLexer {
693
766
 
694
767
  case '-':
695
768
  if (this.match('-')) {
696
- this.tokens.push(new Token(TokenType.MINUS, '--', startLine, startCol, this.currentIndent))
769
+ if (this.isAlpha(this.peek()) || this.peek() === '-') {
770
+ let varName = '--'
771
+ while (this.isAlphaNumeric(this.peek()) || this.peek() === '-' || this.peek() === '_') {
772
+ varName += this.advance()
773
+ }
774
+ this.tokens.push(new Token(TokenType.IDENTIFIER, varName, startLine, startCol, this.currentIndent))
775
+ } else {
776
+ this.tokens.push(new Token(TokenType.MINUS, '--', startLine, startCol, this.currentIndent))
777
+ }
697
778
  } else if (this.match('>')) {
698
779
  this.tokens.push(new Token(TokenType.ARROW, '->', startLine, startCol, this.currentIndent))
699
780
  } else if (this.match('=')) {
@@ -866,4 +947,4 @@ module.exports = {
866
947
  Token,
867
948
  TokenType,
868
949
  LANG_BLOCKS
869
- }
950
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ether-code",
3
- "version": "0.3.9",
3
+ "version": "0.4.1",
4
4
  "description": "Ether - Le langage intentionnel",
5
5
  "main": "cli/compiler.js",
6
6
  "bin": {