try:
    # framework is running
    from .startup_choice import *
except ImportError as _excp:
    # class is imported by itself
    if (
        'attempted relative import with no known parent package' in str(_excp)
        or 'No module named \'omfit_classes\'' in str(_excp)
        or "No module named '__main__.startup_choice'" in str(_excp)
    ):
        from startup_choice import *
    else:
        raise
from omfit_classes.utils_base import *
from omfit_classes.sortedDict import *
import numpy as np
import warnings
import uncertainties
comment_ptrn = re.compile(r'^__comment.*__$')
def _debug():
    try:
        return int(os.environ.get('OMFIT_DEBUG', '0'))
    except ValueError:
        return 0
def _split_arrays_function(name, value, offset=1, sparse_indexing=False):
    names = []
    values = []
    if isinstance(value, sparray):
        tmp = value.fortran_repr()
        for off, sub_value in zip(tmp.keys(), tmp.values()):
            if len(off):
                N, V = _split_arrays_function(name, sub_value, eval(off), sparse_indexing=True)
                names.extend(N)
                values.extend(V)
            else:
                names.append(name + '()')
                values.append(sub_value)
        return names, values
    for k, v in np.ndenumerate(value):
        k = np.atleast_1d(offset) * 0 + np.atleast_1d(k)
        if sparse_indexing and len(k) > 1:
            k[1:] *= 0
        k = tuple(k + offset)
        names.append('%s%s' % (name, re.sub(' ', '', re.sub(r',\)', ')', str(k)))))
        values.append([v])
    return names, values
[docs]def interpreter(orig, escaped_strings=True):
    """
    Parse string value in a fortran namelist format
    NOTE: for strings that are arrays of elements one may use the following notation:
    >> lines = '1 1.2 2.3 4.5 8*5.6'
    >> values = []
    >> for item in re.split('[ |\t]+', line.strip()):
    >>     values.extend(tolist(namelist.interpreter(item)))
    :param orig: string value element in a fortran namelist format
    :param escaped_strings: do strings follow proper escaping
    :return: parsed namelist element
    """
    # originally everything is in form of string but already split
    def eval_(a):
        try:
            return int(a)
        except Exception:
            try:
                return float(a)
            except Exception:
                return ast.literal_eval(a)
    out = orig
    if not isinstance(out, str):
        out = repr(orig)
    try:
        # fist try a number or a string that has been correctly escaped
        # remove fortran namelist escaping
        if escaped_strings:
            orig_ = re.sub(r'\\([!&$])', r'\1', orig)
            # fortran does not escape newlines
            if escaped_strings == 'fortran':
                orig_ = re.sub(r'\\', r'\\\\', orig_)
        else:
            orig_ = orig
        out = eval_(orig_)
        if isinstance(out, tuple):
            return complex(out[0], out[1])
    except Exception:
        try:
            if '+/-' in orig:
                out = uncertainties.ufloat_fromstr(orig)
            elif orig == 'nan':
                out = np.nan
            elif orig == 'inf':
                out = np.inf
            elif re.match(r'\- ?inf', orig):
                out = -np.inf
            else:
                raise
        except Exception:
            try:
                # then try a number saved with float format 0.d0
                tmp = re.sub(r'([0-9]+|[0-9]*(?:[0-9]\.|\.[0-9])[0-9]*)[dD]([\-\+]*[0-9]+)', r'\1e\2', orig)
                # also remove +'s
                tmp = re.sub(r'\+', '', tmp)
                out = eval_(tmp)
            except (SyntaxError, TypeError, ValueError):
                try:
                    # then check if it's one of those fortran repetition array
                    if re.match(r'([0-9]+)\*(.+)', tmp):
                        nrep = int(re.sub(r'([0-9]+)\*(.+)', r'\1', tmp))
                        val = re.sub(r'([0-9]+)\*(.+)', r'\2', tmp)
                        out = [eval_(val)] * nrep
                    else:
                        raise
                except Exception:
                    try:
                        # then try a boolean
                        tmp = re.sub(r'(?i)^\.true\.', '1', orig)
                        tmp = re.sub(r'(?i)^\.false\.', '0', tmp)
                        tmp = re.sub(r'(?i)^true', '1', tmp)
                        tmp = re.sub(r'(?i)^false', '0', tmp)
                        tmp = re.sub(r'(?i)^\.t', '1', tmp)
                        tmp = re.sub(r'(?i)^\.f', '0', tmp)
                        tmp = re.sub(r'(?i)^t', '1', tmp)
                        tmp = re.sub(r'(?i)^f', '0', tmp)
                        tmp = bool(eval_(tmp))
                        out = tmp
                    except (SyntaxError, TypeError, ValueError):
                        pass
    return out 
[docs]def array_encoder(orig, line='', level=0, separator_arrays=' ', compress_arrays=True, max_array_chars=79, escaped_strings=True):
    all = line
    tmps = [encoder(orig, escaped_strings) for orig in np.atleast_1d(orig)]
    # compress repeted entries
    if compress_arrays and len(tmps) > 0:
        tmpsc = []
        c = -1
        kold = tmps[0]
        for k in tmps + [tmps[-1] + ' ']:
            if kold == k:
                c += 1
            else:
                if c == 0:
                    tmpsc.append(kold)
                else:
                    tmpsc.append(str(c + 1) + '*' + kold)
                kold = k
                c = 0
        tmps = tmpsc
    # wrap entries so that they do not take more than max_array_chars per lines
    for k, tmp in enumerate(tmps):
        if k != (len(tmps) - 1):
            tmp = tmp + separator_arrays
        if not max_array_chars or len(line) + len(tmp) < max_array_chars:
            all += tmp
            line += tmp
        else:
            line = ' ' * level + tmp
            all += '\n' + line
    return all 
[docs]def encoder(orig, escaped_strings=True, dotBoolean=True, compress_arrays=True, max_array_chars=79):
    if isinstance(orig, (bool, np.bool_)):
        if dotBoolean:
            tmp = '.' + str(orig).lower() + '.'
        else:
            tmp = str(orig)
    elif escaped_strings and isinstance(orig, str):
        tmp = repr(orig)
        tmp = re.sub('([!&$])', r'\\\1', tmp)
        if escaped_strings == 'fortran':
            tmp = re.sub(r'\\\\', r'\\', tmp)
    elif isinstance(orig, uncertainties.core.AffineScalarFunc):
        tmp = repr(orig)
    elif isinstance(orig, complex):
        tmp = repr((float(np.real(orig)), float(np.imag(orig))))
    elif isinstance(orig, tuple):
        tmp = array_encoder(
            np.array(list(orig)).flatten(),
            compress_arrays=compress_arrays,
            max_array_chars=max_array_chars,
            escaped_strings=escaped_strings,
        )
    elif isinstance(orig, list):
        tmp = array_encoder(
            np.array(orig).flatten(), compress_arrays=compress_arrays, max_array_chars=max_array_chars, escaped_strings=escaped_strings
        )
    elif isinstance(orig, np.ndarray):
        tmp = array_encoder(
            orig.flatten(), compress_arrays=compress_arrays, max_array_chars=max_array_chars, escaped_strings=escaped_strings
        )
    elif orig is None:
        tmp = ''
    elif orig == None:
        tmp = ''
    else:
        tmp = str(orig)
    return tmp 
[docs]class NamelistName(SortedDict):
    """Defines variables defined within a &XXX and / delimiter in a FOTRAN namelist"""
    def __init__(self, *args, **kw):
        SortedDict.__init__(self, *args, **kw)
        self.caseInsensitive = True
        self._collect = False
        self.collect_arrays = False
        self.index_offset = kw.get('index_offset', False)
    def _checkSetitem(self, key, value):
        """
        Namelists can only handle strings as their dictionary keys
        This function will raise an exception during __setitem__ if that's not the case.
        """
        if not isinstance(key, str):
            raise Exception('Namelist only allows strings as dictionary keys')
        else:
            return key, value
[docs]    def collectArrays(self, _dimensions=None, **input_dimensions):
        """
        This function collects the multiple namelist arrays into a single one::
             collectArrays(**{'__default__':0,        # default value for whole namelist (use when no default is found)
                              'BCMOM':{               # options for specific entry in the namelist
                                  'default':3,        # a default value must be defined to perform math ops (automatically set by a=... )
                                  'shape':(30,30),    # this overrides automatic shape detection (automatically set by a(30,30)=...)
                                  'offset':(-10,-10), # this overrides automatic offset detection (automatically set to be the minimum of the offsets of the entries in all dimensions a(-10,-10)=...)
                                  'dtype':0}          # this overrides automatic type detection (automatically set to float if at least one float is found)
                              })
        """
        collect = SortedDict(caseInsensitive=True)
        defaults = SortedDict(caseInsensitive=True)
        offsets = SortedDict(caseInsensitive=True)
        dtypes = SortedDict(caseInsensitive=True)
        outputs = SortedDict(caseInsensitive=True)
        shapes = SortedDict(caseInsensitive=True)
        dimensions = SortedDict(caseInsensitive=True)
        dimensions.update(input_dimensions)
        if isinstance(_dimensions, dict):
            dimensions.update(_dimensions)
        if not isinstance(self.collect_arrays, dict):
            self.collect_arrays = SortedDict(caseInsensitive=True)
        self.collect_arrays.update(dimensions)
        dimensions.update(self.collect_arrays)
        # find the arrays to collect
        for kid in self.keys():
            if isinstance(self[kid], NamelistName):
                self[kid].collect_arrays = self.collect_arrays
                self[kid].collectArrays(dimensions, **input_dimensions)
                continue
            if re.match(r'.*\(.*\)', kid) or kid in self.collect_arrays:
                if re.match(r'.*\(.*\)', kid):
                    var = re.sub(r'(.*)\((.*)\)', r'\1', kid)
                else:
                    var = kid
                # add to list of items to append
                collect.setdefault(var, [])
        # expand ranges
        for kid in self.keys():
            mat = re.match(r'(.*)\((.*)\)', kid)
            if not mat:
                continue
            if self[kid] is None:
                printw('Assigning 0 length array to finite range: %s' % kid)
                continue
            sk = mat.groups()
            if len(sk) < 2:
                continue
            var, ind = sk
            if ':' not in ind:
                continue
            rnk_ranges = []
            ranks = ind.split(',')
            for ri, rank in enumerate(ranks):
                if ':' not in rank:
                    continue
                rnk_ranges.append(ri)
            if len(rnk_ranges) > 1:
                raise NotImplementedError(f'{kid}: must only give one dimension with a range')
            rng_ri = rnk_ranges[0]
            new_key = '{var}({prefix}{rg_ind}{postfix})'
            prefix = ','.join(ranks[:rng_ri])
            if rng_ri > 0:
                prefix = prefix + ','
            postfix = ''
            if rng_ri != len(ranks) - 1:
                postfix = postfix + ','
            postfix = postfix + ','.join(ranks[rng_ri + 1 :])
            rngs = list(map(int, ranks[rng_ri].split(':')))
            rngs[1] = rngs[1] + 1
            if len(range(*rngs)) != len(self[kid]):
                raise ValueError('Length of range and values disagree for %s: %s; %s' % (kid, ranks[rng_ri], self[kid]))
            for ri, rang in enumerate(range(*rngs)):
                self[new_key.format(prefix=prefix, var=var, rg_ind=rang, postfix=postfix)] = self[kid][ri]
            del self[kid]
        # find the arrays to collect
        warn_msg = []
        for kid in self.keys():
            if (
                re.sub(r'(.*)\((.*)\)', r'\1', kid) in collect
                or kid in self.collect_arrays
                and self.collect_arrays[kid].get('sparray', True)
            ):
                if re.match(r'.*\(.*\)', kid):
                    try:
                        var = re.sub(r'(.*)\((.*)\)', r'\1', kid)
                        indexStr = re.sub(r'(.*)\((.*)\)', r'\2', kid)
                        index = np.array(list(map(int, indexStr.split(','))))
                    except ValueError:
                        continue
                else:
                    var = kid
                    indexStr = None
                    index = (1,)
                # types
                if var in dimensions and 'dtype' in dimensions[var]:
                    tmp = dimensions[var]['dtype']
                    if callable(tmp):
                        tmp = tmp(0)
                    dtypes[var] = tmp
                elif np.atleast_1d(self[kid]).dtype.kind.lower() in ['s', 'u']:
                    dtypes[var] = ''  # string
                elif np.atleast_1d(self[kid]).dtype.kind.lower() in ['f']:
                    dtypes[var] = 0.0  # float
                elif np.atleast_1d(self[kid]).dtype.kind.lower() in ['b']:
                    dtypes[var] = False  # boolean
                elif var not in dtypes:
                    dtypes[var] = 0  # int
                # offsets
                if var not in offsets:
                    offsets[var] = index
                off = np.ones(np.shape(index))
                off[: len(offsets[var])] = offsets[var]
                offsets[var] = np.min([off, index], 0)
                offsets[var] = tuple(map(int, offsets[var].tolist()))
                # add to list of items to append
                collect[var].append(indexStr)
        # user defined shapes
        for var in collect.keys():
            if var in dimensions and 'shape' in dimensions[var]:
                shapes[var] = tuple(dimensions[var]['shape'])
            else:
                shapes[var] = None
        # if there is something to collect
        if len(collect):
            # if there was a default as array update the offsets to be at maximum 1
            for var in collect.keys():
                for k, indexStr in enumerate(collect[var]):
                    if indexStr is None:
                        if len(collect[var]) > 1 or offsets[var] != 0:
                            offsets[var] = np.min([offsets[var], np.ones(np.shape(offsets[var]))], 0)
                            offsets[var] = tuple(map(int, offsets[var].tolist()))
                        else:
                            del collect[var]
                            del offsets[var]
                            continue
            # user defined offsets
            for var in offsets.keys():
                if var in dimensions and 'offset' in dimensions[var]:
                    offsets[var] = tuple(dimensions[var]['offset'])
                elif '__offset__' in dimensions:
                    offsets[var] = tuple([dimensions['__offset__']] * len(offsets[var]))
                else:
                    # equal offsets for all dimensions (reasonable?)
                    offsets[var] = tuple([min(offsets[var])] * len(offsets[var]))
            # defaults
            for var in offsets.keys():
                if isinstance(dtypes[var], str):
                    defaults[var] = dimensions.get('__default__', '')
                else:
                    defaults[var] = dimensions.get('__default__', np.nan)
            for var in offsets.keys():
                if var in dimensions and 'default' in dimensions[var]:
                    defaults[var] = dimensions[var]['default']
            # define sparse arrays
            for var in offsets.keys():
                # handle arrays of strings
                if isinstance(dtypes[var], str):
                    # for the time being only handle var(1)=['a','b','c'] type string arrays
                    if len(offsets[var]) > 1:
                        continue
                    # figure out the shape
                    if shapes[var] is None:
                        shapes[var] = 0
                        for indexStr in collect[var]:
                            if indexStr is None:
                                defaults[var] = self[var]
                                continue
                            kid = '%s(%s)' % (var, indexStr)
                            shapes[var] = max([shapes[var], int(indexStr) + len(tolist(self[kid])) - 1])
                        shapes[var] = (shapes[var],)
                    # generate array
                    outputs[var] = [defaults[var]] * shapes[var][0]
                    for indexStr in collect[var]:
                        if indexStr is None:
                            continue
                        kid = '%s(%s)' % (var, indexStr)
                        for k, value in enumerate(tolist(self[kid])):
                            outputs[var][int(indexStr) - offsets[var][0] + k] = value
                # handle numerical arrays
                else:
                    outputs[var] = sparray(shapes[var], offset=offsets[var], default=defaults[var], dtype=dtypes[var], index_offset=True)
                    for indexStr in collect[var]:
                        if indexStr is None:
                            kid = var
                            indexStr = ','.join(list(map(str, offsets[var])))
                        else:
                            kid = '%s(%s)' % (var, indexStr)
                        outputs[var].fortran(indexStr, self[kid])
                    outputs[var].index_offset = self.index_offset
                    # keep it sparse if requested by user
                    if var in self.collect_arrays and self.collect_arrays[var].get('sparray', True):
                        pass
                    # dense if 1D or 2D and offsets start at 1
                    elif (outputs[var].ndim == 1 and np.atleast_1d(outputs[var].offset)[0] == 1) or (
                        outputs[var].ndim == 2 and np.atleast_1d(outputs[var].offset)[0] == 1 and np.atleast_1d(outputs[var].offset)[1] == 1
                    ):
                        tmp = outputs[var].dense()
                        if not np.any(outputs[var].isnan(tmp)):
                            outputs[var] = tmp
        # substitute collected values
        for var in outputs:
            i = -1
            for k, kid in enumerate(self.keys()):
                if kid.lower().startswith(var.lower() + '('):
                    del self[kid]
                    i = k
            self.insert(i, var, outputs[var])
        self._collect = True 
[docs]    def collect(self, value):
        self._collect = value
        for kid in self.keys():
            if isinstance(self[kid], NamelistName):
                self[kid].collect(value) 
    def __setitem__(self, key, value):
        if isinstance(value, dict) and not isinstance(value, NamelistName):
            tmp = NamelistName(index_offset=self.index_offset)
            tmp.update(value)
            value = tmp
        return super().__setitem__(key, value)
    def __getitem__(self, key):
        key = self._keyCaseInsensitive(key)
        # handle internal key indexing
        if isinstance(key, str) and '(' in key and re.match(r'.*\(.*\)', key) and key not in self.keys():
            index = np.array(list(map(int, re.sub(r'(.*)\((.*)\)', r'\2', key).split(','))))
            key = re.sub(r'(.*)\((.*)\)', r'\1', key)
        else:
            index = None
        # get the value
        tmp = super().__getitem__(key)
        # return (indexed) object
        if index is None or not len(np.shape(tmp)):
            return tmp
        else:
            offset = -1
            while index[-1] == 1 and len(index) > len(np.atleast_1d(tmp).shape):
                index = index[:-1]
            index += offset
            return tmp[tuple(index)]
    def __setstate__(self, tmp):
        self.__dict__.update(tmp[0])
        if hasattr(self, '_collect'):
            _collectBCKP = self._collect
            self.collect(False)
        for key, value in zip(self.keyOrder, tmp[1]):
            self[key] = self._setLocation(key, value)
        if hasattr(self, '_collect'):
            self.collect(_collectBCKP)
    def __deepcopy__(self, memo={}):
        if hasattr(self, '_collect'):
            _collectBCKP = self._collect
        else:
            _collectBCKP = False
        self.collect(False)
        tmp = pickle.loads(pickle.dumps(self, pickle.HIGHEST_PROTOCOL))
        tmp.collect(_collectBCKP)
        self.collect(_collectBCKP)
        return tmp 
[docs]class NamelistFile(NamelistName):
    """
    FORTRAN namelist file object, which can contain multiple namelists blocks
    :param filename: filename to be parsed
    :param input_string: input string to be parsed (preceeds filename)
    :param nospaceIsComment: whether a line which starts without a space should be retained as a comment. If None, a "smart" guess is attempted
    :param outsideOfNamelistIsComment: whether the content outside of the namelist blocks should be retained as comments. If None, a "smart" guess is attempted
    :param retain_comments: whether comments should be retained or discarded
    :param skip_to_symbol: string to jump to for the parsing. Content before this string is ignored
    :param collect_arrays: whether arrays defined throughout the namelist should be collected into single entries
     (e.g. a=5,a(1,4)=0)
    :param multiDepth: wether nested namelists are allowed
    :param bang_comment_symbol: string containing the characters that should be interpreted as comment delimiters.
    :param equals: how the equal sign should be written when saving the namelist
    :param compress_arrays: compress repeated elements in an array by using v*n namelist syntax
    :param max_array_chars: wrap long array lines
    :param explicit_arrays: (True,False,1) whether to place `name(1)` in front of arrays.
                            If `1` then (1) is only placed in front of arrays that have only one value.
    :param separator_arrays: characters to use between array elements
    :param split_arrays: write each array element explicitly on a separate line
                         Specifically this functionality was introduced to split TRANSP arrays
    :param idlInput: whether to interpret the namelist as IDL code
    """
    def __init__(
        self,
        filename=None,
        input_string='',
        nospaceIsComment=None,
        outsideOfNamelistIsComment=None,
        retain_comments=True,
        skip_to_symbol=None,
        collect_arrays=True,
        multiDepth=True,
        bang_comment_symbol='!;',
        equals=' = ',
        compress_arrays=True,
        max_array_chars=79,
        explicit_arrays=False,
        separator_arrays=' ',
        split_arrays=False,
        idlInput=False,
        end_token='/',
        index_offset=False,
        **kw,
    ):
        NamelistName.__init__(self, index_offset=index_offset)
        self.filename = filename
        self.retain_comments = retain_comments
        self.nospaceIsComment = nospaceIsComment
        self.outsideOfNamelistIsComment = outsideOfNamelistIsComment
        self.skip_to_symbol = skip_to_symbol
        self.collect_arrays = collect_arrays
        self.multiDepth = multiDepth
        self.idlInput = bool(idlInput)
        self.bang_comment_symbol = bang_comment_symbol
        self._equals = equals
        self.compress_arrays = compress_arrays
        self.max_array_chars = max_array_chars
        self.explicit_arrays = explicit_arrays
        self.separator_arrays = separator_arrays
        self.split_arrays = split_arrays
        self.end_token = end_token
        if idlInput:
            self.bang_comment_symbol = ';'
            self.nospaceIsComment = False
            self.outsideOfNamelistIsComment = False
            self.compress_arrays = False
            self.max_array_chars = None
            self.separator_arrays = ','
        self.dynaLoad = False
        if input_string != '':
            if self.filename is not None:
                with open(self.filename, 'w') as f:
                    f.write(input_string)
            else:
                self.parse(input_string.splitlines(True))
        if self.filename is not None:
            self.dynaLoad = True
[docs]    def parse(self, content):
        self.clear()
        if not len(content):
            return
        if _debug():
            start = time.time()
        # ignore cvs version line by adding a bang comment
        for k, line in enumerate(content):
            if '$Id' in line:
                content[k] = re.sub(r'.*\$Id([^$]*?)\$', self.bang_comment_symbol[0] + r' $Id\1$', line)
        # nospace should be interpreted as a comment if I do not find any line which starts by a letter and has a variable assignment
        if self.nospaceIsComment is None:
            self.nospaceIsComment = True
            for line in content:
                line = line.strip('\n')
                for k in self.bang_comment_symbol:  # skip commented lines
                    line = line.split(k)[0]
                line = line.rstrip()
                if not len(line):  # Skip lines without length
                    continue
                elif re.search(r'^\s', line):  # Skip lines with leading space
                    continue
                elif re.search(r'(?i)^[&$][_a-z0-9]', line):  # Begin/end of namelist on first char
                    self.nospaceIsComment = False
                elif re.search(r'^[&/$]', line):  # End of namelist on first char
                    self.nospaceIsComment = False
                elif re.search(r'(?i)^[_a-z0-9]\w*\s*=\s*[-\'\"\.]?\w*', line):  # Variable assignment on first char
                    self.nospaceIsComment = False
                    # Variable assignment on first char
                elif re.search(r'(?i)^[_a-z0-9](\([,0-9]\))*\w*\s*=\s*[-\'\"\.]?\w*', line):
                    self.nospaceIsComment = False
        # see if there are variable assignements outside of namelist
        if self.outsideOfNamelistIsComment is None:
            self.outsideOfNamelistIsComment = True
            inside = False
            for line in content:
                line = line.strip('\n')
                for k in self.bang_comment_symbol:  # skip commented lines
                    line = line.split(k)[0]
                line = line.rstrip()
                if not len(line):  # skip lines without length
                    continue
                elif re.search('(?i)^[_a-z0-9]', line) and self.nospaceIsComment:  # Skip if it's a line commment
                    continue
                elif re.search(r'(?i)^\s*[&$][_a-z0-9]', line):  # Begin of namelist --> inside=True
                    inside = True
                elif re.search(r"(?i)^\s*[&/$]", line):  # End of namelist --> inside=False
                    inside = False
                elif not inside:
                    tmp1 = re.findall(r'(?i)^[_a-z0-9](\([,0-9]\))*\w*\s*=\s*[-\'\"\.]?\w*', line)
                    if len(tmp1) == 1:  # Only one variable per line
                        tmp2 = re.findall(r'(?i)^[_a-z0-9](\([,0-9]\))*\w*\s*=\s*[-\'\"\.]?\w*', line)
                        if len(tmp2) and tmp2[0] == tmp1[0]:  # There is no text before the first variable
                            self.outsideOfNamelistIsComment = False
        if _debug():
            for k in ['nospaceIsComment', 'outsideOfNamelistIsComment']:
                print(k, getattr(self, k))
            print('read in ' + format(time.time() - start, '3.3f') + ' sec.')
        rules = {}
        rules['single'] = 0
        rules['double'] = 0
        rules['round_bracket'] = 0
        rules['bang_comment'] = 0
        rules['line_comment'] = 0
        if self.skip_to_symbol is None:
            tmpj = ''.join(content)
        else:
            for si, s in enumerate(content):
                if self.skip_to_symbol in s:
                    break
            if si == len(content):
                si = 0
            tmpj = ''.join(content[si:])
        item_offset = 0
        item_length = 0
        item_prepend = ''
        tmp = []
        if self.idlInput:
            # in IDL language `&` is like a new line
            # here it's safer to convert to a space
            tmpj = re.sub('&', ' ', tmpj)
        inside = 0
        tmpj = re.sub(r"(?i)[&/$]end(_\S*)?", "$", tmpj).rstrip() + '\n' * 3
        kk = 0
        for cm, c, cp in zip(*['\n' + tmpj, tmpj, tmpj[1:] + '\n']):
            kk += 1
            if _debug() > 1:
                print(cm, c, cp, tmp)
            if cm == '\\':
                # if it's an escaped character, then it's nothing special, by definition
                item_length += 1
                continue
            if c == '\n':
                # if it's a new line, then it's a new record and all rules should be reset
                for key in rules.keys():
                    rules[key] = 0
            # handle comments outside of namelist
            if self.outsideOfNamelistIsComment:
                # bang comments should always be considered comments
                if c in self.bang_comment_symbol:
                    rules['bang_comment'] = 1
                # when namelist block starts or the file ends, assign all to be comment
                if not rules['bang_comment'] and re.match(r'(?i)[$&][_a-z0-9]', c + cp) or kk == len(tmpj):
                    if not inside:
                        item_ = tmpj[item_offset : item_offset + item_length]
                        item_ = re.sub('[\n \t]', '', item_)
                        if len(item_):  # do not store empty blocks
                            if not len(tmp):
                                item = u'\1' + tmpj[item_offset : item_offset + item_length].rstrip(' \t')[:-1]
                            else:
                                item = u'\1' + tmpj[item_offset : item_offset + item_length].strip(' \t')[1:-1]
                            item_offset += item_length
                            item_length = 0
                            item_prepend = ''
                            tmp.append(item)
                # check if a namelist block is starting
                if not rules['bang_comment'] and re.match(r'(?i)[$&][_a-z0-9]', c + cp):
                    inside += 1
                    if _debug() > 1:
                        print('namelist block starts')
            # if inside of a namelist block or if variables outside namelist blocks are not comments
            if inside or not self.outsideOfNamelistIsComment:
                # this is a new item
                if (c in ' \t,=' and not np.any([rules[key] for key in rules.keys()])) or c == '\n':
                    if item_length > 0:
                        item = item_prepend + tmpj[item_offset : item_offset + item_length]
                        item_offset += item_length
                        item_length = 0
                        item_prepend = ''
                        if len(item.strip('\n \t,=')):
                            tmp.append(item.strip('\n \t,='))
                        if c == '=':
                            tmp.append(c)
                # this is the continuation of the current item
                else:
                    if c == '(' and not np.any([rules[key] for key in rules.keys() if key != 'round_bracket']):
                        rules['round_bracket'] += 1
                    elif c == ')' and not np.any([rules[key] for key in rules.keys() if key != 'round_bracket']):
                        rules['round_bracket'] -= 1
                    elif c == "'" and not np.any([rules[key] for key in rules.keys() if key != 'single']):
                        rules['single'] = (rules['single'] + 1) % 2
                    elif c == '"' and not np.any([rules[key] for key in rules.keys() if key != 'double']):
                        rules['double'] = (rules['double'] + 1) % 2
                    elif (c in self.bang_comment_symbol) and not np.any([rules[key] for key in rules.keys() if key != 'bang_comment']):
                        rules['bang_comment'] = 1
                    elif (
                        (self.nospaceIsComment and not self.outsideOfNamelistIsComment)
                        and (cm == '\n' and c not in ' \t')
                        and not np.any([rules[key] for key in rules.keys() if key != 'line_comment'])
                    ):
                        rules['line_comment'] = 1
                        item_prepend = u'\1'
                # when namelist block ends
                if self.outsideOfNamelistIsComment:
                    if inside and not np.any([rules[key] for key in rules.keys()]) and ((cm == '/' and c == '\n')):
                        inside -= 1
                        # if namelist block ends and last item had / then
                        # reasonable to assume that / was the termination
                        if inside == 0 and len(tmp[-1]) > 1 and tmp[-1][-1] == '/':
                            tmp[-1] = tmp[-1][:-1]
                        if _debug() > 1:
                            print('namelist block ends')
            item_length = item_length + 1
        if _debug():
            print('b in ' + format(time.time() - start, '3.3f') + ' sec.')
        tmp = [re.sub(r"(?i)[&/$]end", "/", line) for line in tmp]
        if _debug() > 1:
            print(tmp)
        def uniqueSortedComment(itm, tree):
            out = '__comment' + omfit_hash(str(itm), 10) + '__'
            while out in tree:
                out = '__comment' + omfit_hash(str(out), 10) + '__'
            return out
        # build the namelist tree
        tree = self
        vlist = None
        for k, item in enumerate(tmp):
            if re.match(r'^[&$].+', item):
                if not self.multiDepth and '__parent__' in tree:
                    tmptree = tree['__parent__']
                    del tree['__parent__']
                    tree = tmptree
                    vlist = None
                # is it a namelist header
                nlist = re.sub(r'^[&$](.*)', r'\1', item)
                if nlist not in tree:
                    tree[nlist] = NamelistName(index_offset=self.index_offset)
                tree[nlist]['__parent__'] = tree
                tree = tree[nlist]
            elif item[0] == u'\1':
                # it's a line comment
                if self.retain_comments:
                    tree[uniqueSortedComment(item[1:], tree)] = item[1:] + '\n'
            elif item[0] in self.bang_comment_symbol:
                # it's a bang comment
                if self.retain_comments:
                    tree[uniqueSortedComment(item, tree)] = item + '\n'
            elif item in ['/', '$', '&']:
                # is it a namelist close
                try:
                    tmptree = tree['__parent__']
                    del tree['__parent__']
                    tree = tmptree
                    vlist = None
                except Exception:
                    if self.multiDepth:
                        warnings.warn('Some problem with namelist closure near: +\n\n' + ' '.join(tmp[k - 100 : k]))
            elif k != len(tmp) - 1 and tmp[k + 1] == '=':
                # If the next item is an equal sign, this means that it's a variable name
                tmp[k] = item = re.sub(' ', '', item)  # variable names in namelists cannot have spaces
                tree[item] = []
                vlist = tree[item]
            elif item == '=':
                pass
            else:
                if not (vlist is None):
                    if '*' in item and re.match(r'([0-9]+)\*(.+)', item):
                        nrep = int(re.sub(r'([0-9]+)\*(.+)', r'\1', item))
                        val = re.sub(r'([0-9]+)\*(.+)', r'\2', item)
                        vlist.extend([val] * nrep)
                    elif self.idlInput:
                        vlist.append(item.strip('[]'))
                    else:
                        vlist.append(item)
        if _debug():
            print('c in ' + format(time.time() - start, '3.3f') + ' sec.')
        if not self.multiDepth:
            fun_str = lambda orig: interpreter(orig, escaped_strings='fortran')
        else:
            fun_str = lambda orig: interpreter(orig, escaped_strings=True)
        # now interpret the list of stings
        def i_traverse(me):
            if '__parent__' in me.keys():
                del me['__parent__']
            for kid in me.keys():
                if isinstance(me[kid], dict):
                    i_traverse(me[kid])
                elif not re.match(comment_ptrn, kid):
                    fun = fun_str
                    if len(me[kid]) > 1:
                        item0 = copy.deepcopy(me[kid][0])
                        try:
                            int(item0)
                            fun = int
                        except Exception:
                            try:
                                float(item0)
                                fun = float
                            except Exception:
                                pass
                    try:
                        me[kid] = list(map(fun, me[kid]))
                    except Exception:
                        me[kid] = list(map(fun_str, me[kid]))
                    if isinstance(me[kid], str):
                        pass
                    elif len(me[kid]) > 1:
                        me[kid] = np.array(me[kid])
                    elif len(me[kid]) == 1:
                        me[kid] = me[kid][0]
                    else:
                        me[kid] = None
        i_traverse(self)
        if _debug():
            print('d in ' + format(time.time() - start, '3.3f') + ' sec.')
        # collect arrays
        if self.collect_arrays:
            try:
                self.collectArrays(self.collect_arrays)
            except Exception as _excp:
                printw('Could not collect arrays: ' + repr(_excp))
        if _debug():
            print('end of loading namelist')
            print('Parsing namelist in ' + format(time.time() - start, '3.3f') + ' sec.') 
[docs]    def saveas(self, filename):
        """save namelist to a new file"""
        self.filename = os.path.abspath(filename)
        directory = os.path.split(os.path.abspath(filename))[0]
        if os.path.exists(directory) == 0:
            os.makedirs(directory)
        self.save() 
[docs]    @dynaSave
    def save(self, fp=None):
        if self.filename == None and fp == None:
            return
        # this whole pointer trickery is to allow saving of namelists in EFIT g-file
        if fp is not None:
            f = fp
        else:
            f = open(self.filename, 'w+')
        def f_traverse(me, level):
            for kid in me.keys():
                if _debug():
                    print('Writing ', kid)
                if isinstance_str(me[kid], ['OMFITexpression', 'OMFITiterableExpression']):
                    meKid = me[kid]._value_()
                else:
                    meKid = me[kid]
                if isinstance(meKid, dict):
                    level += 1
                    f.writelines(' ' * level + '&' + kid + '\n')
                    f_traverse(meKid, level)
                    f.writelines(' ' * level + self.end_token + '\n')
                    level -= 1
                elif re.match(comment_ptrn, kid):
                    f.writelines(meKid.rstrip() + '\n')
                else:
                    # if it is an array
                    if (
                        isinstance(meKid, np.ndarray)
                        or isinstance(meKid, sparray)
                        or isinstance(meKid, list)
                        or re.search(r'^\[(([ \n]*[0-9\-+.ji]+)*)\]$', str(meKid))
                    ):
                        # handle split_arrays functionality by looping over individual segments
                        if not self.split_arrays:
                            kid0, meKid0 = (
                                [kid],
                                [meKid],
                            )
                        elif callable(
                            self.split_arrays
                        ):  # this cannot be saved, but it is useful for development of the _split_arrays_function
                            kid0, meKid0 = self.split_arrays(kid, meKid)
                        else:
                            kid0, meKid0 = _split_arrays_function(kid, meKid)
                        for kid, meKid in zip(kid0, meKid0):
                            if isinstance(meKid, sparray):
                                tmp = meKid.fortran_repr()
                                for item, value in zip(tmp.keys(), tmp.values()):
                                    line = ' ' * level + kid + item + self._equals
                                    if self.idlInput:
                                        line += '['
                                    tmp = array_encoder(
                                        value,
                                        line,
                                        level,
                                        separator_arrays=self.separator_arrays,
                                        compress_arrays=self.compress_arrays,
                                        max_array_chars=self.max_array_chars,
                                        escaped_strings=['fortran', True][self.multiDepth],
                                    )
                                    if self.idlInput:
                                        tmp += ']'
                                    f.writelines(tmp.rstrip() + '\n')
                            elif isinstance(meKid, np.ndarray) and len(meKid.shape) > 1:
                                kidShape = list(meKid.shape)
                                kidShape[0] = 1
                                i = 0
                                fi = lambda i=i: '0:' + str(i)
                                ndim = np.reshape(eval("np.mgrid[" + ','.join([fi(i) for i in kidShape]) + "]"), (len(kidShape), -1))
                                tmpData = np.reshape(meKid, (meKid.shape[0], -1))
                                for kk, k in enumerate(range(len(ndim[0]))):
                                    line = ' ' * level + kid + '(' + ','.join([str(i + 1) for i in ndim[:, k]]) + ')' + self._equals
                                    if self.idlInput:
                                        line += '['
                                    tmp = array_encoder(
                                        tmpData[:, kk],
                                        line,
                                        level,
                                        separator_arrays=self.separator_arrays,
                                        compress_arrays=self.compress_arrays,
                                        max_array_chars=self.max_array_chars,
                                        escaped_strings=['fortran', True][self.multiDepth],
                                    )
                                    if self.idlInput:
                                        tmp += ']'
                                    f.writelines(tmp.rstrip() + '\n')
                            else:
                                if re.match(r'.*\(\)', kid):
                                    kid = kid[:-2]
                                elif self.explicit_arrays is 1 and len(meKid) == 1 and not re.match(r'.*\(.*\)', kid):
                                    kid = kid + '(1)'
                                elif self.explicit_arrays is True and not re.match(r'.*\(.*\)', kid):
                                    kid = kid + '(1)'
                                line = ' ' * level + kid + self._equals
                                if self.idlInput:
                                    line += '['
                                tmp = array_encoder(
                                    meKid,
                                    line,
                                    level,
                                    separator_arrays=self.separator_arrays,
                                    compress_arrays=self.compress_arrays,
                                    max_array_chars=self.max_array_chars,
                                    escaped_strings=['fortran', True][self.multiDepth],
                                )
                                if self.idlInput:
                                    tmp += ']'
                                f.writelines(tmp.rstrip() + '\n')
                    else:
                        f.writelines(
                            (' ' * level + kid + self._equals + encoder(meKid, escaped_strings=['fortran', True][self.multiDepth])).rstrip()
                            + '\n'
                        )
        f_traverse(self, 0)
        if fp is None:
            f.close() 
[docs]    @dynaLoad
    def load(self, filename=''):
        """Load namelist from file."""
        self.clear()
        self.dynaLoad = False
        if filename != '':
            self.filename = filename
        with open(self.filename, 'r') as f:
            self.parse(f.readlines())
        return self  
[docs]class fortran_environment(object):
    """
    Environment class used to allow FORTRAN index convention of sparrays in a namelist
    """
    def __init__(self, nml):
        self.nml = nml
        self.sp = traverse(self.nml, onlyLeaf=(sparray,))
    def __enter__(self):
        self.index_offset_bkp = {}
        for item in self.sp:
            self.index_offset_bkp[item] = getattr(eval("self.nml" + item), 'index_offset', 0)
            eval("self.nml" + item).index_offset = True
    def __exit__(self, type, value, traceback):
        for item in self.sp:
            eval("self.nml" + item).index_offset = self.index_offset_bkp[item] 
[docs]class sparray(object):
    """
    Class for n-dimensional sparse array objects using Python's dictionary structure.
    based upon: http://www.janeriksolem.net/2010/02/sparray-sparse-n-dimensional-arrays-in.html
    """
    def __init__(self, shape=None, default=np.nan, dtype=None, wrap_dim=0, offset=0, index_offset=False):
        if dtype is None:
            self.dtype = None
        elif callable(dtype):
            self.dtype = dtype = np.array(dtype(0)).dtype
        else:
            self.dtype = dtype = np.array(dtype).dtype
        self.default = np.array(default)
        if self.dtype is not None:
            self.default = np.array(default).astype(dtype)
        elif dtype is None:
            self.dtype = dtype = self.default.dtype
        if shape is None:
            self.shape = ()
        else:
            self.shape = tuple(shape)
        self.ndim = len(self.shape)
        self.wrap_dim = wrap_dim
        self.__data = self.data = OrderedDict()
        self.offset = np.atleast_1d(offset)
        self.index_offset = index_offset
    def _index_offset(self, index, mult=-1):
        if not self.index_offset:
            return index
        index = np.atleast_1d(index)
        _index = []
        offset = np.zeros(index.shape) + self.offset
        for i, o in zip(index, offset):
            if isinstance(i, slice):
                start, stop, step = i.start, i.stop, i.step
                if start is not None:
                    start = int(start + o * mult)
                if stop is not None:
                    stop = int(stop + o * mult)
                _index.append(slice(start, stop, step))
            else:
                _index.append(int(i + o * mult))
        return tuple(_index)
    def _setitem_base(self, index, value):
        """set value to position given in index, where index is a tuple."""
        # adjust number of dimensions
        index = tuple(map(int, index))
        if len(index) > len(self.shape):
            tmp = list(np.array(index) + 1)
            tmp[: len(self.shape)] = self.shape
            self.shape = tuple(tmp)
        # adjust dimensions
        for k, dim in enumerate(np.array(index) + 1):
            if dim > self.shape[k]:
                tmp = list(self.shape)
                tmp[k] = dim
                self.shape = tuple(tmp)
        self.ndim = len(self.shape)
        if self.dtype is None:
            self.dtype = np.array(value).dtype
            self.default = np.array(self.default).astype(self.dtype)
        self.__data[index] = np.atleast_1d([value]).astype(self.dtype)
    def __setitem__(self, index, value):
        """set value to position given in index, where index is a tuple."""
        index = self._index_offset(index, -1)
        if isinstance(value, np.ndarray):
            _index = list(index)
            for k in range(len(value)):
                self._setitem_base(tuple(_index), value[k])
                _index[self.wrap_dim] += 1
        else:
            self._setitem_base(index, value)
    def __getitem__(self, index):
        """get value at position given in index, where index is a tuple."""
        index = self._index_offset(index, -1)
        try:
            return self.dense()[index]
        except IndexError:
            return index * 0 + self.default
    def __delitem__(self, index):
        """index is tuples of element to be deleted."""
        index = self._index_offset(index)
        if index in self.__data:
            del self.__data[index]
    def __binary_op__(self, other, op):
        if isinstance(other, sparray):
            if self.shape == other.shape:
                tmp = copy.deepcopy(self)
                for k in set.difference(set(tmp.__data.keys()), set(other.__data.keys())):
                    tmp.__data[k] = eval('tmp.__data[k].' + op + '(other.default)')
                tmp.default = eval('tmp.default.' + op + '(other.default)')
                for k in list(other.__data.keys()):
                    old_val = tmp.__data.setdefault(k, tmp.default)
                    tmp.__data[k] = eval('old_val.' + op + '(other.__data[k])')
                return tmp
            else:
                raise ValueError('Array sizes do not match. ' + str(self.shape) + ' versus ' + str(other.shape))
        else:
            return eval('self.dense().' + op + '(other)')
    def __unary_op__(self, op):
        tmp = copy.deepcopy(self)
        for k in tmp.__data.keys():
            tmp.__data[k] = eval('tmp.__data[k].' + op + '()')
        tmp.default = eval('tmp.default.' + op + '()')
        return tmp
    def __str__(self):
        return str(self.dense())
[docs]    def dense(self):
        """Convert to dense NumPy array"""
        out = (self.default * np.ones(self.shape)).astype(self.dtype)
        for ind in self.__data:
            out[ind] = self.__data[ind].astype(self.dtype)
        return out 
[docs]    def sum(self):
        """Sum of elements"""
        s = self.default * np.array(self.shape).prod()
        for ind in self.__data:
            s += self.__data[ind] - self.default
        return s 
[docs]    def fortran(self, index, value):
        # Drop leading zeros otherwise the index is evaluated as an octal.
        index = _delete_leading_zeros(index)
        index = eval('(' + re.sub('([0-9]+):', r'\1', index.split('(')[-1].strip(')')) + ')')
        self.__setitem__(index, value)
        return self 
[docs]    def isnan(self, x):
        if np.iterable(x):
            x = np.array(x)
            i = np.where(x == -9223372036854775808)
            x = x.astype(float)
            x[i] = np.nan
            return np.isnan(np.array(x))
        elif np.isnan(x):
            return True
        elif x == np.array(np.nan).astype(np.array(x).dtype):
            return x == np.array(np.nan).astype(np.array(x).dtype) 
[docs]    def fortran_repr(self):
        # print strips
        strips = OrderedDict()
        for item in self.__data.keys():
            item = tuple(item)
            strips.setdefault(item[1:], item)
            strips[item[1:]] = tuple(map(int, np.min([strips[item[1:]], item], 0)))
        # prints
        outputs = OrderedDict()
        if not self.isnan(self.default):
            outputs[''] = self.default
        for item in strips.values():
            tmp = list(item)
            tmp[0] = slice(None)
            tmp = tuple(tmp)
            # do not use self[tmp] to avoid self._index_offset
            dense_tmp = self.dense()[tmp]
            if self.isnan(self.default):
                ix_ = np.where(self.isnan(dense_tmp))[0]
            else:
                ix_ = np.where(dense_tmp == self.default)[0]
            # split where default value is found
            substrips_ = np.split(dense_tmp, ix_)
            substrips = []
            ix = []
            ix_ = [0] + list(ix_)
            for ki, ks in zip(ix_, substrips_):
                if len(ks) > 1:
                    ix.append(ki)
                    ix.append(ki + 1)
                    substrips.append(ks[:1])
                    substrips.append(ks[1:])
                elif len(ks):
                    ix.append(ki)
                    substrips.append(ks)
            # print substrips
            # note: values that are equal to the default value or that are NaN will not be printed
            item = item + self.offset
            for k, v in enumerate(substrips):
                if not len(v):
                    continue
                elif self.isnan(v[0]):
                    continue
                elif v[0] == self.default:
                    continue
                item[0] = ix[k] + self.offset[0]
                if len(item) == 1:
                    outputs['(%d)' % item.astype(int)] = v.astype(self.dtype)
                else:
                    outputs[str(re.sub(' ', '', repr(tuple(item.astype(int)))))] = v.astype(self.dtype)
        return outputs 
    def __str__(self):
        tmp = self.fortran_repr()
        out = []
        for item, value in zip(tmp.keys(), tmp.values()):
            out.append('%s: %s' % (str(item)[1:-1], str(value)))
        return '\n'.join(out)
    def __repr__(self):
        return self.__str__()
    def __tree_repr__(self):
        return self.dense(), []
    def __iter__(self):
        for i in self.dense():
            yield i
    # binary operations
[docs]    def lt(self, other):
        return self.__binary_op__(other, 'lt') 
[docs]    def le(self, other):
        return self.__binary_op__(other, 'le') 
[docs]    def eq(self, other):
        return self.__binary_op__(other, 'eq') 
[docs]    def ge(self, other):
        return self.__binary_op__(other, 'ge') 
[docs]    def gt(self, other):
        return self.__binary_op__(other, 'gt') 
    def __lt__(self, other):
        return self.__binary_op__(other, '__lt__')
    def __le__(self, other):
        return self.__binary_op__(other, '__le__')
    def __eq__(self, other):
        return self.__binary_op__(other, '__eq__')
    def __ne__(self, other):
        return self.__binary_op__(other, '__ne__')
    def __ge__(self, other):
        return self.__binary_op__(other, '__ge__')
    def __gt__(self, other):
        return self.__binary_op__(other, '__gt__')
[docs]    def is_(self, other):
        return self.__binary_op__(other, 'is_') 
[docs]    def is_not(self, other):
        return self.__binary_op__(other, 'is_not') 
[docs]    def add(self, other):
        return self.__binary_op__(other, 'add') 
    def __add__(self, other):
        return self.__binary_op__(other, '__add__')
    def __radd__(self, other):
        return self.__binary_op__(other, '__radd__')
[docs]    def and_(self, other):
        return self.__binary_op__(other, 'and_') 
    def __and__(self, other):
        return self.__binary_op__(other, '__and__')
    def __rand__(self, other):
        return self.__binary_op__(other, '__rand__')
[docs]    def floordiv(self, other):
        return self.__binary_op__(other, 'floordiv') 
    def __floordiv__(self, other):
        return self.__binary_op__(other, '__floordiv__')
    def __rfloordiv__(self, other):
        return self.__binary_op__(other, '__rfloordiv__')
[docs]    def index(self, other):
        return self.__binary_op__(other, 'index') 
    def __index__(self, other):
        return self.__binary_op__(other, '__index__')
[docs]    def lshift(self, other):
        return self.__binary_op__(other, 'lshift') 
    def __lshift__(self, other):
        return self.__binary_op__(other, '__lshift__')
    def __rlshift__(self, other):
        return self.__binary_op__(other, '__rlshift__')
[docs]    def mod(self, other):
        return self.__binary_op__(other, 'mod') 
    def __mod__(self, other):
        return self.__binary_op__(other, '__mod__')
    def __rmod__(self, other):
        return self.__binary_op__(other, '__rmod__')
[docs]    def mul(self, other):
        return self.__binary_op__(other, 'mul') 
    def __mul__(self, other):
        return self.__binary_op__(other, '__mul__')
    def __rmul__(self, other):
        return self.__binary_op__(other, '__rmul__')
[docs]    def matmul(self, other):
        return self.__binary_op__(other, 'matmul') 
    def __matmul__(self, other):
        return self.__binary_op__(other, '__matmul__')
[docs]    def or_(self, other):
        return self.__binary_op__(other, 'or_') 
    def __or__(self, other):
        return self.__binary_op__(other, '__or__')
    def __ror__(self, other):
        return self.__binary_op__(other, '__ror__')
[docs]    def pow(self, other):
        return self.__binary_op__(other, 'pow') 
    def __pow__(self, other):
        return self.__binary_op__(other, '__pow__')
    def __rpow__(self, other):
        return self.__binary_op__(other, '__rpow__')
[docs]    def rshift(self, other):
        return self.__binary_op__(other, 'rshift') 
    def __rshift__(self, other):
        return self.__binary_op__(other, '__rshift__')
    def __rrshift__(self, other):
        return self.__binary_op__(other, '__rrshift__')
[docs]    def sub(self, other):
        return self.__binary_op__(other, 'sub') 
    def __sub__(self, other):
        return self.__binary_op__(other, '__sub__')
    def __rsub__(self, other):
        return self.__binary_op__(other, '__rsub__')
[docs]    def truediv(self, other):
        return self.__binary_op__(other, 'truediv') 
    def __truediv__(self, other):
        return self.__binary_op__(other, '__truediv__')
    def __rtruediv__(self, other):
        return self.__binary_op__(other, '__rtruediv__')
    def __divmod__(self, other):
        return self.__binary_op__(other, '__divmod__')
    def __rdivmod__(self, other):
        return self.__binary_op__(other, '__rdivmod__')
[docs]    def xor(self, other):
        return self.__binary_op__(other, 'xor') 
    def __xor__(self, other):
        return self.__binary_op__(other, '__xor__')
    def __rxor__(self, other):
        return self.__binary_op__(other, '__rxor__')
    # unary operations
[docs]    def bool(self):
        return self.__unary_op__('bool') 
    def __bool__(self):
        return self.__unary_op__('__bool__')
    def __nonzero__(self):
        return self.__unary_op__('__nonzero__')
[docs]    def real(self):
        return self.__unary_op__('real') 
[docs]    def imag(self):
        return self.__unary_op__('imag') 
[docs]    def not_(self):
        return self.__unary_op__('not_') 
    def __not__(self):
        return self.__unary_op__('__not__')
[docs]    def truth(self):
        return self.__unary_op__('truth') 
[docs]    def abs(self):
        return self.__unary_op__('abs') 
    def __abs__(self):
        return self.__unary_op__('__abs__')
[docs]    def inv(self):
        return self.__unary_op__('inv') 
[docs]    def invert(self):
        return self.__unary_op__('invert') 
    def __inv__(self):
        return self.__unary_op__('__inv__')
    def __invert__(self):
        return self.__unary_op__('__invert__')
[docs]    def neg(self):
        return self.__unary_op__('neg') 
    def __neg__(self):
        return self.__unary_op__('__neg__')
[docs]    def pos(self):
        return self.__unary_op__('pos') 
    def __pos__(self):
        return self.__unary_op__('__pos__')
    def __float__(self):
        return self.__unary_op__('__float__')
    def __complex__(self):
        return self.__unary_op__('__complex__')
    def __oct__(self):
        return self.__unary_op__('__oct__')
    def __hex__(self):
        return self.__unary_op__('__hex__')
    def __trunc__(self):
        return self.__unary_op__('__trunc__')
    __hash__ = None
[docs]    def copy(self):
        return copy.deepcopy(self)  
def _delete_leading_zeros(number_str):
    """
    Delete leading zeros from array indices
    :param number_str: A string containing comma separated dimension indices and colon separated ranges and strides, such as '008:010,0001:0002'
    :return: String with leading zeros stripped, such as '8:10,1:2'
    """
    return ','.join(
        [
            ':'.join([colon_split.lstrip('0') if colon_split.lstrip('0') else '0' for colon_split in comma_split.split(':')])
            for comma_split in number_str.split(',')
        ]
    )
# --------------------------------
# --------------------------------
# --------------------------------
############################################
if __name__ == '__main__':
    OMFITsrc = os.path.abspath(os.path.dirname(__file__) + os.sep + '..')
    test_classes_main_header()
    ranged_nl = NamelistFile(
        input_string='''
                             &test
                                 entry(1,1) = 1
                                 entry(2,1:2) = 1, 2
                                 entry(3,1) = 1
                                 entry(3,2) = 2
                                 entry(3,3) = 3
                                 entry(4,1) = 4
                                 entry(5,1:2) = 1, 2
                                 entry(6:7,1) = 6, 7
                             /
                             '''
    )
    assert ranged_nl['test']['entry'][1, 1] == 2  # Note that internal slicing is python-style based on 0
    ranged_nl['test']['entry'][1, 1] = 1
    assert ranged_nl['test']['entry'][1, 1] == 1
    assert ranged_nl['test']['entry'][4, 1] == 2
    ranged_nl.filename = '/tmp/ranged_nl.dat'
    ranged_nl.save()
    ranged_nl2 = NamelistFile('/tmp/ranged_nl.dat')
    ranged_nl2.load()
    failed_nl = NamelistFile(
        input_string='''
                             &test
                                entry(1:2,1:2) = 1
                             /
                             '''
    )
    failed_nl.load()
    foo = NamelistFile(OMFITsrc + '/../samples/k139817.00109')
    foo.load()
    print('---')
    foo['asd(1,1)'] = np.array([1, 2, 3])
    foo['asd(1,2)'] = np.array([4, 5, 6])
    foo.collectArrays()
    A2_Z = list(range(2))
    ILA_Z = list(range(3))
    antennas = list(range(6))
    foo['YGEOANT_A'] = YGEOANT_A = sparray(shape=(max(len(A2_Z), len(ILA_Z)), 6), default=0)
    for a, ant in enumerate(antennas):
        for i in range(len(A2_Z)):
            YGEOANT_A[(i + 1, a + 1)] = (i + 1) * 10 + a + 1
    foo.filename = '/tmp/%s/%s' % (os.environ.get('USER', 'nobody'), os.path.split(foo.filename)[1])
    foo.save()
    foo = NamelistFile(foo.filename)
    with open(foo.filename) as f:
        print(f.read())
    # print(foo)
    # asd=copy.deepcopy(foo)
    # foo.saveas('../samples/inone.03680_saved')
    # with open('../samples/inone.03680_saved','r') as f:
    #     for k in f.readlines():
    #        print(k.strip('\n'))
#     test='''
# because of this line it is assumed that everything outside the namelist blocks is a comment
# !b=1
# ac=4
# &my_namelist
#     hello='1 \$ 1'
#     array=1 2 3 3 4 5 6 6 6 7 8
#     array2=1 2 2*3 4 5 3*6 7 8
#     array3=asd asd asd
#     array4=3*asd
#     array5=
#     aaa(1,1)=4
#     aaa(1, 1)=5
# $END
#  ;this is another comment
# !this is a different comment format
#     '''
#     foo = NamelistFile(input_string=test,bang_comment_symbol='!;')
#     print(foo)
#     print(array_encoder(foo['my_namelist']['array']))
#     print(array_encoder(foo['my_namelist']['array3']))
#
#