// Copyright 2018 The OPA Authors. All rights reserved. // Use of this source code is governed by an Apache2 // license that can be found in the LICENSE file. package cmd import ( "encoding/json" "errors" "io" "os" "strings" "github.com/spf13/cobra" "github.com/open-policy-agent/opa/cmd/formats" "github.com/open-policy-agent/opa/cmd/internal/env" pr "github.com/open-policy-agent/opa/internal/presentation" "github.com/open-policy-agent/opa/v1/ast" astJSON "github.com/open-policy-agent/opa/v1/ast/json" "github.com/open-policy-agent/opa/v1/loader" "github.com/open-policy-agent/opa/v1/util" ) type parseParams struct { format *util.EnumFlag jsonInclude string v0Compatible bool v1Compatible bool } func (p *parseParams) regoVersion() ast.RegoVersion { // the '--v0--compatible' flag takes precedence over the '--v1-compatible' flag if p.v0Compatible { return ast.RegoV0 } if p.v1Compatible { return ast.RegoV1 } return ast.DefaultRegoVersion } var configuredParseParams = parseParams{ format: formats.Flag(formats.Pretty, formats.JSON), jsonInclude: "", } func parse(args []string, params *parseParams, stdout io.Writer, stderr io.Writer) int { if len(args) == 0 { return 0 } exposeLocation := false exposeComments := true for opt := range strings.SplitSeq(params.jsonInclude, ",") { value := !strings.HasPrefix(opt, "-") if strings.HasSuffix(opt, "locations") { exposeLocation = value } if strings.HasSuffix(opt, "comments") { exposeComments = value } } parserOpts := ast.ParserOptions{ ProcessAnnotation: true, RegoVersion: params.regoVersion(), } if exposeLocation { astJSON.SetOptions(astJSON.Options{ MarshalOptions: astJSON.MarshalOptions{ IncludeLocationText: true, IncludeLocation: astJSON.NodeToggle{ Term: true, Package: true, Comment: true, Import: true, Rule: true, Head: true, Expr: true, SomeDecl: true, Every: true, With: true, Annotations: true, AnnotationsRef: true, }, }, }) defer astJSON.SetOptions(astJSON.Defaults()) } result, err := loader.RegoWithOpts(args[0], parserOpts) if err != nil { _ = pr.JSON(stderr, pr.Output{Errors: pr.NewOutputErrors(err)}) return 1 } if !exposeComments { result.Parsed.Comments = nil } switch params.format.String() { case formats.JSON: bs, err := json.MarshalIndent(result.Parsed, "", " ") if err != nil { _ = pr.JSON(stderr, pr.Output{Errors: pr.NewOutputErrors(err)}) return 1 } _, _ = stdout.Write(append(bs, '\n')) default: ast.Pretty(stdout, result.Parsed) } return 0 } func initParse(root *cobra.Command, _ string) { parseCommand := &cobra.Command{ Use: "parse ", Short: "Parse Rego source file", Long: `Parse Rego source file and print AST.`, PreRunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("no source file specified") } return env.CmdFlags.CheckEnvironmentVariables(cmd) }, RunE: func(cmd *cobra.Command, args []string) error { cmd.SilenceErrors = true cmd.SilenceUsage = true exit := parse(args, &configuredParseParams, os.Stdout, os.Stderr) if exit != 0 { return newExitError(exit) } return nil }, } addOutputFormat(parseCommand.Flags(), configuredParseParams.format) parseCommand.Flags().StringVarP(&configuredParseParams.jsonInclude, "json-include", "", "", "include or exclude optional elements. By default comments are included. Current options: locations, comments. E.g. --json-include locations,-comments will include locations and exclude comments.") addV1CompatibleFlag(parseCommand.Flags(), &configuredParseParams.v1Compatible, false) addV0CompatibleFlag(parseCommand.Flags(), &configuredParseParams.v0Compatible, false) root.AddCommand(parseCommand) }