init
This commit is contained in:
commit
0bd99eaff7
6 changed files with 663 additions and 0 deletions
29
dir_utils.go
Normal file
29
dir_utils.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func ListDir(path string) ([]os.FileInfo, error) {
|
||||
var files []os.FileInfo
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return files, err
|
||||
}
|
||||
defer f.Close()
|
||||
return f.Readdir(-1)
|
||||
}
|
||||
|
||||
func AnyDirectoryDownThere(path string) bool {
|
||||
files, err := ListDir(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
95
dir_utils_test.go
Normal file
95
dir_utils_test.go
Normal file
|
@ -0,0 +1,95 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var subdirs = map[string][]string{"sub1": []string{"s1s1", "s1s2", "s1s3"}, "sub2": []string{"s2s1", "s2s2"}, "sub3": []string{}}
|
||||
var dirWithSubdirs string
|
||||
var dirWithoutSubdirs string
|
||||
|
||||
func mkchild(root, dir string) (string, error) {
|
||||
childPath := filepath.Join(root, dir)
|
||||
var mode os.FileMode = 2_148_532_735 // means "dtrwxrwxrwx"
|
||||
err := os.Mkdir(childPath, mode)
|
||||
return childPath, err
|
||||
}
|
||||
|
||||
func createTestDirs() string {
|
||||
root, err := ioutil.TempDir("", "ruspa_*")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for dirName, subdirList := range subdirs {
|
||||
dir, err := mkchild(root, dirName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, subdir := range subdirList {
|
||||
if _, err = mkchild(dir, subdir); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
func removeTestDirs(root string) {
|
||||
for dirName, subdirList := range subdirs {
|
||||
dirPath := filepath.Join(root, dirName)
|
||||
for _, subdir := range subdirList {
|
||||
subdirPath := filepath.Join(dirPath, subdir)
|
||||
if err := os.Remove(subdirPath); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
err := os.Remove(dirPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
if err := os.Remove(root); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListDir(t *testing.T) {
|
||||
root := createTestDirs()
|
||||
defer removeTestDirs(root)
|
||||
dirs, err := ListDir(root)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
for _, dir := range dirs {
|
||||
if _, ok := subdirs[dir.Name()]; !ok {
|
||||
t.Errorf("missing %s in %s\n", dir, dirs)
|
||||
}
|
||||
}
|
||||
shouldBeEmpty, err := ListDir(filepath.Join(root, "sub3"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(shouldBeEmpty) != 0 {
|
||||
t.Error("ListDir of sub3 should be empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnyDirectoryDownThere(t *testing.T) {
|
||||
root := createTestDirs()
|
||||
defer removeTestDirs(root)
|
||||
pathNotEmpty := filepath.Join(root, "sub1")
|
||||
if !AnyDirectoryDownThere(pathNotEmpty) {
|
||||
list, _ := ListDir(pathNotEmpty)
|
||||
t.Logf("%s -> %s\n", pathNotEmpty, list)
|
||||
t.Error("sub1 is empty, but should be")
|
||||
}
|
||||
pathEmpty := filepath.Join(root, "sub3")
|
||||
if AnyDirectoryDownThere(pathEmpty) {
|
||||
list, _ := ListDir(pathEmpty)
|
||||
t.Logf("%s -> %s\n", pathEmpty, list)
|
||||
t.Error("sub3 is NOT empty, but it should be")
|
||||
}
|
||||
}
|
401
dynamic_tree.go
Normal file
401
dynamic_tree.go
Normal file
|
@ -0,0 +1,401 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
TopType = "top"
|
||||
IntermediateType = "intermediate"
|
||||
BottomType = "bottom"
|
||||
SingleType = "single"
|
||||
)
|
||||
|
||||
var ErrUnknownType = errors.New("unknown type")
|
||||
var ErrShouldBeBottom = errors.New("should be of type Bottom")
|
||||
var ErrChannelError = errors.New("channel errored")
|
||||
var (
|
||||
NodeB = "│"
|
||||
NodeT = "├──"
|
||||
NodeL = "└──"
|
||||
NodePad = " "
|
||||
)
|
||||
|
||||
type Node interface {
|
||||
Size() int64
|
||||
Spawn(int) error
|
||||
Collect() error
|
||||
Collector() chan int64
|
||||
Level() int
|
||||
}
|
||||
|
||||
func isLastLevel(node Node, maxLevel int) bool {
|
||||
return maxLevel-1 == node.Level()
|
||||
}
|
||||
|
||||
func isBeforeLastLevel(node Node, maxLevel int) bool {
|
||||
return maxLevel-1 > node.Level()
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
/////////// Top //////////////
|
||||
//////////////////////////////
|
||||
|
||||
type Top struct {
|
||||
path string
|
||||
size int64
|
||||
tree []Node
|
||||
collect chan int64
|
||||
level int
|
||||
}
|
||||
|
||||
func NewTop(path string) *Top {
|
||||
t := &Top{path: path, level: 0}
|
||||
t.collect = make(chan int64)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Top) Size() int64 {
|
||||
return t.size
|
||||
}
|
||||
|
||||
func (t *Top) Spawn(maxLevel int) error {
|
||||
if t.Level() >= maxLevel {
|
||||
return ErrShouldBeBottom
|
||||
}
|
||||
files, err := ListDir(t.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, info := range files {
|
||||
switch mode := info.Mode(); {
|
||||
case mode.IsDir():
|
||||
var child Node
|
||||
path := filepath.Join(t.path, info.Name())
|
||||
if isLastLevel(t, maxLevel) || !AnyDirectoryDownThere(path) {
|
||||
child = t.newBottom(path)
|
||||
} else {
|
||||
child = t.newIntermediate(path)
|
||||
}
|
||||
go child.Spawn(maxLevel)
|
||||
defer func() {
|
||||
go child.Collect()
|
||||
}()
|
||||
case !mode.IsDir() && mode.IsRegular():
|
||||
t.collect <- info.Size()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Top) Collect() error {
|
||||
for s := range t.collect {
|
||||
t.size += s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Top) Collector() chan int64 {
|
||||
return t.collect
|
||||
}
|
||||
|
||||
func (t *Top) Level() int {
|
||||
return t.level
|
||||
}
|
||||
|
||||
func (t *Top) Name() string {
|
||||
return filepath.Base(t.path)
|
||||
}
|
||||
|
||||
func (t *Top) String() string {
|
||||
var out string
|
||||
var lines []string
|
||||
out += fmt.Sprintf("(%d) %s\n", t.Size(), t.path)
|
||||
treeSize := len(t.tree)
|
||||
for _, child := range t.tree {
|
||||
lines = append(lines, fmt.Sprintf("%s", child))
|
||||
}
|
||||
for a := 0; a < treeSize-1; a++ {
|
||||
childLines := strings.Split(lines[a], "\n")
|
||||
out += fmt.Sprintf(" %s%s\n", NodeT, childLines[0])
|
||||
for b := 1; b < len(childLines); b++ {
|
||||
out += fmt.Sprintf(" %s%s\n", NodeB, childLines[b])
|
||||
}
|
||||
}
|
||||
childLines := strings.Split(lines[treeSize-1], "\n")
|
||||
out += fmt.Sprintf(" %s%s\n", NodeL, childLines[0])
|
||||
for a := 1; a < len(childLines); a++ {
|
||||
out += fmt.Sprintf(" %s%s\n", NodePad, childLines[a])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t *Top) addToTree(child Node) {
|
||||
t.tree = append(t.tree, child)
|
||||
}
|
||||
|
||||
func (t *Top) newChild(path, kind string) Node {
|
||||
switch {
|
||||
case kind == IntermediateType:
|
||||
i := NewIntermediate(path, t)
|
||||
t.addToTree(i)
|
||||
return i
|
||||
case kind == BottomType:
|
||||
b := NewBottom(path, t)
|
||||
t.addToTree(b)
|
||||
return b
|
||||
default:
|
||||
panic(ErrUnknownType)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Top) newIntermediate(path string) *Intermediate {
|
||||
return t.newChild(path, IntermediateType).(*Intermediate)
|
||||
}
|
||||
|
||||
func (t *Top) newBottom(path string) *Bottom {
|
||||
return t.newChild(path, BottomType).(*Bottom)
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
/////// Intermediate /////////
|
||||
//////////////////////////////
|
||||
|
||||
type Intermediate struct {
|
||||
path string
|
||||
size int64
|
||||
parent Node
|
||||
tree []Node
|
||||
collect chan int64
|
||||
refer chan int64
|
||||
level int
|
||||
}
|
||||
|
||||
func NewIntermediate(path string, parent Node) *Intermediate {
|
||||
i := &Intermediate{path: path, refer: parent.Collector(), parent: parent}
|
||||
i.collect = make(chan int64)
|
||||
i.level = parent.Level() + 1
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *Intermediate) Size() int64 {
|
||||
return i.size
|
||||
}
|
||||
|
||||
func (i *Intermediate) Spawn(maxLevel int) error {
|
||||
if i.Level() >= maxLevel {
|
||||
return ErrShouldBeBottom
|
||||
}
|
||||
|
||||
files, err := ListDir(i.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, info := range files {
|
||||
switch mode := info.Mode(); {
|
||||
case mode.IsDir():
|
||||
var child Node
|
||||
path := filepath.Join(i.path, info.Name())
|
||||
if isLastLevel(i, maxLevel) || !AnyDirectoryDownThere(path) {
|
||||
child = i.newBottom(path)
|
||||
} else {
|
||||
child = i.newIntermediate(path)
|
||||
}
|
||||
go child.Spawn(maxLevel)
|
||||
defer func() {
|
||||
go child.Collect()
|
||||
}()
|
||||
case !mode.IsDir() && mode.IsRegular():
|
||||
i.collect <- info.Size()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Intermediate) Collect() error {
|
||||
for s := range i.collect {
|
||||
i.size += s
|
||||
i.refer <- i.size
|
||||
}
|
||||
close(i.refer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Intermediate) Collector() chan int64 {
|
||||
return i.collect
|
||||
}
|
||||
|
||||
func (i *Intermediate) Level() int {
|
||||
return i.level
|
||||
}
|
||||
|
||||
func (i *Intermediate) Name() string {
|
||||
return filepath.Base(i.path)
|
||||
}
|
||||
|
||||
func (i *Intermediate) String() string {
|
||||
var lines []string
|
||||
out := fmt.Sprintf("(%d) %s\n", i.Size(), i.Name())
|
||||
treeSize := len(i.tree)
|
||||
for _, child := range i.tree {
|
||||
lines = append(lines, fmt.Sprintf("%s", child))
|
||||
}
|
||||
|
||||
for a := 0; a < treeSize-1; a++ {
|
||||
childLines := strings.Split(lines[a], "\n")
|
||||
out += fmt.Sprintf("%s%s%s\n", NodePad, NodeT, childLines[0])
|
||||
for b := 1; b < len(childLines); b++ {
|
||||
out += fmt.Sprintf("%s%s%s\n", NodePad, NodeB, childLines[b])
|
||||
}
|
||||
}
|
||||
|
||||
childLines := strings.Split(lines[treeSize-1], "\n")
|
||||
lenChildLines := len(childLines)
|
||||
out += fmt.Sprintf("%s%s%s\n", NodePad, NodeL, childLines[0])
|
||||
for a := 1; a < lenChildLines-2; a++ {
|
||||
out += fmt.Sprintf("%s%s%s\n", NodePad, NodePad, childLines[a])
|
||||
}
|
||||
if lenChildLines > 0 {
|
||||
out += fmt.Sprintf("%s%s%s", NodePad, NodePad, childLines[lenChildLines-1])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (i *Intermediate) addToTree(child Node) {
|
||||
i.tree = append(i.tree, child)
|
||||
}
|
||||
|
||||
func (i *Intermediate) newChild(path, kind string) Node {
|
||||
switch {
|
||||
case kind == IntermediateType:
|
||||
c := NewIntermediate(path, i)
|
||||
i.addToTree(c)
|
||||
return c
|
||||
case kind == BottomType:
|
||||
b := NewBottom(path, i)
|
||||
i.addToTree(b)
|
||||
return b
|
||||
default:
|
||||
panic(ErrUnknownType)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Intermediate) newIntermediate(path string) *Intermediate {
|
||||
return i.newChild(path, IntermediateType).(*Intermediate)
|
||||
}
|
||||
|
||||
func (i *Intermediate) newBottom(path string) *Bottom {
|
||||
return i.newChild(path, BottomType).(*Bottom)
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
////////// Bottom ////////////
|
||||
//////////////////////////////
|
||||
|
||||
type Bottom struct {
|
||||
path string
|
||||
size int64
|
||||
parent Node
|
||||
walker Walker
|
||||
collect chan int64
|
||||
refer chan int64
|
||||
level int
|
||||
}
|
||||
|
||||
func NewBottom(path string, parent Node) *Bottom {
|
||||
b := &Bottom{path: path, refer: parent.Collector(), parent: parent}
|
||||
b.collect = make(chan int64)
|
||||
b.level = parent.Level() + 1
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Bottom) Size() int64 {
|
||||
return b.size
|
||||
}
|
||||
|
||||
func (b *Bottom) Spawn(maxLevel int) error {
|
||||
b.walker = NewRealWalker(b.path, b.collect)
|
||||
go b.walker.Walk()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bottom) Collect() error {
|
||||
for s := range b.collect {
|
||||
b.size += s
|
||||
b.refer <- b.size
|
||||
}
|
||||
close(b.refer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bottom) Collector() chan int64 {
|
||||
return b.collect
|
||||
}
|
||||
|
||||
func (b *Bottom) Level() int {
|
||||
return b.level
|
||||
}
|
||||
|
||||
func (b *Bottom) Name() string {
|
||||
return filepath.Base(b.path)
|
||||
}
|
||||
|
||||
func (b *Bottom) String() string {
|
||||
return fmt.Sprintf("(%d) %s", b.Size(), b.Name())
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
////////// Single ////////////
|
||||
//////////////////////////////
|
||||
|
||||
type Single struct {
|
||||
path string
|
||||
size int64
|
||||
walker Walker
|
||||
collect chan int64
|
||||
}
|
||||
|
||||
func (s *Single) Size() int64 {
|
||||
return s.size
|
||||
}
|
||||
|
||||
func (s *Single) Spawn(maxLevel int) error {
|
||||
s.walker = NewRealWalker(s.path, s.collect)
|
||||
go s.walker.Walk()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Single) Collect() error {
|
||||
for v := range s.collect {
|
||||
s.size += v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Single) Collector() chan int64 {
|
||||
return s.collect
|
||||
}
|
||||
|
||||
func (s *Single) Level() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func NewSingle(path string) *Single {
|
||||
s := &Single{path: path}
|
||||
s.collect = make(chan int64)
|
||||
return s
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
////////// Walker ////////////
|
||||
//////////////////////////////
|
||||
|
||||
type Walker interface {
|
||||
Walk()
|
||||
}
|
84
dynamic_tree_test.go
Normal file
84
dynamic_tree_test.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const result = `(0) /
|
||||
├──(0) i1
|
||||
│ ├──(0) i1s1
|
||||
│ │ ├──(0) i1s1b1
|
||||
│ │ ├──(0) i1s1b2
|
||||
│ │ └──(0) i1s1b3
|
||||
│ │ (0) i1s1b3
|
||||
│ ├──(0) i1s2
|
||||
│ │ ├──(0) i1s2b1
|
||||
│ │ └──(0) i1s2b2
|
||||
│ │ (0) i1s2b2
|
||||
│ ├──(0) i1s3
|
||||
│ │ ├──(0) i1s3s1
|
||||
│ │ │ ├──(0) i1s3s1b1
|
||||
│ │ │ ├──(0) i1s3s1b2
|
||||
│ │ │ └──(0) i1s3s1b3
|
||||
│ │ │ (0) i1s3s1b3
|
||||
│ │ └──(0) i1s3b1
|
||||
│ │ (0) i1s3b1
|
||||
│ └──(0) i1s4
|
||||
│ (0) i1s4b1
|
||||
├──(0) i2
|
||||
│ ├──(0) i2b1
|
||||
│ └──(0) i2b2
|
||||
│ (0) i2b2
|
||||
└──(0) i3
|
||||
├──(0) i3s1
|
||||
│ └──(0) i3s1b1
|
||||
│ (0) i3s1b1
|
||||
├──(0) i3s2
|
||||
│ ├──(0) i3s2b1
|
||||
│ └──(0) i3s2b2
|
||||
│ (0) i3s2b2
|
||||
└──(0) i3s3
|
||||
(0) i3s3b1
|
||||
`
|
||||
|
||||
func createTree() *Top {
|
||||
t := NewTop("/")
|
||||
i1 := t.newChild("i1", IntermediateType).(*Intermediate)
|
||||
i2 := t.newChild("i2", IntermediateType).(*Intermediate)
|
||||
i3 := t.newChild("i3", IntermediateType).(*Intermediate)
|
||||
i1s1 := i1.newChild("i1s1", IntermediateType).(*Intermediate)
|
||||
i1s2 := i1.newChild("i1s2", IntermediateType).(*Intermediate)
|
||||
i1s3 := i1.newChild("i1s3", IntermediateType).(*Intermediate)
|
||||
i1s4 := i1.newChild("i1s4", IntermediateType).(*Intermediate)
|
||||
i3s1 := i3.newChild("i3s1", IntermediateType).(*Intermediate)
|
||||
i3s2 := i3.newChild("i3s2", IntermediateType).(*Intermediate)
|
||||
i3s3 := i3.newChild("i3s3", IntermediateType).(*Intermediate)
|
||||
_ = i1s1.newChild("i1s1b1", BottomType).(*Bottom)
|
||||
_ = i1s1.newChild("i1s1b2", BottomType).(*Bottom)
|
||||
_ = i1s1.newChild("i1s1b3", BottomType).(*Bottom)
|
||||
_ = i1s2.newChild("i1s2b1", BottomType).(*Bottom)
|
||||
_ = i1s2.newChild("i1s2b2", BottomType).(*Bottom)
|
||||
i1s3s1 := i1s3.newChild("i1s3s1", IntermediateType).(*Intermediate)
|
||||
_ = i1s3.newChild("i1s3b1", BottomType).(*Bottom)
|
||||
_ = i1s4.newChild("i1s4b1", BottomType).(*Bottom)
|
||||
_ = i2.newChild("i2b1", BottomType).(*Bottom)
|
||||
_ = i2.newChild("i2b2", BottomType).(*Bottom)
|
||||
_ = i3s1.newChild("i3s1b1", BottomType).(*Bottom)
|
||||
_ = i3s2.newChild("i3s2b1", BottomType).(*Bottom)
|
||||
_ = i3s2.newChild("i3s2b2", BottomType).(*Bottom)
|
||||
_ = i3s3.newChild("i3s3b1", BottomType).(*Bottom)
|
||||
_ = i1s3s1.newChild("i1s3s1b1", BottomType).(*Bottom)
|
||||
_ = i1s3s1.newChild("i1s3s1b2", BottomType).(*Bottom)
|
||||
_ = i1s3s1.newChild("i1s3s1b3", BottomType).(*Bottom)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
tree := createTree()
|
||||
repr := fmt.Sprintf("%s", tree)
|
||||
if repr != result {
|
||||
t.Errorf("%s", repr)
|
||||
}
|
||||
}
|
26
main.go
Normal file
26
main.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var path string
|
||||
var depth int
|
||||
flag.StringVar(&path, "path", ".", "Path from where to start the walk from")
|
||||
flag.IntVar(&depth, "depth", 0, "Depth to display")
|
||||
flag.Parse()
|
||||
|
||||
t := NewTop(path)
|
||||
go t.Spawn(depth)
|
||||
go t.Collect()
|
||||
for {
|
||||
select {
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
fmt.Printf("\033[H\033[2J")
|
||||
fmt.Println(t)
|
||||
}
|
||||
}
|
||||
}
|
28
real_walker.go
Normal file
28
real_walker.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type RealWalker struct {
|
||||
path string
|
||||
report chan int64
|
||||
}
|
||||
|
||||
func (r *RealWalker) walkFunc(path string, info os.FileInfo, err error) error {
|
||||
switch mode := info.Mode(); {
|
||||
case mode.IsRegular():
|
||||
r.report <- info.Size()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RealWalker) Walk() {
|
||||
filepath.Walk(r.path, r.walkFunc)
|
||||
}
|
||||
|
||||
func NewRealWalker(path string, report chan int64) *RealWalker {
|
||||
r := &RealWalker{path: path, report: report}
|
||||
return r
|
||||
}
|
Loading…
Reference in a new issue