github.com/Benchkram/bob@v0.0.0-20220321080157-7c8f3876e225/pkg/execctl/tree.go (about)

     1  package execctl
     2  
     3  import (
     4  	"context"
     5  	"github.com/Benchkram/bob/pkg/ctl"
     6  	"golang.org/x/sync/errgroup"
     7  )
     8  
     9  // assert cmdTree implements the CommandTree interface
    10  var _ ctl.CommandTree = (*cmdTree)(nil)
    11  
    12  type cmdTree struct {
    13  	*Cmd
    14  
    15  	subcommands []*cmdTree
    16  }
    17  
    18  func NewCmdTree(root *Cmd, subcommands ...*Cmd) *cmdTree {
    19  	cmds := make([]*cmdTree, 0)
    20  
    21  	for _, cmd := range subcommands {
    22  		cmds = append(cmds, NewCmdTree(cmd))
    23  	}
    24  
    25  	return &cmdTree{Cmd: root, subcommands: cmds}
    26  }
    27  
    28  func (c *cmdTree) Subcommands() []ctl.Command {
    29  	subs := make([]ctl.Command, 0)
    30  
    31  	for _, cmd := range c.subcommands {
    32  		subs = append(subs, cmd)
    33  	}
    34  
    35  	return subs
    36  }
    37  
    38  // Start starts the command if it's not already running. It will return ErrCmdAlreadyStarted if it is.
    39  func (c *cmdTree) Start() error {
    40  	// use errgroup to speed up startup of the subcommands (run startup of them in parallel)
    41  	g, _ := errgroup.WithContext(context.Background())
    42  
    43  	for _, sub := range c.subcommands {
    44  		// required or ref is replaced within loop
    45  		subsub := sub
    46  
    47  		g.Go(func() error {
    48  			return subsub.Start()
    49  		})
    50  	}
    51  
    52  	err := g.Wait()
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	return c.Cmd.Start()
    58  }
    59  
    60  // Stop stops the running command with an os.Interrupt signal. It does not return an error if the command has
    61  // already exited gracefully.
    62  func (c *cmdTree) Stop() error {
    63  	err := c.Cmd.Stop()
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	g, _ := errgroup.WithContext(context.Background())
    69  
    70  	for _, sub := range c.subcommands {
    71  		// required or ref is replaced within loop
    72  		subsub := sub
    73  
    74  		g.Go(func() error {
    75  			return subsub.Stop()
    76  		})
    77  	}
    78  
    79  	return g.Wait()
    80  }
    81  
    82  // Restart first interrupts the command if it's already running, and then re-runs the command.
    83  func (c *cmdTree) Restart() error {
    84  	err := c.Stop()
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	return c.Start()
    90  }