From 00199cd2edb2c79124d0418efef33f43c0dce654 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 7 Jun 2018 17:17:47 -0700 Subject: [PATCH] addrs: "Less" comparison method for resource and module instances This can be used to sort lists of resource instance and module instance addresses, such as in a rendered plan. --- addrs/instance_key.go | 53 ++++++++++++++++++++++++++++++++++++++++ addrs/module_instance.go | 21 ++++++++++++++++ addrs/resource.go | 29 ++++++++++++++++++++++ 3 files changed, 103 insertions(+) diff --git a/addrs/instance_key.go b/addrs/instance_key.go index 41576c0b6..cef8b2796 100644 --- a/addrs/instance_key.go +++ b/addrs/instance_key.go @@ -68,3 +68,56 @@ func (k StringKey) String() string { // slightly different than HCL's, but we'll accept it for now. return fmt.Sprintf("[%q]", string(k)) } + +// InstanceKeyLess returns true if the first given instance key i should sort +// before the second key j, and false otherwise. +func InstanceKeyLess(i, j InstanceKey) bool { + iTy := instanceKeyType(i) + jTy := instanceKeyType(j) + + switch { + case i == j: + return false + case i == NoKey: + return true + case j == NoKey: + return false + case iTy != jTy: + // The ordering here is arbitrary except that we want NoKeyType + // to sort before the others, so we'll just use the enum values + // of InstanceKeyType here (where NoKey is zero, sorting before + // any other). + return uint32(iTy) < uint32(jTy) + case iTy == IntKeyType: + return int(i.(IntKey)) < int(j.(IntKey)) + case iTy == StringKeyType: + return string(i.(StringKey)) < string(j.(StringKey)) + default: + // Shouldn't be possible to get down here in practice, since the + // above is exhaustive. + return false + } +} + +func instanceKeyType(k InstanceKey) InstanceKeyType { + if _, ok := k.(StringKey); ok { + return StringKeyType + } + if _, ok := k.(IntKey); ok { + return IntKeyType + } + return NoKeyType +} + +// InstanceKeyType represents the different types of instance key that are +// supported. Usually it is sufficient to simply type-assert an InstanceKey +// value to either IntKey or StringKey, but this type and its values can be +// used to represent the types themselves, rather than specific values +// of those types. +type InstanceKeyType rune + +const ( + NoKeyType InstanceKeyType = 0 + IntKeyType InstanceKeyType = 'I' + StringKeyType InstanceKeyType = 'S' +) diff --git a/addrs/module_instance.go b/addrs/module_instance.go index ba1ee3e00..53184deda 100644 --- a/addrs/module_instance.go +++ b/addrs/module_instance.go @@ -235,6 +235,27 @@ func (m ModuleInstance) String() string { return buf.String() } +// Less returns true if the receiver should sort before the given other value +// in a sorted list of addresses. +func (m ModuleInstance) Less(o ModuleInstance) bool { + if len(m) != len(o) { + // Shorter path sorts first. + return len(m) < len(o) + } + + for i := range m { + mS, oS := m[i], o[i] + switch { + case mS.Name != oS.Name: + return mS.Name < oS.Name + case mS.InstanceKey != oS.InstanceKey: + return InstanceKeyLess(mS.InstanceKey, oS.InstanceKey) + } + } + + return false +} + // Ancestors returns a slice containing the receiver and all of its ancestor // module instances, all the way up to (and including) the root module. // The result is ordered by depth, with the root module always first. diff --git a/addrs/resource.go b/addrs/resource.go index a57a29255..526a309a0 100644 --- a/addrs/resource.go +++ b/addrs/resource.go @@ -204,6 +204,35 @@ func (r AbsResourceInstance) String() string { return fmt.Sprintf("%s.%s", r.Module.String(), r.Resource.String()) } +// Less returns true if the receiver should sort before the given other value +// in a sorted list of addresses. +func (r AbsResourceInstance) Less(o AbsResourceInstance) bool { + switch { + + case len(r.Module) != len(o.Module): + return len(r.Module) < len(o.Module) + + case r.Module.String() != o.Module.String(): + return r.Module.Less(o.Module) + + case r.Resource.Resource.Mode != o.Resource.Resource.Mode: + return r.Resource.Resource.Mode == DataResourceMode + + case r.Resource.Resource.Type != o.Resource.Resource.Type: + return r.Resource.Resource.Type < o.Resource.Resource.Type + + case r.Resource.Resource.Name != o.Resource.Resource.Name: + return r.Resource.Resource.Name < o.Resource.Resource.Name + + case r.Resource.Key != o.Resource.Key: + return InstanceKeyLess(r.Resource.Key, o.Resource.Key) + + default: + return false + + } +} + // ResourceMode defines which lifecycle applies to a given resource. Each // resource lifecycle has a slightly different address format. type ResourceMode rune