code: def expandvars(self, text, environ=None, cache=None):
          
          if not environ:
              environ = os.environ
  
          # make sure we have data
          if not text:
              return ''
  
          # check for circular dependencies
          cache = cache or {}
  
          # return the cleaned variable
          output = nstr(text)
          keys = re.findall('\$(\w+)|\${(\w+)\}|\%(\w+)\%', text)
  
          for first, second, third in keys:
              repl = ''
              key = ''
              if first:
                  repl = '$%s' % first
                  key = first
              elif second:
                  repl = '${%s}' % second
                  key = second
              elif third:
                  repl = '%%%s%%' % third
                  key = third
              else:
                  continue
  
              value = environ.get(key)
              if value:
                  if key not in cache:
                      cache[key] = value
                      value = self.expandvars(value, environ, cache)
                  else:
                      err = '%s environ variable causes an infinite loop.' % key
                      logger.warning(err)
                      value = cache[key]
              else:
                  value = repl
  
              output = output.replace(repl, value)
  
          return os.path.expanduser(output)
docs: Recursively expands the text variables, vs. the os.path \
              method which only works at one level.  The cache value should be \
              left blank as it is used to protect against recursion.
              
              :param      text    | <str>
                          environ | <dict> || None
                          cache   | <dict> { <str>: <str>, .. }
                          
              :return     <str>