diff --git a/2025-10-27 01.06.24 localhost b47035f03aa0.png b/assets/img/flat-view.png
similarity index 100%
rename from 2025-10-27 01.06.24 localhost b47035f03aa0.png
rename to assets/img/flat-view.png
diff --git a/static/js/app.js b/static/js/app.js
index 737ced4..f880b09 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -99,21 +99,6 @@ class ArgumentBuilder {
return args;
}
- addSubcommand(name = '', description = '') {
- const subcommandSection = document.createElement('div');
- subcommandSection.className = 'subcommand-section';
-
- const header = this.createSubcommandHeader(name, description);
- const argsContainer = this.createArgumentsContainer();
-
- subcommandSection.appendChild(header);
- subcommandSection.appendChild(argsContainer);
-
- this.subcommandContainer.appendChild(subcommandSection);
-
- return subcommandSection;
- }
-
updateHelpPreview() {
const header = this.headerInput.value.trim();
const args = this.getArguments();
@@ -142,34 +127,6 @@ class ArgumentBuilder {
this.helpContent.textContent = helpText;
}
- createSubcommandHeader(name, description) {
- const header = document.createElement('div');
- header.className = 'subcommand-header';
- header.innerHTML = `
-
-
-
-
- `;
- return header;
- }
-
- getSubcommands() {
- const sections = this.subcommandContainer.querySelectorAll('.subcommand-section');
- return Array.from(sections).map(section => {
- const nameInput = section.querySelector('.subcommand-name');
- const descInput = section.querySelector('.subcommand-desc');
- const args = this.getArgumentsFromSection(section);
-
- return {
- name: nameInput.value.trim(),
- description: descInput.value.trim(),
- arguments: args
- };
- }).filter(sc => sc.name);
- }
async generateScript() {
const header = this.headerInput.value.trim();
@@ -181,9 +138,8 @@ class ArgumentBuilder {
}
const payload = {
- header: this.headerInput.value.trim(),
- arguments: this.getArguments(),
- subcommands: this.getSubcommands()
+ header: header,
+ arguments: args
};
try {
diff --git a/static/js/v2.js b/static/js/v2.js
index 737ced4..7edc305 100644
--- a/static/js/v2.js
+++ b/static/js/v2.js
@@ -1,31 +1,120 @@
class ArgumentBuilder {
constructor() {
- this.arguments = [];
- this.container = document.getElementById('arguments-container');
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() {
- // Add initial empty row
- this.addArgumentRow();
+ 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;
+ }
- // Event listeners
this.generateBtn.addEventListener('click', () => this.generateScript());
this.headerInput.addEventListener('input', () => this.updateHelpPreview());
- this.copyBtn.addEventListener('click', () => this.copyToClipboard());
- // Update help preview initially
+ if (this.copyBtn) {
+ this.copyBtn.addEventListener('click', () => this.copyToClipboard());
+ }
+
this.updateHelpPreview();
}
- addArgumentRow(params = '', command = '', helpText = '') {
+ 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';
@@ -46,148 +135,287 @@ class ArgumentBuilder {
const removeBtn = document.createElement('button');
removeBtn.className = 'remove-btn';
+ removeBtn.type = 'button';
removeBtn.innerHTML = '×';
- removeBtn.addEventListener('click', () => {
- row.remove();
- this.updateHelpPreview();
- });
-
- // Add input event listeners for real-time updates
- const updatePreview = () => {
- this.updateHelpPreview();
- // Add new empty row if this is the last row and has content
- const rows = this.container.querySelectorAll('.argument-row');
- const isLastRow = rows[rows.length - 1] === row;
- const hasContent = commandInput.value.trim() !== '';
-
- if (isLastRow && hasContent) {
- this.addArgumentRow();
- }
- };
-
- paramsInput.addEventListener('input', updatePreview);
- commandInput.addEventListener('input', updatePreview);
- helpTextInput.addEventListener('input', updatePreview);
row.appendChild(paramsInput);
row.appendChild(commandInput);
row.appendChild(helpTextInput);
row.appendChild(removeBtn);
- this.container.appendChild(row);
+ container.appendChild(row);
+ this.attachRowHandlers(row, container);
+
+ return row;
}
- getArguments() {
- const rows = this.container.querySelectorAll('.argument-row');
- const args = [];
+ hydrateSubcommandSection(section, presetArgs) {
+ if (!section) {
+ return;
+ }
- rows.forEach(row => {
- const inputs = row.querySelectorAll('input');
- const params = inputs[0].value.trim();
- const command = inputs[1].value.trim();
- const helpText = inputs[2].value.trim();
+ 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 (command) {
- args.push({
+ 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
- });
- }
- });
-
- return args;
+ };
+ })
+ .filter(Boolean);
}
- addSubcommand(name = '', description = '') {
- const subcommandSection = document.createElement('div');
- subcommandSection.className = 'subcommand-section';
+ getGlobalArguments() {
+ return this.getArgumentsFromContainer(this.globalContainer);
+ }
- const header = this.createSubcommandHeader(name, description);
- const argsContainer = this.createArgumentsContainer();
+ 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);
+ }
- subcommandSection.appendChild(header);
- subcommandSection.appendChild(argsContainer);
-
- this.subcommandContainer.appendChild(subcommandSection);
-
- return subcommandSection;
+ padLabel(label, width = 20) {
+ if (!label) {
+ return ''.padEnd(width, ' ');
+ }
+ if (label.length >= width) {
+ return label + ' ';
+ }
+ return label + ' '.repeat(width - label.length);
}
updateHelpPreview() {
- const header = this.headerInput.value.trim();
- const args = this.getArguments();
+ if (!this.helpContent) {
+ return;
+ }
- if (args.length === 0) {
+ 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;
}
- let helpText = '';
+ const lines = [];
if (header) {
- helpText += header + '\n\n';
+ lines.push(header, '');
}
- helpText += 'Usage: script.sh [OPTIONS]\n\n';
- helpText += 'Options:\n';
+ 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, '');
- args.forEach(arg => {
- const params = arg.params || arg.command;
- const help = arg.helpText || arg.command;
- helpText += ` ${params.padEnd(20)} ${help}\n`;
- });
+ 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`, '');
+ }
- helpText += ` ${'-h --help'.padEnd(20)} Show this help message\n`;
+ 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.");
- this.helpContent.textContent = helpText;
- }
- createSubcommandHeader(name, description) {
- const header = document.createElement('div');
- header.className = 'subcommand-header';
- header.innerHTML = `
-
-
-
-
- `;
- return header;
- }
+ 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`);
+ });
+ }
- getSubcommands() {
- const sections = this.subcommandContainer.querySelectorAll('.subcommand-section');
- return Array.from(sections).map(section => {
- const nameInput = section.querySelector('.subcommand-name');
- const descInput = section.querySelector('.subcommand-desc');
- const args = this.getArgumentsFromSection(section);
-
- return {
- name: nameInput.value.trim(),
- description: descInput.value.trim(),
- arguments: args
- };
- }).filter(sc => sc.name);
+ this.helpContent.textContent = lines.join('\n');
}
async generateScript() {
- const header = this.headerInput.value.trim();
- const args = this.getArguments();
-
- if (args.length === 0) {
- alert('Please add at least one argument');
- return;
- }
-
const payload = {
- header: this.headerInput.value.trim(),
- arguments: this.getArguments(),
+ 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', {
+ const response = await fetch('/generate/v2', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -200,19 +428,24 @@ class ArgumentBuilder {
}
const scriptCode = await response.text();
- this.generatedCode.textContent = scriptCode;
- this.outputSection.style.display = 'block';
-
- // Smooth scroll to output
- this.outputSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
+ 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() {
- const code = this.generatedCode.textContent;
+ if (!this.generatedCode || !this.copyBtn) {
+ return;
+ }
+ const code = this.generatedCode.textContent;
try {
await navigator.clipboard.writeText(code);
this.copyBtn.textContent = 'Copied!';
@@ -228,7 +461,6 @@ class ArgumentBuilder {
}
}
-// Initialize the application when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
new ArgumentBuilder();
});
diff --git a/templates/advanced.html b/templates/advanced.html
index ee60344..b14d481 100644
--- a/templates/advanced.html
+++ b/templates/advanced.html
@@ -150,6 +150,7 @@
+
+