ghps 0.2__tar.gz → 0.4__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.
- {ghps-0.2 → ghps-0.4}/CHANGELOG.md +20 -1
- {ghps-0.2 → ghps-0.4}/PKG-INFO +31 -5
- {ghps-0.2 → ghps-0.4}/README.md +9 -2
- {ghps-0.2 → ghps-0.4}/SECURITY.md +2 -2
- {ghps-0.2 → ghps-0.4}/dev-requirements.txt +1 -1
- ghps-0.4/ghps/__init__.py +8 -0
- {ghps-0.2 → ghps-0.4}/ghps/cli.py +37 -20
- ghps-0.4/ghps/errors.py +14 -0
- {ghps-0.2 → ghps-0.4}/ghps/params.py +7 -1
- {ghps-0.2 → ghps-0.4}/ghps/server.py +66 -14
- {ghps-0.2 → ghps-0.4}/ghps.egg-info/PKG-INFO +31 -5
- {ghps-0.2 → ghps-0.4}/ghps.egg-info/SOURCES.txt +1 -0
- {ghps-0.2 → ghps-0.4}/setup.py +2 -2
- {ghps-0.2 → ghps-0.4}/tests/test_cli.py +14 -0
- ghps-0.4/tests/test_error.py +209 -0
- {ghps-0.2 → ghps-0.4}/tests/test_server.py +49 -0
- ghps-0.2/ghps/__init__.py +0 -7
- ghps-0.2/tests/test_error.py +0 -126
- {ghps-0.2 → ghps-0.4}/AUTHORS.md +0 -0
- {ghps-0.2 → ghps-0.4}/LICENSE +0 -0
- {ghps-0.2 → ghps-0.4}/MANIFEST.in +0 -0
- {ghps-0.2 → ghps-0.4}/ghps/__main__.py +0 -0
- {ghps-0.2 → ghps-0.4}/ghps.egg-info/dependency_links.txt +0 -0
- {ghps-0.2 → ghps-0.4}/ghps.egg-info/entry_points.txt +0 -0
- {ghps-0.2 → ghps-0.4}/ghps.egg-info/requires.txt +0 -0
- {ghps-0.2 → ghps-0.4}/ghps.egg-info/top_level.txt +0 -0
- {ghps-0.2 → ghps-0.4}/requirements.txt +0 -0
- {ghps-0.2 → ghps-0.4}/setup.cfg +0 -0
|
@@ -5,6 +5,23 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
|
+
## [0.4] - 2026-05-08
|
|
9
|
+
### Added
|
|
10
|
+
- `GHPSError` class
|
|
11
|
+
- `GHPSValidationError` class
|
|
12
|
+
- `GHPSRuntimeError` class
|
|
13
|
+
- Port error messages
|
|
14
|
+
### Changed
|
|
15
|
+
- Test system modified
|
|
16
|
+
## [0.3] - 2026-04-05
|
|
17
|
+
### Added
|
|
18
|
+
- `auto_open` parameter
|
|
19
|
+
- `--auto-open` CLI argument
|
|
20
|
+
- Random port support
|
|
21
|
+
### Changed
|
|
22
|
+
- Test system modified
|
|
23
|
+
- CLI error messages updated
|
|
24
|
+
- `README.md` updated
|
|
8
25
|
## [0.2] - 2026-03-19
|
|
9
26
|
### Added
|
|
10
27
|
- Logo
|
|
@@ -18,7 +35,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
18
35
|
- Command line interface
|
|
19
36
|
- Test system
|
|
20
37
|
|
|
21
|
-
[Unreleased]: https://github.com/sepandhaghighi/ghps/compare/v0.
|
|
38
|
+
[Unreleased]: https://github.com/sepandhaghighi/ghps/compare/v0.4...dev
|
|
39
|
+
[0.4]: https://github.com/sepandhaghighi/ghps/compare/v0.3...v0.4
|
|
40
|
+
[0.3]: https://github.com/sepandhaghighi/ghps/compare/v0.2...v0.3
|
|
22
41
|
[0.2]: https://github.com/sepandhaghighi/ghps/compare/v0.1...v0.2
|
|
23
42
|
[0.1]: https://github.com/sepandhaghighi/ghps/compare/bc0a425...v0.1
|
|
24
43
|
|
{ghps-0.2 → ghps-0.4}/PKG-INFO
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ghps
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4
|
|
4
4
|
Summary: Ghps: A Minimal GitHub Pages Simulator for Local Development
|
|
5
5
|
Home-page: https://github.com/sepandhaghighi/ghps
|
|
6
|
-
Download-URL: https://github.com/sepandhaghighi/ghps/tarball/v0.
|
|
6
|
+
Download-URL: https://github.com/sepandhaghighi/ghps/tarball/v0.4
|
|
7
7
|
Author: Sepand Haghighi
|
|
8
8
|
Author-email: me@sepand.tech
|
|
9
9
|
License: MIT
|
|
@@ -101,13 +101,13 @@ Dynamic: summary
|
|
|
101
101
|
## Installation
|
|
102
102
|
|
|
103
103
|
### Source Code
|
|
104
|
-
- Download [Version 0.
|
|
104
|
+
- Download [Version 0.4](https://github.com/sepandhaghighi/ghps/archive/v0.4.zip) or [Latest Source](https://github.com/sepandhaghighi/ghps/archive/dev.zip)
|
|
105
105
|
- `pip install .`
|
|
106
106
|
|
|
107
107
|
### PyPI
|
|
108
108
|
|
|
109
109
|
- Check [Python Packaging User Guide](https://packaging.python.org/installing/)
|
|
110
|
-
- `pip install ghps==0.
|
|
110
|
+
- `pip install ghps==0.4`
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
## Usage
|
|
@@ -130,6 +130,9 @@ ghps --port=8080 --no-cache --no-threaded
|
|
|
130
130
|
| `--no-strict` | Enable `.html` fallback| Strict mode enabled |
|
|
131
131
|
| `--no-cache` | Disable HTTP caching headers | Cache enabled |
|
|
132
132
|
| `--no-threaded` | Disable threaded server | Threaded enabled |
|
|
133
|
+
| `--auto-open` | Automatically open the server URL | Disable |
|
|
134
|
+
|
|
135
|
+
ℹ️ You can set `--port=0` to let the OS automatically select an available port
|
|
133
136
|
|
|
134
137
|
|
|
135
138
|
### Library
|
|
@@ -144,6 +147,7 @@ server = GHPageServer(
|
|
|
144
147
|
strict=True,
|
|
145
148
|
no_cache=False,
|
|
146
149
|
threaded=True,
|
|
150
|
+
auto_open=True,
|
|
147
151
|
)
|
|
148
152
|
|
|
149
153
|
server.start()
|
|
@@ -159,6 +163,9 @@ server.start()
|
|
|
159
163
|
| `strict` | `bool` | Enforce GitHub-like routing behavior | `True` |
|
|
160
164
|
| `no_cache` | `bool` | Disable cache headers | `False` |
|
|
161
165
|
| `threaded` | `bool` | Enable threaded request handling | `True` |
|
|
166
|
+
| `auto_open` | `bool` | Automatically open the server URL | `False` |
|
|
167
|
+
|
|
168
|
+
ℹ️ You can set `port=0` to let the OS automatically select an available port
|
|
162
169
|
|
|
163
170
|
## Issues & Bug Reports
|
|
164
171
|
|
|
@@ -209,6 +216,23 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
|
209
216
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
210
217
|
|
|
211
218
|
## [Unreleased]
|
|
219
|
+
## [0.4] - 2026-05-08
|
|
220
|
+
### Added
|
|
221
|
+
- `GHPSError` class
|
|
222
|
+
- `GHPSValidationError` class
|
|
223
|
+
- `GHPSRuntimeError` class
|
|
224
|
+
- Port error messages
|
|
225
|
+
### Changed
|
|
226
|
+
- Test system modified
|
|
227
|
+
## [0.3] - 2026-04-05
|
|
228
|
+
### Added
|
|
229
|
+
- `auto_open` parameter
|
|
230
|
+
- `--auto-open` CLI argument
|
|
231
|
+
- Random port support
|
|
232
|
+
### Changed
|
|
233
|
+
- Test system modified
|
|
234
|
+
- CLI error messages updated
|
|
235
|
+
- `README.md` updated
|
|
212
236
|
## [0.2] - 2026-03-19
|
|
213
237
|
### Added
|
|
214
238
|
- Logo
|
|
@@ -222,7 +246,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
222
246
|
- Command line interface
|
|
223
247
|
- Test system
|
|
224
248
|
|
|
225
|
-
[Unreleased]: https://github.com/sepandhaghighi/ghps/compare/v0.
|
|
249
|
+
[Unreleased]: https://github.com/sepandhaghighi/ghps/compare/v0.4...dev
|
|
250
|
+
[0.4]: https://github.com/sepandhaghighi/ghps/compare/v0.3...v0.4
|
|
251
|
+
[0.3]: https://github.com/sepandhaghighi/ghps/compare/v0.2...v0.3
|
|
226
252
|
[0.2]: https://github.com/sepandhaghighi/ghps/compare/v0.1...v0.2
|
|
227
253
|
[0.1]: https://github.com/sepandhaghighi/ghps/compare/bc0a425...v0.1
|
|
228
254
|
|
{ghps-0.2 → ghps-0.4}/README.md
RENAMED
|
@@ -50,13 +50,13 @@
|
|
|
50
50
|
## Installation
|
|
51
51
|
|
|
52
52
|
### Source Code
|
|
53
|
-
- Download [Version 0.
|
|
53
|
+
- Download [Version 0.4](https://github.com/sepandhaghighi/ghps/archive/v0.4.zip) or [Latest Source](https://github.com/sepandhaghighi/ghps/archive/dev.zip)
|
|
54
54
|
- `pip install .`
|
|
55
55
|
|
|
56
56
|
### PyPI
|
|
57
57
|
|
|
58
58
|
- Check [Python Packaging User Guide](https://packaging.python.org/installing/)
|
|
59
|
-
- `pip install ghps==0.
|
|
59
|
+
- `pip install ghps==0.4`
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
## Usage
|
|
@@ -79,6 +79,9 @@ ghps --port=8080 --no-cache --no-threaded
|
|
|
79
79
|
| `--no-strict` | Enable `.html` fallback| Strict mode enabled |
|
|
80
80
|
| `--no-cache` | Disable HTTP caching headers | Cache enabled |
|
|
81
81
|
| `--no-threaded` | Disable threaded server | Threaded enabled |
|
|
82
|
+
| `--auto-open` | Automatically open the server URL | Disable |
|
|
83
|
+
|
|
84
|
+
ℹ️ You can set `--port=0` to let the OS automatically select an available port
|
|
82
85
|
|
|
83
86
|
|
|
84
87
|
### Library
|
|
@@ -93,6 +96,7 @@ server = GHPageServer(
|
|
|
93
96
|
strict=True,
|
|
94
97
|
no_cache=False,
|
|
95
98
|
threaded=True,
|
|
99
|
+
auto_open=True,
|
|
96
100
|
)
|
|
97
101
|
|
|
98
102
|
server.start()
|
|
@@ -108,6 +112,9 @@ server.start()
|
|
|
108
112
|
| `strict` | `bool` | Enforce GitHub-like routing behavior | `True` |
|
|
109
113
|
| `no_cache` | `bool` | Disable cache headers | `False` |
|
|
110
114
|
| `threaded` | `bool` | Enable threaded request handling | `True` |
|
|
115
|
+
| `auto_open` | `bool` | Automatically open the server URL | `False` |
|
|
116
|
+
|
|
117
|
+
ℹ️ You can set `port=0` to let the OS automatically select an available port
|
|
111
118
|
|
|
112
119
|
## Issues & Bug Reports
|
|
113
120
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""ghps modules."""
|
|
3
|
+
from .params import GHPS_VERSION
|
|
4
|
+
from .errors import GHPSError, GHPSRuntimeError, GHPSValidationError
|
|
5
|
+
from .server import GHPageServer
|
|
6
|
+
__version__ = GHPS_VERSION
|
|
7
|
+
|
|
8
|
+
__all__ = ["GHPageServer", "GHPSError", "GHPSRuntimeError", "GHPSValidationError"]
|
|
@@ -5,9 +5,21 @@ import sys
|
|
|
5
5
|
import argparse
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from .params import GHPS_VERSION
|
|
8
|
+
from .errors import GHPSError
|
|
8
9
|
from .server import GHPageServer
|
|
9
10
|
|
|
10
11
|
|
|
12
|
+
def _print_cli_error(message: str, exit_code: int = 1) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Print a formatted CLI error message and exit.
|
|
15
|
+
|
|
16
|
+
:param message: Error message to display.
|
|
17
|
+
:param exit_code: Exit status code (default: 1).
|
|
18
|
+
"""
|
|
19
|
+
print(f"[GHPS ERROR] {message}", file=sys.stderr)
|
|
20
|
+
sys.exit(exit_code)
|
|
21
|
+
|
|
22
|
+
|
|
11
23
|
def _parse_args() -> argparse.Namespace:
|
|
12
24
|
"""Parse arguments."""
|
|
13
25
|
parser = argparse.ArgumentParser(
|
|
@@ -57,29 +69,34 @@ def _parse_args() -> argparse.Namespace:
|
|
|
57
69
|
help="Disable threaded server"
|
|
58
70
|
)
|
|
59
71
|
|
|
72
|
+
parser.add_argument(
|
|
73
|
+
"--auto-open",
|
|
74
|
+
action="store_true",
|
|
75
|
+
help="Automatically open the server URL"
|
|
76
|
+
)
|
|
77
|
+
|
|
60
78
|
args = parser.parse_args()
|
|
61
79
|
return args
|
|
62
80
|
|
|
63
81
|
|
|
64
82
|
def main() -> None:
|
|
65
83
|
"""CLI main function."""
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
)
|
|
85
|
-
server.start()
|
|
84
|
+
try:
|
|
85
|
+
args = _parse_args()
|
|
86
|
+
if args.version:
|
|
87
|
+
print(GHPS_VERSION)
|
|
88
|
+
else:
|
|
89
|
+
server = GHPageServer(
|
|
90
|
+
directory=args.directory,
|
|
91
|
+
port=args.port,
|
|
92
|
+
base_path=args.base_path,
|
|
93
|
+
strict=not args.no_strict,
|
|
94
|
+
no_cache=args.no_cache,
|
|
95
|
+
threaded=not args.no_threaded,
|
|
96
|
+
auto_open=args.auto_open,
|
|
97
|
+
)
|
|
98
|
+
server.start()
|
|
99
|
+
except GHPSError as e:
|
|
100
|
+
_print_cli_error(str(e))
|
|
101
|
+
except Exception as e:
|
|
102
|
+
_print_cli_error(f"Unexpected error: {e}")
|
ghps-0.4/ghps/errors.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""ghps errors."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class GHPSError(Exception):
|
|
6
|
+
"""Base exception for GHPS."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GHPSValidationError(GHPSError, ValueError):
|
|
10
|
+
"""Raised for invalid input parameters."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class GHPSRuntimeError(GHPSError):
|
|
14
|
+
"""Raised for runtime errors during server execution."""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""ghps params."""
|
|
3
3
|
|
|
4
|
-
GHPS_VERSION = "0.
|
|
4
|
+
GHPS_VERSION = "0.4"
|
|
5
5
|
|
|
6
6
|
INVALID_DIRECTORY_TYPE_ERROR = "`directory` must be str or pathlib.Path."
|
|
7
7
|
DIRECTORY_NOT_FOUND_ERROR = "`directory` does not exist."
|
|
@@ -16,3 +16,9 @@ INVALID_BASE_PATH_FORMAT_ERROR = "`base_path` must start with '/' or be empty."
|
|
|
16
16
|
INVALID_STRICT_TYPE_ERROR = "`strict` must be bool."
|
|
17
17
|
INVALID_NO_CACHE_TYPE_ERROR = "`no_cache` must be bool."
|
|
18
18
|
INVALID_THREADED_TYPE_ERROR = "`threaded` must be bool."
|
|
19
|
+
INVALID_AUTO_OPEN_TYPE_ERROR = "`auto_open` must be bool."
|
|
20
|
+
|
|
21
|
+
PORT_IN_USE_ERROR = "`port` is already in use. Try another port or use 0."
|
|
22
|
+
PORT_ACCESS_DENIED_ERROR = "`port` access denied. Try a different port or check permissions."
|
|
23
|
+
PORT_ADDRESS_NOT_AVAILABLE_ERROR = "`port` address is not available. Check network configuration."
|
|
24
|
+
PORT_BIND_GENERIC_ERROR = "Failed to start server on port `{port}`: {error}"
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import http.server
|
|
5
5
|
import socketserver
|
|
6
|
+
import webbrowser
|
|
7
|
+
import errno
|
|
6
8
|
from typing import Optional, Any
|
|
7
9
|
from pathlib import Path
|
|
8
10
|
from urllib.parse import unquote
|
|
@@ -17,7 +19,15 @@ from .params import (
|
|
|
17
19
|
INVALID_STRICT_TYPE_ERROR,
|
|
18
20
|
INVALID_NO_CACHE_TYPE_ERROR,
|
|
19
21
|
INVALID_THREADED_TYPE_ERROR,
|
|
22
|
+
INVALID_AUTO_OPEN_TYPE_ERROR
|
|
20
23
|
)
|
|
24
|
+
from .params import (
|
|
25
|
+
PORT_IN_USE_ERROR,
|
|
26
|
+
PORT_ACCESS_DENIED_ERROR,
|
|
27
|
+
PORT_ADDRESS_NOT_AVAILABLE_ERROR,
|
|
28
|
+
PORT_BIND_GENERIC_ERROR,
|
|
29
|
+
)
|
|
30
|
+
from .errors import GHPSValidationError, GHPSRuntimeError
|
|
21
31
|
|
|
22
32
|
|
|
23
33
|
def _validate_inputs(
|
|
@@ -27,6 +37,7 @@ def _validate_inputs(
|
|
|
27
37
|
strict: Any,
|
|
28
38
|
no_cache: Any,
|
|
29
39
|
threaded: Any,
|
|
40
|
+
auto_open: Any,
|
|
30
41
|
):
|
|
31
42
|
"""
|
|
32
43
|
Validate GHPageServer inputs.
|
|
@@ -37,37 +48,65 @@ def _validate_inputs(
|
|
|
37
48
|
:param strict: If False, enables automatic ".html" resolution.
|
|
38
49
|
:param no_cache: If True, disables client-side caching.
|
|
39
50
|
:param threaded: If True, handles requests using threads.
|
|
51
|
+
:param auto_open: If True, automatically opens the server URL in the default web browser.
|
|
40
52
|
"""
|
|
41
53
|
if not isinstance(directory, (str, Path)):
|
|
42
|
-
raise
|
|
54
|
+
raise GHPSValidationError(INVALID_DIRECTORY_TYPE_ERROR)
|
|
43
55
|
|
|
44
56
|
directory = Path(directory)
|
|
45
57
|
if not directory.exists():
|
|
46
|
-
raise
|
|
58
|
+
raise GHPSValidationError(DIRECTORY_NOT_FOUND_ERROR)
|
|
47
59
|
|
|
48
60
|
if not directory.is_dir():
|
|
49
|
-
raise
|
|
61
|
+
raise GHPSValidationError(DIRECTORY_NOT_DIR_ERROR)
|
|
50
62
|
|
|
51
63
|
if not isinstance(port, int):
|
|
52
|
-
raise
|
|
64
|
+
raise GHPSValidationError(INVALID_PORT_TYPE_ERROR)
|
|
53
65
|
|
|
54
|
-
if not (
|
|
55
|
-
raise
|
|
66
|
+
if not (0 <= port <= 65535):
|
|
67
|
+
raise GHPSValidationError(INVALID_PORT_RANGE_ERROR)
|
|
56
68
|
|
|
57
69
|
if not isinstance(base_path, str):
|
|
58
|
-
raise
|
|
70
|
+
raise GHPSValidationError(INVALID_BASE_PATH_TYPE_ERROR)
|
|
59
71
|
|
|
60
72
|
if base_path and not base_path.startswith("/"):
|
|
61
|
-
raise
|
|
73
|
+
raise GHPSValidationError(INVALID_BASE_PATH_FORMAT_ERROR)
|
|
62
74
|
|
|
63
75
|
if not isinstance(strict, bool):
|
|
64
|
-
raise
|
|
76
|
+
raise GHPSValidationError(INVALID_STRICT_TYPE_ERROR)
|
|
65
77
|
|
|
66
78
|
if not isinstance(no_cache, bool):
|
|
67
|
-
raise
|
|
79
|
+
raise GHPSValidationError(INVALID_NO_CACHE_TYPE_ERROR)
|
|
68
80
|
|
|
69
81
|
if not isinstance(threaded, bool):
|
|
70
|
-
raise
|
|
82
|
+
raise GHPSValidationError(INVALID_THREADED_TYPE_ERROR)
|
|
83
|
+
|
|
84
|
+
if not isinstance(auto_open, bool):
|
|
85
|
+
raise GHPSValidationError(INVALID_AUTO_OPEN_TYPE_ERROR)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _handle_bind_error(port: int, e: OSError) -> None:
|
|
89
|
+
"""
|
|
90
|
+
Handle socket binding errors and raise a user-friendly runtime exception.
|
|
91
|
+
|
|
92
|
+
:param port: Port number that the server attempted to bind to.
|
|
93
|
+
:param e: Original OSError raised during binding.
|
|
94
|
+
"""
|
|
95
|
+
err = getattr(e, "errno", None)
|
|
96
|
+
|
|
97
|
+
if err == errno.EADDRINUSE:
|
|
98
|
+
raise GHPSRuntimeError(PORT_IN_USE_ERROR) from e
|
|
99
|
+
|
|
100
|
+
elif err in (errno.EACCES, errno.EPERM):
|
|
101
|
+
raise GHPSRuntimeError(PORT_ACCESS_DENIED_ERROR) from e
|
|
102
|
+
|
|
103
|
+
elif err == errno.EADDRNOTAVAIL:
|
|
104
|
+
raise GHPSRuntimeError(PORT_ADDRESS_NOT_AVAILABLE_ERROR) from e
|
|
105
|
+
|
|
106
|
+
else:
|
|
107
|
+
raise GHPSRuntimeError(
|
|
108
|
+
PORT_BIND_GENERIC_ERROR.format(port=port, error=e)
|
|
109
|
+
) from e
|
|
71
110
|
|
|
72
111
|
|
|
73
112
|
class _GHRequestHandler(http.server.SimpleHTTPRequestHandler):
|
|
@@ -178,6 +217,7 @@ class GHPageServer:
|
|
|
178
217
|
strict: bool = True,
|
|
179
218
|
no_cache: bool = False,
|
|
180
219
|
threaded: bool = True,
|
|
220
|
+
auto_open: bool = False,
|
|
181
221
|
):
|
|
182
222
|
"""
|
|
183
223
|
Initialize the server.
|
|
@@ -188,6 +228,7 @@ class GHPageServer:
|
|
|
188
228
|
:param strict: If False, enables automatic ".html" resolution.
|
|
189
229
|
:param no_cache: If True, disables client-side caching.
|
|
190
230
|
:param threaded: If True, handles requests using threads.
|
|
231
|
+
:param auto_open: If True, automatically opens the server URL in the default web browser.
|
|
191
232
|
"""
|
|
192
233
|
_validate_inputs(
|
|
193
234
|
directory=directory,
|
|
@@ -196,6 +237,7 @@ class GHPageServer:
|
|
|
196
237
|
strict=strict,
|
|
197
238
|
no_cache=no_cache,
|
|
198
239
|
threaded=threaded,
|
|
240
|
+
auto_open=auto_open,
|
|
199
241
|
)
|
|
200
242
|
self._directory = str(Path(directory).resolve())
|
|
201
243
|
self._port = port
|
|
@@ -203,6 +245,7 @@ class GHPageServer:
|
|
|
203
245
|
self._strict = strict
|
|
204
246
|
self._no_cache = no_cache
|
|
205
247
|
self._threaded = threaded
|
|
248
|
+
self._auto_open = auto_open
|
|
206
249
|
self._httpd = None
|
|
207
250
|
|
|
208
251
|
def start(self) -> None:
|
|
@@ -222,13 +265,22 @@ class GHPageServer:
|
|
|
222
265
|
|
|
223
266
|
server_cls = _ThreadedTCPServer if self._threaded else socketserver.TCPServer
|
|
224
267
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
268
|
+
try:
|
|
269
|
+
self._httpd = server_cls(("", self._port), handler)
|
|
270
|
+
except OSError as e:
|
|
271
|
+
_handle_bind_error(self._port, e)
|
|
272
|
+
self._port = self._httpd.server_address[1]
|
|
273
|
+
url = f"http://localhost:{self._port}{self._base_path}"
|
|
274
|
+
print(f"Serving at {url}")
|
|
228
275
|
print(f"Directory: {self._directory}")
|
|
229
276
|
print(f"Strict mode: {'ON' if self._strict else 'OFF'}")
|
|
230
277
|
print(f"Cache disabled: {'YES' if self._no_cache else 'NO'}")
|
|
231
278
|
|
|
279
|
+
if self._auto_open:
|
|
280
|
+
try:
|
|
281
|
+
webbrowser.open(url)
|
|
282
|
+
except Exception:
|
|
283
|
+
print("[GHPS ERROR]: Failed to open browser automatically")
|
|
232
284
|
try:
|
|
233
285
|
self._httpd.serve_forever()
|
|
234
286
|
except KeyboardInterrupt:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ghps
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4
|
|
4
4
|
Summary: Ghps: A Minimal GitHub Pages Simulator for Local Development
|
|
5
5
|
Home-page: https://github.com/sepandhaghighi/ghps
|
|
6
|
-
Download-URL: https://github.com/sepandhaghighi/ghps/tarball/v0.
|
|
6
|
+
Download-URL: https://github.com/sepandhaghighi/ghps/tarball/v0.4
|
|
7
7
|
Author: Sepand Haghighi
|
|
8
8
|
Author-email: me@sepand.tech
|
|
9
9
|
License: MIT
|
|
@@ -101,13 +101,13 @@ Dynamic: summary
|
|
|
101
101
|
## Installation
|
|
102
102
|
|
|
103
103
|
### Source Code
|
|
104
|
-
- Download [Version 0.
|
|
104
|
+
- Download [Version 0.4](https://github.com/sepandhaghighi/ghps/archive/v0.4.zip) or [Latest Source](https://github.com/sepandhaghighi/ghps/archive/dev.zip)
|
|
105
105
|
- `pip install .`
|
|
106
106
|
|
|
107
107
|
### PyPI
|
|
108
108
|
|
|
109
109
|
- Check [Python Packaging User Guide](https://packaging.python.org/installing/)
|
|
110
|
-
- `pip install ghps==0.
|
|
110
|
+
- `pip install ghps==0.4`
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
## Usage
|
|
@@ -130,6 +130,9 @@ ghps --port=8080 --no-cache --no-threaded
|
|
|
130
130
|
| `--no-strict` | Enable `.html` fallback| Strict mode enabled |
|
|
131
131
|
| `--no-cache` | Disable HTTP caching headers | Cache enabled |
|
|
132
132
|
| `--no-threaded` | Disable threaded server | Threaded enabled |
|
|
133
|
+
| `--auto-open` | Automatically open the server URL | Disable |
|
|
134
|
+
|
|
135
|
+
ℹ️ You can set `--port=0` to let the OS automatically select an available port
|
|
133
136
|
|
|
134
137
|
|
|
135
138
|
### Library
|
|
@@ -144,6 +147,7 @@ server = GHPageServer(
|
|
|
144
147
|
strict=True,
|
|
145
148
|
no_cache=False,
|
|
146
149
|
threaded=True,
|
|
150
|
+
auto_open=True,
|
|
147
151
|
)
|
|
148
152
|
|
|
149
153
|
server.start()
|
|
@@ -159,6 +163,9 @@ server.start()
|
|
|
159
163
|
| `strict` | `bool` | Enforce GitHub-like routing behavior | `True` |
|
|
160
164
|
| `no_cache` | `bool` | Disable cache headers | `False` |
|
|
161
165
|
| `threaded` | `bool` | Enable threaded request handling | `True` |
|
|
166
|
+
| `auto_open` | `bool` | Automatically open the server URL | `False` |
|
|
167
|
+
|
|
168
|
+
ℹ️ You can set `port=0` to let the OS automatically select an available port
|
|
162
169
|
|
|
163
170
|
## Issues & Bug Reports
|
|
164
171
|
|
|
@@ -209,6 +216,23 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
|
209
216
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
210
217
|
|
|
211
218
|
## [Unreleased]
|
|
219
|
+
## [0.4] - 2026-05-08
|
|
220
|
+
### Added
|
|
221
|
+
- `GHPSError` class
|
|
222
|
+
- `GHPSValidationError` class
|
|
223
|
+
- `GHPSRuntimeError` class
|
|
224
|
+
- Port error messages
|
|
225
|
+
### Changed
|
|
226
|
+
- Test system modified
|
|
227
|
+
## [0.3] - 2026-04-05
|
|
228
|
+
### Added
|
|
229
|
+
- `auto_open` parameter
|
|
230
|
+
- `--auto-open` CLI argument
|
|
231
|
+
- Random port support
|
|
232
|
+
### Changed
|
|
233
|
+
- Test system modified
|
|
234
|
+
- CLI error messages updated
|
|
235
|
+
- `README.md` updated
|
|
212
236
|
## [0.2] - 2026-03-19
|
|
213
237
|
### Added
|
|
214
238
|
- Logo
|
|
@@ -222,7 +246,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
222
246
|
- Command line interface
|
|
223
247
|
- Test system
|
|
224
248
|
|
|
225
|
-
[Unreleased]: https://github.com/sepandhaghighi/ghps/compare/v0.
|
|
249
|
+
[Unreleased]: https://github.com/sepandhaghighi/ghps/compare/v0.4...dev
|
|
250
|
+
[0.4]: https://github.com/sepandhaghighi/ghps/compare/v0.3...v0.4
|
|
251
|
+
[0.3]: https://github.com/sepandhaghighi/ghps/compare/v0.2...v0.3
|
|
226
252
|
[0.2]: https://github.com/sepandhaghighi/ghps/compare/v0.1...v0.2
|
|
227
253
|
[0.1]: https://github.com/sepandhaghighi/ghps/compare/bc0a425...v0.1
|
|
228
254
|
|
{ghps-0.2 → ghps-0.4}/setup.py
RENAMED
|
@@ -32,7 +32,7 @@ def read_description() -> str:
|
|
|
32
32
|
setup(
|
|
33
33
|
name='ghps',
|
|
34
34
|
packages=['ghps'],
|
|
35
|
-
version='0.
|
|
35
|
+
version='0.4',
|
|
36
36
|
description='Ghps: A Minimal GitHub Pages Simulator for Local Development',
|
|
37
37
|
long_description=read_description(),
|
|
38
38
|
long_description_content_type='text/markdown',
|
|
@@ -40,7 +40,7 @@ setup(
|
|
|
40
40
|
author='Sepand Haghighi',
|
|
41
41
|
author_email='me@sepand.tech',
|
|
42
42
|
url='https://github.com/sepandhaghighi/ghps',
|
|
43
|
-
download_url='https://github.com/sepandhaghighi/ghps/tarball/v0.
|
|
43
|
+
download_url='https://github.com/sepandhaghighi/ghps/tarball/v0.4',
|
|
44
44
|
keywords="github gh-pages static-server static-hosting development server local http simulator testing cli python",
|
|
45
45
|
project_urls={
|
|
46
46
|
'Source': 'https://github.com/sepandhaghighi/ghps'
|
|
@@ -110,3 +110,17 @@ def test_cli_invalid_directory(monkeypatch):
|
|
|
110
110
|
|
|
111
111
|
with pytest.raises(SystemExit):
|
|
112
112
|
ghps.cli.main()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@patch("ghps.cli.GHPageServer")
|
|
116
|
+
@patch("sys.stderr")
|
|
117
|
+
def test_cli_unexpected_error(mock_stderr, mock_server_cls):
|
|
118
|
+
mock_server = mock_server_cls.return_value
|
|
119
|
+
mock_server.start.side_effect = Exception("boom")
|
|
120
|
+
test_args = ["ghps"]
|
|
121
|
+
with patch("sys.argv", test_args):
|
|
122
|
+
with pytest.raises(SystemExit) as exc:
|
|
123
|
+
ghps.cli.main()
|
|
124
|
+
assert exc.value.code == 1
|
|
125
|
+
written = "".join(call.args[0] for call in mock_stderr.write.call_args_list)
|
|
126
|
+
assert "[GHPS ERROR] Unexpected error: boom" in written
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import errno
|
|
3
|
+
import pytest
|
|
4
|
+
from unittest.mock import patch
|
|
5
|
+
from ghps import GHPageServer, GHPSValidationError, GHPSRuntimeError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_invalid_directory_type():
|
|
9
|
+
with pytest.raises(GHPSValidationError, match="`directory` must be str or pathlib.Path."):
|
|
10
|
+
GHPageServer(
|
|
11
|
+
directory=123,
|
|
12
|
+
port=8000,
|
|
13
|
+
base_path="",
|
|
14
|
+
strict=True,
|
|
15
|
+
no_cache=False,
|
|
16
|
+
threaded=True,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_directory_not_found():
|
|
21
|
+
with pytest.raises(GHPSValidationError, match="`directory` does not exist."):
|
|
22
|
+
GHPageServer(
|
|
23
|
+
directory="__not_existing_dir__",
|
|
24
|
+
port=8000,
|
|
25
|
+
base_path="",
|
|
26
|
+
strict=True,
|
|
27
|
+
no_cache=False,
|
|
28
|
+
threaded=True,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_directory_not_directory(tmp_path):
|
|
33
|
+
file_path = tmp_path / "file.txt"
|
|
34
|
+
file_path.write_text("data")
|
|
35
|
+
|
|
36
|
+
with pytest.raises(GHPSValidationError, match="`directory` must be a valid directory."):
|
|
37
|
+
GHPageServer(
|
|
38
|
+
directory=file_path,
|
|
39
|
+
port=8000,
|
|
40
|
+
base_path="",
|
|
41
|
+
strict=True,
|
|
42
|
+
no_cache=False,
|
|
43
|
+
threaded=True,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_invalid_port_type(tmp_path):
|
|
48
|
+
with pytest.raises(GHPSValidationError, match="`port` must be int."):
|
|
49
|
+
GHPageServer(
|
|
50
|
+
directory=tmp_path,
|
|
51
|
+
port="8000",
|
|
52
|
+
base_path="",
|
|
53
|
+
strict=True,
|
|
54
|
+
no_cache=False,
|
|
55
|
+
threaded=True,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_invalid_port_range(tmp_path):
|
|
60
|
+
with pytest.raises(GHPSValidationError, match="`port` must be between 1 and 65535."):
|
|
61
|
+
GHPageServer(
|
|
62
|
+
directory=tmp_path,
|
|
63
|
+
port=70000,
|
|
64
|
+
base_path="",
|
|
65
|
+
strict=True,
|
|
66
|
+
no_cache=False,
|
|
67
|
+
threaded=True,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_invalid_base_path_type(tmp_path):
|
|
72
|
+
with pytest.raises(GHPSValidationError, match="`base_path` must be str."):
|
|
73
|
+
GHPageServer(
|
|
74
|
+
directory=tmp_path,
|
|
75
|
+
port=8000,
|
|
76
|
+
base_path=123,
|
|
77
|
+
strict=True,
|
|
78
|
+
no_cache=False,
|
|
79
|
+
threaded=True,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_invalid_base_path_format(tmp_path):
|
|
84
|
+
with pytest.raises(GHPSValidationError, match="`base_path` must start with '/' or be empty."):
|
|
85
|
+
GHPageServer(
|
|
86
|
+
directory=tmp_path,
|
|
87
|
+
port=8000,
|
|
88
|
+
base_path="docs",
|
|
89
|
+
strict=True,
|
|
90
|
+
no_cache=False,
|
|
91
|
+
threaded=True,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_invalid_strict_type(tmp_path):
|
|
96
|
+
with pytest.raises(GHPSValidationError, match="`strict` must be bool."):
|
|
97
|
+
GHPageServer(
|
|
98
|
+
directory=tmp_path,
|
|
99
|
+
port=8000,
|
|
100
|
+
base_path="",
|
|
101
|
+
strict="yes",
|
|
102
|
+
no_cache=False,
|
|
103
|
+
threaded=True,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_invalid_no_cache_type(tmp_path):
|
|
108
|
+
with pytest.raises(GHPSValidationError, match="`no_cache` must be bool."):
|
|
109
|
+
GHPageServer(
|
|
110
|
+
directory=tmp_path,
|
|
111
|
+
port=8000,
|
|
112
|
+
base_path="",
|
|
113
|
+
strict=True,
|
|
114
|
+
no_cache="false",
|
|
115
|
+
threaded=True,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_invalid_threaded_type(tmp_path):
|
|
120
|
+
with pytest.raises(GHPSValidationError, match="`threaded` must be bool."):
|
|
121
|
+
GHPageServer(
|
|
122
|
+
directory=tmp_path,
|
|
123
|
+
port=8000,
|
|
124
|
+
base_path="",
|
|
125
|
+
strict=True,
|
|
126
|
+
no_cache=False,
|
|
127
|
+
threaded="true",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_invalid_auto_open_type(tmp_path):
|
|
132
|
+
with pytest.raises(GHPSValidationError, match="`auto_open` must be bool."):
|
|
133
|
+
GHPageServer(
|
|
134
|
+
directory=tmp_path,
|
|
135
|
+
port=8000,
|
|
136
|
+
base_path="",
|
|
137
|
+
strict=True,
|
|
138
|
+
no_cache=False,
|
|
139
|
+
threaded=True,
|
|
140
|
+
auto_open="true",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@patch("ghps.server.webbrowser.open", side_effect=Exception("boom"))
|
|
145
|
+
@patch("ghps.server._ThreadedTCPServer")
|
|
146
|
+
@patch("builtins.print")
|
|
147
|
+
def test_auto_open_failure_logs_error(mock_print, mock_server_cls, mock_web_open, tmp_path):
|
|
148
|
+
mock_server = mock_server_cls.return_value
|
|
149
|
+
mock_server.server_address = ("127.0.0.1", 8000)
|
|
150
|
+
mock_server.serve_forever.side_effect = KeyboardInterrupt
|
|
151
|
+
server = GHPageServer(
|
|
152
|
+
directory=tmp_path,
|
|
153
|
+
port=8000,
|
|
154
|
+
auto_open=True,
|
|
155
|
+
)
|
|
156
|
+
server.start()
|
|
157
|
+
mock_web_open.assert_called_once_with("http://localhost:8000")
|
|
158
|
+
assert any(
|
|
159
|
+
"[GHPS ERROR]: Failed to open browser automatically" in str(call)
|
|
160
|
+
for call in mock_print.call_args_list
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@patch("ghps.server._ThreadedTCPServer")
|
|
165
|
+
def test_port_in_use_error(mock_server_cls, tmp_path):
|
|
166
|
+
mock_server_cls.side_effect = OSError(errno.EADDRINUSE, "in use")
|
|
167
|
+
|
|
168
|
+
server = GHPageServer(directory=tmp_path, port=8000)
|
|
169
|
+
|
|
170
|
+
with pytest.raises(GHPSRuntimeError) as exc:
|
|
171
|
+
server.start()
|
|
172
|
+
|
|
173
|
+
assert "`port` is already in use. Try another port or use 0." in str(exc.value)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@patch("ghps.server._ThreadedTCPServer")
|
|
177
|
+
def test_port_access_denied_error(mock_server_cls, tmp_path):
|
|
178
|
+
mock_server_cls.side_effect = OSError(errno.EACCES, "denied")
|
|
179
|
+
|
|
180
|
+
server = GHPageServer(directory=tmp_path, port=80)
|
|
181
|
+
|
|
182
|
+
with pytest.raises(GHPSRuntimeError) as exc:
|
|
183
|
+
server.start()
|
|
184
|
+
|
|
185
|
+
assert "`port` access denied. Try a different port or check permissions." in str(exc.value)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@patch("ghps.server._ThreadedTCPServer")
|
|
189
|
+
def test_port_generic_error(mock_server_cls, tmp_path):
|
|
190
|
+
mock_server_cls.side_effect = OSError(9999, "boom")
|
|
191
|
+
|
|
192
|
+
server = GHPageServer(directory=tmp_path, port=8000)
|
|
193
|
+
|
|
194
|
+
with pytest.raises(GHPSRuntimeError) as exc:
|
|
195
|
+
server.start()
|
|
196
|
+
|
|
197
|
+
assert "Failed to start server on port" in str(exc.value)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@patch("ghps.server._ThreadedTCPServer")
|
|
201
|
+
def test_port_address_not_available_error(mock_server_cls, tmp_path):
|
|
202
|
+
mock_server_cls.side_effect = OSError(errno.EADDRNOTAVAIL, "not-available")
|
|
203
|
+
|
|
204
|
+
server = GHPageServer(directory=tmp_path, port=80)
|
|
205
|
+
|
|
206
|
+
with pytest.raises(GHPSRuntimeError) as exc:
|
|
207
|
+
server.start()
|
|
208
|
+
|
|
209
|
+
assert "`port` address is not available. Check network configuration." in str(exc.value)
|
|
@@ -128,3 +128,52 @@ def test_keyboard_interrupt(capsys):
|
|
|
128
128
|
|
|
129
129
|
captured = capsys.readouterr()
|
|
130
130
|
assert "Server stopped." in captured.out
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@patch("ghps.server.webbrowser.open")
|
|
134
|
+
@patch("ghps.server._ThreadedTCPServer")
|
|
135
|
+
def test_auto_open_enabled(mock_server_cls, mock_web_open, tmp_path):
|
|
136
|
+
mock_server = mock_server_cls.return_value
|
|
137
|
+
mock_server.server_address = ("127.0.0.1", 8000)
|
|
138
|
+
mock_server.serve_forever.side_effect = KeyboardInterrupt
|
|
139
|
+
|
|
140
|
+
server = GHPageServer(
|
|
141
|
+
directory=tmp_path,
|
|
142
|
+
port=8000,
|
|
143
|
+
auto_open=True,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
server.start()
|
|
147
|
+
|
|
148
|
+
mock_web_open.assert_called_once_with("http://localhost:8000")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@patch("ghps.server.webbrowser.open")
|
|
152
|
+
@patch("ghps.server._ThreadedTCPServer")
|
|
153
|
+
def test_auto_open_disabled(mock_server_cls, mock_web_open, tmp_path):
|
|
154
|
+
mock_server = mock_server_cls.return_value
|
|
155
|
+
mock_server.server_address = ("127.0.0.1", 8000)
|
|
156
|
+
mock_server.serve_forever.side_effect = KeyboardInterrupt
|
|
157
|
+
|
|
158
|
+
server = GHPageServer(
|
|
159
|
+
directory=tmp_path,
|
|
160
|
+
port=8000,
|
|
161
|
+
auto_open=False,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
server.start()
|
|
165
|
+
|
|
166
|
+
mock_web_open.assert_not_called()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@patch("ghps.server._ThreadedTCPServer")
|
|
170
|
+
def test_random_port_resolution(mock_server_cls, tmp_path):
|
|
171
|
+
mock_server = mock_server_cls.return_value
|
|
172
|
+
mock_server.server_address = ("127.0.0.1", 54321)
|
|
173
|
+
mock_server.serve_forever.side_effect = KeyboardInterrupt
|
|
174
|
+
server = GHPageServer(
|
|
175
|
+
directory=tmp_path,
|
|
176
|
+
port=0,
|
|
177
|
+
)
|
|
178
|
+
server.start()
|
|
179
|
+
assert server._port == 54321
|
ghps-0.2/ghps/__init__.py
DELETED
ghps-0.2/tests/test_error.py
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
import pytest
|
|
3
|
-
from ghps import GHPageServer
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_invalid_directory_type():
|
|
7
|
-
with pytest.raises(ValueError, match="`directory` must be str or pathlib.Path."):
|
|
8
|
-
GHPageServer(
|
|
9
|
-
directory=123,
|
|
10
|
-
port=8000,
|
|
11
|
-
base_path="",
|
|
12
|
-
strict=True,
|
|
13
|
-
no_cache=False,
|
|
14
|
-
threaded=True,
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def test_directory_not_found():
|
|
19
|
-
with pytest.raises(ValueError, match="`directory` does not exist."):
|
|
20
|
-
GHPageServer(
|
|
21
|
-
directory="__not_existing_dir__",
|
|
22
|
-
port=8000,
|
|
23
|
-
base_path="",
|
|
24
|
-
strict=True,
|
|
25
|
-
no_cache=False,
|
|
26
|
-
threaded=True,
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def test_directory_not_directory(tmp_path):
|
|
31
|
-
file_path = tmp_path / "file.txt"
|
|
32
|
-
file_path.write_text("data")
|
|
33
|
-
|
|
34
|
-
with pytest.raises(ValueError, match="`directory` must be a valid directory."):
|
|
35
|
-
GHPageServer(
|
|
36
|
-
directory=file_path,
|
|
37
|
-
port=8000,
|
|
38
|
-
base_path="",
|
|
39
|
-
strict=True,
|
|
40
|
-
no_cache=False,
|
|
41
|
-
threaded=True,
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def test_invalid_port_type(tmp_path):
|
|
46
|
-
with pytest.raises(ValueError, match="`port` must be int."):
|
|
47
|
-
GHPageServer(
|
|
48
|
-
directory=tmp_path,
|
|
49
|
-
port="8000",
|
|
50
|
-
base_path="",
|
|
51
|
-
strict=True,
|
|
52
|
-
no_cache=False,
|
|
53
|
-
threaded=True,
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def test_invalid_port_range(tmp_path):
|
|
58
|
-
with pytest.raises(ValueError, match="`port` must be between 1 and 65535."):
|
|
59
|
-
GHPageServer(
|
|
60
|
-
directory=tmp_path,
|
|
61
|
-
port=70000,
|
|
62
|
-
base_path="",
|
|
63
|
-
strict=True,
|
|
64
|
-
no_cache=False,
|
|
65
|
-
threaded=True,
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def test_invalid_base_path_type(tmp_path):
|
|
70
|
-
with pytest.raises(ValueError, match="`base_path` must be str."):
|
|
71
|
-
GHPageServer(
|
|
72
|
-
directory=tmp_path,
|
|
73
|
-
port=8000,
|
|
74
|
-
base_path=123,
|
|
75
|
-
strict=True,
|
|
76
|
-
no_cache=False,
|
|
77
|
-
threaded=True,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def test_invalid_base_path_format(tmp_path):
|
|
82
|
-
with pytest.raises(ValueError, match="`base_path` must start with '/' or be empty."):
|
|
83
|
-
GHPageServer(
|
|
84
|
-
directory=tmp_path,
|
|
85
|
-
port=8000,
|
|
86
|
-
base_path="docs",
|
|
87
|
-
strict=True,
|
|
88
|
-
no_cache=False,
|
|
89
|
-
threaded=True,
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def test_invalid_strict_type(tmp_path):
|
|
94
|
-
with pytest.raises(ValueError, match="`strict` must be bool."):
|
|
95
|
-
GHPageServer(
|
|
96
|
-
directory=tmp_path,
|
|
97
|
-
port=8000,
|
|
98
|
-
base_path="",
|
|
99
|
-
strict="yes",
|
|
100
|
-
no_cache=False,
|
|
101
|
-
threaded=True,
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def test_invalid_no_cache_type(tmp_path):
|
|
106
|
-
with pytest.raises(ValueError, match="`no_cache` must be bool."):
|
|
107
|
-
GHPageServer(
|
|
108
|
-
directory=tmp_path,
|
|
109
|
-
port=8000,
|
|
110
|
-
base_path="",
|
|
111
|
-
strict=True,
|
|
112
|
-
no_cache="false",
|
|
113
|
-
threaded=True,
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def test_invalid_threaded_type(tmp_path):
|
|
118
|
-
with pytest.raises(ValueError, match="`threaded` must be bool."):
|
|
119
|
-
GHPageServer(
|
|
120
|
-
directory=tmp_path,
|
|
121
|
-
port=8000,
|
|
122
|
-
base_path="",
|
|
123
|
-
strict=True,
|
|
124
|
-
no_cache=False,
|
|
125
|
-
threaded="true",
|
|
126
|
-
)
|
{ghps-0.2 → ghps-0.4}/AUTHORS.md
RENAMED
|
File without changes
|
{ghps-0.2 → ghps-0.4}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ghps-0.2 → ghps-0.4}/setup.cfg
RENAMED
|
File without changes
|