|
6 | 6 | "os" |
7 | 7 | "path/filepath" |
8 | 8 | "strings" |
| 9 | + |
| 10 | + v3 "github.com/harness/godotenv/v3" |
9 | 11 | ) |
10 | 12 |
|
11 | 13 | const ( |
@@ -79,54 +81,80 @@ func SetErrorMetadata(message, code, category string) error { |
79 | 81 | } |
80 | 82 |
|
81 | 83 | // UpdateOrRemoveKeyValue updates or deletes a key-value pair in the specified file. |
82 | | -func UpdateOrRemoveKeyValue(envVar, key, newValue string, delete bool) error { |
83 | | - // Get the file path from the environment variable |
| 84 | +func UpdateOrRemoveKeyValue(envVar, key, newValue string, deleteKey bool) error { |
84 | 85 | filePath := os.Getenv(envVar) |
85 | 86 | if filePath == "" { |
86 | 87 | return fmt.Errorf("environment variable %s is not set", envVar) |
87 | 88 | } |
88 | 89 |
|
89 | 90 | // Ensure the file exists before reading |
90 | 91 | if _, err := os.Stat(filePath); os.IsNotExist(err) { |
91 | | - // Create the file if it does not exist |
92 | 92 | _, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) |
93 | 93 | if err != nil { |
94 | 94 | return fmt.Errorf("failed to create file: %w", err) |
95 | 95 | } |
96 | 96 | } |
97 | 97 |
|
98 | | - // Determine the file extension to handle formats |
| 98 | + // Trim trailing newline characters from newValue |
| 99 | + newValue = strings.TrimRight(newValue, "\n") |
| 100 | + |
99 | 101 | ext := strings.ToLower(filepath.Ext(filePath)) |
100 | 102 |
|
101 | | - // Read the file contents into memory |
102 | | - lines, err := ReadLines(filePath) |
103 | | - if err != nil { |
104 | | - return fmt.Errorf("failed to read file: %w", err) |
105 | | - } |
| 103 | + if ext == ".env" { |
| 104 | + // Use godotenv for .env files |
| 105 | + data, err := v3.Read(filePath) |
| 106 | + if err != nil { |
| 107 | + return fmt.Errorf("failed to parse .env file: %w", err) |
| 108 | + } |
106 | 109 |
|
107 | | - // Process lines |
108 | | - var updatedLines []string |
109 | | - found := false |
110 | | - for _, line := range lines { |
111 | | - k, v := ParseKeyValue(line, ext) |
112 | | - if k == key { |
113 | | - found = true |
114 | | - if delete { |
115 | | - continue // Skip the line to delete it |
116 | | - } |
117 | | - updatedLines = append(updatedLines, FormatKeyValue(k, newValue, ext)) |
| 110 | + if deleteKey { |
| 111 | + delete(data, key) |
118 | 112 | } else { |
119 | | - updatedLines = append(updatedLines, FormatKeyValue(k, v, ext)) |
| 113 | + data[key] = newValue |
120 | 114 | } |
121 | | - } |
122 | 115 |
|
123 | | - // Append new key-value if not found and not deleting |
124 | | - if !found && !delete { |
125 | | - updatedLines = append(updatedLines, FormatKeyValue(key, newValue, ext)) |
| 116 | + err = v3.Write(data, filePath) |
| 117 | + if err != nil { |
| 118 | + return fmt.Errorf("failed to write .env file: %w", err) |
| 119 | + } |
| 120 | + } else { |
| 121 | + // For .out files, process manually |
| 122 | + // For .out files, check for multiline values |
| 123 | + if strings.Contains(newValue, "\n") { |
| 124 | + return fmt.Errorf("multiline values are not allowed for key %s in .out file", key) |
| 125 | + } |
| 126 | + |
| 127 | + lines, err := ReadLines(filePath) |
| 128 | + if err != nil { |
| 129 | + return fmt.Errorf("failed to read file: %w", err) |
| 130 | + } |
| 131 | + |
| 132 | + var updatedLines []string |
| 133 | + found := false |
| 134 | + for _, line := range lines { |
| 135 | + k, v := ParseKeyValue(line, ext) |
| 136 | + if k == key { |
| 137 | + found = true |
| 138 | + if deleteKey { |
| 139 | + continue |
| 140 | + } |
| 141 | + updatedLines = append(updatedLines, FormatKeyValue(k, newValue, ext)) |
| 142 | + } else { |
| 143 | + updatedLines = append(updatedLines, FormatKeyValue(k, v, ext)) |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + if !found && !deleteKey { |
| 148 | + updatedLines = append(updatedLines, FormatKeyValue(key, newValue, ext)) |
| 149 | + } |
| 150 | + |
| 151 | + err = WriteLines(filePath, updatedLines) |
| 152 | + if err != nil { |
| 153 | + return fmt.Errorf("failed to write file: %w", err) |
| 154 | + } |
126 | 155 | } |
127 | 156 |
|
128 | | - // Write updated lines back to the file |
129 | | - return WriteLines(filePath, updatedLines) |
| 157 | + return nil |
130 | 158 | } |
131 | 159 |
|
132 | 160 | // ReadLines reads lines from a file and returns them as a slice of strings. |
@@ -164,28 +192,22 @@ func WriteLines(filename string, lines []string) error { |
164 | 192 |
|
165 | 193 | // ParseKeyValue parses a key-value pair from a string and returns the key and value. |
166 | 194 | func ParseKeyValue(line, ext string) (string, string) { |
167 | | - if ext == ".env" { |
168 | | - parts := strings.SplitN(line, "=", 2) |
169 | | - if len(parts) == 2 { |
170 | | - return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) |
171 | | - } |
172 | | - return strings.TrimSpace(parts[0]), "" |
173 | | - } else if ext == ".out" { |
| 195 | + if ext == ".out" { |
174 | 196 | parts := strings.Fields(line) |
175 | 197 | if len(parts) > 1 { |
176 | 198 | return strings.TrimSpace(parts[0]), strings.TrimSpace(strings.Join(parts[1:], " ")) |
177 | 199 | } |
178 | 200 | return strings.TrimSpace(parts[0]), "" |
179 | 201 | } |
| 202 | + // .env is handled by godotenv, so this is not used for .env files |
180 | 203 | return "", "" |
181 | 204 | } |
182 | 205 |
|
183 | | -// FormatKeyValue formats a key-value pair into a string. |
| 206 | +// FormatKeyValue handles formatting for .env and .out files. |
184 | 207 | func FormatKeyValue(key, value, ext string) string { |
185 | | - if ext == ".env" { |
186 | | - return fmt.Sprintf("%s=%s", key, value) |
187 | | - } else if ext == ".out" { |
| 208 | + if ext == ".out" { |
188 | 209 | return fmt.Sprintf("%s %s", key, value) |
189 | 210 | } |
| 211 | + // For .env files, use godotenv directly; this function won't apply |
190 | 212 | return "" |
191 | 213 | } |
0 commit comments