From 2d5370c9a6dd8ab566ec2943e20738b31a7a6606 Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Sun, 12 Oct 2025 00:11:00 -0500 Subject: restructure code --- auto.Complete.go | 508 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ bash.go | 146 ++++++++++++++++ bash.orig.go | 138 --------------- complete.go | 508 ------------------------------------------------------- exit.go | 34 +++- shell.go | 146 ---------------- smartcd.test | 20 --- structs.go | 83 +++++++++ 8 files changed, 770 insertions(+), 813 deletions(-) create mode 100644 auto.Complete.go create mode 100644 bash.go delete mode 100644 bash.orig.go delete mode 100644 complete.go delete mode 100644 shell.go delete mode 100644 smartcd.test create mode 100644 structs.go diff --git a/auto.Complete.go b/auto.Complete.go new file mode 100644 index 0000000..4de01db --- /dev/null +++ b/auto.Complete.go @@ -0,0 +1,508 @@ +package prep + +// initializes logging and command line options + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "go.wit.com/dev/alexflint/arg" + "go.wit.com/lib/config" + "go.wit.com/log" + durationpb "google.golang.org/protobuf/types/known/durationpb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" +) + +// makes a bash autocomplete file for your command +func (pb *Auto) doHandlePB() error { + homeDir, err := os.UserHomeDir() + if err != nil { + return err + } + basedir := filepath.Join(homeDir, ".cache/autocomplete") + os.MkdirAll(basedir, os.ModePerm) + fullname := filepath.Join(basedir, pb.Argname+".pb") + + all := NewAutos() + var last *Auto + data, err := os.ReadFile(fullname) + if err == nil { + err = all.Unmarshal(data) + if err == nil { + for found := range all.IterAll() { + dur := time.Since(found.Ctime.AsTime()) + pb.Duration = durationpb.New(dur) + // found.PrintDebug() + cmd := fmt.Sprintf("cmd='%s'", found.Cmd) + arglast := fmt.Sprintf("last='%s'", found.Last) + partial := fmt.Sprintf("p='%s'", found.Partial) + age := fmt.Sprintf("age='%-6.6s'", config.FormatDuration(dur)) + pb.Debugf("AUTO HISTORY: %s %-18.18s %-18.18s %-12.12s argv='%v' goargs='%v'", age, cmd, arglast, partial, found.Argv, found.Goargs) + last = found + } + } + } + + if all.Len() > 15 { + pb.Debugf("DEBUG: trim() history is over 100 len=%d vs new=%d", all.Len(), all.Len()-90) + all.Autos = all.Autos[all.Len()-10:] + // newall.Autos = all.Autos[0:10] + // for _, found := range all.Autos[0:10] { + // newall.Append(found) + // } + } + + // need this for the first time the user runs autocomplete + if last == nil { + last = new(Auto) + } + + now := time.Now() + pb.Ctime = timestamppb.New(now) + duration := time.Since(last.Ctime.AsTime()) + all.Append(pb) + + data, err = all.Marshal() + if err != nil { + return err + } + + f, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + defer f.Close() + if err != nil { + return err + } + _, err = f.Write(data) + pb.Debugf("WRITE DEBUG: write PB='%s' len(pb)=%d len(data)=%d dur=%v err=%v", fullname, all.Len(), len(data), duration, err) + return err +} + +func (pb *Auto) SubCommand(cmd ...string) { + partial := strings.Trim(pb.Partial, "'") + if pb.Debug { + if myAuto.examples == nil { + pb.Debugf("WRITE DEBUG: argv.Examples() not defined") + // log.Fprintf(os.Stderr, "\n") + // log.Fprintf(os.Stderr, "examples was nil\n") + // log.Fprintf(os.Stderr, "\n") + } else { + log.Fprintf(os.Stderr, "\n") + log.Fprintf(os.Stderr, "\n") + log.Fprintf(os.Stderr, "Examples:\n") + for _, line := range strings.Split(myAuto.examples(), "\n") { + log.Fprintf(os.Stderr, " %s\n", line) + } + // log.Fprintf(os.Stderr, "\n") + } + myAuto.pp.WriteHelpForAutocomplete(os.Stderr, os.Stdout, partial, cmd...) + // myAuto.pp.GetUsageForSubcommand(os.Stdout, os.Stderr, partial, cmd) + // myAuto.pp.GetUsageForSubcommand(os.Stdout, nil, partial, cmd) + } else { + f, _ := os.OpenFile("/tmp/outlook", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + myAuto.pp.WriteHelpForAutocomplete(f, os.Stdout, partial, cmd...) + // myAuto.pp.GetUsageForSubcommand(os.Stdout, nil, partial, cmd) + } + os.Exit(0) + // SubCommand(cmd) +} + +/* +func (pb *Auto) SubCommandShow() { + partial := strings.Trim(pb.Partial, "'") + if pb.Debug { + myAuto.pp.WriteHelpForAutocomplete(os.Stderr, os.Stdout, partial, "show", "repo") + } else { + f, _ := os.OpenFile("/tmp/outlook", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + myAuto.pp.WriteHelpForAutocomplete(f, os.Stdout, partial, "show", "repo") + } + os.Exit(0) +} + +func (pb *Auto) SubCommand2(cmd string, addmatch []string) { + partial := strings.Trim(pb.Partial, "'") + if pb.Debug { + // myAuto.pp.WriteHelpForAutocomplete(os.Stderr, os.Stdout, partial, pb.Cmd) + myAuto.pp.WriteHelpForAutocomplete(os.Stderr, os.Stdout, "", "") + // myAuto.pp.GetUsageForSubcommand(os.Stdout, os.Stderr, partial, cmd) + // myAuto.pp.GetUsageForSubcommand(os.Stdout, nil, partial, cmd) + } else { + f, _ := os.OpenFile("/tmp/outlook", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + myAuto.pp.WriteHelpForAutocomplete(f, os.Stdout, partial, pb.Cmd) + // myAuto.pp.GetUsageForSubcommand(os.Stdout, nil, partial, cmd) + } + os.Exit(0) + // SubCommand(cmd) +} +*/ + +// todo: move everything to this? +func (pb *Auto) Autocomplete3(sendthis []string) { + pb.Autocomplete2(strings.Join(sendthis, " ")) +} + +func (pb *Auto) Autocomplete2(sendthis string) { + dur := pb.Duration.AsDuration() + if dur < time.Millisecond*200 { + pb.Debug = true + /* + pb.Debugf("TODO: show extended help here '%s' '%s' %v dur=%v\n", pb.Arg0, pb.Arg1, pb.Argv, config.FormatDuration(dur)) + pb.PrintDebug() + fmt.Println(" ") + */ + if myAuto.pp == nil { + pb.Debugf("myAuto.pp == nil") + } else { + pb.Debugf("myAuto.pp != nil") + if pb.Cmd == "" { + myAuto.pp.WriteHelp(os.Stderr) + } else { + myAuto.pp.WriteHelpForSubcommand(os.Stderr, pb.Cmd) + } + } + } + + parts := strings.Split(sendthis, " ") + var all []string + for _, part := range parts { + var found bool + for _, s := range os.Args { + if s == part { + found = true + } + } + if found { + continue + } + all = append(all, part) + } + fmt.Printf("%s", strings.Join(all, " ")) + /* + if dur > time.Millisecond*200 { + if dur < time.Millisecond*800 { + // fmt.Println("a b") + fmt.Println(pb.Partial + " hello world") + } + } + */ +} + +func parseArgv(argname string) *Auto { + pb := new(Auto) + pb.Argname = argname + if len(os.Args) == 0 { + return pb + } + + if len(os.Args) > 1 && os.Args[1] == "--bash" { + pb.SetupAuto = true + return pb + } + + // HACK: set debug flag if --autodebug is passed + for _, s := range os.Args { + if s == "--autodebug" { + pb.Debug = true + } + } + + // "complete -C /usr/bin/argv forge" bash ENV values: + // * `COMP_LINE`: A string containing the entire current command line (forge first second third forth ). This is what you are looking for. + // * `COMP_POINT`: A number indicating the cursor's position (index) within the COMP_LINE. + // * `COMP_WORDS`: An array in Bash (seen differently by Go) containing each individual word on the command line. + // * `COMP_CWORD`: A number indicating the index of the word the cursor is currently on within the COMP_WORDS array. + if len(os.Args) > 1 && os.Args[1] == pb.Argname { + pb.IsAuto = true + parts := strings.Split(os.Getenv("COMP_LINE"), " ") + pb.Debug = true + log.Fprintf(os.Stderr, "\n") + pb.Debugf("MATCH Partial os.Args=%v COMP_LINE=%v", os.Args, os.Getenv("COMP_LINE")) + // log.Fprintf(os.Stdout, "jcarr") + if len(parts) > 0 { + pb.Arg0 = parts[0] + } + pb.Arg1 = os.Args[1] + os.Exit(0) + } + + if len(os.Args) > 1 && os.Args[1] == "--version" { + if myAuto.buildtime != nil { + // if binary defined buildtime() then process standard version output here + doVersion(pb) + os.Exit(0) + } + } + if len(os.Args) == 0 { + return pb + } + + if len(os.Args) == 1 { + pb.Arg0 = os.Args[0] + return pb + } + if os.Args[1] != "--auto-complete" { + pb.Cmd = os.Args[1] + return pb + } + + // should we do auto complete here? + if len(os.Args) > 1 && os.Args[1] == "--auto-complete" { + pb.IsAuto = true + pb.Arg0 = os.Args[0] + pb.Arg1 = os.Args[1] + pb.Partial = os.Args[2] + pb.Arg3 = os.Args[3] + if len(os.Args) < 5 { + // the user is doing autocomplete on the command itself + pb.Partial = "" + pb.Cmd = "" + pb.Argv = []string{""} + return pb + } + if pb.Partial == "''" { + pb.Partial = "" + } + // pb.Argv = os.Args[4:] + for _, s := range os.Args[4:] { + if s == "--autodebug" { + continue + } + tmp := strings.Trim(pb.Partial, "'") + if tmp == s { + // don't put pb.Partial into Argv + continue + } + pb.Argv = append(pb.Argv, s) + } + // set pb.Cmd to the first thing that doesn't have a '-' arg + for _, s := range pb.Argv { + if strings.HasPrefix(s, "-") { + continue + } + pb.Cmd = s + break + } + if pb.Partial == "'"+pb.Cmd+"'" { + // not really a command, it's just a partially inputed string from the user + pb.Cmd = "" + } + // try to figure out what the last argv is + for _, s := range pb.Argv { + p := fmt.Sprintf("'%s'", s) + if pb.Partial == p { + pb.Debugf("DEBUG: Last argv MATCHES Partial %s %s", s, p) + continue + } else { + pb.Debugf("DEBUG: Last argv DOES NOT MATCH Partial %s %s", s, p) + } + pb.Last = s + if strings.HasPrefix(s, "-") { + // skip args like -test --verbose when sending subcommands to go-args for help text + continue + } + pb.Goargs = append(pb.Goargs, s) + } + // if pb.Cmd == "" { + // pb.Cmd = strings.Join(pb.Argv, "BLAH") + // } + } + return pb +} + +func Bash3(dest any) *Auto { + return Bash(dest) +} + +func Bash(dest any) *Auto { + myAuto = new(AutoArgs) + findAppInfo(dest) // parses back to main() for argv info + + pb := parseArgv(myAuto.appName) + if pb.SetupAuto { + // --bash was passed. try to configure bash-completion + doBash(myAuto.appName) + os.Exit(0) + } + + myAuto.match = make(map[string]string) + myAuto.match["--gui"] = "andlabs gocui" + + if pb.Debug { + // dump debug info + pb.PrintDebug() + } + + pb.doHandlePB() // read in the history protobuf file + + // turn on debugging if duration < 200 milliseconds + dur := pb.Duration.AsDuration() + if dur < time.Millisecond*200 { + pb.Debug = true + } + + // prepart["--gui"] = "andlabs gocui" + + arg.Register(&argBash) + flags := []string{} + for _, s := range pb.Argv { + if s == "--autodebug" { + continue + } + flags = append(flags, s) + } + // pb.Debug = true + // pb.Debugf("DEBUG: MustParse(%v)", flags) + var err error + myAuto.pp, err = arg.ParseFlags(flags, dest) + if err != nil { + pb.Debugf("DEBUG: Parse error: %v", err) + } + + if myAuto.pp == nil { + pb.Debugf("DEBUG: myAuto.pp == nil after ParseFlags()") + } else { + // pb.Debugf("DEBUG: myAuto.pp is ok after ParseFlags()") + } + + if pb.IsAuto { + for key, val := range myAuto.match { + if pb.Last == key { + pb.Debugf("DEBUG: last=%s found key %s = %s", pb.Last, key, val) + pb.Autocomplete2(val) + os.Exit(0) + } else { + // pb.Debugf("DEBUG: NO MATCH last='%s' found key '%s' = %s", pb.Last, key, val) + } + } + if myAuto.autoFunc == nil { + pb.SubCommand(pb.Argv...) + } else { + myAuto.autoFunc(pb) // run the autocomplete function the user made for their application + } + if pb.Debug { + // TODO: + // check here to see if there was any completion text sent + // if not, send "reset bash newline\n" to cause bash to redraw PS1 for the user + } + os.Exit(0) + } + + arg.Register(&argBash) + myAuto.pp = arg.MustParse(dest) + return pb +} + +// makes a bash autocomplete file for your command +func doBash(argname string) { + fmt.Println(makeBashCompletionText2(argname)) + + homeDir, err := os.UserHomeDir() + if err != nil { + log.Printf("# %v\n", err) + os.Exit(0) + } + filename := filepath.Join(homeDir, ".local/share/bash-completion/completions", argname) + if config.Exists(filename) { + log.Fprintln(os.Stderr, "# file already exists", filename) + // os.Exit(0) + } + basedir, _ := filepath.Split(filename) + if !config.IsDir(basedir) { + os.MkdirAll(basedir, os.ModePerm) + } + + if f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { + f.Write([]byte(makeBashCompletionText2(argname))) + f.Close() + } else { + log.Fprintln(os.Stderr, "# open file error", filename, err) + } + os.Exit(0) +} + +func (pb *Auto) Version() string { + return pb.getVersion() +} + +func doVersion(pb *Auto) { + log.Info(pb.getVersion()) + os.Exit(0) +} + +func (pb *Auto) getVersion() string { + if myAuto.buildtime == nil { + return "app doesn't have argv.BuildVersion()" + } + BUILDTIME, VERSION := myAuto.buildtime() + + parts := strings.Split(BUILDTIME, ".") + if len(parts) == 1 { + // The input epoch seconds + // epochSeconds := int64(1758646486) + num, err := strconv.Atoi(BUILDTIME) + epochSeconds := int64(num) + if err == nil { + + // 1. Convert the epoch seconds to a time.Time object. + // time.Unix() creates the time in the UTC timezone by default. + t := time.Unix(epochSeconds, 0) + + // 2. Convert the UTC time to the computer's local timezone. + localTime := t.Local() + + // 3. Print the result. The default format is clear and includes the timezone. + // fmt.Println("Default format:", localTime) + // For a more human-friendly format, use the Format() method. + // Go uses a special reference time for formatting: Mon Jan 2 15:04:05 2006 MST + // You lay out your desired format using these specific numbers. + // formattedString := localTime.Format("Monday, January 2, 2006 at 3:04:05 PM (MST)") + // fmt.Println(" Custom format:", formattedString) + + // now := time.Now() + // dur := time.Since(localTime) + // BUILDTIME = fmt.Sprintf("%s age(%v)", localTime.String(), , config.FormatDuration(time.Since(localTime))) + stamp := log.Sprintf("Built %s Age(%s)", localTime.Format("2006-01-02 15:04"), config.FormatDuration(time.Since(localTime))) + return fmt.Sprintf("%s %s %s", pb.Argname, VERSION, stamp) + } + } + + return fmt.Sprintf("%s %s Built on %s", pb.Argname, VERSION, BUILDTIME) +} + +func StandardVersion(ARGNAME, VERSION, BUILDTIME string) string { + parts := strings.Split(BUILDTIME, ".") + if len(parts) == 1 { + // The input epoch seconds + // epochSeconds := int64(1758646486) + num, err := strconv.Atoi(BUILDTIME) + epochSeconds := int64(num) + if err == nil { + + // 1. Convert the epoch seconds to a time.Time object. + // time.Unix() creates the time in the UTC timezone by default. + t := time.Unix(epochSeconds, 0) + + // 2. Convert the UTC time to the computer's local timezone. + localTime := t.Local() + + // 3. Print the result. The default format is clear and includes the timezone. + // fmt.Println("Default format:", localTime) + // For a more human-friendly format, use the Format() method. + // Go uses a special reference time for formatting: Mon Jan 2 15:04:05 2006 MST + // You lay out your desired format using these specific numbers. + // formattedString := localTime.Format("Monday, January 2, 2006 at 3:04:05 PM (MST)") + // fmt.Println(" Custom format:", formattedString) + + // now := time.Now() + // dur := time.Since(localTime) + // BUILDTIME = fmt.Sprintf("%s age(%v)", localTime.String(), , config.FormatDuration(time.Since(localTime))) + stamp := log.Sprintf("Built %s Age(%s)", localTime.Format("2006-01-02 15:04"), config.FormatDuration(time.Since(localTime))) + return fmt.Sprintf("%s %s %s", ARGNAME, VERSION, stamp) + } + } + + return fmt.Sprintf("%s %s Built on %s", ARGNAME, VERSION, BUILDTIME) +} diff --git a/bash.go b/bash.go new file mode 100644 index 0000000..2602513 --- /dev/null +++ b/bash.go @@ -0,0 +1,146 @@ +package prep + +// initializes logging and command line options + +import ( + "fmt" + "io/ioutil" + "os" + "strings" +) + +func makeCompletionText(argname string) string { + sh := getParentProcessName() + return fmt.Sprintf("# shell might be %s", sh) +} + +func makeBashCompletionText(argname string) string { + var out string + + out += fmt.Sprintf("# add this in your bashrc:\n") + out += fmt.Sprintf("\n") + out += fmt.Sprintf("# todo: add this to go-arg as a 'hidden' go-arg option --bash\n") + out += fmt.Sprintf("#\n") + out += fmt.Sprintf("# Put the below in the file: ~/.local/share/bash-completion/completions/%s\n", argname) + out += fmt.Sprintf("#\n") + out += fmt.Sprintf("# todo: make this output work/parse with:\n") + out += fmt.Sprintf("# complete -C %s --bash go\n", argname) + out += fmt.Sprintf("\n") + out += fmt.Sprintf("_%s_complete()\n", argname) + out += fmt.Sprintf("{\n") + out += fmt.Sprintf(" # sets local to this func vars\n") + out += fmt.Sprintf(" local cur prev all\n") + out += fmt.Sprintf(" cur=${COMP_WORDS[COMP_CWORD]}\n") + out += fmt.Sprintf(" prev=${COMP_WORDS[COMP_CWORD-1]}\n") + out += fmt.Sprintf(" all=${COMP_WORDS[@]}\n") + out += fmt.Sprintf("\n") + out += fmt.Sprintf(" # this is where we generate the go-arg output\n") + out += fmt.Sprintf(" GOARGS=$(%s --auto-complete $prev \\'$cur\\' $all)\n", argname) + out += fmt.Sprintf("\n") + out += fmt.Sprintf(" # this compares the command line input from the user\n") + out += fmt.Sprintf(" # to whatever strings we output\n") + out += fmt.Sprintf(" COMPREPLY=( $(compgen -W \"$GOARGS\" -- $cur) ) # THIS WORKS\n") + out += fmt.Sprintf(" return 0\n") + out += fmt.Sprintf("}\n") + out += fmt.Sprintf("complete -F _%s_complete %s\n", argname, argname) + out += fmt.Sprintf("\n") + out += fmt.Sprintf("# copy and paste the above into your bash shell should work\n") + return out +} + +func makeBashCompletionText2(argname string) string { + var out string + + out += fmt.Sprintf("# add this in your bashrc:\n") + out += fmt.Sprintf("\n") + out += fmt.Sprintf("# todo: add this to go-arg as a 'hidden' go-arg option --bash\n") + out += fmt.Sprintf("#\n") + out += fmt.Sprintf("# Put the below in the file: ~/.local/share/bash-completion/completions/%s\n", argname) + out += fmt.Sprintf("#\n") + out += fmt.Sprintf("# todo: make this output work/parse with:\n") + out += fmt.Sprintf("# complete -C %s --bash go\n", argname) + out += fmt.Sprintf("\n") + out += fmt.Sprintf("_%s_complete()\n", argname) + out += fmt.Sprintf("{\n") + out += fmt.Sprintf(" # sets local to this func vars\n") + out += fmt.Sprintf(" local cur prev all\n") + out += fmt.Sprintf(" cur=${COMP_WORDS[COMP_CWORD]}\n") + out += fmt.Sprintf(" # prev=${COMP_WORDS[COMP_CWORD-1]}\n") + out += fmt.Sprintf(" all=${COMP_WORDS[@]}\n") + out += fmt.Sprintf("\n") + out += fmt.Sprintf(" # this is where we generate the go-arg output\n") + out += fmt.Sprintf(" GOARGS=$(%s --auto-complete \\'$cur\\' $all)\n", argname) + out += fmt.Sprintf("\n") + out += fmt.Sprintf(" # this compares the command line input from the user\n") + out += fmt.Sprintf(" # to whatever strings we output\n") + out += fmt.Sprintf(" COMPREPLY=( $(compgen -W \"$GOARGS\" -- $cur) ) # THIS WORKS\n") + out += fmt.Sprintf(" return 0\n") + out += fmt.Sprintf("}\n") + out += fmt.Sprintf("complete -F _%s_complete %s\n", argname, argname) + out += fmt.Sprintf("\n") + out += fmt.Sprintf("# copy and paste the above into your bash shell should work\n") + return out +} + +func getParentProcessName() string { + ppid := os.Getppid() + // On Linux, the command name is in /proc//comm + commPath := fmt.Sprintf("/proc/%d/comm", ppid) + content, err := ioutil.ReadFile(commPath) + if err != nil { + return "unknown" + } + // The file content has a trailing newline, so trim it. + return strings.TrimSpace(string(content)) +} + +/* + + This script will be placed in a directory in your $fpath (e.g., ~/.zsh/completions/_my-app). + +#compdef my-app + +# This function will be called by Zsh to get the completions. +_my_app_completions() { + local -a suggestions + + # Here is the key part: + # We execute our Go program with the hidden flag and capture its output. + 9 # The `(f)` flag splits the output by line into an array. + 10 suggestions=( ${(f)"$(my-app --_generate_completions)"} ) + 11 + 12 # Pass the suggestions to the Zsh completion engine. + 13 _describe 'command' suggestions + 14 } + 15 + 16 # Tell Zsh to call our function when completing for my-app + 17 _my_app_completions "$@" +*/ + +// zsh: +/* +#compdef forge + +# Zsh completion function for the 'forge' command. + +_forge_completions() { + local -a words + local -i CURRENT + + # Zsh's equivalent of Bash's COMP_WORDS and COMP_CWORD + words=("${(@)words}") + CURRENT=$CURRENT + + # Generate the completion suggestions by calling the forge command. + # The output is split into an array. + local -a suggestions + suggestions=("${(@f)$(forge --auto-complete "'${words[CURRENT]}'" "${words[@]}")}") + + # Pass the suggestions to the Zsh completion system. + _describe 'completions' suggestions +} + +# Register the function to be called for the 'forge' command. +_forge_completions "$@" + +*/ diff --git a/bash.orig.go b/bash.orig.go deleted file mode 100644 index 7d431cc..0000000 --- a/bash.orig.go +++ /dev/null @@ -1,138 +0,0 @@ -package prep - -// initializes logging and command line options - -import ( - "fmt" - "os" - "strings" - - "go.wit.com/dev/alexflint/arg" - "go.wit.com/lib/config" -) - -/* -This struct can be used with the go-arg package. These -are the generic default command line arguments for the 'GUI' package -*/ -var argBash ArgsBash - -type ArgsBash struct { - Bash bool `arg:"--bash" help:"generate bash completion"` -} - -// try this struct out (?) -var myAuto *AutoArgs - -// this is a work in progress -type AutoArgs struct { - id int // should be unique - hidden bool // don't update the toolkits when it's hidden - Auto func([]string) // the function for shell autocomplete - appName string // a good way to track the name of the binary ? - examples func() string // some examples - buildtime func() (string, string) // some examples - pp *arg.Parser // for parsing the command line args. Yay to alexf lint! - autoFunc func(*Auto) // also a function for autocomplete - match map[string]string // maps for strings -} - -// print out auto complete debugging info -func (pb *Auto) PrintDebug() { - dur := pb.Duration.AsDuration() - pb.Debugf("AUTOCOMPLETE: arg0='%s' arg1='%s' partial='%s' cmd='%s' age=%s argv=%v\n", pb.Arg0, pb.Arg1, pb.Partial, pb.Cmd, config.FormatDuration(dur), pb.Argv) -} - -func (pb *Auto) WriteHelp() { - myAuto.pp.WriteHelp(os.Stdout) -} - -func (pb *Auto) Debugf(fmts string, parts ...any) { - fmts = strings.TrimSpace(fmts) - fmts += "\n" - // NOTE: env doesn't work probably most (all?) the time because bash - // doesn't send all the ENV to autocomplete. so, trap on a "--autodebug" command line arg - if os.Getenv("AUTOCOMPLETE_VERBOSE") == "true" || pb.Debug { - if !pb.Newline { - fmt.Fprintf(os.Stderr, "\n") - pb.Newline = true - } - fmt.Fprintf(os.Stderr, fmts, parts...) - } else { - // fmt.Fprintf(os.Stderr, "NOT DOING ANYTHING\n") - } -} - -// returns the last command (is blank if the current arg is not blank) -func GetLast(cur string, argv []string) string { - if cur != "''" { - return "" - } - for _, s := range argv { - if strings.HasPrefix(s, "-") { - continue - } - return s - } - return "" -} - -// returns the name of the executable registered for shell autocomplete -func AppName() string { - return myAuto.appName -} - -/* -// makes a bash autocomplete file for your command -func doBash(argname string) { - fmt.Println(makeBashCompletionText(argname)) - - homeDir, err := os.UserHomeDir() - if err != nil { - log.Printf("%v\n", err) - os.Exit(0) - } - filename := filepath.Join(homeDir, ".local/share/bash-completion/completions", argname) - if config.Exists(filename) { - log.Println(filename, "file already exists") - os.Exit(0) - } - basedir, _ := filepath.Split(filename) - if !config.IsDir(basedir) { - os.MkdirAll(basedir, os.ModePerm) - } - - if f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { - f.Write([]byte(makeBashCompletionText(argname))) - f.Close() - log.Println("bash file created:", filename) - log.Println("restart bash") - } else { - log.Info(filename, err) - } - os.Exit(0) -} -*/ - -/* -// argname is the name of the executable -func Bash(argname string, autocomplete func([]string)) *AutoArgs { - myAuto = new(AutoArgs) - myAuto.appName = argname - - if len(os.Args) > 1 && os.Args[1] == "--bash" { - doBash(argname) - os.Exit(0) - } - - if len(os.Args) > 1 && os.Args[1] == "--auto-complete" { - autocomplete(os.Args[2:]) - os.Exit(0) - } - - arg.Register(&argBash) - - // parse go.Arg here? - return myAuto -} -*/ diff --git a/complete.go b/complete.go deleted file mode 100644 index 4de01db..0000000 --- a/complete.go +++ /dev/null @@ -1,508 +0,0 @@ -package prep - -// initializes logging and command line options - -import ( - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - "time" - - "go.wit.com/dev/alexflint/arg" - "go.wit.com/lib/config" - "go.wit.com/log" - durationpb "google.golang.org/protobuf/types/known/durationpb" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" -) - -// makes a bash autocomplete file for your command -func (pb *Auto) doHandlePB() error { - homeDir, err := os.UserHomeDir() - if err != nil { - return err - } - basedir := filepath.Join(homeDir, ".cache/autocomplete") - os.MkdirAll(basedir, os.ModePerm) - fullname := filepath.Join(basedir, pb.Argname+".pb") - - all := NewAutos() - var last *Auto - data, err := os.ReadFile(fullname) - if err == nil { - err = all.Unmarshal(data) - if err == nil { - for found := range all.IterAll() { - dur := time.Since(found.Ctime.AsTime()) - pb.Duration = durationpb.New(dur) - // found.PrintDebug() - cmd := fmt.Sprintf("cmd='%s'", found.Cmd) - arglast := fmt.Sprintf("last='%s'", found.Last) - partial := fmt.Sprintf("p='%s'", found.Partial) - age := fmt.Sprintf("age='%-6.6s'", config.FormatDuration(dur)) - pb.Debugf("AUTO HISTORY: %s %-18.18s %-18.18s %-12.12s argv='%v' goargs='%v'", age, cmd, arglast, partial, found.Argv, found.Goargs) - last = found - } - } - } - - if all.Len() > 15 { - pb.Debugf("DEBUG: trim() history is over 100 len=%d vs new=%d", all.Len(), all.Len()-90) - all.Autos = all.Autos[all.Len()-10:] - // newall.Autos = all.Autos[0:10] - // for _, found := range all.Autos[0:10] { - // newall.Append(found) - // } - } - - // need this for the first time the user runs autocomplete - if last == nil { - last = new(Auto) - } - - now := time.Now() - pb.Ctime = timestamppb.New(now) - duration := time.Since(last.Ctime.AsTime()) - all.Append(pb) - - data, err = all.Marshal() - if err != nil { - return err - } - - f, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) - defer f.Close() - if err != nil { - return err - } - _, err = f.Write(data) - pb.Debugf("WRITE DEBUG: write PB='%s' len(pb)=%d len(data)=%d dur=%v err=%v", fullname, all.Len(), len(data), duration, err) - return err -} - -func (pb *Auto) SubCommand(cmd ...string) { - partial := strings.Trim(pb.Partial, "'") - if pb.Debug { - if myAuto.examples == nil { - pb.Debugf("WRITE DEBUG: argv.Examples() not defined") - // log.Fprintf(os.Stderr, "\n") - // log.Fprintf(os.Stderr, "examples was nil\n") - // log.Fprintf(os.Stderr, "\n") - } else { - log.Fprintf(os.Stderr, "\n") - log.Fprintf(os.Stderr, "\n") - log.Fprintf(os.Stderr, "Examples:\n") - for _, line := range strings.Split(myAuto.examples(), "\n") { - log.Fprintf(os.Stderr, " %s\n", line) - } - // log.Fprintf(os.Stderr, "\n") - } - myAuto.pp.WriteHelpForAutocomplete(os.Stderr, os.Stdout, partial, cmd...) - // myAuto.pp.GetUsageForSubcommand(os.Stdout, os.Stderr, partial, cmd) - // myAuto.pp.GetUsageForSubcommand(os.Stdout, nil, partial, cmd) - } else { - f, _ := os.OpenFile("/tmp/outlook", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - myAuto.pp.WriteHelpForAutocomplete(f, os.Stdout, partial, cmd...) - // myAuto.pp.GetUsageForSubcommand(os.Stdout, nil, partial, cmd) - } - os.Exit(0) - // SubCommand(cmd) -} - -/* -func (pb *Auto) SubCommandShow() { - partial := strings.Trim(pb.Partial, "'") - if pb.Debug { - myAuto.pp.WriteHelpForAutocomplete(os.Stderr, os.Stdout, partial, "show", "repo") - } else { - f, _ := os.OpenFile("/tmp/outlook", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - myAuto.pp.WriteHelpForAutocomplete(f, os.Stdout, partial, "show", "repo") - } - os.Exit(0) -} - -func (pb *Auto) SubCommand2(cmd string, addmatch []string) { - partial := strings.Trim(pb.Partial, "'") - if pb.Debug { - // myAuto.pp.WriteHelpForAutocomplete(os.Stderr, os.Stdout, partial, pb.Cmd) - myAuto.pp.WriteHelpForAutocomplete(os.Stderr, os.Stdout, "", "") - // myAuto.pp.GetUsageForSubcommand(os.Stdout, os.Stderr, partial, cmd) - // myAuto.pp.GetUsageForSubcommand(os.Stdout, nil, partial, cmd) - } else { - f, _ := os.OpenFile("/tmp/outlook", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - myAuto.pp.WriteHelpForAutocomplete(f, os.Stdout, partial, pb.Cmd) - // myAuto.pp.GetUsageForSubcommand(os.Stdout, nil, partial, cmd) - } - os.Exit(0) - // SubCommand(cmd) -} -*/ - -// todo: move everything to this? -func (pb *Auto) Autocomplete3(sendthis []string) { - pb.Autocomplete2(strings.Join(sendthis, " ")) -} - -func (pb *Auto) Autocomplete2(sendthis string) { - dur := pb.Duration.AsDuration() - if dur < time.Millisecond*200 { - pb.Debug = true - /* - pb.Debugf("TODO: show extended help here '%s' '%s' %v dur=%v\n", pb.Arg0, pb.Arg1, pb.Argv, config.FormatDuration(dur)) - pb.PrintDebug() - fmt.Println(" ") - */ - if myAuto.pp == nil { - pb.Debugf("myAuto.pp == nil") - } else { - pb.Debugf("myAuto.pp != nil") - if pb.Cmd == "" { - myAuto.pp.WriteHelp(os.Stderr) - } else { - myAuto.pp.WriteHelpForSubcommand(os.Stderr, pb.Cmd) - } - } - } - - parts := strings.Split(sendthis, " ") - var all []string - for _, part := range parts { - var found bool - for _, s := range os.Args { - if s == part { - found = true - } - } - if found { - continue - } - all = append(all, part) - } - fmt.Printf("%s", strings.Join(all, " ")) - /* - if dur > time.Millisecond*200 { - if dur < time.Millisecond*800 { - // fmt.Println("a b") - fmt.Println(pb.Partial + " hello world") - } - } - */ -} - -func parseArgv(argname string) *Auto { - pb := new(Auto) - pb.Argname = argname - if len(os.Args) == 0 { - return pb - } - - if len(os.Args) > 1 && os.Args[1] == "--bash" { - pb.SetupAuto = true - return pb - } - - // HACK: set debug flag if --autodebug is passed - for _, s := range os.Args { - if s == "--autodebug" { - pb.Debug = true - } - } - - // "complete -C /usr/bin/argv forge" bash ENV values: - // * `COMP_LINE`: A string containing the entire current command line (forge first second third forth ). This is what you are looking for. - // * `COMP_POINT`: A number indicating the cursor's position (index) within the COMP_LINE. - // * `COMP_WORDS`: An array in Bash (seen differently by Go) containing each individual word on the command line. - // * `COMP_CWORD`: A number indicating the index of the word the cursor is currently on within the COMP_WORDS array. - if len(os.Args) > 1 && os.Args[1] == pb.Argname { - pb.IsAuto = true - parts := strings.Split(os.Getenv("COMP_LINE"), " ") - pb.Debug = true - log.Fprintf(os.Stderr, "\n") - pb.Debugf("MATCH Partial os.Args=%v COMP_LINE=%v", os.Args, os.Getenv("COMP_LINE")) - // log.Fprintf(os.Stdout, "jcarr") - if len(parts) > 0 { - pb.Arg0 = parts[0] - } - pb.Arg1 = os.Args[1] - os.Exit(0) - } - - if len(os.Args) > 1 && os.Args[1] == "--version" { - if myAuto.buildtime != nil { - // if binary defined buildtime() then process standard version output here - doVersion(pb) - os.Exit(0) - } - } - if len(os.Args) == 0 { - return pb - } - - if len(os.Args) == 1 { - pb.Arg0 = os.Args[0] - return pb - } - if os.Args[1] != "--auto-complete" { - pb.Cmd = os.Args[1] - return pb - } - - // should we do auto complete here? - if len(os.Args) > 1 && os.Args[1] == "--auto-complete" { - pb.IsAuto = true - pb.Arg0 = os.Args[0] - pb.Arg1 = os.Args[1] - pb.Partial = os.Args[2] - pb.Arg3 = os.Args[3] - if len(os.Args) < 5 { - // the user is doing autocomplete on the command itself - pb.Partial = "" - pb.Cmd = "" - pb.Argv = []string{""} - return pb - } - if pb.Partial == "''" { - pb.Partial = "" - } - // pb.Argv = os.Args[4:] - for _, s := range os.Args[4:] { - if s == "--autodebug" { - continue - } - tmp := strings.Trim(pb.Partial, "'") - if tmp == s { - // don't put pb.Partial into Argv - continue - } - pb.Argv = append(pb.Argv, s) - } - // set pb.Cmd to the first thing that doesn't have a '-' arg - for _, s := range pb.Argv { - if strings.HasPrefix(s, "-") { - continue - } - pb.Cmd = s - break - } - if pb.Partial == "'"+pb.Cmd+"'" { - // not really a command, it's just a partially inputed string from the user - pb.Cmd = "" - } - // try to figure out what the last argv is - for _, s := range pb.Argv { - p := fmt.Sprintf("'%s'", s) - if pb.Partial == p { - pb.Debugf("DEBUG: Last argv MATCHES Partial %s %s", s, p) - continue - } else { - pb.Debugf("DEBUG: Last argv DOES NOT MATCH Partial %s %s", s, p) - } - pb.Last = s - if strings.HasPrefix(s, "-") { - // skip args like -test --verbose when sending subcommands to go-args for help text - continue - } - pb.Goargs = append(pb.Goargs, s) - } - // if pb.Cmd == "" { - // pb.Cmd = strings.Join(pb.Argv, "BLAH") - // } - } - return pb -} - -func Bash3(dest any) *Auto { - return Bash(dest) -} - -func Bash(dest any) *Auto { - myAuto = new(AutoArgs) - findAppInfo(dest) // parses back to main() for argv info - - pb := parseArgv(myAuto.appName) - if pb.SetupAuto { - // --bash was passed. try to configure bash-completion - doBash(myAuto.appName) - os.Exit(0) - } - - myAuto.match = make(map[string]string) - myAuto.match["--gui"] = "andlabs gocui" - - if pb.Debug { - // dump debug info - pb.PrintDebug() - } - - pb.doHandlePB() // read in the history protobuf file - - // turn on debugging if duration < 200 milliseconds - dur := pb.Duration.AsDuration() - if dur < time.Millisecond*200 { - pb.Debug = true - } - - // prepart["--gui"] = "andlabs gocui" - - arg.Register(&argBash) - flags := []string{} - for _, s := range pb.Argv { - if s == "--autodebug" { - continue - } - flags = append(flags, s) - } - // pb.Debug = true - // pb.Debugf("DEBUG: MustParse(%v)", flags) - var err error - myAuto.pp, err = arg.ParseFlags(flags, dest) - if err != nil { - pb.Debugf("DEBUG: Parse error: %v", err) - } - - if myAuto.pp == nil { - pb.Debugf("DEBUG: myAuto.pp == nil after ParseFlags()") - } else { - // pb.Debugf("DEBUG: myAuto.pp is ok after ParseFlags()") - } - - if pb.IsAuto { - for key, val := range myAuto.match { - if pb.Last == key { - pb.Debugf("DEBUG: last=%s found key %s = %s", pb.Last, key, val) - pb.Autocomplete2(val) - os.Exit(0) - } else { - // pb.Debugf("DEBUG: NO MATCH last='%s' found key '%s' = %s", pb.Last, key, val) - } - } - if myAuto.autoFunc == nil { - pb.SubCommand(pb.Argv...) - } else { - myAuto.autoFunc(pb) // run the autocomplete function the user made for their application - } - if pb.Debug { - // TODO: - // check here to see if there was any completion text sent - // if not, send "reset bash newline\n" to cause bash to redraw PS1 for the user - } - os.Exit(0) - } - - arg.Register(&argBash) - myAuto.pp = arg.MustParse(dest) - return pb -} - -// makes a bash autocomplete file for your command -func doBash(argname string) { - fmt.Println(makeBashCompletionText2(argname)) - - homeDir, err := os.UserHomeDir() - if err != nil { - log.Printf("# %v\n", err) - os.Exit(0) - } - filename := filepath.Join(homeDir, ".local/share/bash-completion/completions", argname) - if config.Exists(filename) { - log.Fprintln(os.Stderr, "# file already exists", filename) - // os.Exit(0) - } - basedir, _ := filepath.Split(filename) - if !config.IsDir(basedir) { - os.MkdirAll(basedir, os.ModePerm) - } - - if f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err == nil { - f.Write([]byte(makeBashCompletionText2(argname))) - f.Close() - } else { - log.Fprintln(os.Stderr, "# open file error", filename, err) - } - os.Exit(0) -} - -func (pb *Auto) Version() string { - return pb.getVersion() -} - -func doVersion(pb *Auto) { - log.Info(pb.getVersion()) - os.Exit(0) -} - -func (pb *Auto) getVersion() string { - if myAuto.buildtime == nil { - return "app doesn't have argv.BuildVersion()" - } - BUILDTIME, VERSION := myAuto.buildtime() - - parts := strings.Split(BUILDTIME, ".") - if len(parts) == 1 { - // The input epoch seconds - // epochSeconds := int64(1758646486) - num, err := strconv.Atoi(BUILDTIME) - epochSeconds := int64(num) - if err == nil { - - // 1. Convert the epoch seconds to a time.Time object. - // time.Unix() creates the time in the UTC timezone by default. - t := time.Unix(epochSeconds, 0) - - // 2. Convert the UTC time to the computer's local timezone. - localTime := t.Local() - - // 3. Print the result. The default format is clear and includes the timezone. - // fmt.Println("Default format:", localTime) - // For a more human-friendly format, use the Format() method. - // Go uses a special reference time for formatting: Mon Jan 2 15:04:05 2006 MST - // You lay out your desired format using these specific numbers. - // formattedString := localTime.Format("Monday, January 2, 2006 at 3:04:05 PM (MST)") - // fmt.Println(" Custom format:", formattedString) - - // now := time.Now() - // dur := time.Since(localTime) - // BUILDTIME = fmt.Sprintf("%s age(%v)", localTime.String(), , config.FormatDuration(time.Since(localTime))) - stamp := log.Sprintf("Built %s Age(%s)", localTime.Format("2006-01-02 15:04"), config.FormatDuration(time.Since(localTime))) - return fmt.Sprintf("%s %s %s", pb.Argname, VERSION, stamp) - } - } - - return fmt.Sprintf("%s %s Built on %s", pb.Argname, VERSION, BUILDTIME) -} - -func StandardVersion(ARGNAME, VERSION, BUILDTIME string) string { - parts := strings.Split(BUILDTIME, ".") - if len(parts) == 1 { - // The input epoch seconds - // epochSeconds := int64(1758646486) - num, err := strconv.Atoi(BUILDTIME) - epochSeconds := int64(num) - if err == nil { - - // 1. Convert the epoch seconds to a time.Time object. - // time.Unix() creates the time in the UTC timezone by default. - t := time.Unix(epochSeconds, 0) - - // 2. Convert the UTC time to the computer's local timezone. - localTime := t.Local() - - // 3. Print the result. The default format is clear and includes the timezone. - // fmt.Println("Default format:", localTime) - // For a more human-friendly format, use the Format() method. - // Go uses a special reference time for formatting: Mon Jan 2 15:04:05 2006 MST - // You lay out your desired format using these specific numbers. - // formattedString := localTime.Format("Monday, January 2, 2006 at 3:04:05 PM (MST)") - // fmt.Println(" Custom format:", formattedString) - - // now := time.Now() - // dur := time.Since(localTime) - // BUILDTIME = fmt.Sprintf("%s age(%v)", localTime.String(), , config.FormatDuration(time.Since(localTime))) - stamp := log.Sprintf("Built %s Age(%s)", localTime.Format("2006-01-02 15:04"), config.FormatDuration(time.Since(localTime))) - return fmt.Sprintf("%s %s %s", ARGNAME, VERSION, stamp) - } - } - - return fmt.Sprintf("%s %s Built on %s", ARGNAME, VERSION, BUILDTIME) -} diff --git a/exit.go b/exit.go index c6390b5..5a6c5a1 100644 --- a/exit.go +++ b/exit.go @@ -1,6 +1,7 @@ package prep import ( + "fmt" "os" "time" @@ -8,15 +9,21 @@ import ( "go.wit.com/log" ) -// initializes logging and command line options +// since we know when the command starts (duh, this parses os.Args) +// this is a convienent way to provide a standard exit format back +// to the shell that also has built in timing! + +// also, it supports a custom Exit() back to your application func (pb *Auto) GoodExit(msg string) { + go ExitWatchdog() dur := time.Since(pb.Ctime.AsTime()) log.Infof("%s: %s (%s)\n", pb.Argname, msg, config.FormatDuration(dur)) os.Exit(0) } func (pb *Auto) BadExit(msg string, err error) { + go ExitWatchdog() if err != nil { log.Info(err) } @@ -24,3 +31,28 @@ func (pb *Auto) BadExit(msg string, err error) { log.Infof("%s: %s (%s)\n", pb.Argname, msg, config.FormatDuration(dur)) os.Exit(-1) } + +// this code doesn't need to be this complicated. I put it here as reference code for myself so I could remember where it is. +func ExitWatchdog() { + dog := time.NewTicker(time.Second) + defer dog.Stop() + dogchan := make(chan bool) + /* + // this example would exit/destroy the ticker in 10 seconds + go func() { + time.Sleep(10 * time.Second) + done <- true + }() + */ + for { + select { + case <-dogchan: + fmt.Println("Done!") + return + case t := <-dog.C: + _ = t + log.Info("argv.Exit() watchdog: stalled in", myAuto.appName+".Exit()") + // h.Scan() + } + } +} diff --git a/shell.go b/shell.go deleted file mode 100644 index 2602513..0000000 --- a/shell.go +++ /dev/null @@ -1,146 +0,0 @@ -package prep - -// initializes logging and command line options - -import ( - "fmt" - "io/ioutil" - "os" - "strings" -) - -func makeCompletionText(argname string) string { - sh := getParentProcessName() - return fmt.Sprintf("# shell might be %s", sh) -} - -func makeBashCompletionText(argname string) string { - var out string - - out += fmt.Sprintf("# add this in your bashrc:\n") - out += fmt.Sprintf("\n") - out += fmt.Sprintf("# todo: add this to go-arg as a 'hidden' go-arg option --bash\n") - out += fmt.Sprintf("#\n") - out += fmt.Sprintf("# Put the below in the file: ~/.local/share/bash-completion/completions/%s\n", argname) - out += fmt.Sprintf("#\n") - out += fmt.Sprintf("# todo: make this output work/parse with:\n") - out += fmt.Sprintf("# complete -C %s --bash go\n", argname) - out += fmt.Sprintf("\n") - out += fmt.Sprintf("_%s_complete()\n", argname) - out += fmt.Sprintf("{\n") - out += fmt.Sprintf(" # sets local to this func vars\n") - out += fmt.Sprintf(" local cur prev all\n") - out += fmt.Sprintf(" cur=${COMP_WORDS[COMP_CWORD]}\n") - out += fmt.Sprintf(" prev=${COMP_WORDS[COMP_CWORD-1]}\n") - out += fmt.Sprintf(" all=${COMP_WORDS[@]}\n") - out += fmt.Sprintf("\n") - out += fmt.Sprintf(" # this is where we generate the go-arg output\n") - out += fmt.Sprintf(" GOARGS=$(%s --auto-complete $prev \\'$cur\\' $all)\n", argname) - out += fmt.Sprintf("\n") - out += fmt.Sprintf(" # this compares the command line input from the user\n") - out += fmt.Sprintf(" # to whatever strings we output\n") - out += fmt.Sprintf(" COMPREPLY=( $(compgen -W \"$GOARGS\" -- $cur) ) # THIS WORKS\n") - out += fmt.Sprintf(" return 0\n") - out += fmt.Sprintf("}\n") - out += fmt.Sprintf("complete -F _%s_complete %s\n", argname, argname) - out += fmt.Sprintf("\n") - out += fmt.Sprintf("# copy and paste the above into your bash shell should work\n") - return out -} - -func makeBashCompletionText2(argname string) string { - var out string - - out += fmt.Sprintf("# add this in your bashrc:\n") - out += fmt.Sprintf("\n") - out += fmt.Sprintf("# todo: add this to go-arg as a 'hidden' go-arg option --bash\n") - out += fmt.Sprintf("#\n") - out += fmt.Sprintf("# Put the below in the file: ~/.local/share/bash-completion/completions/%s\n", argname) - out += fmt.Sprintf("#\n") - out += fmt.Sprintf("# todo: make this output work/parse with:\n") - out += fmt.Sprintf("# complete -C %s --bash go\n", argname) - out += fmt.Sprintf("\n") - out += fmt.Sprintf("_%s_complete()\n", argname) - out += fmt.Sprintf("{\n") - out += fmt.Sprintf(" # sets local to this func vars\n") - out += fmt.Sprintf(" local cur prev all\n") - out += fmt.Sprintf(" cur=${COMP_WORDS[COMP_CWORD]}\n") - out += fmt.Sprintf(" # prev=${COMP_WORDS[COMP_CWORD-1]}\n") - out += fmt.Sprintf(" all=${COMP_WORDS[@]}\n") - out += fmt.Sprintf("\n") - out += fmt.Sprintf(" # this is where we generate the go-arg output\n") - out += fmt.Sprintf(" GOARGS=$(%s --auto-complete \\'$cur\\' $all)\n", argname) - out += fmt.Sprintf("\n") - out += fmt.Sprintf(" # this compares the command line input from the user\n") - out += fmt.Sprintf(" # to whatever strings we output\n") - out += fmt.Sprintf(" COMPREPLY=( $(compgen -W \"$GOARGS\" -- $cur) ) # THIS WORKS\n") - out += fmt.Sprintf(" return 0\n") - out += fmt.Sprintf("}\n") - out += fmt.Sprintf("complete -F _%s_complete %s\n", argname, argname) - out += fmt.Sprintf("\n") - out += fmt.Sprintf("# copy and paste the above into your bash shell should work\n") - return out -} - -func getParentProcessName() string { - ppid := os.Getppid() - // On Linux, the command name is in /proc//comm - commPath := fmt.Sprintf("/proc/%d/comm", ppid) - content, err := ioutil.ReadFile(commPath) - if err != nil { - return "unknown" - } - // The file content has a trailing newline, so trim it. - return strings.TrimSpace(string(content)) -} - -/* - - This script will be placed in a directory in your $fpath (e.g., ~/.zsh/completions/_my-app). - -#compdef my-app - -# This function will be called by Zsh to get the completions. -_my_app_completions() { - local -a suggestions - - # Here is the key part: - # We execute our Go program with the hidden flag and capture its output. - 9 # The `(f)` flag splits the output by line into an array. - 10 suggestions=( ${(f)"$(my-app --_generate_completions)"} ) - 11 - 12 # Pass the suggestions to the Zsh completion engine. - 13 _describe 'command' suggestions - 14 } - 15 - 16 # Tell Zsh to call our function when completing for my-app - 17 _my_app_completions "$@" -*/ - -// zsh: -/* -#compdef forge - -# Zsh completion function for the 'forge' command. - -_forge_completions() { - local -a words - local -i CURRENT - - # Zsh's equivalent of Bash's COMP_WORDS and COMP_CWORD - words=("${(@)words}") - CURRENT=$CURRENT - - # Generate the completion suggestions by calling the forge command. - # The output is split into an array. - local -a suggestions - suggestions=("${(@f)$(forge --auto-complete "'${words[CURRENT]}'" "${words[@]}")}") - - # Pass the suggestions to the Zsh completion system. - _describe 'completions' suggestions -} - -# Register the function to be called for the 'forge' command. -_forge_completions "$@" - -*/ diff --git a/smartcd.test b/smartcd.test deleted file mode 100644 index d476814..0000000 --- a/smartcd.test +++ /dev/null @@ -1,20 +0,0 @@ -# could work like 'z' ? - -_cd_complete() -{ - # sets local to this func vars - local cur prev all - cur=${COMP_WORDS[COMP_CWORD]} - # prev=${COMP_WORDS[COMP_CWORD-1]} - all=${COMP_WORDS[@]} - - # this is where we generate the go-arg output - GOARGS=$(smartcd --auto-complete \'$cur\' $all) - - # this compares the command line input from the user - # to whatever strings we output - COMPREPLY=( $(compgen -W "$GOARGS" -- $cur) ) # THIS WORKS - return 0 -} -complete -F _cd_complete cd - diff --git a/structs.go b/structs.go new file mode 100644 index 0000000..1f274f1 --- /dev/null +++ b/structs.go @@ -0,0 +1,83 @@ +package prep + +// initializes logging and command line options + +import ( + "fmt" + "os" + "strings" + + "go.wit.com/dev/alexflint/arg" + "go.wit.com/lib/config" +) + +/* +This struct can be used with the go-arg package. These +are the generic default command line arguments for the 'GUI' package +*/ +var argBash ArgsBash + +type ArgsBash struct { + Bash bool `arg:"--bash" help:"generate bash completion"` +} + +// try this struct out (?) +var myAuto *AutoArgs + +// this is a work in progress +type AutoArgs struct { + id int // should be unique + hidden bool // don't update the toolkits when it's hidden + Auto func([]string) // the function for shell autocomplete + appName string // a good way to track the name of the binary ? + examples func() string // some examples + buildtime func() (string, string) // some examples + pp *arg.Parser // for parsing the command line args. Yay to alexf lint! + autoFunc func(*Auto) // also a function for autocomplete + match map[string]string // maps for strings +} + +// print out auto complete debugging info +func (pb *Auto) PrintDebug() { + dur := pb.Duration.AsDuration() + pb.Debugf("AUTOCOMPLETE: arg0='%s' arg1='%s' partial='%s' cmd='%s' age=%s argv=%v\n", pb.Arg0, pb.Arg1, pb.Partial, pb.Cmd, config.FormatDuration(dur), pb.Argv) +} + +func (pb *Auto) WriteHelp() { + myAuto.pp.WriteHelp(os.Stdout) +} + +func (pb *Auto) Debugf(fmts string, parts ...any) { + fmts = strings.TrimSpace(fmts) + fmts += "\n" + // NOTE: env doesn't work probably most (all?) the time because bash + // doesn't send all the ENV to autocomplete. so, trap on a "--autodebug" command line arg + if os.Getenv("AUTOCOMPLETE_VERBOSE") == "true" || pb.Debug { + if !pb.Newline { + fmt.Fprintf(os.Stderr, "\n") + pb.Newline = true + } + fmt.Fprintf(os.Stderr, fmts, parts...) + } else { + // fmt.Fprintf(os.Stderr, "NOT DOING ANYTHING\n") + } +} + +// returns the last command (is blank if the current arg is not blank) +func GetLast(cur string, argv []string) string { + if cur != "''" { + return "" + } + for _, s := range argv { + if strings.HasPrefix(s, "-") { + continue + } + return s + } + return "" +} + +// returns the name of the executable registered for shell autocomplete +func AppName() string { + return myAuto.appName +} -- cgit v1.2.3