UNPKG

@patternfly/react-icons

Version:
253 lines (216 loc) • 9.03 kB
/* eslint-disable no-console */ import { promises as fs } from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { createRequire } from 'module'; import { icons } from '@rhds/icons/metadata.js'; // --- Configuration --- // Prefix for icon keys and output filenames (e.g., "rh" produces "rh-ui-arrow-down" and "rhIconsUI.mjs") const ICON_PREFIX = 'rh'; // Icon types to process. Comment out any types you don't want to parse. const ICON_TYPES = [ 'ui', 'standard', // 'social', 'microns' // Add new types here as they become available in @rhds/icons ]; const ICON_TYPE_CLASSES = { ui: '', standard: 'pf-v6-icon-rh-standard', // social: '', microns: '' }; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const require = createRequire(import.meta.url); // Resolve path to @rhds/icons directory structure const rhdsIconsPath = require.resolve('@rhds/icons/ui/add-circle-fill.svg'); const RHDS_ICONS_BASE_DIR = path.dirname(path.dirname(rhdsIconsPath)); const OUTPUT_DIR = path.join(__dirname, 'icons'); // --------------------- /** * Regex to safely extract the four view box numbers from the outer <svg> tag. * It handles different spacing. */ const VIEWBOX_REGEX = /<svg[^>]+viewBox="(\s*(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s+(-?\d+(\.\d+)?)\s*)"/i; /** * Regex to extract all 'd' attribute content from <path> elements. * Uses global flag to match all paths. */ const PATH_DATA_REGEX = /<path\s+[^>]*d="([^"]+)"/gi; /** * Processes a single SVG file to extract viewbox data and path data. * @param {string} iconName The name of the icon (without .svg extension). * @param {string} iconType The type/category of the icon (ui, standard, social, microns). * @returns {object|null} The parsed icon object, or null on error. */ async function processSvgFile(iconName, iconType) { const typeDir = path.join(RHDS_ICONS_BASE_DIR, iconType); const svgPath = path.join(typeDir, `${iconName}.svg`); try { // 1. Read the file content const content = await fs.readFile(svgPath, 'utf-8'); // 2. Extract view box data const viewBoxMatch = content.match(VIEWBOX_REGEX); if (!viewBoxMatch) { console.error(`Skipping ${iconName}: Could not find or parse viewBox attribute.`); return null; } // Extract viewBox values: "xOffset yOffset width height" const viewboxValues = viewBoxMatch[1].trim().split(/\s+/).map(Number); if (viewboxValues.length !== 4 || viewboxValues.some(isNaN)) { console.error(`Skipping ${iconName}: viewBox values are not valid numbers.`); return null; } const [xOffset, yOffset, width, height] = viewboxValues; // 3. Extract all path data elements const pathDataMatches = [...content.matchAll(PATH_DATA_REGEX)]; if (!pathDataMatches || pathDataMatches.length === 0) { console.error(`Skipping ${iconName}: Could not find 'd' attribute in any <path> element.`); return null; } // Extract all path data values const pathDataValues = pathDataMatches.map((match) => match[1]); // If there's only one path, store as string; if multiple, store as array of objects with 'path' property // (createIcon expects arrays to be SVGPathObject[] with { path: string, className?: string } structure) const svgPathData = pathDataValues.length === 1 ? pathDataValues[0] : pathDataValues.map((pathData) => ({ path: pathData })); // 4. Construct the final object return { iconName, data: { xOffset, yOffset, width, height, svgPathData } }; } catch (error) { console.error(`Error processing ${iconName}:`, error.message); return null; } } /** * Formats an object as an ES module export with proper formatting. * @param {object} obj The object to format. * @param {string} iconType The set of the icon. * @param {number} indent The indentation level. * @returns {string} Formatted module string. */ function formatModule(obj, iconType, indent = 0) { const spaces = ' '.repeat(indent); const entries = Object.entries(obj); if (entries.length === 0) { return '{}'; } const lines = ['{']; entries.forEach(([key, value], index) => { const isLast = index === entries.length - 1; const comma = isLast ? '' : ','; if (typeof value === 'object' && value !== null && !Array.isArray(value)) { // Escape single quotes in key const escapedKey = key.replace(/'/g, "\\'"); lines.push(`${spaces} '${escapedKey}': {`); lines.push(`${spaces} xOffset: ${value.xOffset},`); lines.push(`${spaces} yOffset: ${value.yOffset},`); lines.push(`${spaces} width: ${value.width},`); lines.push(`${spaces} height: ${value.height},`); // Handle set-specific svgClassName if (ICON_TYPE_CLASSES[iconType]) { lines.push(`${spaces} svgClassName: '${ICON_TYPE_CLASSES[iconType]}',`); } // Handle svgPathData: string or array of objects if (Array.isArray(value.svgPathData)) { // Array of path objects with { path: string } structure lines.push(`${spaces} svgPathData: [`); value.svgPathData.forEach((pathObject, idx) => { const isLast = idx === value.svgPathData.length - 1; const escapedPath = pathObject.path.replace(/'/g, "\\'"); if (pathObject.className) { const escapedClassName = pathObject.className.replace(/'/g, "\\'"); lines.push( `${spaces} { path: '${escapedPath}', className: '${escapedClassName}' }${isLast ? '' : ','}` ); } else { lines.push(`${spaces} { path: '${escapedPath}' }${isLast ? '' : ','}`); } }); lines.push(`${spaces} ]`); } else { // Single string path data const escapedPathData = value.svgPathData.replace(/'/g, "\\'"); lines.push(`${spaces} svgPathData:`); lines.push(`${spaces} '${escapedPathData}'`); } lines.push(`${spaces} }${comma}`); } }); lines.push(`${spaces}}`); return lines.join('\n'); } /** * Processes all icons of a specific type. * @param {string} iconType The type/category of icons to process. * @returns {Promise<object>} Object with iconName as keys and icon data as values. */ async function processIconType(iconType) { console.log(`\nProcessing ${iconType} icons...`); // Get icons for this type from metadata const typeIcons = icons.get(iconType); if (!typeIcons) { console.warn(`Warning: Icon type '${iconType}' not found in metadata. Skipping.`); return {}; } const iconList = Array.from(typeIcons); console.log(`Found ${iconList.length} ${iconType} icons to process...`); try { // Process all SVGs concurrently const processingPromises = iconList.map((iconName) => processSvgFile(iconName, iconType)); const results = await Promise.all(processingPromises); // Filter out any icons that failed to process const successfulResults = results.filter((data) => data !== null); // Convert array to object with prefixed icon names as keys const iconsObject = {}; successfulResults.forEach(({ iconName, data }) => { const prefixedKey = `${ICON_PREFIX}-${iconType}-${iconName}`; iconsObject[prefixedKey] = data; }); console.log(`Successfully processed ${successfulResults.length} out of ${iconList.length} ${iconType} icons.`); return iconsObject; } catch (error) { console.error(`Error processing ${iconType} icons:`, error); return {}; } } /** * Main function to process all icon types and create ES module files. */ async function processSVGs() { console.log(`Starting SVG processing from @rhds/icons`); console.log(`Processing ${ICON_TYPES.length} icon type(s): ${ICON_TYPES.join(', ')}`); // Ensure output directory exists await fs.mkdir(OUTPUT_DIR, { recursive: true }); const results = {}; // Process each icon type for (const iconType of ICON_TYPES) { const iconsObject = await processIconType(iconType); if (Object.keys(iconsObject).length > 0) { results[iconType] = iconsObject; // Write individual file for this type // Capitalize first letter, and handle special case for 'ui' -> 'UI' const capitalizedType = iconType === 'ui' ? 'UI' : iconType.charAt(0).toUpperCase() + iconType.slice(1); const outputFile = path.join(OUTPUT_DIR, `rhIcons${capitalizedType}.mjs`); const moduleOutput = `export default ${formatModule(iconsObject, iconType)};\n`; await fs.writeFile(outputFile, moduleOutput, 'utf-8'); console.log(`Output written to: ${outputFile}`); } } // Summary console.log('\n--- SUCCESS ---'); const totalIcons = Object.values(results).reduce((sum, icons) => sum + Object.keys(icons).length, 0); console.log(`Total icons processed: ${totalIcons}`); console.log(`Icon types processed: ${Object.keys(results).join(', ')}`); } processSVGs();