pathapi-uri 0.1.0__py3-none-any.whl
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.
- pathapi_uri-0.1.0.dist-info/METADATA +259 -0
- pathapi_uri-0.1.0.dist-info/RECORD +48 -0
- pathapi_uri-0.1.0.dist-info/WHEEL +4 -0
- pathapi_uri-0.1.0.dist-info/entry_points.txt +17 -0
- pathapi_uri-0.1.0.dist-info/licenses/LICENSE +21 -0
- uripath/AGENTS.md +1094 -0
- uripath/README.md +213 -0
- uripath/__init__.py +27 -0
- uripath/__main__.py +7 -0
- uripath/_pathtext.py +32 -0
- uripath/_rfc3986.py +67 -0
- uripath/_transfer.py +108 -0
- uripath/_uri.py +1007 -0
- uripath/_version.py +11 -0
- uripath/cli/__init__.py +54 -0
- uripath/cli/_common.py +48 -0
- uripath/cli/_root.py +70 -0
- uripath/cli/cp.py +61 -0
- uripath/cli/read.py +22 -0
- uripath/cli/rm.py +38 -0
- uripath/cli/sync.py +78 -0
- uripath/cli/write.py +49 -0
- uripath/exceptions.py +38 -0
- uripath/py.typed +1 -0
- uripath/query.py +240 -0
- uripath/schemes/__init__.py +120 -0
- uripath/schemes/_archive.py +338 -0
- uripath/schemes/_data.py +99 -0
- uripath/schemes/_dav.py +459 -0
- uripath/schemes/_delegate.py +127 -0
- uripath/schemes/_elsewhere.py +102 -0
- uripath/schemes/_file.py +186 -0
- uripath/schemes/_ftp.py +200 -0
- uripath/schemes/_http/__init__.py +4 -0
- uripath/schemes/_http/_backend.py +132 -0
- uripath/schemes/_http/_headers.py +8 -0
- uripath/schemes/_http/_listing.py +403 -0
- uripath/schemes/_http/_path.py +371 -0
- uripath/schemes/_http/_settings.py +6 -0
- uripath/schemes/_http/_streams.py +230 -0
- uripath/schemes/_httpdate.py +48 -0
- uripath/schemes/_path.py +629 -0
- uripath/schemes/_registry.py +134 -0
- uripath/source/__init__.py +20 -0
- uripath/source/_compose.py +212 -0
- uripath/source/_locality.py +44 -0
- uripath/source/_parse.py +183 -0
- uripath/source/_source.py +239 -0
uripath/_version.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""The version of the installed distribution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import metadata as _metadata
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = _metadata.version("pathapi-uri")
|
|
9
|
+
except _metadata.PackageNotFoundError:
|
|
10
|
+
# Run from a source tree that was never installed.
|
|
11
|
+
__version__ = "0+unknown"
|
uripath/cli/__init__.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""The `uripath` command line, built on `duho`.
|
|
2
|
+
|
|
3
|
+
`main` is the entry point of the `uripath` console script and of `python -m
|
|
4
|
+
uripath`. It needs the `cli` extra (`pip install "pathapi-uri[cli]"`); importing
|
|
5
|
+
this package does not, so a bare install gets one line naming the extra in
|
|
6
|
+
place of an `ImportError`. The root command, `Uripath`, is built on first use
|
|
7
|
+
because its base class comes from `duho`.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import sys as _sys
|
|
13
|
+
import typing as _ty
|
|
14
|
+
|
|
15
|
+
if _ty.TYPE_CHECKING:
|
|
16
|
+
from ._root import Uripath
|
|
17
|
+
|
|
18
|
+
__all__ = ["main"]
|
|
19
|
+
|
|
20
|
+
_NEEDS_EXTRA = (
|
|
21
|
+
'uripath: error: the command line needs the "cli" extra: '
|
|
22
|
+
'pip install "pathapi-uri[cli]"'
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main(argv: "_ty.Optional[_ty.Sequence[str]]" = None) -> int:
|
|
27
|
+
"""Run one `uripath` command and return its exit status.
|
|
28
|
+
|
|
29
|
+
`argv` is the arguments after the program name (default `sys.argv[1:]`).
|
|
30
|
+
Returns 0 on success, 1 when the operation failed, 2 for a wrong
|
|
31
|
+
invocation, 130 on Ctrl-C and 141 when the reader of standard output went
|
|
32
|
+
away. Results go to stdout and one `uripath: <Type>: <message>` line per
|
|
33
|
+
failure to stderr. Without `duho` it prints which extra to install and
|
|
34
|
+
returns 1.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
import duho # noqa: F401
|
|
38
|
+
except ModuleNotFoundError as error:
|
|
39
|
+
if error.name != "duho":
|
|
40
|
+
raise
|
|
41
|
+
print(_NEEDS_EXTRA, file=_sys.stderr)
|
|
42
|
+
return 1
|
|
43
|
+
from ._root import run
|
|
44
|
+
|
|
45
|
+
return run(argv)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def __getattr__(name: str) -> "_ty.Any":
|
|
49
|
+
"""`Uripath`, the root command, imported on first use."""
|
|
50
|
+
if name == "Uripath":
|
|
51
|
+
from ._root import Uripath
|
|
52
|
+
|
|
53
|
+
return Uripath
|
|
54
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
uripath/cli/_common.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""What the commands share: the base class and the standard streams."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys as _sys
|
|
6
|
+
import typing as _ty
|
|
7
|
+
|
|
8
|
+
from duho import Cmd, LoggingArgs, UsageError, running_mode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Command(LoggingArgs, Cmd):
|
|
12
|
+
"""The base of every command: `-v`, `-q` and `--loglevel`, and the logger
|
|
13
|
+
they raise, `uripath`."""
|
|
14
|
+
|
|
15
|
+
_logger_name_ = "uripath"
|
|
16
|
+
# A tail after `--` would be ignored; a path that starts with `-` is
|
|
17
|
+
# written `./-name`.
|
|
18
|
+
_allow_passthrough_ = False
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def wants_detail(self) -> bool:
|
|
22
|
+
"""`-v` given more often than `-q`."""
|
|
23
|
+
return self.verbose > self.quiet
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def stdout() -> "_ty.BinaryIO":
|
|
27
|
+
"""The binary standard output of the process."""
|
|
28
|
+
return _sys.stdout.buffer
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def stdin() -> "_ty.BinaryIO":
|
|
32
|
+
"""The binary standard input of the process. A command served as a tool
|
|
33
|
+
has none: the server reads its requests from it."""
|
|
34
|
+
if running_mode() == "mcp":
|
|
35
|
+
raise UsageError("standard input is not available to a tool: name a file")
|
|
36
|
+
return _sys.stdin.buffer
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def source_of(text: str) -> "_ty.Union[str, _ty.BinaryIO]":
|
|
40
|
+
"""Where a command reads bytes from: `-` is standard input, anything else
|
|
41
|
+
the path or URI it names."""
|
|
42
|
+
return stdin() if text == "-" else text
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def target_of(text: str) -> "_ty.Union[str, _ty.BinaryIO]":
|
|
46
|
+
"""Where a command writes bytes to: `-` is standard output, anything else
|
|
47
|
+
the path or URI it names."""
|
|
48
|
+
return stdout() if text == "-" else text
|
uripath/cli/_root.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""The root command and the run around it: exceptions become statuses."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os as _os
|
|
6
|
+
import sys as _sys
|
|
7
|
+
import typing as _ty
|
|
8
|
+
|
|
9
|
+
from duho import AUTO, Cli, DefaultsFormatter
|
|
10
|
+
from duho import main as _main
|
|
11
|
+
|
|
12
|
+
from ..exceptions import OutputClosedError
|
|
13
|
+
from .cp import Cp
|
|
14
|
+
from .read import Read
|
|
15
|
+
from .rm import Rm
|
|
16
|
+
from .sync import Sync
|
|
17
|
+
from .write import Write
|
|
18
|
+
|
|
19
|
+
#: The status of a tool killed by SIGPIPE (128 + 13), and of Ctrl-C (128 + 2).
|
|
20
|
+
_EXIT_BROKEN_PIPE = 141
|
|
21
|
+
_EXIT_INTERRUPTED = 130
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Uripath(Cli):
|
|
25
|
+
"""Read, write, copy, remove and sync local paths and URIs. A PATH is a local path or a URI; - is standard input or output where bytes are read or written."""
|
|
26
|
+
|
|
27
|
+
_parsername_ = "uripath"
|
|
28
|
+
_version_ = AUTO
|
|
29
|
+
_distribution_ = "pathapi-uri"
|
|
30
|
+
_help_formatter_ = DefaultsFormatter
|
|
31
|
+
_exit_codes_ = {
|
|
32
|
+
1: "the operation failed",
|
|
33
|
+
2: "the invocation was wrong",
|
|
34
|
+
130: "interrupted",
|
|
35
|
+
141: "the reader of standard output went away",
|
|
36
|
+
}
|
|
37
|
+
_subcommands_ = [Read, Write, Rm, Cp, Sync]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _discard_stdout() -> None:
|
|
41
|
+
"""Point the real standard output at the null device, so that the flush
|
|
42
|
+
the interpreter makes at exit cannot fail again."""
|
|
43
|
+
try:
|
|
44
|
+
devnull = _os.open(_os.devnull, _os.O_WRONLY)
|
|
45
|
+
_os.dup2(devnull, _sys.stdout.fileno())
|
|
46
|
+
_os.close(devnull)
|
|
47
|
+
except (OSError, ValueError, AttributeError):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def run(argv: "_ty.Optional[_ty.Sequence[str]]") -> int:
|
|
52
|
+
"""Parse `argv`, run the command it names and return its exit status."""
|
|
53
|
+
try:
|
|
54
|
+
status = _main(Uripath, None if argv is None else list(argv))
|
|
55
|
+
except SystemExit as stop:
|
|
56
|
+
# The parser exits through SystemExit: 0 after --help and --version,
|
|
57
|
+
# 2 on a usage error.
|
|
58
|
+
if stop.code is None:
|
|
59
|
+
return 0
|
|
60
|
+
return stop.code if isinstance(stop.code, int) else 2
|
|
61
|
+
except OutputClosedError:
|
|
62
|
+
# Quiet, like a POSIX tool killed by SIGPIPE.
|
|
63
|
+
_discard_stdout()
|
|
64
|
+
return _EXIT_BROKEN_PIPE
|
|
65
|
+
except KeyboardInterrupt:
|
|
66
|
+
return _EXIT_INTERRUPTED
|
|
67
|
+
except Exception as error:
|
|
68
|
+
print(f"uripath: {type(error).__name__}: {error}", file=_sys.stderr)
|
|
69
|
+
return 1
|
|
70
|
+
return 0 if status is None else int(status)
|
uripath/cli/cp.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""`uripath cp`: copy a path, a tree or a stream."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from duho import UsageError
|
|
6
|
+
|
|
7
|
+
from .. import as_path, copy_stream
|
|
8
|
+
from ._common import Command, source_of, target_of
|
|
9
|
+
|
|
10
|
+
__all__ = ["Cp"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Cp(Command):
|
|
14
|
+
"""Copy a file, or with --recursive a directory, from SOURCE to TARGET; either may be a URI."""
|
|
15
|
+
|
|
16
|
+
_parsername_ = "cp"
|
|
17
|
+
|
|
18
|
+
source: str
|
|
19
|
+
"The file, directory or URI to copy; - reads standard input. Required"
|
|
20
|
+
("source",)
|
|
21
|
+
|
|
22
|
+
target: str
|
|
23
|
+
"Where to copy it to; - writes standard output. Required"
|
|
24
|
+
("target",)
|
|
25
|
+
|
|
26
|
+
recursive: bool = False
|
|
27
|
+
"Copy a directory with everything in it; not with -. Omitted: a single file"
|
|
28
|
+
("--recursive", "-r")
|
|
29
|
+
|
|
30
|
+
overwrite: bool = False
|
|
31
|
+
"Replace an existing TARGET. Omitted: an existing TARGET is refused"
|
|
32
|
+
("--overwrite",)
|
|
33
|
+
|
|
34
|
+
follow_symlinks: bool = True
|
|
35
|
+
"Copy what a symbolic link points to. With --no-follow-symlinks: copy the link itself"
|
|
36
|
+
("--follow-symlinks",)
|
|
37
|
+
|
|
38
|
+
preserve_metadata: bool = True
|
|
39
|
+
"Copy modes and times where the target can hold them. With --no-preserve-metadata: leave them out"
|
|
40
|
+
("--preserve-metadata",)
|
|
41
|
+
|
|
42
|
+
def __call__(self) -> int:
|
|
43
|
+
streamed = "-" in (self.source, self.target)
|
|
44
|
+
if streamed and self.recursive:
|
|
45
|
+
raise UsageError("--recursive cannot copy from or to '-'")
|
|
46
|
+
if streamed:
|
|
47
|
+
# An exclusive create: no check-then-write race on the target.
|
|
48
|
+
copy_stream(
|
|
49
|
+
source_of(self.source),
|
|
50
|
+
target_of(self.target),
|
|
51
|
+
exclusive=not self.overwrite,
|
|
52
|
+
)
|
|
53
|
+
return 0
|
|
54
|
+
as_path(self.source).copy(
|
|
55
|
+
as_path(self.target),
|
|
56
|
+
overwrite=self.overwrite,
|
|
57
|
+
follow_symlinks=self.follow_symlinks,
|
|
58
|
+
preserve_metadata=self.preserve_metadata,
|
|
59
|
+
recursive=self.recursive,
|
|
60
|
+
)
|
|
61
|
+
return 0
|
uripath/cli/read.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""`uripath read`: the bytes of a path to standard output."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .. import copy_stream
|
|
6
|
+
from ._common import Command, source_of, stdout
|
|
7
|
+
|
|
8
|
+
__all__ = ["Read"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Read(Command):
|
|
12
|
+
"""Write the bytes of a path or URI to standard output, in chunks."""
|
|
13
|
+
|
|
14
|
+
_parsername_ = "read"
|
|
15
|
+
|
|
16
|
+
path: str
|
|
17
|
+
"The file or URI to read; - reads standard input. Required"
|
|
18
|
+
("path",)
|
|
19
|
+
|
|
20
|
+
def __call__(self) -> int:
|
|
21
|
+
copy_stream(source_of(self.path), stdout())
|
|
22
|
+
return 0
|
uripath/cli/rm.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""`uripath rm`: remove a path."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .. import as_path
|
|
6
|
+
from ._common import Command
|
|
7
|
+
|
|
8
|
+
__all__ = ["Rm"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Rm(Command):
|
|
12
|
+
"""Remove a file, an empty directory or a URI; with --recursive, a directory and everything in it."""
|
|
13
|
+
|
|
14
|
+
_parsername_ = "rm"
|
|
15
|
+
|
|
16
|
+
path: str
|
|
17
|
+
"The file, empty directory or URI to remove. Required"
|
|
18
|
+
("path",)
|
|
19
|
+
|
|
20
|
+
recursive: bool = False
|
|
21
|
+
"Remove a directory with everything in it. Omitted: a directory must be empty"
|
|
22
|
+
("--recursive", "-r")
|
|
23
|
+
|
|
24
|
+
missing_ok: bool = False
|
|
25
|
+
"Succeed when the path does not exist. Omitted: a missing path fails"
|
|
26
|
+
("--missing-ok",)
|
|
27
|
+
|
|
28
|
+
ignore_error: bool = False
|
|
29
|
+
"Keep removing after a failure. Omitted: stop at the first"
|
|
30
|
+
("--ignore-error",)
|
|
31
|
+
|
|
32
|
+
def __call__(self) -> int:
|
|
33
|
+
as_path(self.path).rm(
|
|
34
|
+
recursive=self.recursive,
|
|
35
|
+
missing_ok=self.missing_ok,
|
|
36
|
+
ignore_error=self.ignore_error,
|
|
37
|
+
)
|
|
38
|
+
return 0
|
uripath/cli/sync.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""`uripath sync`: make a target tree match a source tree."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io as _io
|
|
6
|
+
|
|
7
|
+
from pathapi.sync import PathSyncer, SyncEvent
|
|
8
|
+
|
|
9
|
+
from .. import as_path, copy_stream
|
|
10
|
+
from ._common import Command, stdout
|
|
11
|
+
|
|
12
|
+
__all__ = ["Sync"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_ACTIONS = {
|
|
16
|
+
SyncEvent.Copy: "copy",
|
|
17
|
+
SyncEvent.RemovedMissing: "remove",
|
|
18
|
+
SyncEvent.CreatedDirectory: "mkdir",
|
|
19
|
+
SyncEvent.TypeMismatch: "replace",
|
|
20
|
+
SyncEvent.Symlink: "symlink",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Sync(Command):
|
|
25
|
+
"""Make TARGET match SOURCE, comparing file content; nothing is deleted unless --remove-missing."""
|
|
26
|
+
|
|
27
|
+
_parsername_ = "sync"
|
|
28
|
+
|
|
29
|
+
source: str
|
|
30
|
+
"The directory or URI to copy from. Required"
|
|
31
|
+
("source",)
|
|
32
|
+
|
|
33
|
+
target: str
|
|
34
|
+
"The directory or URI to make match SOURCE. Required"
|
|
35
|
+
("target",)
|
|
36
|
+
|
|
37
|
+
dry_run: bool = False
|
|
38
|
+
"Print what would change without changing anything. Omitted: make the changes"
|
|
39
|
+
("--dry-run",)
|
|
40
|
+
|
|
41
|
+
remove_missing: bool = False
|
|
42
|
+
"Delete the entries of TARGET that SOURCE does not have. Omitted: keep them"
|
|
43
|
+
("--remove-missing",)
|
|
44
|
+
|
|
45
|
+
size_only: bool = False
|
|
46
|
+
"Compare file sizes only, which misses same-size edits. Omitted: compare content"
|
|
47
|
+
("--size-only",)
|
|
48
|
+
|
|
49
|
+
follow_symlinks: bool = True
|
|
50
|
+
"Copy what a symbolic link points to. With --no-follow-symlinks: recreate the link"
|
|
51
|
+
("--follow-symlinks",)
|
|
52
|
+
|
|
53
|
+
follow_mounts: bool = True
|
|
54
|
+
"Sync into a mount point inside TARGET. With --no-follow-mounts: fail on one"
|
|
55
|
+
("--follow-mounts",)
|
|
56
|
+
|
|
57
|
+
def __call__(self) -> int:
|
|
58
|
+
PathSyncer(
|
|
59
|
+
(lambda entry: entry.stat.st_size) if self.size_only else None,
|
|
60
|
+
remove_missing=self.remove_missing,
|
|
61
|
+
follow_symlinks=self.follow_symlinks,
|
|
62
|
+
follow_mounts=self.follow_mounts,
|
|
63
|
+
hook=self._report,
|
|
64
|
+
).sync(as_path(self.source), as_path(self.target), dry_run=self.dry_run)
|
|
65
|
+
return 0
|
|
66
|
+
|
|
67
|
+
def _report(self, source, target, event, dry_run) -> None:
|
|
68
|
+
"""Print one line for a change: a planned one under --dry-run, a made one under -v."""
|
|
69
|
+
action = _ACTIONS.get(event)
|
|
70
|
+
if action is None or not (dry_run or self.wants_detail):
|
|
71
|
+
return
|
|
72
|
+
prefix = "would " if dry_run else ""
|
|
73
|
+
if event is SyncEvent.Copy:
|
|
74
|
+
line = f"{prefix}{action} {source.path} -> {target.path}"
|
|
75
|
+
else:
|
|
76
|
+
line = f"{prefix}{action} {target.path}"
|
|
77
|
+
text = line.encode("utf-8", "backslashreplace") + b"\n"
|
|
78
|
+
copy_stream(_io.BytesIO(text), stdout())
|
uripath/cli/write.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""`uripath write`: standard input, or the text given, into a path."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io as _io
|
|
6
|
+
import typing as _ty
|
|
7
|
+
|
|
8
|
+
from duho import Arg, Meta
|
|
9
|
+
|
|
10
|
+
from .. import as_path, copy_stream
|
|
11
|
+
from ._common import Command, stdin, stdout, target_of
|
|
12
|
+
|
|
13
|
+
__all__ = ["Write"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _encoding(name: str) -> str:
|
|
17
|
+
"""A text encoding the interpreter knows (not a bytes-to-bytes codec)."""
|
|
18
|
+
try:
|
|
19
|
+
"".encode(name)
|
|
20
|
+
except LookupError:
|
|
21
|
+
raise ValueError(f"unknown text encoding: {name!r}") from None
|
|
22
|
+
return name
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Write(Command):
|
|
26
|
+
"""Write standard input, or the text DATA, to a path or URI, replacing what is there."""
|
|
27
|
+
|
|
28
|
+
_parsername_ = "write"
|
|
29
|
+
|
|
30
|
+
path: str
|
|
31
|
+
"The file or URI to write, replacing it; - writes standard output. Required"
|
|
32
|
+
("path",)
|
|
33
|
+
|
|
34
|
+
data: _ty.Optional[str] = None
|
|
35
|
+
"The text to write, encoded with --encoding. Omitted: the bytes of standard input"
|
|
36
|
+
("data",)
|
|
37
|
+
|
|
38
|
+
encoding: Arg[str, Meta(type=_encoding)] = "utf-8"
|
|
39
|
+
"The text encoding of DATA"
|
|
40
|
+
("--encoding",)
|
|
41
|
+
|
|
42
|
+
def __call__(self) -> int:
|
|
43
|
+
if self.data is None:
|
|
44
|
+
copy_stream(stdin(), target_of(self.path))
|
|
45
|
+
elif self.path == "-":
|
|
46
|
+
copy_stream(_io.BytesIO(self.data.encode(self.encoding)), stdout())
|
|
47
|
+
else:
|
|
48
|
+
as_path(self.path).write_bytes(self.data.encode(self.encoding))
|
|
49
|
+
return 0
|
uripath/exceptions.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""The exceptions of uripath.
|
|
2
|
+
|
|
3
|
+
`URIPathError` is the base of what goes wrong at run time. A caller's bad
|
|
4
|
+
argument is a plain `ValueError` or `TypeError`, and a failed read or write
|
|
5
|
+
is the `OSError` of the kind `pathlib` raises.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"OutputClosedError",
|
|
12
|
+
"URIPathError",
|
|
13
|
+
"URISyntaxError",
|
|
14
|
+
"UnsupportedSchemeError",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class URIPathError(Exception):
|
|
19
|
+
"""The base of the errors uripath raises."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class URISyntaxError(URIPathError, ValueError):
|
|
23
|
+
"""Text that is not a URI, or not the part of one that was asked for: a port
|
|
24
|
+
that is not 0-65535, a malformed host, a `zip:` URI without the scheme of
|
|
25
|
+
its archive. The message names what is wrong and never the text, which can
|
|
26
|
+
carry a password."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class UnsupportedSchemeError(URIPathError, ValueError):
|
|
30
|
+
"""A scheme that no class registered. The message lists the registered
|
|
31
|
+
ones."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class OutputClosedError(URIPathError, BrokenPipeError):
|
|
35
|
+
"""The reader of a stream that `copy_stream()` was writing went away: a
|
|
36
|
+
pipe whose other end closed (`uripath read big | head`). It is raised for
|
|
37
|
+
a stream passed as the target, never for a failure on a path's own
|
|
38
|
+
connection."""
|
uripath/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Marker file for PEP 561.
|