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