132 lines
3 KiB
Python
Executable file
132 lines
3 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
from datetime import datetime
|
|
from http.server import HTTPServer, ThreadingHTTPServer, SimpleHTTPRequestHandler
|
|
from pathlib import Path
|
|
from watchdog.events import FileSystemEventHandler
|
|
from watchdog.observers.polling import PollingObserver
|
|
|
|
docpath = Path(__file__).resolve().parent
|
|
|
|
|
|
class DocWatcher(FileSystemEventHandler):
|
|
proc = None
|
|
last_restart = 0
|
|
|
|
def __init__(self, path):
|
|
self.path = str(path)
|
|
|
|
|
|
def on_any_event(self, event):
|
|
if event.event_type not in ['modified', 'created', 'deleted']:
|
|
return
|
|
|
|
path = Path(event.src_path)
|
|
directory = str(path.parent).replace(self.path, '')
|
|
|
|
if not path.suffix or not directory:
|
|
return
|
|
|
|
if directory.startswith('/docs/_') or str(path).endswith('/docs/build.py'):
|
|
return
|
|
|
|
if path.suffix[1:] not in {'py', 'rst', 'yml'}:
|
|
return
|
|
|
|
self.run_proc()
|
|
|
|
|
|
def run_proc(self):
|
|
timestamp = datetime.timestamp(datetime.now())
|
|
|
|
if self.last_restart != None and timestamp - 3 < self.last_restart:
|
|
print(timestamp - 3 < self.last_restart, timestamp -3, self.last_restart)
|
|
return
|
|
|
|
self.last_restart = timestamp
|
|
build_docs(False)
|
|
|
|
|
|
class ServerHandler(SimpleHTTPRequestHandler):
|
|
def send_response(self, code, message=None):
|
|
if any(map(self.path.endswith, ['/', '.html'])):
|
|
agent = self.headers.get('User-Agent', 'n/a')
|
|
sys.stdout.write(f'\n{self.address_string()} {self.path} {code} "{agent}"')
|
|
sys.stdout.flush()
|
|
|
|
self.send_response_only(code, message)
|
|
self.send_header('Server', self.version_string())
|
|
self.send_header('Date', self.date_time_string())
|
|
|
|
|
|
class Server(ThreadingHTTPServer):
|
|
def __init__(self, host, port, directory):
|
|
ThreadingHTTPServer.__init__(self, (host, port), SimpleHTTPRequestHandler)
|
|
self.directory = directory
|
|
|
|
|
|
def finish_request(self, request, client_address):
|
|
ServerHandler(request, client_address, self, directory=self.directory)
|
|
|
|
|
|
def run(self):
|
|
with self as httpd:
|
|
host, port = httpd.socket.getsockname()[:2]
|
|
print(f'Serving HTTP on {host} port {port} (http://{host}:{port}/)')
|
|
|
|
try:
|
|
httpd.serve_forever()
|
|
|
|
except KeyboardInterrupt:
|
|
print('\nKeyboard interrupt received, exiting.')
|
|
return 0
|
|
|
|
except Exception as e:
|
|
print(f'\nError running server: {e.__class__.__name__}: {e}')
|
|
|
|
print('Bye! :3')
|
|
|
|
|
|
def build_docs(wait=False):
|
|
proc = subprocess.Popen([sys.executable, '-m', 'sphinx', '-M', 'html', str(docpath), docpath.joinpath('_build')])
|
|
|
|
if wait:
|
|
while proc.poll() == None:
|
|
time.sleep(0.1)
|
|
|
|
return proc
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print('Must include an option: build, serve')
|
|
return 1
|
|
|
|
arg = sys.argv[1]
|
|
|
|
if arg == 'serve':
|
|
build_docs(False)
|
|
s = Server('0.0.0.0', 8080, docpath.joinpath('_build/html'))
|
|
|
|
watcher = PollingObserver()
|
|
watcher.schedule(DocWatcher(docpath.parent), docpath.parent, recursive=True)
|
|
watcher.start()
|
|
|
|
s.run()
|
|
watcher.stop()
|
|
return 0
|
|
|
|
elif arg == 'build':
|
|
return build_docs().returncode
|
|
|
|
print(f'Invalid option: {arg}')
|
|
print('Valid options: build, serve')
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|