Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement mutate command #199

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions cmd/gomtree/cmd/mutate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package cmd

import (
"fmt"
"io"
"math"
"os"
"strings"

cli "github.com/urfave/cli/v2"
"github.com/vbatts/go-mtree"
)

func NewMutateCommand() *cli.Command {

return &cli.Command{
Name: "mutate",
Usage: "mutate an mtree",
Description: `Mutate an mtree to have different shapes.
TODO: more info examples`,
Action: mutateAction,
ArgsUsage: "<path to mtree> [path to output]",
Flags: []cli.Flag{
&cli.StringSliceFlag{
Name: "strip-prefix",
},
&cli.BoolFlag{
Name: "keep-comments",
Value: false,
},
&cli.BoolFlag{
Name: "keep-blank",
Value: false,
},
&cli.StringFlag{
Name: "output",
TakesFile: true,
},
},
}
}

func mutateAction(c *cli.Context) error {
mtreePath := c.Args().Get(0)
outputPath := c.Args().Get(1)
stripPrexies := c.StringSlice("strip-prefix")
keepComments := c.Bool("keep-comments")
keepBlank := c.Bool("keep-blank")

if mtreePath == "" {
return fmt.Errorf("mtree path is required.")
} else if outputPath == "" {
outputPath = mtreePath
}

file, err := os.Open(mtreePath)
if err != nil {
return fmt.Errorf("opening %s: %w", mtreePath, err)
}

spec, err := mtree.ParseSpec(file)
if err != nil {
return fmt.Errorf("parsing mtree %s: %w", mtreePath, err)
}

stripPrefixVisitor := stripPrefixVisitor{
prefixes: stripPrexies,
}
tidyVisitor := tidyVisitor{
keepComments: keepComments,
keepBlank: keepBlank,
}
visitors := []Visitor{
&stripPrefixVisitor,
&tidyVisitor,
}

dropped := map[int]bool{}
entries := []mtree.Entry{}

skip:
for _, entry := range spec.Entries {
if entry.Parent != nil {
if _, ok := dropped[entry.Parent.Pos]; ok {
if entry.Type == mtree.DotDotType {
// directory for this .. has been dropped so shall this
continue
}
entry.Parent = entry.Parent.Parent
// TODO: i am not sure if this is the correct behavior
entry.Raw = strings.TrimPrefix(entry.Raw, " ")
}
}

for _, visitor := range visitors {
drop, err := visitor.Visit(&entry)
if err != nil {
return err
}

if drop {
dropped[entry.Pos] = true
continue skip
}
}

entries = append(entries, entry)
}

spec.Entries = entries

var writer io.Writer = os.Stdout
if outputPath != "-" {
writer, err = os.Create(outputPath)
if err != nil {
return fmt.Errorf("creating output %s: %w", outputPath, err)
}
}

_, err = spec.WriteTo(writer)
if err != nil {
return fmt.Errorf("writing mtree %s: %w", outputPath, err)
}

return nil
}

type Visitor interface {
Visit(entry *mtree.Entry) (bool, error)
}

type tidyVisitor struct {
keepComments bool
keepBlank bool
}

func (m *tidyVisitor) Visit(entry *mtree.Entry) (bool, error) {
if !m.keepComments && entry.Type == mtree.CommentType {
return true, nil
} else if !m.keepBlank && entry.Type == mtree.BlankType {
return true, nil
}
return false, nil
}

type stripPrefixVisitor struct {
prefixes []string
}

func (m *stripPrefixVisitor) Visit(entry *mtree.Entry) (bool, error) {
if entry.Type != mtree.FullType && entry.Type != mtree.RelativeType {
return false, nil
}

fp, err := entry.Path()
if err != nil {
return false, err
}
pathSegments := strings.Split(fp, "/")

for _, prefix := range m.prefixes {

prefixSegments := strings.Split(prefix, "/")
minLen := int(math.Min(float64(len(pathSegments)), float64(len(prefixSegments))))
matches := make([]string, minLen)
for i := 0; i < minLen; i++ {
if pathSegments[i] == prefixSegments[i] {
matches[i] = prefixSegments[i]
}
}

strip := strings.Join(matches, "/")
if entry.Type == mtree.FullType {
entry.Name = strings.TrimPrefix(entry.Name, strip)
entry.Name = strings.TrimPrefix(entry.Name, "/")
if entry.Name == "" {
return true, nil
}
} else if fp == strip {
return true, nil
}
}
return false, nil
}
1 change: 1 addition & 0 deletions cmd/gomtree/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ to support xattrs and interacting with tar archives.`
}
app.Commands = []*cli.Command{
cmd.NewValidateCommand(),
cmd.NewMutateCommand(),
}

// Unfortunately urfave/cli is not at good at using DefaultCommand
Expand Down
5 changes: 4 additions & 1 deletion entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@ func (e Entry) String() string {
if e.Type == SpecialType || e.Type == FullType || inKeyValSlice("type=dir", e.Keywords) {
return fmt.Sprintf("%s %s", e.Name, strings.Join(KeyValToString(e.Keywords), " "))
}
return fmt.Sprintf(" %s %s", e.Name, strings.Join(KeyValToString(e.Keywords), " "))
if e.Parent != nil && e.Type != DotDotType {
return fmt.Sprintf(" %s %s", e.Name, strings.Join(KeyValToString(e.Keywords), " "))
}
return fmt.Sprintf("%s %s", e.Name, strings.Join(KeyValToString(e.Keywords), " "))
}

// AllKeys returns the full set of KeyVal for the given entry, based on the
Expand Down
1 change: 1 addition & 0 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func ParseSpec(r io.Reader) (*DirectoryHierarchy, error) {
e.Type = DotDotType
e.Raw = str
if creator.curDir != nil {
e.Parent = creator.curDir
creator.curDir = creator.curDir.Parent
}
// nothing else to do here
Expand Down
15 changes: 15 additions & 0 deletions testdata/relative.mtree
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# ./lib
lib type=dir mode=0644
foo mode=0644 size=12288 time=1457644483.833957552 type=file

..

./lib type=dir mode=0644


ayo mode=0644 size=12288 time=1457644483.833957552 type=file

lib/dir/sub type=dir
lib/dir/sub/file.txt type=file

lib/dir/PKG.info type=file mode=0644
Loading