Skip to content

Commit

Permalink
Implemented directory:walk
Browse files Browse the repository at this point in the history
Sample usage looks like this:

```
(directory:walk "/etc" (lambda (x)
                         (if (directory? x)
                             (print "Got directory: %s" x)
                           (print "Got file: %s" x)
                           )))
```

To avoid the complexity of adding this as a built-in form the
implementation creates "directory:entries" to return the contents
of the given path, recursively.

If we have a list of file/directories, and a function, we can just
use apply to do the magic.

This closes #34.
  • Loading branch information
skx committed Oct 16, 2022
1 parent 70004d3 commit 11e8cf9
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 0 deletions.
30 changes: 30 additions & 0 deletions builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ func PopulateEnvironment(env *env.Environment) {
env.Set("contains?", &primitive.Procedure{F: containsFn, Help: helpMap["contains?"]})
env.Set("date", &primitive.Procedure{F: dateFn, Help: helpMap["date"]})
env.Set("directory?", &primitive.Procedure{F: directoryFn, Help: helpMap["directory?"]})
env.Set("directory:entries", &primitive.Procedure{F: directoryEntriesFn, Help: helpMap["directory:entries"]})
env.Set("eq", &primitive.Procedure{F: eqFn, Help: helpMap["eq"]})
env.Set("error", &primitive.Procedure{F: errorFn, Help: helpMap["error"]})
env.Set("exists?", &primitive.Procedure{F: existsFn, Help: helpMap["exists?"]})
Expand Down Expand Up @@ -300,6 +301,35 @@ func directoryFn(env *env.Environment, args []primitive.Primitive) primitive.Pri
return primitive.Bool(false)
}

// directoryEntriesFn returns the files beneath given path, recursively.
func directoryEntriesFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {

// We only need a single argument
if len(args) != 1 {
return primitive.Error("invalid argument count")
}

// Which is a string
pth, ok := args[0].(primitive.String)
if !ok {
return primitive.Error("argument not a string")
}

var res primitive.List

filepath.Walk(pth.ToString(), func(path string, info os.FileInfo, err error) error {

if err != nil {
return nil
}

res = append(res, primitive.String(path))
return nil
})

return res
}

// divideFn implements "/"
func divideFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {
// ensure we have at least one argument
Expand Down
98 changes: 98 additions & 0 deletions builtins/builtins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,104 @@ func TestDirectory(t *testing.T) {
if r.ToString() != "#f" {
t.Fatalf("wrong result, got %v", r.ToString())
}
}

// TestDirectoryEntries tests directory:entries
func TestDirectoryEntries(t *testing.T) {

// No arguments
out := directoryEntriesFn(ENV, []primitive.Primitive{})

// Will lead to an error
e, ok := out.(primitive.Error)
if !ok {
t.Fatalf("expected error, got %v", out)
}
if !strings.Contains(string(e), "argument count") {
t.Fatalf("got error, but wrong one %v", out)
}

// One argument, wrong type
out = directoryEntriesFn(ENV, []primitive.Primitive{primitive.Number(33)})

// Will lead to an error
e, ok = out.(primitive.Error)
if !ok {
t.Fatalf("expected error, got %v", out)
}
if !strings.Contains(string(e), "not a string") {
t.Fatalf("got error, but wrong one %v", out)
}

// Create a temporary directory
path, err := os.MkdirTemp("", "directory_")
if err != nil {
t.Fatalf("failed to create a temporary directory")
}

// Populate two files.
a := filepath.Join(path, "one.txt")
b := filepath.Join(path, "two.foo")

err = os.WriteFile(a, []byte("one.txt"), 0777)
if err != nil {
t.Fatalf("failed to write to file")
}
err = os.WriteFile(b, []byte("two.foo"), 0777)
if err != nil {
t.Fatalf("failed to write to file")
}

// Now we should find a list of two files if we walk the
// temporary directory.
res := directoryEntriesFn(ENV, []primitive.Primitive{
primitive.String(path),
})

// Will lead to a list
lst, ok2 := res.(primitive.List)
if !ok2 {
t.Fatalf("expected list, got %v", out)
}
if len(lst) != 3 {
t.Fatalf("failed to find expected file-count, got %v", lst)
}

// Delete one of the two files, and ensure we still find results
os.Remove(a)

// walk again
res = directoryEntriesFn(ENV, []primitive.Primitive{
primitive.String(path),
})

// Will lead to a list
lst, ok2 = res.(primitive.List)
if !ok2 {
t.Fatalf("expected list, got %v", out)
}
if len(lst) != 2 {
t.Fatalf("failed to find expected file-count, got %v", lst)
}

// Finally cleanup
os.RemoveAll(path)

// walk a missing directory
res = directoryEntriesFn(ENV, []primitive.Primitive{
primitive.String("/ fdsf dsf /this doesnt exist"),
})

// Will lead to a list still
lst, ok2 = res.(primitive.List)
if !ok2 {
t.Fatalf("expected list, got %v", out)
}

// but empty
if len(lst) != 0 {
t.Fatalf("failed to find expected file-count, got %v", lst)
}

}

Expand Down
8 changes: 8 additions & 0 deletions builtins/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ directory? returns true if the specified path exists, and is a directory.
See also: exists? file?
Example: (print (directory? "/etc"))

%%
directory:entries

directory:entries returns the names of all files/directories beneath the given
path, recursively. It is a helper function used to implement directory:walk

See also: directory:walk, glob
%%
eq

Expand Down Expand Up @@ -122,6 +129,7 @@ glob

glob returns files matching the given pattern, as a list.

See also: directory:entries directory:walk
Example: (print (glob "/etc/p*"))
%%
help
Expand Down
7 changes: 7 additions & 0 deletions stdlib/stdlib.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,10 @@

;; Define a legacy alias
(alias slurp file:read)


;; Handy function to invoke a callback on files
(set! directory:walk (fn* (path:string fn:function)
"Invoke the specified callback on every file beneath the given path."

(apply (directory:entries path) fn)))

0 comments on commit 11e8cf9

Please sign in to comment.