Compare commits
No commits in common. "mia" and "v1.0.0" have entirely different histories.
25 changed files with 379 additions and 1369 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -159,6 +159,3 @@ cython_debug/
|
|||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# setuptools_scm version file
|
||||
src/sshare/version.py
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +0,0 @@
|
|||
[submodule "examples"]
|
||||
path = examples
|
||||
url = git@forge.monodon.me:Gnarwhal/sshare_plugins.git
|
143
LICENSE
143
LICENSE
|
@ -1,5 +1,5 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
|
@ -7,17 +7,15 @@
|
|||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
|
@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
|||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
@ -72,7 +60,7 @@ modification follow.
|
|||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
|||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
|
@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program 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
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program 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.
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
|
360
README.md
360
README.md
|
@ -2,266 +2,146 @@
|
|||
|
||||
Upload files to a server via ssh
|
||||
|
||||
## Installing
|
||||
## Documentation
|
||||
|
||||
SSHare is not available on [PyPI](https://pypi.org).
|
||||
To install it you will need to clone the repo and use pip to install it
|
||||
### Arguments
|
||||
|
||||
#### `-h` `--help`
|
||||
Show the help message and exit
|
||||
|
||||
#### `-v` `--version`
|
||||
Show the program's version number and exit
|
||||
|
||||
#### `-l` `--latest`
|
||||
Upload the most recently modified file in `source_directory`
|
||||
|
||||
#### `-p` `--paste`
|
||||
Upload the contents of the clipboard as a `.txt` file
|
||||
|
||||
#### `-f FILE` `--file FILE`
|
||||
Upload a file
|
||||
|
||||
#### `-c` `--copy`
|
||||
Copy the resultant URL to the clipboard
|
||||
|
||||
### Configuration File
|
||||
|
||||
The configuration file is located at `$XDG_CONFIG_DIR/sshare/config.toml`.
|
||||
If `XDG_CONFIG_DIR` is not set, then it will read from `$HOME/.config/sshare/config.toml` instead.
|
||||
|
||||
#### `source_directory`
|
||||
The directory from which `--latest` reads.
|
||||
- [x] Optional - Cannot use `--latest` if it is not set
|
||||
|
||||
#### `host`
|
||||
```
|
||||
git clone https://forge.monodon.me/Gnarwhal/sshare.git
|
||||
cd sshare
|
||||
pip install ./
|
||||
https://example.com:4430/path/to/files/*
|
||||
^---^ ^---------^ ^--^^------------^
|
||||
| | | |
|
||||
| | | +- `host.path` = "/path/to/files"
|
||||
| | +----- `host.port` = 4430
|
||||
| +----------------- `host.name` = "example.com"
|
||||
+------------------------- `host.protocol` = "https"
|
||||
|
||||
https://example.com/*
|
||||
^---------^
|
||||
|
|
||||
| `host.path` Not set
|
||||
| `host.port` Not set
|
||||
+----------------- `host.name` = "example.com"
|
||||
`host.protocol` Not set (Defaults to `https`)
|
||||
```
|
||||
#### `host.protocol`
|
||||
The protocol by which the file is served
|
||||
- [x] Optional - Default: `https`
|
||||
|
||||
By default, the only dependency SSHare has is the system's `ssh` (and `scp`) commands.
|
||||
Beyond that it only utilises the python standard library.
|
||||
#### `host.name`
|
||||
The name of the host (e.g. `example.com`, `1.1.1.1`)
|
||||
- [ ] Not optional
|
||||
|
||||
### Additional Functionality
|
||||
#### `host.port`
|
||||
The port from which the host is serving files
|
||||
- [x] Optional - Default: `None` (which means the default port for the protocol)
|
||||
|
||||
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
|
||||
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.
|
||||
#### `host.path`
|
||||
The subpath from which the host is serving files. If set, `host.path` must start
|
||||
with and **NOT** end with a `/` (e.g. `/path/to/files` not `path/to/files/`)
|
||||
- [x] Optional - If the host is serving from the root
|
||||
|
||||
#### `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"]`.
|
||||
ssh -p 1234 example@example.com:/srv/static
|
||||
^--^ ^-----^ ^---------^ ^---------^
|
||||
| | | |
|
||||
| | | +- `ssh.path` = "/srv/static"
|
||||
| | +------------- Uses `host.name`
|
||||
| +--------------------- `ssh.user` = "example"
|
||||
+-------------------------- `ssh.port` = 1234"
|
||||
|
||||
#### `args`
|
||||
ssh example@example.com:/srv/static
|
||||
^-----^ ^---------^ ^---------^
|
||||
| | |
|
||||
| | +- `ssh.path` = "/srv/static"
|
||||
| +------------- Uses `host.name`
|
||||
+--------------------- `ssh.user` Doesn't need to be set if `example` is
|
||||
the name of the user running `sshare`
|
||||
`ssh.part` Not set (Defaults to 22)
|
||||
```
|
||||
|
||||
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.
|
||||
#### `ssh.port`
|
||||
The port the server is listening for ssh connections on
|
||||
- [x] Optional - Default: 22
|
||||
|
||||
#### `init`
|
||||
#### `ssh.user`
|
||||
The user to connect to the host with
|
||||
- [x] Optional - Default: The user that ran `sshare`
|
||||
|
||||
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.
|
||||
#### `ssh.path`
|
||||
The directory on the host from which static files are being hosted. If set,
|
||||
`ssh.path` must **NOT** end with a path (e.g. `/srv/static` not `/srv/static/`)
|
||||
- [ ] Not optional
|
||||
|
||||
#### `logger`
|
||||
#### Example Configuration File
|
||||
|
||||
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`.
|
||||
```toml
|
||||
# config.toml
|
||||
|
||||
### Type Specific Attributes
|
||||
source_directory = "/home/example/Pictures/Screenshots"
|
||||
|
||||
#### `logger`
|
||||
- `info(str)`
|
||||
- `warn(str)`
|
||||
- `error(str)`
|
||||
# Host is serving static files at https://example.com:443/sshare/*
|
||||
# Note: both protocol and port would be optional here as https is
|
||||
# the default protocol and port 443 is the default https port
|
||||
[host]
|
||||
protocol = "https"
|
||||
name = "example.com"
|
||||
port = 443
|
||||
path = "/sshare"
|
||||
|
||||
#### `source` -> `get_source()`
|
||||
# Host is listening for ssh connections on port 1234 and
|
||||
# serving files from the `/srv/sshare` directory
|
||||
[ssh]
|
||||
port = 1234
|
||||
user = "exampleuser2"
|
||||
path = "/srv/sshare"
|
||||
```
|
||||
|
||||
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.
|
||||
## Hmmm
|
||||
|
||||
#### `name` -> `get_name(current_name, source)`
|
||||
Apparently GNOME 42 screenshot utility is called via dbus
|
||||
|
||||
`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.
|
||||
```
|
||||
gdbus call
|
||||
--session
|
||||
--dest org.gnome.Shell
|
||||
--object-path /org/gnome/Shell
|
||||
--method org.gnome.Shell.Eval 'Main.screenshotUI.open()'
|
||||
````
|
||||
but it cannot be called unless `global.context.unsafe_mode = false`
|
||||
is set in gnome shell...which is unideal.
|
||||
|
||||
#### `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...).
|
||||
The ideal would be to launch a desired screenshot (or other generative)
|
||||
utility before running sshare, and for everything that isn't GNOME 42 screenshot,
|
||||
I would not expect it to be too hard. But unfortunately I want to use
|
||||
GNOME 42 screenshot ;-;
|
||||
|
||||
What to do...
|
||||
|
|
1
examples
1
examples
|
@ -1 +0,0 @@
|
|||
Subproject commit 99f2e9c3d2fbab02b0582dc8dcf9ed05df7789a6
|
|
@ -1,10 +1,10 @@
|
|||
[build-system]
|
||||
requires = ["setuptools", "setuptools_scm"]
|
||||
requires = ["setuptools"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "sshare"
|
||||
dynamic = ["version"]
|
||||
version = "1.0.0"
|
||||
authors = [
|
||||
{ name = "Gnarwhal", email = "git.aspect893@passmail.net" },
|
||||
]
|
||||
|
@ -16,21 +16,15 @@ classifiers = [
|
|||
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
||||
]
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"pyclip",
|
||||
]
|
||||
|
||||
[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"
|
||||
|
||||
[project.scripts]
|
||||
sshare = "sshare.main:main"
|
||||
sshare-validate = "sshare.validator:main"
|
||||
|
||||
[tool.setuptools_scm]
|
||||
version_file = "src/sshare/version.py"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = [
|
||||
"--import-mode=importlib"
|
||||
]
|
||||
sshare = "sshare.cli:main"
|
||||
|
|
|
@ -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,4 +12,6 @@
|
|||
# You should have received a copy of the GNU General Public License along with
|
||||
# SSHare. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from .plugin import *
|
||||
from cli import main
|
||||
|
||||
main()
|
183
src/sshare/cli.py
Normal file
183
src/sshare/cli.py
Normal file
|
@ -0,0 +1,183 @@
|
|||
# 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
import os
|
||||
import os.path
|
||||
import pyclip
|
||||
import time
|
||||
import tomllib
|
||||
import subprocess
|
||||
import sys
|
||||
from sshare.version import version
|
||||
|
||||
class Config:
|
||||
def __init__(self):
|
||||
config_directory = os.environ.get("XDG_CONFIG_DIR")
|
||||
if config_directory == None:
|
||||
config_directory = f"{os.environ["HOME"]}/.config"
|
||||
_config = self._load_from_file(f"{config_directory}/sshare/config.toml")
|
||||
|
||||
self.source_directory = _config.get("source_directory")
|
||||
|
||||
host = _config.get("host")
|
||||
if host == None:
|
||||
print("Error: 'host' cannot be 'None'")
|
||||
sys.exit(1)
|
||||
self.host_protocol = host.get("protocol")
|
||||
self.host_name = host.get("name")
|
||||
self.host_port = host.get("port")
|
||||
self.host_path = host.get("path")
|
||||
if self.host_protocol == None:
|
||||
self.host_protocol = "https"
|
||||
if self.host_name == None:
|
||||
print("Error: 'host.name' cannot be 'None'")
|
||||
sys.exit(1)
|
||||
if self.host_port == None:
|
||||
self.host_port = ""
|
||||
else:
|
||||
self.host_port = f":{self.host_port}"
|
||||
if self.host_path == None:
|
||||
self.host_path = ""
|
||||
|
||||
ssh = _config.get("ssh")
|
||||
if ssh == None:
|
||||
print("Error: 'ssh' cannot be 'None'")
|
||||
sys.exit(1)
|
||||
self.ssh_port = ssh.get("port")
|
||||
self.ssh_user = ssh.get("user")
|
||||
self.ssh_path = ssh.get("path")
|
||||
if self.ssh_port == None:
|
||||
self.ssh_port = 22
|
||||
if self.ssh_user == None:
|
||||
self.ssh_user = getpass.getuser()
|
||||
if self.ssh_path == None:
|
||||
print("Error: 'ssh.path' cannot be 'None'")
|
||||
sys.exit(1)
|
||||
|
||||
def _load_from_file(self, config_path):
|
||||
with open(config_path, mode="rb") as file:
|
||||
return tomllib.load(file)
|
||||
|
||||
def main():
|
||||
arguments = parse_arguments()
|
||||
|
||||
config = Config()
|
||||
|
||||
contents = b''
|
||||
target_file_extension = ""
|
||||
if arguments.latest or arguments.file != None:
|
||||
file_path = ""
|
||||
if arguments.latest:
|
||||
if config.source_directory == "":
|
||||
print("Option 'latest' requires source directory to be specified")
|
||||
sys.exit(1)
|
||||
file_path = _latest(config.source_directory)
|
||||
else:
|
||||
file_path = arguments.file
|
||||
print(f"Uploading file '{file_path}'")
|
||||
|
||||
with open(file_path, mode="rb") as file:
|
||||
contents = file.read()
|
||||
|
||||
(_, target_file_extension) = os.path.splitext(file_path)
|
||||
elif arguments.paste:
|
||||
print("Uploading contents of clipboard")
|
||||
contents = pyclip.paste()
|
||||
target_file_extension = ".txt"
|
||||
else:
|
||||
print("Error: must specify one of -f FILE, -l, -p")
|
||||
sys.exit(1)
|
||||
|
||||
target_id = time.time_ns()
|
||||
target_file_name = f"{target_id}{target_file_extension}"
|
||||
target_file = f"{config.ssh_path}/{target_file_name}"
|
||||
target_destination = f"{config.ssh_user}@{config.host_name}"
|
||||
print(f"Uploading to host: {target_destination}, port: {config.ssh_port}, file: {target_file}")
|
||||
process = subprocess.run([
|
||||
"ssh",
|
||||
f"-p {config.ssh_port}",
|
||||
target_destination,
|
||||
"-T",
|
||||
f"cat - > {target_file}"
|
||||
],
|
||||
input = contents,
|
||||
)
|
||||
if process.returncode != 0:
|
||||
print("Error: failed to upload file")
|
||||
sys.exit(1)
|
||||
|
||||
target_url = f"{config.host_protocol}://{config.host_name}{config.host_port}{config.host_path}/{target_file_name}"
|
||||
print(f"File available at '{target_url}'")
|
||||
if arguments.copy:
|
||||
pyclip.copy(target_url)
|
||||
print("URL copied to clipboard")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog = "SSHare",
|
||||
description = "Upload files to a server via ssh",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--version",
|
||||
action="version",
|
||||
version=f"%(prog)s version {version}",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--latest",
|
||||
action="store_const",
|
||||
const=True,
|
||||
help="Upload the latest image from the source directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--paste",
|
||||
action="store_const",
|
||||
const=True,
|
||||
help="Upload the contents of the clipboard as a .txt file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--file",
|
||||
help="Upload a file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--copy",
|
||||
action="store_const",
|
||||
const=True,
|
||||
help="Copy the resultant URL to the clipboard",
|
||||
)
|
||||
arguments = parser.parse_args()
|
||||
|
||||
return arguments
|
||||
|
||||
|
||||
def _latest(directory, key=os.path.getmtime):
|
||||
files = map(lambda file: f"{directory}/{file}", os.listdir(directory))
|
||||
selection = next(files)
|
||||
selection_key = key(selection)
|
||||
for file in files:
|
||||
new_key = key(file)
|
||||
if new_key > selection_key:
|
||||
selection = file
|
||||
selection_key = key
|
||||
return selection
|
|
@ -1,36 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
_LOCATION = Path(os.environ.get("XDG_CONFIG_DIR", f"{os.environ["HOME"]}/.config")) / "sshare"
|
||||
|
||||
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 []
|
|
@ -1,45 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
|
||||
from sshare.plugin import Plugin
|
||||
|
||||
class Logger:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._loggers = []
|
||||
self.add(*args)
|
||||
|
||||
def add(self, *args, **kwargs):
|
||||
for logger in args:
|
||||
self._loggers.append(logger)
|
||||
|
||||
def info(self, message):
|
||||
for logger in self._loggers:
|
||||
logger.info(message)
|
||||
|
||||
def warn(self, message):
|
||||
for logger in self._loggers:
|
||||
logger.warn(message)
|
||||
|
||||
def error(self, message):
|
||||
for logger in self._loggers:
|
||||
logger.error(message)
|
||||
|
||||
def fatal(self, message, error_code=1):
|
||||
self.error(message)
|
||||
sys.exit(error_code)
|
|
@ -1,141 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
import argparse
|
||||
import getpass
|
||||
import os
|
||||
import time
|
||||
import tomllib
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from sshare import config_directory
|
||||
from sshare.logger import Logger
|
||||
from sshare.plugin import Plugin
|
||||
from sshare.plugin import PluginManager
|
||||
from sshare.version import version
|
||||
|
||||
def main():
|
||||
arg_parser = argparse.ArgumentParser(
|
||||
prog="sshare",
|
||||
description="Upload files to a server via ssh",
|
||||
add_help=False,
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"--config",
|
||||
metavar="config",
|
||||
help="Specify location of config file to use"
|
||||
)
|
||||
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", {})
|
||||
|
||||
arg_parser.add_argument(
|
||||
"-h",
|
||||
"--help",
|
||||
action="help",
|
||||
help="show this help message and exit"
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"-v",
|
||||
"--version",
|
||||
action="version",
|
||||
version=f"%(prog)s version {version}",
|
||||
)
|
||||
|
||||
# Load command line early and set it as the active logger
|
||||
# so that it can be used to report errors while loading and
|
||||
# configuring other loggers
|
||||
logger = Logger()
|
||||
command_line = Plugin.internal(
|
||||
"command_line",
|
||||
logger,
|
||||
config["config"],
|
||||
config["flags"],
|
||||
)
|
||||
logger.add(command_line)
|
||||
plugins = PluginManager(
|
||||
logger,
|
||||
use_spec,
|
||||
config["config"],
|
||||
config["flags" ],
|
||||
arg_parser,
|
||||
)
|
||||
plugins.activate("logger")
|
||||
logger.add(*plugins.logger.active)
|
||||
plugins.activate()
|
||||
|
||||
for plugin_type in [ "source", "name", "upload" ]:
|
||||
plugins_of_type = getattr(plugins, plugin_type)
|
||||
if len(plugins_of_type.active) == 0:
|
||||
logger.error(f"Error: No '{plugin_type}' plugins activated. Available plugins:")
|
||||
for plugin in plugins_of_type.inactive:
|
||||
logger.error(f" => {plugin.name}")
|
||||
sys.exit(1)
|
||||
if len(plugins.location.active) == 0 and len(plugins.feedback.active) > 0:
|
||||
logger.warn("Warning: 'feedback' plugins activated with no active 'location' plugins")
|
||||
|
||||
sources = []
|
||||
for plugin in plugins.source.active:
|
||||
sources.append(plugin.get_source())
|
||||
|
||||
for index, source in enumerate(sources):
|
||||
name = ""
|
||||
for plugin in plugins.name.active:
|
||||
name = plugin.get_name(name, source)
|
||||
sources[index] = name, source
|
||||
|
||||
for name, source in sources:
|
||||
for plugin in plugins.upload.active:
|
||||
plugin.upload(name, source)
|
||||
|
||||
for index, (name, _) in enumerate(sources):
|
||||
for plugin in plugins.location.active:
|
||||
sources[index] = plugin.get_location(name)
|
||||
|
||||
for location in sources:
|
||||
for plugin in plugins.feedback.active:
|
||||
sources[index] = plugin.give_feedback(location)
|
||||
|
||||
sys.exit(0)
|
|
@ -1,89 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
class NoDefault: pass
|
||||
|
||||
def Flag(name=None, help=None):
|
||||
return Argument(
|
||||
name,
|
||||
action="store_const",
|
||||
const=Flag,
|
||||
default=False,
|
||||
help=help,
|
||||
)
|
||||
|
||||
class Argument:
|
||||
def __init__(self, name=None, **kwargs):
|
||||
class _None:
|
||||
def __init__(self, default):
|
||||
self.default = default
|
||||
self._None = _None
|
||||
|
||||
self._short = None
|
||||
self._long = name
|
||||
|
||||
if not "default" in kwargs:
|
||||
kwargs["default"] = NoDefault
|
||||
kwargs["default"] = _None(kwargs["default"])
|
||||
self._kwargs = kwargs
|
||||
|
||||
def bind(self, plugin, argument):
|
||||
self._plugin = plugin.name
|
||||
if self._long == None:
|
||||
self._long = argument
|
||||
self._kwargs["metavar"] = argument
|
||||
|
||||
def set_flags(self, short, long):
|
||||
if short != None:
|
||||
if short == False:
|
||||
self._short = None
|
||||
else:
|
||||
self._short = short
|
||||
if long != None:
|
||||
if long == False:
|
||||
self._long = None
|
||||
else:
|
||||
self._long = long
|
||||
|
||||
def default(self):
|
||||
value = self._kwargs["default"]
|
||||
if isinstance(value, self._None):
|
||||
value = value.default
|
||||
return value
|
||||
|
||||
def dest(self):
|
||||
return f"{self._plugin}_{self._kwargs["metavar"]}"
|
||||
|
||||
def extract(self, arguments):
|
||||
value = getattr(arguments, self.dest())
|
||||
was_set = True
|
||||
if isinstance(value, self._None):
|
||||
was_set = False
|
||||
value = value.default
|
||||
return was_set, value
|
||||
|
||||
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()
|
||||
}
|
||||
arg_parser.add_argument(
|
||||
*flags,
|
||||
**kwargs
|
||||
)
|
|
@ -1,211 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
import importlib
|
||||
import importlib.util
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from sshare.plugin.config import Flag
|
||||
from sshare.plugin.config import NoDefault
|
||||
from sshare import config_directory
|
||||
|
||||
class PluginLoader:
|
||||
@staticmethod
|
||||
def all(command_line=False, logger=None, config=dict(), flags=dict()):
|
||||
return PluginLoader.internal(command_line, logger, config, flags) + PluginLoader.external(logger, config, flags)
|
||||
|
||||
@staticmethod
|
||||
def internal(command_line=False, logger=None, config=dict(), flags=dict()):
|
||||
return [
|
||||
Plugin.internal(plugin, logger, config, flags)
|
||||
for plugin
|
||||
in ([ "command_line" ] if command_line else []) + [
|
||||
"file",
|
||||
"stdin",
|
||||
"preserve",
|
||||
"time",
|
||||
"extension",
|
||||
"ssh",
|
||||
"uri",
|
||||
"print_location",
|
||||
]
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def external(logger=None, config=dict(), flags=dict()):
|
||||
return [
|
||||
Plugin.external(plugin, logger, config, flags)
|
||||
for plugin
|
||||
in config_directory.plugins()
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def at(*args, logger=None, config=dict(), flags=dict()):
|
||||
return [
|
||||
Plugin.external(Path(plugin), logger, config, flags)
|
||||
for plugin
|
||||
in args
|
||||
]
|
||||
|
||||
class PluginManager:
|
||||
def __init__(self, logger, spec, config, flags, arg_parser):
|
||||
self._logger = logger
|
||||
self._arg_parser = arg_parser
|
||||
|
||||
class PluginState:
|
||||
def __init__(self):
|
||||
self.active = []
|
||||
self.inactive = []
|
||||
for type in Plugin.types():
|
||||
setattr(self, type, PluginState())
|
||||
|
||||
self._uninitialized = []
|
||||
uninitialized = PluginLoader.all(
|
||||
command_line=False,
|
||||
logger=logger,
|
||||
config=config,
|
||||
flags=flags,
|
||||
)
|
||||
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()
|
||||
for plugin in self._uninitialized.copy():
|
||||
if activate_type == None or activate_type in plugin.plugin_type:
|
||||
self._uninitialized.remove(plugin)
|
||||
active = plugin.load_args_and_activate(args)
|
||||
for type in plugin.plugin_type:
|
||||
getattr(
|
||||
getattr(self, type),
|
||||
active[type],
|
||||
).append(plugin)
|
||||
|
||||
class Plugin:
|
||||
@staticmethod
|
||||
def types(methods=False):
|
||||
types = {
|
||||
"logger": { "info", "warn", "error" },
|
||||
"source": { "get_source" },
|
||||
"name": { "get_name" },
|
||||
"upload": { "upload" },
|
||||
"location": { "get_location" },
|
||||
"feedback": { "give_feedback"},
|
||||
}
|
||||
if methods:
|
||||
return types
|
||||
else:
|
||||
return types.keys()
|
||||
|
||||
def __init__(self, name, module, logger, external_config, external_flags):
|
||||
self.__dict__ = module.__dict__
|
||||
self.name = name
|
||||
|
||||
if logger == None:
|
||||
return
|
||||
|
||||
self.logger = logger
|
||||
|
||||
if not isinstance(self.plugin_type, set):
|
||||
self.plugin_type = { self.plugin_type }
|
||||
|
||||
if hasattr(self, "activate"):
|
||||
if not isinstance(self.activate, dict):
|
||||
activate = self.activate
|
||||
self.activate = dict()
|
||||
for plugin_type in self.plugin_type:
|
||||
self.activate[plugin_type] = activate
|
||||
else:
|
||||
self.activate = dict()
|
||||
for plugin_type in self.plugin_type:
|
||||
self.activate[plugin_type] = set()
|
||||
|
||||
if hasattr(self, "config"):
|
||||
config = self.config
|
||||
else:
|
||||
config = dict()
|
||||
if external_config == None:
|
||||
external_config = dict()
|
||||
for key in config.keys():
|
||||
if key in external_config:
|
||||
config[key] = external_config[key]
|
||||
|
||||
if hasattr(self, "args"):
|
||||
for arg in self.args.items():
|
||||
arg[1].bind(self, arg[0])
|
||||
flags = external_flags.get(arg[0], dict())
|
||||
arg[1].set_flags(flags.get("short"), flags.get("long"))
|
||||
value = arg[1].default()
|
||||
if value != NoDefault:
|
||||
config[arg[0]] = value
|
||||
|
||||
else:
|
||||
self.args = dict()
|
||||
|
||||
class Config: pass
|
||||
flat_config = Config()
|
||||
flat_config.__dict__ = config
|
||||
self.config = flat_config
|
||||
|
||||
def add_args(self, arg_parser):
|
||||
for arg in self.args.values():
|
||||
arg.add(arg_parser)
|
||||
|
||||
def load_args_and_activate(self, args):
|
||||
passed_args = set()
|
||||
for arg_name, arg in self.args.items():
|
||||
was_set, value = arg.extract(args)
|
||||
if was_set:
|
||||
if value != Flag:
|
||||
setattr(self.config, arg_name, value)
|
||||
passed_args.add(arg_name)
|
||||
activate = dict()
|
||||
run_init = False
|
||||
for type in self.plugin_type:
|
||||
if self.activate[type] <= passed_args:
|
||||
activate[type] = "active"
|
||||
run_init = True
|
||||
else:
|
||||
activate[type] = "inactive"
|
||||
if run_init and hasattr(self, "init"):
|
||||
self.init()
|
||||
return activate
|
||||
|
||||
@staticmethod
|
||||
def internal(name=None, logger=None, config=dict(), flags=dict()):
|
||||
return Plugin(name, importlib.import_module(f"sshare.plugins.{name}"), logger, config.get(name, dict()), flags.get(name, dict()))
|
||||
|
||||
@staticmethod
|
||||
def external(path, logger=None, config=dict(), flags=dict()):
|
||||
sys.dont_write_bytecode = True
|
||||
module_spec = importlib.util.spec_from_file_location(
|
||||
path.stem,
|
||||
path.as_posix(),
|
||||
)
|
||||
module = importlib.util.module_from_spec(module_spec)
|
||||
module_spec.loader.exec_module(module)
|
||||
sys.dont_write_bytecode = False
|
||||
return Plugin(path.stem, module, logger, config.get(path.stem, dict()), flags.get(path.stem, dict()))
|
|
@ -1,28 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
class Raw:
|
||||
def __init__(self, name, type, data):
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.data = data
|
||||
|
||||
class File:
|
||||
def __init__(self, path):
|
||||
self.path = Path(path)
|
|
@ -1,30 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
plugin_type = "logger"
|
||||
|
||||
def _print_with_color(color, message):
|
||||
print(f"\033[{color}m{message}\033[0m")
|
||||
|
||||
def info(message):
|
||||
_print_with_color(0, message)
|
||||
|
||||
def warn(message):
|
||||
_print_with_color(93, message)
|
||||
|
||||
def error(message):
|
||||
_print_with_color(91, message)
|
|
@ -1,37 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
from sshare.plugin.source import File
|
||||
from sshare.plugin.source import Raw
|
||||
|
||||
plugin_type = "name"
|
||||
|
||||
def get_name(name, source):
|
||||
if isinstance(source, File):
|
||||
if source.path.is_dir():
|
||||
return name
|
||||
else:
|
||||
start = 1
|
||||
components = source.path.name.split(".")
|
||||
if components[0] == "":
|
||||
start += 1
|
||||
if start > len(components):
|
||||
return name
|
||||
else:
|
||||
return name + "." + ".".join(components[start:])
|
||||
elif isinstance(source, Raw):
|
||||
return name + f".{source.type}"
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
from sshare.plugin.config import Argument
|
||||
from sshare.plugin.config import NoDefault
|
||||
from sshare.plugin.source import File
|
||||
|
||||
plugin_type = "source"
|
||||
|
||||
activate = { "file" }
|
||||
args = {
|
||||
"file": Argument(help="Upload a file")
|
||||
}
|
||||
|
||||
def get_source():
|
||||
file = File(config.file)
|
||||
if file.path.is_dir():
|
||||
logger.info(f"Uploading directory '{config.file}'")
|
||||
else:
|
||||
logger.info(f"Uploading file '{config.file}'")
|
||||
return file
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
|
@ -1,21 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
plugin_type = "feedback"
|
||||
|
||||
def give_feedback(location):
|
||||
logger.info(f"Uploaded to '{location}'")
|
|
@ -1,65 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
import getpass
|
||||
import subprocess
|
||||
|
||||
from sshare.plugin.config import NoDefault
|
||||
from sshare.plugin.source import File
|
||||
from sshare.plugin.source import Raw
|
||||
|
||||
plugin_type = "upload"
|
||||
|
||||
config = {
|
||||
"host": NoDefault,
|
||||
"path": NoDefault,
|
||||
"port": 22,
|
||||
"user": getpass.getuser(),
|
||||
}
|
||||
|
||||
def upload(name, source):
|
||||
logger.info(f"Uploading to {config.user}@{config.host}:{config.path}/{name} on port {config.port}")
|
||||
if isinstance(source, File):
|
||||
command = [
|
||||
"scp",
|
||||
] + ([
|
||||
"-r",
|
||||
] if source.path.is_dir() else []) + [
|
||||
"-P", f"{config.port}",
|
||||
source.path,
|
||||
f"{config.user}@{config.host}:{config.path}/{name}",
|
||||
]
|
||||
process = subprocess.run(command)
|
||||
if process.returncode != 0:
|
||||
if source.path.is_dir():
|
||||
logger.fatal("Error: failed to upload directory")
|
||||
else:
|
||||
logger.fatal("Error: failed to upload file")
|
||||
elif isinstance(source, Raw):
|
||||
command = [
|
||||
"ssh",
|
||||
f"-p {config.port}",
|
||||
f"{config.user}@{config.host}",
|
||||
"-T",
|
||||
f"cat > {config.path}/{name}"
|
||||
]
|
||||
process = subprocess.run(
|
||||
command,
|
||||
input = source.data,
|
||||
)
|
||||
if process.returncode != 0:
|
||||
logger.fatal("Error: failed to upload data")
|
|
@ -1,34 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
|
||||
from sshare.plugin.config import Flag
|
||||
from sshare.plugin.source import Raw
|
||||
|
||||
plugin_type = "source"
|
||||
|
||||
activate = { "stdin" }
|
||||
config = {
|
||||
"suffix": "txt"
|
||||
}
|
||||
args = {
|
||||
"stdin": Flag(help="Upload from stdin")
|
||||
}
|
||||
|
||||
def get_source():
|
||||
return Raw("stdin", config.suffix, sys.stdin.buffer.read())
|
|
@ -1,51 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
import time
|
||||
|
||||
from sshare.plugin.config import Argument
|
||||
from sshare.plugin.source import File
|
||||
|
||||
plugin_type = "name"
|
||||
|
||||
config = {
|
||||
"format": 62,
|
||||
}
|
||||
|
||||
def get_name(name, source):
|
||||
if name != "":
|
||||
name = name + "_"
|
||||
return name + _rebase(config.format, time.time_ns())
|
||||
|
||||
def _rebase(base, number):
|
||||
if number == 0:
|
||||
return "0"
|
||||
if base == 10:
|
||||
return f"{number}"
|
||||
rebased = ""
|
||||
while number != 0:
|
||||
rebased = _number_to_char(number % base) + rebased
|
||||
number = int(number / base)
|
||||
return rebased
|
||||
|
||||
def _number_to_char(number):
|
||||
if number < 10:
|
||||
return chr(number + 48) # 0-9
|
||||
elif number < 36:
|
||||
return chr(number + 87) # a-z
|
||||
else:
|
||||
return chr(number + 29) # A-Z
|
|
@ -1,34 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
from sshare.plugin.config import NoDefault
|
||||
|
||||
plugin_type = "location"
|
||||
|
||||
config = {
|
||||
"protocol": "https",
|
||||
"host": NoDefault,
|
||||
"port": None,
|
||||
"path": "",
|
||||
}
|
||||
|
||||
def get_location(name):
|
||||
if config.port:
|
||||
config.port = f":{config.port}"
|
||||
else:
|
||||
config.port = ""
|
||||
return f"{config.protocol}://{config.host}{config.port}{config.path}/{name}"
|
|
@ -1,135 +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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
import argparse
|
||||
|
||||
from sshare import config_directory
|
||||
from sshare.plugin import Plugin
|
||||
from sshare.plugin import PluginLoader
|
||||
from sshare.version import version
|
||||
|
||||
def validate(plugin, quiet=False):
|
||||
def log(message):
|
||||
if not quiet:
|
||||
print(message)
|
||||
|
||||
error_count = 0
|
||||
def error(message, end=False):
|
||||
nonlocal error_count
|
||||
nonlocal log
|
||||
|
||||
if error_count == 0:
|
||||
log("[\033[91mFAILED\033[0m]")
|
||||
error_count = error_count + 1
|
||||
log(f"\033[91m => {message}\033[0m")
|
||||
|
||||
def success():
|
||||
nonlocal log
|
||||
|
||||
log(f"[\033[92mOK\033[0m]")
|
||||
|
||||
mandatory_keys = dict()
|
||||
def validate_type_and_add_keys(type):
|
||||
nonlocal mandatory_keys
|
||||
|
||||
types = Plugin.types(methods=True)
|
||||
if type in types:
|
||||
mandatory_keys[type] = types[type]
|
||||
|
||||
else:
|
||||
error(f"plugin_type '{type}' is not a valid plugin type")
|
||||
if not hasattr(plugin, "plugin_type"):
|
||||
error("must define plugin_type")
|
||||
else:
|
||||
if isinstance(plugin.plugin_type, set):
|
||||
for index, type in enumerate(plugin.plugin_type):
|
||||
validate_type_and_add_keys(type)
|
||||
elif isinstance(plugin.plugin_type, str):
|
||||
validate_type_and_add_keys(plugin.plugin_type)
|
||||
else:
|
||||
error("plugin_type must be either a string or a set of strings")
|
||||
|
||||
for type, keys in mandatory_keys.items():
|
||||
for key in keys:
|
||||
if not hasattr(plugin, key):
|
||||
error(f"plugin type '{type}' requires definition of '{key}' ")
|
||||
|
||||
if hasattr(plugin, "activate"):
|
||||
def validate_activate(args, type=None):
|
||||
def validate_activate_arg(arg):
|
||||
nonlocal plugin
|
||||
nonlocal type
|
||||
|
||||
if hasattr(plugin, "args"):
|
||||
if arg in plugin.args.keys():
|
||||
return
|
||||
error(f"activate arg '{arg}' does not exist in 'args'")
|
||||
if isinstance(args, set):
|
||||
for arg in args:
|
||||
validate_activate_arg(arg)
|
||||
else:
|
||||
validate_activate_arg(arg)
|
||||
|
||||
if isinstance(plugin.activate, dict):
|
||||
for type, args in plugin.activate.items():
|
||||
if not type in Plugin.types():
|
||||
error(f"activate specified for unrecognized plugin type '{type}'")
|
||||
validate_activate(args, type)
|
||||
else:
|
||||
validate_activate(plugin.activate)
|
||||
|
||||
if error_count == 0:
|
||||
success()
|
||||
return error_count
|
||||
|
||||
def main():
|
||||
arg_parser = argparse.ArgumentParser(
|
||||
prog="sshare-validate",
|
||||
description="Validate sshare plugins",
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"-v",
|
||||
"--version",
|
||||
action="version",
|
||||
version=f"%(prog)s version {version}",
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"--dev",
|
||||
action="store_const",
|
||||
const=True,
|
||||
help="Validate all internal and external plugins"
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"plugins",
|
||||
nargs="*",
|
||||
help="plugin(s) to be validated (Default: all external plugins)",
|
||||
)
|
||||
arguments = arg_parser.parse_args()
|
||||
if arguments.dev:
|
||||
to_be_validated = PluginLoader.all(command_line=True)
|
||||
elif len(arguments.plugins) > 0:
|
||||
to_be_validated = PluginLoader.at(*arguments.plugins)
|
||||
else:
|
||||
to_be_validated = PluginLoader.external()
|
||||
|
||||
longest = 0
|
||||
for plugin in to_be_validated:
|
||||
if len(plugin.name) > longest:
|
||||
longest = len(plugin.name)
|
||||
for plugin in to_be_validated:
|
||||
print(f"{plugin.name}{"".join([ " " for _ in range(0, longest-len(plugin.name))])} ", end="")
|
||||
validate(plugin)
|
1
src/sshare/version.py
Normal file
1
src/sshare/version.py
Normal file
|
@ -0,0 +1 @@
|
|||
version = "1.0.0"
|
Loading…
Reference in a new issue