1 # ws/root: which workspace a command run in a directory belongs to 2 # (EZ-WS-2). ez reads ez.toml from the directory it runs in; in a workspace 3 # that directory may be a member, so a command first finds its root: the 4 # nearest directory at or above it, up to the top of the git checkout, whose 5 # ledger is a workspace root that makes it a member. With none, the 6 # directory is a project of its own, as it always was. The design is 7 # docs/rfc/ez-workspaces.md. 8 # 9 # Both halves are pure. `asks` names the directories whose ez.toml the 10 # interpreter reads, each with the path from it down to where ez runs, nearest 11 # first; `root.go` decides from what was read, in that order. A path is its 12 # segments, relative to the top of the checkout, as `git rev-parse 13 # --show-prefix` prints it (`here.of`). 14 import Base 15 import ./workspace.bend as W 16 17 # a directory at or above where ez runs: its path from the top of the 18 # checkout, and the path from it down to where ez runs 19 type Ask is Data: 20 Ask{dir: List<&2, String>, rel: List<&2, String>} 21 22 # one such directory, read: its ez.toml's text, or None when it has none 23 type Up is Data: 24 Up{dir: List<&2, String>, rel: List<&2, String>, text: Maybe<&2, String>} 25 26 # where a command run in a directory stands: in a project of its own, in a 27 # workspace at `root` as the member `rel` below it (`[]` for the root itself), 28 # or nowhere, because a workspace table above it could not be read 29 type Found is Data: 30 Alone{} 31 Within{root: List<&2, String>, rel: List<&2, String>} 32 Broken{why: String} 33 34 # where ez runs, from what `git rev-parse --show-prefix` printed: the 35 # segments before the `/` it ends with, none at the top. Anything else is no 36 # answer. 37 def here.of(+prefix: String) -> Maybe<&2, List<&2, String>>: 38 W.cut(W.segs.of(prefix), "") 39 40 # every directory asked about from one segment further down, now one segment 41 # further from the top 42 def asks.under(+hd: String, xs: List<&2, Ask>) -> List<&2, Ask>: 43 match xs: 44 case []: 45 [] 46 case Ask{ds, rs} <> tl: 47 Ask{hd <> ds, rs} <> asks.under(hd, tl) 48 49 # the directories whose ez.toml is read: where ez runs, then each directory 50 # above it in turn, ending at the top of the checkout 51 def asks(here: List<&2, String>) -> List<&2, Ask>: 52 match here: 53 case []: 54 [Ask{[], []}] 55 case +hd <> +tl: 56 List.append(&2, Ask, asks.under(hd, asks(tl)), [Ask{[], hd <> tl}]) 57 58 # whether a workspace root makes the directory this far below it a member: 59 # the root itself always, and any other when an entry names it and no 60 # exclusion does 61 def claims(+spec: W.Spec, rel: List<&2, String>) -> Bool: 62 match rel: 63 case []: 64 True{} 65 case +hd <> +tl: 66 W.chosen(spec, hd <> tl) 67 68 # a workspace root claims the directory, so the search ends there, or does 69 # not, and goes on above it 70 def root.claim(hit: Bool, +dir: List<&2, String>, +rel: List<&2, String>, rest: Found) -> Found: 71 match hit: 72 case True{}: 73 Within{dir, rel} 74 case False{}: 75 rest 76 77 # what one directory's ledger says, against what the directories above it 78 # decided 79 def root.read(rd: W.Read, +dir: List<&2, String>, +rel: List<&2, String>, rest: Found) -> Found: 80 match rd: 81 case W.Lone{}: 82 rest 83 case W.Bad{why}: 84 Broken{why} 85 case W.Root{+spec}: 86 root.claim(claims(spec, rel), dir, rel, rest) 87 88 # one directory, with or without a ledger 89 def root.at(text: Maybe<&2, String>, +dir: List<&2, String>, +rel: List<&2, String>, rest: Found) -> Found: 90 match text: 91 case None{}: 92 rest 93 case Some{tt}: 94 root.read(W.spec.of(tt), dir, rel, rest) 95 96 # the root, from the directories read, nearest first: the first whose ledger 97 # is a workspace root that claims where ez runs, a table that could not be 98 # read if one comes first, and a project of its own when there is neither 99 def root.go(ups: List<&2, Up>) -> Found: 100 match ups: 101 case []: 102 Alone{} 103 case Up{ds, rs, tx} <> tl: 104 rest = root.go(tl) 105 root.at(tx, ds, rs, rest)