Source code for betty.cli.commands.new

from __future__ import annotations  # noqa D100

from typing import TYPE_CHECKING, final, Self, Any
from urllib.parse import urlparse

import asyncclick as click
from typing_extensions import override

from betty.app.factory import AppDependentFactory
from betty.assertion import assert_str, assert_path, assert_locale
from betty.cli.commands import command, Command
from betty.cli.error import user_facing_error_to_bad_parameter
from betty.config import write_configuration_file
from betty.locale import get_display_name, DEFAULT_LOCALE
from betty.locale.localizable import _, StaticTranslations
from betty.machine_name import machinify, assert_machine_name
from betty.plugin import ShorthandPluginBase
from betty.plugin.config import PluginInstanceConfiguration
from betty.project.config import LocaleConfiguration, ProjectConfiguration
from betty.project.extension.deriver import Deriver
from betty.project.extension.gramps import Gramps
from betty.project.extension.gramps.config import (
    FamilyTreeConfiguration,
    GrampsConfiguration,
)
from betty.project.extension.http_api_doc import HttpApiDoc
from betty.project.extension.maps import Maps
from betty.project.extension.privatizer import Privatizer
from betty.project.extension.raspberry_mint import RaspberryMint
from betty.project.extension.trees import Trees
from betty.project.extension.webpack import Webpack
from betty.project.extension.wikipedia import Wikipedia

if TYPE_CHECKING:
    from pathlib import Path
    from collections.abc import Sequence, Callable
    from betty.app import App


[docs] @final class New(ShorthandPluginBase, AppDependentFactory, Command): """ A command to create a new project. """ _plugin_id = "new" _plugin_label = _("Create a new project")
[docs] def __init__(self, app: App): self._app = app
[docs] @override @classmethod async def new_for_app(cls, app: App) -> Self: return cls(app)
[docs] @override async def click_command(self) -> click.Command: localizer = await self._app.localizer description = self.plugin_description() @command( self.plugin_id(), short_help=self.plugin_label().localize(localizer), help=description.localize(localizer) if description else self.plugin_label().localize(localizer), ) async def new() -> None: configuration_file_path = click.prompt( localizer._( "Where do you want to save your project's configuration file?" ), value_proc=user_facing_error_to_bad_parameter(localizer)( _assert_project_configuration_file_path ), ) configuration = await ProjectConfiguration.new( configuration_file_path, ) configuration.extensions.enable( Deriver, Privatizer, RaspberryMint, Wikipedia ) webpack_requirement = await Webpack.requirement() if webpack_requirement.is_met(): configuration.extensions.enable(HttpApiDoc, Maps, Trees) configuration.locales.replace( LocaleConfiguration( click.prompt( localizer._( "Which language should your project site be generated in? Enter an IETF BCP 47 language code." ), default=DEFAULT_LOCALE, value_proc=user_facing_error_to_bad_parameter(localizer)( assert_locale() ), ) ) ) while click.confirm(localizer._("Do you want to add another locale?")): configuration.locales.append( LocaleConfiguration( click.prompt( localizer._( "Which language should your project site be generated in? Enter an IETF BCP 47 language code." ), value_proc=user_facing_error_to_bad_parameter(localizer)( assert_locale() ), ) ) ) locales = list(configuration.locales) configuration.title = _prompt_static_translations( locales, localizer._("What is your project called in {locale}?"), ) configuration.name = click.prompt( localizer._("What is your project's machine name?"), default=machinify( configuration.title.localize( await self._app.localizers.get( configuration.locales.default.locale ) ) ), value_proc=user_facing_error_to_bad_parameter(localizer)( assert_machine_name() ), ) configuration.author = _prompt_static_translations( locales, localizer._("What is the project author called in {locale}?"), ) configuration.url = click.prompt( localizer._("At which URL will your site be published?"), default="https://example.com", value_proc=user_facing_error_to_bad_parameter(localizer)(_assert_url), ) if click.confirm(localizer._("Do you want to load a Gramps family tree?")): configuration.extensions.append( PluginInstanceConfiguration( Gramps, configuration=GrampsConfiguration( family_trees=[ FamilyTreeConfiguration( click.prompt( localizer._( "What is the path to your exported Gramps family tree file?" ), value_proc=user_facing_error_to_bad_parameter( localizer )(assert_path()), ) ) ] ), ) ) await write_configuration_file( configuration, configuration.configuration_file_path ) click.secho( localizer._("Saved your project to {configuration_file}.").format( configuration_file=str(configuration_file_path) ), fg="green", ) return new
def _assert_project_configuration_file_path(value: Any) -> Path: configuration_file_path = assert_path()(value) if not configuration_file_path.suffix: configuration_file_path /= "betty.yaml" return configuration_file_path def _assert_url(value: Any) -> str: url = assert_str()(value) parsed_url = urlparse(url) scheme = parsed_url.scheme or "http" return f"{scheme}://{parsed_url.netloc}{parsed_url.path}" def _prompt_static_translations( locales: Sequence[str], text: str, default: Any | None = None, hide_input: bool = False, confirmation_prompt: bool | str = False, type: click.ParamType | Any | None = None, # noqa A002 value_proc: Callable[[str], Any] | None = None, prompt_suffix: str = ": ", show_default: bool = True, err: bool = False, show_choices: bool = True, ) -> StaticTranslations: return { locale: click.prompt( text.format(locale=get_display_name(locale)), default, hide_input, confirmation_prompt, type, value_proc, prompt_suffix, show_default, err, show_choices, ) for locale in locales }