530 lines
14 KiB
Go
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))
|
|
}
|