class Application(object): """Base class for those who needs to maintain global application state. Normally you don't need to subclass this class. .. todo:: Connect b3 build directory and application build directory. :param home_dir: represents root directory for the application. :param init_home_dir: if `home_dir` was specified and it's not existed home directory, the new directory will be initialized if `init_home_dir` is `True`. """ _register = {} def __init__(self, home_dir=None, init_home_dir=False): self._network = Network() self._home_dir = None self._build_dir = None if home_dir: if not self.is_home_dir(home_dir) and init_home_dir: self.init_home_dir(home_dir) self.set_home_dir(home_dir) @classmethod def _register_instance(cls, app): if not isinstance(app, Application): raise TypeError("app must be derived from Application") if not app.get_home_dir() in cls._register: cls._register[app.get_home_dir()] = app @classmethod def _unregister_instance(cls, app): if not isinstance(app, Application): raise TypeError("app must be derived from Application") if app.get_home_dir() in cls._register: del cls._register[app.get_home_dir()] def __str__(self): return "%s[home_dir='%s',num_mappings=%d]" \ % (self.__class__.__name__, self.get_home_dir(), self.get_num_mappings()) @classmethod def get_active_instance(cls): """Returns active application instance. See also :func:`identify_instance`. :returns: An :class:`Application` instance. """ return cls.identify_instance() @classmethod def identify_instance(cls, obj=None): """Identifies last active application instance. :param obj: Returns instance that keeps this object if such was provided. :returns: An :class:`Application` instance that represents active application. """ src = obj and inspect.getsourcefile(obj) or None home_dir = src and cls.find_home_dir(src) or cls.identify_home_dir() if not cls._register.has_key(home_dir): return Application(home_dir=home_dir) return cls._register[home_dir] @classmethod def init_home_dir(cls, home_dir): """Initializes passed home directory if such wasn't already initialized. :returns: Path to home directory. """ if not path_utils.exists(home_dir): raise IOError("'%s' doesn't exist" % home_dir) settings_dir = path_utils.join(home_dir, SETTINGS_DIR) path_utils.mkpath(settings_dir) return home_dir @classmethod def is_home_dir(cls, path): """Returns whether or not a given path is application home directory. :returns: `True` or `False`. :raises: :class:`TypeError`, :class:`IOError` """ if not typecheck.is_string(path): raise TypeError("'path' has to be a string") elif not path_utils.exists(path): raise IOError("'%s' path doesn't exist" % path) return SETTINGS_DIR in os.listdir(path) @classmethod def find_home_dir(cls, path): """Finds top directory of an application by a given path and returns home path. Returns `None` if home direcotry cannot be identified. :param path: Path to directory. :returns: Path as string or `None`. """ if not typecheck.is_string(path): raise TypeError("'path' must be a string") elif not len(path): raise TypeError("'path' is empty") path = path_utils.realpath(path) if path_utils.isfile(path): (path, _) = path_utils.split(path) while path is not os.sep: if os.path.exists(path) and os.path.isdir(path): if cls.is_home_dir(path): return path (path, _) = path_utils.split(path) return None @classmethod def identify_home_dir(cls): for record in inspect.stack(): (frame, filename, lineno, code_ctx, _, index) = record path = path_utils.dirname(path_utils.abspath(filename)) home_dir = cls.find_home_dir(path) if home_dir: return home_dir return cls.find_home_dir(path_utils.realpath(os.curdir)) or os.getcwd() def set_home_dir(self, home_dir): """Set application home directory. There should be no other applications registered to this home directory. :param home_dir: A string that represents path to home directory. The path should exists. :raises: :class:`IOError` """ if not path_utils.exists(home_dir): raise IOError("`%s' doesn't exist" % home_dir) if self._home_dir: self._unregister_instance(self) if home_dir in self._register: raise IOError("%s is already using this home dir: %s" % (self._register[home_dir], home_dir)) self._home_dir = home_dir self._register_instance(self) def get_home_dir(self): """Returns home directory. :returns: A string that represents path to home directory. """ return self._home_dir def set_build_dir(self, path, make=False): """Sets path to the build directory. The build directory will be used to store temporary/autogenerated and compiled build-files. :param path: Build-directory name. :param make: Whether or not the path needs to be created. """ if not path_utils.exists(path): if not make: raise IOError("`%s' doesn't exist" % path) path_utils.mkpath(path) self._build_dir = path def get_build_dir(self): """Returns build directory""" return self._build_dir def get_network(self): """Returns network that represents all the mappings and their relations within this application. """ return self._network def gen_default_mapping_name(self, mapping): """Generates a name for a given mapping. This name will be unique within this application. This technique is used by the mapping itself for registration. :param mapping: A :class:`~bb.app.mapping.Mapping` instance. :returns: A string represented mapping's name. :raises: TypeError """ if not isinstance(mapping, Mapping): raise TypeError() frmt = mapping.__class__.name_format return frmt % self.get_num_mappings() def get_mappings(self): """Returns list of mappings registered by this application. :returns: A list of :class:`Mapping` instances. """ return self._network.get_nodes() def get_num_mappings(self): """Returns number of mappings controlled by this application.""" return len(self.get_mappings()) def has_mapping(self, mapping): if not isinstance(mapping, Mapping): raise TypeError("Mapping must be derived from bb.app.mapping.Mapping") return self._network.has_node(mapping) def add_mapping(self, mapping): """Registers a mapping and adds it to the application network. :param mapping: A :class:`Mapping` instance. :returns: Whether or not the mapping was added. """ if self.has_mapping(mapping): return False self._network.add_node(mapping) return True def add_mappings(self, mappings): """Adds mappings. See also :func:`add_mapping`. :param mappings: A list of :class:`Mapping` instances. """ if not typecheck.is_list(mappings): raise TypeError("'mappings' must be list") for mapping in mappings: self.add_mapping(mapping) def create_mapping(self, *args, **kwargs): """Mapping factory, creates and returns a new mapping connected to this application. .. note:: Use :func:`create_mapping` to automatically create a mapping and connect it to the active application. Otherwise you will have to create mapping manually and then use :func:`add_mapping` in order to connect it to specific :class:`Application` instance. :param args: A list of arguments. :param kwargs: A dict of key-word arguments. :returns: A :class:`~bb.app.mapping.Mapping` instance. """ fixed_kwargs = dict(**kwargs) fixed_kwargs.update(autoreg=False) mapping = Mapping(*args, **fixed_kwargs) self.add_mapping(mapping) return mapping def remove_mapping(self, mapping): if not isinstance(mapping, Mapping): raise TypeError("'mapping' must be derived from %s" % Mapping.__class__) if not self.has_mapping(mapping): return False self._network.remove_node(mapping) return True def remove_mappings(self): for mapping in self.get_mappings(): self.remove_mapping(mapping)
class Application(Object): """Base class for those who needs to maintain global application state. Normally you don't need to subclass this class. By default root application directory name will be taken as application name. You can change that later with help of :func:`set_name` method. .. todo:: Connect b3 build directory and application build directory. :param name: A string that represents application name. :param home_dir: represents root directory for the application. :param init_home_dir: if `home_dir` was specified and it's not existed home directory, the new directory will be initialized if `init_home_dir` is `True`. """ settings_dirname = ".bbapp" def __init__(self, name=None, home_dir=None, init_home_dir=False, settings_dir=None): Object.__init__(self) self._name = None self._network = Network() self._home_dir = None self._build_dir = None self._settings_dir = None if settings_dir: if not path_utils.exists(settings_dir): raise IOError() self._settings_dir = settings_dir if home_dir: if not self.is_home_dir(home_dir) and init_home_dir: self.init_home_dir(home_dir) self._set_home_dir(home_dir) if name: self.set_name(name) elif self.get_home_dir(): self.set_name(path_utils.basename(self.get_home_dir())) else: raise NotImplementedError() def get_name(self): """Returns application name.""" return self._name def set_name(self, name): """Set application name. :param name: A string. """ if not typecheck.is_string(name): raise TypeError() self._name = name def __str__(self): return "%s[home_dir='%s',num_mappings=%d]" \ % (self.__class__.__name__, self.get_home_dir(), self.get_num_mappings()) @classmethod def init_home_dir(cls, home_dir): """Initializes passed home directory if such wasn't already initialized. :returns: Path to home directory. """ if not path_utils.exists(home_dir): raise IOError("'%s' doesn't exist" % home_dir) if not self._settings_dir: self._settings_dir = path_utils.join(home_dir, self.settings_dirname) path_utils.mkpath(settings_dir) return home_dir @classmethod def is_home_dir(cls, path): """Returns whether or not a given path is application home directory. :returns: `True` or `False`. :raises: :class:`TypeError`, :class:`IOError` """ if not typecheck.is_string(path): raise TypeError("'path' has to be a string") elif not path_utils.exists(path): raise IOError("'%s' path doesn't exist" % path) return cls.settings_dirname in os.listdir(path) @classmethod def find_home_dir(cls, path): """Finds top directory of an application by a given path and returns home path. Returns `None` if home direcotry cannot be identified. :param path: Path to directory. :returns: Path as string or `None`. """ if not typecheck.is_string(path): raise TypeError("'path' must be a string") elif not len(path): raise TypeError("'path' is empty") path = path_utils.realpath(path) if path_utils.isfile(path): (path, _) = path_utils.split(path) while path is not os.sep: if os.path.exists(path) and os.path.isdir(path): if cls.is_home_dir(path): return path (path, _) = path_utils.split(path) return None @classmethod def identify_home_dir(cls): for record in inspect.stack(): (frame, filename, lineno, code_ctx, _, index) = record path = path_utils.dirname(path_utils.abspath(filename)) home_dir = cls.find_home_dir(path) if home_dir: return home_dir return cls.find_home_dir(path_utils.realpath(os.curdir)) or os.getcwd() def _set_home_dir(self, home_dir): """Set application home directory. There should be no other applications registered to this home directory. :param home_dir: A string that represents path to home directory. The path should exists. :raises: :class:`IOError` """ if not path_utils.exists(home_dir): raise IOError("`%s' doesn't exist" % home_dir) self._home_dir = home_dir def get_home_dir(self): """Returns home directory. :returns: A string that represents path to home directory. """ return self._home_dir def get_settings_dir(self): return self._settings_dir def set_build_dir(self, path, make=False): """Sets path to the build directory. The build directory will be used to store temporary/autogenerated and compiled build-files. :param path: Build-directory name. :param make: Whether or not the path needs to be created. """ if not path_utils.exists(path): if not make: raise IOError("`%s' doesn't exist" % path) path_utils.mkpath(path) self._build_dir = path def get_build_dir(self): """Returns build directory""" return self._build_dir def get_network(self): """Returns network that represents all the mappings and their relations within this application. """ return self._network def gen_default_mapping_name(self, mapping): """Generates a name for a given mapping. This name will be unique within this application. This technique is used by the mapping itself for registration. :param mapping: A :class:`~bb.app.mapping.Mapping` instance. :returns: A string represented mapping's name. :raises: TypeError """ if not isinstance(mapping, Mapping): raise TypeError() frmt = mapping.__class__.name_format return frmt % self.get_num_mappings() def get_mappings(self): """Returns list of mappings registered by this application. :returns: A list of :class:`Mapping` instances. """ return self._network.get_nodes() def get_num_mappings(self): """Returns number of mappings controlled by this application.""" return len(self.get_mappings()) def has_mapping(self, mapping): if not isinstance(mapping, Mapping): raise TypeError("Mapping must be derived from bb.app.mapping.Mapping") return self._network.has_node(mapping) def add_mapping(self, mapping): """Registers a mapping and adds it to the application network. :param mapping: A :class:`Mapping` instance. :returns: Whether or not the mapping was added. """ if self.has_mapping(mapping): return False self._network.add_node(mapping) return True def add_mappings(self, mappings): """Adds mappings. See also :func:`add_mapping`. :param mappings: A list of :class:`Mapping` instances. """ if not typecheck.is_list(mappings): raise TypeError("'mappings' must be list") for mapping in mappings: self.add_mapping(mapping) def remove_mapping(self, mapping): if not isinstance(mapping, Mapping): raise TypeError("'mapping' must be derived from %s" % Mapping.__class__) if not self.has_mapping(mapping): return False self._network.remove_node(mapping) return True def remove_mappings(self): for mapping in self.get_mappings(): self.remove_mapping(mapping) def serialize(self): return json.dumps({ 'name': self.get_name(), 'mappings': [json.loads(mapping.serialize()) for mapping in self.get_mappings()] })