@@ -0,0 +1,27 @@ | |||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. | |||
[[projects]] | |||
name = "github.com/hashicorp/golang-lru" | |||
packages = [".","simplelru"] | |||
revision = "20f1fb78b0740ba8c3cb143a61e86ba5c8669768" | |||
version = "v0.5.0" | |||
[[projects]] | |||
name = "github.com/hashicorp/hcl" | |||
packages = [".","hcl/ast","hcl/parser","hcl/scanner","hcl/strconv","hcl/token","json/parser","json/scanner","json/token"] | |||
revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241" | |||
version = "v1.0.0" | |||
[[projects]] | |||
branch = "v0" | |||
name = "gopkg.in/opensmtpd.v0" | |||
packages = ["."] | |||
revision = "27c2eae88fcfa0ce76429f3f4a7c3399466ea7d5" | |||
[solve-meta] | |||
analyzer-name = "dep" | |||
analyzer-version = 1 | |||
inputs-digest = "b1622df9f63b5e3728506bf5396befa379916fa781167f07b84c039a865eb4b2" | |||
solver-name = "gps-cdcl" | |||
solver-version = 1 |
@@ -0,0 +1,34 @@ | |||
# Gopkg.toml example | |||
# | |||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md | |||
# for detailed Gopkg.toml documentation. | |||
# | |||
# required = ["github.com/user/thing/cmd/thing"] | |||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] | |||
# | |||
# [[constraint]] | |||
# name = "github.com/user/project" | |||
# version = "1.0.0" | |||
# | |||
# [[constraint]] | |||
# name = "github.com/user/project2" | |||
# branch = "dev" | |||
# source = "github.com/myfork/project2" | |||
# | |||
# [[override]] | |||
# name = "github.com/x/y" | |||
# version = "2.4.0" | |||
[[constraint]] | |||
name = "github.com/hashicorp/golang-lru" | |||
version = "0.5.0" | |||
[[constraint]] | |||
name = "github.com/hashicorp/hcl" | |||
version = "1.0.0" | |||
[[constraint]] | |||
branch = "v0" | |||
name = "gopkg.in/opensmtpd.v0" |
@@ -30,9 +30,9 @@ type TwoQueueCache struct { | |||
size int | |||
recentSize int | |||
recent *simplelru.LRU | |||
frequent *simplelru.LRU | |||
recentEvict *simplelru.LRU | |||
recent simplelru.LRUCache | |||
frequent simplelru.LRUCache | |||
recentEvict simplelru.LRUCache | |||
lock sync.RWMutex | |||
} | |||
@@ -84,7 +84,8 @@ func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCa | |||
return c, nil | |||
} | |||
func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) { | |||
// Get looks up a key's value from the cache. | |||
func (c *TwoQueueCache) Get(key interface{}) (value interface{}, ok bool) { | |||
c.lock.Lock() | |||
defer c.lock.Unlock() | |||
@@ -105,6 +106,7 @@ func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) { | |||
return nil, false | |||
} | |||
// Add adds a value to the cache. | |||
func (c *TwoQueueCache) Add(key, value interface{}) { | |||
c.lock.Lock() | |||
defer c.lock.Unlock() | |||
@@ -160,12 +162,15 @@ func (c *TwoQueueCache) ensureSpace(recentEvict bool) { | |||
c.frequent.RemoveOldest() | |||
} | |||
// Len returns the number of items in the cache. | |||
func (c *TwoQueueCache) Len() int { | |||
c.lock.RLock() | |||
defer c.lock.RUnlock() | |||
return c.recent.Len() + c.frequent.Len() | |||
} | |||
// Keys returns a slice of the keys in the cache. | |||
// The frequently used keys are first in the returned slice. | |||
func (c *TwoQueueCache) Keys() []interface{} { | |||
c.lock.RLock() | |||
defer c.lock.RUnlock() | |||
@@ -174,6 +179,7 @@ func (c *TwoQueueCache) Keys() []interface{} { | |||
return append(k1, k2...) | |||
} | |||
// Remove removes the provided key from the cache. | |||
func (c *TwoQueueCache) Remove(key interface{}) { | |||
c.lock.Lock() | |||
defer c.lock.Unlock() | |||
@@ -188,6 +194,7 @@ func (c *TwoQueueCache) Remove(key interface{}) { | |||
} | |||
} | |||
// Purge is used to completely clear the cache. | |||
func (c *TwoQueueCache) Purge() { | |||
c.lock.Lock() | |||
defer c.lock.Unlock() | |||
@@ -196,13 +203,17 @@ func (c *TwoQueueCache) Purge() { | |||
c.recentEvict.Purge() | |||
} | |||
// Contains is used to check if the cache contains a key | |||
// without updating recency or frequency. | |||
func (c *TwoQueueCache) Contains(key interface{}) bool { | |||
c.lock.RLock() | |||
defer c.lock.RUnlock() | |||
return c.frequent.Contains(key) || c.recent.Contains(key) | |||
} | |||
func (c *TwoQueueCache) Peek(key interface{}) (interface{}, bool) { | |||
// Peek is used to inspect the cache value of a key | |||
// without updating recency or frequency. | |||
func (c *TwoQueueCache) Peek(key interface{}) (value interface{}, ok bool) { | |||
c.lock.RLock() | |||
defer c.lock.RUnlock() | |||
if val, ok := c.frequent.Peek(key); ok { | |||
@@ -18,11 +18,11 @@ type ARCCache struct { | |||
size int // Size is the total capacity of the cache | |||
p int // P is the dynamic preference towards T1 or T2 | |||
t1 *simplelru.LRU // T1 is the LRU for recently accessed items | |||
b1 *simplelru.LRU // B1 is the LRU for evictions from t1 | |||
t1 simplelru.LRUCache // T1 is the LRU for recently accessed items | |||
b1 simplelru.LRUCache // B1 is the LRU for evictions from t1 | |||
t2 *simplelru.LRU // T2 is the LRU for frequently accessed items | |||
b2 *simplelru.LRU // B2 is the LRU for evictions from t2 | |||
t2 simplelru.LRUCache // T2 is the LRU for frequently accessed items | |||
b2 simplelru.LRUCache // B2 is the LRU for evictions from t2 | |||
lock sync.RWMutex | |||
} | |||
@@ -60,11 +60,11 @@ func NewARC(size int) (*ARCCache, error) { | |||
} | |||
// Get looks up a key's value from the cache. | |||
func (c *ARCCache) Get(key interface{}) (interface{}, bool) { | |||
func (c *ARCCache) Get(key interface{}) (value interface{}, ok bool) { | |||
c.lock.Lock() | |||
defer c.lock.Unlock() | |||
// Ff the value is contained in T1 (recent), then | |||
// If the value is contained in T1 (recent), then | |||
// promote it to T2 (frequent) | |||
if val, ok := c.t1.Peek(key); ok { | |||
c.t1.Remove(key) | |||
@@ -153,7 +153,7 @@ func (c *ARCCache) Add(key, value interface{}) { | |||
// Remove from B2 | |||
c.b2.Remove(key) | |||
// Add the key to the frequntly used list | |||
// Add the key to the frequently used list | |||
c.t2.Add(key, value) | |||
return | |||
} | |||
@@ -247,7 +247,7 @@ func (c *ARCCache) Contains(key interface{}) bool { | |||
// Peek is used to inspect the cache value of a key | |||
// without updating recency or frequency. | |||
func (c *ARCCache) Peek(key interface{}) (interface{}, bool) { | |||
func (c *ARCCache) Peek(key interface{}) (value interface{}, ok bool) { | |||
c.lock.RLock() | |||
defer c.lock.RUnlock() | |||
if val, ok := c.t1.Peek(key); ok { | |||
@@ -0,0 +1,21 @@ | |||
// Package lru provides three different LRU caches of varying sophistication. | |||
// | |||
// Cache is a simple LRU cache. It is based on the | |||
// LRU implementation in groupcache: | |||
// https://github.com/golang/groupcache/tree/master/lru | |||
// | |||
// TwoQueueCache tracks frequently used and recently used entries separately. | |||
// This avoids a burst of accesses from taking out frequently used entries, | |||
// at the cost of about 2x computational overhead and some extra bookkeeping. | |||
// | |||
// ARCCache is an adaptive replacement cache. It tracks recent evictions as | |||
// well as recent usage in both the frequent and recent caches. Its | |||
// computational overhead is comparable to TwoQueueCache, but the memory | |||
// overhead is linear with the size of the cache. | |||
// | |||
// ARC has been patented by IBM, so do not use it if that is problematic for | |||
// your program. | |||
// | |||
// All caches in this package take locks while operating, and are therefore | |||
// thread-safe for consumers. | |||
package lru |
@@ -0,0 +1 @@ | |||
module github.com/hashicorp/golang-lru |
@@ -1,6 +1,3 @@ | |||
// This package provides a simple LRU cache. It is based on the | |||
// LRU implementation in groupcache: | |||
// https://github.com/golang/groupcache/tree/master/lru | |||
package lru | |||
import ( | |||
@@ -11,11 +8,11 @@ import ( | |||
// Cache is a thread-safe fixed size LRU cache. | |||
type Cache struct { | |||
lru *simplelru.LRU | |||
lru simplelru.LRUCache | |||
lock sync.RWMutex | |||
} | |||
// New creates an LRU of the given size | |||
// New creates an LRU of the given size. | |||
func New(size int) (*Cache, error) { | |||
return NewWithEvict(size, nil) | |||
} | |||
@@ -33,7 +30,7 @@ func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) | |||
return c, nil | |||
} | |||
// Purge is used to completely clear the cache | |||
// Purge is used to completely clear the cache. | |||
func (c *Cache) Purge() { | |||
c.lock.Lock() | |||
c.lru.Purge() | |||
@@ -41,30 +38,30 @@ func (c *Cache) Purge() { | |||
} | |||
// Add adds a value to the cache. Returns true if an eviction occurred. | |||
func (c *Cache) Add(key, value interface{}) bool { | |||
func (c *Cache) Add(key, value interface{}) (evicted bool) { | |||
c.lock.Lock() | |||
defer c.lock.Unlock() | |||
return c.lru.Add(key, value) | |||
} | |||
// Get looks up a key's value from the cache. | |||
func (c *Cache) Get(key interface{}) (interface{}, bool) { | |||
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) { | |||
c.lock.Lock() | |||
defer c.lock.Unlock() | |||
return c.lru.Get(key) | |||
} | |||
// Check if a key is in the cache, without updating the recent-ness | |||
// or deleting it for being stale. | |||
// Contains checks if a key is in the cache, without updating the | |||
// recent-ness or deleting it for being stale. | |||
func (c *Cache) Contains(key interface{}) bool { | |||
c.lock.RLock() | |||
defer c.lock.RUnlock() | |||
return c.lru.Contains(key) | |||
} | |||
// Returns the key value (or undefined if not found) without updating | |||
// Peek returns the key value (or undefined if not found) without updating | |||
// the "recently used"-ness of the key. | |||
func (c *Cache) Peek(key interface{}) (interface{}, bool) { | |||
func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) { | |||
c.lock.RLock() | |||
defer c.lock.RUnlock() | |||
return c.lru.Peek(key) | |||
@@ -73,16 +70,15 @@ func (c *Cache) Peek(key interface{}) (interface{}, bool) { | |||
// ContainsOrAdd checks if a key is in the cache without updating the | |||
// recent-ness or deleting it for being stale, and if not, adds the value. | |||
// Returns whether found and whether an eviction occurred. | |||
func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evict bool) { | |||
func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) { | |||
c.lock.Lock() | |||
defer c.lock.Unlock() | |||
if c.lru.Contains(key) { | |||
return true, false | |||
} else { | |||
evict := c.lru.Add(key, value) | |||
return false, evict | |||
} | |||
evicted = c.lru.Add(key, value) | |||
return false, evicted | |||
} | |||
// Remove removes the provided key from the cache. | |||
@@ -72,7 +72,7 @@ func TestLRU(t *testing.T) { | |||
if k != v { | |||
t.Fatalf("Evict values not equal (%v!=%v)", k, v) | |||
} | |||
evictCounter += 1 | |||
evictCounter++ | |||
} | |||
l, err := NewWithEvict(128, onEvicted) | |||
if err != nil { | |||
@@ -136,7 +136,7 @@ func TestLRU(t *testing.T) { | |||
func TestLRUAdd(t *testing.T) { | |||
evictCounter := 0 | |||
onEvicted := func(k interface{}, v interface{}) { | |||
evictCounter += 1 | |||
evictCounter++ | |||
} | |||
l, err := NewWithEvict(1, onEvicted) | |||
@@ -36,7 +36,7 @@ func NewLRU(size int, onEvict EvictCallback) (*LRU, error) { | |||
return c, nil | |||
} | |||
// Purge is used to completely clear the cache | |||
// Purge is used to completely clear the cache. | |||
func (c *LRU) Purge() { | |||
for k, v := range c.items { | |||
if c.onEvict != nil { | |||
@@ -48,7 +48,7 @@ func (c *LRU) Purge() { | |||
} | |||
// Add adds a value to the cache. Returns true if an eviction occurred. | |||
func (c *LRU) Add(key, value interface{}) bool { | |||
func (c *LRU) Add(key, value interface{}) (evicted bool) { | |||
// Check for existing item | |||
if ent, ok := c.items[key]; ok { | |||
c.evictList.MoveToFront(ent) | |||
@@ -78,17 +78,18 @@ func (c *LRU) Get(key interface{}) (value interface{}, ok bool) { | |||
return | |||
} | |||
// Check if a key is in the cache, without updating the recent-ness | |||
// Contains checks if a key is in the cache, without updating the recent-ness | |||
// or deleting it for being stale. | |||
func (c *LRU) Contains(key interface{}) (ok bool) { | |||
_, ok = c.items[key] | |||
return ok | |||
} | |||
// Returns the key value (or undefined if not found) without updating | |||
// Peek returns the key value (or undefined if not found) without updating | |||
// the "recently used"-ness of the key. | |||
func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { | |||
if ent, ok := c.items[key]; ok { | |||
var ent *list.Element | |||
if ent, ok = c.items[key]; ok { | |||
return ent.Value.(*entry).value, true | |||
} | |||
return nil, ok | |||
@@ -96,7 +97,7 @@ func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { | |||
// Remove removes the provided key from the cache, returning if the | |||
// key was contained. | |||
func (c *LRU) Remove(key interface{}) bool { | |||
func (c *LRU) Remove(key interface{}) (present bool) { | |||
if ent, ok := c.items[key]; ok { | |||
c.removeElement(ent) | |||
return true | |||
@@ -105,7 +106,7 @@ func (c *LRU) Remove(key interface{}) bool { | |||
} | |||
// RemoveOldest removes the oldest item from the cache. | |||
func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) { | |||
func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) { | |||
ent := c.evictList.Back() | |||
if ent != nil { | |||
c.removeElement(ent) | |||
@@ -116,7 +117,7 @@ func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) { | |||
} | |||
// GetOldest returns the oldest entry | |||
func (c *LRU) GetOldest() (interface{}, interface{}, bool) { | |||
func (c *LRU) GetOldest() (key interface{}, value interface{}, ok bool) { | |||
ent := c.evictList.Back() | |||
if ent != nil { | |||
kv := ent.Value.(*entry) | |||
@@ -0,0 +1,36 @@ | |||
package simplelru | |||
// LRUCache is the interface for simple LRU cache. | |||
type LRUCache interface { | |||
// Adds a value to the cache, returns true if an eviction occurred and | |||
// updates the "recently used"-ness of the key. | |||
Add(key, value interface{}) bool | |||
// Returns key's value from the cache and | |||
// updates the "recently used"-ness of the key. #value, isFound | |||
Get(key interface{}) (value interface{}, ok bool) | |||
// Check if a key exsists in cache without updating the recent-ness. | |||
Contains(key interface{}) (ok bool) | |||
// Returns key's value without updating the "recently used"-ness of the key. | |||
Peek(key interface{}) (value interface{}, ok bool) | |||
// Removes a key from the cache. | |||
Remove(key interface{}) bool | |||
// Removes the oldest entry from cache. | |||
RemoveOldest() (interface{}, interface{}, bool) | |||
// Returns the oldest entry from the cache. #key, value, isFound | |||
GetOldest() (interface{}, interface{}, bool) | |||
// Returns a slice of the keys in the cache, from oldest to newest. | |||
Keys() []interface{} | |||
// Returns the number of items in the cache. | |||
Len() int | |||
// Clear all cache entries | |||
Purge() | |||
} |
@@ -8,7 +8,7 @@ func TestLRU(t *testing.T) { | |||
if k != v { | |||
t.Fatalf("Evict values not equal (%v!=%v)", k, v) | |||
} | |||
evictCounter += 1 | |||
evictCounter++ | |||
} | |||
l, err := NewLRU(128, onEvicted) | |||
if err != nil { | |||
@@ -112,7 +112,7 @@ func TestLRU_GetOldest_RemoveOldest(t *testing.T) { | |||
func TestLRU_Add(t *testing.T) { | |||
evictCounter := 0 | |||
onEvicted := func(k interface{}, v interface{}) { | |||
evictCounter += 1 | |||
evictCounter++ | |||
} | |||
l, err := NewLRU(1, onEvicted) | |||
@@ -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 | |||
- ... |
@@ -0,0 +1,9 @@ | |||
y.output | |||
# ignore intellij files | |||
.idea | |||
*.iml | |||
*.ipr | |||
*.iws | |||
*.test |
@@ -0,0 +1,13 @@ | |||
sudo: false | |||
language: go | |||
go: | |||
- 1.x | |||
- tip | |||
branches: | |||
only: | |||
- master | |||
script: make test |
@@ -0,0 +1,354 @@ | |||
Mozilla Public License, version 2.0 | |||
1. Definitions | |||
1.1. โContributorโ | |||
means each individual or legal entity that creates, contributes to the | |||
creation of, or owns Covered Software. | |||
1.2. โContributor Versionโ | |||
means the combination of the Contributions of others (if any) used by a | |||
Contributor and that particular Contributorโs Contribution. | |||
1.3. โContributionโ | |||
means Covered Software of a particular Contributor. | |||
1.4. โCovered Softwareโ | |||
means Source Code Form to which the initial Contributor has attached the | |||
notice in Exhibit A, the Executable Form of such Source Code Form, and | |||
Modifications of such Source Code Form, in each case including portions | |||
thereof. | |||
1.5. โIncompatible With Secondary Licensesโ | |||
means | |||
a. that the initial Contributor has attached the notice described in | |||
Exhibit B to the Covered Software; or | |||
b. that the Covered Software was made available under the terms of version | |||
1.1 or earlier of the License, but not also under the terms of a | |||
Secondary License. | |||
1.6. โExecutable Formโ | |||
means any form of the work other than Source Code Form. | |||
1.7. โLarger Workโ | |||
means a work that combines Covered Software with other material, in a separate | |||
file or files, that is not Covered Software. | |||
1.8. โLicenseโ | |||
means this document. | |||
1.9. โLicensableโ | |||
means having the right to grant, to the maximum extent possible, whether at the | |||
time of the initial grant or subsequently, any and all of the rights conveyed by | |||
this License. | |||
1.10. โModificationsโ | |||
means any of the following: | |||
a. any file in Source Code Form that results from an addition to, deletion | |||
from, or modification of the contents of Covered Software; or | |||
b. any new file in Source Code Form that contains any Covered Software. | |||
1.11. โPatent Claimsโ of a Contributor | |||
means any patent claim(s), including without limitation, method, process, | |||
and apparatus claims, in any patent Licensable by such Contributor that | |||
would be infringed, but for the grant of the License, by the making, | |||
using, selling, offering for sale, having made, import, or transfer of | |||
either its Contributions or its Contributor Version. | |||
1.12. โSecondary Licenseโ | |||
means either the GNU General Public License, Version 2.0, the GNU Lesser | |||
General Public License, Version 2.1, the GNU Affero General Public | |||
License, Version 3.0, or any later versions of those licenses. | |||
1.13. โSource Code Formโ | |||
means the form of the work preferred for making modifications. | |||
1.14. โYouโ (or โYourโ) | |||
means an individual or a legal entity exercising rights under this | |||
License. For legal entities, โYouโ includes any entity that controls, is | |||
controlled by, or is under common control with You. For purposes of this | |||
definition, โcontrolโ means (a) the power, direct or indirect, to cause | |||
the direction or management of such entity, whether by contract or | |||
otherwise, or (b) ownership of more than fifty percent (50%) of the | |||
outstanding shares or beneficial ownership of such entity. | |||
2. License Grants and Conditions | |||
2.1. Grants | |||
Each Contributor hereby grants You a world-wide, royalty-free, | |||
non-exclusive license: | |||
a. under intellectual property rights (other than patent or trademark) | |||
Licensable by such Contributor to use, reproduce, make available, | |||
modify, display, perform, distribute, and otherwise exploit its | |||
Contributions, either on an unmodified basis, with Modifications, or as | |||
part of a Larger Work; and | |||
b. under Patent Claims of such Contributor to make, use, sell, offer for | |||
sale, have made, import, and otherwise transfer either its Contributions | |||
or its Contributor Version. | |||
2.2. Effective Date | |||
The licenses granted in Section 2.1 with respect to any Contribution become | |||
effective for each Contribution on the date the Contributor first distributes | |||
such Contribution. | |||
2.3. Limitations on Grant Scope | |||
The licenses granted in this Section 2 are the only rights granted under this | |||
License. No additional rights or licenses will be implied from the distribution | |||
or licensing of Covered Software under this License. Notwithstanding Section | |||
2.1(b) above, no patent license is granted by a Contributor: | |||
a. for any code that a Contributor has removed from Covered Software; or | |||
b. for infringements caused by: (i) Your and any other third partyโs | |||
modifications of Covered Software, or (ii) the combination of its | |||
Contributions with other software (except as part of its Contributor | |||
Version); or | |||
c. under Patent Claims infringed by Covered Software in the absence of its | |||
Contributions. | |||
This License does not grant any rights in the trademarks, service marks, or | |||
logos of any Contributor (except as may be necessary to comply with the | |||
notice requirements in Section 3.4). | |||
2.4. Subsequent Licenses | |||
No Contributor makes additional grants as a result of Your choice to | |||
distribute the Covered Software under a subsequent version of this License | |||
(see Section 10.2) or under the terms of a Secondary License (if permitted | |||
under the terms of Section 3.3). | |||
2.5. Representation | |||
Each Contributor represents that the Contributor believes its Contributions | |||
are its original creation(s) or it has sufficient rights to grant the | |||
rights to its Contributions conveyed by this License. | |||
2.6. Fair Use | |||
This License is not intended to limit any rights You have under applicable | |||
copyright doctrines of fair use, fair dealing, or other equivalents. | |||
2.7. Conditions | |||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in | |||
Section 2.1. | |||
3. Responsibilities | |||
3.1. Distribution of Source Form | |||
All distribution of Covered Software in Source Code Form, including any | |||
Modifications that You create or to which You contribute, must be under the | |||
terms of this License. You must inform recipients that the Source Code Form | |||
of the Covered Software is governed by the terms of this License, and how | |||
they can obtain a copy of this License. You may not attempt to alter or | |||
restrict the recipientsโ rights in the Source Code Form. | |||
3.2. Distribution of Executable Form | |||
If You distribute Covered Software in Executable Form then: | |||
a. such Covered Software must also be made available in Source Code Form, | |||
as described in Section 3.1, and You must inform recipients of the | |||
Executable Form how they can obtain a copy of such Source Code Form by | |||
reasonable means in a timely manner, at a charge no more than the cost | |||
of distribution to the recipient; and | |||
b. You may distribute such Executable Form under the terms of this License, | |||
or sublicense it under different terms, provided that the license for | |||
the Executable Form does not attempt to limit or alter the recipientsโ | |||
rights in the Source Code Form under this License. | |||
3.3. Distribution of a Larger Work | |||
You may create and distribute a Larger Work under terms of Your choice, | |||
provided that You also comply with the requirements of this License for the | |||
Covered Software. If the Larger Work is a combination of Covered Software | |||
with a work governed by one or more Secondary Licenses, and the Covered | |||
Software is not Incompatible With Secondary Licenses, this License permits | |||
You to additionally distribute such Covered Software under the terms of | |||
such Secondary License(s), so that the recipient of the Larger Work may, at | |||
their option, further distribute the Covered Software under the terms of | |||
either this License or such Secondary License(s). | |||
3.4. Notices | |||
You may not remove or alter the substance of any license notices (including | |||
copyright notices, patent notices, disclaimers of warranty, or limitations | |||
of liability) contained within the Source Code Form of the Covered | |||
Software, except that You may alter any license notices to the extent | |||
required to remedy known factual inaccuracies. | |||
3.5. Application of Additional Terms | |||
You may choose to offer, and to charge a fee for, warranty, support, | |||
indemnity or liability obligations to one or more recipients of Covered | |||
Software. However, You may do so only on Your own behalf, and not on behalf | |||
of any Contributor. You must make it absolutely clear that any such | |||
warranty, support, indemnity, or liability obligation is offered by You | |||
alone, and You hereby agree to indemnify every Contributor for any | |||
liability incurred by such Contributor as a result of warranty, support, | |||
indemnity or liability terms You offer. You may include additional | |||
disclaimers of warranty and limitations of liability specific to any | |||
jurisdiction. | |||
4. Inability to Comply Due to Statute or Regulation | |||
If it is impossible for You to comply with any of the terms of this License | |||
with respect to some or all of the Covered Software due to statute, judicial | |||
order, or regulation then You must: (a) comply with the terms of this License | |||
to the maximum extent possible; and (b) describe the limitations and the code | |||
they affect. Such description must be placed in a text file included with all | |||
distributions of the Covered Software under this License. Except to the | |||
extent prohibited by statute or regulation, such description must be | |||
sufficiently detailed for a recipient of ordinary skill to be able to | |||
understand it. | |||
5. Termination | |||
5.1. The rights granted under this License will terminate automatically if You | |||
fail to comply with any of its terms. However, if You become compliant, | |||
then the rights granted under this License from a particular Contributor | |||
are reinstated (a) provisionally, unless and until such Contributor | |||
explicitly and finally terminates Your grants, and (b) on an ongoing basis, | |||
if such Contributor fails to notify You of the non-compliance by some | |||
reasonable means prior to 60 days after You have come back into compliance. | |||
Moreover, Your grants from a particular Contributor are reinstated on an | |||
ongoing basis if such Contributor notifies You of the non-compliance by | |||
some reasonable means, this is the first time You have received notice of | |||
non-compliance with this License from such Contributor, and You become | |||
compliant prior to 30 days after Your receipt of the notice. | |||
5.2. If You initiate litigation against any entity by asserting a patent | |||
infringement claim (excluding declaratory judgment actions, counter-claims, | |||
and cross-claims) alleging that a Contributor Version directly or | |||
indirectly infringes any patent, then the rights granted to You by any and | |||
all Contributors for the Covered Software under Section 2.1 of this License | |||
shall terminate. | |||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user | |||
license agreements (excluding distributors and resellers) which have been | |||
validly granted by You or Your distributors under this License prior to | |||
termination shall survive termination. | |||
6. Disclaimer of Warranty | |||
Covered Software is provided under this License on an โas isโ basis, without | |||
warranty of any kind, either expressed, implied, or statutory, including, | |||
without limitation, warranties that the Covered Software is free of defects, | |||
merchantable, fit for a particular purpose or non-infringing. The entire | |||
risk as to the quality and performance of the Covered Software is with You. | |||
Should any Covered Software prove defective in any respect, You (not any | |||
Contributor) assume the cost of any necessary servicing, repair, or | |||
correction. This disclaimer of warranty constitutes an essential part of this | |||
License. No use of any Covered Software is authorized under this License | |||
except under this disclaimer. | |||
7. Limitation of Liability | |||
Under no circumstances and under no legal theory, whether tort (including | |||
negligence), contract, or otherwise, shall any Contributor, or anyone who | |||
distributes Covered Software as permitted above, be liable to You for any | |||
direct, indirect, special, incidental, or consequential damages of any | |||
character including, without limitation, damages for lost profits, loss of | |||
goodwill, work stoppage, computer failure or malfunction, or any and all | |||
other commercial damages or losses, even if such party shall have been | |||
informed of the possibility of such damages. This limitation of liability | |||
shall not apply to liability for death or personal injury resulting from such | |||
partyโs negligence to the extent applicable law prohibits such limitation. | |||
Some jurisdictions do not allow the exclusion or limitation of incidental or | |||
consequential damages, so this exclusion and limitation may not apply to You. | |||
8. Litigation | |||
Any litigation relating to this License may be brought only in the courts of | |||
a jurisdiction where the defendant maintains its principal place of business | |||
and such litigation shall be governed by laws of that jurisdiction, without | |||
reference to its conflict-of-law provisions. Nothing in this Section shall | |||
prevent a partyโs ability to bring cross-claims or counter-claims. | |||
9. Miscellaneous | |||
This License represents the complete agreement concerning the subject matter | |||
hereof. If any provision of this License is held to be unenforceable, such | |||
provision shall be reformed only to the extent necessary to make it | |||
enforceable. Any law or regulation which provides that the language of a | |||
contract shall be construed against the drafter shall not be used to construe | |||
this License against a Contributor. | |||
10. Versions of the License | |||
10.1. New Versions | |||
Mozilla Foundation is the license steward. Except as provided in Section | |||
10.3, no one other than the license steward has the right to modify or | |||
publish new versions of this License. Each version will be given a | |||
distinguishing version number. | |||
10.2. Effect of New Versions | |||
You may distribute the Covered Software under the terms of the version of | |||
the License under which You originally received the Covered Software, or | |||
under the terms of any subsequent version published by the license | |||
steward. | |||
10.3. Modified Versions | |||
If you create software not governed by this License, and you want to | |||
create a new license for such software, you may create and use a modified | |||
version of this License if you rename the license and remove any | |||
references to the name of the license steward (except to note that such | |||
modified license differs from this License). | |||
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses | |||
If You choose to distribute Source Code Form that is Incompatible With | |||
Secondary Licenses under the terms of this version of the License, the | |||
notice described in Exhibit B of this License must be attached. | |||
Exhibit A - Source Code Form License Notice | |||
This Source Code Form is subject to the | |||
terms of the Mozilla Public License, v. | |||
2.0. If a copy of the MPL was not | |||
distributed with this file, You can | |||
obtain one at | |||
http://mozilla.org/MPL/2.0/. | |||
If it is not possible or desirable to put the notice in a particular file, then | |||
You may include the notice in a location (such as a LICENSE file in a relevant | |||
directory) where a recipient would be likely to look for such a notice. | |||
You may add additional accurate notices of copyright ownership. | |||
Exhibit B - โIncompatible With Secondary Licensesโ Notice | |||
This Source Code Form is โIncompatible | |||
With Secondary Licensesโ, as defined by | |||
the Mozilla Public License, v. 2.0. | |||
@@ -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 |
@@ -0,0 +1,125 @@ | |||
# HCL | |||
[](https://godoc.org/github.com/hashicorp/hcl) [](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. |
@@ -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 ./... |
@@ -0,0 +1,729 @@ | |||
package hcl | |||
import ( | |||
"errors" | |||
"fmt" | |||
"reflect" | |||
"sort" | |||
"strconv" | |||
"strings" | |||
"github.com/hashicorp/hcl/hcl/ast" | |||
"github.com/hashicorp/hcl/hcl/parser" | |||
"github.com/hashicorp/hcl/hcl/token" | |||
) | |||
// This is the tag to use with structures to have settings for HCL | |||
const tagName = "hcl" | |||
var ( | |||
// nodeType holds a reference to the type of ast.Node | |||
nodeType reflect.Type = findNodeType() | |||
) | |||
// Unmarshal accepts a byte slice as input and writes the | |||
// data to the value pointed to by v. | |||
func Unmarshal(bs []byte, v interface{}) error { | |||
root, err := parse(bs) | |||
if err != nil { | |||
return err | |||
} | |||
return DecodeObject(v, root) | |||
} | |||
// Decode reads the given input and decodes it into the structure | |||
// given by `out`. | |||
func Decode(out interface{}, in string) error { | |||
obj, err := Parse(in) | |||
if err != nil { | |||
return err | |||
} | |||
return DecodeObject(out, obj) | |||
} | |||
// DecodeObject is a lower-level version of Decode. It decodes a | |||
// raw Object into the given output. | |||
func DecodeObject(out interface{}, n ast.Node) error { | |||
val := reflect.ValueOf(out) | |||
if val.Kind() != reflect.Ptr { | |||
return errors.New("result must be a pointer") | |||
} | |||
// If we have the file, we really decode the root node | |||
if f, ok := n.(*ast.File); ok { | |||
n = f.Node | |||
} | |||
var d decoder | |||
return d.decode("root", n, val.Elem()) | |||
} | |||
type decoder struct { | |||
stack []reflect.Kind | |||
} | |||
func (d *decoder) decode(name string, node ast.Node, result reflect.Value) error { | |||
k := result | |||
// If we have an interface with a valid value, we use that | |||
// for the check. | |||
if result.Kind() == reflect.Interface { | |||
elem := result.Elem() | |||
if elem.IsValid() { | |||
k = elem | |||
} | |||
} | |||
// Push current onto stack unless it is an interface. | |||
if k.Kind() != reflect.Interface { | |||
d.stack = append(d.stack, k.Kind()) | |||
// Schedule a pop | |||
defer func() { | |||
d.stack = d.stack[:len(d.stack)-1] | |||
}() | |||
} | |||
switch k.Kind() { | |||
case reflect.Bool: | |||
return d.decodeBool(name, node, result) | |||
case reflect.Float32, reflect.Float64: | |||
return d.decodeFloat(name, node, result) | |||
case reflect.Int, reflect.Int32, reflect.Int64: | |||
return d.decodeInt(name, node, result) | |||
case reflect.Interface: | |||
// When we see an interface, we make our own thing | |||
return d.decodeInterface(name, node, result) | |||
case reflect.Map: | |||
return d.decodeMap(name, node, result) | |||
case reflect.Ptr: | |||
return d.decodePtr(name, node, result) | |||
case reflect.Slice: | |||
return d.decodeSlice(name, node, result) | |||
case reflect.String: | |||
return d.decodeString(name, node, result) | |||
case reflect.Struct: | |||
return d.decodeStruct(name, node, result) | |||
default: | |||
return &parser.PosError{ | |||
Pos: node.Pos(), | |||
Err: fmt.Errorf("%s: unknown kind to decode into: %s", name, k.Kind()), | |||
} | |||
} | |||
} | |||
func (d *decoder) decodeBool(name string, node ast.Node, result reflect.Value) error { | |||
switch n := node.(type) { | |||
case *ast.LiteralType: | |||
if n.Token.Type == token.BOOL { | |||
v, err := strconv.ParseBool(n.Token.Text) | |||
if err != nil { | |||
return err | |||
} | |||
result.Set(reflect.ValueOf(v)) | |||
return nil | |||
} | |||
} | |||
return &parser.PosError{ | |||
Pos: node.Pos(), | |||
Err: fmt.Errorf("%s: unknown type %T", name, node), | |||
} | |||
} | |||
func (d *decoder) decodeFloat(name string, node ast.Node, result reflect.Value) error { | |||
switch n := node.(type) { | |||
case *ast.LiteralType: | |||
if n.Token.Type == token.FLOAT || n.Token.Type == token.NUMBER { | |||
v, err := strconv.ParseFloat(n.Token.Text, 64) | |||
if err != nil { | |||
return err | |||
} | |||
result.Set(reflect.ValueOf(v).Convert(result.Type())) | |||
return nil | |||
} | |||
} | |||
return &parser.PosError{ | |||
Pos: node.Pos(), | |||
Err: fmt.Errorf("%s: unknown type %T", name, node), | |||
} | |||
} | |||
func (d *decoder) decodeInt(name string, node ast.Node, result reflect.Value) error { | |||
switch n := node.(type) { | |||
case *ast.LiteralType: | |||
switch n.Token.Type { | |||
case token.NUMBER: | |||
v, err := strconv.ParseInt(n.Token.Text, 0, 0) | |||
if err != nil { | |||
return err | |||
} | |||
if result.Kind() == reflect.Interface { | |||
result.Set(reflect.ValueOf(int(v))) | |||
} else { | |||
result.SetInt(v) | |||
} | |||
return nil | |||
case token.STRING: | |||
v, err := strconv.ParseInt(n.Token.Value().(string), 0, 0) | |||
if err != nil { | |||
return err | |||
} | |||
if result.Kind() == reflect.Interface { | |||
result.Set(reflect.ValueOf(int(v))) | |||
} else { | |||
result.SetInt(v) | |||
} | |||
return nil | |||
} | |||
} | |||
return &parser.PosError{ | |||
Pos: node.Pos(), | |||
Err: fmt.Errorf("%s: unknown type %T", name, node), | |||
} | |||
} | |||
func (d *decoder) decodeInterface(name string, node ast.Node, result reflect.Value) error { | |||
// When we see an ast.Node, we retain the value to enable deferred decoding. | |||
// Very useful in situations where we want to preserve ast.Node information | |||
// like Pos | |||
if result.Type() == nodeType && result.CanSet() { | |||
result.Set(reflect.ValueOf(node)) | |||
return nil | |||
} | |||
var set reflect.Value | |||
redecode := true | |||
// For testing types, ObjectType should just be treated as a list. We | |||
// set this to a temporary var because we want to pass in the real node. | |||
testNode := node | |||
if ot, ok := node.(*ast.ObjectType); ok { | |||
testNode = ot.List | |||
} | |||
switch n := testNode.(type) { | |||
case *ast.ObjectList: | |||
// If we're at the root or we're directly within a slice, then we | |||
// decode objects into map[string]interface{}, otherwise we decode | |||
// them into lists. | |||
if len(d.stack) == 0 || d.stack[len(d.stack)-1] == reflect.Slice { | |||
var temp map[string]interface{} | |||
tempVal := reflect.ValueOf(temp) | |||
result := reflect.MakeMap( | |||
reflect.MapOf( | |||
reflect.TypeOf(""), | |||
tempVal.Type().Elem())) | |||
set = result | |||
} else { | |||
var temp []map[string]interface{} | |||
tempVal := reflect.ValueOf(temp) | |||
result := reflect.MakeSlice( | |||
reflect.SliceOf(tempVal.Type().Elem()), 0, len(n.Items)) | |||
set = result | |||
} | |||
case *ast.ObjectType: | |||
// If we're at the root or we're directly within a slice, then we | |||
// decode objects into map[string]interface{}, otherwise we decode | |||
// them into lists. | |||
if len(d.stack) == 0 || d.stack[len(d.stack)-1] == reflect.Slice { | |||
var temp map[string]interface{} | |||
tempVal := reflect.ValueOf(temp) | |||
result := reflect.MakeMap( | |||
reflect.MapOf( | |||
reflect.TypeOf(""), | |||
tempVal.Type().Elem())) | |||
set = result | |||
} else { | |||
var temp []map[string]interface{} | |||
tempVal := reflect.ValueOf(temp) | |||
result := reflect.MakeSlice( | |||
reflect.SliceOf(tempVal.Type().Elem()), 0, 1) | |||
set = result | |||
} | |||
case *ast.ListType: | |||
var temp []interface{} | |||
tempVal := reflect.ValueOf(temp) | |||
result := reflect.MakeSlice( | |||
reflect.SliceOf(tempVal.Type().Elem()), 0, 0) | |||
set = result | |||
case *ast.LiteralType: | |||
switch n.Token.Type { | |||
case token.BOOL: | |||
var result bool | |||
set = reflect.Indirect(reflect.New(reflect.TypeOf(result))) | |||
case token.FLOAT: | |||
var result float64 | |||
set = reflect.Indirect(reflect.New(reflect.TypeOf(result))) | |||
case token.NUMBER: | |||
var result int | |||
set = reflect.Indirect(reflect.New(reflect.TypeOf(result))) | |||
case token.STRING, token.HEREDOC: | |||
set = reflect.Indirect(reflect.New(reflect.TypeOf(""))) | |||
default: | |||
return &parser.PosError{ | |||
Pos: node.Pos(), | |||
Err: fmt.Errorf("%s: cannot decode into interface: %T", name, node), | |||
} | |||
} | |||
default: | |||
return fmt.Errorf( | |||
"%s: cannot decode into interface: %T", | |||
name, node) | |||
} | |||
// Set the result to what its supposed to be, then reset | |||
// result so we don't reflect into this method anymore. | |||
result.Set(set) | |||
if redecode { | |||
// Revisit the node so that we can use the newly instantiated | |||
// thing and populate it. | |||
if err := d.decode(name, node, result); err != nil { | |||
return err | |||
} | |||
} | |||
return nil | |||
} | |||
func (d *decoder) decodeMap(name string, node ast.Node, result reflect.Value) error { | |||
if item, ok := node.(*ast.ObjectItem); ok { | |||
node = &ast.ObjectList{Items: []*ast.ObjectItem{item}} | |||
} | |||
if ot, ok := node.(*ast.ObjectType); ok { | |||
node = ot.List | |||
} | |||
n, ok := node.(*ast.ObjectList) | |||
if !ok { | |||
return &parser.PosError{ | |||
Pos: node.Pos(), | |||
Err: fmt.Errorf("%s: not an object type for map (%T)", name, node), | |||
} | |||
} | |||
// If we have an interface, then we can address the interface, | |||
// but not the slice itself, so get the element but set the interface | |||
set := result | |||
if result.Kind() == reflect.Interface { | |||
result = result.Elem() | |||
} | |||
resultType := result.Type() | |||
resultElemType := resultType.Elem() | |||
resultKeyType := resultType.Key() | |||
if resultKeyType.Kind() != reflect.String { | |||
return &parser.PosError{ | |||
Pos: node.Pos(), | |||
Err: fmt.Errorf("%s: map must have string keys", name), | |||
} | |||
} | |||
// Make a map if it is nil | |||
resultMap := result | |||
if result.IsNil() { | |||
resultMap = reflect.MakeMap( | |||
reflect.MapOf(resultKeyType, resultElemType)) | |||
} | |||
// Go through each element and decode it. | |||
done := make(map[string]struct{}) | |||
for _, item := range n.Items { | |||
if item.Val == nil { | |||
continue | |||
} | |||
// github.com/hashicorp/terraform/issue/5740 | |||
if len(item.Keys) == 0 { | |||
return &parser.PosError{ | |||
Pos: node.Pos(), | |||
Err: fmt.Errorf("%s: map must have string keys", name), | |||
} | |||
} | |||
// Get the key we're dealing with, which is the first item | |||
keyStr := item.Keys[0].Token.Value().(string) | |||
// If we've already processed this key, then ignore it | |||
if _, ok := done[keyStr]; ok { | |||
continue | |||
} | |||
// Determine the value. If we have more than one key, then we | |||
// get the objectlist of only these keys. | |||
itemVal := item.Val | |||
if len(item.Keys) > 1 { | |||
itemVal = n.Filter(keyStr) | |||
done[keyStr] = struct{}{} | |||
} | |||
// Make the field name | |||
fieldName := fmt.Sprintf("%s.%s", name, keyStr) | |||
// Get the key/value as reflection values | |||
key := reflect.ValueOf(keyStr) | |||
val := reflect.Indirect(reflect.New(resultElemType)) | |||
// If we have a pre-existing value in the map, use that | |||
oldVal := resultMap.MapIndex(key) | |||
if oldVal.IsValid() { | |||
val.Set(oldVal) | |||
} | |||
// Decode! | |||
if err := d.decode(fieldName, itemVal, val); err != nil { | |||
return err | |||
} | |||
// Set the value on the map | |||
resultMap.SetMapIndex(key, val) | |||
} | |||
// Set the final map if we can | |||
set.Set(resultMap) | |||
return nil | |||
} | |||
func (d *decoder) decodePtr(name string, node ast.Node, result reflect.Value) error { | |||
// Create an element of the concrete (non pointer) type and decode | |||
// into that. Then set the value of the pointer to this type. | |||
resultType := result.Type() | |||
resultElemType := resultType.Elem() | |||
val := reflect.New(resultElemType) | |||
if err := d.decode(name, node, reflect.Indirect(val)); err != nil { | |||
return err | |||
} | |||
result.Set(val) | |||
return nil | |||
} | |||
func (d *decoder) decodeSlice(name string, node ast.Node, result reflect.Value) error { | |||
// If we have an interface, then we can address the interface, | |||
// but not the slice itself, so get the element but set the interface | |||
set := result | |||
if result.Kind() == reflect.Interface { | |||
result = result.Elem() | |||
} | |||
// Create the slice if it isn't nil | |||
resultType := result.Type() | |||
resultElemType := resultType.Elem() | |||
if result.IsNil() { | |||
resultSliceType := reflect.SliceOf(resultElemType) | |||
result = reflect.MakeSlice( | |||
resultSliceType, 0, 0) | |||
} | |||
// Figure out the items we'll be copying into the slice | |||
var items []ast.Node | |||
switch n := node.(type) { | |||
case *ast.ObjectList: | |||
items = make([]ast.Node, len(n.Items)) | |||
for i, item := range n.Items { | |||
items[i] = item | |||
} | |||
case *ast.ObjectType: | |||
items = []ast.Node{n} | |||
case *ast.ListType: | |||
items = n.List | |||
default: | |||
return &parser.PosError{ | |||
Pos: node.Pos(), | |||
Err: fmt.Errorf("unknown slice type: %T", node), | |||
} | |||
} | |||
for i, item := range items { | |||
fieldName := fmt.Sprintf("%s[%d]", name, i) | |||
// Decode | |||
val := reflect.Indirect(reflect.New(resultElemType)) | |||
// if item is an object that was decoded from ambiguous JSON and | |||
// flattened, make sure it's expanded if it needs to decode into a | |||
// defined structure. | |||
item := expandObject(item, val) | |||
if err := d.decode(fieldName, item, val); err != nil { | |||
return err | |||
} | |||
// Append it onto the slice | |||
result = reflect.Append(result, val) | |||
} | |||
set.Set(result) | |||
return nil | |||
} | |||
// expandObject detects if an ambiguous JSON object was flattened to a List which | |||
// should be decoded into a struct, and expands the ast to properly deocode. | |||
func expandObject(node ast.Node, result reflect.Value) ast.Node { | |||
item, ok := node.(*ast.ObjectItem) | |||
if !ok { | |||
return node | |||
} | |||
elemType := result.Type() | |||
// our target type must be a struct | |||
switch elemType.Kind() { | |||
case reflect.Ptr: | |||
switch elemType.Elem().Kind() { | |||
case reflect.Struct: | |||
//OK | |||
default: | |||
return node | |||
} | |||
case reflect.Struct: | |||
//OK | |||
default: | |||
return node | |||
} | |||
// A list value will have a key and field name. If it had more fields, | |||
// it wouldn't have been flattened. | |||
if len(item.Keys) != 2 { | |||
return node | |||
} | |||
keyToken := item.Keys[0].Token | |||
item.Keys = item.Keys[1:] | |||
// we need to un-flatten the ast enough to decode | |||
newNode := &ast.ObjectItem{ | |||
Keys: []*ast.ObjectKey{ | |||
&ast.ObjectKey{ | |||
Token: keyToken, | |||
}, | |||
}, | |||
Val: &ast.ObjectType{ | |||
List: &ast.ObjectList{ | |||
Items: []*ast.ObjectItem{item}, | |||
}, | |||
}, | |||
} | |||
return newNode | |||
} | |||
func (d *decoder) decodeString(name string, node ast.Node, result reflect.Value) error { | |||
switch n := node.(type) { | |||
case *ast.LiteralType: | |||
switch n.Token.Type { | |||
case token.NUMBER: | |||
result.Set(reflect.ValueOf(n.Token.Text).Convert(result.Type())) | |||
return nil | |||
case token.STRING, token.HEREDOC: | |||
result.Set(reflect.ValueOf(n.Token.Value()).Convert(result.Type())) | |||
return nil | |||
} | |||
} | |||
return &parser.PosError{ | |||
Pos: node.Pos(), | |||
Err: fmt.Errorf("%s: unknown type for string %T", name, node), | |||
} | |||
} | |||
func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value) error { | |||
var item *ast.ObjectItem | |||
if it, ok := node.(*ast.ObjectItem); ok { | |||
item = it | |||
node = it.Val | |||
} | |||
if ot, ok := node.(*ast.ObjectType); ok { | |||
node = ot.List | |||
} | |||
// Handle the special case where the object itself is a literal. Previously | |||
// the yacc parser would always ensure top-level elements were arrays. The new | |||
// parser does not make the same guarantees, thus we need to convert any | |||
// top-level literal elements into a list. | |||
if _, ok := node.(*ast.LiteralType); ok && item != nil { | |||
node = &ast.ObjectList{Items: []*ast.ObjectItem{item}} | |||
} | |||
list, ok := node.(*ast.ObjectList) | |||
if !ok { | |||
return &parser.PosError{ | |||
Pos: node.Pos(), | |||
Err: fmt.Errorf("%s: not an object type for struct (%T)", name, node), | |||
} | |||
} | |||
// This slice will keep track of all the structs we'll be decoding. | |||
// There can be more than one struct if there are embedded structs | |||
// that are squashed. | |||
structs := make([]reflect.Value, 1, 5) | |||
structs[0] = result | |||
// Compile the list of all the fields that we're going to be decoding | |||
// from all the structs. | |||
type field struct { | |||
field reflect.StructField | |||
val reflect.Value | |||
} | |||
fields := []field{} | |||
for len(structs) > 0 { | |||
structVal := structs[0] | |||
structs = structs[1:] | |||
structType := structVal.Type() | |||
for i := 0; i < structType.NumField(); i++ { | |||
fieldType := structType.Field(i) | |||
tagParts := strings.Split(fieldType.Tag.Get(tagName), ",") | |||
// Ignore fields with tag name "-" | |||
if tagParts[0] == "-" { | |||
continue | |||
} | |||
if fieldType.Anonymous { | |||
fieldKind := fieldType.Type.Kind() | |||
if fieldKind != reflect.Struct { | |||
return &parser.PosError{ | |||
Pos: node.Pos(), | |||
Err: fmt.Errorf("%s: unsupported type to struct: %s", | |||
fieldType.Name, fieldKind), | |||
} | |||
} | |||
// We have an embedded field. We "squash" the fields down | |||
// if specified in the tag. | |||
squash := false | |||
for _, tag := range tagParts[1:] { | |||
if tag == "squash" { | |||
squash = true | |||
break | |||
} | |||
} | |||
if squash { | |||
structs = append( | |||
structs, result.FieldByName(fieldType.Name)) | |||
continue | |||
} | |||
} | |||
// Normal struct field, store it away | |||
fields = append(fields, field{fieldType, structVal.Field(i)}) | |||
} | |||
} | |||
usedKeys := make(map[string]struct{}) | |||
decodedFields := make([]string, 0, len(fields)) | |||
decodedFieldsVal := make([]reflect.Value, 0) | |||
unusedKeysVal := make([]reflect.Value, 0) | |||
for _, f := range fields { | |||
field, fieldValue := f.field, f.val | |||
if !fieldValue.IsValid() { | |||
// This should never happen | |||
panic("field is not valid") | |||
} | |||
// If we can't set the field, then it is unexported or something, | |||
// and we just continue onwards. | |||
if !fieldValue.CanSet() { | |||
continue | |||
} | |||
fieldName := field.Name | |||
tagValue := field.Tag.Get(tagName) | |||
tagParts := strings.SplitN(tagValue, ",", 2) | |||
if len(tagParts) >= 2 { | |||
switch tagParts[1] { | |||
case "decodedFields": | |||
decodedFieldsVal = append(decodedFieldsVal, fieldValue) | |||
continue | |||
case "key": | |||
if item == nil { | |||
return &parser.PosError{ | |||
Pos: node.Pos(), | |||
Err: fmt.Errorf("%s: %s asked for 'key', impossible", | |||
name, fieldName), | |||
} | |||
} | |||
fieldValue.SetString(item.Keys[0].Token.Value().(string)) | |||
continue | |||
case "unusedKeys": | |||
unusedKeysVal = append(unusedKeysVal, fieldValue) | |||
continue | |||
} | |||
} | |||
if tagParts[0] != "" { | |||
fieldName = tagParts[0] | |||
} | |||
// Determine the element we'll use to decode. If it is a single | |||
// match (only object with the field), then we decode it exactly. | |||
// If it is a prefix match, then we decode the matches. | |||
filter := list.Filter(fieldName) | |||
prefixMatches := filter.Children() | |||
matches := filter.Elem() | |||
if len(matches.Items) == 0 && len(prefixMatches.Items) == 0 { | |||
continue | |||
} | |||
// Track the used key | |||
usedKeys[fieldName] = struct{}{} | |||
// Create the field name and decode. We range over the elements | |||
// because we actually want the value. | |||
fieldName = fmt.Sprintf("%s.%s", name, fieldName) | |||
if len(prefixMatches.Items) > 0 { | |||
if err := d.decode(fieldName, prefixMatches, fieldValue); err != nil { | |||
return err | |||
} | |||
} | |||
for _, match := range matches.Items { | |||
var decodeNode ast.Node = match.Val | |||
if ot, ok := decodeNode.(*ast.ObjectType); ok { | |||
decodeNode = &ast.ObjectList{Items: ot.List.Items} | |||
} | |||
if err := d.decode(fieldName, decodeNode, fieldValue); err != nil { | |||
return err | |||
} | |||
} | |||
decodedFields = append(decodedFields, field.Name) | |||
} | |||
if len(decodedFieldsVal) > 0 { | |||
// Sort it so that it is deterministic | |||
sort.Strings(decodedFields) | |||
for _, v := range decodedFieldsVal { | |||
v.Set(reflect.ValueOf(decodedFields)) | |||
} | |||
} | |||
return nil | |||
} | |||
// findNodeType returns the type of ast.Node | |||
func findNodeType() reflect.Type { | |||
var nodeContainer struct { | |||
Node ast.Node | |||
} | |||
value := reflect.ValueOf(nodeContainer).FieldByName("Node") | |||
return value.Type() | |||
} |
@@ -0,0 +1,3 @@ | |||
module github.com/hashicorp/hcl | |||
require github.com/davecgh/go-spew v1.1.1 |
@@ -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= |
@@ -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 |
@@ -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 | |||
//------------------------------------------------------------------- | |||