cmd/hcldec: opt-in JSON-formatted diagnostics
By default we generate human-readable diagnostics on the assumption that the caller is a simple program that is capturing stdin via a pipe and letting stderr go to the terminal. More sophisticated callers may wish to analyze the diagnostics themselves and perhaps present them in a different way, such as via a GUI.
This commit is contained in:
parent
609cc35d49
commit
6743a2254b
101
cmd/hcldec/diags_json.go
Normal file
101
cmd/hcldec/diags_json.go
Normal file
@ -0,0 +1,101 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
)
|
||||
|
||||
type jsonDiagWriter struct {
|
||||
w io.Writer
|
||||
diags hcl.Diagnostics
|
||||
}
|
||||
|
||||
var _ hcl.DiagnosticWriter = &jsonDiagWriter{}
|
||||
|
||||
func (wr *jsonDiagWriter) WriteDiagnostic(diag *hcl.Diagnostic) error {
|
||||
wr.diags = append(wr.diags, diag)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wr *jsonDiagWriter) WriteDiagnostics(diags hcl.Diagnostics) error {
|
||||
wr.diags = append(wr.diags, diags...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wr *jsonDiagWriter) Flush() error {
|
||||
if len(wr.diags) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
type PosJSON struct {
|
||||
Line int `json:"line"`
|
||||
Column int `json:"column"`
|
||||
Byte int `json:"byte"`
|
||||
}
|
||||
type RangeJSON struct {
|
||||
Filename string `json:"filename"`
|
||||
Start PosJSON `json:"start"`
|
||||
End PosJSON `json:"end"`
|
||||
}
|
||||
type DiagnosticJSON struct {
|
||||
Severity string `json:"severity"`
|
||||
Summary string `json:"summary"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
Subject *RangeJSON `json:"subject,omitempty"`
|
||||
}
|
||||
type DiagnosticsJSON struct {
|
||||
Diagnostics []DiagnosticJSON `json:"diagnostics"`
|
||||
}
|
||||
|
||||
diagsJSON := make([]DiagnosticJSON, 0, len(wr.diags))
|
||||
for _, diag := range wr.diags {
|
||||
var diagJSON DiagnosticJSON
|
||||
|
||||
switch diag.Severity {
|
||||
case hcl.DiagError:
|
||||
diagJSON.Severity = "error"
|
||||
case hcl.DiagWarning:
|
||||
diagJSON.Severity = "warning"
|
||||
default:
|
||||
diagJSON.Severity = "(unknown)" // should never happen
|
||||
}
|
||||
|
||||
diagJSON.Summary = diag.Summary
|
||||
diagJSON.Detail = diag.Detail
|
||||
if diag.Subject != nil {
|
||||
diagJSON.Subject = &RangeJSON{}
|
||||
sJSON := diagJSON.Subject
|
||||
rng := diag.Subject
|
||||
sJSON.Filename = rng.Filename
|
||||
sJSON.Start.Line = rng.Start.Line
|
||||
sJSON.Start.Column = rng.Start.Column
|
||||
sJSON.Start.Byte = rng.Start.Byte
|
||||
sJSON.End.Line = rng.End.Line
|
||||
sJSON.End.Column = rng.End.Column
|
||||
sJSON.End.Byte = rng.End.Byte
|
||||
}
|
||||
|
||||
diagsJSON = append(diagsJSON, diagJSON)
|
||||
}
|
||||
|
||||
src, err := json.MarshalIndent(DiagnosticsJSON{diagsJSON}, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = wr.w.Write(src)
|
||||
wr.w.Write([]byte{'\n'})
|
||||
return err
|
||||
}
|
||||
|
||||
type flusher interface {
|
||||
Flush() error
|
||||
}
|
||||
|
||||
func flush(maybeFlusher interface{}) error {
|
||||
if f, ok := maybeFlusher.(flusher); ok {
|
||||
return f.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
@ -26,6 +26,7 @@ var vars = &varSpecs{}
|
||||
var (
|
||||
specFile = flag.StringP("spec", "s", "", "path to spec file (required)")
|
||||
outputFile = flag.StringP("out", "o", "", "write to the given file, instead of stdout")
|
||||
diagsFormat = flag.StringP("diags", "", "", "format any returned diagnostics in the given format; currently only \"json\" is accepted")
|
||||
showVarRefs = flag.BoolP("var-refs", "", false, "rather than decoding input, produce a JSON description of the variables referenced by it")
|
||||
showVersion = flag.BoolP("version", "v", false, "show the version number and immediately exit")
|
||||
)
|
||||
@ -35,13 +36,6 @@ var diagWr hcl.DiagnosticWriter // initialized in init
|
||||
|
||||
func init() {
|
||||
flag.VarP(vars, "vars", "V", "provide variables to the given configuration file(s)")
|
||||
|
||||
color := terminal.IsTerminal(int(os.Stderr.Fd()))
|
||||
w, _, err := terminal.GetSize(int(os.Stdout.Fd()))
|
||||
if err != nil {
|
||||
w = 80
|
||||
}
|
||||
diagWr = hcl.NewDiagnosticTextWriter(os.Stderr, parser.Files(), uint(w), color)
|
||||
}
|
||||
|
||||
func main() {
|
||||
@ -55,6 +49,21 @@ func main() {
|
||||
|
||||
args := flag.Args()
|
||||
|
||||
switch *diagsFormat {
|
||||
case "":
|
||||
color := terminal.IsTerminal(int(os.Stderr.Fd()))
|
||||
w, _, err := terminal.GetSize(int(os.Stdout.Fd()))
|
||||
if err != nil {
|
||||
w = 80
|
||||
}
|
||||
diagWr = hcl.NewDiagnosticTextWriter(os.Stderr, parser.Files(), uint(w), color)
|
||||
case "json":
|
||||
diagWr = &jsonDiagWriter{w: os.Stderr}
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Invalid diagnostics format %q: only \"json\" is supported.\n", *diagsFormat)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
err := realmain(args)
|
||||
|
||||
if err != nil {
|
||||
@ -75,6 +84,7 @@ func realmain(args []string) error {
|
||||
diags = append(diags, specDiags...)
|
||||
if specDiags.HasErrors() {
|
||||
diagWr.WriteDiagnostics(diags)
|
||||
flush(diagWr)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
@ -146,6 +156,7 @@ func realmain(args []string) error {
|
||||
|
||||
if diags.HasErrors() {
|
||||
diagWr.WriteDiagnostics(diags)
|
||||
flush(diagWr)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
@ -170,6 +181,7 @@ func realmain(args []string) error {
|
||||
|
||||
if diags.HasErrors() {
|
||||
diagWr.WriteDiagnostics(diags)
|
||||
flush(diagWr)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user