github.com/aergoio/aergo@v1.3.1/cmd/brick/exec/batch.go (about)

     1  package exec
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"net/http"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/aergoio/aergo/cmd/brick/context"
    12  	"github.com/fsnotify/fsnotify"
    13  	"github.com/mattn/go-colorable"
    14  	"github.com/rs/zerolog"
    15  )
    16  
    17  var (
    18  	verboseBatch    = false
    19  	letBatchKnowErr error
    20  	batchErrorCount = 0
    21  	enableWatch     = false
    22  	watchFileList   []string
    23  	watcher         *fsnotify.Watcher
    24  )
    25  
    26  func EnableVerbose() {
    27  	verboseBatch = true
    28  }
    29  
    30  func GetBatchErrorCount() int {
    31  	return batchErrorCount
    32  }
    33  
    34  func EnableWatch() {
    35  	enableWatch = true
    36  }
    37  
    38  func init() {
    39  	registerExec(&batch{})
    40  }
    41  
    42  type batch struct {
    43  	level int
    44  }
    45  
    46  func (c *batch) Command() string {
    47  	return "batch"
    48  }
    49  
    50  func (c *batch) Syntax() string {
    51  	return fmt.Sprintf("%s", context.PathSymbol)
    52  }
    53  
    54  func (c *batch) Usage() string {
    55  	return fmt.Sprintf("batch `<batch_file_path>`")
    56  }
    57  
    58  func (c *batch) Describe() string {
    59  	return "batch run"
    60  }
    61  
    62  func (c *batch) Validate(args string) error {
    63  
    64  	_, err := c.parse(args)
    65  
    66  	return err
    67  }
    68  
    69  func (c *batch) readBatchFile(batchFilePath string) ([]string, error) {
    70  	if strings.HasPrefix(batchFilePath, "http") {
    71  		// search in the web
    72  		req, err := http.NewRequest("GET", batchFilePath, nil)
    73  		if err != nil {
    74  			return nil, err
    75  		}
    76  		client := &http.Client{}
    77  		resp, err := client.Do(req)
    78  		if err != nil {
    79  			return nil, err
    80  		}
    81  		defer resp.Body.Close()
    82  
    83  		var cmdLines []string
    84  		scanner := bufio.NewScanner(resp.Body)
    85  		for scanner.Scan() {
    86  			cmdLines = append(cmdLines, scanner.Text())
    87  		}
    88  
    89  		return cmdLines, nil
    90  	}
    91  
    92  	batchFile, err := os.Open(batchFilePath)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	defer batchFile.Close()
    97  
    98  	var cmdLines []string
    99  	scanner := bufio.NewScanner(batchFile)
   100  	for scanner.Scan() {
   101  		cmdLines = append(cmdLines, scanner.Text())
   102  	}
   103  
   104  	return cmdLines, nil
   105  }
   106  
   107  func (c *batch) parse(args string) (string, error) {
   108  	splitArgs := context.SplitSpaceAndAccent(args, false)
   109  	if len(splitArgs) != 1 {
   110  		return "", fmt.Errorf("invalid format. usage: %s", c.Usage())
   111  	}
   112  
   113  	batchFilePath := splitArgs[0].Text
   114  
   115  	if _, err := c.readBatchFile(batchFilePath); err != nil {
   116  		return "", fmt.Errorf("fail to read a brick batch file %s: %s", batchFilePath, err.Error())
   117  	}
   118  
   119  	return batchFilePath, nil
   120  }
   121  
   122  func (c *batch) Run(args string) (string, error) {
   123  	stdOut := colorable.NewColorableStdout()
   124  
   125  	var err error
   126  
   127  	for {
   128  		if c.level == 0 && enableWatch {
   129  			watcher, err = fsnotify.NewWatcher()
   130  			if err != nil {
   131  				return "", err
   132  			}
   133  			// clear screen
   134  			fmt.Fprintf(stdOut, "\033[H\033[2J")
   135  		}
   136  
   137  		prefix := ""
   138  
   139  		batchFilePath, _ := c.parse(args)
   140  		cmdLines, err := c.readBatchFile(batchFilePath)
   141  		if err != nil {
   142  			return "", err
   143  		}
   144  
   145  		c.level++
   146  
   147  		// set highest log level to turn off verbose
   148  		if false == verboseBatch {
   149  			zerolog.SetGlobalLevel(zerolog.ErrorLevel)
   150  			fmt.Fprintf(stdOut, "> %s\n", batchFilePath)
   151  		} else if verboseBatch && c.level != 1 {
   152  			prefix = fmt.Sprintf("%d-", c.level-1)
   153  			fmt.Fprintf(stdOut, "\n<<<<<<< %s\n", batchFilePath)
   154  		}
   155  
   156  		for i, line := range cmdLines {
   157  			lineNum := i + 1
   158  
   159  			cmd, args := context.ParseFirstWord(line)
   160  			if len(cmd) == 0 {
   161  				if verboseBatch {
   162  					fmt.Fprintf(stdOut, "\x1B[0;37m%s%d\x1B[0m\n", prefix, lineNum)
   163  				}
   164  				continue
   165  			} else if context.Comment == cmd {
   166  				if verboseBatch {
   167  					fmt.Fprintf(stdOut, "\x1B[0;37m%s%d \x1B[32m%s\x1B[0m\n", prefix, lineNum, line)
   168  				}
   169  				continue
   170  			}
   171  			if verboseBatch {
   172  				fmt.Fprintf(stdOut, "\x1B[0;37m%s%d \x1B[34;1m%s \x1B[0m%s\n", prefix, lineNum, cmd, args)
   173  			}
   174  
   175  			Broker(line)
   176  
   177  			if letBatchKnowErr != nil {
   178  				// if there is error during execution, then print line for error trace
   179  				fmt.Fprintf(stdOut, "\x1B[0;37m%s:%d \x1B[34;1m%s \x1B[0m%s\n\n", batchFilePath, lineNum, cmd, args)
   180  				letBatchKnowErr = nil
   181  			}
   182  		}
   183  
   184  		if c.level != 1 && verboseBatch {
   185  			fmt.Fprintf(stdOut, ">>>>>>> %s\n", batchFilePath)
   186  		}
   187  
   188  		c.level--
   189  
   190  		// print final result and reset params
   191  		if c.level == 0 {
   192  			if batchErrorCount == 0 {
   193  				fmt.Fprintf(stdOut, "\x1B[32;1mBatch is successfully finished\x1B[0m\n")
   194  			} else {
   195  				fmt.Fprintf(stdOut, "\x1B[31;1mBatch is failed: Error %d\x1B[0m\n", batchErrorCount)
   196  			}
   197  			// reset params
   198  			batchErrorCount = 0
   199  			zerolog.SetGlobalLevel(zerolog.DebugLevel)
   200  		}
   201  
   202  		// add file to watch list
   203  		if enableWatch && !strings.HasPrefix(batchFilePath, "http") {
   204  			absPath, _ := filepath.Abs(batchFilePath)
   205  			watcher.Add(absPath)
   206  		}
   207  
   208  		if c.level == 0 && enableWatch {
   209  			// wait and check file changes
   210  		fileWatching:
   211  			for {
   212  				select {
   213  				case <-watcher.Events:
   214  					break fileWatching
   215  				case err, _ := <-watcher.Errors:
   216  					if err != nil {
   217  						fmt.Fprintf(stdOut, "\x1B[0;37mWatching File %s Error: %s\x1B[0m\n", batchFilePath, err.Error())
   218  					}
   219  					break fileWatching
   220  				}
   221  			}
   222  			watcher.Close()
   223  			continue
   224  		}
   225  		break
   226  	}
   227  
   228  	return "batch exec is finished", nil
   229  }