Updated 2017-12-06: complete re-write to support nested braces in variable description
Updated 2017-12-05: support minimal variable declaration, eg. variable "foo" {}

When developing terraform code, it is easy to end up with a bunch of variable definitions that are listed in no particular order.

Here's a bit of python code that will sort terraform variable definitions. Use it as a filter from inside vim, or as a standalone tool if you have all your variable definitions in one file.

eg:

tf_sort < variables.tf > variables.tf.sorted
mv variables.tf.sorted variables.tf

Here's the code:

#!/usr/bin/env python
# sort terraform variables

import sys

import regex

# this regex matches terraform variable definitions
# we capture the variable name so we can sort on it
pattern = regex.compile('''
(                   # capturing group #1
    variable        # literal text
    \s*             # white space (optional)
    "               # literal quote character
)
(                   # capturing group #2 (variable name)
    [^"]+           # anything except another quote
)
(                   # capturing group #3
    "               # literal quote character
    \s*             # white space (optional)
)
(?<rec>             # capturing group named "rec"
    {               # literal left brace
    (?:             # non-capturing group
        [^{}]++     # anything but braces one or more times with no backtracking
        |           # or
        (?&rec)     # recursive substitution of group "rec"
    )*              # this group can appear 0 or more times
    }               # literal right brace
)
''', regex.VERBOSE)


def process(content):
    # sort the content (a list of tuples) on the second item of the tuple
    # (which is the variable name)
    matches = sorted(regex.findall(pattern, content), key=lambda x: x[1])

    # iterate over the sorted list and output them
    for match in matches:
        print(''.join(map(str, match)))

        # don't print the newline on the last item
        if match != matches[-1]:
            print('')


# check if we're reading from stdin
if not sys.stdin.isatty():
    stdin = sys.stdin.read()
    if stdin:
        process(stdin)

# process any filenames on the command line
for filename in sys.argv[1:]:
    with open(filename) as f:
        process(f.read())