github.com/andrewrech/lazygit@v0.8.1/pkg/config/app_config.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"github.com/shibukawa/configdir"
    10  	"github.com/spf13/viper"
    11  	yaml "gopkg.in/yaml.v2"
    12  )
    13  
    14  // AppConfig contains the base configuration fields required for lazygit.
    15  type AppConfig struct {
    16  	Debug       bool   `long:"debug" env:"DEBUG" default:"false"`
    17  	Version     string `long:"version" env:"VERSION" default:"unversioned"`
    18  	Commit      string `long:"commit" env:"COMMIT"`
    19  	BuildDate   string `long:"build-date" env:"BUILD_DATE"`
    20  	Name        string `long:"name" env:"NAME" default:"lazygit"`
    21  	BuildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
    22  	UserConfig  *viper.Viper
    23  	AppState    *AppState
    24  	IsNewRepo   bool
    25  }
    26  
    27  // AppConfigurer interface allows individual app config structs to inherit Fields
    28  // from AppConfig and still be used by lazygit.
    29  type AppConfigurer interface {
    30  	GetDebug() bool
    31  	GetVersion() string
    32  	GetCommit() string
    33  	GetBuildDate() string
    34  	GetName() string
    35  	GetBuildSource() string
    36  	GetUserConfig() *viper.Viper
    37  	GetAppState() *AppState
    38  	WriteToUserConfig(string, string) error
    39  	SaveAppState() error
    40  	LoadAppState() error
    41  	SetIsNewRepo(bool)
    42  	GetIsNewRepo() bool
    43  }
    44  
    45  // NewAppConfig makes a new app config
    46  func NewAppConfig(name, version, commit, date string, buildSource string, debuggingFlag bool) (*AppConfig, error) {
    47  	userConfig, err := LoadConfig("config", true)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	if os.Getenv("DEBUG") == "TRUE" {
    53  		debuggingFlag = true
    54  	}
    55  
    56  	appConfig := &AppConfig{
    57  		Name:        "lazygit",
    58  		Version:     version,
    59  		Commit:      commit,
    60  		BuildDate:   date,
    61  		Debug:       debuggingFlag,
    62  		BuildSource: buildSource,
    63  		UserConfig:  userConfig,
    64  		AppState:    &AppState{},
    65  		IsNewRepo:   false,
    66  	}
    67  
    68  	if err := appConfig.LoadAppState(); err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	return appConfig, nil
    73  }
    74  
    75  // GetIsNewRepo returns known repo boolean
    76  func (c *AppConfig) GetIsNewRepo() bool {
    77  	return c.IsNewRepo
    78  }
    79  
    80  // SetIsNewRepo set if the current repo is known
    81  func (c *AppConfig) SetIsNewRepo(toSet bool) {
    82  	c.IsNewRepo = toSet
    83  }
    84  
    85  // GetDebug returns debug flag
    86  func (c *AppConfig) GetDebug() bool {
    87  	return c.Debug
    88  }
    89  
    90  // GetVersion returns debug flag
    91  func (c *AppConfig) GetVersion() string {
    92  	return c.Version
    93  }
    94  
    95  // GetCommit returns debug flag
    96  func (c *AppConfig) GetCommit() string {
    97  	return c.Commit
    98  }
    99  
   100  // GetBuildDate returns debug flag
   101  func (c *AppConfig) GetBuildDate() string {
   102  	return c.BuildDate
   103  }
   104  
   105  // GetName returns debug flag
   106  func (c *AppConfig) GetName() string {
   107  	return c.Name
   108  }
   109  
   110  // GetBuildSource returns the source of the build. For builds from goreleaser
   111  // this will be binaryBuild
   112  func (c *AppConfig) GetBuildSource() string {
   113  	return c.BuildSource
   114  }
   115  
   116  // GetUserConfig returns the user config
   117  func (c *AppConfig) GetUserConfig() *viper.Viper {
   118  	return c.UserConfig
   119  }
   120  
   121  // GetAppState returns the app state
   122  func (c *AppConfig) GetAppState() *AppState {
   123  	return c.AppState
   124  }
   125  
   126  func newViper(filename string) (*viper.Viper, error) {
   127  	v := viper.New()
   128  	v.SetConfigType("yaml")
   129  	v.SetConfigName(filename)
   130  	return v, nil
   131  }
   132  
   133  // LoadConfig gets the user's config
   134  func LoadConfig(filename string, withDefaults bool) (*viper.Viper, error) {
   135  	v, err := newViper(filename)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  	if withDefaults {
   140  		if err = LoadDefaults(v, GetDefaultConfig()); err != nil {
   141  			return nil, err
   142  		}
   143  		if err = LoadDefaults(v, GetPlatformDefaultConfig()); err != nil {
   144  			return nil, err
   145  		}
   146  	}
   147  	if err = LoadAndMergeFile(v, filename+".yml"); err != nil {
   148  		return nil, err
   149  	}
   150  	return v, nil
   151  }
   152  
   153  // LoadDefaults loads in the defaults defined in this file
   154  func LoadDefaults(v *viper.Viper, defaults []byte) error {
   155  	return v.MergeConfig(bytes.NewBuffer(defaults))
   156  }
   157  
   158  func prepareConfigFile(filename string) (string, error) {
   159  	// chucking my name there is not for vanity purposes, the xdg spec (and that
   160  	// function) requires a vendor name. May as well line up with github
   161  	configDirs := configdir.New("jesseduffield", "lazygit")
   162  	folder := configDirs.QueryFolderContainsFile(filename)
   163  	if folder == nil {
   164  		// create the file as empty
   165  		folders := configDirs.QueryFolders(configdir.Global)
   166  		if err := folders[0].WriteFile(filename, []byte{}); err != nil {
   167  			return "", err
   168  		}
   169  		folder = configDirs.QueryFolderContainsFile(filename)
   170  	}
   171  	return filepath.Join(folder.Path, filename), nil
   172  }
   173  
   174  // LoadAndMergeFile Loads the config/state file, creating
   175  // the file has an empty one if it does not exist
   176  func LoadAndMergeFile(v *viper.Viper, filename string) error {
   177  	configPath, err := prepareConfigFile(filename)
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	v.AddConfigPath(filepath.Dir(configPath))
   183  	return v.MergeInConfig()
   184  }
   185  
   186  // WriteToUserConfig adds a key/value pair to the user's config and saves it
   187  func (c *AppConfig) WriteToUserConfig(key, value string) error {
   188  	// reloading the user config directly (without defaults) so that we're not
   189  	// writing any defaults back to the user's config
   190  	v, err := LoadConfig("config", false)
   191  	if err != nil {
   192  		return err
   193  	}
   194  
   195  	v.Set(key, value)
   196  	return v.WriteConfig()
   197  }
   198  
   199  // SaveAppState marhsalls the AppState struct and writes it to the disk
   200  func (c *AppConfig) SaveAppState() error {
   201  	marshalledAppState, err := yaml.Marshal(c.AppState)
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	filepath, err := prepareConfigFile("state.yml")
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	return ioutil.WriteFile(filepath, marshalledAppState, 0644)
   212  }
   213  
   214  // LoadAppState loads recorded AppState from file
   215  func (c *AppConfig) LoadAppState() error {
   216  	filepath, err := prepareConfigFile("state.yml")
   217  	if err != nil {
   218  		return err
   219  	}
   220  	appStateBytes, err := ioutil.ReadFile(filepath)
   221  	if err != nil {
   222  		return err
   223  	}
   224  	if len(appStateBytes) == 0 {
   225  		return yaml.Unmarshal(getDefaultAppState(), c.AppState)
   226  	}
   227  	return yaml.Unmarshal(appStateBytes, c.AppState)
   228  }
   229  
   230  // GetDefaultConfig returns the application default configuration
   231  func GetDefaultConfig() []byte {
   232  	return []byte(
   233  		`gui:
   234    ## stuff relating to the UI
   235    scrollHeight: 2
   236    scrollPastBottom: true
   237    mouseEvents: false # will default to true when the feature is complete
   238    theme:
   239      activeBorderColor:
   240        - white
   241        - bold
   242      inactiveBorderColor:
   243        - white
   244      optionsTextColor:
   245        - blue
   246    commitLength:
   247      show: true
   248    git:
   249      merging:
   250        manualCommit: false
   251      skipHookPrefix: 'WIP'
   252  update:
   253    method: prompt # can be: prompt | background | never
   254    days: 14 # how often a update is checked for
   255  reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined'
   256  confirmOnQuit: false
   257  `)
   258  }
   259  
   260  // AppState stores data between runs of the app like when the last update check
   261  // was performed and which other repos have been checked out
   262  type AppState struct {
   263  	LastUpdateCheck int64
   264  	RecentRepos     []string
   265  }
   266  
   267  func getDefaultAppState() []byte {
   268  	return []byte(`
   269      lastUpdateCheck: 0
   270      recentRepos: []
   271    `)
   272  }
   273  
   274  // // commenting this out until we use it again
   275  // func homeDirectory() string {
   276  // 	usr, err := user.Current()
   277  // 	if err != nil {
   278  // 		log.Fatal(err)
   279  // 	}
   280  // 	return usr.HomeDir
   281  // }