τau / os /

os.tau

source
/Users/niconex/Documents/tau/stdlib/os/os.tau
1# os - files.2#3# Everything goes through syscall, no dependency on the C library of the host.45syscall = import("syscall")6io = import("io")78O_RDONLY = syscall.O_RDONLY9O_WRONLY = syscall.O_WRONLY10O_RDWR = syscall.O_RDWR11O_CREAT = syscall.O_CREAT12O_TRUNC = syscall.O_TRUNC13O_APPEND = syscall.O_APPEND14O_EXCL = syscall.O_EXCL1516bufsize = 327681718newFile = fn(fd, path) {19	f = new()20	f.fd = fd21	f.Name = path2223	# Read returns up to n bytes, or an empty bytes value at end of file.24	f.Read = fn(n) {25		if n == null {26			n = bufsize27		}2829		buf = bytes(n)30		if failed(read = syscall.Read(f.fd, buf, n)) {31			return read32		}33		return slice(buf, 0, read)34	}3536	# ReadAll returns the rest of the file.37	f.ReadAll = fn() { io.ReadAll(f) }3839	f.ReadString = fn() { string(f.ReadAll()) }4041	# Write writes a string or bytes and returns how many bytes went out.42	f.Write = fn(data) {43		if type(data) != "bytes" {44			if failed(data = bytes(string(data))) {45				return data46			}47		}48		return syscall.Write(f.fd, data, len(data))49	}5051	f.Seek = fn(offset, whence) { syscall.Lseek(f.fd, offset, whence) }52	f.Close = fn() { syscall.Close(f.fd) }5354	return f55}5657# Open returns the file at path open with the given flags. Perm is only used58# when the file is created, 0644 is the usual value.59Open = fn(path, flags, perm) {60	if flags == null {61		flags = O_RDONLY62	}63	if perm == null {64		perm = 064465	}6667	if failed(fd = syscall.Open(path, flags, perm)) {68		return fd69	}70	return newFile(fd, path)71}7273# Create opens path for writing, truncating it or creating it as needed.74Create = fn(path) { Open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644) }7576# ReadFile returns the whole content of path as bytes.77ReadFile = fn(path) {78	if failed(f = Open(path, O_RDONLY, 0)) {79		return f80	}8182	data = f.ReadAll()83	f.Close()84	return data85}8687ReadFileString = fn(path) {88	if failed(b = ReadFile(path)) {89		return b90	}91	return string(b)92}9394# WriteFile writes data, a string or bytes, to path.95WriteFile = fn(path, data) {96	if failed(f = Create(path)) {97		return f98	}99100	n = f.Write(data)101	f.Close()102	return n103}104105# Mkdir, Remove, Rmdir and Chmod give back nothing when they worked and an106# error when they didn't, the way Go does.107108Mkdir = fn(path, perm) {109	if perm == null {110		perm = 0755111	}112	return nilOrError(syscall.Mkdir(path, perm))113}114115Remove = fn(path) { nilOrError(syscall.Unlink(path)) }116Rmdir = fn(path) { nilOrError(syscall.Rmdir(path)) }117Chmod = fn(path, mode) { nilOrError(syscall.Chmod(path, mode)) }118119# nilOrError keeps an error and throws away the number a system call returns120# when there is nothing to say.121nilOrError = fn(x) {122	if failed(x) {123		return x124	}125	return null126}127128# Exists reports whether path exists.129Exists = fn(path) { !failed(syscall.Access(path, syscall.F_OK)) }130131# ========== Standard Streams ==========132133Stdin = newFile(0, "/dev/stdin")134Stdout = newFile(1, "/dev/stdout")135Stderr = newFile(2, "/dev/stderr")136137# ========== Arguments and Environment ==========138139# args reads the command line the runtime left in the environment, one140# variable per argument, and takes it away so that a process started from here141# doesn't inherit it.142args = fn() {143	n = syscall.Getenv("TAU_ARGC")144	if n == null {145		return []146	}147	syscall.Unsetenv("TAU_ARGC")148149	out = []150	for i = 0; i < int(n); ++i {151		name = "TAU_ARG{i}"152		out = append(out, syscall.Getenv(name))153		syscall.Unsetenv(name)154	}155	return out156}157158# Args is the command line: the program itself first, then its arguments.159Args = args()160161# Getenv returns the value of the environment variable name, or an empty162# string when it isn't set. LookupEnv tells the two apart.163Getenv = fn(name) {164	v = syscall.Getenv(name)165	if v == null {166		return ""167	}168	return v169}170171# LookupEnv returns the value of name and whether it was set at all.172LookupEnv = fn(name) {173	v = syscall.Getenv(name)174	return [if v == null { "" } else { v }, v != null]175}176177Setenv = fn(name, value) { syscall.Setenv(name, value) }178Unsetenv = fn(name) { syscall.Unsetenv(name) }179180# ========== Files and Directories ==========181182Rename = fn(from, to) { syscall.Rename(from, to) }183Getwd = fn() { syscall.Getwd() }184Chdir = fn(path) { syscall.Chdir(path) }185186# TempDir returns the directory for temporary files.187TempDir = fn() {188	d = syscall.Getenv("TMPDIR")189	if d == null {190		return "/tmp"191	}192	# macOS sets TMPDIR with a trailing slash; drop it so joining a name does193	# not leave a doubled separator, the way Go's os.TempDir does.194	for len(d) > 1 && d[len(d)-1] == "/" {195		d = slice(d, 0, len(d)-1)196	}197	return d198}199200# Stat returns what is known about the file at path: its Name, Size in bytes,201# Mode, whether it IsDir and when it was last modified, in seconds since the202# Unix epoch.203Stat = fn(path) {204	if failed(size = syscall.StatSize(path)) {205		return size206	}207	if failed(mode = syscall.StatMode(path)) {208		return mode209	}210	if failed(mtime = syscall.StatMtime(path)) {211		return mtime212	}213214	st = new()215	st.Name = path216	st.Size = size217	st.Mode = mode218	st.Perm = mode & 0777219	st.IsDir = (mode & syscall.S_IFMT) == syscall.S_IFDIR220	st.ModTime = mtime221	return st222}223224# IsDir reports whether path is a directory. A path that isn't there is not.225IsDir = fn(path) {226	d = syscall.StatIsDir(path)227	if failed(d) {228		return false229	}230	return d231}232233# ReadDir returns the names inside the directory at path, sorted, without234# "." and "..".235ReadDir = fn(path) {236	if failed(names = syscall.Readdirnames(path)) {237		return names238	}239240	out = []241	for i = 0; i < len(names); ++i {242		if names[i] != "." && names[i] != ".." {243			out = append(out, names[i])244		}245	}246	return sorted(out)247}248249# sorted returns the strings in order. Insertion sort: a directory holds few250# enough names that anything smarter would only be longer to read.251sorted = fn(l) {252	for i = 1; i < len(l); ++i {253		v = l[i]254		j = i - 1255		for j >= 0 && l[j] > v {256			l[j + 1] = l[j]257			--j258		}259		l[j + 1] = v260	}261	return l262}263264# MkdirAll creates path and every directory missing above it.265MkdirAll = fn(path, perm) {266	if perm == null {267		perm = 0755268	}269	if IsDir(path) {270		return null271	}272273	parts = []274	start = 0275	for i = 0; i < len(path); ++i {276		if path[i] == "/" {277			parts = append(parts, slice(path, start, i))278			start = i + 1279		}280	}281	parts = append(parts, slice(path, start, len(path)))282283	# An absolute path starts with an empty part, which rebuilds the leading284	# slash on its own.285	cur = ""286	for i = 0; i < len(parts); ++i {287		if parts[i] == "" {288			# A leading empty part is the root and rebuilds the slash; an289			# interior one is a doubled separator and means nothing.290			if i == 0 {291				cur = "/"292			}293			continue294		}295296		cur = if cur == "" || cur == "/" { cur + parts[i] } else { cur + "/" + parts[i] }297		if !IsDir(cur) {298			if failed(err = Mkdir(cur, perm)) {299				return err300			}301		}302	}303	return null304}305306# RemoveAll removes path and everything under it.307RemoveAll = fn(path) {308	if !IsDir(path) {309		if !Exists(path) {310			return null311		}312		return Remove(path)313	}314315	if failed(names = ReadDir(path)) {316		return names317	}318	for i = 0; i < len(names); ++i {319		if failed(err = RemoveAll(path + "/" + names[i])) {320			return err321		}322	}323	return Rmdir(path)324}