class BaseEnv(): """ The intention of this class is to hold environment variables which might be global to the tool, it's menus, and the command executed within. """ def __init__(self, base_dir=None, name='simplecli'): self.name = name self.logger = Logger(name=name, level=INFO) handler = StreamHandler(sys.stdout) handler.setLevel(DEBUG) handler.setFormatter(Formatter( magenta('[%(asctime)s]%(message)s'))) self.logger.addHandler(handler) self._config_namespaces = Namespace() self.menu_cache = [] self.completer_context = None self.base_dir = base_dir or os.path.expanduser('~/.simplecli') if os.path.exists(self.base_dir): if not os.path.isdir(self.base_dir): raise ValueError('base dir provided is not a dir:"{0}' .format(self.base_dir)) else: os.makedirs(self.base_dir) self.simplecli_config_file = os.path.join(self.base_dir, str(name) + '.config') self.simplecli_config = None self.init_simplecli_config() self._setup_stdio() self._load_plugin_menus() def _setup_stdio(self): def _get_stdio(name): if hasattr(sys, name): return getattr(sys, name) else: return open(name, 'wa') if self.simplecli_config.default_input: self.default_input = _get_stdio(self.simplecli_config.default_input) else: self.default_input = sys.stdin if self.simplecli_config.default_output: self.default_output = _get_stdio(self.simplecli_config.default_output) else: self.default_output = sys.stdout if self.simplecli_config.default_error: self.default_error = _get_stdio(self.simplecli_config.default_error) else: self.default_error = sys.stderr def init_simplecli_config(self): simplecli = Config(config_file_path=self.simplecli_config_file, name='simplecli') simplecli.debug = False simplecli.history_file = os.path.join(self.base_dir, 'history') simplecli.page_break = True simplecli.plugin_dir = None simplecli.default_input = 'stdin' # ie stdin simplecli.default_output = 'stdout' # ie stdout or stderr simplecli.default_error = 'stderr' # ie stderr simplecli.prompt_method = None # define this method as a global way to # construct menu prompts simplecli.path_delimeter = ">" # Used for constructing the default # prompt and displayed path string(s) simplecli._update_from_file(markers=['main']) self.add_config_to_namespace(namespace_name='main', config=simplecli, create=True) # Assign to local var for easy access as well self.simplecli_config = simplecli def add_namespace(self, name): ''' Adds a blank configuration namespace using 'name' to the local _config_namespaces list. Raises ValueError if the name already exists Returns the new namespace object ''' if getattr(self._config_namespaces, name, None): raise ValueError('Config Namespace already exists for:"{0}"' .format(name)) newnamespace = Namespace() setattr(self._config_namespaces, name, newnamespace) return newnamespace def get_namespace(self, name, create=False): ''' Will retrieve an existing namespace object of 'name' if found. If not found and 'create == True' then a new namespace obj will be created, added to self._config_namespaces and returned. ''' if isinstance(name, basestring): name = [name] if not isinstance(name, list): raise ValueError('Bad value passed for "name" to get_namespace, got:"{0}/{1}"' .format(name, type(name))) context = self._config_namespaces for n in name: last = context context = getattr(context, n, None) if not context and create: context = Namespace() setattr(last, n, context) return context def get_config_for_namespace(self, item_name, namespace_path=None): ''' Attempts to get a config object from the specified namespace :param item_name: string, name of Config to fetch :param namespace_path: string, or list of strings representing the heirarchy/location of the namespace to fetch config from returns Config obj ''' if namespace_path is None: namespace_path = ['main'] if not isinstance(namespace_path, list): namespace_path = [namespace_path] namespace_path.append(item_name) context = self._config_namespaces for ns in namespace_path: context = getattr(context, ns, None) if not context: raise ValueError('Namespace:"{0}" does not exist within path:{1}' .format(ns, ".".join(str(x) for x in namespace_path))) return context def add_config_to_namespace(self, namespace_name, config, force=False, create=False): ''' Attempts to add a config object to the specified namespace :param namespace_name: string, name of namespace to fetch config from :param config: Config() obj to add to namespace named 'namespace_name' :param force: boolean, will replace existing config block in namespace :param create: bollean, will create namespace if it does not exist ''' assert isinstance(namespace_name, str) context = self.get_namespace(namespace_name, create=create) assert config.name, 'block_name was not populated' if getattr(context, config.name, None) and not force: raise AttributeError('Base env already has config at context:' '"{0}", name:"{1}" use force to replace ' 'this section'.format(namespace_name, config.name)) setattr(context, config.name, config) def save_config(self, path=None): path = path or self.simplecli_config_file backup_path = path + '.bak' config_json = self._config_namespaces._to_json() if os.path.isfile(path): copyfile(path, backup_path) save_file = file(path, "w") with save_file: save_file.write(config_json) save_file.flush() def _load_plugin_menus(self): ''' Loads plugin menus found either in the provided plugin directory or the current working dir. Will attempt to load files starting with 'menu'. See plugin menu_examples for more info. ''' self.plugin_menus = [] return plugin_dir = self.simplecli_config.plugin_dir or os.path.curdir for file in os.listdir(plugin_dir): if (os.path.isfile(file) and file.startswith('menu') and file.endswith('.py')): full_path = os.path.join(plugin_dir, file) p_name = os.path.basename(file).rstrip('.py') plugmod = imp.load_source(p_name, full_path) menuclass = getattr(plugmod, 'menu_class', None) if not menuclass: raise AttributeError('"{0}". Plugin conf does not have ' 'a "menu_class" attribute. See plugin' 'example for info.' .format(full_path)) plugin = menuclass(env=self) existing_menu = self.get_cached_menu_by_class(plugin.__class__) if existing_menu: raise RuntimeError('Duplicate Menu Classes found while' 'loading plugins: class:"{0}", ' 'menu:"{1}", plugin:"{2}", file:"{3}"' .format(plugin.__class__, existing_menu.name, plugin.name, full_path)) parent_menus = getattr(plugmod, 'parent_menus', None) if parent_menus: plugin._parents = parent_menus self.plugin_menus.append(plugin) def get_cached_menu_by_class(self, menuclass, list=None): ''' Returns a loaded menu instance matching the provided class. ''' list = list or self.menu_cache for item in self.menu_cache: if item.__class__ == menuclass: return item return None def get_formatted_conf(self, block=None): ''' Returns a json representation of the current configuration. ''' ret_buf = "" if not block: namespace = self._config_namespaces else: namespace = self.get_namespace(block, create=False) if namespace: ret_buf = namespace._to_json() return ret_buf def get_config_diff(self, file_path=None): ''' Method to show current values vs those (saved) in a file. Will return a formatted string to show the difference ''' #Create formatted string representation of dict values text1 = self._config_namespaces._to_json().splitlines() #Create formatted string representation of values in file file_path = file_path or self.simplecli_config_file file_dict = get_dict_from_file(file_path=file_path) or {} text2 = json.dumps(file_dict, sort_keys=True, indent=4).splitlines() diff = difflib.unified_diff(text2, text1, lineterm='') return '\n'.join(diff) def get_menu_by_name(self, name, list=None): list = list or self.menu_cache for menu in list: if menu.name == name: return menu return None
class BaseEnv: """ The intention of this class is to hold environment variables which might be global to the tool, it's menus, and the command executed within. """ def __init__(self, base_dir=None, name="simplecli"): self.name = name self.logger = Logger(name=name, level=INFO) handler = StreamHandler(sys.stdout) handler.setLevel(DEBUG) handler.setFormatter(Formatter(magenta("[%(asctime)s]%(message)s"))) self.logger.addHandler(handler) self._config_namespaces = Namespace() self.menu_cache = [] self.completer_context = None self.base_dir = base_dir or os.path.expanduser("~/.simplecli") if os.path.exists(self.base_dir): if not os.path.isdir(self.base_dir): raise ValueError('base dir provided is not a dir:"{0}'.format(self.base_dir)) else: os.makedirs(self.base_dir) self.simplecli_config_file = os.path.join(self.base_dir, str(name) + ".config") self.simplecli_config = None self.init_simplecli_config() self._setup_stdio() self._load_plugin_menus() def _setup_stdio(self): def _get_stdio(name): if hasattr(sys, name): return getattr(sys, name) else: return open(name, "wa") if self.simplecli_config.default_input: self.default_input = _get_stdio(self.simplecli_config.default_input) else: self.default_input = sys.stdin if self.simplecli_config.default_output: self.default_output = _get_stdio(self.simplecli_config.default_output) else: self.default_output = sys.stdout if self.simplecli_config.default_error: self.default_error = _get_stdio(self.simplecli_config.default_error) else: self.default_error = sys.stderr def init_simplecli_config(self): simplecli = Config(config_file_path=self.simplecli_config_file, name="simplecli") simplecli.debug = False simplecli.history_file = os.path.join(self.base_dir, "history") simplecli.page_break = True simplecli.plugin_dir = None simplecli.default_input = "stdin" # ie stdin simplecli.default_output = "stdout" # ie stdout or stderr simplecli.default_error = "stderr" # ie stderr simplecli.prompt_method = None # define this method as a global way to # construct menu prompts simplecli.path_delimeter = ">" # Used for constructing the default # prompt and displayed path string(s) simplecli._update_from_file(markers=["main"]) self.add_config_to_namespace(namespace_name="main", config=simplecli, create=True) # Assign to local var for easy access as well self.simplecli_config = simplecli def add_namespace(self, name): """ Adds a blank configuration namespace using 'name' to the local _config_namespaces list. Raises ValueError if the name already exists Returns the new namespace object """ if getattr(self._config_namespaces, name, None): raise ValueError('Config Namespace already exists for:"{0}"'.format(name)) newnamespace = Namespace() setattr(self._config_namespaces, name, newnamespace) return newnamespace def get_namespace(self, name, create=False): """ Will retrieve an existing namespace object of 'name' if found. If not found and 'create == True' then a new namespace obj will be created, added to self._config_namespaces and returned. """ if isinstance(name, basestring): name = [name] if not isinstance(name, list): raise ValueError('Bad value passed for "name" to get_namespace, got:"{0}/{1}"'.format(name, type(name))) context = self._config_namespaces for n in name: last = context context = getattr(context, n, None) if not context and create: context = Namespace() setattr(last, n, context) return context def get_config_for_namespace(self, item_name, namespace_path=None): """ Attempts to get a config object from the specified namespace :param item_name: string, name of Config to fetch :param namespace_path: string, or list of strings representing the heirarchy/location of the namespace to fetch config from returns Config obj """ if namespace_path is None: namespace_path = ["main"] if not isinstance(namespace_path, list): namespace_path = [namespace_path] namespace_path.append(item_name) context = self._config_namespaces for ns in namespace_path: context = getattr(context, ns, None) if not context: raise ValueError( 'Namespace:"{0}" does not exist within path:{1}'.format( ns, ".".join(str(x) for x in namespace_path) ) ) return context def add_config_to_namespace(self, namespace_name, config, force=False, create=False): """ Attempts to add a config object to the specified namespace :param namespace_name: string, name of namespace to fetch config from :param config: Config() obj to add to namespace named 'namespace_name' :param force: boolean, will replace existing config block in namespace :param create: bollean, will create namespace if it does not exist """ assert isinstance(namespace_name, str) context = self.get_namespace(namespace_name, create=create) assert config.name, "block_name was not populated" if getattr(context, config.name, None) and not force: raise AttributeError( "Base env already has config at context:" '"{0}", name:"{1}" use force to replace ' "this section".format(namespace_name, config.name) ) setattr(context, config.name, config) def save_config(self, path=None): path = path or self.simplecli_config_file backup_path = path + ".bak" config_json = self._config_namespaces._to_json() if os.path.isfile(path): copyfile(path, backup_path) save_file = file(path, "w") with save_file: save_file.write(config_json) save_file.flush() def _load_plugin_menus(self): """ Loads plugin menus found either in the provided plugin directory or the current working dir. Will attempt to load files starting with 'menu'. See plugin menu_examples for more info. """ self.plugin_menus = [] return plugin_dir = self.simplecli_config.plugin_dir or os.path.curdir for file in os.listdir(plugin_dir): if os.path.isfile(file) and file.startswith("menu") and file.endswith(".py"): full_path = os.path.join(plugin_dir, file) p_name = os.path.basename(file).rstrip(".py") plugmod = imp.load_source(p_name, full_path) menuclass = getattr(plugmod, "menu_class", None) if not menuclass: raise AttributeError( '"{0}". Plugin conf does not have ' 'a "menu_class" attribute. See plugin' "example for info.".format(full_path) ) plugin = menuclass(env=self) existing_menu = self.get_cached_menu_by_class(plugin.__class__) if existing_menu: raise RuntimeError( "Duplicate Menu Classes found while" 'loading plugins: class:"{0}", ' 'menu:"{1}", plugin:"{2}", file:"{3}"'.format( plugin.__class__, existing_menu.name, plugin.name, full_path ) ) parent_menus = getattr(plugmod, "parent_menus", None) if parent_menus: plugin._parents = parent_menus self.plugin_menus.append(plugin) def get_cached_menu_by_class(self, menuclass, list=None): """ Returns a loaded menu instance matching the provided class. """ list = list or self.menu_cache for item in self.menu_cache: if item.__class__ == menuclass: return item return None def get_formatted_conf(self, block=None): """ Returns a json representation of the current configuration. """ ret_buf = "" if not block: namespace = self._config_namespaces else: namespace = self.get_namespace(block, create=False) if namespace: ret_buf = namespace._to_json() return ret_buf def get_config_diff(self, file_path=None): """ Method to show current values vs those (saved) in a file. Will return a formatted string to show the difference """ # Create formatted string representation of dict values text1 = self._config_namespaces._to_json().splitlines() # Create formatted string representation of values in file file_path = file_path or self.simplecli_config_file file_dict = get_dict_from_file(file_path=file_path) or {} text2 = json.dumps(file_dict, sort_keys=True, indent=4).splitlines() diff = difflib.unified_diff(text2, text1, lineterm="") return "\n".join(diff) def get_menu_by_name(self, name, list=None): list = list or self.menu_cache for menu in list: if menu.name == name: return menu return None