refactor cli.writers (#51)

This commit is contained in:
Neil O'Toole 2020-08-07 21:06:56 -06:00 committed by GitHub
parent 062e2dea88
commit 26abe3eb6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 98 additions and 110 deletions

View File

@ -271,11 +271,14 @@ type RunContext struct {
// Log is the run's logger.
Log lg.Log
wrtr *writers
reg *driver.Registry
files *source.Files
dbases *driver.Databases
clnup *cleanup.Cleanup
// writers holds the various writer types that
// the CLI uses to print output.
writers *writers
registry *driver.Registry
files *source.Files
databases *driver.Databases
clnup *cleanup.Cleanup
}
// newDefaultRunContext returns a RunContext configured
@ -331,6 +334,9 @@ func (rc *RunContext) preRunE() error {
rc.clnup = cleanup.New()
log, cfg := rc.Log, rc.Config
// If the --output=/some/file flag is set, then we need to
// override rc.Out (which is typically stdout) to point it at
// the output destination file.
if cmdFlagChanged(rc.Cmd, flagOutput) {
fpath, _ := rc.Cmd.Flags().GetString(flagOutput)
fpath, err := filepath.Abs(fpath)
@ -347,9 +353,7 @@ func (rc *RunContext) preRunE() error {
rc.Out = f
}
rc.wrtr = newWriters(rc.Log, rc.Cmd, rc.Config.Options, rc.Out, rc.ErrOut)
rc.Out = rc.wrtr.out
rc.ErrOut = rc.wrtr.errOut
rc.writers, rc.Out, rc.ErrOut = newWriters(rc.Log, rc.Cmd, rc.Config.Options, rc.Out, rc.ErrOut)
var scratchSrcFunc driver.ScratchSrcFunc
@ -363,9 +367,9 @@ func (rc *RunContext) preRunE() error {
}
}
rc.reg = driver.NewRegistry(log)
rc.dbases = driver.NewDatabases(log, rc.reg, scratchSrcFunc)
rc.clnup.AddC(rc.dbases)
rc.registry = driver.NewRegistry(log)
rc.databases = driver.NewDatabases(log, rc.registry, scratchSrcFunc)
rc.clnup.AddC(rc.databases)
var err error
rc.files, err = source.NewFiles(log)
@ -374,16 +378,16 @@ func (rc *RunContext) preRunE() error {
}
rc.files.AddTypeDetectors(source.DetectMagicNumber)
rc.reg.AddProvider(sqlite3.Type, &sqlite3.Provider{Log: log})
rc.reg.AddProvider(postgres.Type, &postgres.Provider{Log: log})
rc.reg.AddProvider(sqlserver.Type, &sqlserver.Provider{Log: log})
rc.reg.AddProvider(mysql.Type, &mysql.Provider{Log: log})
csvp := &csv.Provider{Log: log, Scratcher: rc.dbases, Files: rc.files}
rc.reg.AddProvider(csv.TypeCSV, csvp)
rc.reg.AddProvider(csv.TypeTSV, csvp)
rc.registry.AddProvider(sqlite3.Type, &sqlite3.Provider{Log: log})
rc.registry.AddProvider(postgres.Type, &postgres.Provider{Log: log})
rc.registry.AddProvider(sqlserver.Type, &sqlserver.Provider{Log: log})
rc.registry.AddProvider(mysql.Type, &mysql.Provider{Log: log})
csvp := &csv.Provider{Log: log, Scratcher: rc.databases, Files: rc.files}
rc.registry.AddProvider(csv.TypeCSV, csvp)
rc.registry.AddProvider(csv.TypeTSV, csvp)
rc.files.AddTypeDetectors(csv.DetectCSV, csv.DetectTSV)
rc.reg.AddProvider(xlsx.Type, &xlsx.Provider{Log: log, Scratcher: rc.dbases, Files: rc.files})
rc.registry.AddProvider(xlsx.Type, &xlsx.Provider{Log: log, Scratcher: rc.databases, Files: rc.files})
rc.files.AddTypeDetectors(xlsx.DetectXLSX)
// One day we may have more supported user driver genres.
userDriverImporters := map[string]userdriver.ImportFunc{
@ -413,11 +417,11 @@ func (rc *RunContext) preRunE() error {
Log: log,
DriverDef: userDriverDef,
ImportFn: importFn,
Scratcher: rc.dbases,
Scratcher: rc.databases,
Files: rc.files,
}
rc.reg.AddProvider(source.Type(userDriverDef.Name), udp)
rc.registry.AddProvider(source.Type(userDriverDef.Name), udp)
rc.files.AddTypeDetectors(udp.TypeDetectors()...)
}
@ -441,26 +445,10 @@ func (rc *RunContext) Close() error {
return err
}
// writers returns this run context's writers instance.
func (rc *RunContext) writers() *writers {
return rc.wrtr
}
// registry returns rc's Registry instance.
func (rc *RunContext) registry() *driver.Registry {
return rc.reg
}
// registry returns rc's Databases instance.
func (rc *RunContext) databases() *driver.Databases {
return rc.dbases
}
// writers is a container for the various output writer types.
// writers is a container for the various output writers.
type writers struct {
out io.Writer
errOut io.Writer
fmt *output.Formatting
fmt *output.Formatting
recordw output.RecordWriter
metaw output.MetadataWriter
srcw output.SourceWriter
@ -469,8 +457,12 @@ type writers struct {
pingw output.PingWriter
}
func newWriters(log lg.Log, cmd *cobra.Command, opts config.Options, out, errOut io.Writer) *writers {
fm, out, errOut := getWriterFormatting(cmd, out, errOut)
// newWriters returns a writers instance configured per opts and/or
// flags from cmd. The returned out2/errOut2 values may differ
// from the out/errOut args (e.g. decorated to support colorization).
func newWriters(log lg.Log, cmd *cobra.Command, opts config.Options, out, errOut io.Writer) (w *writers, out2, errOut2 io.Writer) {
var fm *output.Formatting
fm, out2, errOut2 = getWriterFormatting(cmd, out, errOut)
// we need to determine --header here because the writer/format
// constructor functions, e.g. table.NewRecordWriter, require it.
@ -494,16 +486,14 @@ func newWriters(log lg.Log, cmd *cobra.Command, opts config.Options, out, errOut
// so we use its writers as the baseline. Later we check the format
// flags and set the various writer fields depending upon which
// writers the format implements.
w := &writers{
out: out,
errOut: errOut,
w = &writers{
fmt: fm,
recordw: tablew.NewRecordWriter(out, fm, hasHeader),
metaw: tablew.NewMetadataWriter(out, fm),
srcw: tablew.NewSourceWriter(out, fm, hasHeader, verbose),
pingw: tablew.NewPingWriter(out, fm),
notifyw: tablew.NewNotifyWriter(out, fm, hasHeader),
errw: tablew.NewErrorWriter(errOut, fm),
recordw: tablew.NewRecordWriter(out2, fm, hasHeader),
metaw: tablew.NewMetadataWriter(out2, fm),
srcw: tablew.NewSourceWriter(out2, fm, hasHeader, verbose),
pingw: tablew.NewPingWriter(out2, fm),
notifyw: tablew.NewNotifyWriter(out2, fm, hasHeader),
errw: tablew.NewErrorWriter(errOut2, fm),
}
// Invoke getFormat to see if the format was specified
@ -513,44 +503,44 @@ func newWriters(log lg.Log, cmd *cobra.Command, opts config.Options, out, errOut
switch format {
default:
// No format specified, use JSON
w.recordw = jsonw.NewStdRecordWriter(out, fm)
w.metaw = jsonw.NewMetadataWriter(out, fm)
w.errw = jsonw.NewErrorWriter(log, errOut, fm)
w.recordw = jsonw.NewStdRecordWriter(out2, fm)
w.metaw = jsonw.NewMetadataWriter(out2, fm)
w.errw = jsonw.NewErrorWriter(log, errOut2, fm)
case config.FormatTable:
// Table is the base format, already set above, no need to do anything.
case config.FormatTSV:
w.recordw = csvw.NewRecordWriter(out, hasHeader, csvw.Tab)
w.pingw = csvw.NewPingWriter(out, csvw.Tab)
w.recordw = csvw.NewRecordWriter(out2, hasHeader, csvw.Tab)
w.pingw = csvw.NewPingWriter(out2, csvw.Tab)
case config.FormatCSV:
w.recordw = csvw.NewRecordWriter(out, hasHeader, csvw.Comma)
w.pingw = csvw.NewPingWriter(out, csvw.Comma)
w.recordw = csvw.NewRecordWriter(out2, hasHeader, csvw.Comma)
w.pingw = csvw.NewPingWriter(out2, csvw.Comma)
case config.FormatXML:
w.recordw = xmlw.NewRecordWriter(out, fm)
w.recordw = xmlw.NewRecordWriter(out2, fm)
case config.FormatXLSX:
w.recordw = xlsxw.NewRecordWriter(out, hasHeader)
w.recordw = xlsxw.NewRecordWriter(out2, hasHeader)
case config.FormatRaw:
w.recordw = raww.NewRecordWriter(out)
w.recordw = raww.NewRecordWriter(out2)
case config.FormatHTML:
w.recordw = htmlw.NewRecordWriter(out)
w.recordw = htmlw.NewRecordWriter(out2)
case config.FormatMarkdown:
w.recordw = markdownw.NewRecordWriter(out)
w.recordw = markdownw.NewRecordWriter(out2)
case config.FormatJSONA:
w.recordw = jsonw.NewArrayRecordWriter(out, fm)
w.recordw = jsonw.NewArrayRecordWriter(out2, fm)
case config.FormatJSONL:
w.recordw = jsonw.NewObjectRecordWriter(out, fm)
w.recordw = jsonw.NewObjectRecordWriter(out2, fm)
}
return w
return w, out2, errOut2
}
// getWriterFormatting returns a Formatting instance and
@ -749,7 +739,7 @@ func printError(rc *RunContext, err error) {
}
log.Errorf("%s [%T] %+v", cmdName, err, err)
wrtrs := rc.writers()
wrtrs := rc.writers
if wrtrs != nil && wrtrs.errw != nil {
// If we have an errorWriter, we print to it
// and return.

View File

@ -95,7 +95,7 @@ func execSrcAdd(rc *RunContext, cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed(flagDriver) {
val, _ := cmd.Flags().GetString(flagDriver)
typ = source.Type(strings.TrimSpace(val))
if !rc.reg.HasProviderFor(typ) {
if !rc.registry.HasProviderFor(typ) {
return errz.Errorf("unsupported source driver type %q", val)
}
} else {
@ -154,7 +154,7 @@ func execSrcAdd(rc *RunContext, cmd *cobra.Command, args []string) error {
}
}
src, err := newSource(rc.Log, rc.registry(), typ, handle, loc, opts)
src, err := newSource(rc.Log, rc.registry, typ, handle, loc, opts)
if err != nil {
return err
}
@ -172,7 +172,7 @@ func execSrcAdd(rc *RunContext, cmd *cobra.Command, args []string) error {
}
}
drvr, err := rc.registry().DriverFor(src.Type)
drvr, err := rc.registry.DriverFor(src.Type)
if err != nil {
return err
}
@ -188,5 +188,5 @@ func execSrcAdd(rc *RunContext, cmd *cobra.Command, args []string) error {
return err
}
return rc.writers().srcw.Source(src)
return rc.writers.srcw.Source(src)
}

View File

@ -26,6 +26,6 @@ func execDrivers(rc *RunContext, cmd *cobra.Command, args []string) error {
return errz.Errorf("invalid arguments: zero arguments expected")
}
drvrs := rc.registry().DriversMetadata()
return rc.writers().metaw.DriverMetadata(drvrs)
drvrs := rc.registry.DriversMetadata()
return rc.writers.metaw.DriverMetadata(drvrs)
}

View File

@ -106,7 +106,7 @@ func execInspect(rc *RunContext, cmd *cobra.Command, args []string) error {
}
}
dbase, err := rc.databases().Open(rc.Context, src)
dbase, err := rc.databases.Open(rc.Context, src)
if err != nil {
return errz.Wrapf(err, "failed to inspect %s", src.Handle)
}
@ -119,7 +119,7 @@ func execInspect(rc *RunContext, cmd *cobra.Command, args []string) error {
return err
}
return rc.writers().metaw.TableMetadata(tblMeta)
return rc.writers.metaw.TableMetadata(tblMeta)
}
meta, err := dbase.SourceMetadata(rc.Context)
@ -133,5 +133,5 @@ func execInspect(rc *RunContext, cmd *cobra.Command, args []string) error {
meta.DBVars = nil
}
return rc.writers().metaw.SourceMetadata(meta)
return rc.writers.metaw.SourceMetadata(meta)
}

View File

@ -23,5 +23,5 @@ func execSrcList(rc *RunContext, cmd *cobra.Command, args []string) error {
return errz.Errorf(msgInvalidArgs)
}
return rc.writers().srcw.SourceSet(rc.Config.Sources)
return rc.writers.srcw.SourceSet(rc.Config.Sources)
}

View File

@ -38,7 +38,7 @@ func newNotifyListCmd() (*cobra.Command, runFunc) {
}
func execNotifyList(rc *RunContext, cmd *cobra.Command, args []string) error {
return rc.writers().notifyw.NotifyDestinations(rc.Config.Notification.Destinations)
return rc.writers.notifyw.NotifyDestinations(rc.Config.Notification.Destinations)
}
func newNotifyRemoveCmd() (*cobra.Command, runFunc) {
@ -165,7 +165,7 @@ func execNotifyAddSlack(rc *RunContext, cmd *cobra.Command, args []string) error
return err
}
return rc.writers().notifyw.NotifyDestinations([]notify.Destination{*dest})
return rc.writers.notifyw.NotifyDestinations([]notify.Destination{*dest})
}
func newNotifyAddHipChatCmd() (*cobra.Command, runFunc) {

View File

@ -100,7 +100,7 @@ func execPing(rc *RunContext, cmd *cobra.Command, args []string) error {
rc.Log.Debugf("Using timeout value: %s", timeout)
return pingSources(rc.Context, rc.Log, rc.registry(), srcs, rc.writers().pingw, timeout)
return pingSources(rc.Context, rc.Log, rc.registry, srcs, rc.writers.pingw, timeout)
}
// pingSources pings each of the sources in srcs, and prints results

View File

@ -41,7 +41,7 @@ func execSrcRemove(rc *RunContext, cmd *cobra.Command, args []string) error {
}
fmt.Fprintf(rc.Out, "Removed data source ")
_, _ = rc.wrtr.fmt.Hilite.Fprintf(rc.Out, "%s", src.Handle)
_, _ = rc.writers.fmt.Hilite.Fprintf(rc.Out, "%s", src.Handle)
fmt.Fprintln(rc.Out)
return nil

View File

@ -57,7 +57,7 @@ func execScratch(rc *RunContext, cmd *cobra.Command, args []string) error {
src = defaultScratch
}
return rc.writers().srcw.Source(src)
return rc.writers.srcw.Source(src)
}
// Set the scratch src
@ -79,5 +79,5 @@ func execScratch(rc *RunContext, cmd *cobra.Command, args []string) error {
return err
}
return rc.writers().srcw.Source(src)
return rc.writers.srcw.Source(src)
}

View File

@ -93,7 +93,7 @@ func execSLQ(rc *RunContext, cmd *cobra.Command, args []string) error {
// execSQLInsert executes the SQL and inserts resulting records
// into destTbl in destSrc.
func execSLQInsert(rc *RunContext, destSrc *source.Source, destTbl string) error {
args, srcs, dbases := rc.Args, rc.Config.Sources, rc.databases()
args, srcs, dbases := rc.Args, rc.Config.Sources, rc.databases
slq, err := preprocessUserSLQ(rc, args)
if err != nil {
return err
@ -113,7 +113,7 @@ func execSLQInsert(rc *RunContext, destSrc *source.Source, destTbl string) error
// stack.
inserter := libsq.NewDBWriter(rc.Log, destDB, destTbl, libsq.DefaultRecordChSize)
err = libsq.ExecuteSLQ(ctx, rc.Log, rc.dbases, rc.dbases, srcs, slq, inserter)
err = libsq.ExecuteSLQ(ctx, rc.Log, rc.databases, rc.databases, srcs, slq, inserter)
if err != nil {
return errz.Wrapf(err, "insert %s.%s failed", destSrc.Handle, destTbl)
}
@ -133,8 +133,8 @@ func execSLQPrint(rc *RunContext) error {
return err
}
recw := output.NewRecordWriterAdapter(rc.writers().recordw)
err = libsq.ExecuteSLQ(rc.Context, rc.Log, rc.dbases, rc.dbases, rc.Config.Sources, slq, recw)
recw := output.NewRecordWriterAdapter(rc.writers.recordw)
err = libsq.ExecuteSLQ(rc.Context, rc.Log, rc.databases, rc.databases, rc.Config.Sources, slq, recw)
if err != nil {
return err
}
@ -172,7 +172,7 @@ func execSLQPrint(rc *RunContext) error {
//
// $ sq '.person' --> sq '@active.person'
func preprocessUserSLQ(rc *RunContext, args []string) (string, error) {
log, reg, dbases, srcs := rc.Log, rc.registry(), rc.databases(), rc.Config.Sources
log, reg, dbases, srcs := rc.Log, rc.registry, rc.databases, rc.Config.Sources
activeSrc := srcs.Active()
if len(args) == 0 {

View File

@ -103,12 +103,12 @@ func execSQL(rc *RunContext, cmd *cobra.Command, args []string) error {
// to the configured writer.
func execSQLPrint(rc *RunContext, fromSrc *source.Source) error {
args := rc.Args
dbase, err := rc.databases().Open(rc.Context, fromSrc)
dbase, err := rc.databases.Open(rc.Context, fromSrc)
if err != nil {
return err
}
recw := output.NewRecordWriterAdapter(rc.writers().recordw)
recw := output.NewRecordWriterAdapter(rc.writers.recordw)
err = libsq.QuerySQL(rc.Context, rc.Log, dbase, recw, args[0])
if err != nil {
return err
@ -121,7 +121,7 @@ func execSQLPrint(rc *RunContext, fromSrc *source.Source) error {
// into destTbl in destSrc.
func execSQLInsert(rc *RunContext, fromSrc, destSrc *source.Source, destTbl string) error {
args := rc.Args
dbases := rc.databases()
dbases := rc.databases
ctx, cancelFn := context.WithCancel(rc.Context)
defer cancelFn()

View File

@ -132,7 +132,6 @@ func TestCmdSQL_StdinQuery(t *testing.T) {
require.NoError(t, err)
ru := newRun(t).hush()
//ru := newRun(t)
ru.rc.Stdin = f
err = ru.exec("sql", "--no-header", "SELECT * FROM "+tc.tbl)

View File

@ -42,7 +42,7 @@ func execSrc(rc *RunContext, cmd *cobra.Command, args []string) error {
return nil
}
return rc.writers().srcw.Source(src)
return rc.writers.srcw.Source(src)
}
src, err := cfg.Sources.SetActive(args[0])
@ -55,5 +55,5 @@ func execSrc(rc *RunContext, cmd *cobra.Command, args []string) error {
return err
}
return rc.writers().srcw.Source(src)
return rc.writers.srcw.Source(src)
}

View File

@ -61,7 +61,7 @@ func execTblCopy(rc *RunContext, cmd *cobra.Command, args []string) error {
return errz.New("one or two table args required")
}
tblHandles, err := parseTableHandleArgs(rc.reg, rc.Config.Sources, args)
tblHandles, err := parseTableHandleArgs(rc.registry, rc.Config.Sources, args)
if err != nil {
return err
}
@ -108,7 +108,7 @@ func execTblCopy(rc *RunContext, cmd *cobra.Command, args []string) error {
}
var dbase driver.Database
dbase, err = rc.databases().Open(rc.Context, tblHandles[0].src)
dbase, err = rc.databases.Open(rc.Context, tblHandles[0].src)
if err != nil {
return err
}
@ -160,7 +160,7 @@ func newTblTruncateCmd() (*cobra.Command, runFunc) {
func execTblTruncate(rc *RunContext, cmd *cobra.Command, args []string) (err error) {
var tblHandles []tblHandle
tblHandles, err = parseTableHandleArgs(rc.reg, rc.Config.Sources, args)
tblHandles, err = parseTableHandleArgs(rc.registry, rc.Config.Sources, args)
if err != nil {
return err
}
@ -200,7 +200,7 @@ func newTblDropCmd() (*cobra.Command, runFunc) {
func execTblDrop(rc *RunContext, cmd *cobra.Command, args []string) (err error) {
var tblHandles []tblHandle
tblHandles, err = parseTableHandleArgs(rc.reg, rc.Config.Sources, args)
tblHandles, err = parseTableHandleArgs(rc.registry, rc.Config.Sources, args)
if err != nil {
return err
}
@ -212,7 +212,7 @@ func execTblDrop(rc *RunContext, cmd *cobra.Command, args []string) (err error)
}
var dbase driver.Database
dbase, err = rc.databases().Open(rc.Context, tblH.src)
dbase, err = rc.databases.Open(rc.Context, tblH.src)
if err != nil {
return err
}

View File

@ -26,16 +26,16 @@ func execVersion(rc *RunContext, cmd *cobra.Command, args []string) error {
version = "0.0.0-dev"
}
rc.wrtr.fmt.Hilite.Fprintf(rc.Out, "sq %s", version)
rc.writers.fmt.Hilite.Fprintf(rc.Out, "sq %s", version)
if len(buildinfo.Commit) > 0 {
fmt.Fprint(rc.Out, " ")
rc.wrtr.fmt.Faint.Fprint(rc.Out, "#"+buildinfo.Commit)
rc.writers.fmt.Faint.Fprint(rc.Out, "#"+buildinfo.Commit)
}
if len(buildinfo.Timestamp) > 0 {
fmt.Fprint(rc.Out, " ")
rc.wrtr.fmt.Faint.Fprint(rc.Out, buildinfo.Timestamp)
rc.writers.fmt.Faint.Fprint(rc.Out, buildinfo.Timestamp)
}
fmt.Fprintln(rc.Out)

View File

@ -84,7 +84,7 @@ func (f *Format) UnmarshalText(text []byte) error {
switch Format(text) {
default:
return errz.Errorf("unknown output format %q", string(text))
case FormatJSON, FormatJSONA, FormatJSONL, FormatTable, FormatGrid, FormatRaw,
case FormatJSON, FormatJSONA, FormatJSONL, FormatTable, FormatRaw,
FormatHTML, FormatMarkdown, FormatXLSX, FormatXML, FormatCSV, FormatTSV:
}
@ -92,13 +92,12 @@ func (f *Format) UnmarshalText(text []byte) error {
return nil
}
// Constants
// Output format values.
const (
FormatJSON Format = "json"
FormatJSONL Format = "jsonl"
FormatJSONA Format = "jsona"
FormatTable Format = "table" // FIXME: rename to FormatText
FormatGrid Format = "grid"
FormatTable Format = "table"
FormatRaw Format = "raw"
FormatHTML Format = "html"
FormatMarkdown Format = "markdown"

View File

@ -37,7 +37,7 @@ const (
flagJSONShort = "j"
flagJSONA = "jsona"
flagJSONAShort = "A"
flagJSONAUsage = "JSON: output each record's values as JSON array on its own line"
flagJSONAUsage = "JSON: output each record's values as a JSON array on its own line"
flagJSONL = "jsonl"
flagJSONLShort = "l"
flagJSONLUsage = "JSON: output each record as a JSON object on its own line"

View File

@ -97,7 +97,6 @@ func activeSrcFromFlagsOrConfig(cmd *cobra.Command, srcs *source.Set) (*source.S
// and returned.
func checkStdinSource(rc *RunContext) (*source.Source, error) {
cmd := rc.Cmd
reg := rc.registry()
f := rc.Stdin
info, err := f.Stat()
@ -130,7 +129,7 @@ func checkStdinSource(rc *RunContext) (*source.Source, error) {
if cmd.Flags().Changed(flagDriver) {
val, _ := cmd.Flags().GetString(flagDriver)
typ = source.Type(val)
if !reg.HasProviderFor(typ) {
if !rc.registry.HasProviderFor(typ) {
return nil, errz.Errorf("unknown driver type: %s", typ)
}
}
@ -150,7 +149,7 @@ func checkStdinSource(rc *RunContext) (*source.Source, error) {
}
}
return newSource(rc.Log, reg, typ, source.StdinHandle, source.StdinHandle, opts)
return newSource(rc.Log, rc.registry, typ, source.StdinHandle, source.StdinHandle, opts)
}
// newSource creates a new Source instance where the

View File

@ -23,6 +23,7 @@ func main() {
err := cli.Execute(ctx, os.Stdin, os.Stdout, os.Stderr, os.Args)
if err != nil {
cancelFn()
os.Exit(1)
}
}