467 lines
15 KiB
JavaScript
467 lines
15 KiB
JavaScript
class ArgumentBuilder {
|
||
constructor() {
|
||
this.headerInput = document.getElementById('custom-header');
|
||
this.generateBtn = document.getElementById('generate-btn');
|
||
this.helpContent = document.getElementById('help-content');
|
||
this.outputSection = document.getElementById('output-section');
|
||
this.generatedCode = document.getElementById('generated-code');
|
||
this.copyBtn = document.getElementById('copy-btn');
|
||
this.globalContainer =
|
||
document.getElementById('global-arguments-container') ||
|
||
document.getElementById('arguments-container');
|
||
this.subcommandsContainer = document.querySelector('.subcommands-container');
|
||
this.addSubcommandBtn = document.getElementById('add-subcommand-btn');
|
||
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
if (!this.headerInput || !this.generateBtn || !this.helpContent || !this.globalContainer) {
|
||
return;
|
||
}
|
||
|
||
this.hydrateArgumentContainer(this.globalContainer);
|
||
|
||
if (this.subcommandsContainer) {
|
||
const sections = Array.from(this.subcommandsContainer.querySelectorAll('.subcommand-section'));
|
||
sections.forEach(section => this.hydrateSubcommandSection(section));
|
||
}
|
||
|
||
if (this.addSubcommandBtn) {
|
||
const clone = this.addSubcommandBtn.cloneNode(true);
|
||
clone.type = 'button';
|
||
clone.addEventListener('click', () => {
|
||
const section = this.createSubcommandSection();
|
||
if (section) {
|
||
section.classList.remove('collapsed');
|
||
section.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||
const nameInput = section.querySelector('.subcommand-name-input');
|
||
if (nameInput) {
|
||
nameInput.focus();
|
||
}
|
||
this.updateHelpPreview();
|
||
}
|
||
});
|
||
this.addSubcommandBtn.replaceWith(clone);
|
||
this.addSubcommandBtn = clone;
|
||
}
|
||
|
||
this.generateBtn.addEventListener('click', () => this.generateScript());
|
||
this.headerInput.addEventListener('input', () => this.updateHelpPreview());
|
||
|
||
if (this.copyBtn) {
|
||
this.copyBtn.addEventListener('click', () => this.copyToClipboard());
|
||
}
|
||
|
||
this.updateHelpPreview();
|
||
}
|
||
|
||
hydrateArgumentContainer(container) {
|
||
if (!container) {
|
||
return;
|
||
}
|
||
const rows = Array.from(container.querySelectorAll('.argument-row'));
|
||
if (rows.length === 0) {
|
||
this.createArgumentRow(container);
|
||
return;
|
||
}
|
||
rows.forEach(row => this.attachRowHandlers(row, container));
|
||
this.ensureTrailingRow(container);
|
||
}
|
||
|
||
attachRowHandlers(row, container) {
|
||
const inputs = Array.from(row.querySelectorAll('input'));
|
||
if (inputs.length === 0) {
|
||
return;
|
||
}
|
||
const update = () => {
|
||
this.updateHelpPreview();
|
||
this.ensureTrailingRow(container);
|
||
};
|
||
inputs.forEach(input => {
|
||
input.addEventListener('input', update);
|
||
});
|
||
|
||
const removeBtn = row.querySelector('.remove-btn');
|
||
if (removeBtn) {
|
||
removeBtn.type = 'button';
|
||
removeBtn.addEventListener('click', () => {
|
||
row.remove();
|
||
this.updateHelpPreview();
|
||
this.ensureTrailingRow(container);
|
||
});
|
||
}
|
||
}
|
||
|
||
ensureTrailingRow(container) {
|
||
if (!container) {
|
||
return;
|
||
}
|
||
const rows = Array.from(container.querySelectorAll('.argument-row'));
|
||
if (rows.length === 0) {
|
||
this.createArgumentRow(container);
|
||
return;
|
||
}
|
||
const lastRow = rows[rows.length - 1];
|
||
const hasContent = Array.from(lastRow.querySelectorAll('input')).some(
|
||
input => input.value.trim() !== ''
|
||
);
|
||
if (hasContent) {
|
||
this.createArgumentRow(container);
|
||
}
|
||
}
|
||
|
||
createArgumentRow(container, params = '', command = '', helpText = '') {
|
||
if (!container) {
|
||
return null;
|
||
}
|
||
const row = document.createElement('div');
|
||
row.className = 'argument-row';
|
||
|
||
const paramsInput = document.createElement('input');
|
||
paramsInput.type = 'text';
|
||
paramsInput.placeholder = '-v --verbose';
|
||
paramsInput.value = params;
|
||
|
||
const commandInput = document.createElement('input');
|
||
commandInput.type = 'text';
|
||
commandInput.placeholder = 'verbose';
|
||
commandInput.value = command;
|
||
|
||
const helpTextInput = document.createElement('input');
|
||
helpTextInput.type = 'text';
|
||
helpTextInput.placeholder = 'Enable verbose output';
|
||
helpTextInput.value = helpText;
|
||
|
||
const removeBtn = document.createElement('button');
|
||
removeBtn.className = 'remove-btn';
|
||
removeBtn.type = 'button';
|
||
removeBtn.innerHTML = '×';
|
||
|
||
row.appendChild(paramsInput);
|
||
row.appendChild(commandInput);
|
||
row.appendChild(helpTextInput);
|
||
row.appendChild(removeBtn);
|
||
|
||
container.appendChild(row);
|
||
this.attachRowHandlers(row, container);
|
||
|
||
return row;
|
||
}
|
||
|
||
hydrateSubcommandSection(section, presetArgs) {
|
||
if (!section) {
|
||
return;
|
||
}
|
||
|
||
const nameInput = section.querySelector('.subcommand-name-input');
|
||
const descInput = section.querySelector('.subcommand-desc-input');
|
||
const toggleBtn = section.querySelector('.toggle-collapse-btn');
|
||
const removeBtn = section.querySelector('.subcommand-header .remove-btn');
|
||
const argsContainer = section.querySelector('.arguments-container');
|
||
|
||
if (nameInput) {
|
||
nameInput.addEventListener('input', () => this.updateHelpPreview());
|
||
}
|
||
if (descInput) {
|
||
descInput.addEventListener('input', () => this.updateHelpPreview());
|
||
}
|
||
if (toggleBtn) {
|
||
toggleBtn.type = 'button';
|
||
toggleBtn.addEventListener('click', () => {
|
||
section.classList.toggle('collapsed');
|
||
});
|
||
}
|
||
if (removeBtn) {
|
||
removeBtn.type = 'button';
|
||
removeBtn.addEventListener('click', () => {
|
||
section.remove();
|
||
this.updateHelpPreview();
|
||
});
|
||
}
|
||
|
||
if (argsContainer) {
|
||
if (Array.isArray(presetArgs)) {
|
||
argsContainer.innerHTML = '';
|
||
presetArgs.forEach(arg => {
|
||
this.createArgumentRow(
|
||
argsContainer,
|
||
arg.params || '',
|
||
arg.command || '',
|
||
arg.helpText || ''
|
||
);
|
||
});
|
||
}
|
||
this.hydrateArgumentContainer(argsContainer);
|
||
}
|
||
}
|
||
|
||
createSubcommandSection(name = '', description = '', args = []) {
|
||
if (!this.subcommandsContainer) {
|
||
return null;
|
||
}
|
||
|
||
const section = document.createElement('div');
|
||
section.className = 'subcommand-section';
|
||
|
||
const header = document.createElement('div');
|
||
header.className = 'subcommand-header';
|
||
|
||
const nameInput = document.createElement('input');
|
||
nameInput.type = 'text';
|
||
nameInput.className = 'subcommand-name-input';
|
||
nameInput.placeholder = 'container';
|
||
nameInput.value = name;
|
||
|
||
const descInput = document.createElement('input');
|
||
descInput.type = 'text';
|
||
descInput.className = 'subcommand-desc-input';
|
||
descInput.placeholder = 'Manage containers';
|
||
descInput.value = description;
|
||
|
||
const toggleBtn = document.createElement('button');
|
||
toggleBtn.className = 'toggle-collapse-btn';
|
||
toggleBtn.type = 'button';
|
||
|
||
const removeBtn = document.createElement('button');
|
||
removeBtn.className = 'remove-btn';
|
||
removeBtn.type = 'button';
|
||
removeBtn.innerHTML = '×';
|
||
|
||
header.appendChild(nameInput);
|
||
header.appendChild(descInput);
|
||
header.appendChild(toggleBtn);
|
||
header.appendChild(removeBtn);
|
||
|
||
const body = document.createElement('div');
|
||
body.className = 'subcommand-body';
|
||
|
||
const headerRow = this.createArgumentHeader();
|
||
const argsContainer = document.createElement('div');
|
||
argsContainer.className = 'arguments-container';
|
||
|
||
body.appendChild(headerRow);
|
||
body.appendChild(argsContainer);
|
||
|
||
section.appendChild(header);
|
||
section.appendChild(body);
|
||
|
||
if (this.addSubcommandBtn) {
|
||
this.addSubcommandBtn.before(section);
|
||
} else {
|
||
this.subcommandsContainer.appendChild(section);
|
||
}
|
||
|
||
this.hydrateSubcommandSection(section, args);
|
||
|
||
return section;
|
||
}
|
||
|
||
createArgumentHeader() {
|
||
const row = document.createElement('div');
|
||
row.className = 'argument-header';
|
||
const labels = ['Parameters', 'Variable Name', 'Help Text', ''];
|
||
labels.forEach(text => {
|
||
const span = document.createElement('span');
|
||
span.textContent = text;
|
||
row.appendChild(span);
|
||
});
|
||
return row;
|
||
}
|
||
|
||
getArgumentsFromContainer(container) {
|
||
if (!container) {
|
||
return [];
|
||
}
|
||
const rows = Array.from(container.querySelectorAll('.argument-row'));
|
||
return rows
|
||
.map(row => {
|
||
const inputs = row.querySelectorAll('input');
|
||
if (inputs.length < 3) {
|
||
return null;
|
||
}
|
||
|
||
const params = inputs[0].value.trim();
|
||
const command = inputs[1].value.trim();
|
||
const helpText = inputs[2].value.trim();
|
||
|
||
if (!command) {
|
||
return null;
|
||
}
|
||
|
||
return {
|
||
params: params || command,
|
||
command: command,
|
||
helpText: helpText || command
|
||
};
|
||
})
|
||
.filter(Boolean);
|
||
}
|
||
|
||
getGlobalArguments() {
|
||
return this.getArgumentsFromContainer(this.globalContainer);
|
||
}
|
||
|
||
getSubcommands() {
|
||
if (!this.subcommandsContainer) {
|
||
return [];
|
||
}
|
||
const sections = Array.from(
|
||
this.subcommandsContainer.querySelectorAll('.subcommand-section')
|
||
);
|
||
return sections
|
||
.map(section => {
|
||
const name =
|
||
section.querySelector('.subcommand-name-input')?.value.trim() || '';
|
||
const description =
|
||
section.querySelector('.subcommand-desc-input')?.value.trim() || '';
|
||
const argsContainer = section.querySelector('.arguments-container');
|
||
const arguments = this.getArgumentsFromContainer(argsContainer);
|
||
return {
|
||
name,
|
||
description,
|
||
arguments
|
||
};
|
||
})
|
||
.filter(subcmd => subcmd.name);
|
||
}
|
||
|
||
padLabel(label, width = 20) {
|
||
if (!label) {
|
||
return ''.padEnd(width, ' ');
|
||
}
|
||
if (label.length >= width) {
|
||
return label + ' ';
|
||
}
|
||
return label + ' '.repeat(width - label.length);
|
||
}
|
||
|
||
updateHelpPreview() {
|
||
if (!this.helpContent) {
|
||
return;
|
||
}
|
||
|
||
const header = this.headerInput ? this.headerInput.value.trim() : '';
|
||
const globalArgs = this.getGlobalArguments();
|
||
const subcommands = this.getSubcommands();
|
||
|
||
if (globalArgs.length === 0 && subcommands.length === 0) {
|
||
this.helpContent.textContent = 'Add arguments to see the help output...';
|
||
return;
|
||
}
|
||
|
||
const lines = [];
|
||
|
||
if (header) {
|
||
lines.push(header, '');
|
||
}
|
||
|
||
let usage = 'Usage: script.sh';
|
||
if (subcommands.length > 0) {
|
||
if (globalArgs.length > 0) {
|
||
usage += ' [GLOBAL OPTIONS]';
|
||
}
|
||
usage += ' COMMAND [COMMAND OPTIONS]';
|
||
} else {
|
||
usage += ' [OPTIONS]';
|
||
}
|
||
lines.push(usage, '');
|
||
|
||
if (globalArgs.length > 0) {
|
||
lines.push('Global Options:');
|
||
globalArgs.forEach(arg => {
|
||
const params = arg.params || arg.command;
|
||
const help = arg.helpText || arg.command;
|
||
lines.push(` ${this.padLabel(params)}${help}`);
|
||
});
|
||
lines.push(` ${this.padLabel('-h --help')}Show this help message`, '');
|
||
}
|
||
|
||
if (subcommands.length > 0) {
|
||
lines.push('Subcommands:');
|
||
subcommands.forEach(subcmd => {
|
||
const desc = subcmd.description || subcmd.name;
|
||
lines.push(` ${this.padLabel(subcmd.name)}${desc}`);
|
||
});
|
||
lines.push('', "Run 'script.sh COMMAND --help' for more information on a command.");
|
||
|
||
subcommands.forEach(subcmd => {
|
||
if (subcmd.arguments.length === 0) {
|
||
return;
|
||
}
|
||
lines.push('', `${subcmd.name} Options:`);
|
||
subcmd.arguments.forEach(arg => {
|
||
const params = arg.params || arg.command;
|
||
const help = arg.helpText || arg.command;
|
||
lines.push(` ${this.padLabel(params)}${help}`);
|
||
});
|
||
lines.push(` ${this.padLabel('-h --help')}Show this help message`);
|
||
});
|
||
}
|
||
|
||
this.helpContent.textContent = lines.join('\n');
|
||
}
|
||
|
||
async generateScript() {
|
||
const payload = {
|
||
header: this.headerInput ? this.headerInput.value.trim() : '',
|
||
arguments: this.getGlobalArguments(),
|
||
subcommands: this.getSubcommands()
|
||
};
|
||
|
||
if (payload.arguments.length === 0 && payload.subcommands.length === 0) {
|
||
alert('Please add at least one global argument or subcommand before generating.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/generate/v2', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(payload)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error('Failed to generate script');
|
||
}
|
||
|
||
const scriptCode = await response.text();
|
||
if (this.generatedCode) {
|
||
this.generatedCode.textContent = scriptCode;
|
||
}
|
||
if (this.outputSection) {
|
||
this.outputSection.style.display = 'block';
|
||
this.outputSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||
}
|
||
} catch (error) {
|
||
alert('Error generating script: ' + error.message);
|
||
}
|
||
}
|
||
|
||
async copyToClipboard() {
|
||
if (!this.generatedCode || !this.copyBtn) {
|
||
return;
|
||
}
|
||
|
||
const code = this.generatedCode.textContent;
|
||
try {
|
||
await navigator.clipboard.writeText(code);
|
||
this.copyBtn.textContent = 'Copied!';
|
||
this.copyBtn.classList.add('copied');
|
||
|
||
setTimeout(() => {
|
||
this.copyBtn.textContent = 'Copy';
|
||
this.copyBtn.classList.remove('copied');
|
||
}, 2000);
|
||
} catch (error) {
|
||
alert('Failed to copy to clipboard');
|
||
}
|
||
}
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
new ArgumentBuilder();
|
||
});
|