Skip to content

Commit cb81aee

Browse files
committed
Add initial refactor code
1 parent 1b41726 commit cb81aee

File tree

11 files changed

+298
-3
lines changed

11 files changed

+298
-3
lines changed

Justfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ install: build
2828
rm -rf $GOPATH/bin/otto
2929
cp bin/otto $GOPATH/bin
3030

31+
add command:
32+
cobra-cli add {{command}}
33+
34+
test:
35+
go test -v ./...
36+
3137
crossbuild:
3238
#!/bin/bash
3339

cmd/ask.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var askCmd = &cobra.Command{
2727
Short: "Ask a question about a file or repo",
2828
Long: `Uses full text search to find relevant code and ask questions about said code.
2929
Requires a path to a repository or file as a positional argument.`,
30+
Aliases: []string{"a"},
3031
PreRun: func(cmd *cobra.Command, args []string) {
3132
if verbose {
3233
log.SetLevel(l.DebugLevel)

cmd/commit.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ type fileDiff struct {
2626

2727
// commitCmd represents the commit command
2828
var commitCmd = &cobra.Command{
29-
Use: "commit",
30-
Short: "Generates a commit message from the git diff",
31-
Long: `Uses the git diff to generate a commit message. Requires Git to be installed on the system.`,
29+
Use: "commit",
30+
Short: "Generates a commit message from the git diff",
31+
Long: `Uses the git diff to generate a commit message. Requires Git to be installed on the system.`,
32+
Aliases: []string{"cm"},
3233
PreRun: func(cmd *cobra.Command, args []string) {
3334
if verbose {
3435
log.SetLevel(l.DebugLevel)

cmd/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ GitHub Tokens need access to the repo scope.
2929
OpenAI API Key Generation: https://platform.openai.com/account/api-keys
3030
GitHub Token Generation: https://github.com/settings/tokens
3131
`,
32+
Aliases: []string{"c"},
3233
Run: func(cmd *cobra.Command, args []string) {
3334
// load the config
3435
c, err := config.Load()

cmd/docs.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ search for files in the directory and document them. If a single file is specifi
2626
Example:
2727
otto docs . -i -w
2828
`,
29+
Aliases: []string{"d"},
2930
PreRun: func(cmd *cobra.Command, args []string) {
3031
if verbose {
3132
log.SetLevel(l.DebugLevel)

cmd/refactor.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
3+
*/
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
"os"
9+
"strings"
10+
11+
"github.com/chand1012/ottodocs/pkg/ai"
12+
"github.com/chand1012/ottodocs/pkg/config"
13+
"github.com/chand1012/ottodocs/pkg/constants"
14+
"github.com/chand1012/ottodocs/pkg/textfile"
15+
"github.com/chand1012/ottodocs/pkg/utils"
16+
l "github.com/charmbracelet/log"
17+
"github.com/spf13/cobra"
18+
)
19+
20+
// refactorCmd represents the refactor command
21+
var refactorCmd = &cobra.Command{
22+
Use: "refactor",
23+
Short: "A brief description of your command",
24+
Long: `A longer description that spans multiple lines and likely contains examples
25+
and usage of using your command. For example:
26+
27+
Cobra is a CLI library for Go that empowers applications.
28+
This application is a tool to generate the needed files
29+
to quickly create a Cobra application.`,
30+
Aliases: []string{"r"},
31+
PreRun: func(cmd *cobra.Command, args []string) {
32+
if verbose {
33+
log.SetLevel(l.DebugLevel)
34+
}
35+
},
36+
Run: func(cmd *cobra.Command, args []string) {
37+
if len(args) == 0 {
38+
log.Error("Requires a file name as an argument. Example: otto refactor main.go")
39+
os.Exit(1)
40+
}
41+
42+
c, err := config.Load()
43+
if err != nil {
44+
log.Errorf("Error loading config: %s", err)
45+
os.Exit(1)
46+
}
47+
48+
fileName := args[0]
49+
50+
// load the file
51+
contents, err := utils.LoadFile(fileName)
52+
if err != nil {
53+
log.Errorf("Error loading file: %s", err)
54+
os.Exit(1)
55+
}
56+
57+
if endLine < 0 || startLine < 0 {
58+
log.Error("End line must be greater than or equal to start line and both must be greater than or equal to 0")
59+
os.Exit(1)
60+
}
61+
62+
if endLine != 0 {
63+
// get the lines to refactor
64+
lines := strings.Split(contents, "\n")
65+
if endLine > len(lines) {
66+
log.Error("End line is greater than the number of lines in the file")
67+
os.Exit(1)
68+
}
69+
70+
contents = strings.Join(lines[startLine-1:endLine], "\n")
71+
} else {
72+
endLine = len(strings.Split(contents, "\n"))
73+
}
74+
75+
log.Debugf("Refactoring lines %d-%d", startLine, endLine)
76+
77+
if chatPrompt == "" {
78+
chatPrompt, err = utils.Input("Goal: ")
79+
if err != nil {
80+
log.Errorf("Error prompting for goal: %s", err)
81+
os.Exit(1)
82+
}
83+
}
84+
85+
prompt := constants.REFACTOR_CODE_PROMPT + "Goal: " + chatPrompt + "\n\n" + strings.TrimRight(contents, " \n")
86+
87+
stream, err := ai.SimpleStreamRequest(prompt, c)
88+
if err != nil {
89+
log.Errorf("Error requesting from OpenAI: %s", err)
90+
os.Exit(1)
91+
}
92+
93+
// print the response
94+
fmt.Println("New Code:")
95+
newCode, err := utils.PrintChatCompletionStream(stream)
96+
if err != nil {
97+
log.Errorf("Error printing chat completion stream: %s", err)
98+
os.Exit(1)
99+
}
100+
101+
if !force {
102+
confirm, err := utils.Input("Would you like to overwrite the file with the new code? (y/N): ")
103+
if err != nil {
104+
log.Errorf("Error getting input: %s", err)
105+
os.Exit(1)
106+
}
107+
108+
confirm = strings.ToLower(confirm)
109+
if confirm != "y" && confirm != "yes" {
110+
os.Exit(0)
111+
}
112+
}
113+
114+
// write the new code to the file
115+
finalCode, err := textfile.ReplaceLines(contents, startLine, endLine, newCode)
116+
if err != nil {
117+
log.Errorf("Error replacing lines: %s", err)
118+
os.Exit(1)
119+
}
120+
121+
err = utils.WriteFile(fileName, finalCode)
122+
if err != nil {
123+
log.Errorf("Error writing file: %s", err)
124+
os.Exit(1)
125+
}
126+
fmt.Println("File written successfully!")
127+
},
128+
}
129+
130+
func init() {
131+
RootCmd.AddCommand(refactorCmd)
132+
133+
refactorCmd.Flags().BoolVarP(&force, "force", "f", false, "Force overwrite of existing files")
134+
refactorCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output")
135+
refactorCmd.Flags().IntVarP(&startLine, "start", "s", 1, "Start line")
136+
refactorCmd.Flags().IntVarP(&endLine, "end", "e", 0, "End line")
137+
refactorCmd.Flags().StringVarP(&chatPrompt, "goal", "g", "", "Goal of the refactor")
138+
}

cmd/vars.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ var useComments bool
4040
var promptOnly bool
4141
var countFinalTokens bool
4242

43+
var startLine int
44+
var endLine int
45+
4346
var log = l.NewWithOptions(os.Stderr, l.Options{
4447
Level: l.InfoLevel,
4548
ReportTimestamp: false,

pkg/constants/prompts.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@ var PR_TITLE_PROMPT string = "You are a helpful assistant who writes pull reques
3939
var PR_BODY_PROMPT string = "You are a helpful assistant who writes pull request bodies. You will be given information related to the pull request and you should use it to create a pull request body. It should detail the changes made to complete the pull request. Do not include file names. Make sure it details the main changes made, ignore any minor changes."
4040

4141
var COMPRESS_DIFF_PROMPT string = "You are a helpful assistant who describes git diff changes. You will be given a Git diff and you should use it to create a description of the changes. The description should be no longer than 75 characters long and should describe the changes in the diff. Do not include the file names in the description."
42+
43+
var REFACTOR_CODE_PROMPT string = `You are a helpful assistant who refactors code. You will be given a file what you are trying to accomplish in the refactor and you should refactor it to the best of your abilities. You should only output the code and should not output any other text.\n`

pkg/textfile/insert.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,21 @@ func InsertLinesAtIndices(file string, indices []int, linesToInsert []string) (s
3838

3939
return strings.Join(lines, "\n"), nil
4040
}
41+
42+
func ReplaceLines(code string, startLine int, endLine int, newText string) (string, error) {
43+
if startLine < 1 || endLine < 1 {
44+
return "", fmt.Errorf("startLine and endLine must be greater than or equal to 1")
45+
}
46+
47+
if startLine > endLine {
48+
return "", fmt.Errorf("startLine must be less than or equal to endLine")
49+
}
50+
51+
lines := strings.Split(code, "\n")
52+
if endLine > len(lines) {
53+
return "", fmt.Errorf("endLine is greater than the number of lines in the code")
54+
}
55+
56+
lines = append(lines[:startLine-1], append([]string{newText}, lines[endLine:]...)...)
57+
return strings.Join(lines, "\n"), nil
58+
}

pkg/textfile/insert_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package textfile
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestReplaceLines(t *testing.T) {
8+
testCases := []struct {
9+
name string
10+
code string
11+
startLine int
12+
endLine int
13+
newText string
14+
expectedResult string
15+
expectError bool
16+
}{
17+
{
18+
name: "Replace single line",
19+
code: "Line 1\nLine 2\nLine 3",
20+
startLine: 2,
21+
endLine: 2,
22+
newText: "New Line 2",
23+
expectedResult: "Line 1\nNew Line 2\nLine 3",
24+
expectError: false,
25+
},
26+
{
27+
name: "Replace multiple lines",
28+
code: "Line 1\nLine 2\nLine 3",
29+
startLine: 1,
30+
endLine: 2,
31+
newText: "New Line 1 and 2",
32+
expectedResult: "New Line 1 and 2\nLine 3",
33+
expectError: false,
34+
},
35+
{
36+
name: "Invalid startLine and endLine",
37+
code: "Line 1\nLine 2\nLine 3",
38+
startLine: 3,
39+
endLine: 1,
40+
newText: "Invalid Test",
41+
expectedResult: "",
42+
expectError: true,
43+
},
44+
}
45+
46+
for _, tc := range testCases {
47+
t.Run(tc.name, func(t *testing.T) {
48+
result, err := ReplaceLines(tc.code, tc.startLine, tc.endLine, tc.newText)
49+
50+
if tc.expectError && err == nil {
51+
t.Errorf("Expected error, but got nil")
52+
}
53+
54+
if !tc.expectError && err != nil {
55+
t.Errorf("Unexpected error: %v", err)
56+
}
57+
58+
if result != tc.expectedResult {
59+
t.Errorf("Expected result: %q, but got: %q", tc.expectedResult, result)
60+
}
61+
})
62+
}
63+
}
64+
65+
func TestInsertLine(t *testing.T) {
66+
testCases := []struct {
67+
name string
68+
code string
69+
lineNumber int
70+
newText string
71+
expectedResult string
72+
expectError bool
73+
}{
74+
{
75+
name: "Insert at beginning",
76+
code: "Line 1\nLine 2\nLine 3",
77+
lineNumber: 1,
78+
newText: "New Line 0",
79+
expectedResult: "New Line 0\nLine 1\nLine 2\nLine 3",
80+
expectError: false,
81+
},
82+
{
83+
name: "Insert in the middle",
84+
code: "Line 1\nLine 2\nLine 3",
85+
lineNumber: 2,
86+
newText: "New Line 2",
87+
expectedResult: "Line 1\nNew Line 2\nLine 2\nLine 3",
88+
expectError: false,
89+
},
90+
{
91+
name: "Invalid line number",
92+
code: "Line 1\nLine 2\nLine 3",
93+
lineNumber: 0,
94+
newText: "Invalid Test",
95+
expectedResult: "",
96+
expectError: true,
97+
},
98+
}
99+
100+
for _, tc := range testCases {
101+
t.Run(tc.name, func(t *testing.T) {
102+
result, err := InsertLine(tc.code, tc.lineNumber, tc.newText)
103+
104+
if tc.expectError && err == nil {
105+
t.Errorf("Expected error, but got nil")
106+
}
107+
108+
if !tc.expectError && err != nil {
109+
t.Errorf("Unexpected error: %v", err)
110+
}
111+
112+
if result != tc.expectedResult {
113+
t.Errorf("Expected result: %q, but got: %q", tc.expectedResult, result)
114+
}
115+
})
116+
}
117+
}

0 commit comments

Comments
 (0)