mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-25 08:53:55 +00:00
147 lines
4.5 KiB
Go
147 lines
4.5 KiB
Go
// ================================================================
|
|
// All the usual contents of main() are put into this package for ease of
|
|
// testing.
|
|
// ================================================================
|
|
|
|
package entrypoint
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
|
|
"miller/src/platform"
|
|
|
|
"miller/src/auxents"
|
|
"miller/src/cli"
|
|
"miller/src/cliutil"
|
|
"miller/src/lib"
|
|
"miller/src/stream"
|
|
"miller/src/transforming"
|
|
)
|
|
|
|
// ----------------------------------------------------------------
|
|
func Main() {
|
|
// Special handling for Windows so we can do things like:
|
|
//
|
|
// mlr put '$a = $b . "cd \"efg\" hi"' foo.dat
|
|
//
|
|
// as on Linux/Unix/MacOS. (On the latter platforms, this is just os.Args
|
|
// as-is.)
|
|
os.Args = platform.GetArgs()
|
|
|
|
// Expand "-xyz" into "-x -y -z" while leaving "--xyz" intact. This is a
|
|
// keystroke-saver for the user.
|
|
//
|
|
// This is OK to do globally here since Miller is quite consistent (in
|
|
// main, verbs, and auxents) that multi-character options start with two
|
|
// dashes, e.g. "--csv". (The sole exception is the sort verb's -nf/-nr
|
|
// which are handled specially there.)
|
|
os.Args = lib.Getoptify(os.Args)
|
|
|
|
// 'mlr repl' or 'mlr lecat' or any other non-miller-per-se toolery which
|
|
// is delivered (for convenience) within the mlr executable. If argv[1] is
|
|
// found then this function will not return.
|
|
auxents.Dispatch(os.Args)
|
|
|
|
options, recordTransformers, err := cli.ParseCommandLine(os.Args)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, os.Args[0], ": ", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if !options.DoInPlace {
|
|
processToStdout(options, recordTransformers)
|
|
} else {
|
|
processInPlace(options)
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// processToStdout is normal processing without mlr -I.
|
|
|
|
func processToStdout(
|
|
options cliutil.TOptions,
|
|
recordTransformers []transforming.IRecordTransformer,
|
|
) {
|
|
err := stream.Stream(options.FileNames, options, recordTransformers, os.Stdout)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, os.Args[0], ": ", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// processInPlace is in-place processing without mlr -I.
|
|
//
|
|
// For in-place mode, reconstruct the transformers on each input file. E.g.
|
|
// 'mlr -I head -n 2 foo bar' should do head -n 2 on foo as well as on bar.
|
|
//
|
|
// I could have implemented this with a single construction of the transformers
|
|
// and having each transformers implement a Reset() method. However, having
|
|
// effectively two initalizers per transformers -- constructor and reset method
|
|
// -- I'd surely miss some logic somewhere. With in-place mode being a less
|
|
// frequently used code path, this would likely lead to latent bugs. So this
|
|
// approach leads to greater code stability.
|
|
|
|
func processInPlace(
|
|
originalOptions cliutil.TOptions,
|
|
) {
|
|
// This should have been already checked by the CLI parser when validating
|
|
// the -I flag.
|
|
lib.InternalCodingErrorIf(originalOptions.FileNames == nil)
|
|
lib.InternalCodingErrorIf(len(originalOptions.FileNames) == 0)
|
|
|
|
// Save off the file names from the command line.
|
|
fileNames := make([]string, len(originalOptions.FileNames))
|
|
for i, fileName := range originalOptions.FileNames {
|
|
fileNames[i] = fileName
|
|
}
|
|
|
|
for _, fileName := range fileNames {
|
|
|
|
if _, err := os.Stat(fileName); os.IsNotExist(err) {
|
|
fmt.Fprintf(os.Stderr, "%s: %v\n", lib.MlrExeName(), err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Reconstruct the transformers for each file name, and allocate
|
|
// reader, mappers, and writer individually for each file name. This
|
|
// way CSV headers appear in each file, head -n 10 puts 10 rows for
|
|
// each output file, and so on.
|
|
|
|
containingDirectory := path.Dir(fileName)
|
|
// Names like ./mlr-in-place-2148227797 and ./mlr-in-place-1792078347,
|
|
// as revealed by printing handle.Name().
|
|
handle, err := os.CreateTemp(containingDirectory, "mlr-in-place-")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%s: %v\n", lib.MlrExeName(), err)
|
|
os.Exit(1)
|
|
}
|
|
tempFileName := handle.Name()
|
|
|
|
options, recordTransformers, err := cli.ParseCommandLine(os.Args)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, os.Args[0], ": ", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
err = stream.Stream([]string{fileName}, options, recordTransformers, handle)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, os.Args[0], ": ", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
err = handle.Close()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%s: %v\n", lib.MlrExeName(), err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
err = os.Rename(tempFileName, fileName)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%s: %v\n", lib.MlrExeName(), err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
}
|