From 865f66b79df693616e02bef9e555a25d84200baf Mon Sep 17 00:00:00 2001 From: Gnarwhal Date: Sat, 14 Sep 2024 18:14:28 +0000 Subject: [PATCH 1/9] Oops --- test_in.py => _test_in.py | 0 test_out.py => _test_out.py | 0 pyproject.toml | 5 +++++ src/sshare/plugin/plugin.py | 7 +++++-- src/sshare/validator.py | 2 +- tests/test_test.py | 4 ++++ 6 files changed, 15 insertions(+), 3 deletions(-) rename test_in.py => _test_in.py (100%) rename test_out.py => _test_out.py (100%) create mode 100644 tests/test_test.py diff --git a/test_in.py b/_test_in.py similarity index 100% rename from test_in.py rename to _test_in.py diff --git a/test_out.py b/_test_out.py similarity index 100% rename from test_out.py rename to _test_out.py diff --git a/pyproject.toml b/pyproject.toml index acb06ef..3fb26fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,3 +29,8 @@ sshare-validate = "sshare.validator:main" [tool.setuptools_scm] version_file = "src/sshare/version.py" + +[tool.pytest.ini_options] +addopts = [ + "--import-mode=importlib" +] diff --git a/src/sshare/plugin/plugin.py b/src/sshare/plugin/plugin.py index 3d75c6b..558f2c2 100644 --- a/src/sshare/plugin/plugin.py +++ b/src/sshare/plugin/plugin.py @@ -14,6 +14,9 @@ import importlib import importlib.util +import sys + +from pathlib import Path from sshare.plugin.config import Flag from sshare.plugin.config import NoDefault @@ -49,9 +52,9 @@ class PluginLoader: ] @staticmethod - def at(logger=None, config=dict(), flags=dict(), *args): + def at(*args, logger=None, config=dict(), flags=dict()): return [ - Plugin.external(plugin, logger, config, flags) + Plugin.external(Path(plugin), logger, config, flags) for plugin in args ] diff --git a/src/sshare/validator.py b/src/sshare/validator.py index f4e21fd..b003ba2 100644 --- a/src/sshare/validator.py +++ b/src/sshare/validator.py @@ -113,7 +113,7 @@ def main(): arg_parser.add_argument( "plugins", nargs="*", - help="plugin(s) to be validated", + help="plugin(s) to be validated (Default: all external plugins)", ) arguments = arg_parser.parse_args() if arguments.dev: diff --git a/tests/test_test.py b/tests/test_test.py new file mode 100644 index 0000000..3c71169 --- /dev/null +++ b/tests/test_test.py @@ -0,0 +1,4 @@ +from src.sshare.plugin.config import Argument + +def test_foo(): + assert True From 0f3b523d684dd99464d13e58483a06939be5f38b Mon Sep 17 00:00:00 2001 From: Gnarwhal Date: Sat, 14 Sep 2024 18:14:28 +0000 Subject: [PATCH 2/9] Oops --- test_in.py => _test_in.py | 0 test_out.py => _test_out.py | 0 pyproject.toml | 5 +++++ src/sshare/plugin/plugin.py | 7 +++++-- src/sshare/validator.py | 2 +- tests/test_test.py | 4 ++++ 6 files changed, 15 insertions(+), 3 deletions(-) rename test_in.py => _test_in.py (100%) rename test_out.py => _test_out.py (100%) create mode 100644 tests/test_test.py diff --git a/test_in.py b/_test_in.py similarity index 100% rename from test_in.py rename to _test_in.py diff --git a/test_out.py b/_test_out.py similarity index 100% rename from test_out.py rename to _test_out.py diff --git a/pyproject.toml b/pyproject.toml index acb06ef..3fb26fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,3 +29,8 @@ sshare-validate = "sshare.validator:main" [tool.setuptools_scm] version_file = "src/sshare/version.py" + +[tool.pytest.ini_options] +addopts = [ + "--import-mode=importlib" +] diff --git a/src/sshare/plugin/plugin.py b/src/sshare/plugin/plugin.py index 3d75c6b..558f2c2 100644 --- a/src/sshare/plugin/plugin.py +++ b/src/sshare/plugin/plugin.py @@ -14,6 +14,9 @@ import importlib import importlib.util +import sys + +from pathlib import Path from sshare.plugin.config import Flag from sshare.plugin.config import NoDefault @@ -49,9 +52,9 @@ class PluginLoader: ] @staticmethod - def at(logger=None, config=dict(), flags=dict(), *args): + def at(*args, logger=None, config=dict(), flags=dict()): return [ - Plugin.external(plugin, logger, config, flags) + Plugin.external(Path(plugin), logger, config, flags) for plugin in args ] diff --git a/src/sshare/validator.py b/src/sshare/validator.py index f4e21fd..b003ba2 100644 --- a/src/sshare/validator.py +++ b/src/sshare/validator.py @@ -113,7 +113,7 @@ def main(): arg_parser.add_argument( "plugins", nargs="*", - help="plugin(s) to be validated", + help="plugin(s) to be validated (Default: all external plugins)", ) arguments = arg_parser.parse_args() if arguments.dev: diff --git a/tests/test_test.py b/tests/test_test.py new file mode 100644 index 0000000..3c71169 --- /dev/null +++ b/tests/test_test.py @@ -0,0 +1,4 @@ +from src.sshare.plugin.config import Argument + +def test_foo(): + assert True From 82b4d8de7c8835f5c97d80e7cf5867550405f758 Mon Sep 17 00:00:00 2001 From: Gnarwhal Date: Tue, 24 Sep 2024 17:51:37 +0000 Subject: [PATCH 3/9] WIP command wrapper. Kind of works....but also janky. Not sure how to make it better :/ --- _test_out.py | 5 +-- src/sshare/plugin/config.py | 18 ++++++++--- src/sshare/plugin/plugin.py | 1 + src/sshare/plugins/stdin.py | 2 +- src/sshare/plugins/wrap_command.py | 50 ++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 src/sshare/plugins/wrap_command.py diff --git a/_test_out.py b/_test_out.py index f89bd5d..94711d4 100755 --- a/_test_out.py +++ b/_test_out.py @@ -1,4 +1,5 @@ #!/usr/bin/python -for i in range(0, 5): - print("\033[91mHello World!") +for i in range(0, 10000): + print(f"\033[{(i % 7) + 30}mHello World!") + print(f"\033[{(i % 7) + 90}mHello World!") diff --git a/src/sshare/plugin/config.py b/src/sshare/plugin/config.py index 6df34ed..b12f099 100644 --- a/src/sshare/plugin/config.py +++ b/src/sshare/plugin/config.py @@ -12,6 +12,8 @@ # You should have received a copy of the GNU General Public License along with # SSHare. If not, see . +import argparse + class NoDefault: pass def Flag(name=None, help=None): @@ -33,6 +35,8 @@ class Argument: self._short = None self._long = name + self._is_remainder = kwargs.get("nargs", None) == argparse.REMAINDER + if not "default" in kwargs: kwargs["default"] = NoDefault kwargs["default"] = _None(kwargs["default"]) @@ -43,6 +47,8 @@ class Argument: if self._long == None: self._long = argument self._kwargs["metavar"] = argument + if self._is_remainder: + self._long = self.dest() def set_flags(self, short, long): if short != None: @@ -75,11 +81,13 @@ class Argument: def add(self, arg_parser): flags = [] - if self._short != None: flags.append(f"-{self._short}") - if self._long != None: flags.append(f"--{self._long}") - kwargs = self._kwargs | { - "dest": self.dest() - } + kwargs = self._kwargs + if self._is_remainder: + flags.append(self._long) + else: + if self._short != None: flags.append(f"-{self._short}") + if self._long != None: flags.append(f"--{self._long}") + kwargs["dest"] = self.dest() arg_parser.add_argument( *flags, **kwargs diff --git a/src/sshare/plugin/plugin.py b/src/sshare/plugin/plugin.py index 558f2c2..dc413c6 100644 --- a/src/sshare/plugin/plugin.py +++ b/src/sshare/plugin/plugin.py @@ -35,6 +35,7 @@ class PluginLoader: in ([ "command_line" ] if command_line else []) + [ "file", "stdin", + "wrap_command", "current_time", "append_type", "ssh", diff --git a/src/sshare/plugins/stdin.py b/src/sshare/plugins/stdin.py index 7b219b6..d4edc64 100644 --- a/src/sshare/plugins/stdin.py +++ b/src/sshare/plugins/stdin.py @@ -21,7 +21,7 @@ plugin_type = "source" activate = { "stdin" } config = { - "suffix": "txt" + "suffix": "txt", } args = { "stdin": Flag(help="Upload from stdin") diff --git a/src/sshare/plugins/wrap_command.py b/src/sshare/plugins/wrap_command.py new file mode 100644 index 0000000..410874b --- /dev/null +++ b/src/sshare/plugins/wrap_command.py @@ -0,0 +1,50 @@ +# This file is part of SSHare. +# +# SSHare is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. +# +# SSHare is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# SSHare. If not, see . + +import argparse +import locale +import subprocess + +from sshare.plugin.config import Argument +from sshare.plugin.source import Raw + +plugin_type = "source" + +activate = { "command" } +config = { + "suffix": "txt", +} +args = { + "command": Argument( + nargs=argparse.REMAINDER, + help="Upload the contents of the wrapped command", + ) +} + +def init(): + config.command = config.command[1:] + +def get_source(): + output = b"" + with subprocess.Popen( + config.command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + bufsize=0, + ) as process: + for line in process.stdout: + print(line.decode(locale.getpreferredencoding()), end="") + output += line + + return Raw(config.suffix, output) From 92f5e1f47a61ea05ecfff6be3a6a9709e594072c Mon Sep 17 00:00:00 2001 From: Gnarwhal Date: Wed, 25 Sep 2024 01:51:12 +0000 Subject: [PATCH 4/9] Add rudimentary support for specifications --- examples | 2 +- src/sshare/main.py | 28 +++++++++++++++- src/sshare/plugin/plugin.py | 19 ++++++++--- src/sshare/plugin/source.py | 3 +- .../plugins/{append_type.py => extension.py} | 0 src/sshare/plugins/preserve.py | 32 +++++++++++++++++++ src/sshare/plugins/stdin.py | 2 +- .../plugins/{current_time.py => time.py} | 14 +++----- 8 files changed, 81 insertions(+), 19 deletions(-) rename src/sshare/plugins/{append_type.py => extension.py} (100%) create mode 100644 src/sshare/plugins/preserve.py rename src/sshare/plugins/{current_time.py => time.py} (80%) diff --git a/examples b/examples index 3273aa4..99f2e9c 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit 3273aa4e4c1898c484e989340a8525edb343cebe +Subproject commit 99f2e9c3d2fbab02b0582dc8dcf9ed05df7789a6 diff --git a/src/sshare/main.py b/src/sshare/main.py index da4369b..f0744ec 100644 --- a/src/sshare/main.py +++ b/src/sshare/main.py @@ -40,6 +40,31 @@ def main(): arguments, _ = arg_parser.parse_known_args() with open(arguments.config or config_directory.default_config(), mode="rb") as file: config = tomllib.load(file) + + config["spec"] = config.get("spec", {}) + for spec_name, spec in config["spec"].items(): + if spec_name != "default": + flags = [] + if spec.get("flag", {}).get("short") != None: + flags = flags + [ f"-{spec["flag"]["short"]}" ] + if spec.get("flag", {}).get("long") != None: + flags = flags + [ f"--{spec["flag"]["long"]}" ] + arg_parser.add_argument( + *flags, + action="store_const", + const=True, + default=False, + help=spec.get(help, f"Use {spec_name} spec"), + dest=spec_name, + ) + + use_spec = config["spec"].get("default", {}) + arguments, _ = arg_parser.parse_known_args() + for spec_name, spec in config["spec"].items(): + if spec_name != "default": + if getattr(arguments, spec_name): + use_spec = spec + config["config"] = config.get("config", {}) config["flags" ] = config.get("flags", {}) @@ -69,8 +94,9 @@ def main(): logger.add(command_line) plugins = PluginManager( logger, + use_spec, config["config"], - config["flags"], + config["flags" ], arg_parser, ) plugins.activate("logger") diff --git a/src/sshare/plugin/plugin.py b/src/sshare/plugin/plugin.py index 558f2c2..213db6d 100644 --- a/src/sshare/plugin/plugin.py +++ b/src/sshare/plugin/plugin.py @@ -35,8 +35,9 @@ class PluginLoader: in ([ "command_line" ] if command_line else []) + [ "file", "stdin", - "current_time", - "append_type", + "preserve", + "time", + "extension", "ssh", "uri", "print_location", @@ -60,7 +61,7 @@ class PluginLoader: ] class PluginManager: - def __init__(self, logger, config, flags, arg_parser): + def __init__(self, logger, spec, config, flags, arg_parser): self._logger = logger self._arg_parser = arg_parser @@ -71,14 +72,22 @@ class PluginManager: for type in Plugin.types(): setattr(self, type, PluginState()) - self._uninitialized = PluginLoader.all( + self._uninitialized = [] + uninitialized = PluginLoader.all( command_line=False, logger=logger, config=config, flags=flags, ) - for plugin in self._uninitialized: + for plugin in uninitialized: plugin.add_args(arg_parser) + if not "name" in plugin.plugin_type: + self._uninitialized.append(plugin) + + for name in spec.get("name", []): + for plugin in uninitialized: + if plugin.name == name: + self._uninitialized.append(plugin) def activate(self, activate_type=None): args = self._arg_parser.parse_args() diff --git a/src/sshare/plugin/source.py b/src/sshare/plugin/source.py index fcfc6a6..b2fede5 100644 --- a/src/sshare/plugin/source.py +++ b/src/sshare/plugin/source.py @@ -15,7 +15,8 @@ from pathlib import Path class Raw: - def __init__(self, type, data): + def __init__(self, name, type, data): + self.name = name self.type = type self.data = data diff --git a/src/sshare/plugins/append_type.py b/src/sshare/plugins/extension.py similarity index 100% rename from src/sshare/plugins/append_type.py rename to src/sshare/plugins/extension.py diff --git a/src/sshare/plugins/preserve.py b/src/sshare/plugins/preserve.py new file mode 100644 index 0000000..d010cde --- /dev/null +++ b/src/sshare/plugins/preserve.py @@ -0,0 +1,32 @@ +# This file is part of SSHare. +# +# SSHare is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. +# +# SSHare is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# SSHare. If not, see . + +from sshare.plugin.source import File +from sshare.plugin.source import Raw + +plugin_type = "name" + +def get_name(name, source): + if name != "": + name = name + "_" + else: + name = "" + if isinstance(source, File): + components = source.path.name.split(".") + if components[0] == "": + return name + "." + components[1] + else: + return name + components[0] + else: + return name + source.name diff --git a/src/sshare/plugins/stdin.py b/src/sshare/plugins/stdin.py index 7b219b6..7edccbe 100644 --- a/src/sshare/plugins/stdin.py +++ b/src/sshare/plugins/stdin.py @@ -28,4 +28,4 @@ args = { } def get_source(): - return Raw(config.suffix, sys.stdin.buffer.read()) + return Raw("stdin", config.suffix, sys.stdin.buffer.read()) diff --git a/src/sshare/plugins/current_time.py b/src/sshare/plugins/time.py similarity index 80% rename from src/sshare/plugins/current_time.py rename to src/sshare/plugins/time.py index 16cdd44..20f65d0 100644 --- a/src/sshare/plugins/current_time.py +++ b/src/sshare/plugins/time.py @@ -20,18 +20,12 @@ from sshare.plugin.source import File plugin_type = "name" config = { - "base": 62, + "format": 62, } - -def init(): - if not isinstance(config.base, int): - logger.fatal("Error: 'base' must be an integer") - elif config.base < 2: - logger.fatal("Error: 'base' cannot be less than 2") - elif config.base > 62: - logger.fatal("Error: 'base' cannot be greater than 62") - + def get_name(name, source): + if name != "": + name = name + "_" return name + _rebase(config.base, time.time_ns()) def _rebase(base, number): From 88e5aed6675fc121494a052a43a172a5d76dd373 Mon Sep 17 00:00:00 2001 From: Gnarwhal Date: Wed, 25 Sep 2024 04:18:00 +0000 Subject: [PATCH 5/9] Cleanup part 1 --- README.md | 153 ++++++++++++++++++++++++++++++++++++++++++--- _test_in.py | 5 -- _test_out.py | 4 -- pyproject.toml | 2 +- tests/test_test.py | 4 -- 5 files changed, 147 insertions(+), 21 deletions(-) delete mode 100755 _test_in.py delete mode 100755 _test_out.py delete mode 100644 tests/test_test.py diff --git a/README.md b/README.md index 38b03cc..aa8c6aa 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,6 @@ Upload files to a server via ssh -## Documentation - -- **End User**: coming soon... -- **Plugin Developer**: coming soon... - ## Installing SSHare is not available on [PyPI](https://pypi.org). @@ -23,7 +18,151 @@ Beyond that it only utilises the python standard library. ### Additional Functionality The base install is fairly bare bones in terms of functionality. -Included in the [`examples/`](https://forge.monodon.me/Gnarwhal/sshare_plugins) submodule, there are included some +Included in the [`examples/`](https://forge.monodon.me/Gnarwhal/sshare_plugins) submodule, there is a sampling of plugins which provide some additional conveniences -Refer to \[coming soon...\] for information on installing plugins +Refer to the above documentation for information on installing plugins + +## Documentation - End User + +Getting started with SSHare is about getting familiar with configuring SSHare. +SSHare configuration is all done in the `${XDG_CONFIG_DIR}/sshare` if set, or in `~/.config/sshare` otherwise. +In this directory are two important items: the `config.toml` file and the `plugins/` directory. + +### `plugins/` + +The `plugins/` directory is home to all external plugins. Installing a plugin is as simple as saving the +the plugin's `.py` file to the `plugins/`. + +### `config.toml` + +The configuration file can be broken down into three major components: `spec`, `config`, and `flags`. + +#### `spec` + +```toml + +[spec.default] +name = [ "time", "extension" ] + +[spec.example] +flag = { short = "e", long = "example" } +name = [ "preserve", "extension" ] + +[spec.noshort] +flag = { long = "only" } +name = [ "time" ] +help = "No short flag, only long flag >:(" +``` + +The `spec` section is a collection of different runtime specifications. The name of a specification is +unimportant with the exception of `default`, which is run when no other spec is activated. In each specification +that isn't `default`, there is a `flag` attribute that sets which command line arguments activate the +specification. The `flag` attribute must have one or both of `short` or `long`, where `short` will be set the flag +`-{short}` and `long` will set the flag `--{long}`. The `name` attribute of a specification is an array of name type +plugins which controls the order in which said plugins are executed. Specifications can also provide a brief description +in the `help` attribute, which will be displayed when `sshare --help` is run. In the example above, +running `sshare --help` would give +``` +options: + -e, --example Use example spec + --only No short flag, only long flag >:( +``` +#### `config` + +```toml +[config.ssh] +host = "example.com" +path = "/directory/to/store/files/in" +port = 22 +``` + +The `config` section is used to set configuration options for plugins. Refer to documentation for a plugin to see +what options can be set here. Documentation for default plugins is included [below](#Default-Plugins). + +#### `flags` + +```toml +[flags.file] +file = { short = "f" } + +[flags.stdin] +stdin = { short = "" } + +[flags.example] +option = { short = "o", long = "ooooption" } +``` + +The `flags` section is used to bind plugin arguments to a command line argument. They are specified in the same way +as the `flag` option for `spec`s. If `long` is left unspecified, the long flag will be set to the default provided by +the plugin. In the example above, running `sshare --help` would give: +``` +options: + -f file, --file file Upload a file + -, --stdin Upload from stdin + -o xmpl, --ooooption xmpl + This is not a real plugin that exists +``` + +### Default Plugins + +#### `command_line` + +A `logger` plugin which prints to the command line. No configuration needed. + +#### `extension` + +A `name` plugin which appends the source type to the end of the file name (i.e. `txt`, `png`, etc...). No configuration needed. + +#### `file` + +A `source` plugin which provides a file to be uploaded. File is specified with the `--file {file_path}` argument. No configuration needed. + +#### `preserve` + +A `name` plugin which preserves the name of the source in the uploaded file. No configuration needed. + +#### `print_location` + +A `feedback` plugin which prints the location that sources can be accessed from. No configuration needed. + +#### `ssh` + +An `upload` plugin which uploads the sources to the server using `ssh`. + +##### Configuration +| Attribute | Type | Default | Description | +|-----------|--------|---------|------------------------------------------------------| +| host | string | None | The hostname to ssh into | +| path | string | None | The path of the directory to upload the sources into | +| port | int | 22 | The port to ssh into | +| user | string | $USER | The user to ssh as | + +#### `stdin` + +A `source` plugin which reads from stdin. No configuration needed. + +#### `time` + +A `name` plugin which adds the current unix time to the name of the of the uploaded file. + +##### Configuration +| Attribute | Type | Default | Description | +|-----------|--------|---------|------------------------------------------------------| +| format | int | 62 | The base between 2 and 62 to represent the number in | + +##### `uri` + +A `location` plugin which constructs the resulting URI that the uploaded file can be accessed from. + +##### Configuration +| Attribute | Type | Default | Description | +|-----------|--------|---------|------------------------------------------------------| +| protocol | string | https | The protocol of the URI | +| host | string | None | The hostname the file can be accessed from | +| port | int | 22 | The port the file can be accessed at | +| path | string | None | The path the file can be accessed at | + +## Documentation - Plugin Development + +Coming soon... diff --git a/_test_in.py b/_test_in.py deleted file mode 100755 index 572e9d1..0000000 --- a/_test_in.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/python - -import sys - -print("=> " + sys.stdin.read()) diff --git a/_test_out.py b/_test_out.py deleted file mode 100755 index f89bd5d..0000000 --- a/_test_out.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/python - -for i in range(0, 5): - print("\033[91mHello World!") diff --git a/pyproject.toml b/pyproject.toml index 3fb26fb..b49baec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ requires-python = ">=3.11" [project.urls] Homepage = "https://forge.monodon.me/Gnarwhal/sshare" -Documentation = "https://forge.monodon.me/Gnarwhal/sshare/README.md#Usage" +Documentation = "https://forge.monodon.me/Gnarwhal/sshare/README.md" Repository = "https://forge.monodon.me/Gnarwhal/sshare" Issues = "https://forge.monodon.me/Gnarwhal/sshare/issues" diff --git a/tests/test_test.py b/tests/test_test.py deleted file mode 100644 index 3c71169..0000000 --- a/tests/test_test.py +++ /dev/null @@ -1,4 +0,0 @@ -from src.sshare.plugin.config import Argument - -def test_foo(): - assert True From 2d5041659f2b73b1ff2d6131b57855065e6a5f40 Mon Sep 17 00:00:00 2001 From: Gnarwhal Date: Sat, 5 Oct 2024 17:06:40 +0000 Subject: [PATCH 6/9] Yeah yeah ok GPL I get it --- src/sshare/config_directory.py | 3 +++ src/sshare/logger.py | 3 +++ src/sshare/main.py | 3 +++ src/sshare/plugin/__init__.py | 3 +++ src/sshare/plugin/config.py | 3 +++ src/sshare/plugin/plugin.py | 3 +++ src/sshare/plugin/source.py | 3 +++ src/sshare/plugins/command_line.py | 3 +++ src/sshare/plugins/extension.py | 3 +++ src/sshare/plugins/file.py | 3 +++ src/sshare/plugins/preserve.py | 3 +++ src/sshare/plugins/print_location.py | 3 +++ src/sshare/plugins/ssh.py | 3 +++ src/sshare/plugins/stdin.py | 3 +++ src/sshare/plugins/time.py | 3 +++ src/sshare/plugins/uri.py | 3 +++ src/sshare/validator.py | 3 +++ 17 files changed, 51 insertions(+) diff --git a/src/sshare/config_directory.py b/src/sshare/config_directory.py index 022480d..2f84532 100644 --- a/src/sshare/config_directory.py +++ b/src/sshare/config_directory.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/logger.py b/src/sshare/logger.py index 60d804c..6f16d85 100644 --- a/src/sshare/logger.py +++ b/src/sshare/logger.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/main.py b/src/sshare/main.py index f0744ec..f5c09e1 100644 --- a/src/sshare/main.py +++ b/src/sshare/main.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/plugin/__init__.py b/src/sshare/plugin/__init__.py index 60040ad..3dca581 100644 --- a/src/sshare/plugin/__init__.py +++ b/src/sshare/plugin/__init__.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/plugin/config.py b/src/sshare/plugin/config.py index 6df34ed..7714abc 100644 --- a/src/sshare/plugin/config.py +++ b/src/sshare/plugin/config.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/plugin/plugin.py b/src/sshare/plugin/plugin.py index 213db6d..5da1184 100644 --- a/src/sshare/plugin/plugin.py +++ b/src/sshare/plugin/plugin.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/plugin/source.py b/src/sshare/plugin/source.py index b2fede5..374988b 100644 --- a/src/sshare/plugin/source.py +++ b/src/sshare/plugin/source.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/plugins/command_line.py b/src/sshare/plugins/command_line.py index 6b86cce..7d1d2c7 100644 --- a/src/sshare/plugins/command_line.py +++ b/src/sshare/plugins/command_line.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/plugins/extension.py b/src/sshare/plugins/extension.py index 7b97012..670aabf 100644 --- a/src/sshare/plugins/extension.py +++ b/src/sshare/plugins/extension.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/plugins/file.py b/src/sshare/plugins/file.py index 12cd2c4..5216bae 100644 --- a/src/sshare/plugins/file.py +++ b/src/sshare/plugins/file.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/plugins/preserve.py b/src/sshare/plugins/preserve.py index d010cde..52773b0 100644 --- a/src/sshare/plugins/preserve.py +++ b/src/sshare/plugins/preserve.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/plugins/print_location.py b/src/sshare/plugins/print_location.py index ed2991e..eb00620 100644 --- a/src/sshare/plugins/print_location.py +++ b/src/sshare/plugins/print_location.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/plugins/ssh.py b/src/sshare/plugins/ssh.py index 2bf71cf..a1d3e3c 100644 --- a/src/sshare/plugins/ssh.py +++ b/src/sshare/plugins/ssh.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/plugins/stdin.py b/src/sshare/plugins/stdin.py index 7edccbe..bfc747f 100644 --- a/src/sshare/plugins/stdin.py +++ b/src/sshare/plugins/stdin.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/plugins/time.py b/src/sshare/plugins/time.py index 20f65d0..53e9777 100644 --- a/src/sshare/plugins/time.py +++ b/src/sshare/plugins/time.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/plugins/uri.py b/src/sshare/plugins/uri.py index 53078b7..143d95b 100644 --- a/src/sshare/plugins/uri.py +++ b/src/sshare/plugins/uri.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of diff --git a/src/sshare/validator.py b/src/sshare/validator.py index b003ba2..0516afa 100644 --- a/src/sshare/validator.py +++ b/src/sshare/validator.py @@ -1,3 +1,6 @@ +# SSHare - upload files to a server +# Copyright (c) 2024 Gnarwhal +# # This file is part of SSHare. # # SSHare is free software: you can redistribute it and/or modify it under the terms of From d99e0db0197f3b7f6830a8e6c7bcabcf06177c9d Mon Sep 17 00:00:00 2001 From: Gnarwhal Date: Sat, 5 Oct 2024 18:04:32 +0000 Subject: [PATCH 7/9] Cleanup part 2 --- README.md | 101 ++++++++++++++++++++++++++++++++++++- src/sshare/plugins/time.py | 6 +-- 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aa8c6aa..2eb9e09 100644 --- a/README.md +++ b/README.md @@ -165,4 +165,103 @@ A `location` plugin which constructs the resulting URI that the uploaded file ca ## Documentation - Plugin Development -Coming soon... +A plugin for SSHare is just a python file (`*.py`). There are currently 6 types of plugins: +- `logger` +- `source` +- `name` +- `upload` +- `location` +- `feedback` +A plugin can specify be any combination of the above types. For examples of plugins see [here](src/sshare/plugins) +and [here](https://forge.monodon.me/Gnarwhal/sshare_plugins) + +### General Attributes + +Every plugin regardless of type specifies or is provided with these attributes. + +#### `plugin_type` + +This mandatory paramater specifies what type(s) this plugin is. +It can be either a: +- `string` - Promotes to `{ string }` +- `set` - A set containing each of the plugin's types. + +#### `activate` + +This optional parameter specifies what flag(s) or argument(s) must be passed for this plugin to be activated. +It can be either a: +- `string` - Promotes to `{ string }` +- `set` - Promotes to `{ plugin_type: { string, ... }, plugin_type2: { string, ... }, ... }` +- `dict` - A dictionary that maps each type the plugin is, to the flags or arguments that activate the plugin for that type +All arguments specified in `activate` must be provided by the user for the plugin to activate. + +#### `config` + +This optional parameter specifies configuration options for the plugin. These values are what a user is allowed to change +through `config.toml`. It is a map containing `{ option: default_value, ... }`. If there is no default value (i.e. it is mandatory +that the user set it explicitly) for the option, it can be set to `NoDefault` provided by +`from sshare.plugin.config import NoDefault`. + +While `config` is initially specified as a `dict`, when the plugin is loaded it will be converted to an object with attributes. +For example, if a config is specified as +```python +config = { + "example0": 42, +} +``` +it would then be accessed by `config.example` not `config["example"]`. + +#### `args` + +This optional parameter specifies arguments for the plugin. These values are also accessed from the `config` object, +however they are provided via program arguments as opposed to being specified in `config.toml`. An option specified +in both `config` and `args` will be loaded from the config file first and overriden by the program argument if +provided. Arguments are of type `Argument` provided by `from sshare.plugin.config import Argument`. +`Argument` takes `name` (optionally) and a list of `kwargs` equivalent to the option document [here](https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument). As a convenience there is also `Flag` provided by +`from sshare.plugin.config import Flag`, which is for boolean arguments which take no parameters. +`Flag` only takes a `help=...` paramater. + +#### `init` + +If a plugin needs to do any initialisation, it can be done in the `init` method. The `init` method takes no parameters +and returns no values. + +#### `logger` + +Logger is not specified by the plugin developer, but is available inside the plugin if needed. The logger +has three levels: `info`, `warn` and `error`. + +### Type Specific Attributes + +#### `logger` +- `info(str)` +- `warn(str)` +- `error(str)` + +#### `source` -> `get_source()` + +The `get_source` function takes no arguments and returns a source. There are currently two types of sources provided by +`from sshare.plugin.source import (Raw | File)`. +- `Raw` - A raw data source. It has a type, a source name, and a byte array providing the data. +- `File` - A file or directory source. It has only the path to the file. + +#### `name` -> `get_name(current_name, source)` + +`name` plugins are chained one after the other. The first `name` plugin is provided an empty string for `current_name`. +Each subsequent `name` plugin is provided the output of the previous `name` plugin's `get_name` function. The `source` +parameter is the either `Raw` or `File` data source. + +#### `upload` -> `upload(name, source)` + +`upload` plugins are responsible for getting the source to the destination. + +#### `location` -> `get_location(name)` + +The `get_location` function takes in the name of a source and returns a location that the source can now be accessed from +(e.g. a URL). + +#### `feedback` -> `give_feedback(location)` + +The `give_feedback` function takes output from `location` plugins and presents it to the user (e.g. printing to console, +desktop notification. etc...). + diff --git a/src/sshare/plugins/time.py b/src/sshare/plugins/time.py index 53e9777..b186ca6 100644 --- a/src/sshare/plugins/time.py +++ b/src/sshare/plugins/time.py @@ -44,8 +44,8 @@ def _rebase(base, number): def _number_to_char(number): if number < 10: - return chr(number + 48) + return chr(number + 48) # 0-9 elif number < 36: - return chr(number + 87) + return chr(number + 87) # a-z else: - return chr(number + 29) + return chr(number + 29) # A-Z From 084ff6e5037f5355d80d1ec3764eb6bf0952c984 Mon Sep 17 00:00:00 2001 From: Gnarwhal Date: Sat, 5 Oct 2024 18:30:42 +0000 Subject: [PATCH 8/9] Fix time plugin --- src/sshare/plugins/time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sshare/plugins/time.py b/src/sshare/plugins/time.py index b186ca6..62b0bac 100644 --- a/src/sshare/plugins/time.py +++ b/src/sshare/plugins/time.py @@ -29,7 +29,7 @@ config = { def get_name(name, source): if name != "": name = name + "_" - return name + _rebase(config.base, time.time_ns()) + return name + _rebase(config.format, time.time_ns()) def _rebase(base, number): if number == 0: From ad5198499167fa5fd4c0314dad9f96ef4259e934 Mon Sep 17 00:00:00 2001 From: Gnarwhal Date: Sat, 5 Oct 2024 18:41:02 +0000 Subject: [PATCH 9/9] Don't crash if there is no plugin directory --- src/sshare/config_directory.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/sshare/config_directory.py b/src/sshare/config_directory.py index 2f84532..cf45ae0 100644 --- a/src/sshare/config_directory.py +++ b/src/sshare/config_directory.py @@ -25,9 +25,12 @@ def default_config(): return _LOCATION / "config.toml" def plugins(): - return [ - path for - path in - (_LOCATION / "plugins").iterdir() - if path.is_file() and path.suffix == ".py" - ] + if (_LOCATION / "plugins").is_dir(): + return [ + path for + path in + (_LOCATION / "plugins").iterdir() + if path.is_file() and path.suffix == ".py" + ] + else: + return []