diff --git a/README.md b/README.md index 9916a62..0bb756c 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,12 @@ Go around uses the stackoverflow API to get the answers for the given query and ## Installation -Download the binary corresponding to your platform from [releases](https://github.com/glendsoza/goaround/releases/tag/v0.3) page +Download the latest binary corresponding to your platform from [releases](https://github.com/glendsoza/goaround/releases/tag/v0.4) page ## Usage +### Querying the API directly + ```bash ./goaround -q "" ``` @@ -27,6 +29,29 @@ To get more accurate results you can pass the tags as comma separated values, re ./goaround -q "" -t "" ``` +### Using `goaround` as wrapper to run other programs (currently only supports go and python) + +`goaround` can be used to capture the std error of other porgrams and query the stackoverflow API with the erorr generated and display the results. + +```bash +./goaround -p "" +``` + +```bash +./goaround -p "go run main.go" +``` + +```bash +./goaround -p "python main.py" +``` + +Few things to note while using `goaround` as wrapper: + +- Stdout of the command will be displayed in the terminal in real time +- Only the error pushed to `Stderr` will be used to query the stackoverflow API +- In this mode tags provided via `-t` will be ignored and name of the executable in the command will be taken as tag + +### Navigating through the results - In questions screen use the Mouse Scroll or Arrow Keys to go up and down - Use the Enter key to open the answer @@ -46,5 +71,4 @@ Following environment can be used to configure the tool. Following Features are planned to be added in the future but any help is welcome! -- Make the tool similar to [Rebound](https://github.com/shobrook/rebound) - Provide button to copy the code from the answers to the clipboard diff --git a/build.sh b/build.sh index 1e1b578..384aeac 100755 --- a/build.sh +++ b/build.sh @@ -29,7 +29,7 @@ platforms=("darwin/amd64" "windows/arm" "windows/arm64") -version="0.3" +version="0.4" package_split=(${package//\// }) package_name=${package_split[-1]} for platform in "${platforms[@]}" diff --git a/executor/executor.go b/executor/executor.go new file mode 100644 index 0000000..fdf6d26 --- /dev/null +++ b/executor/executor.go @@ -0,0 +1,55 @@ +package executor + +import ( + "bytes" + "fmt" + "log" + "os" + "os/exec" + "regexp" + "strings" +) + +var PYTHON_EXEPECTED_ERRORS_REGEX = regexp.MustCompile(`KeyboardInterrupt|SystemExit|GeneratorExit`) + +func Execute() (string, string) { + // Split the command into tokens + commandTokens := strings.Split(Command, " ") + // grab the executable name + executable := commandTokens[0] + cmd := exec.Command(executable, commandTokens[1:]...) + var errorBuff bytes.Buffer + // print the stdout + cmd.Stdout = os.Stdout + // Storing the error in buffer inf any + cmd.Stderr = &errorBuff + err := cmd.Start() + // Exit if commands cannot be run + if err != nil { + log.Println("Please fix the following error") + log.Fatal(err) + } + cmd.Wait() + errorString := errorBuff.String() + // If erorr string is not null then the command exited with the error + if errorString != "" { + fmt.Printf("\nError >>>>>>>>>>\n\n %s \n <<<<<<<<<< Error\n", errorString) + // Depending on the executable return the error string + switch executable { + case "go": + data := strings.Split(errorString, "\n")[0] + if len(strings.Split(data, ": ")) > 1 { + return strings.Join(strings.Split(data, ": ")[1:], " "), executable + } + case "python", "python3": + // expected errors + if !PYTHON_EXEPECTED_ERRORS_REGEX.MatchString(errorString) { + data := strings.Split(errorString, "\n") + return data[len(data)-2], executable + } + case "default": + log.Println("Unrecogized executable") + } + } + return "", "" +} diff --git a/executor/global.go b/executor/global.go new file mode 100644 index 0000000..0ab231c --- /dev/null +++ b/executor/global.go @@ -0,0 +1,3 @@ +package executor + +var Command string diff --git a/main.go b/main.go index d8a1264..57ac548 100755 --- a/main.go +++ b/main.go @@ -2,87 +2,41 @@ package main import ( "flag" + "fmt" "goaround/api" - "goaround/widgets" + "goaround/executor" + "goaround/ui" "log" - - "github.com/gdamore/tcell/v2" - "github.com/rivo/tview" ) -var app = tview.NewApplication() - -// Initialize the loading widget -func initLoading() *widgets.LoadingWD { - lwd := widgets.NewLoadingWidget() - lwd.SetTitle("[red]Please wait, Querying stack overflow api") - lwd.SetBorder(true) - lwd.SetBorderColor(tcell.ColorSnow) - lwd.SetDynamicColors(true) - return lwd -} - -// Initialize the question widget -func initQuestion() *widgets.QuestionWD { - qwd := widgets.NewQuestionWidget() - qwd.SetSelectedBackgroundColor(tcell.ColorDarkCyan) - qwd.ShowSecondaryText(false) - qwd.SetBorder(true) - qwd.SetBorderColor(tcell.ColorSnow) - return qwd -} - func main() { flag.StringVar(&api.Query, "q", "", "Query to search") flag.StringVar(&api.Tags, "t", "", "List of command seperated tags to narrow down the search") + flag.StringVar(&executor.Command, "p", "", "Command to execute") flag.Parse() - if api.Query == "" { - log.Fatal("Please pass the query with -q option") - } - qwd := initQuestion() - lwd := initLoading() - // We want to stop the running app in case of error and exit the program immediately - // this function is passed to other widgets which do not have direct access to app - errorHandler := func(err error) { - app.Stop() - log.Fatal(err) + if api.Query == "" && executor.Command == "" { + log.Fatal("Please pass either a query or a command to execute") } - qwd.SetSelectedFunc(func(a int, b, c string, d rune) { - // When the questions are not loaded secondary text of the question will be set to error - // in this case we simply want to return - if c == "error" { + // check if any command is provided + if executor.Command != "" { + errorString, executable := executor.Execute() + // if error string is not empty then the command failed + if errorString != "" { + var input string + fmt.Println(errorString) + fmt.Println("Do you want to display Stackoverflow results? (y/n)") + fmt.Scanln(&input) + if input == "y" || input == "Y" { + api.Query = errorString + api.Tags = executable + } else { + return + } + } else { + // if error string is empty then the command ran successfully return } - doneChan := make(chan int) - awd, err := widgets.NewAnswerWidget(qwd.GetSelectedQuestion(a)) - if err != nil { - errorHandler(err) - } - awd.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - if event.Key() == tcell.KeyBackspace2 || event.Key() == tcell.KeyBackspace { - app.SetRoot(qwd.Render(), true) - return nil - } - return event - }) - awd.SetWrap(true) - awd.SetDynamicColors(true) - awd.SetBorderColor(tcell.ColorSnow) - awd.SetToggleHighlights(true) - // call the go routine to populate the answers - go awd.Populate(doneChan, errorHandler) - go lwd.Load(app, func() { - app.SetRoot(awd, true) - }, doneChan) - app.SetRoot(lwd, true) - }) - doneChan := make(chan int) - // go the go routine to populate questions - go qwd.Populate(doneChan, errorHandler) - go lwd.Load(app, func() { - app.SetRoot(qwd.Render(), true) - }, doneChan) - if err := app.SetRoot(lwd, true).EnableMouse(false).Run(); err != nil { - panic(err) } + // Initialize the ui depending on if q is provided or command provided via p failed + ui.InIt() } diff --git a/ui/ui.go b/ui/ui.go new file mode 100644 index 0000000..e701d01 --- /dev/null +++ b/ui/ui.go @@ -0,0 +1,78 @@ +package ui + +import ( + "goaround/widgets" + "log" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +var app = tview.NewApplication() + +// Initialize the loading widget +func initLoading() *widgets.LoadingWD { + lwd := widgets.NewLoadingWidget() + lwd.SetTitle("[red]Please wait, Querying stack overflow api") + lwd.SetBorder(true) + lwd.SetBorderColor(tcell.ColorSnow) + lwd.SetDynamicColors(true) + return lwd +} + +// Initialize the question widget +func initQuestion() *widgets.QuestionWD { + qwd := widgets.NewQuestionWidget() + qwd.SetSelectedBackgroundColor(tcell.ColorDarkCyan) + qwd.ShowSecondaryText(false) + qwd.SetBorder(true) + qwd.SetBorderColor(tcell.ColorSnow) + return qwd +} + +func InIt() { + qwd := initQuestion() + lwd := initLoading() + errorHandler := func(err error) { + app.Stop() + log.Fatal(err) + } + qwd.SetSelectedFunc(func(a int, b, c string, d rune) { + // When the questions are not loaded secondary text of the question will be set to error + // in this case we simply want to return + if c == "error" { + return + } + doneChan := make(chan int) + awd, err := widgets.NewAnswerWidget(qwd.GetSelectedQuestion(a)) + if err != nil { + errorHandler(err) + } + awd.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyBackspace2 || event.Key() == tcell.KeyBackspace { + app.SetRoot(qwd.Render(), true) + return nil + } + return event + }) + awd.SetWrap(true) + awd.SetDynamicColors(true) + awd.SetBorderColor(tcell.ColorSnow) + awd.SetToggleHighlights(true) + // call the go routine to populate the answers + go awd.Populate(doneChan, errorHandler) + go lwd.Load(app, func() { + app.SetRoot(awd, true) + }, doneChan) + app.SetRoot(lwd, true) + }) + doneChan := make(chan int) + // go the go routine to populate questions + go qwd.Populate(doneChan, errorHandler) + go lwd.Load(app, func() { + app.SetRoot(qwd.Render(), true) + }, doneChan) + if err := app.SetRoot(lwd, true).EnableMouse(false).Run(); err != nil { + panic(err) + } +}