diff --git a/go/catalogue/seat_from_definition.go b/go/catalogue/seat_from_definition.go index 38994426..7882915b 100644 --- a/go/catalogue/seat_from_definition.go +++ b/go/catalogue/seat_from_definition.go @@ -27,6 +27,14 @@ func SeatFromDefinition(d seatdef.Definition) Seat { } for _, st := range d.States { s.States = append(s.States, st.Name) + if st.View { + s.Views = append(s.Views, st.Name) + } + } + for _, a := range d.Accepts { + if a.Stream != "" { + s.AcceptedOn = append(s.AcceptedOn, a.Stream+"."+a.Name) + } } for _, r := range d.Receives { rv := Receivable{Kind: r.Kind, Comment: r.Comment, Dirs: r.Dirs, Offered: r.Offered} diff --git a/go/catalogue/seats.go b/go/catalogue/seats.go index ca1fe51e..528580d1 100644 --- a/go/catalogue/seats.go +++ b/go/catalogue/seats.go @@ -49,6 +49,12 @@ type Seat struct { // States are the names of the state the seat's holder keeps, which a module reads by `seat:.` (design 47 G // A-116, A-128). Compiled from the definition's states. States []string + // Views are the names of those states the seat offers as views, which a module follows by `consumes` + // `seat:.views.` (design 47 G §616, §699, A-131). Compiled from the definition's states marked `view`. + Views []string + // AcceptedOn are the accepted kinds that ride a stream, each as `.`, which a part names in `invokes` as + // `seat:..` (design 47 G A-102, §415). Compiled from the definition's accepts. + AcceptedOn []string // Receives is what other modules may contribute to the seat's holder, by kind (novox/hq ADR // 0212): each kind is text in the tool's own grammar, placed by the holder with // ${contribution::}. Compiled, never stored: like the protocol, it is the mesh's diff --git a/go/catalogue/state.go b/go/catalogue/state.go index 3608fad9..0afe1348 100644 --- a/go/catalogue/state.go +++ b/go/catalogue/state.go @@ -97,6 +97,21 @@ func SeatRead(read string) (seat, local string, ok bool, err error) { return rest[:at], rest[at+1:], true, nil } +// SeatViewConsume splits a consumed view of a seat, `seat:.views.`, into the seat and the state; ok is false for a +// consumed entry that is not of this kind (an event, `.`), and err says why a `seat:` entry is malformed. The +// holder is not in it: the grants part resolves it from the catalog half (design 47 G §616, A-131). +func SeatViewConsume(consume string) (seat, state string, ok bool, err error) { + rest, isSeat := strings.CutPrefix(consume, SeatReadPrefix) + if !isSeat { + return "", "", false, nil + } + seat, view, named := strings.Cut(rest, ".views.") + if !named || !stateName.MatchString(seat) || !stateName.MatchString(view) { + return "", "", true, fmt.Errorf("%q does not name a seat and its view: a consumed view of a seat is seat:.views.", consume) + } + return seat, view, true, nil +} + // ReadState splits a read into the owning module and the local name, or says why it is not one. func ReadState(read string) (module, local string, err error) { at := strings.LastIndex(read, ".") diff --git a/go/catalogue/state_test.go b/go/catalogue/state_test.go index f6dc005c..8e912168 100644 --- a/go/catalogue/state_test.go +++ b/go/catalogue/state_test.go @@ -88,8 +88,25 @@ func TestSeatRead(t *testing.T) { t.Errorf("SeatRead(%q) = %q, %q, %v, %v", row.read, seat, local, ok, err) } } - s := SeatFromDefinition(seatdef.Definition{Seat: "mesh-provisioner", Scope: "mesh", States: []seatdef.State{{Name: "plans"}, {Name: "provisions"}}}) - if len(s.States) != 2 || s.States[0] != "plans" || s.States[1] != "provisions" { - t.Errorf("the seat's states = %v", s.States) + for _, row := range []struct { + consume, seat, state string + ok, bad bool + }{ + {"seat:mesh-provisioner.views.plans", "mesh-provisioner", "plans", true, false}, + {"mesh-provisioner.views.plans", "", "", false, false}, + {"seat:mesh-provisioner.plans", "", "", true, true}, + {"seat:.views.plans", "", "", true, true}, + } { + seat, state, ok, err := SeatViewConsume(row.consume) + if seat != row.seat || state != row.state || ok != row.ok || (err != nil) != row.bad { + t.Errorf("SeatViewConsume(%q) = %q, %q, %v, %v", row.consume, seat, state, ok, err) + } + } + s := SeatFromDefinition(seatdef.Definition{Seat: "mesh-provisioner", Scope: "mesh", + States: []seatdef.State{{Name: "plans", View: true}, {Name: "provisions"}}, + Accepts: []seatdef.Message{{Name: "step", Stream: "check"}, {Name: "step"}}}) + if len(s.States) != 2 || s.States[0] != "plans" || s.States[1] != "provisions" || len(s.Views) != 1 || s.Views[0] != "plans" || + len(s.AcceptedOn) != 1 || s.AcceptedOn[0] != "check.step" { + t.Errorf("the seat's states = %v, views = %v, accepted on = %v", s.States, s.Views, s.AcceptedOn) } }