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(); });