# Path: habitat/loadable_manager.py
# class LoadableManager:
#     """
#     The main Loadable Manager class.
#     """
# 
#     def __init__(self, config):
#         """
#         On construction, all modules listed in config["loadables"] will be
#         loaded using :py:meth:`load`.
#         """
# 
#         self.libraries = {}
# 
#         for loadable in config["loadables"]:
#             self.load(loadable["class"], loadable["name"])
# 
#     def load(self, module, shorthand):
#         """Loads *module* as a library and assigns it to *shorthand*."""
# 
#         module = dynamicloader.load(module)
#         self.libraries[shorthand] = module
# 
#     def run(self, name, config, data):
#         """
#         Run the loadable specified by *name*, giving it *config* and *data*.
# 
#         If the loadable only takes one argument, it will only be given *data*.
#         *config* is ignored in this case.
# 
#         Returns the result of running the loadable.
#         """
# 
#         name_parts = name.split('.')
#         library_name = '.'.join(name_parts[0:-1])
#         function_name = name_parts[-1]
# 
#         if library_name not in self.libraries:
#             raise ValueError("Invalid library name: " + library_name)
# 
#         library = self.libraries[library_name]
# 
#         if function_name not in library.__all__:
#             raise ValueError("Invalid function name: " + function_name)
# 
#         func = getattr(library, function_name)
# 
#         if dynamicloader.hasnumargs(func, 1):
#             return func(data)
#         else:
#             return func(config, data)
# 
#     _repr_format = "<habitat.LoadableManager: {l} libraries loaded>"
# 
#     def __repr__(self):
#         return self._repr_format.format(l=len(self.libraries))
# 
# Path: habitat/parser_modules/simple_binary_parser.py
# class SimpleBinaryParser(ParserModule):
#     """The Simple Binary Parser Module"""
# 
#     def pre_parse(self, string):
#         """
#         As no callsign is provided by the protocol, assume any string we are
#         given is potentially parseable binary data.
#         """
#         raise CantExtractCallsign()
# 
#     def _verify_config(self, config):
#         """
#         Checks that the provided *config* dict is appropriate for this parser.
#         """
#         if config["protocol"] != "simple_binary":
#             raise ValueError("Configuration document has wrong protocol.")
#         if "fields" not in config:
#             raise ValueError("Config document missing required key `fields'")
#         field_names = []
#         for idx, field in enumerate(config["fields"]):
#             if "name" not in field or "format" not in field:
#                 raise ValueError("Field {0} config missing name or format."
#                                  .format(idx))
#             if field["name"][0] == "_":
#                 raise ValueError("Field {0} name starts with an underscore."
#                                  .format(idx))
#             field_names.append(field["name"])
#         if len(field_names) != len(set(field_names)):
#             raise ValueError("Duplicate field name.")
# 
#     def _parse_field(self, field, config):
#         """
#         Pass off the data from unpacking the binary to the sensor given in
#         the configuration for actual parsing.
#         """
#         name = config["name"]
#         if 'sensor' not in config:
#             return name, field
#         sensor = 'sensors.' + config["sensor"]
#         try:
#             data = self.loadable_manager.run(sensor, config, field)
#         except (ValueError, KeyError) as e:
#             error_type = type(e)
#             raise error_type("(field {f}): {e!s}".format(f=name, e=e))
# 
#         return name, data
# 
#     def parse(self, data, config):
#         """
#         Parse *string*, extracting processed field data.
#         
#         *config* is the relevant sentence dictionary from the payload's
#         configuration document, containing the required binary format and field
#         details.
# 
#         Returns a dictionary of the parsed data.
# 
#         ValueError is raised on invalid messages.
#         """
#         self._verify_config(config)
#         prefix = [config["format_prefix"]] if "format_prefix" in config else []
#         fmtstring = ''.join(prefix + [f["format"] for f in config["fields"]])
#         
#         try:
#             data = struct.unpack(str(fmtstring), data)
#         except struct.error as exp:
#             raise ValueError("Could not unpack binary data: {0}".format(exp))
#         
#         if len(data) != len(config["fields"]):
#             raise ValueError(
#                 "Number of extracted fields does not match config"
#                 " (got {0}, expected {1}).".format(
#                 len(data), len(config["fields"])))
#         
#         output = {}
#         for field, field_config in zip(data, config["fields"]):
#             name, data = self._parse_field(field, field_config)
#             output[name] = data
# 
#         return output
# 
# Path: habitat/parser.py
# class CantExtractCallsign(Exception):
#     """
#     Parser submodule cannot find a callsign, though in theory might be able
#     to parse the sentence if one were provided.
#     """
#     pass


import struct
from nose.tools import assert_raises
from copy import deepcopy
from ...loadable_manager import LoadableManager
from ...parser_modules.simple_binary_parser import SimpleBinaryParser
from ...parser import CantExtractCallsign
}


class FakeParser:
    def __init__(self):
        self.loadable_manager = LoadableManager(fake_sensors_config)

# A 'standard' config. Other configs can copy this and change parts.
base_config = {
    "protocol": "simple_binary",
    "fields": [
        {
            "format": "i",
            "name": "sentence_id"
        }, {
            "format": "d",
            "name": "latitude"
        }, {
            "format": "d",
            "name": "longitude"
        }, {
            "format": "I",
            "name": "altitude"
        }
    ]
}


class TestSimpleBinaryParser:
    def setup(self):

# Path: habitat/loadable_manager.py
# class LoadableManager:
#     """
#     The main Loadable Manager class.
#     """
# 
#     def __init__(self, config):
#         """
#         On construction, all modules listed in config["loadables"] will be
#         loaded using :py:meth:`load`.
#         """
# 
#         self.libraries = {}
# 
#         for loadable in config["loadables"]:
#             self.load(loadable["class"], loadable["name"])
# 
#     def load(self, module, shorthand):
#         """Loads *module* as a library and assigns it to *shorthand*."""
# 
#         module = dynamicloader.load(module)
#         self.libraries[shorthand] = module
# 
#     def run(self, name, config, data):
#         """
#         Run the loadable specified by *name*, giving it *config* and *data*.
# 
#         If the loadable only takes one argument, it will only be given *data*.
#         *config* is ignored in this case.
# 
#         Returns the result of running the loadable.
#         """
# 
#         name_parts = name.split('.')
#         library_name = '.'.join(name_parts[0:-1])
#         function_name = name_parts[-1]
# 
#         if library_name not in self.libraries:
#             raise ValueError("Invalid library name: " + library_name)
# 
#         library = self.libraries[library_name]
# 
#         if function_name not in library.__all__:
#             raise ValueError("Invalid function name: " + function_name)
# 
#         func = getattr(library, function_name)
# 
#         if dynamicloader.hasnumargs(func, 1):
#             return func(data)
#         else:
#             return func(config, data)
# 
#     _repr_format = "<habitat.LoadableManager: {l} libraries loaded>"
# 
#     def __repr__(self):
#         return self._repr_format.format(l=len(self.libraries))
# 
# Path: habitat/parser_modules/simple_binary_parser.py
# class SimpleBinaryParser(ParserModule):
#     """The Simple Binary Parser Module"""
# 
#     def pre_parse(self, string):
#         """
#         As no callsign is provided by the protocol, assume any string we are
#         given is potentially parseable binary data.
#         """
#         raise CantExtractCallsign()
# 
#     def _verify_config(self, config):
#         """
#         Checks that the provided *config* dict is appropriate for this parser.
#         """
#         if config["protocol"] != "simple_binary":
#             raise ValueError("Configuration document has wrong protocol.")
#         if "fields" not in config:
#             raise ValueError("Config document missing required key `fields'")
#         field_names = []
#         for idx, field in enumerate(config["fields"]):
#             if "name" not in field or "format" not in field:
#                 raise ValueError("Field {0} config missing name or format."
#                                  .format(idx))
#             if field["name"][0] == "_":
#                 raise ValueError("Field {0} name starts with an underscore."
#                                  .format(idx))
#             field_names.append(field["name"])
#         if len(field_names) != len(set(field_names)):
#             raise ValueError("Duplicate field name.")
# 
#     def _parse_field(self, field, config):
#         """
#         Pass off the data from unpacking the binary to the sensor given in
#         the configuration for actual parsing.
#         """
#         name = config["name"]
#         if 'sensor' not in config:
#             return name, field
#         sensor = 'sensors.' + config["sensor"]
#         try:
#             data = self.loadable_manager.run(sensor, config, field)
#         except (ValueError, KeyError) as e:
#             error_type = type(e)
#             raise error_type("(field {f}): {e!s}".format(f=name, e=e))
# 
#         return name, data
# 
#     def parse(self, data, config):
#         """
#         Parse *string*, extracting processed field data.
#         
#         *config* is the relevant sentence dictionary from the payload's
#         configuration document, containing the required binary format and field
#         details.
# 
#         Returns a dictionary of the parsed data.
# 
#         ValueError is raised on invalid messages.
#         """
#         self._verify_config(config)
#         prefix = [config["format_prefix"]] if "format_prefix" in config else []
#         fmtstring = ''.join(prefix + [f["format"] for f in config["fields"]])
#         
#         try:
#             data = struct.unpack(str(fmtstring), data)
#         except struct.error as exp:
#             raise ValueError("Could not unpack binary data: {0}".format(exp))
#         
#         if len(data) != len(config["fields"]):
#             raise ValueError(
#                 "Number of extracted fields does not match config"
#                 " (got {0}, expected {1}).".format(
#                 len(data), len(config["fields"])))
#         
#         output = {}
#         for field, field_config in zip(data, config["fields"]):
#             name, data = self._parse_field(field, field_config)
#             output[name] = data
# 
#         return output
# 
# Path: habitat/parser.py
# class CantExtractCallsign(Exception):
#     """
#     Parser submodule cannot find a callsign, though in theory might be able
#     to parse the sentence if one were provided.
#     """
#     pass


# Path: habitat/tests/test_parser_modules/test_simple_binary_parser.py
import struct
from nose.tools import assert_raises
from copy import deepcopy
from ...loadable_manager import LoadableManager
from ...parser_modules.simple_binary_parser import SimpleBinaryParser
from ...parser import CantExtractCallsign

}


class FakeParser:
    def __init__(self):
        self.loadable_manager = LoadableManager(fake_sensors_config)

# A 'standard' config. Other configs can copy this and change parts.
base_config = {
    "protocol": "simple_binary",
    "fields": [
        {
            "format": "i",
            "name": "sentence_id"
        }, {
            "format": "d",
            "name": "latitude"
        }, {
            "format": "d",
            "name": "longitude"
        }, {
            "format": "I",
            "name": "altitude"
        }
    ]
}


class TestSimpleBinaryParser:
    def setup(self):

        self.p = SimpleBinaryParser(FakeParser())