mirror of
https://github.com/go-delve/delve.git
synced 2025-11-02 12:59:01 +08:00
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.
This commit is contained in:
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
79
terminal/terminal_test.go
Normal file
79
terminal/terminal_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user