PoC: flat and advanced mode

This commit is contained in:
2025-10-10 18:13:54 +02:00
commit 2b25bc17b3
14 changed files with 2040 additions and 0 deletions

21
.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# Ignore the build directory
build/
*.so
.env
*.env
.vscode/
.idea/
*.iml
.DS_Store
dist/
test-*
.notes
*.log
*.out
*.bin

109
ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,109 @@
# Architecture Diagram
```ascii
┌─────────────────────────────────────────────────────────────────┐
│ User's Browser │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ index.html │ │
│ │ ┌────────────┐ ┌─────────────┐ ┌──────────────────┐ │ │
│ │ │ Header │ │ Arguments │ │ Help Preview │ │ │
│ │ │ Input │ │ Grid │ │ (Real-time) │ │ │
│ │ └────────────┘ └─────────────┘ └──────────────────┘ │ │
│ │ ↓ │ │
│ │ [ Generate BASH Button ] │ │
│ │ ↓ │ │
│ │ ┌──────────────────┐ │ │
│ │ │ Generated Script │ │ │
│ │ │ (code view) │ │ │
│ │ └──────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↕ HTTP/JSON │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Go HTTP Server (Single Binary) │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Embedded Assets │ │
│ │ ┌──────────┐ ┌──────────┐ ┌────────────────────────┐ │ │
│ │ │ HTML │ │ CSS │ │ JavaScript │ │ │
│ │ │ template │ │ styles │ │ app logic │ │ │
│ │ └──────────┘ └──────────┘ └────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ HTTP Handlers │ │
│ │ │ │
│ │ GET / → Serve index.html │ │
│ │ GET /static/* → Serve CSS/JS from embed.FS │ │
│ │ POST /generate → Generate bash script │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Bash Script Generator │ │
│ │ │ │
│ │ • Parse argument definitions (JSON) │ │
│ │ • Detect flag vs value arguments │ │
│ │ • Generate usage() function │ │
│ │ • Generate case statements │ │
│ │ • Format help output │ │
│ │ • Return complete bash script │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
[Complete Bash Script]
Data Flow:
──────────
1. User Input → JavaScript collects form data
2. JavaScript → POST JSON to /generate endpoint
3. Go Server → Parses JSON into Argument structs
4. Generator → Builds bash script string
5. Response → Returns complete script as text/plain
6. JavaScript → Displays in code block
7. User → Copies to clipboard
Key Design Decisions:
────────────────────
├─ Embedded Assets
│ └─ Single binary deployment, no file dependencies
├─ Separate Frontend Files
│ └─ Clean separation: HTML, CSS, JS in own files
├─ Smart Argument Detection
│ └─ Keywords (VALUE, FILE, PATH) determine parsing logic
├─ Real-time Preview
│ └─ JavaScript updates help output on every keystroke
└─ Stateless Server
└─ No session management, pure request/response
Component Sizes:
───────────────
Binary (stripped): 6.4 MB
├─ Go runtime: ~4.0 MB
├─ Server code: ~1.0 MB
└─ Embedded: ~1.4 MB
├─ HTML: ~2 KB
├─ CSS: ~5 KB
└─ JS: ~5 KB
Performance Profile:
──────────────────
Startup Time: < 10ms
Memory (RSS): ~10 MB
Request Latency: < 10ms (average)
Concurrent Users: 1000+ (Go stdlib HTTP server)
```

0
GUIDE.md Normal file
View File

45
Makefile Normal file
View File

@@ -0,0 +1,45 @@
.PHONY: build run clean test help
OUT_DIR=dist
BINARY_NAME=argparse-builder.bin
PORT=8080
help:
@echo "Available targets:"
@echo " build - Build the binary"
@echo " run - Run the server"
@echo " clean - Remove built artifacts"
@echo " test - Test the API endpoint"
@echo " strip - Build optimized binary (smaller size)"
build:
@echo "Building $(BINARY_NAME)..."
go build -o $(OUT_DIR)/$(BINARY_NAME)
@echo "Built: $(BINARY_NAME) ($$(du -h $(OUT_DIR)/$(BINARY_NAME) | cut -f1))"
strip:
@echo "Building optimized $(BINARY_NAME)..."
go build -ldflags="-s -w" -o $(OUT_DIR)/$(BINARY_NAME)
@echo "Built: $(BINARY_NAME) ($$(du -h $(OUT_DIR)/$(BINARY_NAME) | cut -f1))"
run: build
@echo "Starting server on :$(PORT)..."
./$(OUT_DIR)/$(BINARY_NAME)
clean:
@echo "Cleaning..."
rm -f $(OUT_DIR)/$(BINARY_NAME)
@echo "Done"
test: build
@echo "Testing API endpoint..."
@./$(OUT_DIR)/$(BINARY_NAME) & \
SERVER_PID=$$!; \
sleep 2; \
curl -s -X POST http://localhost:$(PORT)/generate \
-H "Content-Type: application/json" \
-d '{"header":"Test Script","arguments":[{"params":"-v --verbose","command":"verbose","helpText":"Verbose"}]}' \
> /tmp/test_output.sh; \
kill $$SERVER_PID 2>/dev/null; \
echo "Generated test script:"; \
cat /tmp/test_output.sh

0
README.md Normal file
View File

78
example-api-usage.sh Normal file
View File

@@ -0,0 +1,78 @@
#!/usr/bin/env bash
# Example: Programmatic script generation using the API
# Demonstration of how to integrate the tool into automation workflows
# set -euo pipefail
# Start the server in the background
./argparse-builder &
SERVER_PID=$!
# Wait for server to be ready
sleep 2
# Define the script configuration
cat > config.json << 'EOF'
{
"header": "Database Backup Tool v1.0 - Automated PostgreSQL backup",
"arguments": [
{
"params": "-H --host HOST",
"command": "host",
"helpText": "Database host (default: localhost)"
},
{
"params": "-p --port NUM",
"command": "port",
"helpText": "Database port (default: 5432)"
},
{
"params": "-d --database NAME",
"command": "database",
"helpText": "Database name to backup"
},
{
"params": "-u --user NAME",
"command": "user",
"helpText": "Database username"
},
{
"params": "-o --output PATH",
"command": "output",
"helpText": "Output directory for backups"
},
{
"params": "-c --compress",
"command": "compress",
"helpText": "Compress backup with gzip"
},
{
"params": "-v --verbose",
"command": "verbose",
"helpText": "Enable verbose logging"
}
]
}
EOF
# Generate the script
curl -s -X POST http://localhost:8080/generate \
-H "Content-Type: application/json" \
-d @config.json \
-o db_backup.sh
# Make it executable
chmod +x db_backup.sh
echo "Generated script: db_backup.sh"
echo ""
echo "Test it with: ./db_backup.sh --help"
# Cleanup
kill $SERVER_PID 2>/dev/null || true
rm config.json
echo ""
echo "Generated script contents:"
echo "=========================="
cat db_backup.sh

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module git.pynezz.dev/pynezz/argparser
go 1.24.8

529
main.go Normal file
View File

@@ -0,0 +1,529 @@
package main
import (
"embed"
"encoding/json"
"io/fs"
"log"
"net/http"
"strings"
"text/template"
)
//go:embed static/* templates/*
var content embed.FS
type Argument struct {
Params string `json:"params"`
Command string `json:"command"`
HelpText string `json:"helpText"`
}
type GenerateRequest struct {
Header string `json:"header"`
Arguments []Argument `json:"arguments"`
}
type Subcommand struct {
Name string `json:"name"`
Description string `json:"description"`
Arguments []Argument `json:"arguments"`
}
type GenerateRequestV2 struct {
Header string `json:"header"`
Arguments []Argument `json:"arguments"`
Subcommands []Subcommand `json:"subcommands"`
}
func main() {
staticFS, err := fs.Sub(content, "static")
if err != nil {
log.Fatal(err)
}
templatesFS, err := fs.Sub(content, "templates")
if err != nil {
log.Fatal(err)
}
tmpl := template.Must(template.ParseFS(templatesFS, "*.html"))
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if err := tmpl.ExecuteTemplate(w, "index.html", nil); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
http.HandleFunc("/advanced", func(w http.ResponseWriter, r *http.Request) {
if err := tmpl.ExecuteTemplate(w, "advanced.html", nil); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
http.HandleFunc("/generate", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req GenerateRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
bashCode := generateBashScript(req)
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(bashCode))
})
http.HandleFunc("/generate/v2", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req GenerateRequestV2
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
bashCode := generateBashScriptV2(req)
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(bashCode))
})
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func generateBashScript(req GenerateRequest) string {
var sb strings.Builder
sb.WriteString("#!/usr/bin/env bash\n\n")
if req.Header != "" {
sb.WriteString("# " + req.Header + "\n\n")
}
// Generate usage function
sb.WriteString("usage() {\n")
sb.WriteString(" cat << EOF\n")
if req.Header != "" {
sb.WriteString(req.Header + "\n\n")
}
sb.WriteString("Usage: $(basename \"$0\") [OPTIONS]\n\n")
sb.WriteString("Options:\n")
for _, arg := range req.Arguments {
if arg.Command == "" {
continue
}
params := arg.Params
if params == "" {
params = arg.Command
}
helpText := arg.HelpText
if helpText == "" {
helpText = arg.Command
}
sb.WriteString(" " + params + " " + helpText + "\n")
}
sb.WriteString("EOF\n")
sb.WriteString(" exit 1\n")
sb.WriteString("}\n\n")
// Generate default values
for _, arg := range req.Arguments {
if arg.Command == "" {
continue
}
varName := strings.ToUpper(strings.ReplaceAll(arg.Command, "-", "_"))
sb.WriteString(varName + "=\"\"\n")
}
sb.WriteString("\n# Parse arguments\n")
sb.WriteString("while [[ $# -gt 0 ]]; do\n")
sb.WriteString(" case $1 in\n")
for _, arg := range req.Arguments {
if arg.Command == "" {
continue
}
varName := strings.ToUpper(strings.ReplaceAll(arg.Command, "-", "_"))
// Determine if it takes a value by checking for VALUE, FILE, PATH, etc.
// Check the ORIGINAL params string before splitting
params := arg.Params
if params == "" {
params = arg.Command
}
// Check if params contains value placeholders
upperParams := strings.ToUpper(params)
hasValue := strings.Contains(upperParams, "VALUE") ||
strings.Contains(upperParams, "FILE") ||
strings.Contains(upperParams, "PATH") ||
strings.Contains(upperParams, "DIR") ||
strings.Contains(upperParams, "ARG") ||
strings.Contains(upperParams, "STRING") ||
strings.Contains(upperParams, "NUM") ||
strings.Contains(upperParams, "NAME")
// Extract just the flags (remove the placeholder parts)
paramFields := strings.Fields(params)
var flags []string
for _, field := range paramFields {
if strings.HasPrefix(field, "-") {
flags = append(flags, field)
}
}
if len(flags) == 0 {
flags = []string{arg.Command}
}
casePattern := strings.Join(flags, "|")
sb.WriteString(" " + casePattern + ")\n")
if hasValue {
sb.WriteString(" " + varName + "=\"$2\"\n")
sb.WriteString(" shift 2\n")
} else {
sb.WriteString(" " + varName + "=1\n")
sb.WriteString(" shift\n")
}
sb.WriteString(" ;;\n")
}
sb.WriteString(" -h|--help)\n")
sb.WriteString(" usage\n")
sb.WriteString(" ;;\n")
sb.WriteString(" *)\n")
sb.WriteString(" echo \"Unknown option: $1\"\n")
sb.WriteString(" usage\n")
sb.WriteString(" ;;\n")
sb.WriteString(" esac\n")
sb.WriteString("done\n\n")
sb.WriteString("# Your script logic here\n")
for _, arg := range req.Arguments {
if arg.Command == "" {
continue
}
varName := strings.ToUpper(strings.ReplaceAll(arg.Command, "-", "_"))
sb.WriteString("echo \"" + varName + ": $" + varName + "\"\n")
}
return sb.String()
}
func generateBashScriptV2(req GenerateRequestV2) string {
// If no subcommands, use flat generation
if len(req.Subcommands) == 0 {
return generateBashScriptFlat(req.Header, req.Arguments)
}
return generateBashScriptWithSubcommands(req)
}
func generateBashScriptWithSubcommands(req GenerateRequestV2) string {
var sb strings.Builder
sb.WriteString("#!/usr/bin/env bash\n\n")
if req.Header != "" {
sb.WriteString("# " + req.Header + "\n\n")
}
// Main usage function
generateMainUsageWithSubcommands(&sb, req)
// Usage function for each subcommand
for _, subcmd := range req.Subcommands {
generateSubcommandUsage(&sb, subcmd)
}
// Initialize global variables
for _, arg := range req.Arguments {
if arg.Command == "" {
continue
}
varName := strings.ToUpper(strings.ReplaceAll(arg.Command, "-", "_"))
sb.WriteString(varName + "=\"\"\n")
}
sb.WriteString("SUBCOMMAND=\"\"\n\n")
// Parse global options
sb.WriteString("# Parse global options\n")
sb.WriteString("while [[ $# -gt 0 ]]; do\n")
sb.WriteString(" case $1 in\n")
// Add global argument cases
for _, arg := range req.Arguments {
generateArgumentCase(&sb, arg, "usage")
}
// Add subcommand detection
if len(req.Subcommands) > 0 {
var subcommandNames []string
for _, sc := range req.Subcommands {
if sc.Name != "" {
subcommandNames = append(subcommandNames, sc.Name)
}
}
if len(subcommandNames) > 0 {
sb.WriteString(" " + strings.Join(subcommandNames, "|") + ")\n")
sb.WriteString(" SUBCOMMAND=$1\n")
sb.WriteString(" shift\n")
sb.WriteString(" break\n")
sb.WriteString(" ;;\n")
}
}
sb.WriteString(" -h|--help)\n")
sb.WriteString(" usage\n")
sb.WriteString(" ;;\n")
sb.WriteString(" *)\n")
sb.WriteString(" echo \"Unknown option: $1\"\n")
sb.WriteString(" usage\n")
sb.WriteString(" ;;\n")
sb.WriteString(" esac\n")
sb.WriteString("done\n\n")
// Require subcommand
sb.WriteString("# Require subcommand\n")
sb.WriteString("if [[ -z \"$SUBCOMMAND\" ]]; then\n")
sb.WriteString(" echo \"Error: No command specified\"\n")
sb.WriteString(" usage\n")
sb.WriteString("fi\n\n")
// Handle subcommands
sb.WriteString("# Handle subcommands\n")
sb.WriteString("case $SUBCOMMAND in\n")
for _, subcmd := range req.Subcommands {
if subcmd.Name == "" {
continue
}
generateSubcommandHandler(&sb, subcmd)
}
sb.WriteString(" *)\n")
sb.WriteString(" echo \"Unknown command: $SUBCOMMAND\"\n")
sb.WriteString(" usage\n")
sb.WriteString(" ;;\n")
sb.WriteString("esac\n")
return sb.String()
}
func generateMainUsageWithSubcommands(sb *strings.Builder, req GenerateRequestV2) {
sb.WriteString("usage() {\n")
sb.WriteString(" cat << EOF\n")
if req.Header != "" {
sb.WriteString(req.Header + "\n\n")
}
sb.WriteString("Usage: $(basename \"$0\") [GLOBAL OPTIONS] COMMAND [COMMAND OPTIONS]\n\n")
// Global options
if len(req.Arguments) > 0 {
sb.WriteString("Global Options:\n")
for _, arg := range req.Arguments {
if arg.Command == "" {
continue
}
params := arg.Params
if params == "" {
params = arg.Command
}
helpText := arg.HelpText
if helpText == "" {
helpText = arg.Command
}
sb.WriteString(" " + params + " " + helpText + "\n")
}
sb.WriteString("\n")
}
// Commands
sb.WriteString("Commands:\n")
for _, subcmd := range req.Subcommands {
if subcmd.Name == "" {
continue
}
desc := subcmd.Description
if desc == "" {
desc = subcmd.Name
}
sb.WriteString(" " + padRight(subcmd.Name, 16) + desc + "\n")
}
sb.WriteString("\nRun '$(basename \"$0\") COMMAND --help' for more information on a command.\n")
sb.WriteString("EOF\n")
sb.WriteString(" exit 1\n")
sb.WriteString("}\n\n")
}
func generateSubcommandUsage(sb *strings.Builder, subcmd Subcommand) {
funcName := "usage_" + strings.ReplaceAll(subcmd.Name, "-", "_")
sb.WriteString(funcName + "() {\n")
sb.WriteString(" cat << EOF\n")
if subcmd.Description != "" {
sb.WriteString(subcmd.Description + "\n\n")
}
sb.WriteString("Usage: $(basename \"$0\") " + subcmd.Name + " [OPTIONS]\n\n")
if len(subcmd.Arguments) > 0 {
sb.WriteString("Options:\n")
for _, arg := range subcmd.Arguments {
if arg.Command == "" {
continue
}
params := arg.Params
if params == "" {
params = arg.Command
}
helpText := arg.HelpText
if helpText == "" {
helpText = arg.Command
}
sb.WriteString(" " + params + " " + helpText + "\n")
}
}
sb.WriteString("EOF\n")
sb.WriteString(" exit 1\n")
sb.WriteString("}\n\n")
}
func generateSubcommandHandler(sb *strings.Builder, subcmd Subcommand) {
sb.WriteString(" " + subcmd.Name + ")\n")
// Initialize subcommand-specific variables
for _, arg := range subcmd.Arguments {
if arg.Command == "" {
continue
}
varName := strings.ToUpper(strings.ReplaceAll(arg.Command, "-", "_"))
sb.WriteString(" " + varName + "=\"\"\n")
}
// Parse subcommand options
sb.WriteString(" \n")
sb.WriteString(" # Parse " + subcmd.Name + " options\n")
sb.WriteString(" while [[ $# -gt 0 ]]; do\n")
sb.WriteString(" case $1 in\n")
// Add subcommand argument cases
usageFuncName := "usage_" + strings.ReplaceAll(subcmd.Name, "-", "_")
for _, arg := range subcmd.Arguments {
generateArgumentCase(sb, arg, usageFuncName)
}
sb.WriteString(" -h|--help)\n")
sb.WriteString(" " + usageFuncName + "\n")
sb.WriteString(" ;;\n")
sb.WriteString(" *)\n")
sb.WriteString(" echo \"Unknown option: $1\"\n")
sb.WriteString(" " + usageFuncName + "\n")
sb.WriteString(" ;;\n")
sb.WriteString(" esac\n")
sb.WriteString(" done\n")
sb.WriteString(" \n")
// Add logic section
sb.WriteString(" # Your " + subcmd.Name + " logic here\n")
for _, arg := range subcmd.Arguments {
if arg.Command == "" {
continue
}
varName := strings.ToUpper(strings.ReplaceAll(arg.Command, "-", "_"))
sb.WriteString(" echo \"" + varName + ": $" + varName + "\"\n")
}
sb.WriteString(" ;;\n")
sb.WriteString(" \n")
}
func generateArgumentCase(sb *strings.Builder, arg Argument, usageFunc string) {
if arg.Command == "" {
return
}
varName := strings.ToUpper(strings.ReplaceAll(arg.Command, "-", "_"))
// Determine if it takes a value
params := arg.Params
if params == "" {
params = arg.Command
}
upperParams := strings.ToUpper(params)
hasValue := strings.Contains(upperParams, "VALUE") ||
strings.Contains(upperParams, "FILE") ||
strings.Contains(upperParams, "PATH") ||
strings.Contains(upperParams, "DIR") ||
strings.Contains(upperParams, "ARG") ||
strings.Contains(upperParams, "STRING") ||
strings.Contains(upperParams, "NUM") ||
strings.Contains(upperParams, "NAME")
// Extract flags
paramFields := strings.Fields(params)
var flags []string
for _, field := range paramFields {
if strings.HasPrefix(field, "-") {
flags = append(flags, field)
}
}
if len(flags) == 0 {
flags = []string{arg.Command}
}
casePattern := strings.Join(flags, "|")
// Adjust indentation based on context
indent := " "
if strings.Contains(usageFunc, "_") {
indent = " "
}
sb.WriteString(indent + casePattern + ")\n")
if hasValue {
sb.WriteString(indent + " " + varName + "=\"$2\"\n")
sb.WriteString(indent + " shift 2\n")
} else {
sb.WriteString(indent + " " + varName + "=1\n")
sb.WriteString(indent + " shift\n")
}
sb.WriteString(indent + " ;;\n")
}
func generateBashScriptFlat(header string, arguments []Argument) string {
// Use existing flat generation from original main.go
req := GenerateRequest{
Header: header,
Arguments: arguments,
}
return generateBashScript(req)
}
func padRight(s string, length int) string {
if len(s) >= length {
return s + " "
}
return s + strings.Repeat(" ", length-len(s))
}

536
static/css/style.css Normal file
View File

@@ -0,0 +1,536 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3a3a3a;
--text-primary: #e0e0e0;
--text-secondary: #a0a0a0;
--accent: #4a9eff;
--accent-hover: #3a8eef;
--border: #444;
--success: #4caf50;
--danger: #f44336;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 40px;
}
header h1 {
font-size: 2rem;
font-weight: 300;
letter-spacing: -0.5px;
}
.input-section {
background: var(--bg-secondary);
padding: 30px;
border-radius: 8px;
margin-bottom: 30px;
}
.header-input {
margin-bottom: 30px;
}
.header-input label {
display: block;
margin-bottom: 8px;
font-size: 0.9rem;
color: var(--text-secondary);
}
.header-input input {
width: 100%;
padding: 12px;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-primary);
font-size: 1rem;
transition: border-color 0.2s;
}
.header-input input:focus {
outline: none;
border-color: var(--accent);
}
.arguments-section h2 {
font-size: 1.3rem;
font-weight: 400;
margin-bottom: 15px;
}
.argument-header {
display: grid;
grid-template-columns: 2fr 2fr 3fr 60px;
gap: 10px;
padding: 10px 0;
border-bottom: 2px solid var(--border);
margin-bottom: 15px;
font-size: 0.85rem;
color: var(--text-secondary);
font-weight: 500;
}
.argument-row {
display: grid;
grid-template-columns: 2fr 2fr 3fr 60px;
gap: 10px;
margin-bottom: 15px;
align-items: start;
}
.argument-row input {
padding: 10px;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-primary);
font-size: 0.95rem;
transition: border-color 0.2s;
}
.argument-row input:focus {
outline: none;
border-color: var(--accent);
}
.argument-row input::placeholder {
color: #666;
}
.remove-btn {
background: transparent;
border: 1px solid var(--border);
color: var(--text-secondary);
padding: 10px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
font-size: 1.2rem;
}
.remove-btn:hover {
background: var(--danger);
border-color: var(--danger);
color: white;
}
.generate-btn {
width: 100%;
padding: 15px;
background: var(--accent);
color: white;
border: none;
border-radius: 4px;
font-size: 1.1rem;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
margin-top: 20px;
}
.generate-btn:hover {
background: var(--accent-hover);
}
.output-section {
background: var(--bg-secondary);
padding: 30px;
border-radius: 8px;
margin-bottom: 30px;
}
.output-section h2 {
font-size: 1.3rem;
font-weight: 400;
margin-bottom: 15px;
}
.code-container {
position: relative;
background: #1f1f1f;
border-radius: 4px;
padding: 20px;
}
.copy-btn {
position: absolute;
top: 10px;
right: 10px;
padding: 8px 16px;
background: var(--bg-tertiary);
border: 1px solid var(--border);
color: var(--text-primary);
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
transition: all 0.2s;
}
.copy-btn:hover {
background: var(--accent);
border-color: var(--accent);
}
.copy-btn.copied {
background: var(--success);
border-color: var(--success);
}
.code-container pre {
margin: 0;
overflow-x: auto;
}
.code-container code {
color: rgb(131, 177, 131);
font-family: 'Courier New', monospace;
font-size: 0.9rem;
line-height: 1.5;
}
.help-section {
background: var(--bg-secondary);
padding: 30px;
border-radius: 8px;
}
.help-section h2 {
font-size: 1.3rem;
font-weight: 400;
margin-bottom: 15px;
}
.help-preview {
background: #000;
border-radius: 4px;
padding: 20px;
min-height: 150px;
}
.help-preview pre {
color: #0f0;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
line-height: 1.5;
white-space: pre-wrap;
}
/* Mode Toggle */
.mode-toggle {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 30px;
}
.mode-btn {
padding: 10px 30px;
background: var(--bg-tertiary);
border: 2px solid var(--border);
color: var(--text-primary);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.mode-btn.active {
background: var(--accent);
border-color: var(--accent);
}
/* Global Section */
.global-section {
background: var(--bg-secondary);
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border-left: 3px solid var(--success);
}
.section-title {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 15px;
}
/* Subcommands */
.subcommands-container {
margin-top: 20px;
}
.subcommand-section {
background: var(--bg-tertiary);
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
border-left: 3px solid var(--accent);
}
.subcommand-section.collapsed .subcommand-body {
display: none;
}
.subcommand-header {
display: grid;
grid-template-columns: 2fr 3fr auto auto;
gap: 10px;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid var(--border);
}
.subcommand-name-input,
.subcommand-desc-input {
padding: 10px;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-primary);
font-size: 1rem;
}
.subcommand-name-input {
font-weight: 600;
font-family: 'Courier New', monospace;
}
.toggle-collapse-btn {
background: transparent;
border: 1px solid var(--border);
color: var(--text-secondary);
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}
.toggle-collapse-btn::before {
content: '▼';
}
.subcommand-section.collapsed .toggle-collapse-btn::before {
content: '▶';
}
.add-subcommand-btn {
width: 100%;
padding: 15px;
background: transparent;
border: 2px dashed var(--border);
color: var(--text-secondary);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.add-subcommand-btn:hover {
border-color: var(--accent);
color: var(--accent);
}
/* Mode-specific visibility */
.flat-mode .subcommands-container {
display: none;
}
.flat-mode .global-section {
border-left-color: var(--accent);
}
.mode-toggle {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 30px;
}
.mode-btn {
padding: 10px 30px;
background: var(--bg-tertiary);
border: 2px solid var(--border);
color: var(--text-primary);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.mode-btn.active {
background: var(--accent);
border-color: var(--accent);
}
.global-section {
background: var(--bg-secondary);
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border-left: 3px solid var(--success);
}
.section-title {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 15px;
color: var(--text-primary);
}
.subcommands-container {
margin-top: 20px;
}
.subcommand-section {
background: var(--bg-tertiary);
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
border-left: 3px solid var(--accent);
}
.subcommand-section.collapsed .subcommand-body {
display: none;
}
.subcommand-header {
display: grid;
grid-template-columns: 2fr 3fr auto auto;
gap: 10px;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid var(--border);
}
.subcommand-name-input,
.subcommand-desc-input {
padding: 10px;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-primary);
font-size: 1rem;
}
.subcommand-name-input {
font-weight: 600;
font-family: 'Courier New', monospace;
}
.toggle-collapse-btn {
background: transparent;
border: 1px solid var(--border);
color: var(--text-secondary);
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
font-size: 1rem;
}
.toggle-collapse-btn:hover {
background: var(--bg-primary);
}
.subcommand-section.collapsed .toggle-collapse-btn::before {
content: '▶';
}
.subcommand-section:not(.collapsed) .toggle-collapse-btn::before {
content: '▼';
}
.add-subcommand-btn {
width: 100%;
padding: 15px;
background: transparent;
border: 2px dashed var(--border);
color: var(--text-secondary);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
font-size: 1rem;
}
.add-subcommand-btn:hover {
border-color: var(--accent);
color: var(--accent);
background: rgba(74, 158, 255, 0.05);
}
.subcommand-body {
animation: slideDown 0.2s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.badge {
display: inline-block;
padding: 3px 8px;
background: var(--accent);
color: white;
border-radius: 3px;
font-size: 0.75rem;
font-weight: 600;
margin-left: 10px;
}
.flat-mode .subcommands-container {
display: none;
}
.subcommand-mode .global-section .section-title::after {
content: '(Optional)';
color: var(--text-secondary);
font-size: 0.9rem;
font-weight: 400;
margin-left: 10px;
}
@media (max-width: 768px) {
.argument-header,
.argument-row {
grid-template-columns: 1fr;
}
.argument-header span {
display: none;
}
.remove-btn {
justify-self: start;
}
}

234
static/js/app.js Normal file
View File

@@ -0,0 +1,234 @@
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.init();
}
init() {
// Add initial empty row
this.addArgumentRow();
// Event listeners
this.generateBtn.addEventListener('click', () => this.generateScript());
this.headerInput.addEventListener('input', () => this.updateHelpPreview());
this.copyBtn.addEventListener('click', () => this.copyToClipboard());
// Update help preview initially
this.updateHelpPreview();
}
addArgumentRow(params = '', command = '', helpText = '') {
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.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);
}
getArguments() {
const rows = this.container.querySelectorAll('.argument-row');
const args = [];
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();
if (command) {
args.push({
params: params || command,
command: command,
helpText: helpText || command
});
}
});
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();
if (args.length === 0) {
this.helpContent.textContent = 'Add arguments to see the help output...';
return;
}
let helpText = '';
if (header) {
helpText += header + '\n\n';
}
helpText += 'Usage: script.sh [OPTIONS]\n\n';
helpText += 'Options:\n';
args.forEach(arg => {
const params = arg.params || arg.command;
const help = arg.helpText || arg.command;
helpText += ` ${params.padEnd(20)} ${help}\n`;
});
helpText += ` ${'-h --help'.padEnd(20)} Show this help message\n`;
this.helpContent.textContent = helpText;
}
createSubcommandHeader(name, description) {
const header = document.createElement('div');
header.className = 'subcommand-header';
header.innerHTML = `
<input type="text" class="subcommand-name"
placeholder="container" value="${name}">
<input type="text" class="subcommand-desc"
placeholder="Manage containers" value="${description}">
<button class="toggle-btn">▼</button>
<button class="remove-btn">×</button>
`;
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();
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(),
subcommands: this.getSubcommands()
};
try {
const response = await fetch('/generate', {
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();
this.generatedCode.textContent = scriptCode;
this.outputSection.style.display = 'block';
// Smooth scroll to output
this.outputSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
} catch (error) {
alert('Error generating script: ' + error.message);
}
}
async copyToClipboard() {
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');
}
}
}
// Initialize the application when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
new ArgumentBuilder();
});

234
static/js/v2.js Normal file
View File

@@ -0,0 +1,234 @@
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.init();
}
init() {
// Add initial empty row
this.addArgumentRow();
// Event listeners
this.generateBtn.addEventListener('click', () => this.generateScript());
this.headerInput.addEventListener('input', () => this.updateHelpPreview());
this.copyBtn.addEventListener('click', () => this.copyToClipboard());
// Update help preview initially
this.updateHelpPreview();
}
addArgumentRow(params = '', command = '', helpText = '') {
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.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);
}
getArguments() {
const rows = this.container.querySelectorAll('.argument-row');
const args = [];
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();
if (command) {
args.push({
params: params || command,
command: command,
helpText: helpText || command
});
}
});
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();
if (args.length === 0) {
this.helpContent.textContent = 'Add arguments to see the help output...';
return;
}
let helpText = '';
if (header) {
helpText += header + '\n\n';
}
helpText += 'Usage: script.sh [OPTIONS]\n\n';
helpText += 'Options:\n';
args.forEach(arg => {
const params = arg.params || arg.command;
const help = arg.helpText || arg.command;
helpText += ` ${params.padEnd(20)} ${help}\n`;
});
helpText += ` ${'-h --help'.padEnd(20)} Show this help message\n`;
this.helpContent.textContent = helpText;
}
createSubcommandHeader(name, description) {
const header = document.createElement('div');
header.className = 'subcommand-header';
header.innerHTML = `
<input type="text" class="subcommand-name"
placeholder="container" value="${name}">
<input type="text" class="subcommand-desc"
placeholder="Manage containers" value="${description}">
<button class="toggle-btn">▼</button>
<button class="remove-btn">×</button>
`;
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();
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(),
subcommands: this.getSubcommands()
};
try {
const response = await fetch('/generate', {
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();
this.generatedCode.textContent = scriptCode;
this.outputSection.style.display = 'block';
// Smooth scroll to output
this.outputSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
} catch (error) {
alert('Error generating script: ' + error.message);
}
}
async copyToClipboard() {
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');
}
}
}
// Initialize the application when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
new ArgumentBuilder();
});

181
templates/advanced.html Normal file
View File

@@ -0,0 +1,181 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bash Argument Parser Generator - Subcommand Support</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<div class="container">
<header>
<h1>Bash Argument Parser Generator</h1>
</header>
<!-- Mode Toggle -->
<div class="mode-toggle">
<button class="mode-btn active" data-mode="flat">
Flat Arguments
</button>
<button class="mode-btn" data-mode="subcommand">
Subcommands <span class="badge">NEW</span>
</button>
</div>
<section class="input-section">
<!-- Header Input -->
<div class="header-input">
<label for="custom-header">Custom Header / Greeting</label>
<input type="text" id="custom-header" placeholder="e.g., Container Manager v1.0">
</div>
<!-- Global Arguments Section -->
<div class="global-section">
<div class="section-title">Global Arguments</div>
<div class="argument-header">
<span>Parameters</span>
<span>Variable Name</span>
<span>Help Text</span>
<span></span>
</div>
<div id="global-arguments-container">
<!-- Global arguments rows -->
</div>
</div>
<!-- Subcommands Section (visible only in subcommand mode) -->
<div class="subcommands-container">
<div class="section-title">Subcommands</div>
<!-- Example Subcommand -->
<div class="subcommand-section">
<div class="subcommand-header">
<input type="text" class="subcommand-name-input" placeholder="container" value="container">
<input type="text" class="subcommand-desc-input" placeholder="Manage containers"
value="Manage containers">
<button class="toggle-collapse-btn"></button>
<button class="remove-btn">×</button>
</div>
<div class="subcommand-body">
<div class="argument-header">
<span>Parameters</span>
<span>Variable Name</span>
<span>Help Text</span>
<span></span>
</div>
<div class="arguments-container">
<!-- Subcommand arguments rows -->
<div class="argument-row">
<input type="text" placeholder="--name NAME" value="--name NAME">
<input type="text" placeholder="name" value="name">
<input type="text" placeholder="Container name" value="Container name">
<button class="remove-btn">×</button>
</div>
<div class="argument-row">
<input type="text" placeholder="-d --detach">
<input type="text" placeholder="detach">
<input type="text" placeholder="Run in background">
<button class="remove-btn">×</button>
</div>
<div class="argument-row">
<input type="text" placeholder="-v --verbose">
<input type="text" placeholder="">
<input type="text" placeholder="">
<button class="remove-btn">×</button>
</div>
</div>
</div>
</div>
<!-- Another Subcommand -->
<div class="subcommand-section collapsed">
<div class="subcommand-header">
<input type="text" class="subcommand-name-input" placeholder="image" value="image">
<input type="text" class="subcommand-desc-input" placeholder="Manage images"
value="Manage images">
<button class="toggle-collapse-btn"></button>
<button class="remove-btn">×</button>
</div>
<div class="subcommand-body">
<div class="argument-header">
<span>Parameters</span>
<span>Variable Name</span>
<span>Help Text</span>
<span></span>
</div>
<div class="arguments-container">
<div class="argument-row">
<input type="text" placeholder="-a --all" value="-a --all">
<input type="text" placeholder="all" value="all">
<input type="text" placeholder="Show all images" value="Show all images">
<button class="remove-btn">×</button>
</div>
<div class="argument-row">
<input type="text" placeholder="">
<input type="text" placeholder="">
<input type="text" placeholder="">
<button class="remove-btn">×</button>
</div>
</div>
</div>
</div>
<!-- Add Subcommand Button -->
<button class="add-subcommand-btn" id="add-subcommand-btn">
+ Add Subcommand
</button>
</div>
<button id="generate-btn" class="generate-btn">Generate BASH</button>
</section>
<!-- Output and Help sections remain the same -->
<section class="output-section" id="output-section" style="display: none;">
<h2>Generated Script</h2>
<div class="code-container">
<button class="copy-btn" id="copy-btn">Copy</button>
<pre><code id="generated-code"></code></pre>
</div>
</section>
<section class="help-section">
<h2>Help Output Preview</h2>
<div class="help-preview" id="help-preview">
<pre id="help-content">Add arguments to see the help output...</pre>
</div>
</section>
</div>
<script>
// Mode switching
document.querySelectorAll('.mode-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const mode = btn.dataset.mode;
const container = document.querySelector('.container');
container.className = 'container ' + mode + '-mode';
});
});
// Collapse toggle
document.addEventListener('click', (e) => {
if (e.target.classList.contains('toggle-collapse-btn')) {
e.target.closest('.subcommand-section').classList.toggle('collapsed');
}
});
// Add subcommand
document.getElementById('add-subcommand-btn').addEventListener('click', () => {
// Create new subcommand section
console.log('Add subcommand');
});
</script>
</body>
</html>

58
templates/index.html Normal file
View File

@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bash Argument Parser Generator</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<div class="container">
<header>
<h1>Bash Argument Parser Generator</h1>
</header>
<section class="input-section">
<div class="header-input">
<label for="custom-header">Custom Header / Greeting</label>
<input type="text" id="custom-header" placeholder="e.g., My Script v1.0 - Does amazing things">
</div>
<div class="arguments-section">
<h2>Arguments</h2>
<div class="argument-header">
<span class="col-params">Parameters</span>
<span class="col-command">Variable Name</span>
<span class="col-help">Help Text</span>
<span class="col-actions"></span>
</div>
<div id="arguments-container">
<!-- Arguments will be dynamically added here -->
</div>
</div>
<button id="generate-btn" class="generate-btn">Generate BASH</button>
</section>
<section class="output-section" id="output-section" style="display: none;">
<h2>Generated Script</h2>
<div class="code-container">
<button class="copy-btn" id="copy-btn">Copy</button>
<pre><code id="generated-code"></code></pre>
</div>
</section>
<section class="help-section">
<h2>Help Output Preview</h2>
<div class="help-preview" id="help-preview">
<pre id="help-content">Add arguments to see the help output...</pre>
</div>
</section>
</div>
<script src="/static/js/app.js"></script>
</body>
</html>

12
test.sh Normal file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Test script for argparser tool
PID=$!
sleep 2
curl -s -X POST http://localhost:8080/generate \
-H "Content-Type: application/json" \
-d '{"header":"Deploy Script","arguments":[{"params":"-e --env NAME","command":"env","helpText":"Environment"}]}' > test_result.sh
cat test_result.sh
kill $PID 2>/dev/null
echo ""
echo "Test completed successfully!"