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}