src.command_line_session

  1import logging
  2import os
  3import shlex
  4import readline #type: ignore
  5from pathlib import Path
  6import src.constants as cst
  7from src.cmd_types.meta import CommandMetadata
  8from src.cmd_types.output import CommandOutput
  9from src.extra import utils
 10from src.extra.plugins_loader import PluginLoader
 11from src.extra.utils import log_error
 12from src.plugins.plugin_default import LsCommand
 13
 14HANDLED_ERRORS = tuple(cst.ERROR_HANDLERS_MESSAGES_FORMATS.keys())
 15
 16class CommandLineSession:
 17
 18    """
 19    IO stream handler.
 20
 21    :param default_wd: Default working directory.
 22    :type default_wd: str | Path
 23
 24    :param plugins_dir: Directory to look for plugins.
 25    :type plugins_dir: str | Path
 26
 27    :param plugins_prefix: Prefix that plugins file must start with
 28    :type plugins_prefix: str
 29
 30    :param strict_load: If set to False, will not raise any error while importing plugins
 31    :type strict_load: bool
 32    """
 33
 34    def __init__(self, default_wd: str | Path = cst.DEFAULT_PWD,
 35                 plugins_dir: str = cst.PLUGINS_DIR,
 36                 plugins_prefix: str = cst.PLUGINS_PREFIX,
 37                 strict_load: bool = cst.STRICT_PLUGIN_LOADING
 38                 ):
 39        self.default_wd = default_wd or "."
 40        self.cmd_map: dict[str, CommandMetadata] = {}
 41        """Map of commands names to its metadata."""
 42
 43        self.plugins_dir = plugins_dir
 44        self.plugins_prefix = plugins_prefix
 45        self.logger = logging.getLogger(__name__)
 46        self.strict_load = strict_load
 47
 48        self.posix = utils.is_posix()
 49        """Is system posix"""
 50
 51    def shlex_split(self, cmd: str) -> list[str]:
 52        """
 53        Splits a line like bash does(with passed posix param)
 54        :param cmd: Input line(command)
 55        :return: List of split: command name is the first element, the next ones are args
 56        """
 57        return shlex.split(cmd, posix=self.posix)
 58
 59    @staticmethod
 60    def fetch_name_and_args(splitted: list) -> tuple[str, list[str]]:
 61        """
 62        Gets command name and args from shlex split line
 63        :param splitted: shlex split line
 64        :return: command name, args
 65        """
 66        if not splitted:
 67            return '', []
 68        cmd_name = splitted[0]
 69        if len(splitted) < 2:
 70            return cmd_name, []
 71        return cmd_name, splitted[1:]
 72
 73    def start_session(self):
 74        """
 75        Starts an infinite loop of commandline session
 76        :return: None
 77        """
 78        self.load_modules()
 79        default = self.default_wd
 80        to_move = Path(default).expanduser()
 81        os.chdir(to_move)
 82        while True:
 83            cmd = input(f"{os.getcwd()} $ ").strip()
 84            if not cmd.strip():
 85                continue
 86            try:
 87                res = self.execute_command(cmd)
 88                if res:
 89                    res = res.strip()
 90                    if res.stderr:
 91                        errs = res.stderr.split("\n")
 92                        for err in errs:
 93                            if not err:
 94                                continue
 95                            cmd_name = self.parse_line(cmd)[0]
 96                            log_error(f"{cmd_name}: {err}", self.logger)
 97                    if res.stdout:
 98                        print(res.stdout)
 99            except ImportError:
100                raise
101
102            except Exception as e:
103                try:
104                    name = self.shlex_split(cmd)[0]
105                    cmd_meta = self.cmd_map.get(name)
106                except ValueError:
107                    name = ''
108                    cmd_meta = CommandMetadata('default', 'default', 'default', '', LsCommand)
109                if cmd_meta.plugin_author != "default":
110                    utils.log_error(
111                        f"Author of plugin '{cmd_meta.plugin_name}' of version '{cmd_meta.plugin_version}' is a debil(real name - '{cmd_meta.plugin_author}'). His command '{name}' raised an unexpected error:",
112                        self.logger
113                    )
114                    utils.log_error(e, self.logger, exc=True)
115                else:
116                    utils.log_error(e, self.logger)
117
118    def load_modules(self, outer_strict: bool = False):
119        """
120        Loads plugins
121        :param outer_strict: if method was called out of class, will be more prioritized than self.strict_load
122        :type outer_strict: bool
123
124        :return: None
125        """
126
127        if not outer_strict:
128            outer_strict = self.strict_load
129        plugins_loader = PluginLoader(self.plugins_dir, self.plugins_prefix, outer_strict)
130        plugins_loader.load_plugins()
131
132        self.cmd_map = plugins_loader.commands
133        #self.cmd_map["reload-plugins"] = CommandMetadata("reload-plugins", "default_plugin", "default", "1.0.0", ReloadPluginsCommand)
134
135    def parse_line(self, line: str) -> tuple[str, list[str]] | None:
136        """
137        Parses input line by calling self.shlex_split and self.fetch_name_and_args
138        :param line: Input line
139        :return: command name, args | None, if line was empty
140        """
141        args = self.shlex_split(line)
142        if not args:
143            return None
144        cmd_name, cmd_args = self.fetch_name_and_args(splitted=args)
145
146        return cmd_name, cmd_args
147
148    def execute_command(self, line: str):
149        """
150        Executes input command
151        :param line:
152        :return: Result of command(None, if error occurred or command was not found)
153        """
154        parsed = self.parse_line(line)
155        if not parsed:
156            return None
157
158        cmd_name, cmd_args = parsed
159        if cmd_name == "help":
160            out = ""
161            for cmd in sorted(self.cmd_map.keys()):
162                out+=f"{cmd}\n"
163            return CommandOutput(stdout=out)
164
165
166        cmd_meta = self.cmd_map.get(cmd_name)
167        if not cmd_meta:
168            utils.write_history(line)
169            utils.log_error(f"{cmd_name}: command not found", self.logger)
170            return None
171
172        self.logger.info(line)
173        cmd_obj = cmd_meta.cmd(args = cmd_args)
174        cmd_obj.history()
175
176        if "--help" in cmd_args:
177            return cmd_obj.help() #TODO: --help keys
178
179        try:
180            return cmd_obj.handled_run()
181        except HANDLED_ERRORS as e:
182            log_error(f"{cmd_name}: {str(e)}", self.logger)
HANDLED_ERRORS = (<class 'FileNotFoundError'>, <class 'PermissionError'>, <class 'UnicodeDecodeError'>)
class CommandLineSession:
 17class CommandLineSession:
 18
 19    """
 20    IO stream handler.
 21
 22    :param default_wd: Default working directory.
 23    :type default_wd: str | Path
 24
 25    :param plugins_dir: Directory to look for plugins.
 26    :type plugins_dir: str | Path
 27
 28    :param plugins_prefix: Prefix that plugins file must start with
 29    :type plugins_prefix: str
 30
 31    :param strict_load: If set to False, will not raise any error while importing plugins
 32    :type strict_load: bool
 33    """
 34
 35    def __init__(self, default_wd: str | Path = cst.DEFAULT_PWD,
 36                 plugins_dir: str = cst.PLUGINS_DIR,
 37                 plugins_prefix: str = cst.PLUGINS_PREFIX,
 38                 strict_load: bool = cst.STRICT_PLUGIN_LOADING
 39                 ):
 40        self.default_wd = default_wd or "."
 41        self.cmd_map: dict[str, CommandMetadata] = {}
 42        """Map of commands names to its metadata."""
 43
 44        self.plugins_dir = plugins_dir
 45        self.plugins_prefix = plugins_prefix
 46        self.logger = logging.getLogger(__name__)
 47        self.strict_load = strict_load
 48
 49        self.posix = utils.is_posix()
 50        """Is system posix"""
 51
 52    def shlex_split(self, cmd: str) -> list[str]:
 53        """
 54        Splits a line like bash does(with passed posix param)
 55        :param cmd: Input line(command)
 56        :return: List of split: command name is the first element, the next ones are args
 57        """
 58        return shlex.split(cmd, posix=self.posix)
 59
 60    @staticmethod
 61    def fetch_name_and_args(splitted: list) -> tuple[str, list[str]]:
 62        """
 63        Gets command name and args from shlex split line
 64        :param splitted: shlex split line
 65        :return: command name, args
 66        """
 67        if not splitted:
 68            return '', []
 69        cmd_name = splitted[0]
 70        if len(splitted) < 2:
 71            return cmd_name, []
 72        return cmd_name, splitted[1:]
 73
 74    def start_session(self):
 75        """
 76        Starts an infinite loop of commandline session
 77        :return: None
 78        """
 79        self.load_modules()
 80        default = self.default_wd
 81        to_move = Path(default).expanduser()
 82        os.chdir(to_move)
 83        while True:
 84            cmd = input(f"{os.getcwd()} $ ").strip()
 85            if not cmd.strip():
 86                continue
 87            try:
 88                res = self.execute_command(cmd)
 89                if res:
 90                    res = res.strip()
 91                    if res.stderr:
 92                        errs = res.stderr.split("\n")
 93                        for err in errs:
 94                            if not err:
 95                                continue
 96                            cmd_name = self.parse_line(cmd)[0]
 97                            log_error(f"{cmd_name}: {err}", self.logger)
 98                    if res.stdout:
 99                        print(res.stdout)
100            except ImportError:
101                raise
102
103            except Exception as e:
104                try:
105                    name = self.shlex_split(cmd)[0]
106                    cmd_meta = self.cmd_map.get(name)
107                except ValueError:
108                    name = ''
109                    cmd_meta = CommandMetadata('default', 'default', 'default', '', LsCommand)
110                if cmd_meta.plugin_author != "default":
111                    utils.log_error(
112                        f"Author of plugin '{cmd_meta.plugin_name}' of version '{cmd_meta.plugin_version}' is a debil(real name - '{cmd_meta.plugin_author}'). His command '{name}' raised an unexpected error:",
113                        self.logger
114                    )
115                    utils.log_error(e, self.logger, exc=True)
116                else:
117                    utils.log_error(e, self.logger)
118
119    def load_modules(self, outer_strict: bool = False):
120        """
121        Loads plugins
122        :param outer_strict: if method was called out of class, will be more prioritized than self.strict_load
123        :type outer_strict: bool
124
125        :return: None
126        """
127
128        if not outer_strict:
129            outer_strict = self.strict_load
130        plugins_loader = PluginLoader(self.plugins_dir, self.plugins_prefix, outer_strict)
131        plugins_loader.load_plugins()
132
133        self.cmd_map = plugins_loader.commands
134        #self.cmd_map["reload-plugins"] = CommandMetadata("reload-plugins", "default_plugin", "default", "1.0.0", ReloadPluginsCommand)
135
136    def parse_line(self, line: str) -> tuple[str, list[str]] | None:
137        """
138        Parses input line by calling self.shlex_split and self.fetch_name_and_args
139        :param line: Input line
140        :return: command name, args | None, if line was empty
141        """
142        args = self.shlex_split(line)
143        if not args:
144            return None
145        cmd_name, cmd_args = self.fetch_name_and_args(splitted=args)
146
147        return cmd_name, cmd_args
148
149    def execute_command(self, line: str):
150        """
151        Executes input command
152        :param line:
153        :return: Result of command(None, if error occurred or command was not found)
154        """
155        parsed = self.parse_line(line)
156        if not parsed:
157            return None
158
159        cmd_name, cmd_args = parsed
160        if cmd_name == "help":
161            out = ""
162            for cmd in sorted(self.cmd_map.keys()):
163                out+=f"{cmd}\n"
164            return CommandOutput(stdout=out)
165
166
167        cmd_meta = self.cmd_map.get(cmd_name)
168        if not cmd_meta:
169            utils.write_history(line)
170            utils.log_error(f"{cmd_name}: command not found", self.logger)
171            return None
172
173        self.logger.info(line)
174        cmd_obj = cmd_meta.cmd(args = cmd_args)
175        cmd_obj.history()
176
177        if "--help" in cmd_args:
178            return cmd_obj.help() #TODO: --help keys
179
180        try:
181            return cmd_obj.handled_run()
182        except HANDLED_ERRORS as e:
183            log_error(f"{cmd_name}: {str(e)}", self.logger)

IO stream handler.

Parameters
  • default_wd: Default working directory.

  • plugins_dir: Directory to look for plugins.

  • plugins_prefix: Prefix that plugins file must start with

  • strict_load: If set to False, will not raise any error while importing plugins

CommandLineSession( default_wd: str | pathlib._local.Path = PosixPath('/home/runner/playground'), plugins_dir: str = 'src.plugins', plugins_prefix: str = 'plugin', strict_load: bool = False)
35    def __init__(self, default_wd: str | Path = cst.DEFAULT_PWD,
36                 plugins_dir: str = cst.PLUGINS_DIR,
37                 plugins_prefix: str = cst.PLUGINS_PREFIX,
38                 strict_load: bool = cst.STRICT_PLUGIN_LOADING
39                 ):
40        self.default_wd = default_wd or "."
41        self.cmd_map: dict[str, CommandMetadata] = {}
42        """Map of commands names to its metadata."""
43
44        self.plugins_dir = plugins_dir
45        self.plugins_prefix = plugins_prefix
46        self.logger = logging.getLogger(__name__)
47        self.strict_load = strict_load
48
49        self.posix = utils.is_posix()
50        """Is system posix"""
default_wd

Map of commands names to its metadata.

plugins_dir
plugins_prefix
logger
strict_load
posix

Is system posix

def shlex_split(self, cmd: str) -> list[str]:
52    def shlex_split(self, cmd: str) -> list[str]:
53        """
54        Splits a line like bash does(with passed posix param)
55        :param cmd: Input line(command)
56        :return: List of split: command name is the first element, the next ones are args
57        """
58        return shlex.split(cmd, posix=self.posix)

Splits a line like bash does(with passed posix param)

Parameters
  • cmd: Input line(command)
Returns

List of split: command name is the first element, the next ones are args

@staticmethod
def fetch_name_and_args(splitted: list) -> tuple[str, list[str]]:
60    @staticmethod
61    def fetch_name_and_args(splitted: list) -> tuple[str, list[str]]:
62        """
63        Gets command name and args from shlex split line
64        :param splitted: shlex split line
65        :return: command name, args
66        """
67        if not splitted:
68            return '', []
69        cmd_name = splitted[0]
70        if len(splitted) < 2:
71            return cmd_name, []
72        return cmd_name, splitted[1:]

Gets command name and args from shlex split line

Parameters
  • splitted: shlex split line
Returns

command name, args

def start_session(self):
 74    def start_session(self):
 75        """
 76        Starts an infinite loop of commandline session
 77        :return: None
 78        """
 79        self.load_modules()
 80        default = self.default_wd
 81        to_move = Path(default).expanduser()
 82        os.chdir(to_move)
 83        while True:
 84            cmd = input(f"{os.getcwd()} $ ").strip()
 85            if not cmd.strip():
 86                continue
 87            try:
 88                res = self.execute_command(cmd)
 89                if res:
 90                    res = res.strip()
 91                    if res.stderr:
 92                        errs = res.stderr.split("\n")
 93                        for err in errs:
 94                            if not err:
 95                                continue
 96                            cmd_name = self.parse_line(cmd)[0]
 97                            log_error(f"{cmd_name}: {err}", self.logger)
 98                    if res.stdout:
 99                        print(res.stdout)
100            except ImportError:
101                raise
102
103            except Exception as e:
104                try:
105                    name = self.shlex_split(cmd)[0]
106                    cmd_meta = self.cmd_map.get(name)
107                except ValueError:
108                    name = ''
109                    cmd_meta = CommandMetadata('default', 'default', 'default', '', LsCommand)
110                if cmd_meta.plugin_author != "default":
111                    utils.log_error(
112                        f"Author of plugin '{cmd_meta.plugin_name}' of version '{cmd_meta.plugin_version}' is a debil(real name - '{cmd_meta.plugin_author}'). His command '{name}' raised an unexpected error:",
113                        self.logger
114                    )
115                    utils.log_error(e, self.logger, exc=True)
116                else:
117                    utils.log_error(e, self.logger)

Starts an infinite loop of commandline session

Returns

None

def load_modules(self, outer_strict: bool = False):
119    def load_modules(self, outer_strict: bool = False):
120        """
121        Loads plugins
122        :param outer_strict: if method was called out of class, will be more prioritized than self.strict_load
123        :type outer_strict: bool
124
125        :return: None
126        """
127
128        if not outer_strict:
129            outer_strict = self.strict_load
130        plugins_loader = PluginLoader(self.plugins_dir, self.plugins_prefix, outer_strict)
131        plugins_loader.load_plugins()
132
133        self.cmd_map = plugins_loader.commands
134        #self.cmd_map["reload-plugins"] = CommandMetadata("reload-plugins", "default_plugin", "default", "1.0.0", ReloadPluginsCommand)

Loads plugins

Parameters
  • outer_strict: if method was called out of class, will be more prioritized than self.strict_load
Returns

None

def parse_line(self, line: str) -> tuple[str, list[str]] | None:
136    def parse_line(self, line: str) -> tuple[str, list[str]] | None:
137        """
138        Parses input line by calling self.shlex_split and self.fetch_name_and_args
139        :param line: Input line
140        :return: command name, args | None, if line was empty
141        """
142        args = self.shlex_split(line)
143        if not args:
144            return None
145        cmd_name, cmd_args = self.fetch_name_and_args(splitted=args)
146
147        return cmd_name, cmd_args

Parses input line by calling self.shlex_split and self.fetch_name_and_args

Parameters
  • line: Input line
Returns

command name, args | None, if line was empty

def execute_command(self, line: str):
149    def execute_command(self, line: str):
150        """
151        Executes input command
152        :param line:
153        :return: Result of command(None, if error occurred or command was not found)
154        """
155        parsed = self.parse_line(line)
156        if not parsed:
157            return None
158
159        cmd_name, cmd_args = parsed
160        if cmd_name == "help":
161            out = ""
162            for cmd in sorted(self.cmd_map.keys()):
163                out+=f"{cmd}\n"
164            return CommandOutput(stdout=out)
165
166
167        cmd_meta = self.cmd_map.get(cmd_name)
168        if not cmd_meta:
169            utils.write_history(line)
170            utils.log_error(f"{cmd_name}: command not found", self.logger)
171            return None
172
173        self.logger.info(line)
174        cmd_obj = cmd_meta.cmd(args = cmd_args)
175        cmd_obj.history()
176
177        if "--help" in cmd_args:
178            return cmd_obj.help() #TODO: --help keys
179
180        try:
181            return cmd_obj.handled_run()
182        except HANDLED_ERRORS as e:
183            log_error(f"{cmd_name}: {str(e)}", self.logger)

Executes input command

Parameters
  • line:
Returns

Result of command(None, if error occurred or command was not found)