github.com/shogo82148/std@v1.22.1-0.20240327122250-4e474527810c/cmd/go/internal/script/engine.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package script implements a small, customizable, platform-agnostic scripting 6 // language. 7 // 8 // Scripts are run by an [Engine] configured with a set of available commands 9 // and conditions that guard those commands. Each script has an associated 10 // working directory and environment, along with a buffer containing the stdout 11 // and stderr output of a prior command, tracked in a [State] that commands can 12 // inspect and modify. 13 // 14 // The default commands configured by [NewEngine] resemble a simplified Unix 15 // shell. 16 // 17 // # Script Language 18 // 19 // Each line of a script is parsed into a sequence of space-separated command 20 // words, with environment variable expansion within each word and # marking an 21 // end-of-line comment. Additional variables named ':' and '/' are expanded 22 // within script arguments (expanding to the value of os.PathListSeparator and 23 // os.PathSeparator respectively) but are not inherited in subprocess 24 // environments. 25 // 26 // Adding single quotes around text keeps spaces in that text from being treated 27 // as word separators and also disables environment variable expansion. 28 // Inside a single-quoted block of text, a repeated single quote indicates 29 // a literal single quote, as in: 30 // 31 // 'Don''t communicate by sharing memory.' 32 // 33 // A line beginning with # is a comment and conventionally explains what is 34 // being done or tested at the start of a new section of the script. 35 // 36 // Commands are executed one at a time, and errors are checked for each command; 37 // if any command fails unexpectedly, no subsequent commands in the script are 38 // executed. The command prefix ! indicates that the command on the rest of the 39 // line (typically go or a matching predicate) must fail instead of succeeding. 40 // The command prefix ? indicates that the command may or may not succeed, but 41 // the script should continue regardless. 42 // 43 // The command prefix [cond] indicates that the command on the rest of the line 44 // should only run when the condition is satisfied. 45 // 46 // A condition can be negated: [!root] means to run the rest of the line only if 47 // the user is not root. Multiple conditions may be given for a single command, 48 // for example, '[linux] [amd64] skip'. The command will run if all conditions 49 // are satisfied. 50 package script 51 52 import ( 53 "github.com/shogo82148/std/bufio" 54 "github.com/shogo82148/std/io" 55 ) 56 57 // An Engine stores the configuration for executing a set of scripts. 58 // 59 // The same Engine may execute multiple scripts concurrently. 60 type Engine struct { 61 Cmds map[string]Cmd 62 Conds map[string]Cond 63 64 // If Quiet is true, Execute deletes log prints from the previous 65 // section when starting a new section. 66 Quiet bool 67 } 68 69 // NewEngine returns an Engine configured with a basic set of commands and conditions. 70 func NewEngine() *Engine 71 72 // A Cmd is a command that is available to a script. 73 type Cmd interface { 74 Run(s *State, args ...string) (WaitFunc, error) 75 76 Usage() *CmdUsage 77 } 78 79 // A WaitFunc is a function called to retrieve the results of a Cmd. 80 type WaitFunc func(*State) (stdout, stderr string, err error) 81 82 // A CmdUsage describes the usage of a Cmd, independent of its name 83 // (which can change based on its registration). 84 type CmdUsage struct { 85 Summary string 86 Args string 87 Detail []string 88 89 // If Async is true, the Cmd is meaningful to run in the background, and its 90 // Run method must return either a non-nil WaitFunc or a non-nil error. 91 Async bool 92 93 // RegexpArgs reports which arguments, if any, should be treated as regular 94 // expressions. It takes as input the raw, unexpanded arguments and returns 95 // the list of argument indices that will be interpreted as regular 96 // expressions. 97 // 98 // If RegexpArgs is nil, all arguments are assumed not to be regular 99 // expressions. 100 RegexpArgs func(rawArgs ...string) []int 101 } 102 103 // A Cond is a condition deciding whether a command should be run. 104 type Cond interface { 105 Eval(s *State, suffix string) (bool, error) 106 107 Usage() *CondUsage 108 } 109 110 // A CondUsage describes the usage of a Cond, independent of its name 111 // (which can change based on its registration). 112 type CondUsage struct { 113 Summary string 114 115 // If Prefix is true, the condition is a prefix and requires a 116 // colon-separated suffix (like "[GOOS:linux]" for the "GOOS" condition). 117 // The suffix may be the empty string (like "[prefix:]"). 118 Prefix bool 119 } 120 121 // Execute reads and executes script, writing the output to log. 122 // 123 // Execute stops and returns an error at the first command that does not succeed. 124 // The returned error's text begins with "file:line: ". 125 // 126 // If the script runs to completion or ends by a 'stop' command, 127 // Execute returns nil. 128 // 129 // Execute does not stop background commands started by the script 130 // before returning. To stop those, use [State.CloseAndWait] or the 131 // [Wait] command. 132 func (e *Engine) Execute(s *State, file string, script *bufio.Reader, log io.Writer) (err error) 133 134 // ListCmds prints to w a list of the named commands, 135 // annotating each with its arguments and a short usage summary. 136 // If verbose is true, ListCmds prints full details for each command. 137 // 138 // Each of the name arguments should be a command name. 139 // If no names are passed as arguments, ListCmds lists all the 140 // commands registered in e. 141 func (e *Engine) ListCmds(w io.Writer, verbose bool, names ...string) error 142 143 // ListConds prints to w a list of conditions, one per line, 144 // annotating each with a description and whether the condition 145 // is true in the state s (if s is non-nil). 146 // 147 // Each of the tag arguments should be a condition string of 148 // the form "name" or "name:suffix". If no tags are passed as 149 // arguments, ListConds lists all conditions registered in 150 // the engine e. 151 func (e *Engine) ListConds(w io.Writer, s *State, tags ...string) error