Skip to content

Commit

Permalink
Code refactor and added the feature to grab the erorrs from other pro…
Browse files Browse the repository at this point in the history
…gram
  • Loading branch information
glendsoza committed Jan 2, 2022
1 parent 6a44348 commit 69d9ce2
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 74 deletions.
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<your query>"
```
Expand All @@ -27,6 +29,29 @@ To get more accurate results you can pass the tags as comma separated values, re
./goaround -q "<your query>" -t "<comma seperated values>"
```

### 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 "<your command>"
```

```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
Expand All @@ -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
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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[@]}"
Expand Down
55 changes: 55 additions & 0 deletions executor/executor.go
Original file line number Diff line number Diff line change
@@ -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 "", ""
}
3 changes: 3 additions & 0 deletions executor/global.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package executor

var Command string
96 changes: 25 additions & 71 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
78 changes: 78 additions & 0 deletions ui/ui.go
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit 69d9ce2

Please sign in to comment.