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  }