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/` (`Named`), as it is, or that there is none. 21 import Base 22 import ../toml/toml.bend as T 23 import ../lock/lock.bend as L 24 import ../lock/world.bend as W 25 import ../lock/up.bend as Up 26 27 # a question about one package of the lock, or one name it records 28 type Ask is Data: 29 Laid{hash: String} # the tree under BEND_LIB/, as its manifest names it 30 Git{hash: String, ask: Up.Ask} # a checkout at the pinned rev (`Up.Clone`) 31 Hub{ask: W.Ask} # the hub's manifest and every file it names (`W.Pkg`) 32 Named{nv: String} # the file BEND_LIB/names/, as it is 33 34 # the answer to one question: a manifest and the texts of the files it 35 # names, what a clone answered, or a names file's text, None when there is 36 # none 37 type Answer is Data: 38 Read{answer: W.Answer} # Laid, Hub 39 Said{answer: Up.Answer} # Git 40 Held{got: Maybe<&2, String>} # Named 41 42 # one question, answered 43 type Reply is Data: 44 Reply{ask: Ask, answer: Answer} 45 46 # everything `ez fetch` reads 47 type World is Data: 48 World{here: String, ledger: Bool, lock: Maybe<&2, String>, replies: List<&2, Reply>} 49 50 # the ledger's path, which is fixed 51 def toml() -> String: 52 "ez.toml" 53 54 # the lock's path, which is fixed 55 def lockfile() -> String: 56 "ez.lock.toml" 57 58 # the directory the project is in, which every relative path in the lock is 59 # read against 60 def here(world: World) -> String: 61 World{h, _ledger, _lock, _replies} = world 62 h 63 64 # whether there is an ez.toml 65 def ledger(world: World) -> Bool: 66 World{_here, l, _lock, _replies} = world 67 l 68 69 # the lock's text, None when there is no ez.lock.toml 70 def lock(world: World) -> Maybe<&2, String>: 71 World{_here, _ledger, l, _replies} = world 72 l 73 74 # every question answered so far 75 def replies(world: World) -> List<&2, Reply>: 76 World{_here, _ledger, _lock, rs} = world 77 rs 78 79 # a lock's text, as tables; none when there is no lock 80 def sects.of(got: Maybe<&2, String>) -> List<&2, T.Sect>: 81 match got: 82 case None{}: 83 [] 84 case Some{text}: 85 T.sects(T.parse(text)) 86 87 # the lock, as tables 88 def sects(world: World) -> List<&2, T.Sect>: 89 sects.of(lock(world)) 90 91 # a hub question as a key 92 def hub.key(ask: W.Ask) -> String: 93 match ask: 94 case W.Pkg{hash, _src, hub}: 95 "hub\n" ++ hash ++ "\n" ++ hub 96 case W.Name{nv, hub}: 97 "hubname\n" ++ nv ++ "\n" ++ hub 98 case W.Lock{}: 99 "hublock" 100 101 # a question as a key. A lock's value never holds a newline, so the fields 102 # are kept apart by one. 103 def key(ask: Ask) -> String: 104 match ask: 105 case Laid{hash}: 106 "laid\n" ++ hash 107 case Git{hash, q}: 108 "git\n" ++ hash ++ "\n" ++ Up.key(q) 109 case Hub{q}: 110 hub.key(q) 111 case Named{nv}: 112 "named\n" ++ nv 113 114 # an answer looked up, or none yet 115 type Heard is Data: 116 Heard{answer: Answer} 117 Open{} 118 119 # this answer when it is the question's, otherwise the rest's. The rest 120 # arrives as a thunk, so the first hit ends the scan. 121 def look.step(hit: Bool, answer: Answer, rest: Unit -> Heard) -> Heard: 122 match hit: 123 case True{}: 124 Heard{answer} 125 case False{}: 126 rest(Unit{}) 127 128 # the first answer given to a question 129 def look(rs: List<&2, Reply>, +want: String) -> Heard: 130 match rs: 131 case []: 132 Open{} 133 case Reply{ask, answer} <> t: 134 look.step(String.eq(key(ask), want), answer, _u => look(t, want)) 135 136 # the answer to a question, or none yet 137 def heard(rs: List<&2, Reply>, ask: Ask) -> Heard: 138 look(rs, key(ask))