Files
argparser/main.go
2025-10-10 18:13:54 +02:00

530 lines
14 KiB
Go

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