github.com/hashicorp/hcl/v2@v2.20.0/hclparse/parser.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 // Package hclparse has the main API entry point for parsing both HCL native 5 // syntax and HCL JSON. 6 // 7 // The main HCL package also includes SimpleParse and SimpleParseFile which 8 // can be a simpler interface for the common case where an application just 9 // needs to parse a single file. The gohcl package simplifies that further 10 // in its SimpleDecode function, which combines hcl.SimpleParse with decoding 11 // into Go struct values 12 // 13 // Package hclparse, then, is useful for applications that require more fine 14 // control over parsing or which need to load many separate files and keep 15 // track of them for possible error reporting or other analysis. 16 package hclparse 17 18 import ( 19 "fmt" 20 "io/ioutil" 21 22 "github.com/hashicorp/hcl/v2" 23 "github.com/hashicorp/hcl/v2/hclsyntax" 24 "github.com/hashicorp/hcl/v2/json" 25 ) 26 27 // NOTE: This is the public interface for parsing. The actual parsers are 28 // in other packages alongside this one, with this package just wrapping them 29 // to provide a unified interface for the caller across all supported formats. 30 31 // Parser is the main interface for parsing configuration files. As well as 32 // parsing files, a parser also retains a registry of all of the files it 33 // has parsed so that multiple attempts to parse the same file will return 34 // the same object and so the collected files can be used when printing 35 // diagnostics. 36 // 37 // Any diagnostics for parsing a file are only returned once on the first 38 // call to parse that file. Callers are expected to collect up diagnostics 39 // and present them together, so returning diagnostics for the same file 40 // multiple times would create a confusing result. 41 type Parser struct { 42 files map[string]*hcl.File 43 } 44 45 // NewParser creates a new parser, ready to parse configuration files. 46 func NewParser() *Parser { 47 return &Parser{ 48 files: map[string]*hcl.File{}, 49 } 50 } 51 52 // ParseHCL parses the given buffer (which is assumed to have been loaded from 53 // the given filename) as a native-syntax configuration file and returns the 54 // hcl.File object representing it. 55 func (p *Parser) ParseHCL(src []byte, filename string) (*hcl.File, hcl.Diagnostics) { 56 if existing := p.files[filename]; existing != nil { 57 return existing, nil 58 } 59 60 file, diags := hclsyntax.ParseConfig(src, filename, hcl.Pos{Byte: 0, Line: 1, Column: 1}) 61 p.files[filename] = file 62 return file, diags 63 } 64 65 // ParseHCLFile reads the given filename and parses it as a native-syntax HCL 66 // configuration file. An error diagnostic is returned if the given file 67 // cannot be read. 68 func (p *Parser) ParseHCLFile(filename string) (*hcl.File, hcl.Diagnostics) { 69 if existing := p.files[filename]; existing != nil { 70 return existing, nil 71 } 72 73 src, err := ioutil.ReadFile(filename) 74 if err != nil { 75 return nil, hcl.Diagnostics{ 76 { 77 Severity: hcl.DiagError, 78 Summary: "Failed to read file", 79 Detail: fmt.Sprintf("The configuration file %q could not be read.", filename), 80 }, 81 } 82 } 83 84 return p.ParseHCL(src, filename) 85 } 86 87 // ParseJSON parses the given JSON buffer (which is assumed to have been loaded 88 // from the given filename) and returns the hcl.File object representing it. 89 func (p *Parser) ParseJSON(src []byte, filename string) (*hcl.File, hcl.Diagnostics) { 90 if existing := p.files[filename]; existing != nil { 91 return existing, nil 92 } 93 94 file, diags := json.Parse(src, filename) 95 p.files[filename] = file 96 return file, diags 97 } 98 99 // ParseJSONFile reads the given filename and parses it as JSON, similarly to 100 // ParseJSON. An error diagnostic is returned if the given file cannot be read. 101 func (p *Parser) ParseJSONFile(filename string) (*hcl.File, hcl.Diagnostics) { 102 if existing := p.files[filename]; existing != nil { 103 return existing, nil 104 } 105 106 file, diags := json.ParseFile(filename) 107 p.files[filename] = file 108 return file, diags 109 } 110 111 // AddFile allows a caller to record in a parser a file that was parsed some 112 // other way, thus allowing it to be included in the registry of sources. 113 func (p *Parser) AddFile(filename string, file *hcl.File) { 114 p.files[filename] = file 115 } 116 117 // Sources returns a map from filenames to the raw source code that was 118 // read from them. This is intended to be used, for example, to print 119 // diagnostics with contextual information. 120 // 121 // The arrays underlying the returned slices should not be modified. 122 func (p *Parser) Sources() map[string][]byte { 123 ret := make(map[string][]byte) 124 for fn, f := range p.files { 125 ret[fn] = f.Bytes 126 } 127 return ret 128 } 129 130 // Files returns a map from filenames to the File objects produced from them. 131 // This is intended to be used, for example, to print diagnostics with 132 // contextual information. 133 // 134 // The returned map and all of the objects it refers to directly or indirectly 135 // must not be modified. 136 func (p *Parser) Files() map[string]*hcl.File { 137 return p.files 138 }