Skip to content

Commit

Permalink
config, terminal, command: Add substitute-path parameter to configura…
Browse files Browse the repository at this point in the history
…tion file. (go-delve#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.
  • Loading branch information
Evgeny L authored and derekparker committed Oct 25, 2016
1 parent 0f4b515 commit 35bc789
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 3 deletions.
24 changes: 23 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion terminal/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
45 changes: 44 additions & 1 deletion terminal/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"os"
"os/signal"
"runtime"
"strings"

"syscall"
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
79 changes: 79 additions & 0 deletions terminal/terminal_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}

0 comments on commit 35bc789

Please sign in to comment.