github.com/mithrandie/csvq@v1.18.1/lib/option/environment.go (about) 1 package option 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "os" 11 "path/filepath" 12 "time" 13 14 "github.com/mitchellh/go-homedir" 15 "github.com/mithrandie/csvq/lib/file" 16 "github.com/mithrandie/go-text/color" 17 ) 18 19 const ( 20 XDGConfigHomeEnvName = "XDG_CONFIG_HOME" 21 DefaultXDGConfigDir = ".config" 22 CSVQConfigDir = "csvq" 23 EnvFileName = "csvq_env.json" 24 PreloadCommandFileName = "csvqrc" 25 26 HiddenPrefix = '.' 27 ) 28 29 type Environment struct { 30 DatetimeFormat []string `json:"datetime_format"` 31 Timezone *string `json:"timezone"` 32 AnsiQuotes *bool `json:"ansi_quotes"` 33 InteractiveShell InteractiveShell `json:"interactive_shell"` 34 EnvironmentVariables map[string]string `json:"environment_variables"` 35 Palette color.PaletteConfig `json:"palette"` 36 } 37 38 func NewEnvironment(ctx context.Context, defaultWaitTimeout time.Duration, retryDelay time.Duration) (*Environment, error) { 39 env := &Environment{} 40 if err := json.Unmarshal([]byte(DefaultEnvJson), env); err != nil { 41 err = errors.New(fmt.Sprintf("`json syntax error: %s", err.Error())) 42 return nil, err 43 } 44 45 err := env.Load(ctx, defaultWaitTimeout, retryDelay) 46 return env, err 47 } 48 49 func (e *Environment) Merge(e2 *Environment) { 50 for _, f := range e2.DatetimeFormat { 51 e.DatetimeFormat = AppendStrIfNotExist(e.DatetimeFormat, f) 52 } 53 54 if e2.Timezone != nil { 55 e.Timezone = e2.Timezone 56 } 57 58 if e2.AnsiQuotes != nil { 59 e.AnsiQuotes = e2.AnsiQuotes 60 } 61 62 if 0 < len(e2.InteractiveShell.HistoryFile) { 63 e.InteractiveShell.HistoryFile = e2.InteractiveShell.HistoryFile 64 } 65 66 if e2.InteractiveShell.HistoryLimit != nil { 67 e.InteractiveShell.HistoryLimit = e2.InteractiveShell.HistoryLimit 68 } 69 70 if 0 < len(e2.InteractiveShell.Prompt) { 71 e.InteractiveShell.Prompt = e2.InteractiveShell.Prompt 72 } 73 74 if 0 < len(e2.InteractiveShell.ContinuousPrompt) { 75 e.InteractiveShell.ContinuousPrompt = e2.InteractiveShell.ContinuousPrompt 76 } 77 78 if e2.InteractiveShell.Completion != nil { 79 e.InteractiveShell.Completion = e2.InteractiveShell.Completion 80 } 81 82 if e2.InteractiveShell.KillWholeLine != nil { 83 e.InteractiveShell.KillWholeLine = e2.InteractiveShell.KillWholeLine 84 } 85 86 if e2.InteractiveShell.ViMode != nil { 87 e.InteractiveShell.ViMode = e2.InteractiveShell.ViMode 88 } 89 90 for k, v := range e2.EnvironmentVariables { 91 e.EnvironmentVariables[k] = v 92 } 93 94 for k, v := range e2.Palette.Effectors { 95 e.Palette.Effectors[k] = v 96 } 97 } 98 99 type InteractiveShell struct { 100 HistoryFile string `json:"history_file"` 101 HistoryLimit *int `json:"history_limit"` 102 Prompt string `json:"prompt"` 103 ContinuousPrompt string `json:"continuous_prompt"` 104 Completion *bool `json:"completion"` 105 KillWholeLine *bool `json:"kill_whole_line"` 106 ViMode *bool `json:"vi_mode"` 107 } 108 109 func (e *Environment) Load(ctx context.Context, defaultWaitTimeout time.Duration, retryDelay time.Duration) (err error) { 110 container := file.NewContainer() 111 defer func() { 112 if e := container.CloseAll(); e != nil { 113 if err == nil { 114 err = e 115 } else { 116 err = errors.New(err.Error() + "\n" + e.Error()) 117 } 118 } 119 }() 120 121 files := GetSpecialFilePath(EnvFileName) 122 for _, fpath := range files { 123 if !file.Exists(fpath) { 124 continue 125 } 126 127 var h *file.Handler 128 var buf []byte 129 130 h, err = container.CreateHandlerWithoutLock(ctx, fpath, defaultWaitTimeout, retryDelay) 131 if err != nil { 132 return 133 } 134 135 buf, err = io.ReadAll(h.File()) 136 if err != nil { 137 err = errors.New(fmt.Sprintf("failed to load %q: %s", fpath, err.Error())) 138 return 139 } 140 buf = bytes.TrimSuffix(buf, []byte{0x00}) 141 userDefinedEnv := &Environment{} 142 if err = json.Unmarshal(buf, userDefinedEnv); err != nil { 143 err = errors.New(fmt.Sprintf("failed to load %q: %s", fpath, err.Error())) 144 return 145 } 146 147 e.Merge(userDefinedEnv) 148 } 149 150 for k, v := range e.EnvironmentVariables { 151 if e := os.Setenv(k, v); e != nil { 152 if err == nil { 153 err = e 154 } else { 155 err = errors.New(err.Error() + "\n" + e.Error()) 156 } 157 } 158 } 159 160 return 161 } 162 163 func GetSpecialFilePath(filename string) []string { 164 files := make([]string, 0, 4) 165 files = AppendStrIfNotExist(files, GetConfigDirFilePath(filename)) 166 files = AppendStrIfNotExist(files, GetHomeDirFilePath(filename)) 167 files = AppendStrIfNotExist(files, GetCSVQConfigDirFilePath(filename)) 168 files = AppendStrIfNotExist(files, GetCurrentDirFilePath(filename)) 169 return files 170 } 171 172 func GetHomeDirFilePath(filename string) string { 173 home, err := homedir.Dir() 174 if err != nil { 175 return filename 176 } 177 178 if 0 < len(filename) && filename[0] != HiddenPrefix { 179 filename = string(HiddenPrefix) + filename 180 } 181 182 return filepath.Join(home, filename) 183 } 184 185 func GetCSVQConfigDirFilePath(filename string) string { 186 home, err := homedir.Dir() 187 if err != nil { 188 return filename 189 } 190 191 return filepath.Join(home, string(HiddenPrefix)+CSVQConfigDir, filename) 192 } 193 194 func GetConfigDirFilePath(filename string) string { 195 configHome := os.Getenv(XDGConfigHomeEnvName) 196 if len(configHome) < 1 { 197 home, err := homedir.Dir() 198 if err != nil { 199 return filename 200 } 201 configHome = filepath.Join(home, DefaultXDGConfigDir) 202 } 203 204 return filepath.Join(configHome, CSVQConfigDir, filename) 205 } 206 207 func GetCurrentDirFilePath(filename string) string { 208 if !filepath.IsAbs(filename) { 209 if abs, err := filepath.Abs(filename); err == nil { 210 filename = abs 211 } 212 } 213 return filename 214 }