-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutil.go
More file actions
314 lines (278 loc) · 8.67 KB
/
util.go
File metadata and controls
314 lines (278 loc) · 8.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
package pathlib
import (
"encoding/csv"
"encoding/json"
"fmt"
"io"
"os"
"os/user"
"path/filepath"
"runtime"
"strings"
)
var (
_, b, _, _ = runtime.Caller(0)
// rootDir folder of this project, you must config this in every project, this is just a demo usage.
rootDir = filepath.Join(filepath.Dir(b), "../..")
)
var userLookup = os.UserHomeDir // This can be overridden in tests
// ResolveAbsPath takes a file path and returns its absolute path.
//
// This function first expands any environment variables or user home directory
// references in the given path. Then, it converts the expanded path to an
// absolute path without resolving symbolic links.
//
// Parameters:
// - filePath: A string representing the file path to be converted.
//
// Returns:
// - string: The absolute path of the input file path.
//
// If the function encounters an error while converting to an absolute path,
// it returns the expanded path instead.
//
// Example:
//
// absPath := ResolveAbsPath("~/documents/file.txt")
// // On a Unix system, this might return "/home/user/documents/file.txt"
//
// Note: This function does not check if the path actually exists in the file system.
func ResolveAbsPath(filePath string) (string, error) {
// Expand the path first
expandedPath := Expand(filePath)
// Convert to absolute path without resolving symlinks
absPath, err := filepath.Abs(expandedPath)
if err != nil {
// If we can't get the absolute path, use the expanded path
return "", err
}
return absPath, nil
}
// Home returns the home directory of the current user.
func Home() (*FsPath, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}
return Path(home), nil
}
// Cwd returns a new FsPath representing the current working directory.
func Cwd() (*FsPath, error) {
dir, err := os.Getwd()
if err != nil {
return nil, err
}
return Path(dir), nil
}
// Expand takes a file path and expands any environment variables and user home directory references.
//
// This function performs the following expansions:
// 1. Expands the user's home directory (e.g., "~" or "~username")
// 2. Expands environment variables (e.g., "$HOME" or "${HOME}")
//
// The function first expands the user's home directory using ExpandUser,
// then expands any environment variables using os.ExpandEnv.
//
// Parameters:
// - path: A string representing the file path to be expanded.
//
// Returns:
// - string: The expanded path.
//
// Example:
//
// expanded := Expand("~/documents/$USER/file.txt")
// // This might return "/home/username/documents/username/file.txt"
//
// Note: This function does not check if the expanded path actually exists in the file system.
func Expand(path string) string {
// Return immediately if path is empty
if path == "" {
return path
}
// Handle home directory expansion first
expandedPath := ExpandUser(path)
// Protect escaped dollar signs
expandedPath = strings.ReplaceAll(expandedPath, "\\$", "\u0001")
// Expand environment variables
expandedPath = os.ExpandEnv(expandedPath)
// Restore protected dollar signs
expandedPath = strings.ReplaceAll(expandedPath, "\u0001", "$")
return expandedPath
}
// ExpandUser replaces a leading ~ or ~user with the user's home directory.
//
// This function handles the following cases:
// 1. "~" or "~/..." expands to the current user's home directory
// 2. "~username" or "~username/..." expands to the specified user's home directory
//
// If the user's home directory cannot be determined, the original path is returned.
//
// Parameters:
// - path: A string representing the file path to be expanded.
//
// Returns:
// - string: The expanded path with the user's home directory.
//
// Example:
//
// expanded := ExpandUser("~/documents")
// // This might return "/home/username/documents"
//
// expanded := ExpandUser("~otheruser/documents")
// // This might return "/home/otheruser/documents"
//
// Note: This function does not expand environment variables. Use Expand for full expansion.
func ExpandUser(path string) string {
if !strings.HasPrefix(path, "~") {
return path
}
var (
homeDir string
err error
)
if path == "~" || strings.HasPrefix(path, "~/") {
homeDir, err = userLookup()
if err != nil {
return path
}
if path == "~" {
return homeDir
}
return filepath.Join(homeDir, path[2:])
}
// Handle ~user case
parts := strings.SplitN(path[1:], "/", 2)
username := parts[0]
restPath := ""
if len(parts) > 1 {
restPath = parts[1]
}
homeDir, err = getUserHomeDir(username)
if err != nil {
return path
}
return filepath.Join(homeDir, restPath)
}
func getUserHomeDir(username string) (string, error) {
u, err := user.Lookup(username)
if err != nil {
return filepath.Join("/home", username), err
}
return u.HomeDir, nil
}
// IsJSON checks if the given byte slice contains valid JSON data.
//
// This function attempts to unmarshal the input data into a json.RawMessage.
// If the unmarshal operation succeeds, it means the data is valid JSON.
//
// Parameters:
// - data: A byte slice containing the data to be checked.
//
// Returns:
// - bool: true if the data is valid JSON, false otherwise.
//
// The function returns true for valid JSON structures including objects,
// arrays, strings, numbers, booleans, and null. It returns false for any
// input that cannot be parsed as valid JSON.
//
// Example:
//
// validJSON := []byte(`{"name": "John", "age": 30}`)
// fmt.Println(IsJSON(validJSON)) // Output: true
//
// invalidJSON := []byte(`{"name": "John", "age": }`)
// fmt.Println(IsJSON(invalidJSON)) // Output: false
//
// Note: This function does not provide information about why the JSON might
// be invalid. For more detailed error information, use json.Unmarshal directly.
func IsJSON(data []byte) bool {
var js json.RawMessage
return json.Unmarshal(data, &js) == nil
}
// StructToJSONMap converts a struct to a map[string]interface{} representation of JSON.
// This function uses JSON marshaling and unmarshaling to perform the conversion,
// which means it respects JSON tags and only includes exported fields.
//
// Parameters:
// - src: The source struct to convert.
//
// Returns:
// - map[string]interface{}: A map representing the JSON structure of the input struct.
// - error: An error if the conversion process fails.
//
// Example usage:
//
// type Person struct {
// Name string `json:"name"`
// Age int `json:"age"`
// }
//
// person := Person{Name: "John Doe", Age: 30}
// jsonMap, err := StructToJSONMap(person)
// if err != nil {
// // Handle error
// }
// // jsonMap will be: map[string]interface{}{"name": "John Doe", "age": 30}
//
// Note: This function will only include fields that would be marshaled to JSON.
// Unexported fields and fields with `json:"-"` tags will be omitted.
func StructToJSONMap(src any) (map[string]any, error) {
jsonData, err := json.Marshal(src)
if err != nil {
return nil, fmt.Errorf("failed to marshal struct to JSON: %w", err)
}
// Then unmarshal the JSON to a map
var result map[string]any
err = json.Unmarshal(jsonData, &result)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON to map: %w", err)
}
return result, nil
}
// SanitizeJSON cleans a JSON string by removing empty fields.
// It unmarshals the raw JSON into the provided struct template,
// then marshals it back to JSON, effectively removing any empty or zero-value fields.
//
// Parameters:
// - rawJSON: The input JSON string to sanitize.
// - template: A pointer to a struct that defines the expected schema of the JSON.
//
// Returns:
// - []byte: The sanitized JSON as a byte slice.
// - error: Any error encountered during the process.
//
// Example usage:
//
// type Person struct {
// Name string `json:"name,omitempty"`
// Age int `json:"age,omitempty"`
// }
//
// input := `{"name":"John","age":0,"extra":""}`
// cleaned, err := SanitizeJSON(input, &Person{})
// if err != nil {
// // Handle error
// }
// // cleaned will be: `{"name":"John"}`
//
// Note: This function relies on the `omitempty` tag for fields that should be
// removed when empty. Make sure your struct is properly tagged for the desired behavior.
func SanitizeJSON(rawJSON string, template any) ([]byte, error) {
// Unmarshal raw JSON into the provided struct template
if err := json.Unmarshal([]byte(rawJSON), template); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
}
// Marshal the struct back to JSON, which will omit empty fields
sanitized, err := json.Marshal(template)
if err != nil {
return nil, fmt.Errorf("failed to marshal sanitized struct: %w", err)
}
return sanitized, nil
}
func ToSlices(reader io.Reader, separator rune) ([][]string, error) {
r := csv.NewReader(reader)
r.Comma = separator
r.Comment = '#'
return r.ReadAll()
}