def substitute_tokens(tokens, attributes)
      unmatched_tokens = []
      encoded_tokens = tokens.encode("UTF-8", "binary", {
        :invalid => :replace,
        :undef => :replace,
        :replace => ""
      })
      substituted = encoded_tokens.gsub(/:::([^:].*?):::/) do
        token, default = $1.to_s.split("|", 2)
        path = token.split(".").map(&:to_sym)
        matched = find_attribute_value(attributes, path, default)
        if matched.nil?
          unmatched_tokens << token
        end
        matched
      end
      [substituted, unmatched_tokens]
    end
def substitute_tokens(tokens, attributes)
      unmatched_tokens = []
      encoded_tokens = tokens.encode("UTF-8", "binary", {
        :invalid => :replace,
        :undef => :replace,
        :replace => ""
      })
      substituted = encoded_tokens.gsub(/:::([^:].*?):::/) do
        token, default = $1.to_s.split("|", 2)
        path = token.split(".").map(&:to_sym)
        matched = find_attribute_value(attributes, path, default)
        if matched.nil?
          unmatched_tokens << token
        end
        matched
      end
      [substituted, unmatched_tokens]
    end
Substitute dot notation tokens (eg. :::db.name|production:::)
 with the associated definition attribute value. Tokens can
 provide a fallback default value, following a pipe.

 @param tokens [String]
 @param attributes [Hash]
 @return [Array] containing the string with tokens substituted
   and an array of unmatched tokens.