@@ -33,6 +33,18 @@ const (
33
33
cliModeSQLReadWrite
34
34
)
35
35
36
+ type outputFormat int
37
+
38
+ const (
39
+ outputFormatGoal outputFormat = iota
40
+ outputFormatCSV
41
+ outputFormatJSON
42
+ outputFormatJSONPretty
43
+ outputFormatLatex
44
+ outputFormatMarkdown
45
+ outputFormatTSV
46
+ )
47
+
36
48
const (
37
49
cliModeGoalPrompt = " "
38
50
cliModeGoalNextPrompt = " "
@@ -43,11 +55,12 @@ const (
43
55
)
44
56
45
57
type CliSystem struct {
58
+ ariContext * ari.Context
59
+ autoCompleter * AutoCompleter
46
60
cliEditor * bubbline.Editor
47
61
cliMode cliMode
48
- autoCompleter * AutoCompleter
49
- ariContext * ari.Context
50
62
debug bool
63
+ outputFormat outputFormat
51
64
programName string
52
65
}
53
66
@@ -138,7 +151,28 @@ func cliModeFromString(s string) (cliMode, error) {
138
151
case "sql!" :
139
152
return cliModeSQLReadWrite , nil
140
153
default :
141
- return 0 , errors .New ("unsupported ari mode: " + s )
154
+ return 0 , errors .New ("unsupported --mode: " + s )
155
+ }
156
+ }
157
+
158
+ func outputFormatFromString (s string ) (outputFormat , error ) {
159
+ switch s {
160
+ case "csv" :
161
+ return outputFormatCSV , nil
162
+ case "goal" :
163
+ return outputFormatGoal , nil
164
+ case "json" :
165
+ return outputFormatJSON , nil
166
+ case "json+pretty" :
167
+ return outputFormatJSONPretty , nil
168
+ case "latex" :
169
+ return outputFormatLatex , nil
170
+ case "markdown" :
171
+ return outputFormatMarkdown , nil
172
+ case "tsv" :
173
+ return outputFormatTSV , nil
174
+ default :
175
+ return 0 , errors .New ("unsupported --output-format: " + s )
142
176
}
143
177
}
144
178
@@ -191,16 +225,27 @@ func ariMain(cmd *cobra.Command, args []string) int {
191
225
defer pprof .StopCPUProfile ()
192
226
}
193
227
228
+ // Defaults to outputFormatGoal
229
+ startupOutputFormatString := viper .GetString ("output-format" )
230
+ startupOutputFormat , err := outputFormatFromString (startupOutputFormatString )
231
+ if err != nil {
232
+ fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
233
+ return 1
234
+ }
235
+ mainCliSystem .outputFormat = startupOutputFormat
236
+
194
237
// MUST PRECEDE EXECUTE/REPL
195
238
goalFilesToLoad := viper .GetStringSlice ("load" )
196
239
for _ , f := range goalFilesToLoad {
197
- err = runScript (& mainCliSystem , f )
240
+ _ , err = runScript (& mainCliSystem , f )
198
241
if err != nil {
199
242
fmt .Fprintf (os .Stderr , "Failed to load file %q with error: %v" , f , err )
200
243
return 1
201
244
}
202
245
}
203
246
247
+ // By default, we don't print the final return value of a script, but this flag supports that.
248
+ printFinalValue := viper .GetBool ("println" )
204
249
// Support file argument both with -e and standalone.
205
250
hasFileArgument := len (args ) > 0
206
251
@@ -211,13 +256,16 @@ func ariMain(cmd *cobra.Command, args []string) int {
211
256
return 1
212
257
}
213
258
if programToExecute != "" {
214
- err = runCommand (& mainCliSystem , programToExecute )
215
- if err != nil {
259
+ goalV , errr : = runCommand (& mainCliSystem , programToExecute )
260
+ if errr != nil {
216
261
fmt .Fprintf (os .Stderr , "Failed to execute program:\n %q\n with error:\n %v\n " , programToExecute , err )
217
262
return 1
218
263
}
219
264
// Support -e/--execute along with a file argument.
220
265
if ! hasFileArgument {
266
+ if printFinalValue {
267
+ printInOutputFormat (ariContext .GoalContext , mainCliSystem .outputFormat , goalV )
268
+ }
221
269
return 0
222
270
}
223
271
}
@@ -230,11 +278,14 @@ func ariMain(cmd *cobra.Command, args []string) int {
230
278
fmt .Fprintf (os .Stderr , "File %q is not recognized as a path on your system: %v" , f , err )
231
279
}
232
280
ariContext .GoalContext .AssignGlobal ("FILE" , goal .NewS (path ))
233
- err = runScript (& mainCliSystem , f )
234
- if err != nil {
281
+ goalV , errr : = runScript (& mainCliSystem , f )
282
+ if errr != nil {
235
283
fmt .Fprintf (os .Stderr , "Failed to run file %q with error: %v" , f , err )
236
284
return 1
237
285
}
286
+ if printFinalValue {
287
+ printInOutputFormat (ariContext .GoalContext , mainCliSystem .outputFormat , goalV )
288
+ }
238
289
return 0
239
290
}
240
291
@@ -332,18 +383,54 @@ func (cliSystem *CliSystem) replEvalGoal(line string) {
332
383
}
333
384
334
385
if ! goalContext .AssignedLast () {
335
- ariPrintFn := cliSystem .detectAriPrint ()
386
+ // In the REPL, make it easy to get the value of the _p_revious expression
387
+ // just evaluated. Equivalent of *1 in Lisp REPLs. Skip assignments.
388
+ printInOutputFormat (goalContext , cliSystem .outputFormat , value )
389
+ }
390
+
391
+ cliSystem .detectAriPrompt ()
392
+ }
393
+
394
+ func printInOutputFormat (goalContext * goal.Context , outputFormat outputFormat , value goal.V ) {
395
+ goalContext .AssignGlobal ("ari.p" , value )
396
+ switch outputFormat {
397
+ case outputFormatGoal :
398
+ ariPrintFn := detectAriPrint (goalContext )
336
399
if ariPrintFn != nil {
337
400
ariPrintFn (value )
338
401
} else {
339
402
fmt .Fprintln (os .Stdout , value .Sprint (goalContext , false ))
340
403
}
341
- // In the REPL, make it easy to get the value of the _p_revious expression
342
- // just evaluated. Equivalent of *1 in Lisp REPLs. Skip assignments.
343
- goalContext .AssignGlobal ("ari.p" , value )
404
+ case outputFormatCSV :
405
+ evalThen (goalContext , value , `csv ari.p` )
406
+ case outputFormatJSON :
407
+ evalThen (goalContext , value , `""json ari.p` )
408
+ case outputFormatJSONPretty :
409
+ evalThen (goalContext , value , `" "json ari.p` )
410
+ case outputFormatLatex :
411
+ evalThen (goalContext , value , `out.ltx[ari.p;"%.2f"]` )
412
+ case outputFormatMarkdown :
413
+ evalThen (goalContext , value , `out.md[ari.p;"%.2f"]` )
414
+ case outputFormatTSV :
415
+ evalThen (goalContext , value , `"\t"csv ari.p` )
344
416
}
417
+ }
345
418
346
- cliSystem .detectAriPrompt ()
419
+ // evalThen evaluates the given goalProgram for side effects, with ari.p already bound to previous evaluation.
420
+ func evalThen (goalContext * goal.Context , value goal.V , goalProgram string ) {
421
+ nextValue , err := goalContext .Eval (goalProgram )
422
+ if err != nil {
423
+ formatREPLError (err )
424
+ }
425
+ if value .IsError () {
426
+ formatREPLError (newExitError (goalContext , value .Error ()))
427
+ }
428
+ switch jsonS := nextValue .BV ().(type ) {
429
+ case goal.S :
430
+ fmt .Fprintln (os .Stdout , string (jsonS ))
431
+ default :
432
+ formatREPLError (errors .New ("developer error: json must produce a string" ))
433
+ }
347
434
}
348
435
349
436
// ExitError is returned by Cmd when the program returns a Goal error value.
@@ -411,8 +498,7 @@ func (cliSystem *CliSystem) detectAriPrompt() {
411
498
}
412
499
413
500
// detectAriPrint returns a function for printing values at the REPL in goal mode.
414
- func (cliSystem * CliSystem ) detectAriPrint () func (goal.V ) {
415
- goalContext := cliSystem .ariContext .GoalContext
501
+ func detectAriPrint (goalContext * goal.Context ) func (goal.V ) {
416
502
printFn , found := goalContext .GetGlobal ("ari.print" )
417
503
if found {
418
504
if printFn .IsCallable () {
@@ -459,9 +545,22 @@ func (cliSystem *CliSystem) replEvalSystemCommand(line string) error {
459
545
cmdAndArgs := strings .Split (line , " " )
460
546
systemCommand := cmdAndArgs [0 ]
461
547
switch systemCommand {
462
- // IDEA )help that doesn't require quoting
463
548
case ")goal" :
464
549
return cliSystem .switchMode (cliModeGoal , nil )
550
+ case ")output.goal" :
551
+ cliSystem .outputFormat = outputFormatGoal
552
+ case ")output.csv" :
553
+ cliSystem .outputFormat = outputFormatCSV
554
+ case ")output.json" :
555
+ cliSystem .outputFormat = outputFormatJSON
556
+ case ")output.json+pretty" :
557
+ cliSystem .outputFormat = outputFormatJSONPretty
558
+ case ")output.latex" :
559
+ cliSystem .outputFormat = outputFormatLatex
560
+ case ")output.markdown" :
561
+ cliSystem .outputFormat = outputFormatMarkdown
562
+ case ")output.tsv" :
563
+ cliSystem .outputFormat = outputFormatTSV
465
564
case ")sql" :
466
565
return cliSystem .switchMode (cliModeSQLReadOnly , cmdAndArgs [1 :])
467
566
case ")sql!" :
@@ -510,43 +609,43 @@ func debugPrintStack(ctx *goal.Context, programName string) {
510
609
}
511
610
512
611
// Adapted from Goal's implementation.
513
- func runCommand (cliSystem * CliSystem , cmd string ) error {
612
+ func runCommand (cliSystem * CliSystem , cmd string ) (goal. V , error ) {
514
613
return runSource (cliSystem , cmd , "" )
515
614
}
516
615
517
616
// Adapted from Goal's implementation.
518
- func runScript (cliSystem * CliSystem , fname string ) error {
617
+ func runScript (cliSystem * CliSystem , fname string ) (goal. V , error ) {
519
618
bs , err := os .ReadFile (fname )
520
619
if err != nil {
521
- return fmt .Errorf ("%s: %w" , cliSystem .programName , err )
620
+ return goal . NewGap (), fmt .Errorf ("%s: %w" , cliSystem .programName , err )
522
621
}
523
622
// We avoid redundant copy in bytes->string conversion.
524
623
source := unsafe .String (unsafe .SliceData (bs ), len (bs ))
525
624
return runSource (cliSystem , source , fname )
526
625
}
527
626
528
627
// Adapted from Goal's implementation.
529
- func runSource (cliSystem * CliSystem , source , loc string ) error {
628
+ func runSource (cliSystem * CliSystem , source , loc string ) (goal. V , error ) {
530
629
goalContext := cliSystem .ariContext .GoalContext
531
630
err := goalContext .Compile (source , loc , "" )
532
631
if err != nil {
533
632
if cliSystem .debug {
534
633
printProgram (goalContext , cliSystem .programName )
535
634
}
536
- return formatError (cliSystem .programName , err )
635
+ return goal . NewGap (), formatError (cliSystem .programName , err )
537
636
}
538
637
if cliSystem .debug {
539
638
printProgram (goalContext , cliSystem .programName )
540
- return nil
639
+ return goal . NewGap (), nil
541
640
}
542
641
r , err := goalContext .Run ()
543
642
if err != nil {
544
- return formatError (cliSystem .programName , err )
643
+ return r , formatError (cliSystem .programName , err )
545
644
}
546
645
if r .IsError () {
547
- return fmt .Errorf ("%s" , formatGoalError (goalContext , r ))
646
+ return r , fmt .Errorf ("%s" , formatGoalError (goalContext , r ))
548
647
}
549
- return nil
648
+ return r , nil
550
649
}
551
650
552
651
// printProgram prints debug information about the context and any compiled
@@ -655,21 +754,15 @@ working with SQL and HTTP APIs.`,
655
754
var cfgFile string
656
755
cobra .OnInitialize (initConfigFn (cfgFile ))
657
756
658
- // Here you will define your flags and configuration settings.
659
- // Cobra supports persistent flags, which, if defined here,
660
- // will be global for your application.
661
-
662
757
home , err := os .UserHomeDir ()
663
758
cobra .CheckErr (err )
664
759
cfgDir := path .Join (home , ".config" , "ari" )
665
-
666
760
defaultHistFile := path .Join (cfgDir , "ari-history.txt" )
667
761
defaultCfgFile := path .Join (cfgDir , "ari-config.yaml" )
668
762
669
763
// Config file has processing in initConfigFn outside of viper lifecycle, so it's a separate variable.
670
764
rootCmd .PersistentFlags ().StringVar (& cfgFile , "config" , defaultCfgFile , "ari configuration" )
671
765
672
- // Everything else should go through viper for consistency.
673
766
pFlags := rootCmd .PersistentFlags ()
674
767
675
768
flagNameHistory := "history"
@@ -692,6 +785,12 @@ working with SQL and HTTP APIs.`,
692
785
rootCmd .Flags ().StringP ("mode" , "m" , "goal" , "language mode at startup" )
693
786
err = viper .BindPFlag ("mode" , rootCmd .Flags ().Lookup ("mode" ))
694
787
cobra .CheckErr (err )
788
+ rootCmd .Flags ().StringP ("output-format" , "f" , "goal" , "evaluation output format" )
789
+ err = viper .BindPFlag ("output-format" , rootCmd .Flags ().Lookup ("output-format" ))
790
+ cobra .CheckErr (err )
791
+ rootCmd .Flags ().BoolP ("println" , "p" , false , "print final value of the script + newline" )
792
+ err = viper .BindPFlag ("println" , rootCmd .Flags ().Lookup ("println" ))
793
+ cobra .CheckErr (err )
695
794
rootCmd .Flags ().BoolP ("version" , "v" , false , "print version info and exit" )
696
795
697
796
// NB: MUST be last in this method.
0 commit comments