cmd/hcldec: --var-refs option
This option skips the usual decoding step and instead prints out a JSON- formatted list of the variables that are referenced by the configuration. In simple cases this is not required, but for more complex use-cases it can be useful to first analyze the input to see which variables need to be in the scope, then construct a suitable set of variables before finally decoding the input. For example, some of the variable values may be expensive to produce.
This commit is contained in:
parent
bb724af7fd
commit
609cc35d49
@ -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")
|
||||
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")
|
||||
)
|
||||
|
||||
@ -159,6 +160,11 @@ func realmain(args []string) error {
|
||||
body = hcl.MergeBodies(bodies)
|
||||
}
|
||||
|
||||
if *showVarRefs {
|
||||
vars := hcldec.Variables(body, spec)
|
||||
return showVarRefsJSON(vars, ctx)
|
||||
}
|
||||
|
||||
val, decDiags := hcldec.Decode(body, spec, ctx)
|
||||
diags = append(diags, decDiags...)
|
||||
|
||||
@ -197,6 +203,108 @@ func usage() {
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func showVarRefsJSON(vars []hcl.Traversal, ctx *hcl.EvalContext) error {
|
||||
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 StepJSON struct {
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Key json.RawMessage `json:"key,omitempty"`
|
||||
Range RangeJSON `json:"range"`
|
||||
}
|
||||
type TraversalJSON struct {
|
||||
RootName string `json:"root_name"`
|
||||
Value json.RawMessage `json:"value,omitempty"`
|
||||
Steps []StepJSON `json:"steps"`
|
||||
Range RangeJSON `json:"range"`
|
||||
}
|
||||
|
||||
ret := make([]TraversalJSON, 0, len(vars))
|
||||
for _, traversal := range vars {
|
||||
tJSON := TraversalJSON{
|
||||
Steps: make([]StepJSON, 0, len(traversal)),
|
||||
}
|
||||
|
||||
for _, step := range traversal {
|
||||
var sJSON StepJSON
|
||||
rng := step.SourceRange()
|
||||
sJSON.Range.Filename = rng.Filename
|
||||
sJSON.Range.Start.Line = rng.Start.Line
|
||||
sJSON.Range.Start.Column = rng.Start.Column
|
||||
sJSON.Range.Start.Byte = rng.Start.Byte
|
||||
sJSON.Range.End.Line = rng.End.Line
|
||||
sJSON.Range.End.Column = rng.End.Column
|
||||
sJSON.Range.End.Byte = rng.End.Byte
|
||||
switch ts := step.(type) {
|
||||
case hcl.TraverseRoot:
|
||||
sJSON.Kind = "root"
|
||||
sJSON.Name = ts.Name
|
||||
tJSON.RootName = ts.Name
|
||||
case hcl.TraverseAttr:
|
||||
sJSON.Kind = "attr"
|
||||
sJSON.Name = ts.Name
|
||||
case hcl.TraverseIndex:
|
||||
sJSON.Kind = "index"
|
||||
src, err := ctyjson.Marshal(ts.Key, ts.Key.Type())
|
||||
if err == nil {
|
||||
sJSON.Key = json.RawMessage(src)
|
||||
}
|
||||
default:
|
||||
// Should never get here, since the above should be exhaustive
|
||||
// for all possible traversal step types.
|
||||
sJSON.Kind = "(unknown)"
|
||||
}
|
||||
tJSON.Steps = append(tJSON.Steps, sJSON)
|
||||
}
|
||||
|
||||
// Best effort, we'll try to include the current known value of this
|
||||
// traversal, if any.
|
||||
val, diags := traversal.TraverseAbs(ctx)
|
||||
if !diags.HasErrors() {
|
||||
enc, err := ctyjson.Marshal(val, val.Type())
|
||||
if err == nil {
|
||||
tJSON.Value = json.RawMessage(enc)
|
||||
}
|
||||
}
|
||||
|
||||
rng := traversal.SourceRange()
|
||||
tJSON.Range.Filename = rng.Filename
|
||||
tJSON.Range.Start.Line = rng.Start.Line
|
||||
tJSON.Range.Start.Column = rng.Start.Column
|
||||
tJSON.Range.Start.Byte = rng.Start.Byte
|
||||
tJSON.Range.End.Line = rng.End.Line
|
||||
tJSON.Range.End.Column = rng.End.Column
|
||||
tJSON.Range.End.Byte = rng.End.Byte
|
||||
|
||||
ret = append(ret, tJSON)
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(ret, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal variable references as JSON: %s", err)
|
||||
}
|
||||
|
||||
target := os.Stdout
|
||||
if *outputFile != "" {
|
||||
target, err = os.OpenFile(*outputFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't open %s for writing: %s", *outputFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(target, "%s\n", out)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func stripJSONNullProperties(src []byte) []byte {
|
||||
var v interface{}
|
||||
err := json.Unmarshal(src, &v)
|
||||
|
Loading…
Reference in New Issue
Block a user