diff --git a/README.md b/README.md index 2eb9e09..38b03cc 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ 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). @@ -18,250 +23,7 @@ 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 is a sampling of +Included in the [`examples/`](https://forge.monodon.me/Gnarwhal/sshare_plugins) submodule, there are included some plugins which provide some additional conveniences -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 - -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...). - +Refer to \[coming soon...\] for information on installing plugins diff --git a/_test_in.py b/_test_in.py new file mode 100755 index 0000000..572e9d1 --- /dev/null +++ b/_test_in.py @@ -0,0 +1,5 @@ +#!/usr/bin/python + +import sys + +print("=> " + sys.stdin.read()) diff --git a/_test_out.py b/_test_out.py new file mode 100755 index 0000000..94711d4 --- /dev/null +++ b/_test_out.py @@ -0,0 +1,5 @@ +#!/usr/bin/python + +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/examples b/examples index 99f2e9c..3273aa4 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit 99f2e9c3d2fbab02b0582dc8dcf9ed05df7789a6 +Subproject commit 3273aa4e4c1898c484e989340a8525edb343cebe diff --git a/pyproject.toml b/pyproject.toml index b49baec..3fb26fb 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" +Documentation = "https://forge.monodon.me/Gnarwhal/sshare/README.md#Usage" Repository = "https://forge.monodon.me/Gnarwhal/sshare" Issues = "https://forge.monodon.me/Gnarwhal/sshare/issues" diff --git a/src/sshare/config_directory.py b/src/sshare/config_directory.py index cf45ae0..022480d 100644 --- a/src/sshare/config_directory.py +++ b/src/sshare/config_directory.py @@ -1,6 +1,3 @@ -# 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 @@ -25,12 +22,9 @@ def default_config(): return _LOCATION / "config.toml" def plugins(): - if (_LOCATION / "plugins").is_dir(): - return [ - path for - path in - (_LOCATION / "plugins").iterdir() - if path.is_file() and path.suffix == ".py" - ] - else: - return [] + return [ + path for + path in + (_LOCATION / "plugins").iterdir() + if path.is_file() and path.suffix == ".py" + ] diff --git a/src/sshare/logger.py b/src/sshare/logger.py index 6f16d85..60d804c 100644 --- a/src/sshare/logger.py +++ b/src/sshare/logger.py @@ -1,6 +1,3 @@ -# 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 f5c09e1..da4369b 100644 --- a/src/sshare/main.py +++ b/src/sshare/main.py @@ -1,6 +1,3 @@ -# 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 @@ -43,31 +40,6 @@ 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", {}) @@ -97,9 +69,8 @@ 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/__init__.py b/src/sshare/plugin/__init__.py index 3dca581..60040ad 100644 --- a/src/sshare/plugin/__init__.py +++ b/src/sshare/plugin/__init__.py @@ -1,6 +1,3 @@ -# 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 7714abc..b12f099 100644 --- a/src/sshare/plugin/config.py +++ b/src/sshare/plugin/config.py @@ -1,6 +1,3 @@ -# 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 @@ -15,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): @@ -36,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"]) @@ -46,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: @@ -78,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 5da1184..dc413c6 100644 --- a/src/sshare/plugin/plugin.py +++ b/src/sshare/plugin/plugin.py @@ -1,6 +1,3 @@ -# 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 @@ -38,9 +35,9 @@ class PluginLoader: in ([ "command_line" ] if command_line else []) + [ "file", "stdin", - "preserve", - "time", - "extension", + "wrap_command", + "current_time", + "append_type", "ssh", "uri", "print_location", @@ -64,7 +61,7 @@ class PluginLoader: ] class PluginManager: - def __init__(self, logger, spec, config, flags, arg_parser): + def __init__(self, logger, config, flags, arg_parser): self._logger = logger self._arg_parser = arg_parser @@ -75,22 +72,14 @@ class PluginManager: for type in Plugin.types(): setattr(self, type, PluginState()) - self._uninitialized = [] - uninitialized = PluginLoader.all( + self._uninitialized = PluginLoader.all( command_line=False, logger=logger, config=config, flags=flags, ) - for plugin in uninitialized: + for plugin in self._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 374988b..fcfc6a6 100644 --- a/src/sshare/plugin/source.py +++ b/src/sshare/plugin/source.py @@ -1,6 +1,3 @@ -# 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 @@ -18,8 +15,7 @@ from pathlib import Path class Raw: - def __init__(self, name, type, data): - self.name = name + def __init__(self, type, data): self.type = type self.data = data diff --git a/src/sshare/plugins/extension.py b/src/sshare/plugins/append_type.py similarity index 94% rename from src/sshare/plugins/extension.py rename to src/sshare/plugins/append_type.py index 670aabf..7b97012 100644 --- a/src/sshare/plugins/extension.py +++ b/src/sshare/plugins/append_type.py @@ -1,6 +1,3 @@ -# 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 7d1d2c7..6b86cce 100644 --- a/src/sshare/plugins/command_line.py +++ b/src/sshare/plugins/command_line.py @@ -1,6 +1,3 @@ -# 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/current_time.py similarity index 71% rename from src/sshare/plugins/time.py rename to src/sshare/plugins/current_time.py index 62b0bac..16cdd44 100644 --- a/src/sshare/plugins/time.py +++ b/src/sshare/plugins/current_time.py @@ -1,6 +1,3 @@ -# 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 @@ -23,13 +20,19 @@ from sshare.plugin.source import File plugin_type = "name" config = { - "format": 62, + "base": 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.format, time.time_ns()) + return name + _rebase(config.base, time.time_ns()) def _rebase(base, number): if number == 0: @@ -44,8 +47,8 @@ def _rebase(base, number): def _number_to_char(number): if number < 10: - return chr(number + 48) # 0-9 + return chr(number + 48) elif number < 36: - return chr(number + 87) # a-z + return chr(number + 87) else: - return chr(number + 29) # A-Z + return chr(number + 29) diff --git a/src/sshare/plugins/file.py b/src/sshare/plugins/file.py index 5216bae..12cd2c4 100644 --- a/src/sshare/plugins/file.py +++ b/src/sshare/plugins/file.py @@ -1,6 +1,3 @@ -# 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 deleted file mode 100644 index 52773b0..0000000 --- a/src/sshare/plugins/preserve.py +++ /dev/null @@ -1,35 +0,0 @@ -# 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 -# 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/print_location.py b/src/sshare/plugins/print_location.py index eb00620..ed2991e 100644 --- a/src/sshare/plugins/print_location.py +++ b/src/sshare/plugins/print_location.py @@ -1,6 +1,3 @@ -# 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 a1d3e3c..2bf71cf 100644 --- a/src/sshare/plugins/ssh.py +++ b/src/sshare/plugins/ssh.py @@ -1,6 +1,3 @@ -# 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 bfc747f..d4edc64 100644 --- a/src/sshare/plugins/stdin.py +++ b/src/sshare/plugins/stdin.py @@ -1,6 +1,3 @@ -# 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 @@ -24,11 +21,11 @@ plugin_type = "source" activate = { "stdin" } config = { - "suffix": "txt" + "suffix": "txt", } args = { "stdin": Flag(help="Upload from stdin") } def get_source(): - return Raw("stdin", config.suffix, sys.stdin.buffer.read()) + return Raw(config.suffix, sys.stdin.buffer.read()) diff --git a/src/sshare/plugins/uri.py b/src/sshare/plugins/uri.py index 143d95b..53078b7 100644 --- a/src/sshare/plugins/uri.py +++ b/src/sshare/plugins/uri.py @@ -1,6 +1,3 @@ -# 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/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) diff --git a/src/sshare/validator.py b/src/sshare/validator.py index 0516afa..b003ba2 100644 --- a/src/sshare/validator.py +++ b/src/sshare/validator.py @@ -1,6 +1,3 @@ -# 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/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