diff --git a/src/textual_paint/args.py b/src/textual_paint/args.py new file mode 100644 index 0000000..08fc8fb --- /dev/null +++ b/src/textual_paint/args.py @@ -0,0 +1,76 @@ +"""Command line arguments for the app.""" + +import argparse +import os +import re + +from .__init__ import __version__ + +parser = argparse.ArgumentParser(description='Paint in the terminal.', usage='%(prog)s [options] [filename]', prog="textual-paint") +parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}') +parser.add_argument('--theme', default='light', help='Theme to use, either "light" or "dark"', choices=['light', 'dark']) +parser.add_argument('--language', default='en', help='Language to use', choices=['ar', 'cs', 'da', 'de', 'el', 'en', 'es', 'fi', 'fr', 'he', 'hu', 'it', 'ja', 'ko', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ru', 'sk', 'sl', 'sv', 'tr', 'zh', 'zh-simplified']) +parser.add_argument('--ascii-only-icons', action='store_true', help='Use only ASCII characters for tool icons, no emoji or other Unicode symbols') +parser.add_argument('--ascii-only', action='store_true', help='Use only ASCII characters for the entire UI, for use in older terminals. Implies --ascii-only-icons') +parser.add_argument('--backup-folder', default=None, metavar="FOLDER", help='Folder to save backups to. By default a backup is saved alongside the edited file.') + +# TODO: hide development options from help? there's quite a few of them now +parser.add_argument('--inspect-layout', action='store_true', help='Enables DOM inspector (F12) and middle click highlight, for development') +# This flag is for development, because it's very confusing +# to see the error message from the previous run, +# when a problem is actually solved. +# There are enough ACTUAL "that should have worked!!" moments to deal with. +# I really don't want false ones mixed in. You want to reward your brain for finding good solutions, after all. +parser.add_argument('--clear-screen', action='store_true', help='Clear the screen before starting; useful for development, to avoid seeing outdated errors') +parser.add_argument('--restart-on-changes', action='store_true', help='Restart the app when the source code is changed, for development') +parser.add_argument('--recode-samples', action='store_true', help='Open and save each file in samples/, for testing') + +parser.add_argument('filename', nargs='?', default=None, help='Path to a file to open. File will be created if it doesn\'t exist.') + +def update_cli_help_on_readme(): + """Update the readme with the current CLI usage info""" + readme_help_start = re.compile(r"```\n.*--help\n") + readme_help_end = re.compile(r"```") + readme_file_path = os.path.join(os.path.dirname(__file__), "../../README.md") + with open(readme_file_path, "r+", encoding="utf-8") as f: + # By default, argparse uses the terminal width for formatting help text, + # even when using format_help() to get a string. + # The only way to override that is to override the formatter_class. + # This is hacky, but it seems like the simplest way to fix the width + # without creating a separate ArgumentParser, and without breaking the wrapping for --help. + # This lambda works because python uses the same syntax for construction and function calls, + # so formatter_class doesn't need to be an actual class. + # See: https://stackoverflow.com/questions/44333577/explain-lambda-argparse-helpformatterprog-width + width = 80 + old_formatter_class = parser.formatter_class + parser.formatter_class = lambda prog: argparse.HelpFormatter(prog, width=width) + help_text = parser.format_help() + parser.formatter_class = old_formatter_class + + md = f.read() + start_match = readme_help_start.search(md) + if start_match is None: + raise Exception("Couldn't find help section in readme") + start = start_match.end() + end_match = readme_help_end.search(md, start) + if end_match is None: + raise Exception("Couldn't find end of help section in readme") + end = end_match.start() + md = md[:start] + help_text + md[end:] + f.seek(0) + f.write(md) + f.truncate() +# Manually disabled for release. +# TODO: disable for release builds, while keeping it automatic during development. +# (I could make this another dev flag, but I like the idea of it being automatic.) +# (Maybe a pre-commit hook would be ideal, if it's worth the complexity.) +# update_cli_help_on_readme() + +args = parser.parse_args() +"""Parsed command line arguments.""" + +def get_help_text() -> str: + """Get the help text for the command line arguments.""" + return parser.format_help() + +__all__ = ["args", "get_help_text"] diff --git a/src/textual_paint/paint.py b/src/textual_paint/paint.py index 82039dc..6714f84 100755 --- a/src/textual_paint/paint.py +++ b/src/textual_paint/paint.py @@ -7,7 +7,6 @@ import os from pathlib import Path import re import shlex -import argparse import asyncio from enum import Enum from random import randint, random @@ -57,6 +56,7 @@ from .localization.i18n import get as _, load_language, remove_hotkey from .rasterize_ansi_art import rasterize from .wallpaper import get_config_dir, set_wallpaper from .auto_restart import restart_on_changes, restart_program +from .args import args, get_help_text from .__init__ import __version__ @@ -69,72 +69,10 @@ DEBUG_SVG_LOADING = False # writes debug.svg when flexible character grid loader # ICNS is disabled because it only supports a limited set of sizes. SAVE_DISABLED_FORMATS = ["JPEG", "ICNS"] -# Command line arguments -parser = argparse.ArgumentParser(description='Paint in the terminal.', usage='%(prog)s [options] [filename]', prog="textual-paint") -parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}') -parser.add_argument('--theme', default='light', help='Theme to use, either "light" or "dark"', choices=['light', 'dark']) -parser.add_argument('--language', default='en', help='Language to use', choices=['ar', 'cs', 'da', 'de', 'el', 'en', 'es', 'fi', 'fr', 'he', 'hu', 'it', 'ja', 'ko', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ru', 'sk', 'sl', 'sv', 'tr', 'zh', 'zh-simplified']) -parser.add_argument('--ascii-only-icons', action='store_true', help='Use only ASCII characters for tool icons, no emoji or other Unicode symbols') -parser.add_argument('--ascii-only', action='store_true', help='Use only ASCII characters for the entire UI, for use in older terminals. Implies --ascii-only-icons') -parser.add_argument('--backup-folder', default=None, metavar="FOLDER", help='Folder to save backups to. By default a backup is saved alongside the edited file.') - -# TODO: hide development options from help? there's quite a few of them now -parser.add_argument('--inspect-layout', action='store_true', help='Enables DOM inspector (F12) and middle click highlight, for development') -# This flag is for development, because it's very confusing -# to see the error message from the previous run, -# when a problem is actually solved. -# There are enough ACTUAL "that should have worked!!" moments to deal with. -# I really don't want false ones mixed in. You want to reward your brain for finding good solutions, after all. -parser.add_argument('--clear-screen', action='store_true', help='Clear the screen before starting; useful for development, to avoid seeing outdated errors') -parser.add_argument('--restart-on-changes', action='store_true', help='Restart the app when the source code is changed, for development') -parser.add_argument('--recode-samples', action='store_true', help='Open and save each file in samples/, for testing') - -parser.add_argument('filename', nargs='?', default=None, help='Path to a file to open. File will be created if it doesn\'t exist.') - -def update_cli_help_on_readme(): - """Update the readme with the current CLI usage info""" - readme_help_start = re.compile(r"```\n.*--help\n") - readme_help_end = re.compile(r"```") - readme_file_path = os.path.join(os.path.dirname(__file__), "../../README.md") - with open(readme_file_path, "r+", encoding="utf-8") as f: - # By default, argparse uses the terminal width for formatting help text, - # even when using format_help() to get a string. - # The only way to override that is to override the formatter_class. - # This is hacky, but it seems like the simplest way to fix the width - # without creating a separate ArgumentParser, and without breaking the wrapping for --help. - # This lambda works because python uses the same syntax for construction and function calls, - # so formatter_class doesn't need to be an actual class. - # See: https://stackoverflow.com/questions/44333577/explain-lambda-argparse-helpformatterprog-width - width = 80 - old_formatter_class = parser.formatter_class - parser.formatter_class = lambda prog: argparse.HelpFormatter(prog, width=width) - help_text = parser.format_help() - parser.formatter_class = old_formatter_class - - md = f.read() - start_match = readme_help_start.search(md) - if start_match is None: - raise Exception("Couldn't find help section in readme") - start = start_match.end() - end_match = readme_help_end.search(md, start) - if end_match is None: - raise Exception("Couldn't find end of help section in readme") - end = end_match.start() - md = md[:start] + help_text + md[end:] - f.seek(0) - f.write(md) - f.truncate() -# Manually disabled for release. -# TODO: disable for release builds, while keeping it automatic during development. -# (I could make this another dev flag, but I like the idea of it being automatic.) -# (Maybe a pre-commit hook would be ideal, if it's worth the complexity.) -# update_cli_help_on_readme() - -args = parser.parse_args() - +# Most arguments are handled at the end of the file, +# but it may be important to do this one early. load_language(args.language) -# Most arguments are handled at the end of the file. class MetaGlyphFont: def __init__(self, file_path: str, width: int, height: int, covered_characters: str): @@ -4244,7 +4182,7 @@ Columns: {len(palette) // 2} allow_maximize=True, allow_minimize=True, ) - help_text = parser.format_help() + help_text = get_help_text() window.content.mount(Container(Static(help_text, markup=False), classes="help_text_container")) window.content.mount(Button(_("OK"), classes="ok submit")) self.mount(window) @@ -5299,9 +5237,6 @@ Columns: {len(palette) // 2} if args.ascii_only: args.ascii_only_icons = True - import textual_paint.windows - textual_paint.windows.ascii_only = True - from .ascii_borders import force_ascii_borders force_ascii_borders() diff --git a/src/textual_paint/windows.py b/src/textual_paint/windows.py index d36bca2..defb1ad 100644 --- a/src/textual_paint/windows.py +++ b/src/textual_paint/windows.py @@ -14,9 +14,7 @@ from textual.containers import Container, Horizontal, Vertical from textual.css.query import NoMatches from .localization.i18n import get as _ - -ascii_only = False -"""This is overwritten in paint.py when --ascii-only is passed.""" +from .args import args class WindowTitleBar(Container): """A title bar widget.""" @@ -597,7 +595,7 @@ warning_icon_markup_ascii_dark_mode = """[#ffff00] [/]""" def get_warning_icon() -> Static: - markup = warning_icon_markup_ascii if ascii_only else warning_icon_markup_unicode + markup = warning_icon_markup_ascii if args.ascii_only else warning_icon_markup_unicode # TODO: Use warning_icon_markup_ascii_dark_mode for a less blocky looking outline in dark mode. return Static(markup, classes="warning_icon message_box_icon") @@ -664,7 +662,7 @@ question_icon_console_markup_ascii = """ question_icon_console_markup_ascii = question_icon_console_markup_ascii.replace(" on rgb(128,128,128)", "") def get_question_icon() -> Static: - markup = question_icon_console_markup_ascii if ascii_only else question_icon_console_markup + markup = question_icon_console_markup_ascii if args.ascii_only else question_icon_console_markup return Static(markup, classes="question_icon message_box_icon") @@ -691,7 +689,7 @@ paint_icon_console_markup_ascii = """ paint_icon_console_markup_ascii = paint_icon_console_markup_ascii.replace(" on rgb(255,0,255)", "") def get_paint_icon() -> Static: - markup = paint_icon_console_markup_ascii if ascii_only else paint_icon_console_markup + markup = paint_icon_console_markup_ascii if args.ascii_only else paint_icon_console_markup return Static(markup, classes="paint_icon message_box_icon")