github.com/wtfutil/wtf@v0.43.0/cfg/config_files.go (about)

     1  package cfg
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"os/user"
     8  	"path/filepath"
     9  
    10  	"github.com/olebedev/config"
    11  )
    12  
    13  const (
    14  	// XdgConfigDir defines the path to the minimal XDG-compatible configuration directory
    15  	XdgConfigDir = "~/.config/"
    16  
    17  	// WtfConfigDirV1 defines the path to the first version of configuration. Do not use this
    18  	WtfConfigDirV1 = "~/.wtf/"
    19  
    20  	// WtfConfigDirV2 defines the path to the second version of the configuration. Use this.
    21  	WtfConfigDirV2 = "~/.config/wtf/"
    22  
    23  	// WtfConfigFile defines the name of the default config file
    24  	WtfConfigFile = "config.yml"
    25  )
    26  
    27  /* -------------------- Exported Functions -------------------- */
    28  
    29  // CreateFile creates the named file in the config directory, if it does not already exist.
    30  // If the file exists it does not recreate it.
    31  // If successful, returns the absolute path to the file
    32  // If unsuccessful, returns an error
    33  func CreateFile(fileName string) (string, error) {
    34  	configDir, err := WtfConfigDir()
    35  	if err != nil {
    36  		return "", err
    37  	}
    38  
    39  	filePath := filepath.Join(configDir, fileName)
    40  
    41  	// Check if the file already exists; if it does not, create it
    42  	_, err = os.Stat(filePath)
    43  	if err != nil {
    44  		if os.IsNotExist(err) {
    45  			_, err = os.Create(filePath)
    46  			if err != nil {
    47  				return "", err
    48  			}
    49  		} else {
    50  			return "", err
    51  		}
    52  	}
    53  
    54  	return filePath, nil
    55  }
    56  
    57  // Initialize takes care of settings up the initial state of WTF configuration
    58  // It ensures necessary directories and files exist
    59  func Initialize(hasCustom bool) {
    60  	if !hasCustom {
    61  		migrateOldConfig()
    62  	}
    63  
    64  	// These always get created because this is where modules should write any permanent
    65  	// data they need to persist between runs (i.e.: log, textfile, etc.)
    66  	createWtfConfigDir()
    67  
    68  	if !hasCustom {
    69  		createWtfConfigFile()
    70  		chmodConfigFile()
    71  	}
    72  }
    73  
    74  // WtfConfigDir returns the absolute path to the configuration directory
    75  func WtfConfigDir() (string, error) {
    76  	configDir := os.Getenv("XDG_CONFIG_HOME")
    77  	if configDir == "" {
    78  		configDir = WtfConfigDirV2
    79  	} else {
    80  		configDir += "/wtf/"
    81  	}
    82  	configDir, err := expandHomeDir(configDir)
    83  	if err != nil {
    84  		return "", err
    85  	}
    86  
    87  	return configDir, nil
    88  }
    89  
    90  // LoadWtfConfigFile loads the specified config file
    91  func LoadWtfConfigFile(filePath string) *config.Config {
    92  	absPath, _ := expandHomeDir(filePath)
    93  
    94  	cfg, err := config.ParseYamlFile(absPath)
    95  	if err != nil {
    96  		displayWtfConfigFileLoadError(absPath, err)
    97  		os.Exit(1)
    98  	}
    99  
   100  	return cfg
   101  }
   102  
   103  /* -------------------- Unexported Functions -------------------- */
   104  
   105  // chmodConfigFile sets the mode of the config file to r+w for the owner only
   106  func chmodConfigFile() {
   107  	configDir, _ := WtfConfigDir()
   108  	relPath := filepath.Join(configDir, WtfConfigFile)
   109  	absPath, _ := expandHomeDir(relPath)
   110  
   111  	_, err := os.Stat(absPath)
   112  	if err != nil && os.IsNotExist(err) {
   113  		return
   114  	}
   115  
   116  	err = os.Chmod(absPath, 0600)
   117  	if err != nil {
   118  		return
   119  	}
   120  }
   121  
   122  // createWtfConfigDir creates the necessary directories for storing the default config file
   123  // If ~/.config/wtf is missing, it will try to create it
   124  func createWtfConfigDir() {
   125  	wtfConfigDir, _ := WtfConfigDir()
   126  
   127  	if _, err := os.Stat(wtfConfigDir); os.IsNotExist(err) {
   128  		err := os.MkdirAll(wtfConfigDir, os.ModePerm)
   129  		if err != nil {
   130  			displayWtfConfigDirCreateError(err)
   131  			os.Exit(1)
   132  		}
   133  	}
   134  }
   135  
   136  // createWtfConfigFile creates a simple config file in the config directory if
   137  // one does not already exist
   138  func createWtfConfigFile() {
   139  	filePath, err := CreateFile(WtfConfigFile)
   140  	if err != nil {
   141  		displayDefaultConfigCreateError(err)
   142  		os.Exit(1)
   143  	}
   144  
   145  	// If the file is empty, write to it
   146  	file, _ := os.Stat(filePath)
   147  
   148  	if file.Size() == 0 {
   149  		if os.WriteFile(filePath, []byte(defaultConfigFile), 0600) != nil {
   150  			displayDefaultConfigWriteError(err)
   151  			os.Exit(1)
   152  		}
   153  	}
   154  }
   155  
   156  // Expand expands the path to include the home directory if the path
   157  // is prefixed with `~`. If it isn't prefixed with `~`, the path is
   158  // returned as-is.
   159  func expandHomeDir(path string) (string, error) {
   160  	if path == "" {
   161  		return path, nil
   162  	}
   163  
   164  	if path[0] != '~' {
   165  		return path, nil
   166  	}
   167  
   168  	if len(path) > 1 && path[1] != '/' && path[1] != '\\' {
   169  		return "", errors.New("cannot expand user-specific home dir")
   170  	}
   171  
   172  	dir, err := home()
   173  	if err != nil {
   174  		return "", err
   175  	}
   176  
   177  	return filepath.Join(dir, path[1:]), nil
   178  }
   179  
   180  // Dir returns the home directory for the executing user.
   181  // An error is returned if a home directory cannot be detected.
   182  func home() (string, error) {
   183  	currentUser, err := user.Current()
   184  	if err != nil {
   185  		return "", err
   186  	}
   187  	if currentUser.HomeDir == "" {
   188  		return "", errors.New("cannot find user-specific home dir")
   189  	}
   190  
   191  	return currentUser.HomeDir, nil
   192  }
   193  
   194  // migrateOldConfig copies any existing configuration from the old location
   195  // to the new, XDG-compatible location
   196  func migrateOldConfig() {
   197  	srcDir, _ := expandHomeDir(WtfConfigDirV1)
   198  	destDir, _ := WtfConfigDir()
   199  
   200  	// If the old config directory doesn't exist, do not move
   201  	if _, err := os.Stat(srcDir); os.IsNotExist(err) {
   202  		return
   203  	}
   204  
   205  	// If the new config directory already exists, do not move
   206  	if _, err := os.Stat(destDir); err == nil {
   207  		return
   208  	}
   209  
   210  	// Time to move
   211  	err := Copy(srcDir, destDir)
   212  	if err != nil {
   213  		panic(err)
   214  	}
   215  
   216  	// Delete the old directory if the new one exists
   217  	if _, err := os.Stat(destDir); err == nil {
   218  		err := os.RemoveAll(srcDir)
   219  		if err != nil {
   220  			fmt.Println(err)
   221  		}
   222  	}
   223  }