def parse_header(self, msg):
        r"""Extract header info from a message.

        Args:
            msg (str): Message to extract header from.

        Returns:
            dict: Message properties.

        """
        if CIS_MSG_HEAD not in msg:
            out = dict(body=msg, size=len(msg))
            return out
        _, header, body = msg.split(CIS_MSG_HEAD)
        out = dict(body=body)
        for x in header.split(HEAD_KEY_SEP):
            k, v = x.split(HEAD_VAL_SEP)
            out[backwards.bytes2unicode(k)] = backwards.bytes2unicode(v)
        for k in ['size', 'as_array', 'stype']:
            if k in out:
                out[k] = int(float(out[k]))
        for k in ['format_str', 'field_names', 'field_units']:
            if k in out:
                out[k] = backwards.unicode2bytes(out[k])
                if k in ['field_names', 'field_units']:
                    out[k] = out[k].split(backwards.unicode2bytes(','))
        # for k in ['format_str']:
        #     if k in out:
        #         out[k] = backwards.unicode2bytes(out[k])
        return out
Exemple #2
0
 def __init__(self, *args, **kwargs):
     super(TestAsciiTableInputDriver_Array, self).__init__(*args, **kwargs)
     self.inst_kwargs['as_array'] = True
     names = [backwards.bytes2unicode(n) for n in self.field_names]
     units = [backwards.bytes2unicode(n) for n in self.field_units]
     self.inst_kwargs['column_names'] = names
     self.inst_kwargs['column_units'] = units
     self.inst_kwargs['use_astropy'] = False
 def __init__(self, *args, **kwargs):
     super(TestAsciiTableOutputDriver_Array, self).__init__(*args, **kwargs)
     self.inst_kwargs['as_array'] = 'True'
     names = [backwards.bytes2unicode(n) for n in self.field_names]
     units = [backwards.bytes2unicode(n) for n in self.field_units]
     self.inst_kwargs['column_names'] = ','.join(names)
     self.inst_kwargs['column_units'] = ','.join(units)
     self.inst_kwargs['use_astropy'] = 'False'
    def read_array(self, names=None):
        r"""Read the table in as an array.

        Args:
            names (list, optional): List of column names to label columns. If
                not provided, existing names are used if they exist. Defaults
                to None.

        Returns:
            np.ndarray: Array of table contents.

        Raises:
            ValueError: If names are provided, but not the same number as
                there are columns.

        """
        if names is None:
            names = self.column_names
        if (names is not None) and (len(names) != self.ncols):
            raise ValueError(
                "The number of names does not match the number of columns")
        if hasattr(self, '_arr'):
            return self._arr
        openned = False
        if not self.is_open:
            self.open()
            openned = True
        if self.use_astropy:
            tab = apy_ascii.read(self.fd,
                                 names=names,
                                 **getattr(self, 'astropy_kwargs', {}))
            arr = tab.as_array()
            typs = [arr.dtype[i].str for i in range(len(arr.dtype))]
            cols = tab.columns
            for i in range(len(arr.dtype)):
                if np.issubdtype(arr.dtype[i], np.dtype('S')):
                    new_typs = copy.copy(typs)
                    new_typs[i] = 'complex'
                    new_dtyp = np.dtype([(c, t)
                                         for c, t in zip(cols, new_typs)])
                    try:
                        arr = arr.astype(new_dtyp)
                    except ValueError:
                        pass
        else:
            arr = np.genfromtxt(self.fd,
                                comments=backwards.bytes2unicode(self.comment),
                                delimiter=backwards.bytes2unicode(self.column),
                                dtype=self.dtype,
                                autostrip=True,
                                names=names)
        if openned:
            self.close()
        return arr
    def format_header(self, header_info):
        r"""Format header info to form a string that should prepend a message.

        Args:
            header_info (dict): Properties that should be included in the header.

        Returns:
            str: Message with header in front.

        """
        header = backwards.bytes2unicode(CIS_MSG_HEAD)
        header_str = {}
        for k, v in header_info.items():
            if isinstance(v, list):
                header_str[k] = ','.join(
                    [backwards.bytes2unicode(x) for x in v])
            elif isinstance(v, backwards.string_types):
                header_str[k] = backwards.bytes2unicode(v)
            else:
                header_str[k] = str(v)
        header += backwards.bytes2unicode(HEAD_KEY_SEP).join([
            '%s%s%s' %
            (backwards.bytes2unicode(k), backwards.bytes2unicode(HEAD_VAL_SEP),
             backwards.bytes2unicode(v)) for k, v in header_str.items()
        ])
        header += backwards.bytes2unicode(CIS_MSG_HEAD)
        return backwards.unicode2bytes(header)
Exemple #6
0
    def run_cmake(self, target=None):
        r"""Run the cmake command on the source.

        Args:
            target (str, optional): Target to build.

        Raises:
            RuntimeError: If there is an error in running cmake.
        
        """
        curdir = os.getcwd()
        os.chdir(self.sourcedir)
        if not os.path.isfile('CMakeLists.txt'):
            os.chdir(curdir)
            self.cleanup()
            raise IOError('No CMakeLists.txt file found in %s.' %
                          self.sourcedir)
        # Configuration
        if target != 'clean':
            config_cmd = ['cmake'] + self.cmakeargs
            config_cmd += ['-H.', self.sourcedir, '-B%s' % self.builddir]
            self.debug(' '.join(config_cmd))
            comp_process = tools.popen_nobuffer(config_cmd)
            output, err = comp_process.communicate()
            exit_code = comp_process.returncode
            if exit_code != 0:
                os.chdir(curdir)
                self.cleanup()
                self.error(backwards.bytes2unicode(output))
                raise RuntimeError("CMake config failed with code %d." %
                                   exit_code)
            self.debug('Config output: \n%s' % output)
        # Build
        build_cmd = ['cmake', '--build', self.builddir, '--clean-first']
        if self.target is not None:
            build_cmd += ['--target', self.target]
        self.info(' '.join(build_cmd))
        comp_process = tools.popen_nobuffer(build_cmd)
        output, err = comp_process.communicate()
        exit_code = comp_process.returncode
        if exit_code != 0:
            os.chdir(curdir)
            self.error(backwards.bytes2unicode(output))
            self.cleanup()
            raise RuntimeError("CMake build failed with code %d." % exit_code)
        self.debug('Build output: \n%s' % output)
        self.debug('Make complete')
        os.chdir(curdir)
Exemple #7
0
    def check_reply_socket_recv(self, msg):
        r"""Check incoming message for reply address.

        Args:
            msg (str): Incoming message to check.

        Returns:
            str: Messages with reply address removed if present.

        """
        if self.direction == 'send':
            return msg, None
        prefix = backwards.format_bytes(backwards.unicode2bytes(':%s:'),
                                        (_reply_msg, ))
        if msg.startswith(prefix):
            _, address, new_msg = msg.split(prefix)
            if address not in self.reply_socket_recv:
                self.reply_socket_recv[address] = self.context.socket(zmq.REQ)
                self.reply_socket_recv[address].setsockopt(zmq.LINGER, 0)
                self.reply_socket_recv[address].connect(address)
                self.register_comm(
                    'REPLY_RECV_' + backwards.bytes2unicode(address),
                    self.reply_socket_recv[address])
                self._n_reply_recv[address] = 0
                self._n_zmq_recv[address] = 0
            self.debug("new recv address: %s", address)
        else:  # pragma: debug
            new_msg = msg
            raise Exception("No reply socket address attached.")
        return new_msg, address
Exemple #8
0
    def func_serialize(self, args):
        r"""Serialize a message.

        Args:
            args (obj): Python object to be serialized.

        Returns:
            bytes, str: Serialized message.

        """
        fd = backwards.StringIO()
        if backwards.PY2:
            args_ = args
        else:
            # For Python 3 and higher, bytes need to be encoded
            args_ = copy.deepcopy(args)
            for c in args.columns:
                if isinstance(args_[c][0], backwards.bytes_type):
                    args_[c] = args_[c].apply(lambda s: s.decode('utf-8'))
        if self.field_names is not None:
            args_.columns = [
                backwards.bytes2unicode(n) for n in self.field_names
            ]
        args_.to_csv(fd,
                     index=False,
                     sep=self.delimiter,
                     mode='wb',
                     encoding='utf8',
                     header=self.write_header)
        out = fd.getvalue()
        fd.close()
        return backwards.unicode2bytes(out)
    def send_dict(self, args_dict, field_order=None, **kwargs):
        r"""Send a message with fields specified in the input dictionary.

        Args:
            args_dict (dict): Dictionary with fields specifying output fields.
            field_order (list, optional): List of fields in the order they
                should be passed to send. If not provided, the fields from
                the serializer are used. If the serializer dosn't have
                field names an error will be raised.
            **kwargs: Additiona keyword arguments are passed to send.

        Returns:
            bool: Success/failure of send.

        Raises:
            RuntimeError: If the field order can not be determined.

        """
        if field_order is None:
            if self.serializer.field_names is not None:
                field_order = [
                    backwards.bytes2unicode(n) for n in self.serializer.field_names]
            elif len(args_dict) <= 1:
                field_order = [k for k in args_dict.keys()]
            else:  # pragma: debug
                raise RuntimeError("Could not determine the field order.")
        args = (serialize.dict2pandas(args_dict, order=field_order), )
        return self.send(*args, **kwargs)
Exemple #10
0
def test_format_bytes():
    r"""Test formating of bytes string."""
    s0 = "%s, %s"
    ans = "one, one"
    arg0 = "one"
    args = (backwards.unicode2bytes(arg0), backwards.bytes2unicode(arg0))
    for cvt in [backwards.unicode2bytes, backwards.bytes2unicode]:
        res = backwards.format_bytes(cvt(s0), args)
        nt.assert_equal(res, cvt(ans))
 def test_send_recv_dict(self):
     r"""Test send/recv Pandas data frame as dict."""
     msg_send = serialize.pandas2dict(self.msg_short)
     names = [backwards.bytes2unicode(n) for n in self.field_names]
     flag = self.send_instance.send_dict(msg_send)
     assert (flag)
     flag, msg_recv = self.recv_instance.recv_dict()
     assert (flag)
     msg_recv = serialize.dict2pandas(msg_recv, order=names)
     self.assert_msg_equal(msg_recv, self.msg_short)
Exemple #12
0
 def test_send_recv_dict(self):
     r"""Test send/recv numpy array as dict."""
     msg_send = serialize.numpy2dict(self.msg_short)
     names = [backwards.bytes2unicode(n) for n in self.field_names]
     flag = self.send_instance.send_dict(msg_send, field_order=names)
     assert (flag)
     flag, msg_recv = self.recv_instance.recv_dict()
     assert (flag)
     msg_recv = serialize.dict2numpy(msg_recv, order=names)
     self.assert_msg_equal(msg_recv, self.msg_short)
Exemple #13
0
 def test_send_recv_dict(self):
     r"""Test send/recv numpy array as dict."""
     msg_send = {
         backwards.bytes2unicode(k): v
         for k, v in zip(self.field_names, self.msg_short)
     }
     flag = self.send_instance.send_dict(msg_send)
     assert (flag)
     flag, msg_recv = self.recv_instance.recv_dict()
     assert (flag)
     nt.assert_equal(msg_recv, msg_send)
Exemple #14
0
 def _close_backlog(self, wait=False):
     r"""Close the backlog thread and the reply sockets."""
     super(ZMQComm, self)._close_backlog(wait=wait)
     if self.direction == 'send':
         if (self.reply_socket_send is not None):
             self.reply_socket_send.close(linger=0)  # self.zmq_sleeptime)
             self.unregister_comm("REPLY_SEND_" + self.reply_socket_address)
     else:
         for k, socket in self.reply_socket_recv.items():
             socket.close(linger=0)
             self.unregister_comm("REPLY_RECV_" +
                                  backwards.bytes2unicode(k))
Exemple #15
0
 def set_reply_socket_recv(self, address):
     r"""Set the recv reply socket if the address dosn't exist."""
     if address not in self.reply_socket_recv:
         s = self.context.socket(zmq.REQ)
         s.setsockopt(zmq.LINGER, 0)
         s.connect(address)
         self.register_comm(
             'REPLY_RECV_' + backwards.bytes2unicode(address), s)
         with self.reply_socket_lock:
             self._n_reply_recv[address] = 0
             self._n_zmq_recv[address] = 0
             self.reply_socket_recv[address] = s
         self.debug("new recv address: %s", address)
     return address
def cformat2pyscanf(cfmt):
    r"""Convert a c format specification string to a version that the
    python scanf module can use.

    Args:
        cfmt (str): C format specification string.

    Returns:
        str: Version of cfmt that can be parsed by scanf.

    Raises:
        TypeError: if cfmt is not a bytes/str.
        ValueError: If the c format does not begin with '%'.
        ValueError: If the c format does not contain type info.

    """
    if not isinstance(cfmt, backwards.bytes_type):
        raise TypeError("Input must be of type %s." % backwards.bytes_type)
    elif not cfmt.startswith(_fmt_char):
        raise ValueError("Provided C format string (%s) " % cfmt +
                         "does not start with '%'")
    elif len(cfmt) == 1:
        raise ValueError("Provided C format string (%s) " % cfmt +
                         "does not contain type info")
    # Hacky, but necessary to handle concatenation of a single byte
    cfmt_str = backwards.bytes2unicode(cfmt)
    if cfmt_str[-1] == 'j':
        # Handle complex format specifier
        out = '%g%+gj'
    else:
        out = backwards.bytes2unicode(_fmt_char)
        out += cfmt_str[-1]
        out = out.replace('h', '')
        out = out.replace('l', '')
        out = out.replace('64', '')
    return backwards.unicode2bytes(out)
    def _send(self, msg):
        r"""Write message to a file.

        Args:
            msg (bytes, str): Data to write to the file.

        Returns:
            bool: Success or failure of writing to the file.

        """
        if msg != self.eof_msg:
            if not self.open_as_binary:
                msg = backwards.bytes2unicode(msg)
            self.fd.write(msg)
        self.fd.flush()
        return True
Exemple #18
0
def cformat2pyscanf(cfmt):
    r"""Convert a c format specification string to a version that the
    python scanf module can use.

    Args:
        cfmt (str, bytes, list): C format specification string or list of format
            strings.

    Returns:
        str, bytes, list: Version of cfmt or list of cfmts that can be parsed by
            scanf.

    Raises:
        TypeError: If cfmt is not a bytes/str/list.
        ValueError: If there are not an format codes in the format string.

    """
    if not (isinstance(cfmt, list) or isinstance(cfmt, backwards.string_types)):
        raise TypeError("Input must be a string, bytes string, or list, not %s" %
                        type(cfmt))
    if isinstance(cfmt, list):
        return [cformat2pyscanf(f) for f in cfmt]
    cfmt_out = backwards.bytes2unicode(cfmt)
    fmt_list = extract_formats(cfmt_out)
    if len(fmt_list) == 0:
        raise ValueError("Could not locate any format codes in the "
                         + "provided format string (%s)." % cfmt)
    for cfmt_str in fmt_list:
        # Hacky, but necessary to handle concatenation of a single byte
        if cfmt_str[-1] == 's':
            out = '%s'
        else:
            out = cfmt_str
        # if cfmt_str[-1] == 'j':
        #     # Handle complex format specifier
        #     out = '%g%+gj'
        # else:
        #     out = backwards.bytes2unicode(_fmt_char)
        #     out += cfmt_str[-1]
        #     out = out.replace('h', '')
        #     out = out.replace('l', '')
        #     out = out.replace('64', '')
        cfmt_out = cfmt_out.replace(cfmt_str, out, 1)
    if isinstance(cfmt, backwards.bytes_type):
        cfmt_out = backwards.unicode2bytes(cfmt_out)
    return cfmt_out
Exemple #19
0
def is_unit(ustr):
    r"""Determine if a string is a valid unit.

    Args:
        ustr: String representation to test.

    Returns:
        bool: True if the string is a valid unit. False otherwise.

    """
    ustr = backwards.bytes2unicode(ustr)
    if ustr == 'n/a':
        return True
    try:
        _ureg(ustr)
    except pint.errors.UndefinedUnitError:
        return False
    return True
Exemple #20
0
def is_unit(ustr):
    r"""Determine if a string is a valid unit.

    Args:
        ustr (str): String representation to test.

    Returns:
        bool: True if the string is a valid unit. False otherwise.

    """
    ustr = backwards.bytes2unicode(ustr)
    if is_null_unit(ustr):
        return True
    try:
        as_unit(ustr)
    except ValueError:
        return False
    return True
Exemple #21
0
def extract_formats(fmt_str):
    r"""Locate format codes within a format string.

    Args:
        fmt_str (str, bytes): Format string.

    Returns:
        list: List of identified format codes.

    """
    fmt_regex = (
        "%(?:\\d+\\$)?[+-]?(?:[ 0]|\'.{1})?-?\\d*(?:\\.\\d+)?"
        + "[lhjztL]*(?:64)?[bcdeEufFgGosxXi]"
        + "(?:%(?:\\d+\\$)?[+-](?:[ 0]|\'.{1})?-?\\d*(?:\\.\\d+)?"
        + "[lhjztL]*[eEfFgG]j)?")
    out = re.findall(fmt_regex, backwards.bytes2unicode(fmt_str))
    if isinstance(fmt_str, backwards.bytes_type):
        out = [backwards.unicode2bytes(f) for f in out]
    return out
Exemple #22
0
def test_cformat2nptype():
    r"""Test conversion from C format string to numpy dtype."""
    for a, b in map_cformat2nptype:
        if isinstance(a, str):
            a = [a]
        for _ia in a:
            if _ia.startswith(backwards.bytes2unicode(AsciiTable._fmt_char)):
                ia = backwards.unicode2bytes(_ia)
            else:
                ia = AsciiTable._fmt_char + backwards.unicode2bytes(_ia)
            assert_equal(AsciiTable.cformat2nptype(ia), np.dtype(b).str)
    assert_raises(TypeError, AsciiTable.cformat2nptype, 0)
    assert_raises(ValueError, AsciiTable.cformat2nptype,
                  backwards.unicode2bytes('s'))
    assert_raises(ValueError, AsciiTable.cformat2nptype,
                  backwards.unicode2bytes('%'))
    for a in unsupported_nptype:
        assert_raises(ValueError, AsciiTable.cformat2nptype,
                      backwards.unicode2bytes('%' + a))
Exemple #23
0
def print_encoded(msg, *args, **kwargs):
    r"""Print bytes to stdout, encoding if possible.

    Args:
        msg (str, bytes): Message to print.
        *args: Additional arguments are passed to print.
        **kwargs: Additional keyword arguments are passed to print.


    """
    try:
        print(backwards.bytes2unicode(msg), *args, **kwargs)
    except UnicodeEncodeError:  # pragma: debug
        logging.debug("sys.stdout.encoding = %s, cannot print unicode",
                      sys.stdout.encoding)
        kwargs.pop('end', None)
        try:
            print(msg, *args, **kwargs)
        except UnicodeEncodeError:  # pragma: debug
            print(backwards.unicode2bytes(msg), *args, **kwargs)
    def _send(self, msg):
        r"""Write message to a file.

        Args:
            msg (bytes, str): Data to write to the file.

        Returns:
            bool: Success or failure of writing to the file.

        """
        if msg != self.eof_msg:
            if not self.open_as_binary:
                msg = backwards.bytes2unicode(msg)
            self.fd.write(msg)
            if self.append == 'ow':
                self.fd.truncate()
        self.fd.flush()
        if msg != self.eof_msg and self.is_series:
            self.advance_in_series()
            self.debug("Advanced to %d", self._series_index)
        return True
Exemple #25
0
    def writeline_full(self, line):
        r"""Write a line to the file in its present state. If it is not open,
        nothing happens.

        Args:
            line (str/bytes): Line to be written.

        Raises:
            TypeError: If line is not the correct bytes type.

        """
        if not self.is_open:
            print("The file is not open. Nothing written.")
            return
        if self.open_as_binary:
            line = backwards.unicode2bytes(line)
        else:
            line = backwards.bytes2unicode(line)
        # if not isinstance(line, backwards.bytes_type):
        #     raise TypeError("Line must be of type %s" % backwards.bytes_type)
        self.fd.write(line)
Exemple #26
0
def array_to_table(arrs, fmt_str, use_astropy=False):
    r"""Serialize an array as an ASCII table.

    Args:
        arrs (np.ndarray, list, tuple): Structured array or list/tuple of
            arrays that contain table information.
        fmt_str (str, bytes): Format string that should be used to structure
            the ASCII array.
        use_astropy (bool, optional): If True, astropy will be used to format
            the table if it is installed. Defaults to False.

    Returns:
        bytes: ASCII table.

    """
    if not _use_astropy:
        use_astropy = False
    dtype = cformat2nptype(fmt_str)
    info = format2table(fmt_str)
    arr1 = consolidate_array(arrs, dtype=dtype)
    if use_astropy:
        fd = backwards.StringIO()
        table = apy_Table(arr1)
        delimiter = info['delimiter']
        delimiter = backwards.bytes2unicode(delimiter)
        apy_ascii.write(table, fd, delimiter=delimiter,
                        format='no_header')
        out = backwards.unicode2bytes(fd.getvalue())
    else:
        fd = backwards.BytesIO()
        for ele in arr1:
            line = format_message(ele.tolist(), fmt_str)
            fd.write(line)
        # fmt = fmt_str.split(info['newline'])[0]
        # np.savetxt(fd, arr1,
        #            fmt=fmt, delimiter=info['delimiter'],
        #            newline=info['newline'], header='')
        out = fd.getvalue()
    fd.close()
    return out
Exemple #27
0
def test_cformat2nptype():
    r"""Test conversion from C format string to numpy dtype."""
    for a, b in map_cformat2nptype:
        if isinstance(a, str):
            a = [a]
        for _ia in a:
            if _ia.startswith(backwards.bytes2unicode(serialize._fmt_char)):
                ia = backwards.unicode2bytes(_ia)
            else:
                ia = serialize._fmt_char + backwards.unicode2bytes(_ia)
            nt.assert_equal(serialize.cformat2nptype(ia), np.dtype(b))  # .str)
            # nt.assert_equal(serialize.cformat2nptype(ia), np.dtype(b).str)
    nt.assert_raises(TypeError, serialize.cformat2nptype, 0)
    nt.assert_raises(ValueError, serialize.cformat2nptype,
                     backwards.unicode2bytes('s'))
    nt.assert_raises(ValueError, serialize.cformat2nptype,
                     backwards.unicode2bytes('%'))
    nt.assert_raises(ValueError, serialize.cformat2nptype,
                     '%d\t%f', names=['one'])
    for a in unsupported_nptype:
        nt.assert_raises(ValueError, serialize.cformat2nptype,
                         backwards.unicode2bytes('%' + a))
Exemple #28
0
    def deserialize(self, msg):
        r"""Deserialize a message.

        Args:
            msg (str, bytes): Message to be deserialized.

        Returns:
            tuple(obj, dict): Deserialized message and header information.

        Raises:
            TypeError: If msg is not bytes type (str on Python 2).

        """
        if not isinstance(msg, backwards.bytes_type):
            raise TypeError("Message to be deserialized is not bytes type.")
        if len(msg) == 0:
            obj = self._empty_msg
        else:
            metadata, data = msg.split(self.sep)
            metadata = json.loads(backwards.bytes2unicode(metadata))
            obj = self.__class__.decode(metadata, data, self._typedef)
        return obj
Exemple #29
0
def test_bytes2unicode():
    r"""Ensure what results is proper bytes type."""
    if backwards.PY2:  # pragma: Python 2
        res = backwards.unicode_type('hello')
        backwards.assert_unicode(res)
        nt.assert_equal(backwards.bytes2unicode('hello'), res)
        nt.assert_equal(backwards.bytes2unicode(unicode('hello')), res)
        nt.assert_equal(backwards.bytes2unicode(bytearray('hello', 'utf-8')),
                        res)
        nt.assert_raises(TypeError, backwards.bytes2unicode, 1)
    else:  # pragma: Python 3
        res = 'hello'
        backwards.assert_unicode(res)
        nt.assert_equal(backwards.bytes2unicode('hello'), res)
        nt.assert_equal(backwards.bytes2unicode(b'hello'), res)
        nt.assert_equal(backwards.bytes2unicode(bytearray('hello', 'utf-8')),
                        res)
        nt.assert_raises(TypeError, backwards.bytes2unicode, 1)
Exemple #30
0
    def func_deserialize(self, msg):
        r"""Deserialize a message.

        Args:
            msg (str, bytes): Message to be deserialized.

        Returns:
            dict: Deserialized Python dictionary.

        """
        if len(msg) == 0:
            out = self.empty_msg
        else:
            out = dict()
            lines = backwards.bytes2unicode(msg).split(self.newline)
            for l in lines:
                kv = l.split(self.delimiter)
                if len(kv) <= 1:
                    continue
                elif len(kv) == 2:
                    out[kv[0]] = eval(kv[1])
                else:
                    raise ValueError("Line has more than one delimiter: " + l)
        return out