# nobodd.tools

This module houses a series of miscellaneous functions which did not fit particularly well anywhere else and are needed across a variety of modules. They should never be needed by developers using nobodd as an application or a library, but are documented in case they are useful.

nobodd.tools.labels(desc)

  Given the description of a C structure in desc, returns a tuple of the labels.

  The `str` desc must contain one entry per line (blank lines are ignored) where each entry consists of whitespace separated type (in Python `struct` format) and label. For example:

      >>> EBPB = '''
      B     drive_number
      1x    reserved
      B     extended_boot_sig
      4s    volume_id
      11s   volume_label
      8s    file_system
      '''
      >>> labels(EBPB)
      ('drive_number', 'extended_boot_sig', 'volume_id', 'volume_label',
      'file_system')

  Note the amount of whitespace is arbitrary, and further that any entries with the type “x” (which is used to indicate padding) will be excluded from the result (“reserved” is missing from the result tuple above).

  The corresponding function `formats()` can be used to obtain a tuple of the types.

nobodd.tools.formats(desc, prefix='<')

  Given the description of a C structure in desc, returns a concatenated `str` of the types with an optional prefix (for endianness).

  The `str` desc must contain one entry per line (blank lines are ignored) where each entry consists of whitespace separated type (in Python `struct` format) and label. For example:

      >>> EBPB = '''
      B     drive_number
      1x    reserved
      B     extended_boot_sig
      4s    volume_id
      11s   volume_label
      8s    file_system
      '''
      >>> formats(EBPB)
      '<B1xB4s11s8s'

  Note the amount of whitespace is arbitrary, and further that any entries with the type “x” (which is used to indicate padding) are not excluded (unlike in `labels()`).

  The corresponding function `labels()` can be used to obtain a tuple of the labels.

nobodd.tools.get_best_family(host, port)

  Given a host name and a port specification (either a number or a service name), returns the network family (e.g. `socket.AF_INET`) and socket address to listen on as a tuple.

nobodd.tools.format_address(address)

  Given a socket address, return a suitable `str` representation of it.

  Specifically, for IP4 addresses a simple “host:port” representation is used. For IP6 addresses (which typically incorporate “:” in the host portion), a “[host]:port” variant is used.

nobodd.tools.decode_timestamp(date, time, cs=0, tz=datetime.timezone.utc)

  Given the integers date, time, and optionally cs (from various fields in `DirectoryEntry`), return a `datetime` with the decoded timestamp. The returned value will have the `tzinfo` set by tz.

nobodd.tools.encode_timestamp(ts)

  Given a `datetime`, encode it as a FAT-compatible triple of three 16-bit integers representing (date, time, 1/100th seconds).

nobodd.tools.any_match(s, expressions)

  Given a `str` s, and expressions, a sequence of compiled regexes, return the `re.Match` object from the first regex that matches s. If no regexes match, return `None`.

nobodd.tools.exclude(ranges, value)

  Given a list non-overlapping of ranges, sorted in ascending order, this function modifies the range containing value (an integer, which must belong to one and only one range in the list) to exclude it.

nobodd.tools.open_file(arg, mode='w')

  A context manager for handling files specified on the command line. Handles returning a standard stream when arg is “-”, and closes files when the context exits (if required).

class nobodd.tools.BufferedTranscoder(stream, output_encoding, input_encoding=None, errors='strict')

  A read-only transcoder, somewhat similar to `codecs.StreamRecoder`, but which strictly obeys the definition of the `read` method (with internal buffering).

  This class is primarily intended for use in `netascii` encoded transfers where it is used to transcode the underlying file stream into netascii encoding for the TFTP server.

  The built-in `codecs.StreamRecoder` class would seem ideal for this but for one issue: under certain circumstances (including those involved in netascii encoding), it violates the contract of the `read` method by returning more bytes than requested. For example:

      >>> import io, codecs
      >>> latin1_stream = io.BytesIO('abcdé'.encode('latin-1'))
      >>> utf8_stream = codecs.StreamRecoder(latin1_stream,
      ... codecs.getencoder('utf-8'), codecs.getdecoder('utf-8'),
      ... codecs.getreader('latin-1'), codecs.getwriter('latin-1'))
      >>> utf8_stream.read(3)
      b'abc'
      >>> utf8_stream.read(1)
      b'd'
      >>> utf8_stream.read(1)
      b'\xc3\xa9'

  This is alluded to in the documentation of `StreamReader.read` so it probably isn’t a bug, but it is rather inconvenient when the caller is looking to fill a network packet of a specific size, and thus expects not to over-run.

  This class implements a rather simpler recoder, which is read-only, does not permit seeking, but by use of an internal buffer, guarantees that the `read()` method (and associated methods like `readinto()`) will not return more bytes than requested.

  It is constructed with the underlying stream, the name of the output_encoding, the name of the input_encoding (which defaults to the output_encoding when not specified), and the errors mode to use with the codecs. For example:

      >>> import io
      >>> from nobodd.tools import BufferedTranscoder
      >>> latin1_stream = io.BytesIO('abcdé'.encode('latin-1'))
      >>> utf8_stream = BufferedTranscoder(latin1_stream, 'utf-8', 'latin-1')
      >>> utf8_stream.read(4)
      b'abcd'
      >>> utf8_stream.read(1)
      b'\xc3'
      >>> utf8_stream.read(1)
      b'\xa9'

  readable()

    Return whether object was opened for reading.

    If False, read() will raise OSError.

  readall()

    Read until EOF, using multiple read() call.

class nobodd.tools.FrozenDict(*args)

  A hashable, immutable mapping type.

  The arguments to `FrozenDict` are processed just like those to `dict`.