github.com/mithrandie/csvq@v1.18.1/lib/action/run.go (about)

     1  package action
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strings"
    10  	"time"
    11  	"unicode"
    12  
    13  	csvqfile "github.com/mithrandie/csvq/lib/file"
    14  	"github.com/mithrandie/csvq/lib/option"
    15  	"github.com/mithrandie/csvq/lib/parser"
    16  	"github.com/mithrandie/csvq/lib/query"
    17  	"github.com/mithrandie/csvq/lib/terminal"
    18  
    19  	"github.com/mithrandie/go-file/v2"
    20  )
    21  
    22  func Run(ctx context.Context, proc *query.Processor, input string, sourceFile string, outfile string) error {
    23  	start := time.Now()
    24  
    25  	defer func() {
    26  		showStats(ctx, proc, start)
    27  	}()
    28  
    29  	statements, _, err := parser.Parse(input, sourceFile, false, proc.Tx.Flags.AnsiQuotes)
    30  	if err != nil {
    31  		return query.NewSyntaxError(err.(*parser.SyntaxError))
    32  	}
    33  
    34  	if 0 < len(outfile) {
    35  		if abs, err := filepath.Abs(outfile); err == nil {
    36  			outfile = abs
    37  		}
    38  		if csvqfile.Exists(outfile) {
    39  			return query.NewFileAlreadyExistError(parser.Identifier{Literal: outfile})
    40  		}
    41  
    42  		fp, err := file.Create(outfile)
    43  		if err != nil {
    44  			return query.NewIOError(nil, err.Error())
    45  		}
    46  		defer func() {
    47  			if info, err := fp.Stat(); err == nil && info.Size() < 1 {
    48  				if err = os.Remove(outfile); err != nil {
    49  					proc.LogError(err.Error())
    50  				}
    51  			}
    52  			if err = fp.Close(); err != nil {
    53  				proc.LogError(err.Error())
    54  			}
    55  		}()
    56  		proc.Tx.Session.SetOutFile(fp)
    57  	}
    58  
    59  	proc.Tx.AutoCommit = true
    60  	_, err = proc.Execute(ctx, statements)
    61  	return err
    62  }
    63  
    64  func LaunchInteractiveShell(ctx context.Context, proc *query.Processor) error {
    65  	if proc.Tx.Session.CanReadStdin {
    66  		return query.NewIncorrectCommandUsageError("input from pipe or redirection cannot be used in interactive shell")
    67  	}
    68  
    69  	if err := proc.Tx.Session.SetStdin(terminal.GetStdinForREPL()); err != nil {
    70  		return query.NewIOError(nil, err.Error())
    71  	}
    72  
    73  	term, err := terminal.NewTerminal(ctx, proc.ReferenceScope)
    74  	if err != nil {
    75  		return query.ConvertLoadConfigurationError(err)
    76  	}
    77  	proc.Tx.Session.SetTerminal(term)
    78  	defer func() {
    79  		if e := proc.Tx.Session.Terminal().Teardown(); e != nil {
    80  			proc.LogError(e.Error())
    81  		}
    82  		proc.Tx.Session.SetTerminal(nil)
    83  	}()
    84  
    85  	StartUpMessage := "" +
    86  		"csvq interactive shell\n" +
    87  		"Press Ctrl+D or execute \"EXIT;\" to terminate this shell.\n\n"
    88  	proc.Log(StartUpMessage, false)
    89  
    90  	lines := make([]string, 0)
    91  
    92  	for {
    93  		if ctx.Err() != nil {
    94  			err = query.ConvertContextError(ctx.Err())
    95  			break
    96  		}
    97  
    98  		proc.Tx.Session.Terminal().UpdateCompleter()
    99  		line, e := proc.Tx.Session.Terminal().ReadLine()
   100  		if e != nil {
   101  			if e == io.EOF {
   102  				break
   103  			}
   104  			return query.NewIOError(nil, e.Error())
   105  		}
   106  
   107  		line = strings.TrimRightFunc(line, unicode.IsSpace)
   108  
   109  		if len(lines) < 1 && len(line) < 1 {
   110  			continue
   111  		}
   112  
   113  		if 0 < len(line) && line[len(line)-1] == '\\' {
   114  			lines = append(lines, line[:len(line)-1])
   115  			proc.Tx.Session.Terminal().SetContinuousPrompt(ctx)
   116  			continue
   117  		}
   118  
   119  		lines = append(lines, line)
   120  
   121  		saveLines := make([]string, 0, len(lines))
   122  		for _, l := range lines {
   123  			s := strings.TrimSpace(l)
   124  			if len(s) < 1 {
   125  				continue
   126  			}
   127  			saveLines = append(saveLines, s)
   128  		}
   129  
   130  		saveQuery := strings.Join(saveLines, " ")
   131  		if len(saveQuery) < 1 || saveQuery == ";" {
   132  			lines = lines[:0]
   133  			proc.Tx.Session.Terminal().SetPrompt(ctx)
   134  			continue
   135  		}
   136  		if e := proc.Tx.Session.Terminal().SaveHistory(saveQuery); e != nil {
   137  			proc.LogError(e.Error())
   138  		}
   139  
   140  		statements, _, e := parser.Parse(strings.Join(lines, "\n"), "", false, proc.Tx.Flags.AnsiQuotes)
   141  		if e != nil {
   142  			if e = query.NewSyntaxError(e.(*parser.SyntaxError)); e != nil {
   143  				proc.LogError(e.Error())
   144  			}
   145  			lines = lines[:0]
   146  			proc.Tx.Session.Terminal().SetPrompt(ctx)
   147  			continue
   148  		}
   149  
   150  		flow, e := proc.Execute(ctx, statements)
   151  		if e != nil {
   152  			if ex, ok := e.(*query.ForcedExit); ok {
   153  				err = ex
   154  				break
   155  			} else {
   156  				proc.LogError(e.Error())
   157  				lines = lines[:0]
   158  				proc.Tx.Session.Terminal().SetPrompt(ctx)
   159  				continue
   160  			}
   161  		}
   162  
   163  		if flow == query.Exit {
   164  			break
   165  		}
   166  
   167  		lines = lines[:0]
   168  		proc.Tx.Session.Terminal().SetPrompt(ctx)
   169  	}
   170  
   171  	return err
   172  }
   173  
   174  func showStats(ctx context.Context, proc *query.Processor, start time.Time) {
   175  	if ctx.Err() != nil {
   176  		return
   177  	}
   178  
   179  	if !proc.Tx.Flags.Stats {
   180  		return
   181  	}
   182  	var mem runtime.MemStats
   183  	runtime.ReadMemStats(&mem)
   184  
   185  	exectime := option.FormatNumber(time.Since(start).Seconds(), 6, ".", ",", "")
   186  	talloc := option.FormatNumber(float64(mem.TotalAlloc), 0, ".", ",", "")
   187  	sys := option.FormatNumber(float64(mem.HeapSys), 0, ".", ",", "")
   188  	mallocs := option.FormatNumber(float64(mem.Mallocs), 0, ".", ",", "")
   189  	frees := option.FormatNumber(float64(mem.Frees), 0, ".", ",", "")
   190  
   191  	width := len(exectime)
   192  	for _, v := range []string{talloc, sys, mallocs, frees} {
   193  		if width < len(v) {
   194  			width = len(v)
   195  		}
   196  	}
   197  	width = width + 1
   198  
   199  	w := proc.Tx.CreateDocumentWriter()
   200  	w.WriteColor(" TotalTime:", option.LableEffect)
   201  	w.WriteSpaces(width - len(exectime))
   202  	w.WriteWithoutLineBreak(exectime + " seconds")
   203  	w.NewLine()
   204  	w.WriteColor("TotalAlloc:", option.LableEffect)
   205  	w.WriteSpaces(width - len(talloc))
   206  	w.WriteWithoutLineBreak(talloc + " bytes")
   207  	w.NewLine()
   208  	w.WriteColor("   HeapSys:", option.LableEffect)
   209  	w.WriteSpaces(width - len(sys))
   210  	w.WriteWithoutLineBreak(sys + " bytes")
   211  	w.NewLine()
   212  	w.WriteColor("   Mallocs:", option.LableEffect)
   213  	w.WriteSpaces(width - len(mallocs))
   214  	w.WriteWithoutLineBreak(mallocs + " objects")
   215  	w.NewLine()
   216  	w.WriteColor("     Frees:", option.LableEffect)
   217  	w.WriteSpaces(width - len(frees))
   218  	w.WriteWithoutLineBreak(frees + " objects")
   219  	w.NewLine()
   220  	w.NewLine()
   221  
   222  	w.Title1 = "Resource Statistics"
   223  
   224  	proc.Log("\n"+w.String(), false)
   225  }