Skip to content

Commit fc4fe0b

Browse files
authored
Merge pull request #2 from Intility/feature/config
feat: add new config command and streamline flags
2 parents 8b3b28f + fea94ff commit fc4fe0b

File tree

7 files changed

+374
-95
lines changed

7 files changed

+374
-95
lines changed

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ After installing Chat With Code, you're just a few steps away from a conversatio
8383
cwc login \
8484
--api-key=$API_KEY \
8585
--endpoint "https://your-endpoint.openai.azure.com/" \
86-
--api-version "2023-12-01-preview" \
8786
--deployment-model "gpt-4-turbo"
8887
```
8988

@@ -142,7 +141,30 @@ PROMPT="please write me a conventional commit for these changes"
142141
git diff HEAD | cwc $PROMPT | git commit -e --file -
143142
```
144143

145-
## Template Features
144+
## Configuration
145+
146+
Managing your configuration is simple with the `cwc config` command. This command allows you to view and set configuration options for cwc.
147+
To view the current configuration, use:
148+
149+
```sh
150+
cwc config get
151+
```
152+
153+
To set a configuration option, use:
154+
155+
```sh
156+
cwc config set key1=value1 key2=value2 ...
157+
```
158+
159+
For example, to disable the gitignore feature and the git directory exclusion, use:
160+
161+
```sh
162+
cwc config set useGitignore=false excludeGitDir=false
163+
```
164+
165+
To reset the configuration to default values use `cwc login` to re-authenticate.
166+
167+
## Templates
146168

147169
### Overview
148170

cmd/config.go

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
package cmd
2+
3+
import (
4+
stdErrors "errors"
5+
"fmt"
6+
"strconv"
7+
"strings"
8+
9+
"github.com/intility/cwc/pkg/config"
10+
"github.com/intility/cwc/pkg/errors"
11+
"github.com/intility/cwc/pkg/ui"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
func createConfigCommand() *cobra.Command {
16+
cmd := &cobra.Command{
17+
Use: "config",
18+
Short: "Get or set config variables",
19+
Long: `Get or set config variables`,
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
err := cmd.Usage()
22+
if err != nil {
23+
return fmt.Errorf("failed to print usage: %w", err)
24+
}
25+
26+
return nil
27+
},
28+
}
29+
30+
cmd.AddCommand(createGetConfigCommand())
31+
cmd.AddCommand(createSetConfigCommand())
32+
33+
return cmd
34+
}
35+
36+
func createGetConfigCommand() *cobra.Command {
37+
cmd := &cobra.Command{
38+
Use: "get",
39+
Short: "Print current config",
40+
Long: "Print current config",
41+
Args: cobra.NoArgs,
42+
RunE: func(cmd *cobra.Command, args []string) error {
43+
cfg, err := config.LoadConfig()
44+
if err != nil {
45+
return fmt.Errorf("failed to load config: %w", err)
46+
}
47+
48+
printConfig(cfg)
49+
50+
return nil
51+
},
52+
}
53+
54+
return cmd
55+
}
56+
57+
func createSetConfigCommand() *cobra.Command {
58+
cmd := &cobra.Command{
59+
Use: "set",
60+
Short: "Set config variables",
61+
Long: "Set config variables",
62+
RunE: func(cmd *cobra.Command, args []string) error {
63+
cfg, err := config.LoadConfig()
64+
if err != nil {
65+
return fmt.Errorf("failed to load config: %w", err)
66+
}
67+
68+
// if no args are given, print the help and exit
69+
if len(args) == 0 {
70+
err = cmd.Help()
71+
if err != nil {
72+
return fmt.Errorf("failed to print help: %w", err)
73+
}
74+
75+
return nil
76+
}
77+
78+
err = processKeyValuePairs(cfg, args)
79+
80+
if err != nil {
81+
var suppressedError errors.SuppressedError
82+
if ok := stdErrors.As(err, &suppressedError); ok {
83+
cmd.SilenceUsage = true
84+
cmd.SilenceErrors = true
85+
}
86+
87+
return err
88+
}
89+
90+
return nil
91+
},
92+
}
93+
94+
return cmd
95+
}
96+
97+
func processKeyValuePairs(cfg *config.Config, kvPairs []string) error {
98+
// iterate over each argument and process them as key=value pairs
99+
argKvSubstrCount := 2
100+
for _, arg := range kvPairs {
101+
kvPair := strings.SplitN(arg, "=", argKvSubstrCount)
102+
if len(kvPair) != argKvSubstrCount {
103+
return errors.ArgParseError{Message: fmt.Sprintf("invalid argument format: %s, expected key=value", arg)}
104+
}
105+
106+
key := kvPair[0]
107+
value := kvPair[1]
108+
109+
err := setConfigValue(cfg, key, value)
110+
if err != nil {
111+
return fmt.Errorf("failed to set config value: %w", err)
112+
}
113+
}
114+
115+
err := config.SaveConfig(cfg)
116+
if err != nil {
117+
return fmt.Errorf("failed to save config: %w", err)
118+
}
119+
120+
printConfig(cfg)
121+
122+
return nil
123+
}
124+
125+
func setConfigValue(cfg *config.Config, key, value string) error {
126+
switch key {
127+
case "endpoint":
128+
cfg.Endpoint = value
129+
case "deploymentName":
130+
cfg.ModelDeployment = value
131+
case "apiKey":
132+
cfg.SetAPIKey(value)
133+
case "useGitignore":
134+
b, err := strconv.ParseBool(value)
135+
if err != nil {
136+
return errors.ArgParseError{Message: "invalid boolean value for useGitignore: " + value}
137+
}
138+
139+
cfg.UseGitignore = b
140+
case "excludeGitDir":
141+
b, err := strconv.ParseBool(value)
142+
if err != nil {
143+
return errors.ArgParseError{Message: "invalid boolean value for excludeGitDir: " + value}
144+
}
145+
146+
cfg.ExcludeGitDir = b
147+
default:
148+
ui.PrintMessage(fmt.Sprintf("Unknown config key: %s\n", key), ui.MessageTypeError)
149+
150+
validKeys := []string{
151+
"endpoint",
152+
"deploymentName",
153+
"apiKey",
154+
"useGitignore",
155+
"excludeGitDir",
156+
}
157+
158+
ui.PrintMessage("Valid keys are: "+strings.Join(validKeys, ", "), ui.MessageTypeInfo)
159+
160+
return errors.SuppressedError{}
161+
}
162+
163+
return nil
164+
}
165+
166+
func printConfig(cfg *config.Config) {
167+
table := [][]string{
168+
{"Name", "Value"},
169+
{"endpoint", cfg.Endpoint},
170+
{"deploymentName", cfg.ModelDeployment},
171+
{"apiKey", cfg.APIKey()},
172+
{"SEP", ""},
173+
{"useGitignore", fmt.Sprintf("%t", cfg.UseGitignore)},
174+
{"excludeGitDir", fmt.Sprintf("%t", cfg.ExcludeGitDir)},
175+
}
176+
177+
printTable(table)
178+
}
179+
180+
func printTable(table [][]string) {
181+
columnLengths := calculateColumnLengths(table)
182+
183+
var lineLength int
184+
185+
additionalChars := 3 // +3 for 3 additional characters before and after each field: "| %s "
186+
for _, c := range columnLengths {
187+
lineLength += c + additionalChars // +3 for 3 additional characters before and after each field: "| %s "
188+
}
189+
190+
lineLength++ // +1 for the last "|" in the line
191+
singleLineLength := lineLength - len("++") // -2 because of "+" as first and last character
192+
193+
for lineIndex, line := range table {
194+
if lineIndex == 0 { // table header
195+
// lineLength-2 because of "+" as first and last charactr
196+
ui.PrintMessage(fmt.Sprintf("+%s+\n", strings.Repeat("-", singleLineLength)), ui.MessageTypeInfo)
197+
}
198+
199+
lineLoop:
200+
for rowIndex, val := range line {
201+
if val == "SEP" {
202+
// lineLength-2 because of "+" as first and last character
203+
ui.PrintMessage(fmt.Sprintf("+%s+\n", strings.Repeat("-", singleLineLength)), ui.MessageTypeInfo)
204+
break lineLoop
205+
}
206+
207+
ui.PrintMessage(fmt.Sprintf("| %-*s ", columnLengths[rowIndex], val), ui.MessageTypeInfo)
208+
if rowIndex == len(line)-1 {
209+
ui.PrintMessage("|\n", ui.MessageTypeInfo)
210+
}
211+
}
212+
213+
if lineIndex == 0 || lineIndex == len(table)-1 { // table header or last line
214+
// lineLength-2 because of "+" as first and last character
215+
ui.PrintMessage(fmt.Sprintf("+%s+\n", strings.Repeat("-", singleLineLength)), ui.MessageTypeInfo)
216+
}
217+
}
218+
}
219+
220+
func calculateColumnLengths(table [][]string) []int {
221+
columnLengths := make([]int, len(table[0]))
222+
223+
for _, line := range table {
224+
for i, val := range line {
225+
if len(val) > columnLengths[i] {
226+
columnLengths[i] = len(val)
227+
}
228+
}
229+
}
230+
231+
return columnLengths
232+
}

0 commit comments

Comments
 (0)