github.com/undoio/delve@v1.9.0/pkg/config/config.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/user"
     9  	"path"
    10  	"runtime"
    11  
    12  	"gopkg.in/yaml.v2"
    13  )
    14  
    15  const (
    16  	configDir       string = "dlv"
    17  	configDirHidden string = ".dlv"
    18  	configFile      string = "config.yml"
    19  )
    20  
    21  // SubstitutePathRule describes a rule for substitution of path to source code file.
    22  type SubstitutePathRule struct {
    23  	// Directory path will be substituted if it matches `From`.
    24  	From string
    25  	// Path to which substitution is performed.
    26  	To string
    27  }
    28  
    29  // SubstitutePathRules is a slice of source code path substitution rules.
    30  type SubstitutePathRules []SubstitutePathRule
    31  
    32  // Config defines all configuration options available to be set through the config file.
    33  type Config struct {
    34  	// Commands aliases.
    35  	Aliases map[string][]string `yaml:"aliases"`
    36  	// Source code path substitution rules.
    37  	SubstitutePath SubstitutePathRules `yaml:"substitute-path"`
    38  
    39  	// MaxStringLen is the maximum string length that the commands print,
    40  	// locals, args and vars should read (in verbose mode).
    41  	MaxStringLen *int `yaml:"max-string-len,omitempty"`
    42  	// MaxArrayValues is the maximum number of array items that the commands
    43  	// print, locals, args and vars should read (in verbose mode).
    44  	MaxArrayValues *int `yaml:"max-array-values,omitempty"`
    45  	// MaxVariableRecurse is output evaluation depth of nested struct members, array and
    46  	// slice items and dereference pointers
    47  	MaxVariableRecurse *int `yaml:"max-variable-recurse,omitempty"`
    48  	// DisassembleFlavor allow user to specify output syntax flavor of assembly, one of
    49  	// this list "intel"(default), "gnu", "go"
    50  	DisassembleFlavor *string `yaml:"disassemble-flavor,omitempty"`
    51  
    52  	// If ShowLocationExpr is true whatis will print the DWARF location
    53  	// expression for its argument.
    54  	ShowLocationExpr bool `yaml:"show-location-expr"`
    55  
    56  	// Source list line-number color, as a terminal escape sequence.
    57  	// For historic reasons, this can also be an integer color code.
    58  	SourceListLineColor interface{} `yaml:"source-list-line-color"`
    59  
    60  	// Source list arrow color, as a terminal escape sequence.
    61  	SourceListArrowColor string `yaml:"source-list-arrow-color"`
    62  
    63  	// Source list keyword color, as a terminal escape sequence.
    64  	SourceListKeywordColor string `yaml:"source-list-keyword-color"`
    65  
    66  	// Source list string color, as a terminal escape sequence.
    67  	SourceListStringColor string `yaml:"source-list-string-color"`
    68  
    69  	// Source list number color, as a terminal escape sequence.
    70  	SourceListNumberColor string `yaml:"source-list-number-color"`
    71  
    72  	// Source list comment color, as a terminal escape sequence.
    73  	SourceListCommentColor string `yaml:"source-list-comment-color"`
    74  
    75  	// number of lines to list above and below cursor when printfile() is
    76  	// called (i.e. when execution stops, listCommand is used, etc)
    77  	SourceListLineCount *int `yaml:"source-list-line-count,omitempty"`
    78  
    79  	// DebugFileDirectories is the list of directories Delve will use
    80  	// in order to resolve external debug info files.
    81  	DebugInfoDirectories []string `yaml:"debug-info-directories"`
    82  }
    83  
    84  func (c *Config) GetSourceListLineCount() int {
    85  	n := 5 // default value
    86  	lcp := c.SourceListLineCount
    87  	if lcp != nil && *lcp >= 0 {
    88  		n = *lcp
    89  	}
    90  	return n
    91  }
    92  
    93  // LoadConfig attempts to populate a Config object from the config.yml file.
    94  func LoadConfig() (*Config, error) {
    95  	err := createConfigPath()
    96  	if err != nil {
    97  		return &Config{}, fmt.Errorf("could not create config directory: %v", err)
    98  	}
    99  	fullConfigFile, err := GetConfigFilePath(configFile)
   100  	if err != nil {
   101  		return &Config{}, fmt.Errorf("unable to get config file path: %v", err)
   102  	}
   103  
   104  	hasOldConfig, _ := hasOldConfig()
   105  
   106  	if hasOldConfig {
   107  		userHomeDir := getUserHomeDir()
   108  		oldLocation := path.Join(userHomeDir, configDirHidden)
   109  		if err := moveOldConfig(); err != nil {
   110  			return &Config{}, fmt.Errorf("unable to move old config: %v", err)
   111  		}
   112  
   113  		if err := os.RemoveAll(oldLocation); err != nil {
   114  			return &Config{}, fmt.Errorf("unable to remove old config location: %v", err)
   115  		}
   116  		fmt.Fprintf(os.Stderr, "Successfully moved config from: %s to: %s\n", oldLocation, fullConfigFile)
   117  	}
   118  
   119  	f, err := os.Open(fullConfigFile)
   120  	if err != nil {
   121  		f, err = createDefaultConfig(fullConfigFile)
   122  		if err != nil {
   123  			return &Config{}, fmt.Errorf("error creating default config file: %v", err)
   124  		}
   125  	}
   126  	defer f.Close()
   127  
   128  	data, err := ioutil.ReadAll(f)
   129  	if err != nil {
   130  		return &Config{}, fmt.Errorf("unable to read config data: %v", err)
   131  	}
   132  
   133  	var c Config
   134  	err = yaml.Unmarshal(data, &c)
   135  	if err != nil {
   136  		return &Config{}, fmt.Errorf("unable to decode config file: %v", err)
   137  	}
   138  
   139  	if len(c.DebugInfoDirectories) == 0 {
   140  		c.DebugInfoDirectories = []string{"/usr/lib/debug/.build-id"}
   141  	}
   142  
   143  	return &c, nil
   144  }
   145  
   146  // SaveConfig will marshal and save the config struct
   147  // to disk.
   148  func SaveConfig(conf *Config) error {
   149  	fullConfigFile, err := GetConfigFilePath(configFile)
   150  	if err != nil {
   151  		return err
   152  	}
   153  
   154  	out, err := yaml.Marshal(*conf)
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	f, err := os.Create(fullConfigFile)
   160  	if err != nil {
   161  		return err
   162  	}
   163  	defer f.Close()
   164  
   165  	_, err = f.Write(out)
   166  	return err
   167  }
   168  
   169  // moveOldConfig attempts to move config to new location
   170  // $HOME/.dlv to $XDG_CONFIG_HOME/dlv
   171  func moveOldConfig() error {
   172  	if os.Getenv("XDG_CONFIG_HOME") == "" && runtime.GOOS != "linux" {
   173  		return nil
   174  	}
   175  
   176  	userHomeDir := getUserHomeDir()
   177  
   178  	p := path.Join(userHomeDir, configDirHidden, configFile)
   179  	_, err := os.Stat(p)
   180  	if err != nil {
   181  		return fmt.Errorf("unable to read config file located at: %s", p)
   182  	}
   183  
   184  	newFile, err := GetConfigFilePath(configFile)
   185  	if err != nil {
   186  		return fmt.Errorf("unable to read config file located at: %s", err)
   187  	}
   188  
   189  	if err := os.Rename(p, newFile); err != nil {
   190  		return fmt.Errorf("unable to move %s to %s", p, newFile)
   191  	}
   192  	return nil
   193  }
   194  
   195  func createDefaultConfig(path string) (*os.File, error) {
   196  	f, err := os.Create(path)
   197  	if err != nil {
   198  		return nil, fmt.Errorf("unable to create config file: %v", err)
   199  	}
   200  	err = writeDefaultConfig(f)
   201  	if err != nil {
   202  		return nil, fmt.Errorf("unable to write default configuration: %v", err)
   203  	}
   204  	f.Seek(0, io.SeekStart)
   205  	return f, nil
   206  }
   207  
   208  func writeDefaultConfig(f *os.File) error {
   209  	_, err := f.WriteString(
   210  		`# Configuration file for the delve debugger.
   211  
   212  # This is the default configuration file. Available options are provided, but disabled.
   213  # Delete the leading hash mark to enable an item.
   214  
   215  # Uncomment the following line and set your preferred ANSI color for source
   216  # line numbers in the (list) command. The default is 34 (dark blue). See
   217  # https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit
   218  # source-list-line-color: "\x1b[34m"
   219  
   220  # Uncomment the following lines to change the colors used by syntax highlighting.
   221  # source-list-keyword-color: "\x1b[0m"
   222  # source-list-string-color: "\x1b[92m"
   223  # source-list-number-color: "\x1b[0m"
   224  # source-list-comment-color: "\x1b[95m"
   225  # source-list-arrow-color: "\x1b[93m"
   226  
   227  # Uncomment to change the number of lines printed above and below cursor when
   228  # listing source code.
   229  # source-list-line-count: 5
   230  
   231  # Provided aliases will be added to the default aliases for a given command.
   232  aliases:
   233    # command: ["alias1", "alias2"]
   234  
   235  # Define sources path substitution rules. Can be used to rewrite a source path stored
   236  # in program's debug information, if the sources were moved to a different place
   237  # between compilation and debugging.
   238  # Note that substitution rules will not be used for paths passed to "break" and "trace"
   239  # commands.
   240  substitute-path:
   241    # - {from: path, to: path}
   242    
   243  # Maximum number of elements loaded from an array.
   244  # max-array-values: 64
   245  
   246  # Maximum loaded string length.
   247  # max-string-len: 64
   248  
   249  # Output evaluation.
   250  # max-variable-recurse: 1
   251  
   252  # Uncomment the following line to make the whatis command also print the DWARF location expression of its argument.
   253  # show-location-expr: true
   254  
   255  # Allow user to specify output syntax flavor of assembly, one of this list "intel"(default), "gnu", "go".
   256  # disassemble-flavor: intel
   257  
   258  # List of directories to use when searching for separate debug info files.
   259  debug-info-directories: ["/usr/lib/debug/.build-id"]
   260  `)
   261  	return err
   262  }
   263  
   264  // createConfigPath creates the directory structure at which all config files are saved.
   265  func createConfigPath() error {
   266  	path, err := GetConfigFilePath("")
   267  	if err != nil {
   268  		return err
   269  	}
   270  	return os.MkdirAll(path, 0700)
   271  }
   272  
   273  // GetConfigFilePath gets the full path to the given config file name.
   274  func GetConfigFilePath(file string) (string, error) {
   275  	if configPath := os.Getenv("XDG_CONFIG_HOME"); configPath != "" {
   276  		return path.Join(configPath, configDir, file), nil
   277  	}
   278  
   279  	userHomeDir := getUserHomeDir()
   280  
   281  	if runtime.GOOS == "linux" {
   282  		return path.Join(userHomeDir, ".config", configDir, file), nil
   283  	}
   284  	return path.Join(userHomeDir, configDirHidden, file), nil
   285  }
   286  
   287  // Checks if the user has a config at the old location: $HOME/.dlv
   288  func hasOldConfig() (bool, error) {
   289  	// If you don't have XDG_CONFIG_HOME set and aren't on Linux you have nothing to move
   290  	if os.Getenv("XDG_CONFIG_HOME") == "" && runtime.GOOS != "linux" {
   291  		return false, nil
   292  	}
   293  
   294  	userHomeDir := getUserHomeDir()
   295  
   296  	o := path.Join(userHomeDir, configDirHidden, configFile)
   297  	_, err := os.Stat(o)
   298  	if err != nil {
   299  		if os.IsNotExist(err) {
   300  			return false, nil
   301  		}
   302  		return false, err
   303  	}
   304  	return true, nil
   305  }
   306  
   307  func getUserHomeDir() string {
   308  	userHomeDir := "."
   309  	usr, err := user.Current()
   310  	if err == nil {
   311  		userHomeDir = usr.HomeDir
   312  	}
   313  	return userHomeDir
   314  }