Skip to content

Commit

Permalink
cmd/fillstruct: add option to select by line number. (#8)
Browse files Browse the repository at this point in the history
The new command line parameter -line can be used to specify the
position of one or more struct literals by providing the corresponding
line number.

If both parameters, -offset and -line, are provided, then the tool
first uses the more specific offset information. If there was no
struct literal found at the given offset, then the line information is
used.

If more than one struct literal was found, code to fill all of them is
generated. The output is a JSON array with the elements in reverse
order of their occurence.

Fixes #3.
  • Loading branch information
David R. Jenni authored Dec 13, 2017
1 parent ed200a5 commit cdebcee
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 28 deletions.
9 changes: 7 additions & 2 deletions cmd/fillstruct/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,16 @@ after applying fillstruct.
## Usage

```
% fillstruct [-modified] -file=<filename> -offset=<byte offset>
% fillstruct [-modified] -file=<filename> -offset=<byte offset> -line=<line number>
```

Flags:

-file: filename
-modified: read an archive of modified files from stdin
-offset: byte offset of the struct literal
-offset: byte offset of the struct literal, optional if -line is present
-line: line number of the struct literal, optional if -offset is present

If -offset as well as -line are present, then the tool first uses the
more specific offset information. If there was no struct literal found
at the given offset, then the line information is used.
153 changes: 127 additions & 26 deletions cmd/fillstruct/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,22 @@
//
// Usage:
//
// % fillstruct [-modified] -file=<filename> -offset=<byte offset>
// % fillstruct [-modified] -file=<filename> -offset=<byte offset> -line=<line number>
//
// Flags:
//
// -file: filename
//
// -modified: read an archive of modified files from stdin
//
// -offset: byte offset of the struct literal
// -offset: byte offset of the struct literal, optional if -line is present
//
// -line: line number of the struct literal, optional if -offset is present
//
//
// If -offset as well as -line are present, then the tool first uses the
// more specific offset information. If there was no struct literal found
// at the given offset, then the line information is used.
//
package main

Expand Down Expand Up @@ -330,18 +337,21 @@ func isImported(pkg *types.Package, n *types.Named) bool {
return n != nil && pkg != n.Obj().Pkg()
}

var errNotFound = errors.New("no struct literal found at selection")

func main() {
log.SetFlags(0)
log.SetPrefix("fillstruct: ")

var (
filename = flag.String("file", "", "filename")
modified = flag.Bool("modified", false, "read an archive of modified files from stdin")
offset = flag.Int("offset", 0, "byte offset of the struct literal")
offset = flag.Int("offset", 0, "byte offset of the struct literal, optional if -line is present")
line = flag.Int("line", 0, "line number of the struct literal, optional if -offset is present")
)
flag.Parse()

if *offset == 0 || *filename == "" {
if (*offset == 0 && *line == 0) || *filename == "" {
flag.PrintDefaults()
os.Exit(1)
}
Expand All @@ -357,23 +367,29 @@ func main() {
}
pkg := lprog.InitialPackages()[0]

f, pos, err := findPos(lprog, path, *offset)
if err != nil {
log.Fatal(err)
if *offset > 0 {
err = byOffset(lprog, path, pkg, *offset)
switch err {
case nil:
return
case errNotFound:
// try to use line information
default:
log.Fatal(err)
}
}

lit, typ, name, err := findCompositeLit(f, pkg.Info, pos)
if err != nil {
log.Fatal(err)
if *line > 0 {
err = byLine(lprog, path, pkg, *line)
switch err {
case nil:
return
default:
log.Fatal(err)
}
}

start := lprog.Fset.Position(lit.Pos()).Offset
end := lprog.Fset.Position(lit.End()).Offset

newlit, lines := zeroValue(pkg.Pkg, lit, typ, name)
if err := print(newlit, lines, start, end); err != nil {
log.Fatal(err)
}
log.Fatal(errNotFound)
}

func absPath(filename string) (string, error) {
Expand Down Expand Up @@ -407,6 +423,28 @@ func load(path string, modified bool) (*loader.Program, error) {
return conf.Load()
}

func byOffset(lprog *loader.Program, path string, pkg *loader.PackageInfo, offset int) error {
f, pos, err := findPos(lprog, path, offset)
if err != nil {
return err
}

lit, typ, name, err := findCompositeLit(f, pkg.Info, pos)
if err != nil {
return err
}

start := lprog.Fset.Position(lit.Pos()).Offset
end := lprog.Fset.Position(lit.End()).Offset

newlit, lines := zeroValue(pkg.Pkg, lit, typ, name)
out, err := prepareOutput(newlit, lines, start, end)
if err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode([]output{out})
}

func findPos(lprog *loader.Program, filename string, off int) (*ast.File, token.Pos, error) {
for _, f := range lprog.InitialPackages()[0].Files {
if file := lprog.Fset.File(f.Pos()); file.Name() == filename {
Expand All @@ -429,15 +467,78 @@ func findCompositeLit(f *ast.File, info types.Info, pos token.Pos) (*ast.Composi
name, _ := info.Types[lit].Type.(*types.Named)
typ, ok := info.Types[lit].Type.Underlying().(*types.Struct)
if !ok {
return nil, nil, nil, errors.New("no struct literal found at selection")
return nil, nil, nil, errNotFound
}
return lit, typ, name, nil
}
}
return nil, nil, nil, errors.New("no struct literal found at selection")
return nil, nil, nil, errNotFound
}

func print(n ast.Node, lines, start, end int) error {
func byLine(lprog *loader.Program, path string, pkg *loader.PackageInfo, line int) (err error) {
var f *ast.File
for _, af := range lprog.InitialPackages()[0].Files {
if file := lprog.Fset.File(af.Pos()); file.Name() == path {
f = af
}
}
if f == nil {
return fmt.Errorf("could not find file %q", path)
}

var outs []output
ast.Inspect(f, func(n ast.Node) bool {
lit, ok := n.(*ast.CompositeLit)
if !ok {
return true
}
startLine := lprog.Fset.Position(lit.Pos()).Line
endLine := lprog.Fset.Position(lit.End()).Line
if !(startLine <= line && line <= endLine) {
return true
}

name, _ := pkg.Types[lit].Type.(*types.Named)
typ, ok := pkg.Types[lit].Type.Underlying().(*types.Struct)
if !ok {
err = errNotFound
return false
}

startOff := lprog.Fset.Position(lit.Pos()).Offset
endOff := lprog.Fset.Position(lit.End()).Offset
newlit, lines := zeroValue(pkg.Pkg, lit, typ, name)

var out output
out, err = prepareOutput(newlit, lines, startOff, endOff)
if err != nil {
return false
}
outs = append(outs, out)
return false
})
if err != nil {
return err
}
if len(outs) == 0 {
return errNotFound
}

for i := len(outs)/2 - 1; i >= 0; i-- {
opp := len(outs) - 1 - i
outs[i], outs[opp] = outs[opp], outs[i]
}

return json.NewEncoder(os.Stdout).Encode(outs)
}

type output struct {
Start int `json:"start"`
End int `json:"end"`
Code string `json:"code"`
}

func prepareOutput(n ast.Node, lines, start, end int) (output, error) {
fset := token.NewFileSet()
file := fset.AddFile("", -1, lines)
for i := 1; i <= lines; i++ {
Expand All @@ -446,13 +547,13 @@ func print(n ast.Node, lines, start, end int) error {

var buf bytes.Buffer
if err := format.Node(&buf, fset, n); err != nil {
return err
return output{}, err
}
return json.NewEncoder(os.Stdout).Encode(struct {
Start int `json:"start"`
End int `json:"end"`
Code string `json:"code"`
}{Start: start, End: end, Code: buf.String()})
return output{
Start: start,
End: end,
Code: buf.String(),
}, nil
}

func allowErrors(conf *loader.Config) {
Expand Down

0 comments on commit cdebcee

Please sign in to comment.