text: import collections
  import copy
  import os
  import simpleeval
  from munch import Munch
  from simpleeval import EvalWithCompoundTypes
  from collections import OrderedDict as odict
  from cryptography.fernet import Fernet
  
  
  class Munch2(Munch):
      """
      Provide easy way to access item in object by dot-notation.
      Example:
          obj = {'item': 'value'}
          # old way
          value = obj.get('item')
          # with munch
          value = obj.item
      """
      pass
  
  
  def update_recursive(d, u):
      """
      Update dictionary recursively. It traverse any object implements
      "collections.Mapping", anything else, it overwrites the original value.
      :param d: Original dictionary to be updated
      :param u: Value dictionary
      :return: Updated dictionary after merged
      """
      for k, v in u.items():
          if isinstance(d, dict):
              d2 = d.get(k, {})
          else:
              d2 = getattr(d, k, object())
  
          if isinstance(v, dict):
              r = update_recursive(d2, v)
              u2 = r
          else:
              u2 = u[k]
  
          if isinstance(d, dict):
              d[k] = u2
          else:
              setattr(d, k, u2)
      return d
  
  
  def yaml_safe_load(stream, ordered=False):
      import yaml
      from yaml.resolver import BaseResolver
  
      if ordered:
          def ordered_load(stream, loader_cls):
              class OrderedLoader(loader_cls):
                  pass
  
              def construct_mapping(loader, node):
                  loader.flatten_mapping(node)
                  return odict(loader.construct_pairs(node))
  
              OrderedLoader.add_constructor(BaseResolver.DEFAULT_MAPPING_TAG, construct_mapping)
              return yaml.load(stream, OrderedLoader)
  
          return ordered_load(stream, yaml.SafeLoader)
      else:
          return yaml.safe_load(stream)
  
  
  def filter_value(value):
      if type(value) == str:
          if value == '[empty]':
              value = ''
      elif type(value) == Munch2:
          value = value.toDict()
      if type(value) == str:
          value = value.strip()
      return value
  
  
  def fn_str(val):
      return str(val)
  
  
  def fn_float(val):
      return float(val)
  
  
  def fn_int(val):
      return int(val)
  
  
  def fn_has(val, name):
      if isinstance(val, collections.Mapping):
          return val.get(name, False) is not False
      else:
          return name in val
  
  
  def fn_list_next(vals, default=None):
      vals = [vals] if type(vals) != list else vals
      val = next(iter(vals), default)
      return val
  
  
  def fn_list_join(vals, sep=' '):
      vals = [val for val in vals if val]
      return sep.join(vals)
  
  
  def fn_str_trim(val: str, cs: list = None):
      cs = cs if cs else ['/', ' ']
      for c in cs:
          val = val.strip(c)
      return val
  
  
  def fn_linspace(start, end, steps):
      delta = (end-start)/(steps-1)
      return [start + delta*i for i in range(steps)]
  
  
  def fn_arange(start, end, delta):
      vals = [start]
      while vals[-1] + delta <= end:
          vals.append(vals[-1] + delta)
      return vals
  
  
  def fn_extend(val, val2):
      val = copy.deepcopy(val)
      type_val = type(val)
      type_val2 = type(val2)
      if type_val == list and type_val2 == list:
          val.extend(val2)
      elif type_val in [dict, odict, Munch2] and type_val2 in [dict, odict, Munch2]:
          for k, v in val2.items():
              val[k] = v
      return val
  
  
  def fn_update(update, parent):
      def walk(u, p):
          tu, tp = type(u), type(p)
          if tu in [dict, odict, Munch2] and tp in [dict, odict, Munch2]:
              for k, v in u.items():
                  p[k] = walk(v, p.get(k, v))
              return p
          else:
              return u
  
      walk(update, parent)
  
  
  def fn_encrypt_names(names: dict):
      def fn_encrypt(data, etype=None, ekey=None):
          r = (names if names else {}).get('R', {}).get('_', {})
          etype = etype if etype else r.get('etype')
          ekey = ekey if ekey else r.get('ekey')
          token = None
          if etype == 'fernet':
              f = Fernet(ekey)
              token = f.encrypt(data=str(data).encode()).decode()
          return token
  
      return fn_encrypt
  
  
  def fn_crypt_generate_key_names(names: dict = None):
      def fn_crypt_generate_key(etype=None):
          r = (names if names else {}).get('R', {}).get('_', {})
          etype = etype if etype else r.get('etype')
          if etype == 'fernet':
              key = Fernet.generate_key()
              return key
  
      return fn_crypt_generate_key
  
  
  def fn_decrypt_names(names: dict):
      def fn_decrypt(data, etype=None, ekey=None):
          r = (names if names else {}).get('R', {}).get('_', {})
          etype = etype if etype else r.get('etype')
          ekey = ekey if ekey else r.get('ekey')
          message = None
          if etype == 'fernet':
              f = Fernet(ekey)
              message = f.decrypt(token=str(data).encode()).decode()
          return message
  
      return fn_decrypt
  
  
  def fn_inc_names(names: dict):
      def fn_inc(fs_path, itype: str = 'yaml', fs_root: str = None, errors=None):
          fs_root = fs_root if fs_root else (names if names else {}).get('R', {}).get('_', {}).get('fs_root')
          data = None
          if itype == 'yaml':
              data = load(fs_path=fs_path, fs_root=fs_root, errors=errors)
          return data
      return fn_inc
  
  
  def fn_foreach(foreach, parent, names={}, fns={}):
      template = foreach['template']
      if not isinstance(template, dict):
          raise ValueError('template item of F.foreach must be a dict')
      for i, v in enumerate(foreach['values']):
          names.update({'loop': {'index': i, 'value': v,
                                 'length': len(foreach['values'])}})
          result = {}
          for key, val in template.items():
              pkey = parse_expr(key, names=names, fns=fns)
              pval = parse(copy.copy(val), names=names)
              result[pkey] = pval
          parent.update(result)
      # remove this specific foreach loop info from names dict so we don't break
      # any subsequent foreach loops
      del names['loop']
  
  
  def parse_expr(expr: str, names: dict, fns: dict, errors: list = None):
      errors = errors if type(errors) == list else []
      names2 = Munch2.fromDict({**names, **{
      }})
      fns = {'F': update_recursive(fns, {
          'list': {
              'next': fn_list_next,
              'join': fn_list_join
          },
          'str': fn_str,
          'float': fn_float,
          'int': fn_int,
          'has': fn_has,
          'next': fn_list_next,
          'join': fn_list_join,
          'trim': fn_str_trim,
          'extend': fn_extend,
          'update': fn_update,
          'encrypt': fn_encrypt_names(names2),
          'decrypt': fn_decrypt_names(names2),
          'inc': fn_inc_names(names2),
          'linspace': fn_linspace,
          'arange': fn_arange
      })}
      s = EvalWithCompoundTypes(names=names2, functions=fns)
      try:
          v = s.eval(expr=expr)
      except Exception as ex:
          v = expr
          errors.append([expr, ex])
      v = filter_value(v)
      return v
  
  
  def parse(root, names: dict = None, fns: dict = None, errors: list = None):
      names = names if names else {'R': root}
      fns = fns if fns else {}
      errors = errors if type(errors) == list else []
      root_type = type(root)
      if root_type == dict or root_type == odict:
          root_keys = list(root.keys())
          for k, v in root.items():
              root[k] = parse(root=v, names=names, fns=fns, errors=errors)
          if 'F.extend' in root_keys:
              root = fn_extend(root['F.extend'], root)
              if isinstance(root, dict):
                  del root['F.extend']
          if 'F.update' in root_keys:
              fn_update(root['F.update'], root)
              del root['F.update']
          if 'F.foreach' in root_keys:
              for k in ('values', 'template'):
                  if k not in root['F.foreach']:
                      raise ValueError('F.foreach missing key: {}'.format(k))
              fn_foreach(root['F.foreach'], root, names=names, fns=fns)
              del root['F.foreach']
      elif root_type == list:
          for i, v in enumerate(root):
              root[i] = parse(root=v, names=names, fns=fns, errors=errors)
      elif root_type == str:
          value = root
          if type(value) == str:
              value = parse_expr(root, names=names, fns=fns, errors=errors)
          return value
      return root
  
  
  def load(fs_path: str, fs_root: str = '', errors: list = None, params: dict = None, opts: dict = None):
      errors = errors if type(errors) == list else []
      fs_file_path = os.path.join(fs_root, fs_path)
      fs_root = fs_root if fs_root is None else os.path.dirname(fs_file_path)
      params = params if params else {}
      opts = opts if opts else {
          'max_power': simpleeval.MAX_POWER,
          'max_string_length': simpleeval.MAX_STRING_LENGTH,
          'disallow_prefixes': simpleeval.DISALLOW_PREFIXES
      }
      try:
          with open(fs_file_path) as stream:
              # load_yaml initial structure
              data = yaml_safe_load(stream, ordered=True)
              data['_'] = data['_'] if data.get('_') else {}
              data_internal = {'fs_path': fs_path, 'fs_root': fs_root}
              data_internal = {**{'etype': 'fernet'}, **data_internal, **params}
              data['_'] = {**data['_'], **data_internal}
              names = {'R': data}
              fns = {}
              data = parse(data, names=names, fns=fns)
              # data cleanup to ensure no data_internal
              all(map(data['_'].pop, data_internal))
              # remove when empty
              if len(data['_']) == 0:
                  del data['_']
              return data
      except Exception as ex:
          errors.append([fs_file_path, ex])
      return None
  