"""Integrate Betty with `nginx <https://nginx.org/>`_."""
from collections.abc import Sequence
from pathlib import Path
from typing import Any, Self
from PyQt6.QtWidgets import (
QFormLayout,
QButtonGroup,
QRadioButton,
QWidget,
QHBoxLayout,
QLineEdit,
QFileDialog,
QPushButton,
)
from betty.app import App
from betty.app.extension import ConfigurableExtension
from betty.config import Configuration
from betty.extension.nginx.artifact import (
generate_configuration_file,
generate_dockerfile_file,
)
from betty.generate import Generator, GenerationContext
from betty.gui import GuiBuilder
from betty.gui.error import ExceptionCatcher
from betty.locale import Str
from betty.serde.dump import Dump, VoidableDump, minimize, Void, VoidableDictDump
from betty.serde.load import Asserter, Fields, OptionalField, Assertions
from betty.serve import ServerProvider, Server
[docs]
class NginxConfiguration(Configuration):
def __init__(
self,
*,
www_directory_path: str | None = None,
https: bool | None = None,
):
super().__init__()
self._https = https
self.www_directory_path = www_directory_path
@property
def https(self) -> bool | None:
return self._https
@https.setter
def https(self, https: bool | None) -> None:
self._https = https
self._dispatch_change()
@property
def www_directory_path(self) -> str | None:
return self._www_directory_path
@www_directory_path.setter
def www_directory_path(self, www_directory_path: str | None) -> None:
self._www_directory_path = www_directory_path
self._dispatch_change()
[docs]
def update(self, other: Self) -> None:
self._https = other._https
self._www_directory_path = other._www_directory_path
self._dispatch_change()
[docs]
@classmethod
def load(
cls,
dump: Dump,
configuration: Self | None = None,
) -> Self:
if configuration is None:
configuration = cls()
asserter = Asserter()
asserter.assert_record(
Fields(
OptionalField(
"https",
Assertions(
asserter.assert_or(
asserter.assert_bool(), asserter.assert_none()
)
)
| asserter.assert_setattr(configuration, "https"),
),
OptionalField(
"www_directory_path",
Assertions(asserter.assert_str())
| asserter.assert_setattr(configuration, "www_directory_path"),
),
)
)(dump)
return configuration
[docs]
def dump(self) -> VoidableDump:
dump: VoidableDictDump[VoidableDump] = {
"https": self.https,
"www_directory_path": (
Void
if self.www_directory_path is None
else str(self.www_directory_path)
),
}
return minimize(dump, True)
[docs]
class Nginx(
ConfigurableExtension[NginxConfiguration], Generator, ServerProvider, GuiBuilder
):
[docs]
@classmethod
def label(cls) -> Str:
return Str.plain("Nginx")
[docs]
@classmethod
def description(cls) -> Str:
return Str._(
'Generate <a href="">nginx</a> configuration for your site, as well as a <code>Dockerfile</code> to build a <a href="https://www.docker.com/">Docker</a> container around it.'
)
[docs]
@classmethod
def default_configuration(cls) -> NginxConfiguration:
return NginxConfiguration()
@property
def servers(self) -> Sequence[Server]:
from betty.extension.nginx.serve import DockerizedNginxServer
if DockerizedNginxServer.is_available():
return [DockerizedNginxServer(self._app)]
return []
[docs]
async def generate(self, job_context: GenerationContext) -> None:
await generate_configuration_file(self._app)
await generate_dockerfile_file(self._app)
[docs]
@classmethod
def assets_directory_path(cls) -> Path | None:
return Path(__file__).parent / "assets"
@property
def https(self) -> bool:
if self._configuration.https is None:
return self._app.project.configuration.base_url.startswith("https")
return self._configuration.https
@property
def www_directory_path(self) -> str:
return self._configuration.www_directory_path or str(
self._app.project.configuration.www_directory_path
)
[docs]
def gui_build(self) -> QWidget:
return _NginxGuiWidget(self._app, self._configuration)
class _NginxGuiWidget(QWidget):
def __init__(
self, app: App, configuration: NginxConfiguration, *args: Any, **kwargs: Any
):
super().__init__(*args, **kwargs)
self._app = app
self._configuration = configuration
layout = QFormLayout()
self.setLayout(layout)
https_button_group = QButtonGroup()
def _update_configuration_https_base_url(checked: bool) -> None:
if checked:
self._configuration.https = None
self._nginx_https_base_url = QRadioButton(
"Use HTTPS and HTTP/2 if the site's URL starts with https://"
)
self._nginx_https_base_url.setChecked(self._configuration.https is None)
self._nginx_https_base_url.toggled.connect(_update_configuration_https_base_url)
layout.addRow(self._nginx_https_base_url)
https_button_group.addButton(self._nginx_https_base_url)
def _update_configuration_https_https(checked: bool) -> None:
if checked:
self._configuration.https = True
self._nginx_https_https = QRadioButton("Use HTTPS and HTTP/2")
self._nginx_https_https.setChecked(self._configuration.https is True)
self._nginx_https_https.toggled.connect(_update_configuration_https_https)
layout.addRow(self._nginx_https_https)
https_button_group.addButton(self._nginx_https_https)
def _update_configuration_https_http(checked: bool) -> None:
if checked:
self._configuration.https = False
self._nginx_https_http = QRadioButton("Use HTTP")
self._nginx_https_http.setChecked(self._configuration.https is False)
self._nginx_https_http.toggled.connect(_update_configuration_https_http)
layout.addRow(self._nginx_https_http)
https_button_group.addButton(self._nginx_https_http)
def _update_configuration_www_directory_path(www_directory_path: str) -> None:
self._configuration.www_directory_path = (
None
if www_directory_path == ""
or www_directory_path
== str(self._app.project.configuration.www_directory_path)
else www_directory_path
)
self._nginx_www_directory_path = QLineEdit()
self._nginx_www_directory_path.setText(
str(self._configuration.www_directory_path)
if self._configuration.www_directory_path is not None
else str(self._app.project.configuration.www_directory_path)
)
self._nginx_www_directory_path.textChanged.connect(
_update_configuration_www_directory_path
)
www_directory_path_layout = QHBoxLayout()
www_directory_path_layout.addWidget(self._nginx_www_directory_path)
def find_www_directory_path() -> None:
with ExceptionCatcher(self):
found_www_directory_path = QFileDialog.getExistingDirectory(
self,
"Serve your site from...",
directory=self._nginx_www_directory_path.text(),
)
if "" != found_www_directory_path:
self._nginx_www_directory_path.setText(found_www_directory_path)
self._nginx_www_directory_path_find = QPushButton("...")
self._nginx_www_directory_path_find.released.connect(find_www_directory_path)
www_directory_path_layout.addWidget(self._nginx_www_directory_path_find)
layout.addRow("WWW directory", www_directory_path_layout)