Skip to content

Commit 4eeb583

Browse files
committed
Add initial documentation functionality
1 parent 9ce519b commit 4eeb583

File tree

17 files changed

+1082
-27
lines changed

17 files changed

+1082
-27
lines changed

.gptignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.vscode/**
2+
LICENSE

Justfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
default:
2+
just --list --unsorted
3+
4+
build:
5+
go build -v -o otto
6+
7+
clean:
8+
rm -f otto
9+
10+
run *commands:
11+
go run main.go {{commands}}

cmd/ask.go renamed to cmd/chat.go

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import (
1414

1515
var question string
1616

17-
// askCmd represents the ask command
18-
var askCmd = &cobra.Command{
19-
Use: "ask",
17+
// chatCmd represents the chat command
18+
var chatCmd = &cobra.Command{
19+
Use: "chat",
2020
Short: "Ask ChatGPT a question from the command line.",
2121
Long: `Ask ChatGPT a question from the command line.
2222
@@ -37,11 +37,11 @@ If '-q' is not specified, the user will be prompted to enter a question.
3737

3838
// if the question is not provided, prompt the user for it
3939
if question == "" {
40-
fmt.Print("What would you like to ask ChatGPT?\n> ")
40+
fmt.Print("What would you like to chat ChatGPT?\n> ")
4141
fmt.Scanln(&question)
4242
}
4343

44-
// ask ChatGPT the question
44+
// chat ChatGPT the question
4545
resp, err := openai.CreateChatSimple(question, 0) // 0 sets the max tokens to 1024
4646
if err != nil {
4747
fmt.Printf("Error: %s", err)
@@ -55,17 +55,7 @@ If '-q' is not specified, the user will be prompted to enter a question.
5555
}
5656

5757
func init() {
58-
rootCmd.AddCommand(askCmd)
58+
rootCmd.AddCommand(chatCmd)
5959

60-
// Here you will define your flags and configuration settings.
61-
62-
// Cobra supports Persistent Flags which will work for this command
63-
// and all subcommands, e.g.:
64-
// askCmd.PersistentFlags().String("foo", "", "A help for foo")
65-
66-
// Cobra supports local flags which will only run when this command
67-
// is called directly, e.g.:
68-
// askCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
69-
70-
askCmd.Flags().StringVarP(&question, "question", "q", "", "Question to ask ChatGPT")
60+
chatCmd.Flags().StringVarP(&question, "question", "q", "", "Question to chat ChatGPT")
7161
}

cmd/doc.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
3+
*/
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
"os"
9+
10+
"github.com/spf13/cobra"
11+
12+
"github.com/chand1012/ottodocs/config"
13+
"github.com/chand1012/ottodocs/document"
14+
)
15+
16+
// docCmd represents the doc command
17+
var docCmd = &cobra.Command{
18+
Use: "doc",
19+
Short: "Document a file",
20+
Long: `Document a file using the OpenAI ChatGPT API.`,
21+
Run: func(cmd *cobra.Command, args []string) {
22+
if filePath == "" {
23+
fmt.Println("Please provide a path to a file to document.")
24+
fmt.Println("Run `ottodocs doc -h` for more information.")
25+
os.Exit(1)
26+
}
27+
28+
if chatPrompt == "" {
29+
chatPrompt = "Write documentation for the following code snippet:"
30+
}
31+
32+
conf, err := config.Load()
33+
if err != nil || conf.APIKey == "" {
34+
// if the API key is not set, prompt the user to login
35+
fmt.Println("Please login first.")
36+
fmt.Println("Run `ottodocs login` to login.")
37+
os.Exit(1)
38+
}
39+
40+
var contents string
41+
42+
if inlineMode || !markdownMode {
43+
contents, err = document.SingleFile(filePath, chatPrompt, conf.APIKey)
44+
} else {
45+
contents, err = document.Markdown(filePath, chatPrompt, conf.APIKey)
46+
}
47+
48+
if err != nil {
49+
fmt.Printf("Error: %s", err)
50+
os.Exit(1)
51+
}
52+
53+
if outputFile != "" {
54+
// write the string to the output file
55+
err = os.WriteFile(outputFile, []byte(contents), 0644)
56+
if err != nil {
57+
fmt.Printf("Error: %s", err)
58+
os.Exit(1)
59+
}
60+
} else if overwriteOriginal {
61+
// overwrite the original file
62+
// clear the contents of the file
63+
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
64+
if err != nil {
65+
fmt.Printf("Error: %s", err)
66+
os.Exit(1)
67+
}
68+
69+
// write the new contents to the file
70+
_, err = file.WriteString(contents)
71+
if err != nil {
72+
fmt.Printf("Error: %s", err)
73+
os.Exit(1)
74+
}
75+
} else {
76+
fmt.Println(contents)
77+
}
78+
},
79+
}
80+
81+
func init() {
82+
rootCmd.AddCommand(docCmd)
83+
84+
// see cmd/vars.go for the definition of these flags
85+
docCmd.Flags().StringVarP(&filePath, "file", "f", "", "The file to document")
86+
docCmd.Flags().StringVarP(&chatPrompt, "prompt", "p", "", "The prompt to use for the document")
87+
docCmd.Flags().StringVarP(&outputFile, "output", "o", "", "The output file to write the documentation to. ")
88+
docCmd.Flags().BoolVarP(&inlineMode, "inline", "i", false, "Inline mode. Adds the documentation to the code.")
89+
docCmd.Flags().BoolVarP(&markdownMode, "markdown", "m", false, "Markdown mode. Outputs the documentation in markdown.")
90+
docCmd.Flags().BoolVarP(&overwriteOriginal, "overwrite", "w", false, "Overwrite the original file.")
91+
}

cmd/docs.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
3+
*/
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
"os"
9+
"path/filepath"
10+
11+
"github.com/chand1012/git2gpt/prompt"
12+
"github.com/chand1012/ottodocs/config"
13+
"github.com/chand1012/ottodocs/document"
14+
"github.com/spf13/cobra"
15+
)
16+
17+
// docsCmd represents the docs command
18+
var docsCmd = &cobra.Command{
19+
Use: "docs",
20+
Short: "Document a repository of files",
21+
Long: `Document an entire repository of files. Specify the path to the repo as the first positional argument. This command will recursively
22+
search for files in the directory and document them.
23+
`,
24+
Args: cobra.PositionalArgs(func(cmd *cobra.Command, args []string) error {
25+
if len(args) < 1 {
26+
return fmt.Errorf("requires a path to a repository")
27+
}
28+
return nil
29+
}),
30+
Run: func(cmd *cobra.Command, args []string) {
31+
repoPath = args[0]
32+
33+
if (!markdownMode || inlineMode) && outputFile != "" {
34+
fmt.Println("Error: cannot specify an output file in inline mode")
35+
os.Exit(1)
36+
}
37+
38+
if markdownMode && overwriteOriginal {
39+
fmt.Println("Error: cannot overwrite original file in markdown mode")
40+
os.Exit(1)
41+
}
42+
43+
if markdownMode && outputFile == "" {
44+
fmt.Println("Error: must specify an output file in markdown mode")
45+
os.Exit(1)
46+
}
47+
48+
if outputFile != "" {
49+
// if output file exists, throw error
50+
if _, err := os.Stat(outputFile); err == nil {
51+
fmt.Printf("Error: output file %s already exists!\n", outputFile)
52+
os.Exit(1)
53+
}
54+
}
55+
56+
conf, err := config.Load()
57+
if err != nil || conf.APIKey == "" {
58+
// if the API key is not set, prompt the user to login
59+
fmt.Println("Please login first.")
60+
fmt.Println("Run `ottodocs login` to login.")
61+
os.Exit(1)
62+
}
63+
64+
ignoreList := prompt.GenerateIgnoreList(ignoreFilePath, ignoreFilePath, !ignoreGitignore)
65+
ignoreList = append(ignoreList, filepath.Join(repoPath, ".gptignore"))
66+
repo, err := prompt.ProcessGitRepo(repoPath, ignoreList)
67+
if err != nil {
68+
fmt.Printf("Error: %s", err)
69+
os.Exit(1)
70+
}
71+
72+
for _, file := range repo.Files {
73+
var contents string
74+
75+
path := filepath.Join(repoPath, file.Path)
76+
77+
if outputFile != "" {
78+
fmt.Println("Documenting file", file.Path)
79+
}
80+
81+
if chatPrompt == "" {
82+
chatPrompt = "Write documentation for the following code snippet. The file name is" + file.Path + ":"
83+
}
84+
85+
if inlineMode || !markdownMode {
86+
contents, err = document.SingleFile(path, chatPrompt, conf.APIKey)
87+
} else {
88+
contents, err = document.Markdown(path, chatPrompt, conf.APIKey)
89+
}
90+
91+
if err != nil {
92+
fmt.Printf("Error documenting file %s: %s\n", path, err)
93+
continue
94+
}
95+
96+
if outputFile != "" && markdownMode {
97+
// write the string to the output file
98+
// append if the file already exists
99+
file, err := os.OpenFile(outputFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
100+
if err != nil {
101+
fmt.Printf("Error: %s\n", err)
102+
os.Exit(1)
103+
}
104+
105+
_, err = file.WriteString(contents)
106+
if err != nil {
107+
fmt.Printf("Error: %s\n", err)
108+
os.Exit(1)
109+
}
110+
111+
file.Close()
112+
} else if overwriteOriginal {
113+
// overwrite the original file
114+
// clear the contents of the file
115+
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
116+
if err != nil {
117+
fmt.Printf("Error: %s\n", err)
118+
os.Exit(1)
119+
}
120+
121+
// write the new contents to the file
122+
_, err = file.WriteString(contents)
123+
if err != nil {
124+
fmt.Printf("Error: %s\n", err)
125+
os.Exit(1)
126+
}
127+
128+
file.Close()
129+
} else {
130+
fmt.Println(contents)
131+
}
132+
}
133+
134+
},
135+
}
136+
137+
func init() {
138+
rootCmd.AddCommand(docsCmd)
139+
140+
// see cmd/vars for the definition of these flags
141+
docsCmd.Flags().StringVarP(&chatPrompt, "prompt", "p", "", "Prompt to use for the ChatGPT API")
142+
docsCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Path to the output file. For use with --markdown")
143+
docsCmd.Flags().StringVarP(&ignoreFilePath, "ignore", "n", "", "path to .gptignore file")
144+
docsCmd.Flags().BoolVarP(&markdownMode, "markdown", "m", false, "Output in Markdown format")
145+
docsCmd.Flags().BoolVarP(&inlineMode, "inline", "i", false, "Output in inline format")
146+
docsCmd.Flags().BoolVarP(&overwriteOriginal, "overwrite", "w", false, "Overwrite the original file")
147+
docsCmd.Flags().BoolVarP(&ignoreGitignore, "ignore-gitignore", "g", false, "ignore .gitignore file")
148+
}

cmd/prompt.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,17 @@ import (
1111
"github.com/spf13/cobra"
1212
)
1313

14-
var repoPath string
15-
var preambleFile string
16-
var outputFile string
17-
var estimateTokens bool
18-
var ignoreFilePath string
19-
var ignoreGitignore bool
20-
var outputJSON bool
21-
2214
// promptCmd represents the prompt command
2315
var promptCmd = &cobra.Command{
2416
Use: "prompt",
2517
Short: "Generates a ChatGPT prompt from a given Git repo",
26-
Long: `Generates a ChatGPT prompt from a given Git repo.`,
18+
Long: `Generates a ChatGPT prompt from a given Git repo. Specify the path to the repo as the first positional argument.`,
19+
Args: cobra.PositionalArgs(func(cmd *cobra.Command, args []string) error {
20+
if len(args) < 1 {
21+
return fmt.Errorf("requires a path to a repository")
22+
}
23+
return nil
24+
}),
2725
Run: func(cmd *cobra.Command, args []string) {
2826
repoPath = args[0]
2927
ignoreList := prompt.GenerateIgnoreList(repoPath, ignoreFilePath, !ignoreGitignore)
@@ -82,6 +80,7 @@ var promptCmd = &cobra.Command{
8280
func init() {
8381
rootCmd.AddCommand(promptCmd)
8482

83+
// see cmd/vars.go for the definition of these flags
8584
promptCmd.Flags().StringVarP(&preambleFile, "preamble", "p", "", "path to preamble text file")
8685
// output to file flag. Should be a string
8786
promptCmd.Flags().StringVarP(&outputFile, "output", "o", "", "path to output file")

cmd/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111

1212
// rootCmd represents the base command when called without any subcommands
1313
var rootCmd = &cobra.Command{
14-
Use: "ottodocs",
14+
Use: "otto",
1515
Short: "Document your code with ease",
1616
Long: `Code documentation made easy using GPT.`,
1717
// Uncomment the following line if your bare application

cmd/vars.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package cmd
2+
3+
var repoPath string
4+
var preambleFile string
5+
var outputFile string
6+
var estimateTokens bool
7+
var ignoreFilePath string
8+
var ignoreGitignore bool
9+
var outputJSON bool
10+
11+
var filePath string
12+
var chatPrompt string
13+
var inlineMode bool
14+
var markdownMode bool
15+
var overwriteOriginal bool

constants/commentOperators.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package constants
2+
3+
var CommentOperators = map[string]string{
4+
".py": "#", // Python
5+
".go": "//", // Go
6+
".c": "//", // C
7+
".cpp": "//", // C++
8+
".h": "//", // C/C++ Header
9+
".cs": "//", // C#
10+
".java": "//", // Java
11+
".js": "//", // JavaScript
12+
".ts": "//", // TypeScript
13+
".php": "//", // PHP
14+
".rb": "#", // Ruby
15+
".rs": "//", // Rust
16+
".swift": "//", // Swift
17+
".sh": "#", // Shell Script
18+
".pl": "#", // Perl
19+
".lua": "--", // Lua
20+
".m": "%", // MATLAB
21+
".r": "#", // R
22+
".scala": "//", // Scala
23+
".kts": "//", // Kotlin
24+
".vb": "'", // Visual Basic .NET
25+
".f": "!", // Fortran
26+
".asm": ";", // Assembly
27+
".html": "<!--", // HTML (Opening Comment)
28+
".css": "/*", // CSS (Opening Comment)
29+
}

constants/openAI.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package constants
2+
3+
const OPENAI_MAX_TOKENS int = 4000

0 commit comments

Comments
 (0)