Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go support #43

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ venv
.env
env/
.vscode/
wolverine/GoLang/internal/main/.env
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,31 @@ This is just a quick prototype I threw together in a few hours. There are many p
## Star History

[![Star History Chart](https://api.star-history.com/svg?repos=biobootloader/wolverine&type=Date)](https://star-history.com/#biobootloader/wolverine)

# Go Usage:
#### The Wolverine supports .go files to be executed now!
## Setup step by step:

1) git clone https://....<the_link>
2) cd wolverine/wolverine/GoLang/internal/main
3) go mod install
4) go build -o ../../../../../your_wish_folder/wolverine.exe
5) cd ../../../../../your_wish_folder
6) > .env
7) fill the .env file like in the example below, but with your personal data

### Example .env:
OPENAI_API_KEY=gorinfwe:mfwbevnmowemo9fn20f439vn03v4
GPT_MODEL=text-davinci-003
ATTEMPTS_TO_TRY=15

#### Remember that each attempt is a request to GPT so think twice about \<ATTEMPTS_TO_TRY> value
#### GPT 4 is the most preferable model here, but you can try to use any model

## Example Usage

./wolverine.exe main

#### main here is your filename WITHOUT .go extension
#### If you see the "Success" message, then you must have obtained a file enterFilename+"__fixed".go>, and it's free of any compile errors. So you can freely run it
go run main__fixed.go
14 changes: 14 additions & 0 deletions wolverine/GoLang/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module wolverine

go 1.20

require (
github.com/fatih/color v1.15.0
github.com/joho/godotenv v1.5.1
)

require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
golang.org/x/sys v0.6.0 // indirect
)
12 changes: 12 additions & 0 deletions wolverine/GoLang/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
84 changes: 84 additions & 0 deletions wolverine/GoLang/internal/main/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package main

import (
"embed"
"fmt"
"github.com/joho/godotenv"
"log"
"os"
"strconv"
"wolverine/internal/service"
"wolverine/internal/service/healFile"
)

//go:embed prompt.txt
var promptContent embed.FS

func main() {
content, err := promptContent.ReadFile("prompt.txt")
if err != nil {
fmt.Println("Failed to read prompt.txt file")
log.Println(err)
return
}

prompt := string(content)

godotenv.Load()

gptModel := os.Getenv("GPT_MODEL")
apiKey := os.Getenv("OPENAI_API_KEY")
attemptsToTryString := os.Getenv("ATTEMPTS_TO_TRY")
if gptModel == "" || apiKey == "" {
log.Println("You need to set GPT_MODEL and OPENAI_API_KEY environment variables to run the program.")
return
}

attemptsToTry, err := strconv.Atoi(attemptsToTryString)
if err != nil || attemptsToTryString == "" {
log.Println("ATTEMPTS_TO_TRY environment variable is invalid or not set. It should be an integer.")
log.Println(err)
return
}

sourceFilename, err := service.ReceiveFile()
if err != nil {
fmt.Println("Failed to extract a filename from the inputted string")
log.Println(err)
return
}

healedFilename := sourceFilename + "__fixed.go"
sourceFilename += ".go"

_, err = os.Stat(sourceFilename)
if os.IsNotExist(err) {
log.Println("The file you entered doesn't exist. Enter another one and try again.")
return
}

// prepare file to be filled code with changes
err = service.PrepareNewFile(healedFilename)
if err != nil {
fmt.Println("Failed to prepare a new file to have the code with changes")
log.Println(err)
return
}

// check the inputted model
modelIsValid := service.ValidateGPTModel(apiKey, gptModel)
if !modelIsValid {
log.Println("The GPT model you entered is invalid or doesn't exist. Enter another one and try again.")
return
}

err = healFile.HealFile(sourceFilename, healedFilename, apiKey, gptModel, prompt, attemptsToTry)
if err != nil {
fmt.Println("Failed to heal the file")
log.Println(err)
return
}

fmt.Println("\n+++ <<-- Success! -->> +++")
fmt.Println("Now you can successfully run " + healedFilename)
}
56 changes: 56 additions & 0 deletions wolverine/GoLang/internal/main/prompt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
You are part of an elite automated software fixing team. You will be given a script followed by the arguments it provided and the stack trace of the error it produced. Your job is to figure out what went wrong and suggest changes to the code.

Because you are part of an automated system, the format you respond in is very strict and sharp. You must provide changes in JSON format, using one of 3 actions: 'Replace', 'Delete', or 'InsertAfter'. 'Delete' will remove that line from the code. 'Replace' will replace the existing line with the content you provide. 'InsertAfter' will insert the new lines you provide AFTER the code is already at the specified line number. For instance, if you want to write line as <x>th, so "line" field should be <x-1> to write to the <x> line etc. For multi-line insertions or replacements, provide the content as a single string with '\n' as the newline character. The first line in each file is given line number 1. Edits will be applied in reverse line order so that line numbers won't be impacted by other edits.

In addition to the changes, please also provide short explanations of what went wrong. A single explanation message is required, but if you think it's helpful, feel free to provide more explanation messages groups of more complicated changes, but each explanations object must have corresponding action object, connected with unique id field. Each operation item from operations block must have same operation type. Be careful to use proper indentation and spacing in your changes. An example response could be:

Be ABSOLUTELY SURE to include the CORRECT INDENTATION when making replacements and respond exceptionally in JSON format.
example response:

{
"explanations": [
{
"id": 1,
"messages": [
"This is just an example, this would usually be a brief explanation of what went wrong"
]
},
{
"id": 2,
"messages": [
"This is another example, this would usually be a brief explanation of what went wrong",
"another one"
]
},
{
"id": 3,
"messages": [
"This is a third example, this would usually be a brief explanation of what went wrong",
"another one",
"another one"
]
}
],
"actions": [
{
"id": 1,
"operations": [
{"operation": "Delete", "line": 20, "content": ""},
{"operation": "Delete", "line": 21, "content": ""},
{"operation": "Delete", "line": 22, "content": ""}
]
},
{
"id": 2,
"operations": [
{"operation": "InsertAfter", "line": 48, "content": "x := 1\ny := 2\nz := x * y"}
]
},
{
"id": 3,
"operations": [
{"operation": "Replace", "line": 50, "content": "return nil"}
]
}
]
}
108 changes: 108 additions & 0 deletions wolverine/GoLang/internal/service/healFile/applyChanges.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package healFile

import (
"fmt"
"strings"
"wolverine/pkg/cli"
)

func applyChanges(sourceFilename, targetFilename string, changes GPTResponse) error {
fileLines, err := readFileLines(sourceFilename)
if err != nil {
return err
}

sortChanges(&changes)

// define entities only after sorting the object
explanations := changes.Explanations
actions := changes.Actions

for _, action := range actions {
switch action.Operations[0].Operation {
case DeleteOperationType:
fmt.Println("Delete Operation:")
printExplanations(explanations, action, cli.PrintRed)

for _, operation := range action.Operations {
deleteLine(&fileLines, operation.Line)
}

case InsertOperationType:
fmt.Println("Insert Operation:")
printExplanations(explanations, action, cli.PrintGreen)

for _, operation := range action.Operations {
insertLine(&fileLines, operation.Line, operation.Content)
}
case ReplaceOperationType:
fmt.Println("Replace Operation:")
printExplanations(explanations, action, cli.PrintYellow)

for _, operation := range action.Operations {
replaceLine(&fileLines, operation.Line, operation.Content)
}
}
}

entireCode := strings.Join(fileLines, "\n")
err = writeToExistingFile(targetFilename, entireCode)
if err != nil {
return err
}

return nil
}

func sortChanges(changes *GPTResponse) {
// reverse the actions
for i := 0; i < len(changes.Actions)/2; i++ {
changes.Actions[i], changes.Actions[len(changes.Actions)-i-1] = changes.Actions[len(changes.Actions)-i-1], changes.Actions[i]
}

// reverse the explanations
for i := 0; i < len(changes.Explanations)/2; i++ {
changes.Explanations[i], changes.Explanations[len(changes.Explanations)-i-1] = changes.Explanations[len(changes.Explanations)-i-1], changes.Explanations[i]
}
}

func deleteLine(fileLines *[]string, line int) {
// GPT would respond like 1st line has an error, but we work with 0th item, not 1st
line--
*fileLines = append((*fileLines)[:line], (*fileLines)[line+1:]...)
}

func insertLine(fileLines *[]string, line int, content string) {
// GPT would respond like 1st line has an error, but we work with 0th item, not 1st
line--
*fileLines = append((*fileLines)[:line+1], append([]string{content}, (*fileLines)[line+1:]...)...)
}

func replaceLine(fileLines *[]string, line int, content string) {
// GPT would respond like 1st line has an error, but we work with 0th item, not 1st
line--
(*fileLines)[line] = content
}

func printExplanations(explanations []GPTExplanation, action GPTAction, printFunc func(string)) {
currentExplanationIndex := -1

for i, explanation := range explanations {
if explanation.Id == action.Id {
currentExplanationIndex = i
break
}
}

if currentExplanationIndex == -1 {
fmt.Println("The explanation is not provided.")
} else {
for _, explanation := range explanations[currentExplanationIndex].Messages {
fmt.Println(explanation)
}
}

for _, operation := range action.Operations {
printFunc(operation.Content)
}
}
23 changes: 23 additions & 0 deletions wolverine/GoLang/internal/service/healFile/attempt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package healFile

func attempt(sourceFilename, targetFilename, compileError, apiToken, model, prompt string) error {
// get file content
code, err := getFileContent(sourceFilename)
if err != nil {
return err
}

// make request to gpt
gptResponse, err := makeRequestToGPT(code, compileError, apiToken, model, prompt)
if err != nil {
return err
}

// apply changes: write them to the targetFilename
err = applyChanges(sourceFilename, targetFilename, gptResponse)
if err != nil {
return err
}

return nil
}
57 changes: 57 additions & 0 deletions wolverine/GoLang/internal/service/healFile/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package healFile

import (
"bufio"
"io"
"os"
)

func writeToExistingFile(filename, content string) error {
// Open file with O_TRUNC flag to truncate the file before writing
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0660)
if err != nil {
return err
}
defer file.Close()

_, err = file.WriteString(content)
if err != nil {
return err
}

return nil
}

func getFileContent(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()

code, err := io.ReadAll(file)
if err != nil {
return "", err
}

return string(code), nil
}

func readFileLines(filename string) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()

var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, err
}

return lines, nil
}
Loading