package main import ( "ccat/stack" "embed" "errors" "io/fs" "os" "os/user" "path/filepath" "regexp" "runtime" "strings" "gopkg.in/yaml.v2" ) //go:embed config var storedConfigs embed.FS // Embed the folder containing config files func runningOnWindows() bool { return runtime.GOOS == "windows" } // generateDefaultConfigs is used to generate a folder of default config files // for common languages. These default config files are embedded into the program, and will // be outputted into a directory. // // If there is an error encountered, the error is returned. func generateDefaultConfigs() error { var configOutputPath string // Location of config files, depends on OS if runningOnWindows() { configOutputPath = "%APPDATA%\\ccat" } else { currentUser, err := user.Current() if err != nil { panic(err) } configOutputPath = filepath.Join("/home/" + currentUser.Username + "/.config/ccat/") } err := os.MkdirAll(configOutputPath, 0755) if err != nil { if os.IsExist(err) { return errors.New("Directory already exists.") } else { return errors.New("Unable to create directory.") } } // Copy each folder from the embedded filesystem, into the destination path err = fs.WalkDir(storedConfigs, "config", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { // Skip directories return nil } relPath, _ := filepath.Rel("config", path) dstPath := filepath.Join(configOutputPath, relPath) // Destination path data, err := os.ReadFile(path) if err != nil { return err } if err := os.WriteFile(dstPath, data, 0644); err != nil { return err } return nil }) return nil } // loadConfig takes in the filename of a config file. It reads the file, // and returns a stack of RegColors, with the item at the bottom being the one that // was read first. This ensures that, _when accessing the RegColors in the stack, the last // one (ie. the one that was read first) has highest precedence_. // If there is an error compiling the regular expressions, the error is returned. func loadConfig(configFilename string) (stack.Stack[regColor], error) { configFile, err := os.ReadFile(configFilename) if err != nil { return *stack.NewStack[regColor](0), err } // Here, I create a MapSlice. This is a slice of key-value pairs, and will // store the results of unmarshalling the YAML file. tempMapSlice := yaml.MapSlice{} if err := yaml.Unmarshal(configFile, &tempMapSlice); err != nil { return *stack.NewStack[regColor](0), err } // Here, I create the stack which will eventually be returned. // Each element of the MapSlice (created above) stores the key and value of a line // in the file. // Each regex string is compiled, and if there is an error, that error is // returned. regColorStack := stack.NewStack[regColor](len(strings.Split(string(configFile), "\n"))) // The stack will have the same size as the number of lines in the file for _, item := range tempMapSlice { re := regexp.MustCompile(item.Key.(string)) clr, err := newColor(item.Value.(string)) if err != nil { return *stack.NewStack[regColor](0), err } // If we got past the errors, then the color _must_ be valid. regColorStack.Push(regColor{re, clr}) } return *regColorStack, nil }