tusktsk
Version:
TuskTsk - The Freedom Configuration Language. Query databases, use any syntax, never bow to any king!
370 lines (314 loc) • 9.71 kB
JavaScript
/**
* CSS Commands for TuskLang CLI
* =============================
* CSS processing and optimization commands
*
* Commands:
* - expand: Expand CSS shorthand properties
* - map: Generate CSS source maps
*/
const { Command } = require('commander');
const fs = require('fs').promises;
const path = require('path');
/**
* CSS shorthand patterns and their expansions
*/
const CSS_SHORTHANDS = {
// Margin shorthand
'm': 'margin',
'mt': 'margin-top',
'mr': 'margin-right',
'mb': 'margin-bottom',
'ml': 'margin-left',
'mx': ['margin-left', 'margin-right'],
'my': ['margin-top', 'margin-bottom'],
// Padding shorthand
'p': 'padding',
'pt': 'padding-top',
'pr': 'padding-right',
'pb': 'padding-bottom',
'pl': 'padding-left',
'px': ['padding-left', 'padding-right'],
'py': ['padding-top', 'padding-bottom'],
// Border shorthand
'b': 'border',
'bt': 'border-top',
'br': 'border-right',
'bb': 'border-bottom',
'bl': 'border-left',
'bx': ['border-left', 'border-right'],
'by': ['border-top', 'border-bottom'],
// Background shorthand
'bg': 'background',
'bgc': 'background-color',
'bgi': 'background-image',
'bgr': 'background-repeat',
'bgp': 'background-position',
'bgs': 'background-size',
// Font shorthand
'f': 'font',
'fs': 'font-size',
'fw': 'font-weight',
'ff': 'font-family',
'fc': 'font-color',
// Display shorthand
'd': 'display',
'f': 'float',
'p': 'position',
't': 'top',
'r': 'right',
'b': 'bottom',
'l': 'left',
// Width/Height shorthand
'w': 'width',
'h': 'height',
'mw': 'max-width',
'mh': 'max-height',
'minw': 'min-width',
'minh': 'min-height',
// Flexbox shorthand
'fd': 'flex-direction',
'fw': 'flex-wrap',
'fg': 'flex-grow',
'fs': 'flex-shrink',
'fb': 'flex-basis',
// Grid shorthand
'gc': 'grid-column',
'gr': 'grid-row',
'ga': 'grid-area',
// Text shorthand
'ta': 'text-align',
'td': 'text-decoration',
'tt': 'text-transform',
'ls': 'letter-spacing',
'ws': 'word-spacing',
'lh': 'line-height',
// Box model shorthand
'box': 'box-sizing',
'ov': 'overflow',
'ovx': 'overflow-x',
'ovy': 'overflow-y',
'z': 'z-index',
// Animation shorthand
'anim': 'animation',
'tr': 'transition',
'tf': 'transform',
// Other common shorthand
'cur': 'cursor',
'op': 'opacity',
'vis': 'visibility',
'clip': 'clip-path',
'filter': 'filter'
};
/**
* Expand CSS shorthand properties
*/
function expandCSSShorthand(css) {
let expanded = css;
let changes = 0;
// Process each line
const lines = expanded.split('\n');
const processedLines = [];
for (let line of lines) {
const originalLine = line;
// Find CSS property declarations
const propertyMatch = line.match(/([a-zA-Z-]+)\s*:\s*([^;]+);/);
if (propertyMatch) {
const [, property, value] = propertyMatch;
const trimmedProperty = property.trim();
// Check if this is a shorthand property
if (CSS_SHORTHANDS[trimmedProperty]) {
const expansion = CSS_SHORTHANDS[trimmedProperty];
if (Array.isArray(expansion)) {
// Multiple properties
const newProperties = expansion.map(prop => ` ${prop}: ${value};`);
line = line.replace(propertyMatch[0], newProperties.join('\n'));
changes += expansion.length;
} else {
// Single property
line = line.replace(trimmedProperty, expansion);
changes++;
}
}
}
processedLines.push(line);
}
return {
css: processedLines.join('\n'),
changes,
original: css
};
}
/**
* Generate CSS source map
*/
function generateCSSSourceMap(css, sourceFile, outputFile) {
const sourceMap = {
version: 3,
file: path.basename(outputFile),
sourceRoot: '',
sources: [path.basename(sourceFile)],
names: [],
mappings: ''
};
// Generate mappings (simplified - in production would use proper source map library)
const lines = css.split('\n');
let currentLine = 0;
let currentColumn = 0;
const mappings = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const lineLength = line.length;
// Add mapping for this line
mappings.push(`${currentColumn},${i},${currentColumn},${i}`);
currentLine++;
currentColumn += lineLength + 1; // +1 for newline
}
sourceMap.mappings = mappings.join(';');
return sourceMap;
}
/**
* Expand CSS file
*/
async function expandCSSFile(inputFile, outputFile = null) {
try {
// Validate input file
if (!inputFile) {
throw new Error('Input file is required');
}
const inputPath = path.resolve(inputFile);
const inputExt = path.extname(inputPath);
if (inputExt !== '.css') {
throw new Error('Input file must have .css extension');
}
// Generate output filename if not provided
if (!outputFile) {
const baseName = path.basename(inputPath, '.css');
const outputPath = path.join(path.dirname(inputPath), `${baseName}.expanded.css`);
outputFile = outputPath;
}
// Read CSS file
const css = await fs.readFile(inputPath, 'utf8');
// Expand shorthand properties
const result = expandCSSShorthand(css);
// Write expanded CSS
await fs.writeFile(outputFile, result.css, 'utf8');
return {
success: true,
input: inputPath,
output: outputFile,
originalSize: result.original.length,
expandedSize: result.css.length,
changes: result.changes,
message: `✅ Successfully expanded ${inputPath} to ${outputFile} (${result.changes} properties expanded)`
};
} catch (error) {
return {
success: false,
error: error.message,
message: `❌ Failed to expand ${inputFile}: ${error.message}`
};
}
}
/**
* Generate CSS source map
*/
async function generateCSSMap(inputFile, outputFile = null) {
try {
// Validate input file
if (!inputFile) {
throw new Error('Input file is required');
}
const inputPath = path.resolve(inputFile);
const inputExt = path.extname(inputPath);
if (inputExt !== '.css') {
throw new Error('Input file must have .css extension');
}
// Generate output filename if not provided
if (!outputFile) {
const outputPath = `${inputPath}.map`;
outputFile = outputPath;
}
// Read CSS file
const css = await fs.readFile(inputPath, 'utf8');
// Generate source map
const sourceMap = generateCSSSourceMap(css, inputPath, outputFile);
// Write source map
await fs.writeFile(outputFile, JSON.stringify(sourceMap, null, 2), 'utf8');
// Add source map comment to CSS if it doesn't exist
const sourceMapComment = `\n/*# sourceMappingURL=${path.basename(outputFile)} */`;
const cssWithMap = css + sourceMapComment;
await fs.writeFile(inputPath, cssWithMap, 'utf8');
return {
success: true,
input: inputPath,
output: outputFile,
cssSize: css.length,
mapSize: JSON.stringify(sourceMap).length,
message: `✅ Successfully generated source map ${outputFile} for ${inputPath}`
};
} catch (error) {
return {
success: false,
error: error.message,
message: `❌ Failed to generate source map for ${inputFile}: ${error.message}`
};
}
}
// Command definitions
const expand = new Command('expand')
.description('Expand CSS shorthand properties')
.argument('<input>', 'Input CSS file')
.option('-o, --output <file>', 'Output CSS file (auto-generated if not specified)')
.option('-v, --verbose', 'Show detailed expansion information')
.action(async (input, options) => {
const result = await expandCSSFile(input, options.output);
console.log(result.message);
if (result.success) {
console.log(` Input: ${result.input}`);
console.log(` Output: ${result.output}`);
console.log(` Original size: ${result.originalSize} bytes`);
console.log(` Expanded size: ${result.expandedSize} bytes`);
console.log(` Properties expanded: ${result.changes}`);
if (options.verbose) {
console.log('\nShorthand patterns supported:');
Object.keys(CSS_SHORTHANDS).forEach(shorthand => {
const expansion = CSS_SHORTHANDS[shorthand];
if (Array.isArray(expansion)) {
console.log(` ${shorthand} → ${expansion.join(', ')}`);
} else {
console.log(` ${shorthand} → ${expansion}`);
}
});
}
}
});
const map = new Command('map')
.description('Generate CSS source map')
.argument('<input>', 'Input CSS file')
.option('-o, --output <file>', 'Output source map file (auto-generated if not specified)')
.option('-n, --no-comment', 'Do not add source map comment to CSS file')
.action(async (input, options) => {
const result = await generateCSSMap(input, options.output);
console.log(result.message);
if (result.success) {
console.log(` Input: ${result.input}`);
console.log(` Output: ${result.output}`);
console.log(` CSS size: ${result.cssSize} bytes`);
console.log(` Map size: ${result.mapSize} bytes`);
if (!options.comment) {
console.log(` Source map comment added to CSS file`);
}
}
});
module.exports = {
expand,
map,
// Export functions for testing
_expandCSSShorthand: expandCSSShorthand,
_generateCSSSourceMap: generateCSSSourceMap,
_expandCSSFile: expandCSSFile,
_generateCSSMap: generateCSSMap,
_CSS_SHORTHANDS: CSS_SHORTHANDS
};