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  }