sharex-cli 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sharex_cli-0.0.1/.gitignore +19 -0
- sharex_cli-0.0.1/LICENSE +21 -0
- sharex_cli-0.0.1/PKG-INFO +176 -0
- sharex_cli-0.0.1/README.md +149 -0
- sharex_cli-0.0.1/pyproject.toml +118 -0
- sharex_cli-0.0.1/src/sharex/__init__.py +12 -0
- sharex_cli-0.0.1/src/sharex/_version.py +2 -0
- sharex_cli-0.0.1/src/sharex/api.py +149 -0
- sharex_cli-0.0.1/src/sharex/cli.py +275 -0
- sharex_cli-0.0.1/src/sharex/py.typed +0 -0
- sharex_cli-0.0.1/src/sharex/utils.py +19 -0
sharex_cli-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 CSSNR
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sharex-cli
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: CLI to Upload Files to a ShareX Server using a ShareX Custom Uploader (.sxcu) Configuration File.
|
|
5
|
+
Project-URL: Changelog, https://github.com/cssnr/sharex-cli/releases
|
|
6
|
+
Project-URL: Discord, https://discord.gg/wXy6m2X8wY
|
|
7
|
+
Project-URL: Documentation, https://cssnr.github.io/sharex-cli/
|
|
8
|
+
Project-URL: Funding, https://ko-fi.com/cssnr
|
|
9
|
+
Project-URL: GitHub, https://github.com/cssnr/sharex-cli
|
|
10
|
+
Project-URL: Homepage, https://cssnr.com/
|
|
11
|
+
Project-URL: Issues, https://github.com/cssnr/sharex-cli/issues
|
|
12
|
+
Author: Shane
|
|
13
|
+
License: MIT License
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: jsonpath-ng<2,>=1
|
|
23
|
+
Requires-Dist: pyperclip<2,>=1
|
|
24
|
+
Requires-Dist: requests<3,>=2
|
|
25
|
+
Requires-Dist: typer>=0.20.0
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
[](https://pypi.org/project/sharex-cli/)
|
|
29
|
+
[](https://github.com/cssnr/sharex-cli/releases)
|
|
30
|
+
[](https://github.com/cssnr/sharex-cli?tab=readme-ov-file#readme)
|
|
31
|
+
[](https://pypistats.org/packages/sharex-cli)
|
|
32
|
+
[](https://clickpy.clickhouse.com/dashboard/sharex-cli)
|
|
33
|
+
[](https://codecov.io/gh/cssnr/sharex-cli)
|
|
34
|
+
[](https://github.com/cssnr/sharex-cli/actions/workflows/lint.yaml)
|
|
35
|
+
[](https://github.com/cssnr/sharex-cli/actions/workflows/test.yaml)
|
|
36
|
+
[](https://pypi.org/project/sharex-cli/)
|
|
37
|
+
[](https://cssnr.github.io/sharex-cli/)
|
|
38
|
+
[](https://github.com/cssnr/sharex-cli/pulse)
|
|
39
|
+
[](https://github.com/cssnr/sharex-cli)
|
|
40
|
+
[](https://github.com/cssnr/sharex-cli?tab=readme-ov-file#readme)
|
|
41
|
+
[](https://github.com/cssnr/sharex-cli/graphs/contributors)
|
|
42
|
+
[](https://github.com/cssnr/sharex-cli/issues)
|
|
43
|
+
[](https://github.com/cssnr/sharex-cli/discussions)
|
|
44
|
+
[](https://github.com/cssnr/sharex-cli/forks)
|
|
45
|
+
[](https://github.com/cssnr/sharex-cli/stargazers)
|
|
46
|
+
[](https://cssnr.github.io/)
|
|
47
|
+
[](https://discord.gg/wXy6m2X8wY)
|
|
48
|
+
[](https://ko-fi.com/cssnr)
|
|
49
|
+
|
|
50
|
+
# ShareX CLI
|
|
51
|
+
|
|
52
|
+
<a title="ShareX CLI" href="https://cssnr.github.io/sharex-cli/" target="_blank">
|
|
53
|
+
<img alt="ShareX CLI" align="right" width="128" height="auto" src="https://cssnr.github.io/sharex-cli/assets/images/logo.svg"></a>
|
|
54
|
+
|
|
55
|
+
- [Features](#features)
|
|
56
|
+
- [Install](#install)
|
|
57
|
+
- [Setup](#setup)
|
|
58
|
+
- [Usage](#usage)
|
|
59
|
+
- [Support](#support)
|
|
60
|
+
- [Contributing](#contributing)
|
|
61
|
+
|
|
62
|
+
Command Line Interface to Upload Files to a ShareX Server using a ShareX Custom Uploader `.sxcu` configuration file.
|
|
63
|
+
|
|
64
|
+
Upload any file, multiple files, directories, use globs, create archives and much more...
|
|
65
|
+
|
|
66
|
+
To get started [Install](#install) the app and view the [Usage](#usage).
|
|
67
|
+
|
|
68
|
+
If you run into any issues or have any questions, [support](#support) is available.
|
|
69
|
+
|
|
70
|
+
[](https://cssnr.github.io/sharex-cli/)
|
|
71
|
+
|
|
72
|
+
## Features<a id="features"></a>
|
|
73
|
+
|
|
74
|
+
- Use a ShareX Custom Uploader `*.sxcu` config.
|
|
75
|
+
- Upload a file or multiple files.
|
|
76
|
+
- Specify a custom file name.
|
|
77
|
+
- Upload files in a directory with optional glob.
|
|
78
|
+
- Upload a directory as an archive.
|
|
79
|
+
- Automatically open the URL in browser.
|
|
80
|
+
- Automatically copy the URL to the clipboard.
|
|
81
|
+
- Display confirmation before uploading multiple files.
|
|
82
|
+
- Override all options with flags or env vars.
|
|
83
|
+
|
|
84
|
+
## Install<a id="install"></a>
|
|
85
|
+
|
|
86
|
+
From PyPI: <https://pypi.org/p/sharex-cli>
|
|
87
|
+
|
|
88
|
+
```shell
|
|
89
|
+
pip install sharex-cli
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
From GitHub.
|
|
93
|
+
|
|
94
|
+
```shell
|
|
95
|
+
pip install git+https://github.com/cssnr/sharex-cli.git
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
From source.
|
|
99
|
+
|
|
100
|
+
```shell
|
|
101
|
+
git clone https://github.com/cssnr/sharex-cli.git
|
|
102
|
+
pip install sharex-cli
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
[](https://cssnr.github.io/sharex-cli/cli/#install)
|
|
106
|
+
|
|
107
|
+
## Setup<a id="setup"></a>
|
|
108
|
+
|
|
109
|
+
To configure you need your server's ShareX Custom Uploader `*.sxcu` configuration JSON.
|
|
110
|
+
|
|
111
|
+
The app will automatically enter configuration on the first run.
|
|
112
|
+
|
|
113
|
+
```shell
|
|
114
|
+
sharex
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This allows you to enter a file path, or open a text editor to enter the contents.
|
|
118
|
+
|
|
119
|
+
You can reconfigure with `--config` and pass a path to the config file.
|
|
120
|
+
|
|
121
|
+
```shell
|
|
122
|
+
sharex --config path/to/config.sxcu
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
[](https://cssnr.github.io/sharex-cli/cli/#setup)
|
|
126
|
+
|
|
127
|
+
## Usage<a id="usage"></a>
|
|
128
|
+
|
|
129
|
+
Once the configuration is saved you can upload a file, or multiple.
|
|
130
|
+
|
|
131
|
+
```shell
|
|
132
|
+
sharex file1.txt /path/to/file2.txt
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
You can upload the contents of a directory, default glob is `*`.
|
|
136
|
+
|
|
137
|
+
```shell
|
|
138
|
+
sharex dir1
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Or create an archive of the directory (no glob support).
|
|
142
|
+
|
|
143
|
+
```shell
|
|
144
|
+
sharex /path/to/dir2 -a
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
[](https://cssnr.github.io/sharex-cli/cli/#uploading)
|
|
148
|
+
|
|
149
|
+
## Support<a id="support"></a>
|
|
150
|
+
|
|
151
|
+
For general help or to request a feature, see:
|
|
152
|
+
|
|
153
|
+
- Q&A Discussion: <https://github.com/cssnr/sharex-cli/discussions/categories/q-a>
|
|
154
|
+
- Request a Feature: <https://github.com/cssnr/sharex-cli/issues/new?template=1-feature.yaml>
|
|
155
|
+
- Chat with us on Discord: <https://discord.gg/wXy6m2X8wY>
|
|
156
|
+
|
|
157
|
+
If you are experiencing an issue/bug or getting unexpected results, you can:
|
|
158
|
+
|
|
159
|
+
- Report an Issue: <https://github.com/cssnr/sharex-cli/issues>
|
|
160
|
+
- Chat with us on Discord: <https://discord.gg/wXy6m2X8wY>
|
|
161
|
+
|
|
162
|
+
[](https://github.com/cssnr/sharex-cli/issues/new?template=1-feature.yaml)
|
|
163
|
+
[](https://github.com/cssnr/sharex-cli/issues)
|
|
164
|
+
[](https://github.com/cssnr/sharex-cli/discussions)
|
|
165
|
+
[](https://discord.gg/wXy6m2X8wY)
|
|
166
|
+
|
|
167
|
+
## Contributing<a id="contributing"></a>
|
|
168
|
+
|
|
169
|
+
If you would like to submit a PR, please review the [CONTRIBUTING.md](#contributing-ov-file).
|
|
170
|
+
|
|
171
|
+
Please consider making a donation to support the development of this project
|
|
172
|
+
and [additional](https://cssnr.com/) open source projects.
|
|
173
|
+
|
|
174
|
+
[](https://ko-fi.com/cssnr)
|
|
175
|
+
|
|
176
|
+
For a full list of current projects visit: [https://cssnr.github.io/](https://cssnr.github.io/)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
[](https://pypi.org/project/sharex-cli/)
|
|
2
|
+
[](https://github.com/cssnr/sharex-cli/releases)
|
|
3
|
+
[](https://github.com/cssnr/sharex-cli?tab=readme-ov-file#readme)
|
|
4
|
+
[](https://pypistats.org/packages/sharex-cli)
|
|
5
|
+
[](https://clickpy.clickhouse.com/dashboard/sharex-cli)
|
|
6
|
+
[](https://codecov.io/gh/cssnr/sharex-cli)
|
|
7
|
+
[](https://github.com/cssnr/sharex-cli/actions/workflows/lint.yaml)
|
|
8
|
+
[](https://github.com/cssnr/sharex-cli/actions/workflows/test.yaml)
|
|
9
|
+
[](https://pypi.org/project/sharex-cli/)
|
|
10
|
+
[](https://cssnr.github.io/sharex-cli/)
|
|
11
|
+
[](https://github.com/cssnr/sharex-cli/pulse)
|
|
12
|
+
[](https://github.com/cssnr/sharex-cli)
|
|
13
|
+
[](https://github.com/cssnr/sharex-cli?tab=readme-ov-file#readme)
|
|
14
|
+
[](https://github.com/cssnr/sharex-cli/graphs/contributors)
|
|
15
|
+
[](https://github.com/cssnr/sharex-cli/issues)
|
|
16
|
+
[](https://github.com/cssnr/sharex-cli/discussions)
|
|
17
|
+
[](https://github.com/cssnr/sharex-cli/forks)
|
|
18
|
+
[](https://github.com/cssnr/sharex-cli/stargazers)
|
|
19
|
+
[](https://cssnr.github.io/)
|
|
20
|
+
[](https://discord.gg/wXy6m2X8wY)
|
|
21
|
+
[](https://ko-fi.com/cssnr)
|
|
22
|
+
|
|
23
|
+
# ShareX CLI
|
|
24
|
+
|
|
25
|
+
<a title="ShareX CLI" href="https://cssnr.github.io/sharex-cli/" target="_blank">
|
|
26
|
+
<img alt="ShareX CLI" align="right" width="128" height="auto" src="https://cssnr.github.io/sharex-cli/assets/images/logo.svg"></a>
|
|
27
|
+
|
|
28
|
+
- [Features](#features)
|
|
29
|
+
- [Install](#install)
|
|
30
|
+
- [Setup](#setup)
|
|
31
|
+
- [Usage](#usage)
|
|
32
|
+
- [Support](#support)
|
|
33
|
+
- [Contributing](#contributing)
|
|
34
|
+
|
|
35
|
+
Command Line Interface to Upload Files to a ShareX Server using a ShareX Custom Uploader `.sxcu` configuration file.
|
|
36
|
+
|
|
37
|
+
Upload any file, multiple files, directories, use globs, create archives and much more...
|
|
38
|
+
|
|
39
|
+
To get started [Install](#install) the app and view the [Usage](#usage).
|
|
40
|
+
|
|
41
|
+
If you run into any issues or have any questions, [support](#support) is available.
|
|
42
|
+
|
|
43
|
+
[](https://cssnr.github.io/sharex-cli/)
|
|
44
|
+
|
|
45
|
+
## Features<a id="features"></a>
|
|
46
|
+
|
|
47
|
+
- Use a ShareX Custom Uploader `*.sxcu` config.
|
|
48
|
+
- Upload a file or multiple files.
|
|
49
|
+
- Specify a custom file name.
|
|
50
|
+
- Upload files in a directory with optional glob.
|
|
51
|
+
- Upload a directory as an archive.
|
|
52
|
+
- Automatically open the URL in browser.
|
|
53
|
+
- Automatically copy the URL to the clipboard.
|
|
54
|
+
- Display confirmation before uploading multiple files.
|
|
55
|
+
- Override all options with flags or env vars.
|
|
56
|
+
|
|
57
|
+
## Install<a id="install"></a>
|
|
58
|
+
|
|
59
|
+
From PyPI: <https://pypi.org/p/sharex-cli>
|
|
60
|
+
|
|
61
|
+
```shell
|
|
62
|
+
pip install sharex-cli
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
From GitHub.
|
|
66
|
+
|
|
67
|
+
```shell
|
|
68
|
+
pip install git+https://github.com/cssnr/sharex-cli.git
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
From source.
|
|
72
|
+
|
|
73
|
+
```shell
|
|
74
|
+
git clone https://github.com/cssnr/sharex-cli.git
|
|
75
|
+
pip install sharex-cli
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
[](https://cssnr.github.io/sharex-cli/cli/#install)
|
|
79
|
+
|
|
80
|
+
## Setup<a id="setup"></a>
|
|
81
|
+
|
|
82
|
+
To configure you need your server's ShareX Custom Uploader `*.sxcu` configuration JSON.
|
|
83
|
+
|
|
84
|
+
The app will automatically enter configuration on the first run.
|
|
85
|
+
|
|
86
|
+
```shell
|
|
87
|
+
sharex
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This allows you to enter a file path, or open a text editor to enter the contents.
|
|
91
|
+
|
|
92
|
+
You can reconfigure with `--config` and pass a path to the config file.
|
|
93
|
+
|
|
94
|
+
```shell
|
|
95
|
+
sharex --config path/to/config.sxcu
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
[](https://cssnr.github.io/sharex-cli/cli/#setup)
|
|
99
|
+
|
|
100
|
+
## Usage<a id="usage"></a>
|
|
101
|
+
|
|
102
|
+
Once the configuration is saved you can upload a file, or multiple.
|
|
103
|
+
|
|
104
|
+
```shell
|
|
105
|
+
sharex file1.txt /path/to/file2.txt
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
You can upload the contents of a directory, default glob is `*`.
|
|
109
|
+
|
|
110
|
+
```shell
|
|
111
|
+
sharex dir1
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Or create an archive of the directory (no glob support).
|
|
115
|
+
|
|
116
|
+
```shell
|
|
117
|
+
sharex /path/to/dir2 -a
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
[](https://cssnr.github.io/sharex-cli/cli/#uploading)
|
|
121
|
+
|
|
122
|
+
## Support<a id="support"></a>
|
|
123
|
+
|
|
124
|
+
For general help or to request a feature, see:
|
|
125
|
+
|
|
126
|
+
- Q&A Discussion: <https://github.com/cssnr/sharex-cli/discussions/categories/q-a>
|
|
127
|
+
- Request a Feature: <https://github.com/cssnr/sharex-cli/issues/new?template=1-feature.yaml>
|
|
128
|
+
- Chat with us on Discord: <https://discord.gg/wXy6m2X8wY>
|
|
129
|
+
|
|
130
|
+
If you are experiencing an issue/bug or getting unexpected results, you can:
|
|
131
|
+
|
|
132
|
+
- Report an Issue: <https://github.com/cssnr/sharex-cli/issues>
|
|
133
|
+
- Chat with us on Discord: <https://discord.gg/wXy6m2X8wY>
|
|
134
|
+
|
|
135
|
+
[](https://github.com/cssnr/sharex-cli/issues/new?template=1-feature.yaml)
|
|
136
|
+
[](https://github.com/cssnr/sharex-cli/issues)
|
|
137
|
+
[](https://github.com/cssnr/sharex-cli/discussions)
|
|
138
|
+
[](https://discord.gg/wXy6m2X8wY)
|
|
139
|
+
|
|
140
|
+
## Contributing<a id="contributing"></a>
|
|
141
|
+
|
|
142
|
+
If you would like to submit a PR, please review the [CONTRIBUTING.md](#contributing-ov-file).
|
|
143
|
+
|
|
144
|
+
Please consider making a donation to support the development of this project
|
|
145
|
+
and [additional](https://cssnr.com/) open source projects.
|
|
146
|
+
|
|
147
|
+
[](https://ko-fi.com/cssnr)
|
|
148
|
+
|
|
149
|
+
For a full list of current projects visit: [https://cssnr.github.io/](https://cssnr.github.io/)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sharex-cli"
|
|
3
|
+
description = "CLI to Upload Files to a ShareX Server using a ShareX Custom Uploader (.sxcu) Configuration File."
|
|
4
|
+
readme = "README.md"
|
|
5
|
+
requires-python = ">=3.10"
|
|
6
|
+
license = { text = "MIT License" }
|
|
7
|
+
authors = [{ name = "Shane" }]
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Natural Language :: English",
|
|
10
|
+
"Operating System :: OS Independent",
|
|
11
|
+
"Programming Language :: Python",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
14
|
+
"Typing :: Typed",
|
|
15
|
+
]
|
|
16
|
+
dependencies = [
|
|
17
|
+
"jsonpath-ng>=1,<2",
|
|
18
|
+
"pyperclip>=1,<2",
|
|
19
|
+
"requests>=2,<3",
|
|
20
|
+
"typer>=0.20.0",
|
|
21
|
+
]
|
|
22
|
+
dynamic = ["version"]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Changelog = "https://github.com/cssnr/sharex-cli/releases"
|
|
26
|
+
Discord = "https://discord.gg/wXy6m2X8wY"
|
|
27
|
+
Documentation = "https://cssnr.github.io/sharex-cli/"
|
|
28
|
+
Funding = "https://ko-fi.com/cssnr"
|
|
29
|
+
GitHub = "https://github.com/cssnr/sharex-cli"
|
|
30
|
+
Homepage = "https://cssnr.com/"
|
|
31
|
+
Issues = "https://github.com/cssnr/sharex-cli/issues"
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
sharex = "sharex.cli:app"
|
|
35
|
+
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = [
|
|
38
|
+
"bandit>=1.9.2",
|
|
39
|
+
"black>=25.12.0",
|
|
40
|
+
"coverage>=7.13.0",
|
|
41
|
+
"hatch>=1.16.2",
|
|
42
|
+
"isort>=7.0.0",
|
|
43
|
+
"mypy>=1.19.1",
|
|
44
|
+
"pytest>=9.0.2",
|
|
45
|
+
"ruff>=0.14.9",
|
|
46
|
+
"tombi>=0.7.7",
|
|
47
|
+
"ty>=0.0.2",
|
|
48
|
+
"typer>=0.20.0",
|
|
49
|
+
"types-pyperclip>=1.9.0.20250801",
|
|
50
|
+
"types-requests>=2.32.4.20250913",
|
|
51
|
+
"validate-pyproject[all]>=0.24.1",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# [build-system]
|
|
55
|
+
# requires = ["uv_build>=0.9.9,<0.10.0"]
|
|
56
|
+
# build-backend = "uv_build"
|
|
57
|
+
|
|
58
|
+
# https://hatch.pypa.io/latest/config/build/#build-system
|
|
59
|
+
[build-system]
|
|
60
|
+
requires = ["hatchling"]
|
|
61
|
+
build-backend = "hatchling.build"
|
|
62
|
+
|
|
63
|
+
# Bandit
|
|
64
|
+
# https://bandit.readthedocs.io/en/latest/config.html
|
|
65
|
+
[tool.bandit]
|
|
66
|
+
exclude_dirs = [".venv"]
|
|
67
|
+
skips = ["B105", "B107"]
|
|
68
|
+
|
|
69
|
+
# Black
|
|
70
|
+
# https://black.readthedocs.io/en/stable/usage_and_configuration/
|
|
71
|
+
[tool.black]
|
|
72
|
+
line-length = 119
|
|
73
|
+
extend-exclude = '(\.github)'
|
|
74
|
+
|
|
75
|
+
# Coverage
|
|
76
|
+
# https://coverage.readthedocs.io/en/latest/
|
|
77
|
+
[tool.coverage.run]
|
|
78
|
+
omit = ["_version.py", "utils.py", "cli.py"]
|
|
79
|
+
source = ["src"]
|
|
80
|
+
|
|
81
|
+
# Hatchling
|
|
82
|
+
# https://hatch.pypa.io/latest/config/build/#build-system
|
|
83
|
+
[tool.hatch.version]
|
|
84
|
+
path = "src/sharex/_version.py"
|
|
85
|
+
|
|
86
|
+
[tool.hatch.build.targets.sdist]
|
|
87
|
+
include = ["src", "CONTRIBUTING.md"]
|
|
88
|
+
|
|
89
|
+
[tool.hatch.build.targets.wheel]
|
|
90
|
+
packages = ["src/sharex"]
|
|
91
|
+
|
|
92
|
+
# Isort
|
|
93
|
+
# https://pycqa.github.io/isort/docs/configuration/options.html
|
|
94
|
+
[tool.isort]
|
|
95
|
+
profile = "black"
|
|
96
|
+
lines_after_imports = 2
|
|
97
|
+
src_paths = ["src", "test"]
|
|
98
|
+
skip = [".venv", ".github"]
|
|
99
|
+
|
|
100
|
+
# Mypy
|
|
101
|
+
# https://mypy.readthedocs.io/en/stable/config_file.html
|
|
102
|
+
[tool.mypy]
|
|
103
|
+
# ignore_missing_imports = true
|
|
104
|
+
exclude = [".venv"]
|
|
105
|
+
|
|
106
|
+
[[tool.mypy.overrides]]
|
|
107
|
+
module = ["jsonpath_ng.*"]
|
|
108
|
+
follow_untyped_imports = true
|
|
109
|
+
|
|
110
|
+
# Ruff
|
|
111
|
+
# https://docs.astral.sh/ruff/configuration/
|
|
112
|
+
[tool.ruff]
|
|
113
|
+
line-length = 119
|
|
114
|
+
target-version = "py313"
|
|
115
|
+
extend-exclude = [".github"]
|
|
116
|
+
|
|
117
|
+
[tool.ruff.lint]
|
|
118
|
+
select = ["E4", "E7", "E9", "F", "B", "Q"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
[red bold] ____ [yellow] _ [cyan]__ __ [green] ____ _ ___
|
|
3
|
+
[red bold] / ___|[yellow]| |__ __ _ _ __ ___[cyan]\ \/ / [green] / ___| | |_ _|
|
|
4
|
+
[red bold] \___ \\[yellow]| '_ \ / _` | '__/ _ \\[cyan]\ / [green]| | | | | |
|
|
5
|
+
[red bold] ___) [yellow]| | | | (_| | | | __/[cyan]/ \ [green]| |___| |___ | |
|
|
6
|
+
[red bold] |____/[yellow]|_| |_|\__,_|_| \___[cyan]/_/\_\ [green] \____|_____|___|
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .api import get_config, upload_file
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__all__ = ["get_config", "upload_file"]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import mimetypes
|
|
3
|
+
import os
|
|
4
|
+
import warnings
|
|
5
|
+
from json import JSONDecodeError
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import IO, Optional
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
from jsonpath_ng.ext import parse
|
|
11
|
+
|
|
12
|
+
from ._version import __version__
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
session = requests.Session()
|
|
16
|
+
|
|
17
|
+
session.headers.update({"user-agent": f"requests/sharex-{__version__}"})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upload_file(config, file_name: str, file_data: IO) -> str:
|
|
21
|
+
"""Upload a File from an SXCU Config"""
|
|
22
|
+
url = config["RequestURL"]
|
|
23
|
+
# print(f"{url=}")
|
|
24
|
+
method = config["RequestMethod"]
|
|
25
|
+
name = config["FileFormName"]
|
|
26
|
+
headers = config["Headers"]
|
|
27
|
+
key = config["URL"]
|
|
28
|
+
|
|
29
|
+
path = Path(file_name)
|
|
30
|
+
mime_type = get_type(path)
|
|
31
|
+
# print(f"{mime_type=}")
|
|
32
|
+
|
|
33
|
+
files = {name: (file_name, file_data, mime_type)}
|
|
34
|
+
r = session.request(method, url, headers=headers, files=files) # nosec
|
|
35
|
+
if not r.ok: # pragma: no cover
|
|
36
|
+
print(f"{r.status_code=}")
|
|
37
|
+
print(f"{r.text=}")
|
|
38
|
+
r.raise_for_status()
|
|
39
|
+
|
|
40
|
+
data = r.json()
|
|
41
|
+
# print(f"{data=}")
|
|
42
|
+
|
|
43
|
+
key_path = key.strip("{}").split(":")[1]
|
|
44
|
+
# print(f"{key_path=}")
|
|
45
|
+
|
|
46
|
+
jsonpath_expr = parse(key_path)
|
|
47
|
+
match = [match.value for match in jsonpath_expr.find(data)]
|
|
48
|
+
# print(f"{match=}")
|
|
49
|
+
return match[0]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_config(config_path: Optional[Path] = None, env_var: str = "", create_empty: bool = True):
|
|
53
|
+
"""Parse a JSON config from Env or Path or return None"""
|
|
54
|
+
if env_var:
|
|
55
|
+
var = os.environ.get(env_var, None)
|
|
56
|
+
if var:
|
|
57
|
+
try:
|
|
58
|
+
return json.loads(var)
|
|
59
|
+
except JSONDecodeError:
|
|
60
|
+
pass
|
|
61
|
+
if not config_path:
|
|
62
|
+
return None
|
|
63
|
+
if not config_path.is_file():
|
|
64
|
+
if create_empty:
|
|
65
|
+
config_path.touch()
|
|
66
|
+
return None
|
|
67
|
+
try:
|
|
68
|
+
with open(config_path, "r") as f:
|
|
69
|
+
return json.loads(f.read())
|
|
70
|
+
except JSONDecodeError:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_type(file_path: Path) -> str: # NOSONAR
|
|
75
|
+
"""
|
|
76
|
+
Get MIME type from guess_type or by reading magic headers
|
|
77
|
+
https://en.wikipedia.org/wiki/List_of_file_signatures
|
|
78
|
+
|
|
79
|
+
Deprecated since version 3.13: Passing a file path instead of URL is soft deprecated. Use guess_file_type() for this.
|
|
80
|
+
https://docs.python.org/3/library/mimetypes.html#mimetypes.guess_type
|
|
81
|
+
"""
|
|
82
|
+
mime_type, _ = mimetypes.guess_type(file_path, strict=False)
|
|
83
|
+
if mime_type:
|
|
84
|
+
return mime_type
|
|
85
|
+
|
|
86
|
+
if not file_path.is_file():
|
|
87
|
+
return "text/plain"
|
|
88
|
+
|
|
89
|
+
with open(file_path, "rb") as file:
|
|
90
|
+
chunk = file.read(512)
|
|
91
|
+
|
|
92
|
+
# print(f"chunk: {type(chunk)} - {chunk[:20]}")
|
|
93
|
+
# print(f"test chunk: {chunk[8:11]}")
|
|
94
|
+
|
|
95
|
+
if isinstance(chunk, str): # pragma: no cover
|
|
96
|
+
warnings.warn("This condition should not be True...", stacklevel=2)
|
|
97
|
+
return "text/plain"
|
|
98
|
+
|
|
99
|
+
# Images
|
|
100
|
+
if chunk[0:3] == b"\xff\xd8\xff" or chunk[6:10] in (b"JFIF", b"Exif"):
|
|
101
|
+
return "image/jpeg"
|
|
102
|
+
elif chunk.startswith(b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"):
|
|
103
|
+
return "image/png"
|
|
104
|
+
elif chunk.startswith(b"RIFF") and chunk[8:12] == b"WEBP":
|
|
105
|
+
return "image/webp"
|
|
106
|
+
elif chunk.startswith((b"\x47\x49\x46\x38\x37\x61", b"\x47\x49\x46\x38\x39\x61")):
|
|
107
|
+
return "image/gif"
|
|
108
|
+
elif chunk.startswith(b"\x66\x74\x79\x70\x68\x65\x69\x63\x66\x74\79\70\6d") or chunk[4:12] == b"ftypheic":
|
|
109
|
+
return "image/heic"
|
|
110
|
+
elif chunk.startswith(b"\x00\x00\x01\x00"):
|
|
111
|
+
return "image/ico"
|
|
112
|
+
elif chunk.startswith(b"II*") or chunk.startswith(b"II+") or chunk.startswith(b"MM"):
|
|
113
|
+
return "image/tiff"
|
|
114
|
+
elif chunk.startswith(b"BM"):
|
|
115
|
+
return "image/bmp"
|
|
116
|
+
elif chunk[4:12] == b"ftypavif":
|
|
117
|
+
return "image/avif"
|
|
118
|
+
|
|
119
|
+
# Video
|
|
120
|
+
elif chunk[4:12] == b"ftypisom" or chunk[4:12] == b"ftypMSNV" or chunk[4:12] == b"ftypmp42":
|
|
121
|
+
return "video/mp4"
|
|
122
|
+
elif chunk[3:11] in (b"\x66\x74\x79\x70\x4d\x53\x4e\x56", b"\x66\x74\x79\x70\x69\x73\x6f\x6d"):
|
|
123
|
+
return "video/mp4"
|
|
124
|
+
elif chunk.startswith(b"\x1a\x45\xdf\xa3"):
|
|
125
|
+
# https://www.loc.gov/preservation/digital/formats//fdd/fdd000342.shtml
|
|
126
|
+
return "video/x-matroska"
|
|
127
|
+
elif chunk.startswith(b"RIFF") and chunk[8:11] == b"AVI":
|
|
128
|
+
return "video/x-msvideo"
|
|
129
|
+
elif chunk.startswith(b"\x30\x26\xb2\x75\x8e\x66\xcf\x11\xa6\xd9\x00\xaa\x00\x62\xce\x6c"):
|
|
130
|
+
# https://www.loc.gov/preservation/digital/formats/fdd/fdd000091.shtml
|
|
131
|
+
return "video/x-ms-asf"
|
|
132
|
+
elif chunk.startswith(b"\x6d\x6f\x6f\x76") or chunk[4:10] == b"ftypqt":
|
|
133
|
+
# https://www.file-recovery.com/mov-signature-format.htm
|
|
134
|
+
return "video/quicktime"
|
|
135
|
+
|
|
136
|
+
# Audio
|
|
137
|
+
elif chunk.startswith((b"\xff\xfb", b"\xff\xfb", b"\xff\xfb", b"\x49\x44\x33")):
|
|
138
|
+
return "audio/mp3"
|
|
139
|
+
elif chunk.startswith(b"RIFF") and chunk[8:12] == b"WAVE":
|
|
140
|
+
return "audio/wav"
|
|
141
|
+
elif chunk.startswith(b"OggS"):
|
|
142
|
+
return "application/ogg"
|
|
143
|
+
|
|
144
|
+
# Fallback
|
|
145
|
+
try:
|
|
146
|
+
chunk.decode("utf-8")
|
|
147
|
+
return "text/plain"
|
|
148
|
+
except UnicodeDecodeError:
|
|
149
|
+
return "application/octet-stream"
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import sys
|
|
5
|
+
import tempfile
|
|
6
|
+
from json import JSONDecodeError
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
import pyperclip
|
|
12
|
+
import typer
|
|
13
|
+
from rich import print
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.table import Table
|
|
17
|
+
from typing_extensions import Annotated
|
|
18
|
+
|
|
19
|
+
from . import __doc__ as package_doc
|
|
20
|
+
from . import api, utils
|
|
21
|
+
from ._version import __version__
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
app = typer.Typer(
|
|
25
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
26
|
+
pretty_exceptions_show_locals=False,
|
|
27
|
+
add_completion=False,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
console = Console(stderr=True)
|
|
31
|
+
|
|
32
|
+
state = {"verbose": False, "copy": True}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def vprint(*objects: Any, sep="\n", **kwargs):
|
|
36
|
+
if state.get("verbose"):
|
|
37
|
+
print(*objects, file=sys.stderr, sep=sep, **kwargs)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def copy_text(text: str):
|
|
41
|
+
if state.get("copy"):
|
|
42
|
+
try:
|
|
43
|
+
pyperclip.copy(text)
|
|
44
|
+
except pyperclip.PyperclipException as error:
|
|
45
|
+
vprint(f"[yellow bold]Clipboard Warning[/yellow bold]: {error}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_config_path(name: str = "sharex-cli", create: bool = False) -> Path:
|
|
49
|
+
app_path = Path(typer.get_app_dir(name))
|
|
50
|
+
if not app_path.is_dir():
|
|
51
|
+
app_path.mkdir(parents=True)
|
|
52
|
+
config_path = app_path / "config.json"
|
|
53
|
+
if create:
|
|
54
|
+
config_path.touch()
|
|
55
|
+
return config_path
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def update_config(config_path: Path, config_files: List[Path]):
|
|
59
|
+
console.rule("[bold green]ShareX CLI Configuration", style="cyan bold")
|
|
60
|
+
vprint(f"{config_path=}", f"{config_files=}")
|
|
61
|
+
user_config = None
|
|
62
|
+
|
|
63
|
+
if config_files and config_files[0].is_file():
|
|
64
|
+
vprint(f"{config_files[0]=}")
|
|
65
|
+
user_config = json.loads(config_files[0].read_text())
|
|
66
|
+
vprint(f"1 {user_config=}")
|
|
67
|
+
|
|
68
|
+
if not user_config:
|
|
69
|
+
print(
|
|
70
|
+
"\nDownload your servers ShareX Custom Uploader (*.sxcu) configuration, then:\n\n"
|
|
71
|
+
" - Type or Paste the config [cyan bold]file path[/cyan bold].\n"
|
|
72
|
+
" - Press [green bold]Enter[/green bold] to open a [cyan bold]text editor[/cyan bold].\n"
|
|
73
|
+
" - Or press [yellow bold]Ctrl+C[/yellow bold] to abort.\n"
|
|
74
|
+
)
|
|
75
|
+
input_file = Path(typer.prompt("File Path", default="", show_default=False))
|
|
76
|
+
vprint(f"{input_file=}")
|
|
77
|
+
if input_file.is_file():
|
|
78
|
+
user_config = json.loads(input_file.read_text())
|
|
79
|
+
vprint(f"2 {user_config=}")
|
|
80
|
+
|
|
81
|
+
if not user_config:
|
|
82
|
+
res: Optional[str] = click.edit()
|
|
83
|
+
vprint(f"{res=}")
|
|
84
|
+
if res:
|
|
85
|
+
user_config = json.loads(res)
|
|
86
|
+
vprint(f"3 {user_config=}")
|
|
87
|
+
|
|
88
|
+
if user_config and isinstance(user_config, dict):
|
|
89
|
+
config_path.write_text(json.dumps(user_config))
|
|
90
|
+
print(f"\n[green bold]Config Saved/Updated[/green bold]: [cyan bold]{config_path.absolute()}\n")
|
|
91
|
+
else:
|
|
92
|
+
raise click.ClickException("Error Saving Config")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def archive_dir(config, params: Dict[str, Any], dir_path: Path):
|
|
96
|
+
vprint(f"{params}", f"{dir_path=}", f"{os.getcwd()=}")
|
|
97
|
+
file_name = dir_path.name
|
|
98
|
+
vprint(f"{file_name=}")
|
|
99
|
+
if _name := params.get("_name"):
|
|
100
|
+
file_name = _name
|
|
101
|
+
if file_name.lower().endswith(".zip"):
|
|
102
|
+
file_name = file_name[:-4]
|
|
103
|
+
vprint(f"{file_name=}")
|
|
104
|
+
table = Table(title="Archive Details")
|
|
105
|
+
table.add_column("File Name", style="magenta bold", no_wrap=True)
|
|
106
|
+
table.add_column("Path", style="cyan bold")
|
|
107
|
+
table.add_row(file_name + ".zip", dir_path.absolute().as_posix())
|
|
108
|
+
console.print(table)
|
|
109
|
+
if not params.get("_yes"):
|
|
110
|
+
typer.confirm("Create and Upload Archive?", abort=True)
|
|
111
|
+
|
|
112
|
+
with tempfile.TemporaryDirectory() as d:
|
|
113
|
+
vprint(f"{d=}")
|
|
114
|
+
path = Path(d)
|
|
115
|
+
archive = path / file_name
|
|
116
|
+
vprint(f"{archive.absolute()=}")
|
|
117
|
+
with console.status(f"Creating Archive: [cyan bold]{archive.absolute()}"):
|
|
118
|
+
name = shutil.make_archive(archive.absolute().as_posix(), "zip", dir_path)
|
|
119
|
+
vprint(f"{name=}")
|
|
120
|
+
final_path = path / name
|
|
121
|
+
vprint(f"{final_path=}", f"{final_path.name=}")
|
|
122
|
+
size = utils.fmt_bytes(final_path.stat().st_size)
|
|
123
|
+
with console.status(
|
|
124
|
+
f"Uploading File: [green bold]{click.format_filename(final_path.name)}[/green bold] - [yellow bold]{size}"
|
|
125
|
+
):
|
|
126
|
+
with open(final_path, "rb") as f:
|
|
127
|
+
url = api.upload_file(config, final_path.name, f)
|
|
128
|
+
print(url)
|
|
129
|
+
copy_text(url)
|
|
130
|
+
raise typer.Exit()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def version_callback(value: bool):
|
|
134
|
+
if value:
|
|
135
|
+
if package_doc:
|
|
136
|
+
print(package_doc.lstrip("\n"), file=sys.stderr)
|
|
137
|
+
print(f"[white bold]{__version__}")
|
|
138
|
+
raise typer.Exit()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@app.command()
|
|
142
|
+
def main( # NOSONAR
|
|
143
|
+
ctx: typer.Context,
|
|
144
|
+
files: Annotated[Optional[List[Path]], typer.Argument(help="Files or Directory...", exists=True)] = None,
|
|
145
|
+
_name: Annotated[
|
|
146
|
+
Optional[str], typer.Option("-n", "--name", metavar="", help="File Name (sent with upload).")
|
|
147
|
+
] = None,
|
|
148
|
+
_glob: Annotated[
|
|
149
|
+
str, typer.Option("-g", "--glob", metavar="", help="Directory Files Glob (use ** to recurse).")
|
|
150
|
+
] = "*",
|
|
151
|
+
_archive: Annotated[
|
|
152
|
+
bool, typer.Option("-a", "--archive", help="Directory Create Archive (no glob support).")
|
|
153
|
+
] = False,
|
|
154
|
+
_yes: Annotated[
|
|
155
|
+
bool, typer.Option("-y", "--yes", metavar="", help="Answer YES to Prompts.", envvar="SHAREX_YES")
|
|
156
|
+
] = False,
|
|
157
|
+
_copy: Annotated[
|
|
158
|
+
bool, typer.Option("--copy/--no-copy", "-c/-nc", help="Copy URL to Clipboard.", envvar="SHAREX_COPY")
|
|
159
|
+
] = True,
|
|
160
|
+
_launch: Annotated[
|
|
161
|
+
bool,
|
|
162
|
+
typer.Option("--launch/--no-launch", "-l/-nl", help="Launch URL in Browser.", envvar="SHAREX_LAUNCH"),
|
|
163
|
+
] = True,
|
|
164
|
+
_verbose: Annotated[
|
|
165
|
+
bool, typer.Option("-v", "--verbose", help="Verbose Output.", envvar="SHAREX_VERBOSE")
|
|
166
|
+
] = False,
|
|
167
|
+
_config: Annotated[bool, typer.Option("-C", "--config", help="Update Configuration.")] = False,
|
|
168
|
+
_version: Annotated[
|
|
169
|
+
Optional[bool],
|
|
170
|
+
typer.Option("-V", "--version", callback=version_callback, help="Show Installed Version."),
|
|
171
|
+
] = None,
|
|
172
|
+
):
|
|
173
|
+
"""
|
|
174
|
+
Files: A File, Multiple Files or a Directory.
|
|
175
|
+
|
|
176
|
+
Docs: https://cssnr.github.io/sharex-cli/
|
|
177
|
+
"""
|
|
178
|
+
# vprint(f"{dir(ctx)=}")
|
|
179
|
+
vprint(f"{ctx.params=}")
|
|
180
|
+
state["verbose"], state["copy"] = _verbose, _copy
|
|
181
|
+
# state.update({"verbose": _verbose, "copy": _copy})
|
|
182
|
+
vprint(f"{state=}")
|
|
183
|
+
|
|
184
|
+
_files = files or []
|
|
185
|
+
vprint(f"{_files=}")
|
|
186
|
+
show_confirm = len(_files) > 1
|
|
187
|
+
vprint(f"{show_confirm=}")
|
|
188
|
+
|
|
189
|
+
config_path = get_config_path()
|
|
190
|
+
vprint(f"config_path: [cyan bold]{config_path.resolve()}")
|
|
191
|
+
|
|
192
|
+
config = api.get_config(config_path, "SHAREX_CONFIG")
|
|
193
|
+
vprint(f"config loaded: {bool(config)}")
|
|
194
|
+
|
|
195
|
+
vprint(f"{_config=}")
|
|
196
|
+
if not config or _config:
|
|
197
|
+
# Process Configuration
|
|
198
|
+
try:
|
|
199
|
+
update_config(config_path, _files)
|
|
200
|
+
raise typer.Exit()
|
|
201
|
+
except JSONDecodeError as error:
|
|
202
|
+
vprint(f"{error=}")
|
|
203
|
+
raise click.ClickException(f"Error Decoding Config: {error.msg}") # noqa: B904
|
|
204
|
+
|
|
205
|
+
tty = sys.stdin.isatty()
|
|
206
|
+
vprint(f"{tty=}")
|
|
207
|
+
|
|
208
|
+
if not tty or not _files:
|
|
209
|
+
# Process STDIN
|
|
210
|
+
file_name = _name or f"{utils.gen_rand(8)}.txt"
|
|
211
|
+
vprint(f"{file_name=}")
|
|
212
|
+
content = click.get_text_stream("stdin")
|
|
213
|
+
url = api.upload_file(config, file_name, content)
|
|
214
|
+
print(url)
|
|
215
|
+
copy_text(url)
|
|
216
|
+
if _launch:
|
|
217
|
+
typer.launch(url)
|
|
218
|
+
raise typer.Exit()
|
|
219
|
+
|
|
220
|
+
if len(_files) == 1 and _files[0].is_dir():
|
|
221
|
+
# Process Directory
|
|
222
|
+
vprint(f"{_archive=}", f"{_yes=}")
|
|
223
|
+
vprint(f"Processing Directory: [cyan bold]{_files[0].name}")
|
|
224
|
+
if _archive:
|
|
225
|
+
archive_dir(config, ctx.params, _files[0])
|
|
226
|
+
raise typer.Exit()
|
|
227
|
+
_files = [p for p in _files[0].glob(_glob.strip('"`')) if p.is_file()]
|
|
228
|
+
show_confirm = True
|
|
229
|
+
vprint(f"{_files=}")
|
|
230
|
+
else:
|
|
231
|
+
# Remove Directories
|
|
232
|
+
files, dirs = [], []
|
|
233
|
+
for fp in _files:
|
|
234
|
+
files.append(fp) if fp.is_file() else dirs.append(fp)
|
|
235
|
+
if dirs:
|
|
236
|
+
_files = files
|
|
237
|
+
text = "[yellow]" + "\n".join(p.absolute().as_posix() for p in dirs)
|
|
238
|
+
print(Panel.fit(text, title=f"Directories Removed: [white]{len(dirs)}", style="red", title_align="left"))
|
|
239
|
+
print("[yellow bold]Warning[/yellow bold]: Directories can be processed individually.\n", file=sys.stderr)
|
|
240
|
+
|
|
241
|
+
# Process Files
|
|
242
|
+
if not _files:
|
|
243
|
+
raise click.ClickException("No Files to Process...")
|
|
244
|
+
table = Table(title=f"Processing {len(_files)} File{utils.s(_files)}")
|
|
245
|
+
table.add_column("File Name", style="magenta bold", no_wrap=True)
|
|
246
|
+
table.add_column("Size", style="yellow bold")
|
|
247
|
+
table.add_column("Path", style="cyan bold")
|
|
248
|
+
for path in _files:
|
|
249
|
+
table.add_row(path.name, utils.fmt_bytes(path.stat().st_size), path.absolute().as_posix())
|
|
250
|
+
console.print(table)
|
|
251
|
+
|
|
252
|
+
vprint(f"{show_confirm=}")
|
|
253
|
+
if show_confirm and not _yes:
|
|
254
|
+
typer.confirm(f"Confirm Uploading {len(_files)} Files?", abort=True)
|
|
255
|
+
|
|
256
|
+
for i, file in enumerate(_files, 1):
|
|
257
|
+
if file.is_dir():
|
|
258
|
+
print(f"[yellow bold]Skipping directory[yellow bold]: [cyan bold]{file.absolute()}", file=sys.stderr)
|
|
259
|
+
continue
|
|
260
|
+
size = utils.fmt_bytes(file.stat().st_size)
|
|
261
|
+
with console.status(
|
|
262
|
+
f"Uploading {i}/{len(_files)}: [green bold]{click.format_filename(file.name)}[/green bold] - [yellow bold]{size}"
|
|
263
|
+
):
|
|
264
|
+
file_name = _name if _name and len(_files) == 1 else file.name
|
|
265
|
+
with open(file, "rb") as f:
|
|
266
|
+
url = api.upload_file(config, file_name, f)
|
|
267
|
+
print(url)
|
|
268
|
+
copy_text(url)
|
|
269
|
+
if _launch and len(_files) == 1 and url:
|
|
270
|
+
typer.launch(url)
|
|
271
|
+
raise typer.Exit()
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
if __name__ == "__main__":
|
|
275
|
+
app()
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import secrets
|
|
2
|
+
import string
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def s(to_len: list) -> str:
|
|
6
|
+
return "s" if len(to_len) > 1 else ""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def gen_rand(length: int = 4) -> str:
|
|
10
|
+
r = "".join(secrets.choice(string.ascii_letters) for _ in range(length))
|
|
11
|
+
return "".join(r)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def fmt_bytes(num: float, suffix: str = "B") -> str:
|
|
15
|
+
for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
|
|
16
|
+
if abs(num) < 1024.0:
|
|
17
|
+
return f"{num:3.1f} {unit}{suffix}"
|
|
18
|
+
num /= 1024.0
|
|
19
|
+
return f"{num:.1f} Yi{suffix}"
|