def list(self): """ Get the list of files from the workspace. """ p = Path(self.console.config['WORKSPACE']).expanduser() for f in p.walk(filter_func=lambda p: p.is_file(), relative=True): if all(not re.match(x, f.filename) for x in ["(data|key|store)\.db.*", "history"]): yield f
def start(self, filename, overwrite=False): """ Start the recorder, creating the record file. """ self.__file = f = Path(filename) if f.suffix != ".rc": self.__file = f = Path(self.root_dir).joinpath(filename + ".rc") if not overwrite and f.exists(): raise OSError("File already exists") f.reset()
def edit(self, filename): """ Edit a file using the configured text editor. """ #FIXME: edit by calling the locator and manage its local file (e.g. for a URL, point to a temp folder) ted = self.console.config['TEXT_EDITOR'] if which(ted) is None: raise ValueError("'%s' does not exist or is not installed" % ted) p = Path(self.console.config['WORKSPACE']).joinpath(filename) if not p.exists(): p.touch() call([ted, str(p)], stderr=PIPE)
def run(self, project): self.logger.debug("Loading archive '{}'...".format(project + ".zip")) projpath = Path(self.workspace).joinpath(project) archive = ProjectPath(projpath.with_suffix(".zip")) ask = self.console.config.option("ENCRYPT_PROJECT").value try: archive.load(ask=ask) self.logger.success("'{}' loaded".format(project)) except Exception as e: logger.error("Bad password" if "error -3" in str(e) else str(e)) self.logger.failure("'{}' not loaded".format(project))
def __init__(self, n, cmd, **kwargs): self.id = n self.parent = kwargs.pop('parent') if isinstance(cmd, str): cmd = shlex.split(cmd) self._path = Path(self.parent.console._files.tempdir, "session", str(n), create=True) for i, s in enumerate(["stdin", "stdout", "stderr"]): fifo = str(self._path.joinpath(str(i))) self._named_pipes.append(fifo) os.mkfifo(fifo, 0o777) setattr(self, "_" + s, os.open(fifo, os.O_WRONLY))
def __new__(meta, name, bases, clsdict): subcls = type.__new__(meta, name, bases, clsdict) # compute module's path from its root folder if no path attribute defined on its class if not hasattr(subcls, "path") or subcls.path is None: p = Path(getfile(subcls)).parent # collect the source temporary attribute s = getattr(subcls, "_source", ".") try: subcls.path = str(p.relative_to(Path(s))) except ValueError: subcls.path = None # then pass the subclass with its freshly computed path attribute to the original __new__ method, for # registration in subclasses and in the list of modules super(MetaModule, meta).__new__(meta, name, bases, clsdict, subcls) return subcls
class Session(object): """ Class representing a session object based on a shell command """ def __init__(self, n, cmd, **kwargs): self.id = n self.parent = kwargs.pop('parent') if isinstance(cmd, str): cmd = shlex.split(cmd) self._path = Path(self.parent.console._files.tempdir, "session", str(n), create=True) for i, s in enumerate(["stdin", "stdout", "stderr"]): fifo = str(self._path.joinpath(str(i))) self._named_pipes.append(fifo) os.mkfifo(fifo, 0o777) setattr(self, "_" + s, os.open(fifo, os.O_WRONLY)) def close(self): for s in ["stdin", "stdout", "stderr"]: getattr(self, "_" + s).close() shutil.rmtree(str(self._path)) self._process.wait() del self.parent[self.id] def start(self, **kwargs): kwargs['close_fds'] = True kwargs[ 'preexec_fn'] = os.setsid # NB: see subprocess' doc ; preexec_fn is not thread-safe self._process = Popen(cmd, stdout=self._stdout, stderr=self._stderr, stdin=self._stdin, **kwargs)
def value(self): """ Normalized value attribute. """ value = self.input if self.required and value is None: raise ValueError("{} must be defined".format(self.name)) try: # try to expand format variables using console's attributes kw = {} for n in re.findall(r'\{([a-z]+)\}', str(value)): kw[n] = self.config.console.__dict__.get(n, "") try: value = value.format(**kw) except: pass except AttributeError as e: # occurs when console is not linked to config (i.e. at startup) pass # expand and resolve paths if self.name.endswith("FOLDER") or self.name.endswith("WORKSPACE"): # this will ensure that every path is expanded value = str(Path(value, expand=True)) # convert common formats to their basic types try: if value.isdigit(): value = int(value) if value.lower() in ["false", "true"]: value = value.lower() == "true" except AttributeError: # occurs e.g. if value is already a bool pass # then try to transform using the user-defined function if isinstance(self.transform, type(lambda: 0)) and self.transform.__name__ == ( lambda: 0).__name__: value = self.transform(value) return value
def __init__(self, appname=None, *args, **kwargs): Console.appname = appname or getattr(self, "appname", Console.appname) o, v = self.config.option('APP_FOLDER'), str(self.config['APP_FOLDER']) self.config[o] = Path(v.format(appname=self.appname.lower())) o.old_value = None self._set_app_folder() self._set_workspace() super(FrameworkConsole, self).__init__(*args, **kwargs)
def run(self, key, rcfile=None): if key == "start": self.recorder.start(str(Path(self.workspace).joinpath(rcfile))) elif key == "stop": self.recorder.stop() elif key == "status": self.logger.info("Recording is {}".format( ["disabled", "enabled"][self.recorder.enabled]))
def validate(self, key, rcfile=None): if key == "start": if rcfile is None: raise ValueError("please enter a filename") if Path(self.workspace).joinpath(rcfile).exists(): raise ValueError("a file with the same name already exists") elif key in ["stop", "status"]: if rcfile is not None: raise ValueError("this key takes no value")
def _sources(self, items): """ Return the list of sources for the related items [banners|entities|libraries], first trying subclass' one then Console class' one. Also, resolve paths relative to the path where the parent Console is found. """ src = self.sources.get(items, Console.sources[items]) if isinstance(src, (str, Path)): src = [src] return [ Path(self._root.dirname.joinpath(s).expanduser().resolve()) for s in (src or []) ]
def __init__(self, appname=None, *args, **kwargs): Console._dev_mode = kwargs.pop("dev", False) Console.appname = appname or getattr(self, "appname", Console.appname) o, v = self.config.option('APP_FOLDER'), str(self.config['APP_FOLDER']) self.config[o] = Path(v.format(appname=self.appname.lower())) o.old_value = None self.config['DEBUG'] = kwargs.get('debug', False) self._set_app_folder(silent=True, **kwargs) self._set_workspace() super(FrameworkConsole, self).__init__(*args, **kwargs)
def page(self, *filenames): """ Page a list of files using Less. """ tvw = self.console.config['TEXT_VIEWER'] if which(tvw) is None: raise ValueError("'%s' does not exist or is not installed" % tvw) filenames = list(map(str, filenames)) for f in filenames: if not Path(str(f)).is_file(): raise OSError("File does not exist") call([tvw] + filenames, stderr=PIPE)
def run(self, key, value=None): if key == "files": if value is None: data = [["Path", "Size"]] p = Path(self.config.option("WORKSPACE").value) for f in self.console._files.list: data.append([f, human_readable_size(p.joinpath(f).size)]) print_formatted_text( BorderlessTable(data, "Files from the workspace")) elif self.config.option("TEXT_VIEWER").value: self.console._files.view(value) elif key == "issues": t = Entity.get_issues() if len(t) > 0: print_formatted_text(t) elif key == "modules": h = Module.get_help(value) if h.strip() != "": print_formatted_text(h) else: self.logger.warning("No module loaded") elif key == "options": if value is None: print_formatted_text(ANSI(str(self.config))) else: c = Config() c[self.config.option(value)] = self.config[value] print_formatted_text(ANSI(str(c))) elif key == "projects": if value is None: data = [["Name"]] for p in projects(self): data.append([p]) print_formatted_text(BorderlessTable(data, "Existing projects")) else: print_formatted_text(value) elif key == "sessions": data = [["ID", "Description"]] for i, s in self.console._sessions: data.append([str(i), getattr(s, "description", "<undefined>")]) print_formatted_text(BorderlessTable(data, "Open sessions"))
def run(self, project): projpath = Path(self.workspace).joinpath(project) folder = ProjectPath(projpath) self.logger.debug("Archiving project '{}'...".format(project)) ask = self.console.config.option("ENCRYPT_PROJECT").value try: folder.archive(ask=ask) self.logger.success("'{}' archived".format(project)) except OSError as e: logger.error(str(e)) self.logger.failure("'{}' not archived".format(project))
def identifier(self): """ Compute a unique identifier for this entity subclass. """ f = Path(getattr(self, "__file__", getfile(self))) d, fn = f.dirname, f.filename if len(d.parts) > 0 and d.parts[-1] == "__pycache__": parts = fn.split(".") if re.match(r".?python\-?[23]\d", parts[-2]): parts.pop(-2) parts[-1] = "py" f = d.parent.joinpath(".".join(parts)) return str(f), self.__name__
def __init__(self, parent=None, **kwargs): fail = kwargs.pop("fail", True) super(Console, self).__init__() # determine the relevant parent self.parent = parent if self.parent is not None and self.parent.level == self.level: while parent is not None and parent.level == self.level: parent = parent.parent # go up of one console level # raise an exception in the context of command's .run() execution, to be propagated to console's .run() # execution, setting the directly higher level console in argument raise ConsoleDuplicate(self, parent) # back-reference the console self.config.console = self # configure the console regarding its parenthood if self.parent is None: if Console.parent is not None: raise Exception("Only one parent console can be used") Console.parent = self Console.parent._start_time = datetime.now() Console.appdispname = Console.appname Console.appname = Console.appname.lower() self._root = Path(getfile(self.__class__)).resolve() self.__init(**kwargs) else: self.parent.child = self # reset commands and other bound stuffs self.reset() # setup the session with the custom completer and validator completer, validator = CommandCompleter(), CommandValidator(fail) completer.console = validator.console = self message, style = self.prompt self._session = PromptSession( message, completer=completer, history=FileHistory( Path(self.config.option("WORKSPACE").value).joinpath( "history")), validator=validator, style=Style.from_dict(style), ) CustomLayout(self)
def view(self, key): """ View a file using the configured text viewer. """ try: self.page_text(self[key]) except KeyError: pass p = Path(self.console.config['WORKSPACE'], expand=True).joinpath(key) if p.suffix == ".md": self.page_text(txt_terminal_render(p.text, format="md").strip()) else: # if the given key is not in the dictionary of files (APP_FOLDER/files/), it can still be in the workspace self.page(p)
def run(self, project): p = self.workspace.joinpath(project) loader = Load() if project in loader.complete_values() and confirm( "An archive with this name already exists ; " "do you want to load the archive instead ?"): loader.run(project) if not p.exists(): self.logger.debug("Creating project '{}'...".format(project)) p.mkdir() self.logger.success("'{}' created".format(project)) ProjectConsole(self.console, project).start() self.config['WORKSPACE'] = str(Path(self.config['WORKSPACE']).parent)
def __set_folder(self, option, subpath=""): """ Set a new folder, moving an old to the new one if necessary. """ o = self.config.option(option) old, new = o.old_value, o.value if old == new: return try: if old is not None: os.rename(old, new) except Exception as e: pass Path(new).joinpath(subpath).mkdir(parents=True, exist_ok=True) return new
def reset(self): """ Setup commands for the current level, reset bindings between commands and the current console then update store's object. """ self.detach("command") # setup level's commands, starting from general-purpose commands self.commands = {} # add commands for n, c in chain( Command.commands.get("general", {}).items(), Command.commands.get(self.level, {}).items()): self.attach(c) if self.level not in getattr(c, "except_levels", []) and c.check(): self.commands[n] = c else: self.detach(c) root = self.config.option('WORKSPACE').value # get the relevant store and bind it to loaded models Console.store = Console._storage.get(Path(root).joinpath("store.db")) # update command recorder's root directory self._recorder.root_dir = root
def unregister_commands(cls, *identifiers): """ Unregister items from Command based on their 'identifiers' (functionality or level/name). """ for i in identifiers: _ = i.split("/", 1) try: l, n = _ # level, name except ValueError: f, n = _[0], None # functionality # apply deletions if n is None: if f not in cls._functionalities: raise ValueError("Unknown functionality {}".format(f)) p = Path(__file__).parent.joinpath("../base/commands/" + f + ".py").resolve() for c in PythonPath(str(p)).get_classes(Command): Command.unregister_command(c) else: try: c = Command.commands[l][n] Command.unregister_command(c) except KeyError: pass
def subpath(self): """ First child path of the module. """ return str(Path(self.path).child)
def fullpath(self): """ Full path of the module, that is, its path joined with its name. """ return str(Path(self.path).joinpath(self.name))
def category(self): """ Module's category. """ try: return str(Path(self.path).parts[0]) except IndexError: return ""
def base(self): """ Module's category. """ return str(Path( self.fullpath).child) if self.category != "" else self.name
def complete_values(self, key): if key.upper() == "WORKSPACE": return [str(x) for x in Path(".").home().iterpubdir()] return self.config.option(key).choices or []
def run(self): h = Path(self.config.option("WORKSPACE").value).joinpath("history") self.console._files.page(str(h))
def run(self, filename): f = Path(self.config.option("WORKSPACE").value).joinpath(filename) self.console._files.edit(str(f))