Skip to content

Commit

Permalink
Merge pull request #561 from AmadeusITGroup/feat/styling-metadata
Browse files Browse the repository at this point in the history
feat: add metadata for styling
  • Loading branch information
matthieu-crouzet authored Jul 21, 2023
2 parents 19bb565 + dbaca12 commit 4b51396
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 35 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getLibraryCmsMetadata } from '@o3r/extractors';
import type { CssMetadata, CssVariable } from '@o3r/styling';
import type { CssMetadata, CssVariable, CssVariableType } from '@o3r/styling';
import * as fs from 'node:fs';
import { pathToFileURL } from 'node:url';
import * as path from 'node:path';
Expand All @@ -8,6 +8,7 @@ import {
SassBoolean,
SassColor,
SassList,
SassMap,
SassNumber,
SassString,
Value
Expand Down Expand Up @@ -90,6 +91,22 @@ export class CssVariableExtractor {
return url.split('/').slice(0, 2).join('/');
}

private static extractTags(tags: Value) {
let contextTags: string[] | undefined;
if (tags instanceof SassString) {
contextTags = [tags.text];
} else if (tags instanceof SassList) {
contextTags = [];
for (let i = 0; i < tags.asList.size; i++) {
const item = tags.get(i);
if (item instanceof SassString) {
contextTags.push(item.text);
}
}
}
return contextTags;
}

/**
* Extract metadata from Sass file
*
Expand Down Expand Up @@ -124,36 +141,66 @@ export class CssVariableExtractor {
// eslint-disable-next-line @typescript-eslint/naming-convention
'metadata-report($name, $value, $tags: null)': (args: Value[]) => {
let contextTags: string[] | undefined;
const name = args[0];
const value = args[1];
const varName = args[0];
const varValue = args[1];
const tags = args[2];
let description: string | undefined;
let label: string | undefined;
let category: string | undefined;
let type: CssVariableType | undefined;
if (tags) {
if (tags instanceof SassString) {
contextTags = [tags.text];
} else if (tags instanceof SassList) {
contextTags = [];
for (let i = 0; i < tags.asList.size; i++) {
const item = tags.get(i);
if (item instanceof SassString) {
contextTags.push(item.text);
if (tags instanceof SassMap) {
for (const [key, value] of tags.contents.toArray()) {
if (key instanceof SassString) {
switch (key.text) {
case 'description':
if (value instanceof SassString) {
description = value.text;
}
break;
case 'label':
if (value instanceof SassString) {
label = value.text;
}
break;
case 'type':
if (value instanceof SassString) {
type = value.text as CssVariableType;
}
break;
case 'category':
if (value instanceof SassString) {
category = value.text;
}
break;
case 'tags':
contextTags = CssVariableExtractor.extractTags(value);
break;
default:
console.warn(`Unsupported property: ${key.text}`);
break;
}
}
}
}
if (!contextTags) {
contextTags = CssVariableExtractor.extractTags(tags);
}
}
if (!(name instanceof SassString)) {
if (!(varName instanceof SassString)) {
throw new Error('invalid variable name');
}

let parsedValue: string;
if (value instanceof SassString || value instanceof SassNumber || value instanceof SassBoolean) {
parsedValue = value.toString();
} else if (value instanceof SassColor) {
parsedValue = CssVariableExtractor.getColorString(value);
} else if (value instanceof SassList) {
if (varValue instanceof SassString || varValue instanceof SassNumber || varValue instanceof SassBoolean) {
parsedValue = varValue.toString();
} else if (varValue instanceof SassColor) {
parsedValue = CssVariableExtractor.getColorString(varValue);
} else if (varValue instanceof SassList) {
const invalidIndexes: number[] = [];
const parsedValueItems: string[] = [];
for (let i = 0; i < value.asList.size; i++) {
const item = value.get(i);
for (let i = 0; i < varValue.asList.size; i++) {
const item = varValue.get(i);
if (item instanceof SassString || item instanceof SassNumber || item instanceof SassBoolean) {
parsedValueItems.push(item.toString());
} else if (item instanceof SassColor) {
Expand All @@ -166,20 +213,22 @@ export class CssVariableExtractor {
}
parsedValue = parsedValueItems.join(' ');
if (invalidIndexes.length) {
console.warn(`invalid value in the list (indexes: ${invalidIndexes.join(', ')}) for variable ${name.text}, it will be ignored`);
console.warn(`invalid value in the list (indexes: ${invalidIndexes.join(', ')}) for variable ${varName.text}, it will be ignored`);
}
} else if (CssVariableExtractor.isSassCalculation(value)) {
parsedValue = `calc(${value.$arguments[0]})`;
} else if (CssVariableExtractor.isSassCalculation(varValue)) {
parsedValue = `calc(${varValue.$arguments[0]})`;
} else {
console.warn(`invalid value for variable ${name.text}, it will be ignored`);
return new SassString(`[METADATA:VARIABLE] ${name.text} : invalid value`);
console.warn(`invalid value for variable ${varName.text}, it will be ignored`);
return new SassString(`[METADATA:VARIABLE] ${varName.text} : invalid value`);
}
const cssVariableObj = this.parseCssVariable(name.text, parsedValue);
const cssVariableObj = this.parseCssVariable(varName.text, parsedValue);
cssVariableObj.tags = contextTags;
if (cssVariableObj) {
cssVariables.push(cssVariableObj);
}
return new SassString(`[METADATA:VARIABLE] ${name.text} : ${parsedValue}` + (contextTags ? ` (tags: ${contextTags.join(', ')})` : ''));
cssVariableObj.description = description;
cssVariableObj.label = label;
cssVariableObj.type = type || 'string';
cssVariableObj.category = category;
cssVariables.push(cssVariableObj);
return new SassString(`[METADATA:VARIABLE] ${varName.text} : ${parsedValue}` + (contextTags ? ` (tags: ${contextTags.join(', ')})` : ''));
}
}
};
Expand Down
38 changes: 34 additions & 4 deletions packages/@o3r/styling/builders/style-extractor/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
import { BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { CmsMedataData, getLibraryCmsMetadata, validateJson } from '@o3r/extractors';
import type { CssMetadata, CssVariable } from '@o3r/styling';
import { isO3rClassComponent } from '@o3r/schematics';
import type { CssMetadata } from '@o3r/styling';
import * as chokidar from 'chokidar';
import * as fs from 'node:fs';
import { sync as globbySync } from 'globby';
import * as path from 'node:path';
import * as ts from 'typescript';
import { CssVariableExtractor } from './helpers/index';
import { StyleExtractorBuilderSchema } from './schema';

export * from './schema';


/**
* Get the library name from package.json
*
* @param currentDir
*/
const defaultLibraryName = (currentDir: string = process.cwd()) => {
const packageJsonPath = path.resolve(currentDir, 'package.json');
return JSON.parse(fs.readFileSync(packageJsonPath, { encoding: 'utf-8'})).name as string;
};

export default createBuilder<StyleExtractorBuilderSchema>(async (options, context): Promise<BuilderOutput> => {
context.reportRunning();
const libraryName = options.name || defaultLibraryName(context.currentDirectory);

const cssVariableExtractor = new CssVariableExtractor();

Expand All @@ -30,7 +44,23 @@ export default createBuilder<StyleExtractorBuilderSchema>(async (options, contex
await Promise.all(files.map((file, idx) => {
try {
context.reportProgress(idx, STEP_NUMBER, `Extracting ${file}`);
return cssVariableExtractor.extractFile(file);
const variables = cssVariableExtractor.extractFile(file);
const themeFileSuffix = '.style.theme.scss';
if (file.endsWith(themeFileSuffix)) {
const componentPath = path.join(path.dirname(file), `${path.basename(file, themeFileSuffix)}.component.ts`);
const componentSourceFile = ts.createSourceFile(
componentPath,
fs.readFileSync(componentPath).toString(),
ts.ScriptTarget.ES2020,
true
);
const componentDeclaration = componentSourceFile.statements.find((s): s is ts.ClassDeclaration => ts.isClassDeclaration(s) && isO3rClassComponent(s));
const componentName: string | undefined = componentDeclaration?.name?.escapedText.toString();
if (componentName) {
return variables.map((variable) => ({ ...variable, component: { library: libraryName, name: componentName }}));
}
}
return variables;
} catch (error: any) {
hasFailedFiles.push({file, error});
return [];
Expand All @@ -40,9 +70,9 @@ export default createBuilder<StyleExtractorBuilderSchema>(async (options, contex
// Check duplicate CSS variable
cssVarList
.filter((cssVar) => !!acc.variables[cssVar.name])
.filter((cssVar) => !initialPreviousMetadata.variables[cssVar.name] && (acc.variables[cssVar.name] as CssVariable).defaultValue !== cssVar.defaultValue)
.filter((cssVar) => !initialPreviousMetadata.variables[cssVar.name] && acc.variables[cssVar.name].defaultValue !== cssVar.defaultValue)
.forEach((cssVar) =>
context.logger.warn(`Duplicate "${cssVar.name}" (${(acc.variables[cssVar.name] as CssVariable).defaultValue} will be replaced by ${cssVar.defaultValue})`)
context.logger.warn(`Duplicate "${cssVar.name}" (${acc.variables[cssVar.name].defaultValue} will be replaced by ${cssVar.defaultValue})`)
);

// merge all variables form all the files
Expand Down
4 changes: 4 additions & 0 deletions packages/@o3r/styling/builders/style-extractor/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
},
"default": ["src/**/*.style.scss"]
},
"name": {
"type": "string",
"description": "Library/Application name to be assigned into metadata"
},
"outputFile": {
"type": "string",
"description": "Path to the output file",
Expand Down
3 changes: 3 additions & 0 deletions packages/@o3r/styling/builders/style-extractor/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ export interface StyleExtractorBuilderSchema extends JsonObject {

/** List of libraries imported */
libraries: string[];

/** Library/Application name to be assigned into metadata */
name: string | null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@ $palette-primary: o3r.get-mandatory(o3r.$default-theme, 'primary');

// list of variables used by the component
// The variables will be exposed by the CMS and should default on the theme
// Example of variable
$<%=name%>-color: o3r.variable('<%=name%>-color', o3r.color($palette-primary, 700)) !default;
$<%=name%>-color: o3r.variable('<%=name%>-color', o3r.color($palette-primary, 700), (description: 'Example of variable', type: 'color', category: 'examples', label: 'Color example')) !default;
12 changes: 12 additions & 0 deletions packages/@o3r/styling/src/core/styles.interface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export type CssVariableType = 'string' | 'color';

/** Metadata for a CSS Variable */
export interface CssVariable {
/** Name of the variable */
Expand All @@ -8,6 +10,16 @@ export interface CssVariable {
references?: CssVariable[];
/** Tags of the variable */
tags?: string[];
/** Description of the variable */
description?: string;
/** Description of the variable */
label?: string;
/** Type of the variable */
type?: CssVariableType;
/** Name of a group of variables */
category?: string;
/** component reference if the variable is linked to one */
component?: { library: string; name: string };
}

/** Style Metadata map */
Expand Down

0 comments on commit 4b51396

Please sign in to comment.