PoC: flat and advanced mode
This commit is contained in:
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal 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
109
ARCHITECTURE.md
Normal 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)
|
||||||
|
```
|
45
Makefile
Normal file
45
Makefile
Normal 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
|
78
example-api-usage.sh
Normal file
78
example-api-usage.sh
Normal 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
|
529
main.go
Normal file
529
main.go
Normal 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
536
static/css/style.css
Normal 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
234
static/js/app.js
Normal 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
234
static/js/v2.js
Normal 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
181
templates/advanced.html
Normal 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
58
templates/index.html
Normal 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
12
test.sh
Normal 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!"
|
Reference in New Issue
Block a user