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 }