src/fetch/world.bend
1# fetch/world: what `ez fetch` reads. The directory the project is in
2# (`here`), which a relative path in the lock is anchored to; whether there is
3# an ez.toml, since fetch fills BEND_LIB for a project and nothing else; the
4# lock's text, None when there is no ez.lock.toml; and the answers to the
5# questions the planner (fetch/plan.bend) asked. There is no field for
6# BEND_LIB's path, the environment or the clock, because the planner reads
7# none of them.
8#
9# A question is about one package of the lock, by its hash: the tree already
10# under BEND_LIB (`Laid`), or the package fetched from where the lock says it
11# comes from. A git package is the upgrade's own `Clone` question
12# (lock/up.bend), a shallow checkout at the pinned rev with every file's
13# text; a hub package is the lock's own question (lock/world.bend), the
14# hub's manifest and every file it names. `Laid` and a hub package are
15# answered the way the lock answers its questions, with a manifest and the
16# texts of the files it names, and a clone the way the upgrade answers
17# `Clone`.
18#
19# A name the lock records is asked about too: the file bend reads its hash
20# from, `$BEND_LIB/names/<nv>` (`Named`), as it is, or that there is none.
21#
22# A workspace member's package (EZ-WS-8) is asked about as the committed
23# `.bend` files (`Tree`), answered the way `ez lock` lists them, and the
24# planner walks the member from its entry over them itself.
25import Base
26import ezx@1.11.0.0/src/toml/toml.bend as T
27import ezx@1.11.0.0/src/lock/lock.bend as L
28import ezx@1.11.0.0/src/lock/world.bend as W
29import ezx@1.11.0.0/src/lock/up.bend as Up
30
31# a question about one package of the lock, or one name it records
32type Ask is Data:
33 Laid{hash: String} # the tree under BEND_LIB/<hash>, as its manifest names it
34 Git{hash: String, ask: Up.Ask} # a checkout at the pinned rev (`Up.Clone`)
35 Hub{ask: W.Ask} # the hub's manifest and every file it names (`W.Pkg`)
36 Named{nv: String} # the file BEND_LIB/names/<nv>, as it is
37 Tree{hash: String, entry: String} # the committed files, a member's package walked over them
38
39# the answer to one question: a manifest and the texts of the files it
40# names, what a clone answered, or a names file's text, None when there is
41# none
42type Answer is Data:
43 Read{answer: W.Answer} # Laid, Hub
44 Said{answer: Up.Answer} # Git
45 Held{got: Maybe<&2, String>} # Named
46 Seen{listing: W.Listing} # Tree
47
48# one question, answered
49type Reply is Data:
50 Reply{ask: Ask, answer: Answer}
51
52# everything `ez fetch` reads
53type World is Data:
54 World{here: String, ledger: Bool, lock: Maybe<&2, String>, replies: List<&2, Reply>}
55
56# the ledger's path, which is fixed
57def toml() -> String:
58 "ez.toml"
59
60# the lock's path, which is fixed
61def lockfile() -> String:
62 "ez.lock.toml"
63
64# the directory the project is in, which every relative path in the lock is
65# read against
66def here(world: World) -> String:
67 World{h, _ledger, _lock, _replies} = world
68 h
69
70# whether there is an ez.toml
71def ledger(world: World) -> Bool:
72 World{_here, l, _lock, _replies} = world
73 l
74
75# the lock's text, None when there is no ez.lock.toml
76def lock(world: World) -> Maybe<&2, String>:
77 World{_here, _ledger, l, _replies} = world
78 l
79
80# every question answered so far
81def replies(world: World) -> List<&2, Reply>:
82 World{_here, _ledger, _lock, rs} = world
83 rs
84
85# a lock's text, as tables; none when there is no lock
86def sects.of(got: Maybe<&2, String>) -> List<&2, T.Sect>:
87 match got:
88 case None{}:
89 []
90 case Some{text}:
91 T.sects(T.parse(text))
92
93# the lock, as tables
94def sects(world: World) -> List<&2, T.Sect>:
95 sects.of(lock(world))
96
97# a hub question as a key
98def hub.key(ask: W.Ask) -> String:
99 match ask:
100 case W.Pkg{hash, _src, hub}:
101 "hub\n" ++ hash ++ "\n" ++ hub
102 case W.Name{nv, hub}:
103 "hubname\n" ++ nv ++ "\n" ++ hub
104 case W.Lock{}:
105 "hublock"
106
107# a question as a key. A lock's value never holds a newline, so the fields
108# are kept apart by one.
109def key(ask: Ask) -> String:
110 match ask:
111 case Laid{hash}:
112 "laid\n" ++ hash
113 case Git{hash, q}:
114 "git\n" ++ hash ++ "\n" ++ Up.key(q)
115 case Hub{q}:
116 hub.key(q)
117 case Named{nv}:
118 "named\n" ++ nv
119 case Tree{hash, _entry}:
120 "tree\n" ++ hash
121
122# an answer looked up, or none yet
123type Heard is Data:
124 Heard{answer: Answer}
125 Open{}
126
127# this answer when it is the question's, otherwise the rest's. The rest
128# arrives as a thunk, so the first hit ends the scan.
129def look.step(hit: Bool, answer: Answer, rest: Unit -> Heard) -> Heard:
130 match hit:
131 case True{}:
132 Heard{answer}
133 case False{}:
134 rest(Unit{})
135
136# the first answer given to a question
137def look(rs: List<&2, Reply>, +want: String) -> Heard:
138 match rs:
139 case []:
140 Open{}
141 case Reply{ask, answer} <> t:
142 look.step(String.eq(key(ask), want), answer, _u => look(t, want))
143
144# the answer to a question, or none yet
145def heard(rs: List<&2, Reply>, ask: Ask) -> Heard:
146 look(rs, key(ask))