From 68128b5dc6ab86888ab9ad2a6fd57dfd91b83408 Mon Sep 17 00:00:00 2001 From: evilchili Date: Fri, 24 Nov 2023 10:01:33 -0500 Subject: [PATCH] docs, modifying cli to use language packs --- README.md | 112 +++++++++++++++++++++++++++++---- language/__init__.py | 27 +++++++- language/cli.py | 43 ++++++++++--- language/languages/__init__.py | 14 ----- 4 files changed, 162 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 408e3c6..9f20516 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,84 @@ -# D&D Language Generator +# D&D Name and Language Generator This package is a fantasy language generator. By defining a number of characteristics about your imagined language -- the graphemes, their relative frequency distributions, the construction of syllables, and so on -- you can generate random but internally consistent gibberish that feels distinct, evocative, and appropriate to your setting. -## Quick Start +## Usage + +The `fanlang` command-line utility supports three commands: + +* **names**: generate names +* **text**: generate a paragraph of text +* **list**: list the supported language in the current language pack + +### Examples: ``` ->>> from language imported supported_languages +% fanlang --language=dwarvish names --count 5 +Tiny Châ Pothesadottyr +Khâkhu Zhûdothir +Quiet Ke Vêdothir +Cû Tozhon +Big Pâ Thadottyr +``` + +``` +% fanlang --language=dwarvish text +Cû ne do tho khâ tasha, vê wûva lû, ku phu thâ thê, tûko kê, pevo kâ têtetv zha +pataso keks khate? Fâ zhû shû yf pho pa me. Dupha dê thê khâ! Shikm tu! Cê +sâdêto. Dê yo nâ topho, my sû pida phe, vi phûtw châcho, po sotê? +``` + +``` +% fanlang list +Abyssal +Celestial +Common +Draconic +Dwarvish +Elvish +Gnomish +Halfling +Infernal +Lizardfolk +Orcish +Undercommon +``` + +## Language Packs + +A *Language Pack* is a python package that defines one or more language modules. The default language pack includes a number of D&D languages with rules built according to the conventions established by my D&D group over several years of play in our homebrew setting. + +The default language pack is [language.languages](language/languages/); each submodule contains a README that describes the basic characteristics of the language, along with examples. + +### Using Your Own Language Pack + +You can override `fanlang`'s default language pack by specifying the `FANLANG_LANGUAGE_PACK` environment variable: + +``` +# Create your ancient_elvish module in campaign/language_pack/ancient_elvish +% FANLANG_LANGUAGE_PACK=campaign.language_pack fanlang list +Ancient Elvish +`` + +### Setting the Default Language + +'common' is the default language module. You can override this by setting the `FANLANG_DEFAULT_LANGUAGE` environment variable: + +``` +% FANLANG_DEFAULT_LANGUAGE=gnomish fanlang names --count=1 +Jey Lea +``` + +You can read about creating custom language packs below. + + +## Library Quick Start + +You can load all supported languages in a language pack using `language.load_langauge_pack()`: + +``` +>>> import language +>>> language_pack, supported_languages = language.load_language_pack() >>> common = supported_languages['common'] >>> common.word(2) ['apsoo', 'nirtoet'] @@ -21,23 +94,38 @@ Proitsiiiy be itkif eesof detytaen. Ojaot tyskuaz apsoo nirtoet prenao. } ``` -## Supported Languages +You can also load individual languages directly: -A number of D&D languages are defined, with rules built according to the -conventions established by my D&D group over several years of play in our -homebrew setting. You can find all supported languages [in the languages -submodule](language/languages/); each submodule contains a README that -describes the basic characteristics of the language, along with examples. +``` +>>> from language.languages import common +>>> common.Language.word(2) +['apsoo', 'nirtoet'] +>>> str(common.Name) +"Quiet" Gushi Murk Lirpusome +``` -## Defining a Language +## Defining a New Language Pack -### Layout +Language packs are python packages with the following structure: + +``` +language_pack: + __init__.py + language_name: + __init__.py + base.py + names.py + rules.py + ... +``` + +### Languge Modules A language consists of several submodules: * `base.py`, which contains grapheme definitions and the `Language` subclasses; * `names.py`, which defines the `NameGenerator` subclasses; and -* `rules.py`, which is optional but defines the rules all words in the language must follow. +* `rules.py`, which is optional, and defines the rules all words in the language must follow. ### Language Construction diff --git a/language/__init__.py b/language/__init__.py index edf123a..4363b72 100644 --- a/language/__init__.py +++ b/language/__init__.py @@ -1 +1,26 @@ -from .languages import supported_languages +import importlib +import os +import pkgutil +import sys + +from types import ModuleType + +language_pack = None +supported_languages = None + + +def _import_submodules(module): + pkgs = pkgutil.iter_modules(module.__path__) + for loader, module_name, is_pkg in pkgs: + yield importlib.import_module(f"{module.__name__}.{module_name}") + + +def load_language_pack(module_name: str = "") -> ModuleType: + if not module_name: + module_name = os.getenv("FANLANG_LANGUAGE_PACK", "language.languages") + language_pack = importlib.import_module(module_name) + _import_submodules(language_pack) + supported_languages = dict( + (module.__name__.split(".")[-1], module) for module in list(_import_submodules(sys.modules[module_name])) + ) + return language_pack, supported_languages diff --git a/language/cli.py b/language/cli.py index 2769f64..d006247 100644 --- a/language/cli.py +++ b/language/cli.py @@ -1,20 +1,23 @@ import logging import os +import random from enum import Enum from types import ModuleType +import language import typer from rich.logging import RichHandler from rich.console import Console from rich.markdown import Markdown -from language import supported_languages - app = typer.Typer() app_state = {} +default_language = os.environ.get("FANLANG_DEFAULT_LANGUAGE", "common") +language_pack, supported_languages = language.load_language_pack() + Supported = Enum("Supported", ((k, k) for k in supported_languages.keys())) @@ -34,21 +37,47 @@ def print_sample(lang: str, module: ModuleType) -> None: @app.callback() def main( - language: Supported = typer.Option("common", help="The language to use."), + language: Supported = typer.Option( + default=default_language, + help="The language to use." + ), ): app_state["language"] = supported_languages[language.name] - debug = os.getenv("DEBUG", None) + debug = os.getenv("FANLANG_DEBUG", None) logging.basicConfig( - format="%(message)s", + format="%(name)s %(message)s", level=logging.DEBUG if debug else logging.INFO, handlers=[RichHandler(rich_tracebacks=True, tracebacks_suppress=[typer])], ) + logging.getLogger('markdown_it').setLevel(logging.ERROR) + logging.debug(f"Loaded language pack {language_pack}.") + logging.debug(f"Default language: {default_language}.") @app.command() -def words(count: int = typer.Option(50, help="The number of words to generate.")): - print(" ".join(list(app_state["language"].Language.word(count)))) +def text(count: int = typer.Option(50, help="The number of words to generate.")): + + phrases = [] + phrase = [] + for word in app_state["language"].Language.word(count): + phrase.append(str(word)) + if len(phrase) >= random.randint(1, 12): + phrases.append(' '.join(phrase)) + phrase = [] + if phrase: + phrases.append(' '.join(phrase)) + + paragraph = phrases[0].capitalize() + for phrase in phrases[1:]: + if random.choice([0, 0, 1]): + paragraph = paragraph + random.choice('?!.') + ' ' + phrase.capitalize() + else: + paragraph = paragraph + ', ' + phrase + paragraph = paragraph + random.choice('?!.') + + console = Console(width=80) + console.print(paragraph) @app.command() diff --git a/language/languages/__init__.py b/language/languages/__init__.py index 14c802a..e69de29 100644 --- a/language/languages/__init__.py +++ b/language/languages/__init__.py @@ -1,14 +0,0 @@ -import importlib -import pkgutil -import sys - - -def import_submodules(module): - pkgs = pkgutil.iter_modules(module.__path__) - for loader, module_name, is_pkg in pkgs: - yield importlib.import_module(f"{module.__name__}.{module_name}") - - -supported_languages = dict( - (module.__name__.split(".")[-1], module) for module in list(import_submodules(sys.modules[__name__])) -)