summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorMartin Atkins <mart@degeneration.co.uk>2018-08-09 17:40:14 -0700
committerMartin Atkins <mart@degeneration.co.uk>2018-08-09 18:10:14 -0700
commit609cc35d49710798824b43cce9622fb08d20ecd9 (patch)
tree889718d28d45c497f3bb8963e50af4ce71079f74 /cmd
parentbb724af7fd64ce766b39cad5f147d09d93221546 (diff)
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.
Diffstat (limited to 'cmd')
-rw-r--r--cmd/hcldec/main.go108
1 files changed, 108 insertions, 0 deletions
diff --git a/cmd/hcldec/main.go b/cmd/hcldec/main.go
index 6bb3bf7..dde87a5 100644
--- a/cmd/hcldec/main.go
+++ b/cmd/hcldec/main.go
@@ -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)