τau / path /

path.tau

source
/Users/niconex/Documents/tau/stdlib/path/path.tau
1# path - slash separated paths.2#3# Paths are strings and the separator is "/", the way it is on every system4# tau runs on. Nothing here touches the filesystem: these are string5# operations, and a path that doesn't exist is handled just the same.67strings = import("strings")89Separator = "/"1011# split returns the parts of p between the separators, empty parts included:12# they are what tells "/a" from "a".13split = fn(p) {14	out = []15	start = 01617	for i = 0; i < len(p); ++i {18		if p[i] == Separator {19			out = append(out, slice(p, start, i))20			start = i + 121		}22	}23	return append(out, slice(p, start, len(p)))24}2526# IsAbs reports whether p starts at the root.27IsAbs = fn(p) { len(p) > 0 && p[0] == Separator }2829# Base returns the last element of p. An empty path gives ".", a path made of30# separators gives "/".31Base = fn(p) {32	if p == "" {33		return "."34	}3536	# Trailing separators are not part of the name.37	end = len(p)38	for end > 0 && p[end - 1] == Separator {39		--end40	}41	if end == 0 {42		return Separator43	}4445	start = end46	for start > 0 && p[start - 1] != Separator {47		--start48	}49	return slice(p, start, end)50}5152# Dir returns everything but the last element of p, cleaned. A path without53# separators gives ".".54Dir = fn(p) {55	i = len(p) - 156	for i >= 0 && p[i] != Separator {57		--i58	}59	if i < 0 {60		return "."61	}62	return Clean(slice(p, 0, i + 1))63}6465# Ext returns the extension of p, the final dot included, or an empty string66# when the last element has none.67Ext = fn(p) {68	for i = len(p) - 1; i >= 0 && p[i] != Separator; --i {69		if p[i] == "." {70			return slice(p, i, len(p))71		}72	}73	return ""74}7576# Split returns the directory and the file part of p, joined back by "+".77Split = fn(p) {78	i = len(p) - 179	for i >= 0 && p[i] != Separator {80		--i81	}82	return [slice(p, 0, i + 1), slice(p, i + 1, len(p))]83}8485# Clean returns the shortest path with the same meaning: no "." elements, no86# double separators, and ".." resolved against what comes before it.87Clean = fn(p) {88	if p == "" {89		return "."90	}9192	abs = IsAbs(p)93	parts = split(p)94	out = []9596	for i = 0; i < len(parts); ++i {97		part = parts[i]9899		if part == "" || part == "." {100			continue101		}102103		if part == ".." {104			# Above the root there is the root, so an absolute path simply105			# drops it. A relative one has to keep it: it means something.106			if len(out) > 0 && out[len(out) - 1] != ".." {107				out = slice(out, 0, len(out) - 1)108				continue109			}110			if abs {111				continue112			}113		}114		out = append(out, part)115	}116117	res = strings.Join(out, Separator)118	if abs {119		return Separator + res120	}121	if res == "" {122		return "."123	}124	return res125}126127# Join joins the non empty elements with the separator and cleans the result.128Join = fn(elems) {129	out = []130	for i = 0; i < len(elems); ++i {131		if elems[i] != "" {132			out = append(out, elems[i])133		}134	}135	if len(out) == 0 {136		return ""137	}138	return Clean(strings.Join(out, Separator))139}