-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexec.go
More file actions
165 lines (144 loc) · 6.08 KB
/
Copy pathexec.go
File metadata and controls
165 lines (144 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package main
import (
"context"
"fmt"
"strings"
mdriver "github.com/vista-cloud-dev/m-driver-sdk"
"github.com/vista-cloud-dev/m-iris/clikit"
"github.com/vista-cloud-dev/m-iris/internal/config"
)
// execCmd is the exec axis (driver-contract §5.3): run M against the attached
// namespace. It is written against execTransport (newExecTransport selects the
// strategy by --transport), so every verb works on remote (Atelier PUT +
// m.iris.Runner SQL substrate, output recovered from a result global) and on
// local/docker (`iris session`, device output captured directly). load stages +
// compiles routine source (neutral .m → .int); run/eval execute an entryref / one
// command under a fault trap that surfaces a structured engineError (§7); abort
// stops a run still in flight under its ephemeral --prefix.
type execCmd struct {
Load execLoadCmd `cmd:"" name:"load" help:"Stage routine source into the namespace (Atelier PUT) and compile it; neutral .m → .int. Compile faults surface as engineError."`
Run execRunCmd `cmd:"" name:"run" help:"Run an entryref (LABEL^ROUTINE) through the runner; args → the formallist. Faults surface as engineError."`
Eval execEvalCmd `cmd:"" name:"eval" help:"Evaluate a single M command through the runner. Faults surface as engineError."`
Abort execAbortCmd `cmd:"" name:"abort" help:"Stop a run still in flight under an ephemeral --prefix (the runner terminates its recorded process)."`
}
type execResult struct {
Stdout string `json:"stdout"`
Status int `json:"status"`
}
type execLoadResult struct {
Loaded []string `json:"loaded"`
Compiled bool `json:"compiled"`
}
// --- load --------------------------------------------------------------------
type execLoadCmd struct {
Paths []string `arg:"" optional:"" help:"Routine source files (or directories) to stage."`
Prefix string `help:"Ephemeral docname prefix applied to each staged routine." placeholder:"PREFIX"`
}
func (c *execLoadCmd) Run(cc *clikit.Context, conn *config.Conn) error {
if len(c.Paths) == 0 {
return clikit.Fail(clikit.ExitUsage, "NO_SOURCE", "exec load needs <paths…>", "")
}
tr, err := newExecTransport(conn)
if err != nil {
return err
}
res, err := tr.Load(context.Background(), mdriver.LoadRequest{Paths: c.Paths, Prefix: c.Prefix})
if err != nil {
return runtimeErr(err)
}
if res.EngineError != nil {
msg := strings.TrimSpace(res.EngineError.Mnemonic + " " + res.EngineError.Text)
return clikit.FailEngine(clikit.ExitRuntime, "COMPILE_ERROR", "compile failed: "+msg, "", toClikitEngineError(res.EngineError))
}
return cc.Result(execLoadResult{Loaded: nonNil(res.Loaded), Compiled: true}, func() {
cc.Title("load complete")
cc.KV([2]string{"loaded", fmt.Sprint(len(res.Loaded))}, [2]string{"compiled", "yes"})
fmt.Fprintln(cc.Stdout, cc.Success("routines staged + compiled"))
})
}
// --- run ---------------------------------------------------------------------
type execRunCmd struct {
EntryRef string `arg:"" help:"Entryref to run (LABEL^ROUTINE or ^ROUTINE)."`
Args []string `arg:"" optional:"" help:"Arguments passed to the entryref."`
Prefix string `help:"Ephemeral-run prefix; the runner keys its result global by it." placeholder:"PREFIX"`
}
func (c *execRunCmd) Run(cc *clikit.Context, conn *config.Conn) error {
return runExec(cc, conn, mdriver.ExecRequest{EntryRef: c.EntryRef, Args: c.Args, Prefix: c.Prefix})
}
// --- abort -------------------------------------------------------------------
type execAbortCmd struct {
Prefix string `help:"Ephemeral-run prefix to abort (the run id passed to 'exec run --prefix')." placeholder:"PREFIX"`
}
type execAbortResult struct {
Killed []string `json:"killed"`
}
func (c *execAbortCmd) Run(cc *clikit.Context, conn *config.Conn) error {
if c.Prefix == "" {
return clikit.Fail(clikit.ExitUsage, "NO_PREFIX", "exec abort needs --prefix", "")
}
tr, err := newExecTransport(conn)
if err != nil {
return err
}
killed, err := tr.Abort(context.Background(), c.Prefix)
if err != nil {
return runtimeErr(err)
}
return cc.Result(execAbortResult{Killed: nonNil(killed)}, func() {
if len(killed) == 0 {
fmt.Fprintln(cc.Stdout, cc.Faint("no run in flight under --prefix "+c.Prefix))
return
}
fmt.Fprintln(cc.Stdout, cc.Success(fmt.Sprintf("aborted %d run(s): %s", len(killed), strings.Join(killed, ", "))))
})
}
// --- eval --------------------------------------------------------------------
type execEvalCmd struct {
Command []string `arg:"" help:"M command to evaluate (joined with spaces; quote it as one shell arg)."`
}
func (c *execEvalCmd) Run(cc *clikit.Context, conn *config.Conn) error {
return runExec(cc, conn, mdriver.ExecRequest{Command: strings.Join(c.Command, " ")})
}
// --- shared ------------------------------------------------------------------
// runExec dispatches req through the remote runner and renders the result: a §7
// fault becomes an ok=false envelope with engineError (exit 5); otherwise
// {stdout, status}.
func runExec(cc *clikit.Context, conn *config.Conn, req mdriver.ExecRequest) error {
tr, err := newExecTransport(conn)
if err != nil {
return err
}
res, err := tr.Exec(context.Background(), req)
if err != nil {
return runtimeErr(err)
}
if res.EngineError != nil {
msg := res.EngineError.Mnemonic
if res.EngineError.Text != "" {
msg = strings.TrimSpace(msg + " " + res.EngineError.Text)
}
return clikit.FailEngine(clikit.ExitRuntime, "ENGINE_ERROR", msg, "", toClikitEngineError(res.EngineError))
}
return cc.Result(execResult{Stdout: res.Stdout, Status: res.Status}, func() {
if res.Stdout != "" {
fmt.Fprint(cc.Stdout, res.Stdout)
if !strings.HasSuffix(res.Stdout, "\n") {
fmt.Fprintln(cc.Stdout)
}
}
fmt.Fprintln(cc.Stdout, cc.Faint(fmt.Sprintf("status %d", res.Status)))
})
}
// toClikitEngineError converts the SDK §7 fault to clikit's own copy (drivers
// convert at the envelope boundary — consistency-protocol).
func toClikitEngineError(e *mdriver.EngineError) *clikit.EngineError {
if e == nil {
return nil
}
return &clikit.EngineError{
Routine: e.Routine,
Line: e.Line,
Mnemonic: e.Mnemonic,
Text: e.Text,
}
}