okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

nekomimi.petfeat: background cells, jobs page, and session list fixesf65a648

main
3.8 KiB127 linesraw
1package daemon
2
3import (
4	"context"
5	"encoding/json"
6	"errors"
7	"maps"
8	"strings"
9	"unicode"
10)
11
12type CommandResult struct {
13	Effort    *EffortResult
14	Page      *PageDocument
15	Message   string
16	Result    json.RawMessage
17	Submitted bool
18}
19
20func ListSessionCommands(ctx context.Context, conn *Connection, session string) ([]SessionCommand, error) {
21	catalog, err := readCapabilityCatalog(ctx, conn, session, false)
22	if err != nil {
23		return nil, err
24	}
25	return catalog.Commands, nil
26}
27
28// InvokeDeclaredCommand follows the catalog's HTTP operation or admits its typed input.
29func InvokeDeclaredCommand(ctx context.Context, conn *Connection, snapshot Session, command SessionCommand, arguments map[string]json.RawMessage) (CommandResult, error) {
30	if command.Delivery == "input" {
31		encoded, err := json.Marshal(arguments)
32		if err != nil {
33			return CommandResult{}, err
34		}
35		request := SubmissionRequest{Type: "command", Name: command.CommandID, CommandArguments: encoded}
36		handle, err := NewSubmission(snapshot.ID, request)
37		if err != nil {
38			return CommandResult{}, err
39		}
40		_, err = SubmitOperation(ctx, conn, handle)
41		return CommandResult{Submitted: err == nil}, err
42	}
43	if command.Operation.PathTemplate == "" {
44		return CommandResult{}, errors.New("the command has no declared HTTP operation")
45	}
46	data, err := executeBoundOperation(ctx, conn, command.Operation, nil, arguments, &snapshot)
47	if err != nil {
48		return CommandResult{}, err
49	}
50	if command.Delivery == "read" {
51		page, err := readResultPage(data, command.Name, command.Description)
52		if err != nil {
53			return CommandResult{}, err
54		}
55		page.Session = &snapshot
56		page.read = &pageRead{Name: command.Name, Description: command.Description, Operation: command.Operation, Form: maps.Clone(arguments)}
57		return CommandResult{Result: data, Page: page}, nil
58	}
59	return extensionResult(data), nil
60}
61
62// ParseCommandArguments reads the text typed after a command name. Words fill
63// the declared arguments in order and the last argument takes the rest of the
64// line; a JSON object names them instead.
65func ParseCommandArguments(command SessionCommand, text string) (map[string]json.RawMessage, error) {
66	text = strings.TrimSpace(text)
67	if len(command.Arguments) > 1 && strings.HasPrefix(text, "{") {
68		return namedCommandArguments(command, text)
69	}
70	result := map[string]json.RawMessage{}
71	for index, argument := range command.Arguments {
72		word := text
73		if index < len(command.Arguments)-1 {
74			word, text = cutWord(text)
75		} else {
76			text = ""
77		}
78		if word == "" && !argument.Required {
79			continue
80		}
81		value, err := actionFieldValue(argument.field, word)
82		if err != nil {
83			return nil, err
84		}
85		result[argument.Name] = value
86	}
87	if text != "" {
88		return nil, errors.New(command.Name + " takes no arguments")
89	}
90	return result, nil
91}
92
93func cutWord(text string) (string, string) {
94	end := strings.IndexFunc(text, unicode.IsSpace)
95	if end < 0 {
96		return text, ""
97	}
98	return text[:end], strings.TrimSpace(text[end:])
99}
100
101func namedCommandArguments(command SessionCommand, text string) (map[string]json.RawMessage, error) {
102	result := map[string]json.RawMessage{}
103	if err := json.Unmarshal([]byte(text), &result); err != nil {
104		return nil, errors.New("this command accepts words in argument order or a JSON object with its declared argument names")
105	}
106	for _, field := range command.Arguments {
107		raw, ok := result[field.Name]
108		if ok {
109			if err := validateFormValue(field.field, raw); err != nil {
110				return nil, err
111			}
112		}
113		if !ok && field.Required {
114			return nil, errors.New("missing command argument: " + field.Name)
115		}
116	}
117	for name := range result {
118		known := false
119		for _, field := range command.Arguments {
120			known = known || field.Name == name
121		}
122		if !known {
123			return nil, errors.New("unknown command argument: " + name)
124		}
125	}
126	return result, nil
127}