Skip to content

Instantly share code, notes, and snippets.

@derammo
Last active October 22, 2024 15:59
Show Gist options
  • Select an option

  • Save derammo/ab3de45b7b1a52e6c8b232658b7279c9 to your computer and use it in GitHub Desktop.

Select an option

Save derammo/ab3de45b7b1a52e6c8b232658b7279c9 to your computer and use it in GitHub Desktop.
const fs = require('fs');
const readline = require('readline');
async function parseFile(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
const sections = [];
let currentSection = null;
const keyCodeMap = {
0x3B: 'F1', 0x3C: 'F2', 0x3D: 'F3', 0x3E: 'F4', 0x3F: 'F5', 0x40: 'F6', 0x41: 'F7', 0x42: 'F8', 0x43: 'F9', 0x44: 'F10',
0x57: 'F11', 0x58: 'F12', 0x2: '1', 0x3: '2', 0x4: '3', 0x5: '4', 0x6: '5', 0x7: '6', 0x8: '7', 0x9: '8', 0xA: '9', 0xB: '0',
0x1E: 'A', 0x30: 'B', 0x2E: 'C', 0x20: 'D', 0x12: 'E', 0x21: 'F', 0x22: 'G', 0x23: 'H', 0x17: 'I', 0x24: 'J', 0x25: 'K',
0x26: 'L', 0x32: 'M', 0x31: 'N', 0x18: 'O', 0x19: 'P', 0x10: 'Q', 0x13: 'R', 0x1F: 'S', 0x14: 'T', 0x16: 'U', 0x2F: 'V',
0x11: 'W', 0x2D: 'X', 0x15: 'Y', 0x2C: 'Z', 0x29: '`', 0xC: '-', 0xD: '=', 0x1A: '[', 0x1B: ']', 0x27: ';', 0x28: "'",
0x2B: '\\', 0x33: ',', 0x34: '.', 0x35: '/', 0x1: 'Esc', 0xF: 'Tab', 0x3A: 'Caps Lock', 0xE: 'Backspace', 0x1C: 'Return',
0x39: 'Space', 0xB7: 'Print', 0x46: 'Scrl Lock', 0xD2: 'Insert', 0xD3: 'Delete', 0xC7: 'Home', 0xCF: 'End', 0xC9: 'Page Up',
0xD1: 'Page Dn', 0xC8: 'Up', 0xD0: 'Down', 0xCD: 'Right', 0xCB: 'Left', 0xB5: 'Num /', 0x37: 'Num *', 0x4A: 'Num -', 0x4E: 'Num +',
0x9C: 'Num Enter', 0x53: 'Num .', 0x52: 'Num 0', 0x4F: 'Num 1', 0x50: 'Num 2', 0x51: 'Num 3', 0x4B: 'Num 4', 0x4C: 'Num 5',
0x4D: 'Num 6', 0x47: 'Num 7', 0x48: 'Num 8', 0x49: 'Num 9'
};
for await (const line of rl) {
if (line.startsWith('#')) {
continue; // Skip comment lines
}
if (line.startsWith('SimDoNothing') && line.includes('"==')) {
const sectionTitle = line.match(/"([^"]+)"/)[1];
const sanitizedTitle = sectionTitle.replace(/=/g, '').replace(/\s+/g, ' ');
currentSection = {
title: sanitizedTitle,
commands: []
};
sections.push(currentSection);
} else if (currentSection && line.trim()) {
if (line.startsWith('SimDoNothing')) {
continue; // Skip SimDoNothing lines
}
const parts = line.split('"');
const command = parts[0].trim();
// escape markdown special characters
const description = parts[1].replace(/\*/g, '\\*').replace(/_/g, '\\_').trim();
// drop any extended ASCII characters
const sanitizedDescription = description.replace(/[\x80-\xFF]/g, '');
const commandParts = command.split(' ');
const commandName = commandParts[0];
const keyCode = commandParts[3];
if (/^0[xX][fF][fF][fF][fF]+$/.test(keyCode)) {
currentSection.commands.push({ commandName, sanitizedDescription, keystroke: '(unbound)' });
continue;
}
const modifierMask = commandParts[4];
const modifierString = buildModifierString(modifierMask);
const keyName = keyCodeMap[parseInt(keyCode, 16)] || keyCode;
const keystroke = `${modifierString} ${keyName}`.trim();
currentSection.commands.push({ commandName, sanitizedDescription, keystroke });
}
}
return sections;
}
const inputFilePath = process.argv[2];
if (!inputFilePath) {
console.error('Please provide the input file path as a command line argument.');
console.error('Optional: Provide the output format as a second argument (json, text, csv, md). Default is md.');
process.exit(1);
}
parseFile(inputFilePath).then(sections => {
const filteredSections = sections.filter(section => section.commands.length > 0);
const outputFormat = process.argv[3] || 'md';
switch (outputFormat.toLowerCase()) {
case 'json':
printJson(filteredSections);
break;
case 'text':
printText(filteredSections);
break;
case 'csv':
printCSV(filteredSections);
break;
case 'md':
default:
printMarkdown(filteredSections);
break;
}
}).catch(err => {
console.error(err);
});
function printJson(sections) {
console.log(JSON.stringify(sections, null, 2));
}
function printMarkdown(sections) {
sections.forEach(section => {
console.log(`# ${section.title}`);
console.log();
console.log('| Callback | Description | Keyboard Binding |');
console.log('|----------|-------------|------------------|');
section.commands.forEach(command => {
console.log(`| ${command.commandName} | ${command.sanitizedDescription} | ${command.keystroke} |`);
});
console.log();
});
}
function printText(sections) {
sections.forEach(section => {
const sectionTitle = section.title.trim();
console.log(`${sectionTitle}:`)
section.commands.forEach(command => {
if (command.keystroke === '(unbound)') {
console.log(`- "${command.sanitizedDescription}": (no keys bound)`);
} else {
console.log(`- "${command.sanitizedDescription}": Press ${command.keystroke}`);
}
});
console.log();
});
}
function printCSV(sections) {
console.log(`Section, Callback, Description, "Keyboard Binding"`);
sections.forEach(section => {
const sectionTitle = section.title.trim();
section.commands.forEach(command => {
console.log(`"${sectionTitle}", ${command.commandName}, "${command.sanitizedDescription}", "${command.keystroke}"`);
});
});
}
function buildModifierString(modifierMask) {
const modifierKeys = [];
const modifierValue = parseInt(modifierMask, 10);
if (modifierValue & 1) modifierKeys.push('Shift');
if (modifierValue & 2) modifierKeys.push('Control');
if (modifierValue & 4) modifierKeys.push('Alt');
const modifierString = modifierKeys.join(' ');
return modifierString;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment