Skip to content

Commit fea94ff

Browse files
committed
feat: add new config command and streamline flags
This change introduces the concept of configurable settings in the main cwc configuration. The rationale behind this change is to enable more configurable options without polluting the commandline interface with more flags. The configuration can be managed manually by editing the `cwc.yaml` config file or by using the `cwc config set` command. - Remove `--api-version` flag as it's now hardcoded The apiVersion config is confusing the end-user where they conflate the azure api version with the openai model version. The azure api version is dictating the capabilities of the API and should therefore be managed by the cwc system and not the end user. - Add `config` cmd to the root command for managing CWC configurations - Update `README.md` to include configuration management section - Refactor `cmd/cwc.go` to remove obsolete flags related to gitignore and git directory exclusion, configuring through `cwc config` - Modify error handling in `main.go` to suppress certain error messages - Change config file format from JSON to YAML - Introduce constants for API version and config file properties - Improve error types with specific checks for template and suppressed errors - Amend `login.go` to adapt to the removal of the `--api-version` flag and use the new config structure - Remove unnecessary error message handling in `config.go` related to unmarshalling config data
1 parent 8b3b28f commit fea94ff

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)