// Copyright 2022 schukai GmbH // SPDX-License-Identifier: AGPL-3.0 package configuration import ( "io/fs" "os" "path" "path/filepath" "golang.org/x/exp/slices" ) type files struct { path string format Format } type fileBackend struct { directories []string files []files format Format name string fs fs.FS } func (s *Settings[c]) HasFile(file string) bool { for _, f := range s.files.files { if f.path == file { return true } } return false } // AddFile adds a file to the list of files to import func (s *Settings[C]) AddFile(file string, format ...Format) *Settings[C] { var f Format errorCount := len(s.errors) defer func() { if len(s.errors) > errorCount { s.notifyErrorHooks() } }() if format == nil || len(format) == 0 { f = RecogniseFormat } else if format != nil && len(format) == 1 { f = format[0] } else { panic("too many arguments") } if f == RecogniseFormat { ext := filepath.Ext(file) switch ext { case ".yaml": f = Yaml case ".json": f = Json case ".toml": f = Toml case ".properties": f = Properties default: if ext != "" { s.errors = append(s.errors, newUnknownFileExtensionError(ext, file)) return s } f = s.files.format } } s.files.files = append(s.files.files, files{file, f}) return s } func (s *Settings[C]) RemoveFile(file string) *Settings[C] { for i, f := range s.files.files { if f.path == file { s.files.files = append(s.files.files[:i], s.files.files[i+1:]...) return s } } return s } func initFileBackend(files *fileBackend) { files.format = Yaml files.name = fileName files.fs = fs.FS(internalFS{}) } // Directories returns the list of directories to search for configuration files func (s *Settings[C]) Directories() []string { return s.files.directories } // AddDirectory adds a directory to the list of directories to search for configuration files func (s *Settings[C]) AddDirectory(d string) *Settings[C] { s.Lock() defer s.Unlock() s.files.directories = append(s.files.directories, d) s.sanitizeDirectories() return s } func (s *Settings[C]) sanitizeDirectories() { errorCount := len(s.errors) defer func() { if len(s.errors) > errorCount { s.notifyErrorHooks() } }() wd, err := os.Getwd() if err != nil { s.errors = append(s.errors, err) } visited := make(map[string]bool, 0) for i := 0; i < len(s.files.directories); i++ { d := s.files.directories[i] if !path.IsAbs(d) { d = path.Join(wd, d) } if visited[d] == true { s.errors = append(s.errors, newPathAlreadyExistsError(d)) } else { visited[d] = true } } // check last entry d := s.files.directories[len(s.files.directories)-1] if _, err := os.Stat(d); os.IsNotExist(err) { s.errors = append(s.errors, newPathDoesNotExistError(d)) } } // SetDirectories sets the list of directories to search for configuration files func (s *Settings[C]) SetDirectories(d []string) *Settings[C] { s.Lock() defer s.Unlock() s.files.directories = d s.sanitizeDirectories() return s } // AddWorkingDirectory adds the current working directory to the list of directories to search for configuration files func (s *Settings[C]) AddWorkingDirectory() *Settings[C] { s.Lock() defer s.Unlock() errorCount := len(s.errors) defer func() { if len(s.errors) > errorCount { s.notifyErrorHooks() } }() current, err := os.Getwd() if err != nil { s.errors = append(s.errors, err) return s } s.files.directories = append(s.files.directories, current) s.sanitizeDirectories() return s } // AddEtcDirectory adds the /etc directory to the list of directories to search for configuration files func (s *Settings[C]) AddEtcDirectory() *Settings[C] { s.Lock() defer s.Unlock() errorCount := len(s.errors) defer func() { if len(s.errors) > errorCount { s.notifyErrorHooks() } }() if s.mnemonic == "" { s.errors = append(s.errors, MnemonicEmptyError) return s } p := "/etc" if _, err := os.Stat(p); os.IsNotExist(err) { s.errors = append(s.errors, newPathDoesNotExistError(p)) return s } p = path.Join(p, s.mnemonic) s.files.directories = append(s.files.directories, p) s.sanitizeDirectories() return s } // AddUserConfigDirectory Add the user configuration directory to the configuration directory // The mnemonic must be set for this function func (s *Settings[C]) AddUserConfigDirectory() *Settings[C] { s.Lock() defer s.Unlock() errorCount := len(s.errors) defer func() { if len(s.errors) > errorCount { s.notifyErrorHooks() } }() if s.mnemonic == "" { s.errors = append(s.errors, MnemonicEmptyError) return s } current, err := os.UserConfigDir() if err != nil { s.errors = append(s.errors, err) return s } current = path.Join(current, s.mnemonic) s.files.directories = append(s.files.directories, current) s.sanitizeDirectories() return s } // SetDefaultDirectories Add the current working directory, the user configuration directory // and the Unix etc directory to the configuration directory func (s *Settings[C]) SetDefaultDirectories() *Settings[C] { s.AddWorkingDirectory(). AddUserConfigDirectory(). AddEtcDirectory() return s } func (s *Settings[C]) SetFileFormat(format Format) *Settings[C] { errorCount := len(s.errors) defer func() { if len(s.errors) > errorCount { s.notifyErrorHooks() } }() if slices.Contains(availableFormats, format) { s.files.format = format } else { s.errors = append(s.errors, newFormatNotSupportedError(format)) } return s } // SetFileName sets the name of the configuration file func (s *Settings[C]) SetFileName(name string) *Settings[C] { errorCount := len(s.errors) defer func() { if len(s.errors) > errorCount { s.notifyErrorHooks() } }() if name == "" { s.errors = append(s.errors, FileNameEmptyError) } else { s.files.name = name } return s } func (s *Settings[C]) SetFilesystem(f fs.FS) *Settings[C] { s.files.fs = f return s }