-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from m-mizutani/feature/cmd
Add cli
- Loading branch information
Showing
6 changed files
with
296 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
|
||
"cloud.google.com/go/bigquery" | ||
"github.com/m-mizutani/bqs" | ||
"github.com/m-mizutani/goerr" | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
func inferCommand() *cli.Command { | ||
var ( | ||
output string | ||
) | ||
return &cli.Command{ | ||
Name: "infer", | ||
UsageText: "bqs infer [command options] [json files...]", | ||
Description: "Infer schema from JSON data and output as BigQuery schema file. If no file is specified, read from stdin.", | ||
Flags: []cli.Flag{ | ||
&cli.StringFlag{ | ||
Name: "output", | ||
Aliases: []string{"o"}, | ||
Usage: "Output schema file path", | ||
Value: "-", | ||
Destination: &output, | ||
}, | ||
}, | ||
Action: func(c *cli.Context) error { | ||
var w io.Writer | ||
if output == "-" { | ||
w = os.Stdout | ||
} else { | ||
file, err := os.Create(filepath.Clean(output)) | ||
if err != nil { | ||
return goerr.Wrap(err, "Failed to create schema file").With("path", output) | ||
} | ||
defer file.Close() | ||
w = file | ||
} | ||
|
||
type reader struct { | ||
r io.Reader | ||
name string | ||
} | ||
|
||
var readers []*reader | ||
if c.Args().Len() == 0 { | ||
readers = append(readers, &reader{ | ||
r: os.Stdin, | ||
name: "(stdin)", | ||
}) | ||
} else { | ||
for _, path := range c.Args().Slice() { | ||
file, err := os.Open(filepath.Clean(path)) | ||
if err != nil { | ||
return goerr.Wrap(err, "Failed to open file").With("path", path) | ||
} | ||
defer file.Close() | ||
readers = append(readers, &reader{ | ||
r: file, | ||
name: path, | ||
}) | ||
} | ||
} | ||
|
||
var schema bigquery.Schema | ||
for _, reader := range readers { | ||
logger.Debug("infer schema", "input", reader.name) | ||
|
||
decoder := json.NewDecoder(reader.r) | ||
for i := 0; ; i++ { | ||
var data any | ||
if err := decoder.Decode(&data); err != nil { | ||
if err == io.EOF { | ||
break | ||
} | ||
return goerr.Wrap(err, "Failed to decode JSON data").With("input", reader.name) | ||
} | ||
|
||
inferred, err := bqs.Infer(data) | ||
if err != nil { | ||
return goerr.Wrap(err, "Failed to infer schema").With("data", data).With("input", reader.name).With("line", i+1) | ||
} | ||
|
||
merged, err := bqs.Merge(schema, inferred) | ||
if err != nil { | ||
return goerr.Wrap(err, "Failed to merge schema").With("input", reader.name).With("line", i+1) | ||
} | ||
|
||
schema = merged | ||
} | ||
} | ||
|
||
raw, err := schema.ToJSONFields() | ||
if err != nil { | ||
return goerr.Wrap(err, "Failed to convert schema to JSON") | ||
} | ||
if _, err := w.Write(raw); err != nil { | ||
return goerr.Wrap(err, "Failed to write schema").With("output", output) | ||
} | ||
|
||
return nil | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"io" | ||
"log/slog" | ||
|
||
"github.com/fatih/color" | ||
"github.com/m-mizutani/clog" | ||
) | ||
|
||
var logger *slog.Logger = slog.Default() | ||
|
||
func configureLogger(level string, w io.Writer) error { | ||
levelMap := map[string]slog.Level{ | ||
"debug": slog.LevelDebug, | ||
"info": slog.LevelInfo, | ||
"warn": slog.LevelWarn, | ||
"error": slog.LevelError, | ||
} | ||
|
||
logLevel, ok := levelMap[level] | ||
if !ok { | ||
return errors.New("invalid log level") | ||
} | ||
|
||
handler := clog.New( | ||
clog.WithWriter(w), | ||
clog.WithLevel(logLevel), | ||
// clog.WithReplaceAttr(filter), | ||
clog.WithSource(true), | ||
// clog.WithTimeFmt("2006-01-02 15:04:05"), | ||
clog.WithColorMap(&clog.ColorMap{ | ||
Level: map[slog.Level]*color.Color{ | ||
slog.LevelDebug: color.New(color.FgGreen, color.Bold), | ||
slog.LevelInfo: color.New(color.FgCyan, color.Bold), | ||
slog.LevelWarn: color.New(color.FgYellow, color.Bold), | ||
slog.LevelError: color.New(color.FgRed, color.Bold), | ||
}, | ||
LevelDefault: color.New(color.FgBlue, color.Bold), | ||
Time: color.New(color.FgWhite), | ||
Message: color.New(color.FgHiWhite), | ||
AttrKey: color.New(color.FgHiCyan), | ||
AttrValue: color.New(color.FgHiWhite), | ||
}), | ||
) | ||
logger = slog.New(handler) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package main | ||
|
||
import ( | ||
"io" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/m-mizutani/goerr" | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
func main() { | ||
var ( | ||
logLevel string | ||
logOutput string | ||
) | ||
|
||
app := cli.App{ | ||
Name: "bqs", | ||
Flags: []cli.Flag{ | ||
&cli.StringFlag{ | ||
Name: "log-level", | ||
Category: "Log", | ||
Aliases: []string{"l"}, | ||
Usage: "Log level (debug, info, warn, error)", | ||
Value: "info", | ||
Destination: &logLevel, | ||
}, | ||
&cli.StringFlag{ | ||
Name: "log-output", | ||
Category: "Log", | ||
Aliases: []string{"L"}, | ||
Usage: "Log output destination, stdout('-') or file path", | ||
Value: "-", | ||
Destination: &logOutput, | ||
}, | ||
}, | ||
|
||
Before: func(c *cli.Context) error { | ||
var logWriter io.Writer | ||
switch logOutput { | ||
case "-", "stdout": | ||
logWriter = os.Stdout | ||
default: | ||
file, err := os.Create(filepath.Clean(logOutput)) | ||
if err != nil { | ||
return goerr.Wrap(err, "Failed to open log file") | ||
} | ||
logWriter = file | ||
} | ||
|
||
return configureLogger(logLevel, logWriter) | ||
}, | ||
|
||
Commands: []*cli.Command{ | ||
inferCommand(), | ||
}, | ||
} | ||
|
||
if err := app.Run(os.Args); err != nil { | ||
logger.Error("Failed", "error", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters