package argvpb // This is where the actual autocomplete happens // lots of the fun magic is in here import ( "errors" "fmt" "os" "strings" "time" "go.wit.com/lib/cobol" "go.wit.com/lib/config" "go.wit.com/lib/env" durationpb "google.golang.org/protobuf/types/known/durationpb" ) // This function behaves oddly. It works in several different ways. It can: // - Initialize a protobuf and returns it to the application // - Help a user generate shell completion support & os.Exit // - figures out what to print to Stdout & Stderr and then does os.Exit() func Autocomplete() *Argv { // parses os.Args into the protobuf PB.parseOsArgs() // initializes the lib/env library env.Init(PB.AppInfo.APPNAME, PB.AppInfo.VERSION, cobol.Time(PB.AppInfo.BUILDTIME), PB.Real, GoodExit, BadExit) // user is trying to setup bash or zsh autocomplete // --bash or --zsh is the first os.Args if me.setupAuto { // --bash or --zsh was passed. try to configure bash-completion MakeAutocompleteFiles(PB.AppInfo.APPNAME) // never forget to exit here or you will hate yourself and the choices you have made saveAndExit() } // open the argv cache history file to figure out the timing examineArgvHistory() // try to register bash args for go-args // arg.Register(&ArgvBash) // todo: figure this out if me.guiFunc != nil { // register gui args me.guiFunc() // fmt.Println("gui init") } else { // fmt.Println("no gui init") } env.SetGlobal("argv", "real", fmt.Sprintf("%v", PB.Real)) for _, a := range PB.Real { if strings.HasPrefix(a, "--") { env.SetGlobal("argv", a, "true") } } if !me.isAuto { // not autocompleting. return to the application me.Err = errors.Join(me.Err, me.mustParseFunc()) fmt.Fprintf(Stddbg, "did mustParseFunc(). heading to forge. err(%v)\n", me.Err) PB.ErrCounter = 0 PB.OutCounter = 0 // save the pb & history. todo: remove this what things work. slow. disk i/o savePB() return PB } fmt.Fprintf(Stddbg, "heading to autocomplete. err(%v)\n", me.Err) prepareStdout() // print to Stdout & Stderr saveAndExit() // never gets here return PB } // doAutocomplete(): print what is needed to Stdout & Stderr, then exit // // everything sent to STDOUT and STDERR matters past this point // any "junk" output, hidden fmt(), print() or log() statements are bad func prepareStdout() { if me.debug { // add an initial debug line PB.printDebug(me.last.GetCmd()) } if strings.HasPrefix(PB.Partial, "'--argv") { fmt.Fprintf(Stdout, " --argvdebug --argvhelp") PB.Stddbg += fmt.Sprintln("argv override") me.debug = true return } // highjack "--gui" if len(PB.Real) > 1 { lastarg := PB.Real[len(PB.Real)-1] // this is a work in progress if lastarg == "--gui" { PB.debugf("DEBUG: real=(%v) found --gui", PB.Real) PB.Stdout += " andlabs gogui" return } else { // PB.debugf("DEBUG: NO MATCH last='%s' found key '%s' = %s", last, key, val) } } if me.Err == nil { fmt.Fprintf(Stddbg, "got to the end of Argv ok\n") } else { fmt.Fprintf(Stddbg, "got to the end of Argv with err(%v)\n", me.Err) } return } func savePB() { // save now. this is near the end probably dur := time.Since(PB.Ctime.AsTime()) PB.ArgvDuration = durationpb.New(dur) // track how long autocomplete took me.all.Append(PB) // npb := new(Argv) // npb.Uuid = uuid.New().String() // me.all.Append(npb) me.all.Save() } func saveAndExit() { if me.debug { me.all.printHistory("HIST") fmt.Fprintf(Stddbg, "me.debug=true pb.Stdout=(%v)\n", strings.TrimSpace(PB.Stdout)) printStddbg() } PB.ErrCounter += 1 if PB.ErrCounter < 3 { printStderr() } else { if PB.ErrCounter > 10 { PB.ErrCounter = 0 } } PB.OutCounter += 1 printStdout() if PB.OutCounter < 3 { } else { if PB.OutCounter > 5 { PB.OutCounter = 0 } } //if PB.Fast == 0 { // PB.OutCounter = 0 //} savePB() os.Exit(0) } // sets me.last func examineArgvHistory() { me.all = NewArgvs() // loads the argv autocomplete history file me.Err = config.ForceCreateCacheDirPB(me.all, "argv", PB.AppInfo.APPNAME) if me.Err != nil { // // debug this // me.debug = true PB.Stddbg += fmt.Sprintf("config.CreateCacheDirPB() err(%v)\n", me.Err) PB.Stddbg += fmt.Sprintf("argvpb.Load() history file failed") printStderr() printStddbg() return } if me.debug { // use this if you are having trouble debugging this code // me.all.printHistory("EARLY") } // roll the autocomplete file maxsize := 17 trim := 10 if me.all.Len() > maxsize { PB.debugf("DEBUG: trim() history is over %d len=%d vs new=%d", maxsize, me.all.Len(), me.all.Len()-trim) me.all.Argvs = me.all.Argvs[me.all.Len()-trim:] // newall.Autos = me.all.Autos[0:10] } // drops nil entries from the history // even gets dup uuids. don't have time to figure this out now // need to launch the WIT private cloud product // todo: figure out why nil values are happening // a facinating PITA this is counter := 0 uuidmap := make(map[string]*Argv) for pb := range me.all.IterAll() { counter += 1 if pb.Ctime == nil { me.all.Delete(pb) continue } if pb.Uuid == "" { me.all.Delete(pb) continue } if _, ok := uuidmap[pb.Uuid]; ok { // already have this uuid, keep the oldest one me.all.Delete(pb) continue } uuidmap[pb.Uuid] = pb // more early code for debugging if things go really wrong // if me.debug { // hist := fmt.Sprintf("HISTNIL(%d)", counter) // pb.printDebugNew(hist, "too soon") // } } // only have nil values in the .pb file. todo: figure out why this happens if me.all.Len() == 0 { me.debug = true fmt.Fprintf(Stddbg, "examineArgvHistory() empty file %s\n", me.all.Filename) // saveAndExit() } // get the last autocomplete if me.all.Len() == 0 { me.last = new(Argv) } else { me.last = me.all.Argvs[me.all.Len()-1] } // compute the duration since the last time dur := time.Since(me.last.Ctime.AsTime()) PB.Duration = durationpb.New(dur) PB.ErrCounter = me.last.ErrCounter PB.OutCounter = me.last.OutCounter // do the smart something here if PB.GetCmd() == me.last.GetCmd() { // turn on debugging if duration < 200 milliseconds } else { // reset counters if user types things PB.OutCounter = 0 PB.ErrCounter = 0 PB.Fast = 0 } // user keeps hitting tab. trigger help if dur < time.Millisecond*300 { PB.Fast = me.last.Fast + 1 } else { PB.Fast = 0 errors.Join(me.Err, me.autoFunc()) // run the autocomplete function the user made for their application } }