Skip to content

chore: add oss licenses #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 1, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/licenses.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# GitHub MCP Server dependencies

The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server.

## Go Packages

Some packages may only be included on certain architectures or operating systems.

{{ range . }}
- [{{.Name}}](https://pkg.go.dev/{{.Name}}) ([{{.LicenseName}}]({{.LicenseURL}}))
{{- end }}

[github/github-mcp-server]: https://github.com/github/github-mcp-server
21 changes: 21 additions & 0 deletions .github/workflows/license-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Create a github action that runs the license check script and fails if it exits with a non-zero status

name: License Check
on: [push, pull_request]
permissions:
contents: read

jobs:
license-check:
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- name: check licenses
run: ./script/licenses-check
5 changes: 5 additions & 0 deletions script/licenses
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

go install github.com/google/go-licenses@latest
go-licenses report ./... --template .github/licenses.tmpl > third-party-licenses.md || echo "Ignore warnings"
go-licenses save ./... --save_path=third-party --force || echo "Ignore warnings"
12 changes: 12 additions & 0 deletions script/licenses-check
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash

trap 'rm -f third-party-licenses.copy.md' EXIT

go install github.com/google/go-licenses@latest
go-licenses report ./... --template .github/licenses.tmpl > third-party-licenses.copy.md || echo "Ignore warnings"
# if not diff -s then exit 1

if ! diff -s third-party-licenses.copy.md third-party-licenses.md; then
echo "License check failed. Please update the license file."
exit 1
fi
36 changes: 36 additions & 0 deletions third-party-licenses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# GitHub MCP Server dependencies

The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server.

## Go Packages

Some packages may only be included on certain architectures or operating systems.


- [github.com/aws/smithy-go/ptr](https://pkg.go.dev/github.com/aws/smithy-go/ptr) ([Apache-2.0](https://github.com/aws/smithy-go/blob/v1.22.3/LICENSE))
- [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.7.0/LICENSE))
- [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE))
- [github.com/google/go-github/v69/github](https://pkg.go.dev/github.com/google/go-github/v69/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v69.2.0/LICENSE))
- [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE))
- [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.6.0/LICENSE))
- [github.com/hashicorp/hcl](https://pkg.go.dev/github.com/hashicorp/hcl) ([MPL-2.0](https://github.com/hashicorp/hcl/blob/v1.0.0/LICENSE))
- [github.com/magiconair/properties](https://pkg.go.dev/github.com/magiconair/properties) ([BSD-2-Clause](https://github.com/magiconair/properties/blob/v1.8.9/LICENSE.md))
- [github.com/mark3labs/mcp-go](https://pkg.go.dev/github.com/mark3labs/mcp-go) ([MIT](https://github.com/mark3labs/mcp-go/blob/v0.14.1/LICENSE))
- [github.com/mitchellh/mapstructure](https://pkg.go.dev/github.com/mitchellh/mapstructure) ([MIT](https://github.com/mitchellh/mapstructure/blob/v1.5.0/LICENSE))
- [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.2/LICENSE))
- [github.com/sagikazarmark/slog-shim](https://pkg.go.dev/github.com/sagikazarmark/slog-shim) ([BSD-3-Clause](https://github.com/sagikazarmark/slog-shim/blob/v0.1.0/LICENSE))
- [github.com/sirupsen/logrus](https://pkg.go.dev/github.com/sirupsen/logrus) ([MIT](https://github.com/sirupsen/logrus/blob/v1.9.3/LICENSE))
- [github.com/spf13/afero](https://pkg.go.dev/github.com/spf13/afero) ([Apache-2.0](https://github.com/spf13/afero/blob/v1.11.0/LICENSE.txt))
- [github.com/spf13/cast](https://pkg.go.dev/github.com/spf13/cast) ([MIT](https://github.com/spf13/cast/blob/v1.6.0/LICENSE))
- [github.com/spf13/cobra](https://pkg.go.dev/github.com/spf13/cobra) ([Apache-2.0](https://github.com/spf13/cobra/blob/v1.9.1/LICENSE.txt))
- [github.com/spf13/pflag](https://pkg.go.dev/github.com/spf13/pflag) ([BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.6/LICENSE))
- [github.com/spf13/viper](https://pkg.go.dev/github.com/spf13/viper) ([MIT](https://github.com/spf13/viper/blob/v1.19.0/LICENSE))
- [github.com/subosito/gotenv](https://pkg.go.dev/github.com/subosito/gotenv) ([MIT](https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE))
- [github.com/yosida95/uritemplate/v3](https://pkg.go.dev/github.com/yosida95/uritemplate/v3) ([BSD-3-Clause](https://github.com/yosida95/uritemplate/blob/v3.0.2/LICENSE))
- [golang.org/x/exp/rand](https://pkg.go.dev/golang.org/x/exp/rand) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/92128663:LICENSE))
- [golang.org/x/sys/unix](https://pkg.go.dev/golang.org/x/sys/unix) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE))
- [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.21.0:LICENSE))
- [gopkg.in/ini.v1](https://pkg.go.dev/gopkg.in/ini.v1) ([Apache-2.0](https://github.com/go-ini/ini/blob/v1.67.0/LICENSE))
- [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) ([MIT](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE))

[github/github-mcp-server]: https://github.com/github/github-mcp-server
175 changes: 175 additions & 0 deletions third-party/github.com/aws/smithy-go/ptr/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@

Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.

"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:

(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and

(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and

(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and

(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.

You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
25 changes: 25 additions & 0 deletions third-party/github.com/fsnotify/fsnotify/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Copyright © 2012 The Go Authors. All rights reserved.
Copyright © fsnotify Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
* Neither the name of Google Inc. nor the names of its contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 GitHub

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright (c) 2013 The go-github AUTHORS. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright (c) 2013 Google. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 changes: 27 additions & 0 deletions third-party/github.com/google/uuid/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright (c) 2009,2014 Google Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
### HCL Template
```hcl
# Place your HCL configuration file here
```

### Expected behavior
What should have happened?

### Actual behavior
What actually happened?

### Steps to reproduce
1.
2.
3.

### References
Are there any other GitHub issues (open or closed) that should
be linked here? For example:
- GH-1234
- ...
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
y.output

# ignore intellij files
.idea
*.iml
*.ipr
*.iws

*.test
13 changes: 13 additions & 0 deletions third-party/github.com/hashicorp/hcl/.travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
sudo: false

language: go

go:
- 1.x
- tip

branches:
only:
- master

script: make test
354 changes: 354 additions & 0 deletions third-party/github.com/hashicorp/hcl/LICENSE

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions third-party/github.com/hashicorp/hcl/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
TEST?=./...

default: test

fmt: generate
go fmt ./...

test: generate
go get -t ./...
go test $(TEST) $(TESTARGS)

generate:
go generate ./...

updatedeps:
go get -u golang.org/x/tools/cmd/stringer

.PHONY: default generate test updatedeps
125 changes: 125 additions & 0 deletions third-party/github.com/hashicorp/hcl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# HCL

[![GoDoc](https://godoc.org/github.com/hashicorp/hcl?status.png)](https://godoc.org/github.com/hashicorp/hcl) [![Build Status](https://travis-ci.org/hashicorp/hcl.svg?branch=master)](https://travis-ci.org/hashicorp/hcl)

HCL (HashiCorp Configuration Language) is a configuration language built
by HashiCorp. The goal of HCL is to build a structured configuration language
that is both human and machine friendly for use with command-line tools, but
specifically targeted towards DevOps tools, servers, etc.

HCL is also fully JSON compatible. That is, JSON can be used as completely
valid input to a system expecting HCL. This helps makes systems
interoperable with other systems.

HCL is heavily inspired by
[libucl](https://github.com/vstakhov/libucl),
nginx configuration, and others similar.

## Why?

A common question when viewing HCL is to ask the question: why not
JSON, YAML, etc.?

Prior to HCL, the tools we built at [HashiCorp](http://www.hashicorp.com)
used a variety of configuration languages from full programming languages
such as Ruby to complete data structure languages such as JSON. What we
learned is that some people wanted human-friendly configuration languages
and some people wanted machine-friendly languages.

JSON fits a nice balance in this, but is fairly verbose and most
importantly doesn't support comments. With YAML, we found that beginners
had a really hard time determining what the actual structure was, and
ended up guessing more often than not whether to use a hyphen, colon, etc.
in order to represent some configuration key.

Full programming languages such as Ruby enable complex behavior
a configuration language shouldn't usually allow, and also forces
people to learn some set of Ruby.

Because of this, we decided to create our own configuration language
that is JSON-compatible. Our configuration language (HCL) is designed
to be written and modified by humans. The API for HCL allows JSON
as an input so that it is also machine-friendly (machines can generate
JSON instead of trying to generate HCL).

Our goal with HCL is not to alienate other configuration languages.
It is instead to provide HCL as a specialized language for our tools,
and JSON as the interoperability layer.

## Syntax

For a complete grammar, please see the parser itself. A high-level overview
of the syntax and grammar is listed here.

* Single line comments start with `#` or `//`

* Multi-line comments are wrapped in `/*` and `*/`. Nested block comments
are not allowed. A multi-line comment (also known as a block comment)
terminates at the first `*/` found.

* Values are assigned with the syntax `key = value` (whitespace doesn't
matter). The value can be any primitive: a string, number, boolean,
object, or list.

* Strings are double-quoted and can contain any UTF-8 characters.
Example: `"Hello, World"`

* Multi-line strings start with `<<EOF` at the end of a line, and end
with `EOF` on its own line ([here documents](https://en.wikipedia.org/wiki/Here_document)).
Any text may be used in place of `EOF`. Example:
```
<<FOO
hello
world
FOO
```

* Numbers are assumed to be base 10. If you prefix a number with 0x,
it is treated as a hexadecimal. If it is prefixed with 0, it is
treated as an octal. Numbers can be in scientific notation: "1e10".

* Boolean values: `true`, `false`

* Arrays can be made by wrapping it in `[]`. Example:
`["foo", "bar", 42]`. Arrays can contain primitives,
other arrays, and objects. As an alternative, lists
of objects can be created with repeated blocks, using
this structure:

```hcl
service {
key = "value"
}
service {
key = "value"
}
```
Objects and nested objects are created using the structure shown below:
```
variable "ami" {
description = "the AMI to use"
}
```
This would be equivalent to the following json:
``` json
{
"variable": {
"ami": {
"description": "the AMI to use"
}
}
}
```

## Thanks

Thanks to:

* [@vstakhov](https://github.com/vstakhov) - The original libucl parser
and syntax that HCL was based off of.

* [@fatih](https://github.com/fatih) - The rewritten HCL parser
in pure Go (no goyacc) and support for a printer.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: "build-{branch}-{build}"
image: Visual Studio 2015
clone_folder: c:\gopath\src\github.com\hashicorp\hcl
environment:
GOPATH: c:\gopath
init:
- git config --global core.autocrlf false
install:
- cmd: >-
echo %Path%
go version
go env
go get -t ./...
build_script:
- cmd: go test -v ./...
729 changes: 729 additions & 0 deletions third-party/github.com/hashicorp/hcl/decoder.go

Large diffs are not rendered by default.

1,203 changes: 1,203 additions & 0 deletions third-party/github.com/hashicorp/hcl/decoder_test.go

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions third-party/github.com/hashicorp/hcl/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/hashicorp/hcl

require github.com/davecgh/go-spew v1.1.1
2 changes: 2 additions & 0 deletions third-party/github.com/hashicorp/hcl/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 changes: 11 additions & 0 deletions third-party/github.com/hashicorp/hcl/hcl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Package hcl decodes HCL into usable Go structures.
//
// hcl input can come in either pure HCL format or JSON format.
// It can be parsed into an AST, and then decoded into a structure,
// or it can be decoded directly from a string into a structure.
//
// If you choose to parse HCL into a raw AST, the benefit is that you
// can write custom visitor implementations to implement custom
// semantic checks. By default, HCL does not perform any semantic
// checks.
package hcl
219 changes: 219 additions & 0 deletions third-party/github.com/hashicorp/hcl/hcl/ast/ast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// Package ast declares the types used to represent syntax trees for HCL
// (HashiCorp Configuration Language)
package ast

import (
"fmt"
"strings"

"github.com/hashicorp/hcl/hcl/token"
)

// Node is an element in the abstract syntax tree.
type Node interface {
node()
Pos() token.Pos
}

func (File) node() {}
func (ObjectList) node() {}
func (ObjectKey) node() {}
func (ObjectItem) node() {}
func (Comment) node() {}
func (CommentGroup) node() {}
func (ObjectType) node() {}
func (LiteralType) node() {}
func (ListType) node() {}

// File represents a single HCL file
type File struct {
Node Node // usually a *ObjectList
Comments []*CommentGroup // list of all comments in the source
}

func (f *File) Pos() token.Pos {
return f.Node.Pos()
}

// ObjectList represents a list of ObjectItems. An HCL file itself is an
// ObjectList.
type ObjectList struct {
Items []*ObjectItem
}

func (o *ObjectList) Add(item *ObjectItem) {
o.Items = append(o.Items, item)
}

// Filter filters out the objects with the given key list as a prefix.
//
// The returned list of objects contain ObjectItems where the keys have
// this prefix already stripped off. This might result in objects with
// zero-length key lists if they have no children.
//
// If no matches are found, an empty ObjectList (non-nil) is returned.
func (o *ObjectList) Filter(keys ...string) *ObjectList {
var result ObjectList
for _, item := range o.Items {
// If there aren't enough keys, then ignore this
if len(item.Keys) < len(keys) {
continue
}

match := true
for i, key := range item.Keys[:len(keys)] {
key := key.Token.Value().(string)
if key != keys[i] && !strings.EqualFold(key, keys[i]) {
match = false
break
}
}
if !match {
continue
}

// Strip off the prefix from the children
newItem := *item
newItem.Keys = newItem.Keys[len(keys):]
result.Add(&newItem)
}

return &result
}

// Children returns further nested objects (key length > 0) within this
// ObjectList. This should be used with Filter to get at child items.
func (o *ObjectList) Children() *ObjectList {
var result ObjectList
for _, item := range o.Items {
if len(item.Keys) > 0 {
result.Add(item)
}
}

return &result
}

// Elem returns items in the list that are direct element assignments
// (key length == 0). This should be used with Filter to get at elements.
func (o *ObjectList) Elem() *ObjectList {
var result ObjectList
for _, item := range o.Items {
if len(item.Keys) == 0 {
result.Add(item)
}
}

return &result
}

func (o *ObjectList) Pos() token.Pos {
// always returns the uninitiliazed position
return o.Items[0].Pos()
}

// ObjectItem represents a HCL Object Item. An item is represented with a key
// (or keys). It can be an assignment or an object (both normal and nested)
type ObjectItem struct {
// keys is only one length long if it's of type assignment. If it's a
// nested object it can be larger than one. In that case "assign" is
// invalid as there is no assignments for a nested object.
Keys []*ObjectKey

// assign contains the position of "=", if any
Assign token.Pos

// val is the item itself. It can be an object,list, number, bool or a
// string. If key length is larger than one, val can be only of type
// Object.
Val Node

LeadComment *CommentGroup // associated lead comment
LineComment *CommentGroup // associated line comment
}

func (o *ObjectItem) Pos() token.Pos {
// I'm not entirely sure what causes this, but removing this causes
// a test failure. We should investigate at some point.
if len(o.Keys) == 0 {
return token.Pos{}
}

return o.Keys[0].Pos()
}

// ObjectKeys are either an identifier or of type string.
type ObjectKey struct {
Token token.Token
}

func (o *ObjectKey) Pos() token.Pos {
return o.Token.Pos
}

// LiteralType represents a literal of basic type. Valid types are:
// token.NUMBER, token.FLOAT, token.BOOL and token.STRING
type LiteralType struct {
Token token.Token

// comment types, only used when in a list
LeadComment *CommentGroup
LineComment *CommentGroup
}

func (l *LiteralType) Pos() token.Pos {
return l.Token.Pos
}

// ListStatement represents a HCL List type
type ListType struct {
Lbrack token.Pos // position of "["
Rbrack token.Pos // position of "]"
List []Node // the elements in lexical order
}

func (l *ListType) Pos() token.Pos {
return l.Lbrack
}

func (l *ListType) Add(node Node) {
l.List = append(l.List, node)
}

// ObjectType represents a HCL Object Type
type ObjectType struct {
Lbrace token.Pos // position of "{"
Rbrace token.Pos // position of "}"
List *ObjectList // the nodes in lexical order
}

func (o *ObjectType) Pos() token.Pos {
return o.Lbrace
}

// Comment node represents a single //, # style or /*- style commment
type Comment struct {
Start token.Pos // position of / or #
Text string
}

func (c *Comment) Pos() token.Pos {
return c.Start
}

// CommentGroup node represents a sequence of comments with no other tokens and
// no empty lines between.
type CommentGroup struct {
List []*Comment // len(List) > 0
}

func (c *CommentGroup) Pos() token.Pos {
return c.List[0].Pos()
}

//-------------------------------------------------------------------
// GoStringer
//-------------------------------------------------------------------

func (o *ObjectKey) GoString() string { return fmt.Sprintf("*%#v", *o) }
func (o *ObjectList) GoString() string { return fmt.Sprintf("*%#v", *o) }
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package ast

import (
"reflect"
"strings"
"testing"

"github.com/hashicorp/hcl/hcl/token"
)

func TestObjectListFilter(t *testing.T) {
var cases = []struct {
Filter []string
Input []*ObjectItem
Output []*ObjectItem
}{
{
[]string{"foo"},
[]*ObjectItem{
&ObjectItem{
Keys: []*ObjectKey{
&ObjectKey{
Token: token.Token{Type: token.STRING, Text: `"foo"`},
},
},
},
},
[]*ObjectItem{
&ObjectItem{
Keys: []*ObjectKey{},
},
},
},

{
[]string{"foo"},
[]*ObjectItem{
&ObjectItem{
Keys: []*ObjectKey{
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"foo"`}},
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"bar"`}},
},
},
&ObjectItem{
Keys: []*ObjectKey{
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"baz"`}},
},
},
},
[]*ObjectItem{
&ObjectItem{
Keys: []*ObjectKey{
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"bar"`}},
},
},
},
},
}

for _, tc := range cases {
input := &ObjectList{Items: tc.Input}
expected := &ObjectList{Items: tc.Output}
if actual := input.Filter(tc.Filter...); !reflect.DeepEqual(actual, expected) {
t.Fatalf("in order: input, expected, actual\n\n%#v\n\n%#v\n\n%#v", input, expected, actual)
}
}
}

func TestWalk(t *testing.T) {
items := []*ObjectItem{
&ObjectItem{
Keys: []*ObjectKey{
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"foo"`}},
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"bar"`}},
},
Val: &LiteralType{Token: token.Token{Type: token.STRING, Text: `"example"`}},
},
&ObjectItem{
Keys: []*ObjectKey{
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"baz"`}},
},
},
}

node := &ObjectList{Items: items}

order := []string{
"*ast.ObjectList",
"*ast.ObjectItem",
"*ast.ObjectKey",
"*ast.ObjectKey",
"*ast.LiteralType",
"*ast.ObjectItem",
"*ast.ObjectKey",
}
count := 0

Walk(node, func(n Node) (Node, bool) {
if n == nil {
return n, false
}

typeName := reflect.TypeOf(n).String()
if order[count] != typeName {
t.Errorf("expected '%s' got: '%s'", order[count], typeName)
}
count++
return n, true
})
}

func TestWalkEquality(t *testing.T) {
items := []*ObjectItem{
&ObjectItem{
Keys: []*ObjectKey{
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"foo"`}},
},
},
&ObjectItem{
Keys: []*ObjectKey{
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"bar"`}},
},
},
}

node := &ObjectList{Items: items}

rewritten := Walk(node, func(n Node) (Node, bool) { return n, true })

newNode, ok := rewritten.(*ObjectList)
if !ok {
t.Fatalf("expected Objectlist, got %T", rewritten)
}

if !reflect.DeepEqual(node, newNode) {
t.Fatal("rewritten node is not equal to the given node")
}

if len(newNode.Items) != 2 {
t.Errorf("expected newNode length 2, got: %d", len(newNode.Items))
}

expected := []string{
`"foo"`,
`"bar"`,
}

for i, item := range newNode.Items {
if len(item.Keys) != 1 {
t.Errorf("expected keys newNode length 1, got: %d", len(item.Keys))
}

if item.Keys[0].Token.Text != expected[i] {
t.Errorf("expected key %s, got %s", expected[i], item.Keys[0].Token.Text)
}

if item.Val != nil {
t.Errorf("expected item value should be nil")
}
}
}

func TestWalkRewrite(t *testing.T) {
items := []*ObjectItem{
&ObjectItem{
Keys: []*ObjectKey{
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"foo"`}},
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"bar"`}},
},
},
&ObjectItem{
Keys: []*ObjectKey{
&ObjectKey{Token: token.Token{Type: token.STRING, Text: `"baz"`}},
},
},
}

node := &ObjectList{Items: items}

suffix := "_example"
node = Walk(node, func(n Node) (Node, bool) {
switch i := n.(type) {
case *ObjectKey:
i.Token.Text = i.Token.Text + suffix
n = i
}
return n, true
}).(*ObjectList)

Walk(node, func(n Node) (Node, bool) {
switch i := n.(type) {
case *ObjectKey:
if !strings.HasSuffix(i.Token.Text, suffix) {
t.Errorf("Token '%s' should have suffix: %s", i.Token.Text, suffix)
}
}
return n, true
})

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package ast

import "fmt"

// WalkFunc describes a function to be called for each node during a Walk. The
// returned node can be used to rewrite the AST. Walking stops the returned
// bool is false.
type WalkFunc func(Node) (Node, bool)

// Walk traverses an AST in depth-first order: It starts by calling fn(node);
// node must not be nil. If fn returns true, Walk invokes fn recursively for
// each of the non-nil children of node, followed by a call of fn(nil). The
// returned node of fn can be used to rewrite the passed node to fn.
func Walk(node Node, fn WalkFunc) Node {
rewritten, ok := fn(node)
if !ok {
return rewritten
}

switch n := node.(type) {
case *File:
n.Node = Walk(n.Node, fn)
case *ObjectList:
for i, item := range n.Items {
n.Items[i] = Walk(item, fn).(*ObjectItem)
}
case *ObjectKey:
// nothing to do
case *ObjectItem:
for i, k := range n.Keys {
n.Keys[i] = Walk(k, fn).(*ObjectKey)
}

if n.Val != nil {
n.Val = Walk(n.Val, fn)
}
case *LiteralType:
// nothing to do
case *ListType:
for i, l := range n.List {
n.List[i] = Walk(l, fn)
}
case *ObjectType:
n.List = Walk(n.List, fn).(*ObjectList)
default:
// should we panic here?
fmt.Printf("unknown type: %T\n", n)
}

fn(nil)
return rewritten
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Derivative work from:
// - https://golang.org/src/cmd/gofmt/gofmt.go
// - https://github.com/fatih/hclfmt

package fmtcmd

import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/hashicorp/hcl/hcl/printer"
)

var (
ErrWriteStdin = errors.New("cannot use write option with standard input")
)

type Options struct {
List bool // list files whose formatting differs
Write bool // write result to (source) file instead of stdout
Diff bool // display diffs of formatting changes
}

func isValidFile(f os.FileInfo, extensions []string) bool {
if !f.IsDir() && !strings.HasPrefix(f.Name(), ".") {
for _, ext := range extensions {
if strings.HasSuffix(f.Name(), "."+ext) {
return true
}
}
}

return false
}

// If in == nil, the source is the contents of the file with the given filename.
func processFile(filename string, in io.Reader, out io.Writer, stdin bool, opts Options) error {
if in == nil {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
in = f
}

src, err := ioutil.ReadAll(in)
if err != nil {
return err
}

res, err := printer.Format(src)
if err != nil {
return fmt.Errorf("In %s: %s", filename, err)
}

if !bytes.Equal(src, res) {
// formatting has changed
if opts.List {
fmt.Fprintln(out, filename)
}
if opts.Write {
err = ioutil.WriteFile(filename, res, 0644)
if err != nil {
return err
}
}
if opts.Diff {
data, err := diff(src, res)
if err != nil {
return fmt.Errorf("computing diff: %s", err)
}
fmt.Fprintf(out, "diff a/%s b/%s\n", filename, filename)
out.Write(data)
}
}

if !opts.List && !opts.Write && !opts.Diff {
_, err = out.Write(res)
}

return err
}

func walkDir(path string, extensions []string, stdout io.Writer, opts Options) error {
visitFile := func(path string, f os.FileInfo, err error) error {
if err == nil && isValidFile(f, extensions) {
err = processFile(path, nil, stdout, false, opts)
}
return err
}

return filepath.Walk(path, visitFile)
}

func Run(
paths, extensions []string,
stdin io.Reader,
stdout io.Writer,
opts Options,
) error {
if len(paths) == 0 {
if opts.Write {
return ErrWriteStdin
}
if err := processFile("<standard input>", stdin, stdout, true, opts); err != nil {
return err
}
return nil
}

for _, path := range paths {
switch dir, err := os.Stat(path); {
case err != nil:
return err
case dir.IsDir():
if err := walkDir(path, extensions, stdout, opts); err != nil {
return err
}
default:
if err := processFile(path, nil, stdout, false, opts); err != nil {
return err
}
}
}

return nil
}

func diff(b1, b2 []byte) (data []byte, err error) {
f1, err := ioutil.TempFile("", "")
if err != nil {
return
}
defer os.Remove(f1.Name())
defer f1.Close()

f2, err := ioutil.TempFile("", "")
if err != nil {
return
}
defer os.Remove(f2.Name())
defer f2.Close()

f1.Write(b1)
f2.Write(b2)

data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
if len(data) > 0 {
// diff exits with a non-zero status when the files don't match.
// Ignore that failure as long as we get output.
err = nil
}
return
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invalid
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invalid
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package parser

import (
"fmt"

"github.com/hashicorp/hcl/hcl/token"
)

// PosError is a parse error that contains a position.
type PosError struct {
Pos token.Pos
Err error
}

func (e *PosError) Error() string {
return fmt.Sprintf("At %s: %s", e.Pos, e.Err)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package parser

import (
"testing"
)

func TestPosError_impl(t *testing.T) {
var _ error = new(PosError)
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
foo = [
"1",
"2", # comment
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
provisioner "remote-exec" {
scripts = [
"${path.module}/scripts/install-consul.sh" // missing comma
"${path.module}/scripts/install-haproxy.sh"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
resource = [{
"foo": {
"bar": {},
"baz": [1, 2, "foo"],
}
}]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
resource = [{
foo = [{
bar = {}
}]
}]
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Foo

/* Bar */

/*
/*
Baz
*/

# Another

# Multiple
# Lines

foo = "bar"
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Foo

/* Bar */

/*
/*
Baz
*/

# Another

# Multiple
# Lines

foo = "bar"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#foo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Hello
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
variable "foo" {
default = "bar"
description = "bar"
}

variable "groups" { }

provider "aws" {
access_key = "foo"
secret_key = "bar"
}

provider "do" {
api_key = "${var.foo}"
}

resource "aws_security_group" "firewall" {
count = 5
}

resource aws_instance "web" {
ami = "${var.foo}"
security_groups = [
"foo",
"${aws_security_group.firewall.foo}",
"${element(split(\",\", var.groups)}",
]
network_interface = {
device_index = 0
description = "Main network interface"
}
}
resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
VPC = "foo"
depends_on = ["aws_instance.web"]
}
output "web_ip" {
value = "${aws_instance.web.private_ip}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
variable "foo" {
default = "bar"
description = "bar"
}

variable "groups" { }

provider "aws" {
access_key = "foo"
secret_key = "bar"
}

provider "do" {
api_key = "${var.foo}"
}

resource "aws_security_group" "firewall" {
count = 5
}

resource aws_instance "web" {
ami = "${var.foo}"
security_groups = [
"foo",
"${aws_security_group.firewall.foo}",
"${element(split(\",\", var.groups)}",
]
network_interface = {
device_index = 0
description = "Main network interface"
}
}
resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
VPC = "foo"
depends_on = ["aws_instance.web"]
}
output "web_ip" {
value = "${aws_instance.web.private_ip}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo.bar = "baz"
Empty file.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo = [1, 2, "foo"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo = [1, 2, "foo",]
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# should error, but not crash
resource "template_file" "cloud_config" {
template = "$file("${path.module}/some/path")"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
foo = "bar"
key = 7
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
foo {
bar =
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
foo {
baz = 7
bar =
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
foo {
bar =
baz = 7
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
foo {
bar
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo = {one = 1, two = 2}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
default = {
"eu-west-1": "ami-b1cf19c6",
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// This is a test structure for the lexer
foo bar "baz" {
key = 7
foo = "bar"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
foo {
value = 7
"value" = 8
"complex::value" = 9
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
resource "foo" "bar" {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
foo = "bar"
bar = 7
baz = [1,2,3]
foo = -12
bar = 3.14159
foo = true
bar = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
foo "baz" {
bar = "baz"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
resource "aws_eip" "EIP1" { a { a { a { a { a {
count = "1"

resource "aws_eip" "EIP2" {
count = "1"
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Package printer implements printing of AST nodes to HCL format.
package printer

import (
"bytes"
"io"
"text/tabwriter"

"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/hcl/hcl/parser"
)

var DefaultConfig = Config{
SpacesWidth: 2,
}

// A Config node controls the output of Fprint.
type Config struct {
SpacesWidth int // if set, it will use spaces instead of tabs for alignment
}

func (c *Config) Fprint(output io.Writer, node ast.Node) error {
p := &printer{
cfg: *c,
comments: make([]*ast.CommentGroup, 0),
standaloneComments: make([]*ast.CommentGroup, 0),
// enableTrace: true,
}

p.collectComments(node)

if _, err := output.Write(p.unindent(p.output(node))); err != nil {
return err
}

// flush tabwriter, if any
var err error
if tw, _ := output.(*tabwriter.Writer); tw != nil {
err = tw.Flush()
}

return err
}

// Fprint "pretty-prints" an HCL node to output
// It calls Config.Fprint with default settings.
func Fprint(output io.Writer, node ast.Node) error {
return DefaultConfig.Fprint(output, node)
}

// Format formats src HCL and returns the result.
func Format(src []byte) ([]byte, error) {
node, err := parser.Parse(src)
if err != nil {
return nil, err
}

var buf bytes.Buffer
if err := DefaultConfig.Fprint(&buf, node); err != nil {
return nil, err
}

// Add trailing newline to result
buf.WriteString("\n")
return buf.Bytes(), nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package printer

import (
"bytes"
"errors"
"flag"
"fmt"
"io/ioutil"
"path/filepath"
"testing"

"github.com/hashicorp/hcl/hcl/parser"
)

var update = flag.Bool("update", false, "update golden files")

const (
dataDir = "testdata"
)

type entry struct {
source, golden string
}

// Use go test -update to create/update the respective golden files.
var data = []entry{
{"complexhcl.input", "complexhcl.golden"},
{"list.input", "list.golden"},
{"list_comment.input", "list_comment.golden"},
{"comment.input", "comment.golden"},
{"comment_crlf.input", "comment.golden"},
{"comment_aligned.input", "comment_aligned.golden"},
{"comment_array.input", "comment_array.golden"},
{"comment_end_file.input", "comment_end_file.golden"},
{"comment_multiline_indent.input", "comment_multiline_indent.golden"},
{"comment_multiline_no_stanza.input", "comment_multiline_no_stanza.golden"},
{"comment_multiline_stanza.input", "comment_multiline_stanza.golden"},
{"comment_newline.input", "comment_newline.golden"},
{"comment_object_multi.input", "comment_object_multi.golden"},
{"comment_standalone.input", "comment_standalone.golden"},
{"empty_block.input", "empty_block.golden"},
{"list_of_objects.input", "list_of_objects.golden"},
{"multiline_string.input", "multiline_string.golden"},
{"object_singleline.input", "object_singleline.golden"},
{"object_with_heredoc.input", "object_with_heredoc.golden"},
}

func TestFiles(t *testing.T) {
for _, e := range data {
source := filepath.Join(dataDir, e.source)
golden := filepath.Join(dataDir, e.golden)
t.Run(e.source, func(t *testing.T) {
check(t, source, golden)
})
}
}

func check(t *testing.T, source, golden string) {
src, err := ioutil.ReadFile(source)
if err != nil {
t.Error(err)
return
}

res, err := format(src)
if err != nil {
t.Error(err)
return
}

// update golden files if necessary
if *update {
if err := ioutil.WriteFile(golden, res, 0644); err != nil {
t.Error(err)
}
return
}

// get golden
gld, err := ioutil.ReadFile(golden)
if err != nil {
t.Error(err)
return
}

// formatted source and golden must be the same
if err := diff(source, golden, res, gld); err != nil {
t.Error(err)
return
}
}

// diff compares a and b.
func diff(aname, bname string, a, b []byte) error {
var buf bytes.Buffer // holding long error message

// compare lengths
if len(a) != len(b) {
fmt.Fprintf(&buf, "\nlength changed: len(%s) = %d, len(%s) = %d", aname, len(a), bname, len(b))
}

// compare contents
line := 1
offs := 1
for i := 0; i < len(a) && i < len(b); i++ {
ch := a[i]
if ch != b[i] {
fmt.Fprintf(&buf, "\n%s:%d:%d: %q", aname, line, i-offs+1, lineAt(a, offs))
fmt.Fprintf(&buf, "\n%s:%d:%d: %q", bname, line, i-offs+1, lineAt(b, offs))
fmt.Fprintf(&buf, "\n\n")
break
}
if ch == '\n' {
line++
offs = i + 1
}
}

if buf.Len() > 0 {
return errors.New(buf.String())
}
return nil
}

// format parses src, prints the corresponding AST, verifies the resulting
// src is syntactically correct, and returns the resulting src or an error
// if any.
func format(src []byte) ([]byte, error) {
formatted, err := Format(src)
if err != nil {
return nil, err
}

// make sure formatted output is syntactically correct
if _, err := parser.Parse(formatted); err != nil {
return nil, fmt.Errorf("parse: %s\n%s", err, formatted)
}

return formatted, nil
}

// lineAt returns the line in text starting at offset offs.
func lineAt(text []byte, offs int) []byte {
i := offs
for i < len(text) && text[i] != '\n' {
i++
}
return text[offs:i]
}

// TestFormatParsable ensures that the output of Format() is can be parsed again.
func TestFormatValidOutput(t *testing.T) {
cases := []string{
"#\x00",
"#\ue123t",
"x=//\n0y=<<_\n_\n",
"y=[1,//\n]",
"Y=<<4\n4/\n\n\n/4/@=4/\n\n\n/4000000004\r\r\n00004\n",
"x=<<_\n_\r\r\n_\n",
"X=<<-\n\r\r\n",
}

for _, c := range cases {
f, err := Format([]byte(c))
if err != nil {
// ignore these failures, not all inputs are valid HCL.
t.Logf("Format(%q) = %v", c, err)
continue
}

if _, err := parser.Parse(f); err != nil {
t.Errorf("Format(%q) = %q; Parse(%q) = %v", c, f, f, err)
continue
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// A standalone comment is a comment which is not attached to any kind of node

// This comes from Terraform, as a test
variable "foo" {
# Standalone comment should be still here

default = "bar"
description = "bar" # yooo
}

/* This is a multi line standalone
comment*/

// fatih arslan
/* This is a developer test
account and a multine comment */
developer = ["fatih", "arslan"] // fatih arslan

# One line here
numbers = [1, 2] // another line here

# Another comment
variable = {
description = "bar" # another yooo

foo {
# Nested standalone

bar = "fatih"
}
}

// lead comment
foo {
bar = "fatih" // line comment 2
} // line comment 3

// comment
multiline = "assignment"
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// A standalone comment is a comment which is not attached to any kind of node

// This comes from Terraform, as a test
variable "foo" {
# Standalone comment should be still here

default = "bar"
description = "bar" # yooo
}

/* This is a multi line standalone
comment*/


// fatih arslan
/* This is a developer test
account and a multine comment */
developer = [ "fatih", "arslan"] // fatih arslan

# One line here
numbers = [1,2] // another line here

# Another comment
variable = {
description = "bar" # another yooo
foo {
# Nested standalone

bar = "fatih"
}
}

// lead comment
foo {
bar = "fatih" // line comment 2
} // line comment 3

multiline = // comment
"assignment"
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
aligned {
# We have some aligned items below
foo = "fatih" # yoo1
default = "bar" # yoo2
bar = "bar and foo" # yoo3

default = {
bar = "example"
}

#deneme arslan
fatih = ["fatih"] # yoo4

#fatih arslan
fatiharslan = ["arslan"] // yoo5

default = {
bar = "example"
}

security_groups = [
"foo", # kenya 1
"${aws_security_group.firewall.foo}", # kenya 2
]

security_groups2 = [
"foo", # kenya 1
"bar", # kenya 1.5
"${aws_security_group.firewall.foo}", # kenya 2
"foobar", # kenya 3
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
aligned {
# We have some aligned items below
foo = "fatih" # yoo1
default = "bar" # yoo2
bar = "bar and foo" # yoo3
default = {
bar = "example"
}
#deneme arslan
fatih = ["fatih"] # yoo4
#fatih arslan
fatiharslan = ["arslan"] // yoo5
default = {
bar = "example"
}

security_groups = [
"foo", # kenya 1
"${aws_security_group.firewall.foo}", # kenya 2
]

security_groups2 = [
"foo", # kenya 1
"bar", # kenya 1.5
"${aws_security_group.firewall.foo}", # kenya 2
"foobar", # kenya 3
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
banana = [
# I really want to comment this item in the array.
"a",

# This as well
"b",

"c", # And C
"d",

# And another
"e",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
banana = [
# I really want to comment this item in the array.
"a",

# This as well
"b",

"c", # And C
"d",

# And another
"e",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// A standalone comment is a comment which is not attached to any kind of node

// This comes from Terraform, as a test
variable "foo" {
# Standalone comment should be still here

default = "bar"
description = "bar" # yooo
}

/* This is a multi line standalone
comment*/


// fatih arslan
/* This is a developer test
account and a multine comment */
developer = [ "fatih", "arslan"] // fatih arslan

# One line here
numbers = [1,2] // another line here

# Another comment
variable = {
description = "bar" # another yooo
foo {
# Nested standalone

bar = "fatih"
}
}

// lead comment
foo {
bar = "fatih" // line comment 2
} // line comment 3

multiline = // comment
"assignment"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
resource "blah" "blah" {}

//
//
//

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
resource "blah" "blah" {}

//
//
//
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
resource "provider" "resource" {
/*
SPACE_SENSITIVE_CODE = <<EOF
yaml code:
foo: ""
bar: ""
EOF
*/
/*
OTHER
*/
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
resource "provider" "resource" {
/*
SPACE_SENSITIVE_CODE = <<EOF
yaml code:
foo: ""
bar: ""
EOF
*/

/*
OTHER
*/
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This is a multiline comment
# That has values like this:
#
# ami-abcd1234
#
# Do not delete this comment

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# This is a multiline comment
# That has values like this:
#
# ami-abcd1234
#
# Do not delete this comment
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This is a multiline comment
# That has values like this:
#
# ami-abcd1234
#
# Do not delete this comment

resource "aws_instance" "web" {
ami_id = "ami-abcd1234"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This is a multiline comment
# That has values like this:
#
# ami-abcd1234
#
# Do not delete this comment

resource "aws_instance" "web" {
ami_id = "ami-abcd1234"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Hello
# World

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Hello
# World
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
variable "environment" {
default = {}

# default {
# "region" = "us-west-2"
# "sg" = "playground"
# "env" = "prod"
# }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
variable "environment" {
default = {}

# default {
# "region" = "us-west-2"
# "sg" = "playground"
# "env" = "prod"
# }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// A standalone comment

aligned {
# Standalone 1

a = "bar" # yoo1
default = "bar" # yoo2

# Standalone 2
}

# Standalone 3

numbers = [1, 2] // another line here

# Standalone 4

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// A standalone comment

aligned {
# Standalone 1

a = "bar" # yoo1
default = "bar" # yoo2

# Standalone 2
}

# Standalone 3

numbers = [1,2] // another line here

# Standalone 4
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
variable "foo" {
default = "bar"
description = "bar"
}

developer = ["fatih", "arslan"]

provider "aws" {
access_key = "foo"
secret_key = "bar"
}

provider "do" {
api_key = "${var.foo}"
}

resource "aws_security_group" "firewall" {
count = 5
}

resource aws_instance "web" {
ami = "${var.foo}"

security_groups = [
"foo",
"${aws_security_group.firewall.foo}",
]

network_interface {
device_index = 0
description = "Main network interface"
}

network_interface = {
device_index = 1

description = <<EOF
ANOTHER NETWORK INTERFACE
EOF
}
}

resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
VPC = "foo"

depends_on = ["aws_instance.web"]
}

output "web_ip" {
value = <<EOF
TUBES
EOF
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
variable "foo" {
default = "bar"
description = "bar"
}

developer = [ "fatih", "arslan"]

provider "aws" {
access_key ="foo"
secret_key = "bar"
}

provider "do" {
api_key = "${var.foo}"
}

resource "aws_security_group" "firewall" {
count = 5
}

resource aws_instance "web" {
ami = "${var.foo}"
security_groups = [
"foo",
"${aws_security_group.firewall.foo}"
]

network_interface {
device_index = 0
description = "Main network interface"
}

network_interface = {
device_index = 1
description = <<EOF
ANOTHER NETWORK INTERFACE
EOF
}
}

resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
VPC = "foo"

depends_on = ["aws_instance.web"]
}

output "web_ip" {

value=<<EOF
TUBES
EOF
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
variable "foo" {}
variable "foo" {}

variable "foo" {
# Standalone comment should be still here
}

foo {}

foo {
bar = "mssola"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
variable "foo" {}
variable "foo" {
}

variable "foo" {
# Standalone comment should be still here
}

foo {
}

foo {
bar = "mssola"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
foo = ["fatih", "arslan"]

foo = ["bar", "qaz"]

foo = [
"zeynep",
"arslan",
]

foo = [
"fatih",
"zeynep",
"arslan",
]

foo = [
"vim-go",
"golang",
"hcl",
]

foo = []

foo = [1, 2, 3, 4]

foo = [
"kenya",
"ethiopia",
"columbia",
]

foo = [
<<EOS
one
EOS
,
<<EOS
two
EOS
,
]

foo = [<<EOS
one
EOS
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
foo = ["fatih", "arslan" ]

foo = [ "bar", "qaz", ]

foo = [ "zeynep",
"arslan", ]

foo = ["fatih", "zeynep",
"arslan", ]

foo = [
"vim-go",
"golang", "hcl"]

foo = []

foo = [1, 2,3, 4]

foo = [
"kenya", "ethiopia",
"columbia"]

foo = [
<<EOS
one
EOS
,
<<EOS
two
EOS
,
]

foo = [<<EOS
one
EOS
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
foo = [
1, # Hello
2,
]

foo = [
1, # Hello
2, # World
]

foo = [
1, # Hello
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
foo = [1, # Hello
2]

foo = [1, # Hello
2, # World
]

foo = [1, # Hello
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
list_of_objects = [
{
key1 = "value1"
key2 = "value2"
},
{
key3 = "value3"
key4 = "value4"
},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
list_of_objects = [
{
key1 = "value1"
key2 = "value2"
},
{
key3 = "value3"
key4 = "value4"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
resource "null_resource" "some_command" {
provisioner "local-exec" {
command = "${echo '
some newlines
and additonal output'}"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
resource "null_resource" "some_command" {
provisioner "local-exec" {
command = "${echo '
some newlines
and additonal output'}"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
variable "foo" {}
variable "bar" {}
variable "baz" {}

variable "qux" {}

variable "foo" {
foo = "bar"
}

variable "foo" {}

# lead comment
variable "bar" {}

variable "foo" {
default = "bar"
}

variable "bar" {}

# Purposeful newline check below:

variable "foo" {}

variable "purposeful-newline" {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
variable "foo" {}
variable "bar" {}
variable "baz" {}

variable "qux" {}
variable "foo" { foo = "bar" }

variable "foo" {}
# lead comment
variable "bar" {}

variable "foo" { default = "bar" }
variable "bar" {}

# Purposeful newline check below:

variable "foo" {}

variable "purposeful-newline" {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
obj {
foo = [<<EOF
TEXT!
!!EOF
EOF
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
obj {
foo = [<<EOF
TEXT!
!!EOF
EOF
]
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package strconv

import (
"errors"
"unicode/utf8"
)

// ErrSyntax indicates that a value does not have the right syntax for the target type.
var ErrSyntax = errors.New("invalid syntax")

// Unquote interprets s as a single-quoted, double-quoted,
// or backquoted Go string literal, returning the string value
// that s quotes. (If s is single-quoted, it would be a Go
// character literal; Unquote returns the corresponding
// one-character string.)
func Unquote(s string) (t string, err error) {
n := len(s)
if n < 2 {
return "", ErrSyntax
}
quote := s[0]
if quote != s[n-1] {
return "", ErrSyntax
}
s = s[1 : n-1]

if quote != '"' {
return "", ErrSyntax
}
if !contains(s, '$') && !contains(s, '{') && contains(s, '\n') {
return "", ErrSyntax
}

// Is it trivial? Avoid allocation.
if !contains(s, '\\') && !contains(s, quote) && !contains(s, '$') {
switch quote {
case '"':
return s, nil
case '\'':
r, size := utf8.DecodeRuneInString(s)
if size == len(s) && (r != utf8.RuneError || size != 1) {
return s, nil
}
}
}

var runeTmp [utf8.UTFMax]byte
buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations.
for len(s) > 0 {
// If we're starting a '${}' then let it through un-unquoted.
// Specifically: we don't unquote any characters within the `${}`
// section.
if s[0] == '$' && len(s) > 1 && s[1] == '{' {
buf = append(buf, '$', '{')
s = s[2:]

// Continue reading until we find the closing brace, copying as-is
braces := 1
for len(s) > 0 && braces > 0 {
r, size := utf8.DecodeRuneInString(s)
if r == utf8.RuneError {
return "", ErrSyntax
}

s = s[size:]

n := utf8.EncodeRune(runeTmp[:], r)
buf = append(buf, runeTmp[:n]...)

switch r {
case '{':
braces++
case '}':
braces--
}
}
if braces != 0 {
return "", ErrSyntax
}
if len(s) == 0 {
// If there's no string left, we're done!
break
} else {
// If there's more left, we need to pop back up to the top of the loop
// in case there's another interpolation in this string.
continue
}
}

if s[0] == '\n' {
return "", ErrSyntax
}

c, multibyte, ss, err := unquoteChar(s, quote)
if err != nil {
return "", err
}
s = ss
if c < utf8.RuneSelf || !multibyte {
buf = append(buf, byte(c))
} else {
n := utf8.EncodeRune(runeTmp[:], c)
buf = append(buf, runeTmp[:n]...)
}
if quote == '\'' && len(s) != 0 {
// single-quoted must be single character
return "", ErrSyntax
}
}
return string(buf), nil
}

// contains reports whether the string contains the byte c.
func contains(s string, c byte) bool {
for i := 0; i < len(s); i++ {
if s[i] == c {
return true
}
}
return false
}

func unhex(b byte) (v rune, ok bool) {
c := rune(b)
switch {
case '0' <= c && c <= '9':
return c - '0', true
case 'a' <= c && c <= 'f':
return c - 'a' + 10, true
case 'A' <= c && c <= 'F':
return c - 'A' + 10, true
}
return
}

func unquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) {
// easy cases
switch c := s[0]; {
case c == quote && (quote == '\'' || quote == '"'):
err = ErrSyntax
return
case c >= utf8.RuneSelf:
r, size := utf8.DecodeRuneInString(s)
return r, true, s[size:], nil
case c != '\\':
return rune(s[0]), false, s[1:], nil
}

// hard case: c is backslash
if len(s) <= 1 {
err = ErrSyntax
return
}
c := s[1]
s = s[2:]

switch c {
case 'a':
value = '\a'
case 'b':
value = '\b'
case 'f':
value = '\f'
case 'n':
value = '\n'
case 'r':
value = '\r'
case 't':
value = '\t'
case 'v':
value = '\v'
case 'x', 'u', 'U':
n := 0
switch c {
case 'x':
n = 2
case 'u':
n = 4
case 'U':
n = 8
}
var v rune
if len(s) < n {
err = ErrSyntax
return
}
for j := 0; j < n; j++ {
x, ok := unhex(s[j])
if !ok {
err = ErrSyntax
return
}
v = v<<4 | x
}
s = s[n:]
if c == 'x' {
// single-byte string, possibly not UTF-8
value = v
break
}
if v > utf8.MaxRune {
err = ErrSyntax
return
}
value = v
multibyte = true
case '0', '1', '2', '3', '4', '5', '6', '7':
v := rune(c) - '0'
if len(s) < 2 {
err = ErrSyntax
return
}
for j := 0; j < 2; j++ { // one digit already; two more
x := rune(s[j]) - '0'
if x < 0 || x > 7 {
err = ErrSyntax
return
}
v = (v << 3) | x
}
s = s[2:]
if v > 255 {
err = ErrSyntax
return
}
value = v
case '\\':
value = '\\'
case '\'', '"':
if c != quote {
err = ErrSyntax
return
}
value = rune(c)
default:
err = ErrSyntax
return
}
tail = s
return
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package strconv

import "testing"

type quoteTest struct {
in string
out string
ascii string
}

var quotetests = []quoteTest{
{"\a\b\f\r\n\t\v", `"\a\b\f\r\n\t\v"`, `"\a\b\f\r\n\t\v"`},
{"\\", `"\\"`, `"\\"`},
{"abc\xffdef", `"abc\xffdef"`, `"abc\xffdef"`},
{"\u263a", `"☺"`, `"\u263a"`},
{"\U0010ffff", `"\U0010ffff"`, `"\U0010ffff"`},
{"\x04", `"\x04"`, `"\x04"`},
}

type unQuoteTest struct {
in string
out string
}

var unquotetests = []unQuoteTest{
{`""`, ""},
{`"a"`, "a"},
{`"abc"`, "abc"},
{`"☺"`, "☺"},
{`"hello world"`, "hello world"},
{`"\xFF"`, "\xFF"},
{`"\377"`, "\377"},
{`"\u1234"`, "\u1234"},
{`"\U00010111"`, "\U00010111"},
{`"\U0001011111"`, "\U0001011111"},
{`"\a\b\f\n\r\t\v\\\""`, "\a\b\f\n\r\t\v\\\""},
{`"'"`, "'"},
{`"${file("foo")}"`, `${file("foo")}`},
{`"${file("\"foo\"")}"`, `${file("\"foo\"")}`},
{`"echo ${var.region}${element(split(",",var.zones),0)}"`,
`echo ${var.region}${element(split(",",var.zones),0)}`},
{`"${HH\\:mm\\:ss}"`, `${HH\\:mm\\:ss}`},
{`"${\n}"`, `${\n}`},
}

var misquoted = []string{
``,
`"`,
`"a`,
`"'`,
`b"`,
`"\"`,
`"\9"`,
`"\19"`,
`"\129"`,
`'\'`,
`'\9'`,
`'\19'`,
`'\129'`,
`'ab'`,
`"\x1!"`,
`"\U12345678"`,
`"\z"`,
"`",
"`xxx",
"`\"",
`"\'"`,
`'\"'`,
"\"\n\"",
"\"\\n\n\"",
"'\n'",
`"${"`,
`"${foo{}"`,
"\"${foo}\n\"",
}

func TestUnquote(t *testing.T) {
for _, tt := range unquotetests {
if out, err := Unquote(tt.in); err != nil || out != tt.out {
t.Errorf("Unquote(%#q) = %q, %v want %q, nil", tt.in, out, err, tt.out)
}
}

// run the quote tests too, backward
for _, tt := range quotetests {
if in, err := Unquote(tt.out); in != tt.in {
t.Errorf("Unquote(%#q) = %q, %v, want %q, nil", tt.out, in, err, tt.in)
}
}

for _, s := range misquoted {
if out, err := Unquote(s); out != "" || err != ErrSyntax {
t.Errorf("Unquote(%#q) = %q, %v want %q, %v", s, out, err, "", ErrSyntax)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
foo = [
"1",
"2", # comment
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
resource = [{
"foo": {
"bar": {},
"baz": [1, 2, "foo"],
}
}]
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Foo

/* Bar */

/*
/*
Baz
*/

# Another

# Multiple
# Lines

foo = "bar"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Hello
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// This comes from Terraform, as a test
variable "foo" {
default = "bar"
description = "bar"
}

provider "aws" {
access_key = "foo"
secret_key = "bar"
}

provider "do" {
api_key = "${var.foo}"
}

resource "aws_security_group" "firewall" {
count = 5
}

resource aws_instance "web" {
ami = "${var.foo}"
security_groups = [
"foo",
"${aws_security_group.firewall.foo}"
]

network_interface {
device_index = 0
description = "Main network interface"
}
}

resource "aws_instance" "db" {
security_groups = "${aws_security_group.firewall.*.id}"
VPC = "foo"

depends_on = ["aws_instance.web"]
}

output "web_ip" {
value = "${aws_instance.web.private_ip}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo.bar = "baz"
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo = [1, 2, "foo"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo = [1, 2, "foo",]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
foo = "bar"
key = 7
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
default = {
"eu-west-1": "ami-b1cf19c6",
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// This is a test structure for the lexer
foo bar "baz" {
key = 7
foo = "bar"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
foo {
value = 7
"value" = 8
"complex::value" = 9
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
resource "foo" "bar" {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
foo = "bar"
bar = 7
baz = [1,2,3]
foo = -12
bar = 3.14159
foo = true
bar = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package token

import "fmt"

// Pos describes an arbitrary source position
// including the file, line, and column location.
// A Position is valid if the line number is > 0.
type Pos struct {
Filename string // filename, if any
Offset int // offset, starting at 0
Line int // line number, starting at 1
Column int // column number, starting at 1 (character count)
}

// IsValid returns true if the position is valid.
func (p *Pos) IsValid() bool { return p.Line > 0 }

// String returns a string in one of several forms:
//
// file:line:column valid position with file name
// line:column valid position without file name
// file invalid position with file name
// - invalid position without file name
func (p Pos) String() string {
s := p.Filename
if p.IsValid() {
if s != "" {
s += ":"
}
s += fmt.Sprintf("%d:%d", p.Line, p.Column)
}
if s == "" {
s = "-"
}
return s
}

// Before reports whether the position p is before u.
func (p Pos) Before(u Pos) bool {
return u.Offset > p.Offset || u.Line > p.Line
}

// After reports whether the position p is after u.
func (p Pos) After(u Pos) bool {
return u.Offset < p.Offset || u.Line < p.Line
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// Package token defines constants representing the lexical tokens for HCL
// (HashiCorp Configuration Language)
package token

import (
"fmt"
"strconv"
"strings"

hclstrconv "github.com/hashicorp/hcl/hcl/strconv"
)

// Token defines a single HCL token which can be obtained via the Scanner
type Token struct {
Type Type
Pos Pos
Text string
JSON bool
}

// Type is the set of lexical tokens of the HCL (HashiCorp Configuration Language)
type Type int

const (
// Special tokens
ILLEGAL Type = iota
EOF
COMMENT

identifier_beg
IDENT // literals
literal_beg
NUMBER // 12345
FLOAT // 123.45
BOOL // true,false
STRING // "abc"
HEREDOC // <<FOO\nbar\nFOO
literal_end
identifier_end

operator_beg
LBRACK // [
LBRACE // {
COMMA // ,
PERIOD // .

RBRACK // ]
RBRACE // }

ASSIGN // =
ADD // +
SUB // -
operator_end
)

var tokens = [...]string{
ILLEGAL: "ILLEGAL",

EOF: "EOF",
COMMENT: "COMMENT",

IDENT: "IDENT",
NUMBER: "NUMBER",
FLOAT: "FLOAT",
BOOL: "BOOL",
STRING: "STRING",

LBRACK: "LBRACK",
LBRACE: "LBRACE",
COMMA: "COMMA",
PERIOD: "PERIOD",
HEREDOC: "HEREDOC",

RBRACK: "RBRACK",
RBRACE: "RBRACE",

ASSIGN: "ASSIGN",
ADD: "ADD",
SUB: "SUB",
}

// String returns the string corresponding to the token tok.
func (t Type) String() string {
s := ""
if 0 <= t && t < Type(len(tokens)) {
s = tokens[t]
}
if s == "" {
s = "token(" + strconv.Itoa(int(t)) + ")"
}
return s
}

// IsIdentifier returns true for tokens corresponding to identifiers and basic
// type literals; it returns false otherwise.
func (t Type) IsIdentifier() bool { return identifier_beg < t && t < identifier_end }

// IsLiteral returns true for tokens corresponding to basic type literals; it
// returns false otherwise.
func (t Type) IsLiteral() bool { return literal_beg < t && t < literal_end }

// IsOperator returns true for tokens corresponding to operators and
// delimiters; it returns false otherwise.
func (t Type) IsOperator() bool { return operator_beg < t && t < operator_end }

// String returns the token's literal text. Note that this is only
// applicable for certain token types, such as token.IDENT,
// token.STRING, etc..
func (t Token) String() string {
return fmt.Sprintf("%s %s %s", t.Pos.String(), t.Type.String(), t.Text)
}

// Value returns the properly typed value for this token. The type of
// the returned interface{} is guaranteed based on the Type field.
//
// This can only be called for literal types. If it is called for any other
// type, this will panic.
func (t Token) Value() interface{} {
switch t.Type {
case BOOL:
if t.Text == "true" {
return true
} else if t.Text == "false" {
return false
}

panic("unknown bool value: " + t.Text)
case FLOAT:
v, err := strconv.ParseFloat(t.Text, 64)
if err != nil {
panic(err)
}

return float64(v)
case NUMBER:
v, err := strconv.ParseInt(t.Text, 0, 64)
if err != nil {
panic(err)
}

return int64(v)
case IDENT:
return t.Text
case HEREDOC:
return unindentHeredoc(t.Text)
case STRING:
// Determine the Unquote method to use. If it came from JSON,
// then we need to use the built-in unquote since we have to
// escape interpolations there.
f := hclstrconv.Unquote
if t.JSON {
f = strconv.Unquote
}

// This case occurs if json null is used
if t.Text == "" {
return ""
}

v, err := f(t.Text)
if err != nil {
panic(fmt.Sprintf("unquote %s err: %s", t.Text, err))
}

return v
default:
panic(fmt.Sprintf("unimplemented Value for type: %s", t.Type))
}
}

// unindentHeredoc returns the string content of a HEREDOC if it is started with <<
// and the content of a HEREDOC with the hanging indent removed if it is started with
// a <<-, and the terminating line is at least as indented as the least indented line.
func unindentHeredoc(heredoc string) string {
// We need to find the end of the marker
idx := strings.IndexByte(heredoc, '\n')
if idx == -1 {
panic("heredoc doesn't contain newline")
}

unindent := heredoc[2] == '-'

// We can optimize if the heredoc isn't marked for indentation
if !unindent {
return string(heredoc[idx+1 : len(heredoc)-idx+1])
}

// We need to unindent each line based on the indentation level of the marker
lines := strings.Split(string(heredoc[idx+1:len(heredoc)-idx+2]), "\n")
whitespacePrefix := lines[len(lines)-1]

isIndented := true
for _, v := range lines {
if strings.HasPrefix(v, whitespacePrefix) {
continue
}

isIndented = false
break
}

// If all lines are not at least as indented as the terminating mark, return the
// heredoc as is, but trim the leading space from the marker on the final line.
if !isIndented {
return strings.TrimRight(string(heredoc[idx+1:len(heredoc)-idx+1]), " \t")
}

unindentedLines := make([]string, len(lines))
for k, v := range lines {
if k == len(lines)-1 {
unindentedLines[k] = ""
break
}

unindentedLines[k] = strings.TrimPrefix(v, whitespacePrefix)
}

return strings.Join(unindentedLines, "\n")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package token

import (
"reflect"
"testing"
)

func TestTypeString(t *testing.T) {
var tokens = []struct {
tt Type
str string
}{
{ILLEGAL, "ILLEGAL"},
{EOF, "EOF"},
{COMMENT, "COMMENT"},
{IDENT, "IDENT"},
{NUMBER, "NUMBER"},
{FLOAT, "FLOAT"},
{BOOL, "BOOL"},
{STRING, "STRING"},
{HEREDOC, "HEREDOC"},
{LBRACK, "LBRACK"},
{LBRACE, "LBRACE"},
{COMMA, "COMMA"},
{PERIOD, "PERIOD"},
{RBRACK, "RBRACK"},
{RBRACE, "RBRACE"},
{ASSIGN, "ASSIGN"},
{ADD, "ADD"},
{SUB, "SUB"},
}

for _, token := range tokens {
if token.tt.String() != token.str {
t.Errorf("want: %q got:%q\n", token.str, token.tt)
}
}

}

func TestTokenValue(t *testing.T) {
var tokens = []struct {
tt Token
v interface{}
}{
{Token{Type: BOOL, Text: `true`}, true},
{Token{Type: BOOL, Text: `false`}, false},
{Token{Type: FLOAT, Text: `3.14`}, float64(3.14)},
{Token{Type: NUMBER, Text: `42`}, int64(42)},
{Token{Type: IDENT, Text: `foo`}, "foo"},
{Token{Type: STRING, Text: `"foo"`}, "foo"},
{Token{Type: STRING, Text: `"foo\nbar"`}, "foo\nbar"},
{Token{Type: STRING, Text: `"${file("foo")}"`}, `${file("foo")}`},
{
Token{
Type: STRING,
Text: `"${replace("foo", ".", "\\.")}"`,
},
`${replace("foo", ".", "\\.")}`},
{Token{Type: HEREDOC, Text: "<<EOF\nfoo\nbar\nEOF"}, "foo\nbar"},
}

for _, token := range tokens {
if val := token.tt.Value(); !reflect.DeepEqual(val, token.v) {
t.Errorf("want: %v got:%v\n", token.v, val)
}
}

}
19 changes: 19 additions & 0 deletions third-party/github.com/hashicorp/hcl/hcl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package hcl

import (
"io/ioutil"
"path/filepath"
"testing"
)

// This is the directory where our test fixtures are.
const fixtureDir = "./test-fixtures"

func testReadFile(t *testing.T, n string) string {
d, err := ioutil.ReadFile(filepath.Join(fixtureDir, n))
if err != nil {
t.Fatalf("err: %s", err)
}

return string(d)
}
Loading