From 35bc789b3b37caabaf5af5e2f68f777e322aa44d Mon Sep 17 00:00:00 2001 From: Evgeny L Date: Tue, 25 Oct 2016 20:01:38 +0300 Subject: [PATCH] config, terminal, command: Add substitute-path parameter to configuration file. (#640) Allows to rewrite a source path stored in program's debug information, if the sources were moved to a different place between compilation and debugging. --- config/config.go | 24 +++++++++++- terminal/command.go | 2 +- terminal/terminal.go | 45 +++++++++++++++++++++- terminal/terminal_test.go | 79 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 terminal/terminal_test.go diff --git a/config/config.go b/config/config.go index 58eb5c9f..3abb4857 100644 --- a/config/config.go +++ b/config/config.go @@ -15,9 +15,23 @@ const ( configFile string = "config.yml" ) +// Describes a rule for substitution of path to source code file. +type SubstitutePathRule struct { + // Directory path will be substituted if it matches `From`. + From string + // Path to which substitution is performed. + To string +} + +// Slice of source code path substitution rules. +type SubstitutePathRules []SubstitutePathRule + // Config defines all configuration options available to be set through the config file. type Config struct { - Aliases map[string][]string + // Commands aliases. + Aliases map[string][]string + // Source code path substitution rules. + SubstitutePath SubstitutePathRules `yaml:"substitute-path"` } // LoadConfig attempts to populate a Config object from the config.yml file. @@ -89,6 +103,14 @@ func writeDefaultConfig(f *os.File) error { # Provided aliases will be added to the default aliases for a given command. aliases: # command: ["alias1", "alias2"] + +# Define sources path substitution rules. Can be used to rewrite a source path stored +# in program's debug information, if the sources were moved to a different place +# between compilation and debugging. +# Note that substitution rules will not be used for paths passed to "break" and "trace" +# commands. +substitute-path: + # - {from: path, to: path} `) return err } diff --git a/terminal/command.go b/terminal/command.go index 9e6cf8ed..ea5c0ed9 100644 --- a/terminal/command.go +++ b/terminal/command.go @@ -1252,7 +1252,7 @@ func printcontextThread(t *Term, th *api.Thread) { } func printfile(t *Term, filename string, line int, showArrow bool) error { - file, err := os.Open(filename) + file, err := os.Open(t.substitutePath(filename)) if err != nil { return err } diff --git a/terminal/terminal.go b/terminal/terminal.go index 58e3b474..b6bb7e96 100644 --- a/terminal/terminal.go +++ b/terminal/terminal.go @@ -5,6 +5,7 @@ import ( "io" "os" "os/signal" + "runtime" "strings" "syscall" @@ -24,6 +25,7 @@ const ( // Term represents the terminal running dlv. type Term struct { client service.Client + conf *config.Config prompt string line *liner.State cmds *Commands @@ -49,9 +51,10 @@ func New(client service.Client, conf *config.Config) *Term { } return &Term{ + client: client, + conf: conf, prompt: "(dlv) ", line: liner.NewLiner(), - client: client, cmds: cmds, dumb: dumb, stdout: w, @@ -150,6 +153,46 @@ func (t *Term) Println(prefix, str string) { fmt.Fprintf(t.stdout, "%s%s\n", prefix, str) } +// Substitues directory to source file. +// +// Ensures that only directory is substitued, for example: +// substitute from `/dir/subdir`, substitute to `/new` +// for file path `/dir/subdir/file` will return file path `/new/file`. +// for file path `/dir/subdir-2/file` substitution will not be applied. +// +// If more than one substitution rule is defined, the rules are applied +// in the order they are defined, first rule that matches is used for +// substitution. +func (t *Term) substitutePath(path string) string { + path = crossPlatformPath(path) + if t.conf == nil { + return path + } + separator := string(os.PathSeparator) + for _, r := range t.conf.SubstitutePath { + from := crossPlatformPath(r.From) + to := r.To + + if !strings.HasSuffix(from, separator) { + from = from + separator + } + if !strings.HasSuffix(to, separator) { + to = to + separator + } + if strings.HasPrefix(path, from) { + return strings.Replace(path, from, to, 1) + } + } + return path +} + +func crossPlatformPath(path string) string { + if runtime.GOOS == "darwin" || runtime.GOOS == "windows" { + return strings.ToLower(path) + } + return path +} + func (t *Term) promptForInput() (string, error) { l, err := t.line.Prompt(t.prompt) if err != nil { diff --git a/terminal/terminal_test.go b/terminal/terminal_test.go new file mode 100644 index 00000000..04aa75a3 --- /dev/null +++ b/terminal/terminal_test.go @@ -0,0 +1,79 @@ +package terminal + +import ( + "testing" + "runtime" + + "github.com/derekparker/delve/config" +) + +type tRule struct { + from string + to string +} + +type tCase struct { + rules []tRule + path string + res string +} + +func platformCases() []tCase { + casesUnix := []tCase{ + // Should not depend on separator at the end of rule path + {[]tRule{{"/tmp/path", "/new/path2"}}, "/tmp/path/file.go", "/new/path2/file.go"}, + {[]tRule{{"/tmp/path/", "/new/path2/"}}, "/tmp/path/file.go", "/new/path2/file.go"}, + {[]tRule{{"/tmp/path/", "/new/path2"}}, "/tmp/path/file.go", "/new/path2/file.go"}, + {[]tRule{{"/tmp/path", "/new/path2/"}}, "/tmp/path/file.go", "/new/path2/file.go"}, + // Should apply only for directory names + {[]tRule{{"/tmp/path", "/new/path2"}}, "/tmp/path-2/file.go", "/tmp/path-2/file.go"}, + // First matched rule should be used + {[]tRule{ + {"/tmp/path1", "/new/path1"}, + {"/tmp/path2", "/new/path2"}, + {"/tmp/path2", "/new/path3"}}, "/tmp/path2/file.go", "/new/path2/file.go"}, + } + casesLinux := []tCase{ + // Should be case-sensitive + {[]tRule{{"/tmp/path", "/new/path2"}}, "/TmP/path/file.go", "/TmP/path/file.go"}, + } + casesDarwin := []tCase{ + // Should be case-insensitive + {[]tRule{{"/tmp/path", "/new/path2"}}, "/TmP/PaTh/file.go", "/new/path2/file.go"}, + } + casesWindows := []tCase{ + // Should not depend on separator at the end of rule path + {[]tRule{{`c:\tmp\path`, `d:\new\path2`}}, `c:\tmp\path\file.go`, `d:\new\path2\file.go`}, + {[]tRule{{`c:\tmp\path\`, `d:\new\path2\`}}, `c:\tmp\path\file.go`, `d:\new\path2\file.go`}, + {[]tRule{{`c:\tmp\path`, `d:\new\path2\`}}, `c:\tmp\path\file.go`, `d:\new\path2\file.go`}, + {[]tRule{{`c:\tmp\path\`, `d:\new\path2`}}, `c:\tmp\path\file.go`, `d:\new\path2\file.go`}, + // Should apply only for directory names + {[]tRule{{`c:\tmp\path`, `d:\new\path2`}}, `c:\tmp\path-2\file.go`, `c:\tmp\path-2\file.go`}, + // Should be case-insensitive + {[]tRule{{`c:\tmp\path`, `d:\new\path2`}}, `C:\TmP\PaTh\file.go`, `d:\new\path2\file.go`}, + } + + if runtime.GOOS == "windows" { + return casesWindows + } + if runtime.GOOS == "darwin" { + return append(casesUnix, casesDarwin...) + } + if runtime.GOOS == "linux" { + return append(casesUnix, casesLinux...) + } + return casesUnix +} + +func TestSubstitutePath(t *testing.T) { + for _, c := range(platformCases()) { + var subRules config.SubstitutePathRules + for _, r := range(c.rules) { + subRules = append(subRules, config.SubstitutePathRule{From: r.from, To: r.to}) + } + res := New(nil, &config.Config{SubstitutePath: subRules}).substitutePath(c.path) + if c.res != res { + t.Errorf("terminal.SubstitutePath(%q) => %q, want %q", c.path, res, c.res) + } + } +} -- GitLab