def __init__(self, format_string, strip_strings=True): """ Sets the format string and determines how we will read and write strings using this format """ self.format = format_string self.strip_strings = strip_strings # for ease of copying # Define a function that processes all arguments prior to adding them to # the returned list. By default, do nothing, but this allows us to # optionally strip whitespace from strings. self.process_method = lambda x: x if FortranFormat.strre.match(format_string): rematch = FortranFormat.strre.match(format_string) # replace our write() method with write_string to force left-justify self.type, self.write = str, self._write_string nitems, itemlen = rematch.groups() if nitems is None: self.nitems = 1 else: self.nitems = int(nitems) self.itemlen = int(itemlen) self.fmt = '%s' # See if we want to strip the strings if strip_strings: self.process_method = lambda x: x.strip() elif FortranFormat.intre.match(format_string): self.type = int rematch = FortranFormat.intre.match(format_string) nitems, itemlen = rematch.groups() if nitems is None: self.nitems = 1 else: self.nitems = int(nitems) self.itemlen = int(itemlen) self.fmt = '%%%dd' % self.itemlen elif FortranFormat.floatre.match(format_string): self.type = float rematch = FortranFormat.floatre.match(format_string) nitems, itemlen, num_decimals = rematch.groups() if nitems is None: self.nitems = 1 else: self.nitems = int(nitems) self.itemlen = int(itemlen) self.num_decimals = int(num_decimals) if 'F' in format_string.upper(): self.fmt = '%%%s.%sF' % (self.itemlen, self.num_decimals) else: self.fmt = '%%%s.%sE' % (self.itemlen, self.num_decimals) elif FortranFormat.floatre2.match(format_string): self.type = float rematch = FortranFormat.floatre2.match(format_string) nitems, itemlen, num_decimals = rematch.groups() if nitems is None: self.nitems = 1 else: self.nitems = int(nitems) self.itemlen = int(itemlen) self.num_decimals = int(num_decimals) if 'F' in format_string.upper(): self.fmt = '%%%s.%sF' % (self.itemlen, self.num_decimals) else: self.fmt = '%%%s.%sE' % (self.itemlen, self.num_decimals) else: # We tried... now just use the fortranformat package self._reader = FortranRecordReader(format_string) self._writer = FortranRecordWriter(format_string) self.write = self._write_ffwriter self.read = self._read_ffreader
class FortranFormat(object): """ Processes Fortran format strings according to the Fortran specification for such formats. This object handles reading and writing data with any valid Fortran format. It does this by using the `fortranformat` project [https://bitbucket.org/brendanarnold/py-fortranformat]. However, while `fortranformat` is very general and adheres well to the standard, it is very slow. As a result, simple, common format strings have been optimized and processes reads and writes between 3 and 5 times faster. The format strings (case-insensitive) of the following form (where # can be replaced by any number) are optimized: - #E#.# - #D#.# - #F#.# - #(F#.#) - #a# - #I# Parameters ---------- format_string : str The Fortran Format string to process strip_strings : bool=True If True, strings are stripped before being processed by stripping (only) trailing whitespace """ strre = re.compile(r'(\d+)?a(\d+)$', re.I) intre = re.compile(r'(\d+)?i(\d+)$', re.I) floatre = re.compile(r'(\d+)?[edf](\d+)\.(\d+)$', re.I) floatre2 = re.compile(r'(\d+)?\([edf](\d+)\.(\d+)\)$', re.I) #=================================================== def __init__(self, format_string, strip_strings=True): """ Sets the format string and determines how we will read and write strings using this format """ self.format = format_string self.strip_strings = strip_strings # for ease of copying # Define a function that processes all arguments prior to adding them to # the returned list. By default, do nothing, but this allows us to # optionally strip whitespace from strings. self.process_method = lambda x: x if FortranFormat.strre.match(format_string): rematch = FortranFormat.strre.match(format_string) # replace our write() method with write_string to force left-justify self.type, self.write = str, self._write_string nitems, itemlen = rematch.groups() if nitems is None: self.nitems = 1 else: self.nitems = int(nitems) self.itemlen = int(itemlen) self.fmt = '%s' # See if we want to strip the strings if strip_strings: self.process_method = lambda x: x.strip() elif FortranFormat.intre.match(format_string): self.type = int rematch = FortranFormat.intre.match(format_string) nitems, itemlen = rematch.groups() if nitems is None: self.nitems = 1 else: self.nitems = int(nitems) self.itemlen = int(itemlen) self.fmt = '%%%dd' % self.itemlen elif FortranFormat.floatre.match(format_string): self.type = float rematch = FortranFormat.floatre.match(format_string) nitems, itemlen, num_decimals = rematch.groups() if nitems is None: self.nitems = 1 else: self.nitems = int(nitems) self.itemlen = int(itemlen) self.num_decimals = int(num_decimals) if 'F' in format_string.upper(): self.fmt = '%%%s.%sF' % (self.itemlen, self.num_decimals) else: self.fmt = '%%%s.%sE' % (self.itemlen, self.num_decimals) elif FortranFormat.floatre2.match(format_string): self.type = float rematch = FortranFormat.floatre2.match(format_string) nitems, itemlen, num_decimals = rematch.groups() if nitems is None: self.nitems = 1 else: self.nitems = int(nitems) self.itemlen = int(itemlen) self.num_decimals = int(num_decimals) if 'F' in format_string.upper(): self.fmt = '%%%s.%sF' % (self.itemlen, self.num_decimals) else: self.fmt = '%%%s.%sE' % (self.itemlen, self.num_decimals) else: # We tried... now just use the fortranformat package self._reader = FortranRecordReader(format_string) self._writer = FortranRecordWriter(format_string) self.write = self._write_ffwriter self.read = self._read_ffreader #=================================================== def __copy__(self): return type(self)(self.format, self.strip_strings) #=================================================== def __str__(self): return self.format def __repr__(self): return "<%s: %s>" % (type(self).__name__, self.format) #=================================================== def write(self, items, dest): """ Writes an iterable of data (or a single item) to the passed file-like object Parameters ---------- items : iterable or single float/str/int These are the objects to write in this format. The types of each item should match the type specified in this Format for that argument dest : file or file-like This is the file to write the data to. It must have a `write` method or an AttributeError will be raised Notes ----- This method may be replaced with _write_string (for #a#-style formats) or _write_ffwriter in the class initializer if no optimization is provided for this format, but the call signatures and behavior are the same for each of those functions. """ if hasattr(items, '__iter__') and not isinstance(items, string_types): mod = self.nitems - 1 for i, item in enumerate(items): dest.write(self.fmt % item) if i % self.nitems == mod: dest.write('\n') if i % self.nitems != mod: dest.write('\n') else: dest.write(self.fmt % items) dest.write('\n') #=================================================== def _write_string(self, items, dest): """ Writes a list/tuple of strings """ if hasattr(items, '__iter__') and not isinstance(items, string_types): mod = self.nitems - 1 for i, item in enumerate(items): dest.write((self.fmt % item).ljust(self.itemlen)) if i % self.nitems == mod: dest.write('\n') if i % self.nitems != mod: dest.write('\n') else: dest.write((self.fmt % items).ljust(self.itemlen)) dest.write('\n') #=================================================== def _read_nostrip(self, line): """ Reads the line and returns converted data. Special-cased for flags that may contain 'blank' data. ugh. """ line = line.rstrip('\n') nitems = int(ceil(len(line) / self.itemlen)) ret = [0 for i in range(nitems)] start, end = 0, self.itemlen for i in range(nitems): ret[i] = self.process_method(self.type(line[start:end])) start = end end += self.itemlen return ret #=================================================== def read(self, line): """ Reads the line and returns the converted data """ line = line.rstrip() nitems = int(ceil(len(line) / self.itemlen)) ret = [0 for i in range(nitems)] start, end = 0, self.itemlen for i in range(nitems): ret[i] = self.process_method(self.type(line[start:end])) start = end end += self.itemlen return ret #=================================================== def _read_ffreader(self, line): """ Reads the line and returns the converted data """ return self._reader.read(line.rstrip()) #=================================================== def _write_ffwriter(self, items, dest): dest.write('%s\n' % self._writer.write(items)) #=================================================== def __eq__(self, other): return (self.format == other.format and self.strip_strings == other.strip_strings) #=================================================== def __hash__(self): return hash((self.format, self.strip_strings)) #=================================================== def __getstate__(self): return dict(format=self.format, strip_strings=self.strip_strings) def __setstate__(self, d): self.__init__(d['format'], d['strip_strings'])
class FortranFormat(object): """ Processes Fortran format strings according to the Fortran specification for such formats. This object handles reading and writing data with any valid Fortran format. It does this by using the `fortranformat` project [https://bitbucket.org/brendanarnold/py-fortranformat]. However, while `fortranformat` is very general and adheres well to the standard, it is very slow. As a result, simple, common format strings have been optimized and processes reads and writes between 3 and 5 times faster. The format strings (case-insensitive) of the following form (where # can be replaced by any number) are optimized: - #E#.# - #D#.# - #F#.# - #(F#.#) - #a# - #I# Parameters ---------- format_string : str The Fortran Format string to process strip_strings : bool=True If True, strings are stripped before being processed by stripping (only) trailing whitespace """ strre = re.compile(r'(\d+)?a(\d+)$', re.I) intre = re.compile(r'(\d+)?i(\d+)$', re.I) floatre = re.compile(r'(\d+)?[edf](\d+)\.(\d+)$', re.I) floatre2 = re.compile(r'(\d+)?\([edf](\d+)\.(\d+)\)$', re.I) #=================================================== def __init__(self, format_string, strip_strings=True): """ Sets the format string and determines how we will read and write strings using this format """ self.format = format_string self.strip_strings = strip_strings # for ease of copying # Define a function that processes all arguments prior to adding them to # the returned list. By default, do nothing, but this allows us to # optionally strip whitespace from strings. self.process_method = lambda x: x if FortranFormat.strre.match(format_string): rematch = FortranFormat.strre.match(format_string) # replace our write() method with write_string to force left-justify self.type, self.write = str, self._write_string nitems, itemlen = rematch.groups() if nitems is None: self.nitems = 1 else: self.nitems = int(nitems) self.itemlen = int(itemlen) self.fmt = '%s' # See if we want to strip the strings if strip_strings: self.process_method = lambda x: x.strip() elif FortranFormat.intre.match(format_string): self.type = int rematch = FortranFormat.intre.match(format_string) nitems, itemlen = rematch.groups() if nitems is None: self.nitems = 1 else: self.nitems = int(nitems) self.itemlen = int(itemlen) self.fmt = '%%%dd' % self.itemlen elif FortranFormat.floatre.match(format_string): self.type = float rematch = FortranFormat.floatre.match(format_string) nitems, itemlen, num_decimals = rematch.groups() if nitems is None: self.nitems = 1 else: self.nitems = int(nitems) self.itemlen = int(itemlen) self.num_decimals = int(num_decimals) if 'F' in format_string.upper(): self.fmt = '%%%s.%sF' % (self.itemlen, self.num_decimals) else: self.fmt = '%%%s.%sE' % (self.itemlen, self.num_decimals) elif FortranFormat.floatre2.match(format_string): self.type = float rematch = FortranFormat.floatre2.match(format_string) nitems, itemlen, num_decimals = rematch.groups() if nitems is None: self.nitems = 1 else: self.nitems = int(nitems) self.itemlen = int(itemlen) self.num_decimals = int(num_decimals) if 'F' in format_string.upper(): self.fmt = '%%%s.%sF' % (self.itemlen, self.num_decimals) else: self.fmt = '%%%s.%sE' % (self.itemlen, self.num_decimals) else: # We tried... now just use the fortranformat package self._reader = FortranRecordReader(format_string) self._writer = FortranRecordWriter(format_string) self.write = self._write_ffwriter self.read = self._read_ffreader #=================================================== def __copy__(self): return type(self)(self.format, self.strip_strings) #=================================================== def __str__(self): return self.format def __repr__(self): return "<%s: %s>" % (type(self).__name__, self.format) #=================================================== def write(self, items, dest): """ Writes an iterable of data (or a single item) to the passed file-like object Parameters ---------- items : iterable or single float/str/int These are the objects to write in this format. The types of each item should match the type specified in this Format for that argument dest : file or file-like This is the file to write the data to. It must have a `write` method or an AttributeError will be raised Notes ----- This method may be replaced with _write_string (for #a#-style formats) or _write_ffwriter in the class initializer if no optimization is provided for this format, but the call signatures and behavior are the same for each of those functions. """ if hasattr(items, '__iter__') and not isinstance(items, string_types): mod = self.nitems - 1 for i, item in enumerate(items): dest.write(self.fmt % item) if i % self.nitems == mod: dest.write('\n') if i % self.nitems != mod: dest.write('\n') else: dest.write(self.fmt % item) dest.write('\n') #=================================================== def _write_string(self, items, dest): """ Writes a list/tuple of strings """ if hasattr(items, '__iter__') and not isinstance(items, string_types): mod = self.nitems - 1 for i, item in enumerate(items): dest.write((self.fmt % item).ljust(self.itemlen)) if i % self.nitems == mod: dest.write('\n') if i % self.nitems != mod: dest.write('\n') else: dest.write((self.fmt % item).ljust(self.itemlen)) dest.write('\n') #=================================================== def _read_nostrip(self, line): """ Reads the line and returns converted data. Special-cased for flags that may contain 'blank' data. ugh. """ line = line.rstrip('\n') nitems = int(ceil(len(line) / self.itemlen)) ret = [0 for i in range(nitems)] start, end = 0, self.itemlen for i in range(nitems): ret[i] = self.process_method(self.type(line[start:end])) start = end end += self.itemlen return ret #=================================================== def read(self, line): """ Reads the line and returns the converted data """ line = line.rstrip() nitems = int(ceil(len(line) / self.itemlen)) ret = [0 for i in range(nitems)] start, end = 0, self.itemlen for i in range(nitems): ret[i] = self.process_method(self.type(line[start:end])) start = end end += self.itemlen return ret #=================================================== def _read_ffreader(self, line): """ Reads the line and returns the converted data """ return self._reader.read(line.rstrip()) #=================================================== def _write_ffwriter(self, items, dest): dest.write('%s\n' % self._writer.write(items))