fs/dir
Creating, listing, copying, moving and removing directories.
import fs.dir
The fs.dir package covers everything you do with directories: create them, list them, walk a tree, measure it, copy, move and delete it. It is built on the folder functions of os and adds two things: when a function fails it records the detail (an IOError with the operation and the path) in os.error, and Move works across disks. For files use fs.file, and to build the paths fs.path.
Listing functions return bare names; Walk returns full paths. Functions that change the disk return 0 or -1.
What people get wrong:
- Using
Existsfor files. Here it is true only for a directory; for files usefs.file.Exists. - Expecting
CopyandMoveto refuse an existing target. Both merge into it: files with the same name are replaced and other files stay. - Pointing
RemoveAll,Walk,SizeorCopyat a tree with symbolic links. They follow a link to a folder as if it were part of the tree, soRemoveAlldeletes the contents of the link's target and a link loop never ends.
Create a project layout
MkdirAll creates every missing parent and succeeds when the folder is already there, so it is the safe choice for setting up a layout that may partly exist. List, ListDirs and ListFiles return bare names, not full paths, in whatever order the file system keeps them, and that order differs between systems. Sort them with sort.Sort before you show or compare them.
import fs.dir
import fs.file
import sort
func main:
each d in ["shop/src/models", "shop/src/views", "shop/tests", "shop/docs"]:
dir.MkdirAll(d)
end
file.WriteText("shop/main.salam", "func main:\nend\n")
file.WriteText("shop/README.md", "# Shop\n")
file.WriteText("shop/src/models/order.salam", "")
top := dir.ListDirs("shop")
sort.Sort(top)
println "folders:", top
files := dir.ListFiles("shop")
sort.Sort(files)
println "files: ", files
println "src has", dir.ListDirs("shop/src").len(), "folders"
dir.RemoveAll("shop")
endWalk a whole tree
Walk returns the path of every file below a directory, already joined to the directory you passed in, so each one can go straight to an fs.file function. Directories themselves are not in the list, and like the listing functions it has no fixed order. This example counts files by extension and adds up their sizes, which is what Size does in one call.
import fs.dir
import fs.file
import fs.path
func main:
dir.MkdirAll("site/pages/blog")
dir.MkdirAll("site/assets/img")
file.WriteText("site/pages/index.html", "<h1>Home</h1>")
file.WriteText("site/pages/blog/first.html", "<p>First post</p>")
file.WriteText("site/assets/style.css", "body { margin: 0 }")
file.WriteText("site/assets/img/logo.svg", "<svg/>")
counts := HashMap {} as HashMap<str, int>
mut bytes := 0 as i64
each p in dir.Walk("site"):
ext := path.Ext(p)
if counts.has(ext): counts.put(ext, counts.get(ext) + 1) else: counts.put(ext, 1) end
bytes += file.Size(p)
end
each ext in [".html", ".css", ".svg"]:
println ext, counts.get(ext)
end
println "total:", bytes, "bytes, same as dir.Size:", bytes == dir.Size("site")
dir.RemoveAll("site")
endKeep rotating backups
Copy copies a tree with everything in it (file contents only; permissions are not kept), and Move renames one, falling back to copy and delete when the rename fails, for example because the target is on another disk. Together they make a simple rotation: drop the oldest backup, shift the others up by one, and copy the live folder to .bak1. The oldest is removed first so that its name is free before anything moves onto it.
import fs.dir
import fs.file
import str
func backup(src: str, keep: int):
dir.RemoveAll(src.concat(".bak").concat(str.FromInt(keep)))
mut n := keep - 1
until n >= 1:
from := src.concat(".bak").concat(str.FromInt(n))
if dir.Exists(from): dir.Move(from, src.concat(".bak").concat(str.FromInt(n + 1))) end
n -= 1
end
dir.Copy(src, src.concat(".bak1"))
end
func main:
dir.MkdirAll("notes")
repeat 1 to 4 in day:
file.WriteText("notes/today.txt", "day ".concat(str.FromInt(day)))
backup("notes", 3)
end
repeat 1 to 3 in n:
name := "notes.bak".concat(str.FromInt(n))
println name, file.ReadText(name.concat("/today.txt"))
end
println "notes.bak4 exists:", dir.Exists("notes.bak4")
each d in ["notes", "notes.bak1", "notes.bak2", "notes.bak3"]: dir.RemoveAll(d) end
endErrors and pitfalls
Every function that changes the disk returns 0 or -1, and on failure it leaves a short message in os.error. The message names the operation that failed, not the reason, so when the reason matters, check the likely causes yourself: the folder already exists, its parent is missing, or it is not empty.
import fs.dir
import fs.file
import os.error
func report(what: str, rc: int):
if rc == 0:
println what, "ok"
else:
println what, "failed:", error.LastMessage()
end
end
func main:
report("mkdir data", dir.Mkdir("data"))
report("mkdir data again", dir.Mkdir("data"))
report("mkdir a/b/c", dir.Mkdir("a/b/c"))
file.WriteText("data/x.txt", "x")
report("remove data", dir.Remove("data"))
println "data/x.txt is a dir:", dir.Exists("data/x.txt"), "is a file:", file.Exists("data/x.txt")
report("remove all data", dir.RemoveAll("data"))
println "listing a missing dir:", dir.List("data").len()
endMkdirfails when the folder exists or its parent is missing;MkdirAllhandles both.Removeonly removes an empty folder.RemoveAlldeletes everything inside without asking, so build its argument carefully and never pass it a path straight from user input; path.IsSafe checks that a path stays inside a base folder.Existsis false for a file, andListon a missing folder gives an empty vector rather than an error.
Which function for which job
| Job | Function |
|---|---|
| Create one folder in an existing parent | Mkdir |
| Create a whole path of folders | MkdirAll |
| Names inside one folder | List, or ListDirs and ListFiles for one kind only |
| Every file below a folder, with paths | Walk |
| Total size of a tree | Size |
| Duplicate a tree | Copy |
| Rename or move a tree | Move |
| Delete an empty folder | Remove |
| Delete a folder and its contents | RemoveAll |
Functions
Mkdir
func Mkdir(p: str): intos.error record) when it exists or its parent is missing.import fs.dir
import fs.file
func main:
dir.Mkdir("library")
dir.MkdirAll("library/poetry/hafez")
file.WriteText("library/index.txt", "3 books")
file.WriteText("library/poetry/hafez/ghazal.txt", "a line of verse")
println dir.Exists("library"), dir.IsDir("library/index.txt")
println dir.List("library").len(), dir.ListDirs("library"), dir.ListFiles("library")
println dir.Walk("library").len(), dir.Size("library") > 0
println dir.Permissions("library") > 0, dir.ModTime("library") > 0
dir.Copy("library", "backup")
dir.Move("backup", "archive")
println dir.Exists("backup"), dir.Exists("archive/poetry/hafez"), file.Exists("archive/poetry/hafez/ghazal.txt")
dir.Mkdir("empty")
dir.Remove("empty")
dir.RemoveAll("library")
dir.RemoveAll("archive")
println dir.Exists("library"), dir.Exists("empty")
endMkdirAll
func MkdirAll(p: str): intThe example for this item is shown with the ones above: fs-dir-List.salam
Remove
func Remove(p: str): intThe example for this item is shown with the ones above: fs-dir-List.salam
RemoveAll
func RemoveAll(p: str): intThe example for this item is shown with the ones above: fs-dir-List.salam
List
func List(p: str): Vector<str>. and .., in no fixed order. A missing directory gives an empty vector.The example for this item is shown with the ones above: fs-dir-List.salam
ListDirs
func ListDirs(p: str): Vector<str>The example for this item is shown with the ones above: fs-dir-List.salam
ListFiles
func ListFiles(p: str): Vector<str>The example for this item is shown with the ones above: fs-dir-List.salam
Walk
func Walk(p: str): Vector<str>os.Walk.The example for this item is shown with the ones above: fs-dir-List.salam
Copy
func Copy(src: str, dst: str): intdst, creating it if needed and merging with what is already there. Permissions are not copied.The example for this item is shown with the ones above: fs-dir-List.salam
Move
func Move(src: str, dst: str): intThe example for this item is shown with the ones above: fs-dir-List.salam
Exists
func Exists(p: str): boolThe example for this item is shown with the ones above: fs-dir-List.salam
IsDir
func IsDir(p: str): boolThe example for this item is shown with the ones above: fs-dir-List.salam
Size
func Size(p: str): i64The example for this item is shown with the ones above: fs-dir-List.salam
Permissions
func Permissions(p: str): intThe example for this item is shown with the ones above: fs-dir-List.salam
ModTime
func ModTime(p: str): i64The example for this item is shown with the ones above: fs-dir-List.salam