diff --git a/Lib/shaperglot/cli.py b/Lib/shaperglot/cli.py deleted file mode 100644 index 43ea15a..0000000 --- a/Lib/shaperglot/cli.py +++ /dev/null @@ -1,190 +0,0 @@ -import argparse -import sys -from textwrap import fill -import os -import re - -from shaperglot.checker import Checker -from shaperglot.languages import Languages - - -def describe(options): - """Describe the checks shaperglot will perform to determine support for a given language""" - langs = Languages() - if options.lang not in langs: - maybe = langs.disambiguate(options.lang) - if len(maybe) == 1: - lang = langs[maybe[0]] - print(f"Assuming you meant {maybe[0]} ({lang['full_name']}).") - elif len(maybe) > 1: - print(f"Language '{options.lang}' not known", end="") - print("; try one of: " + ", ".join(maybe)) - return - else: - print(f"Language '{options.lang}' not known", end="") - print("") - return - else: - lang = langs[options.lang] - print(f"To test for {lang['name']} support, shaperglot will:") - for shaperglot_check in lang.get("shaperglot_checks", []): - print( - fill( - "ensure " + shaperglot_check.describe(), - initial_indent=" * ", - subsequent_indent=" ", - width=os.get_terminal_size()[0] - 2, - ) - ) - - -def check(options): - """Check a particular language or languages are supported""" - checker = Checker(options.font) - langs = Languages() - for lang in options.lang: - if lang not in langs: - print(f"Language '{options.lang}' not known") - continue - - results = checker.check(langs[lang]) - - if results.is_unknown: - print(f"Cannot determine whether font supports language '{lang}'") - elif results.is_success: - print(f"Font supports language '{lang}'") - else: - print(f"Font does not fully support language '{lang}'") - - if options.verbose and options.verbose > 1: - for message in results: - print(f" * {message}") - elif options.verbose or not results.is_success: - for message in results.fails: - print(f" * {message}") - - -def report(options): - """Report which languages are supported by the given font""" - checker = Checker(options.font) - langs = Languages() - messages = [] - supported = [] - unsupported = [] - - for lang in sorted(langs.keys()): - if options.filter and not re.search(options.filter, lang): - continue - results = checker.check(langs[lang]) - - if results.is_unknown: - continue - - if results.is_success: - supported.append(langs[lang]['name']) - print(f"Font supports language '{lang}' ({langs[lang]['name']})") - else: - unsupported.append(langs[lang]['name']) - print( - f"Font does not fully support language '{lang}' ({langs[lang]['name']})" - ) - messages.extend(results) - if options.verbose and options.verbose > 1: - for status, message in results: - print(f" * {status.value}: {message}") - - # Collate a useful fixing guide - print("\n== Summary ==\n") - print(f"* {len(supported)+len(unsupported)} languages checked") - if supported: - print(f"* {len(supported)} languages supported") - if not options.verbose: - return - if unsupported: - print( - fill( - "* Unsupported languages: " + ", ".join(unsupported), - subsequent_indent=" " * 25, - width=os.get_terminal_size()[0] - 2, - ) - ) - print("\nTo add support:") - missing_bases = set() - missing_marks = set() - missing_anchors = set() - for msg in messages: - if msg.result_code == "bases-missing": - missing_bases |= set(msg.context["glyphs"]) - if msg.result_code == "marks-missing": - missing_marks |= set(msg.context["glyphs"]) - if msg.result_code == "orphaned-mark": - missing_anchors.add((msg.context["base"], msg.context["mark"])) - if missing_marks: - print( - fill( - " * Add mark glyphs: " - + ", ".join(["\u25cc" + x for x in sorted(missing_marks)]), - subsequent_indent=" ", - width=os.get_terminal_size()[0] - 2, - ) - ) - if missing_bases: - print( - fill( - " * Support characters: " + ", ".join(sorted(missing_bases)), - subsequent_indent=" ", - width=os.get_terminal_size()[0] - 2, - ) - ) - if missing_anchors: - print( - fill( - " * Add anchor attachments: " - + ", ".join( - [base + '/' + mark for base, mark in sorted(missing_anchors)] - ), - subsequent_indent=" ", - width=os.get_terminal_size()[0] - 2, - ) - ) - - -def main(args=None): - if args is None: - args = sys.argv[1:] - parser = argparse.ArgumentParser( - description="Check a font file's language coverage" - ) - subparsers = parser.add_subparsers(help='sub-commands') - - parser_describe = subparsers.add_parser('describe', help=describe.__doc__) - parser_describe.add_argument( - 'lang', metavar='LANG', help='an ISO639-3 language code' - ) - parser_describe.set_defaults(func=describe) - - parser_check = subparsers.add_parser('check', help=check.__doc__) - parser_check.add_argument('--verbose', '-v', action='count') - parser_check.add_argument('font', metavar='FONT', help='the font file') - parser_check.add_argument( - 'lang', metavar='LANG', help='one or more ISO639-3 language codes', nargs="+" - ) - parser_check.set_defaults(func=check) - - parser_report = subparsers.add_parser('report', help=report.__doc__) - parser_report.add_argument('font', metavar='FONT', help='the font file') - parser_report.add_argument('--verbose', '-v', action='count') - parser_report.add_argument( - '--filter', type=str, help="Regular expression to filter languages" - ) - parser_report.set_defaults(func=report) - - options = parser.parse_args(args) - if not hasattr(options, "func"): - parser.print_help() - sys.exit(1) - options.func(options) - - -if __name__ == '__main__': # pragma: no cover - main() diff --git a/Lib/shaperglot/cli/__init__.py b/Lib/shaperglot/cli/__init__.py new file mode 100644 index 0000000..3ea4afb --- /dev/null +++ b/Lib/shaperglot/cli/__init__.py @@ -0,0 +1,51 @@ +import argparse +import sys + +from shaperglot.cli.check import check +from shaperglot.cli.report import report +from shaperglot.cli.describe import describe + + +def main(args=None): + if args is None: + args = sys.argv[1:] + parser = argparse.ArgumentParser( + description="Check a font file's language coverage" + ) + subparsers = parser.add_subparsers(help='sub-commands') + + parser_describe = subparsers.add_parser('describe', help=describe.__doc__) + parser_describe.add_argument( + 'lang', metavar='LANG', help='an ISO639-3 language code' + ) + parser_describe.set_defaults(func=describe) + + parser_check = subparsers.add_parser('check', help=check.__doc__) + parser_check.add_argument('--verbose', '-v', action='count') + parser_check.add_argument('font', metavar='FONT', help='the font file') + parser_check.add_argument( + 'lang', metavar='LANG', help='one or more ISO639-3 language codes', nargs="+" + ) + parser_check.set_defaults(func=check) + + parser_report = subparsers.add_parser('report', help=report.__doc__) + parser_report.add_argument('font', metavar='FONT', help='the font file') + parser_report.add_argument('--verbose', '-v', action='count') + parser_report.add_argument('--csv', action='store_true', help="Output as CSV") + parser_report.add_argument( + '--group', action='store_true', help="Group by success/failure" + ) + parser_report.add_argument( + '--filter', type=str, help="Regular expression to filter languages" + ) + parser_report.set_defaults(func=report) + + options = parser.parse_args(args) + if not hasattr(options, "func"): + parser.print_help() + sys.exit(1) + options.func(options) + + +if __name__ == '__main__': # pragma: no cover + main() diff --git a/Lib/shaperglot/cli/check.py b/Lib/shaperglot/cli/check.py new file mode 100644 index 0000000..45854ea --- /dev/null +++ b/Lib/shaperglot/cli/check.py @@ -0,0 +1,28 @@ +from shaperglot.checker import Checker +from shaperglot.languages import Languages + + +def check(options): + """Check a particular language or languages are supported""" + checker = Checker(options.font) + langs = Languages() + for lang in options.lang: + if lang not in langs: + print(f"Language '{options.lang}' not known") + continue + + results = checker.check(langs[lang]) + + if results.is_unknown: + print(f"Cannot determine whether font supports language '{lang}'") + elif results.is_success: + print(f"Font supports language '{lang}'") + else: + print(f"Font does not fully support language '{lang}'") + + if options.verbose and options.verbose > 1: + for message in results: + print(f" * {message}") + elif options.verbose or not results.is_success: + for message in results.fails: + print(f" * {message}") diff --git a/Lib/shaperglot/cli/describe.py b/Lib/shaperglot/cli/describe.py new file mode 100644 index 0000000..7eafe53 --- /dev/null +++ b/Lib/shaperglot/cli/describe.py @@ -0,0 +1,34 @@ +import os +from textwrap import fill + +from shaperglot.languages import Languages + +def describe(options): + """Describe the checks shaperglot will perform to determine support for a given language""" + langs = Languages() + if options.lang not in langs: + maybe = langs.disambiguate(options.lang) + if len(maybe) == 1: + lang = langs[maybe[0]] + print(f"Assuming you meant {maybe[0]} ({lang['full_name']}).") + elif len(maybe) > 1: + print(f"Language '{options.lang}' not known", end="") + print("; try one of: " + ", ".join(maybe)) + return + else: + print(f"Language '{options.lang}' not known", end="") + print("") + return + else: + lang = langs[options.lang] + print(f"To test for {lang['name']} support, shaperglot will:") + for shaperglot_check in lang.get("shaperglot_checks", []): + print( + fill( + "ensure " + shaperglot_check.describe(), + initial_indent=" * ", + subsequent_indent=" ", + width=os.get_terminal_size()[0] - 2, + ) + ) + diff --git a/Lib/shaperglot/cli/report.py b/Lib/shaperglot/cli/report.py new file mode 100644 index 0000000..a0bd5ae --- /dev/null +++ b/Lib/shaperglot/cli/report.py @@ -0,0 +1,146 @@ +import os +import re +from textwrap import fill +from typing import Iterable + +from shaperglot.checker import Checker +from shaperglot.languages import Languages +from shaperglot.reporter import Result + + +def report(options): + """Report which languages are supported by the given font""" + checker = Checker(options.font) + langs = Languages() + messages = [] + supported = [] + unsupported = [] + + if options.csv: + print("Language,Name,Supported,Bases Missing,Marks Missing,Orphaned Marks,Other") + + for lang in sorted(langs.keys()): + if options.filter and not re.search(options.filter, lang): + continue + results = checker.check(langs[lang]) + + if results.is_unknown: + continue + + if options.csv: + report_csv(lang, langs[lang], results) + continue + + if results.is_success: + supported.append(lang) + msg = "supports" + else: + unsupported.append(lang) + msg = "does not fully support" + if options.group: + continue + print(f"Font {msg} language '{lang}' ({langs[lang]['name']})") + + messages.extend(results) + if options.verbose and options.verbose > 1: + for status, message in results: + print(f" * {status.value}: {message}") + + if options.group: + if supported: + print("Supported languages") + print("===================\n") + for lang in supported: + print(f"Font supports language '{lang}' ({langs[lang]['name']})") + + if unsupported: + print("\nUnsupported languages") + print("====================\n") + for lang in unsupported: + print(f"Font does not fully support language '{lang}' ({langs[lang]['name']})") + # Collate a useful fixing guide + if options.csv: + return + + short_summary(supported, unsupported) + if options.verbose: + long_summary(messages, unsupported) + + +def short_summary(supported, unsupported): + print("\n== Summary ==\n") + print(f"* {len(supported)+len(unsupported)} languages checked") + if supported: + print(f"* {len(supported)} languages supported") + + +def long_summary(messages, unsupported): + if unsupported: + print( + fill( + "* Unsupported languages: " + ", ".join(unsupported), + subsequent_indent=" " * 25, + width=os.get_terminal_size()[0] - 2, + ) + ) + print("\nTo add support:") + missing_bases = set() + missing_marks = set() + missing_anchors = set() + for msg in messages: + if msg.result_code == "bases-missing": + missing_bases |= set(msg.context["glyphs"]) + if msg.result_code == "marks-missing": + missing_marks |= set(msg.context["glyphs"]) + if msg.result_code == "orphaned-mark": + missing_anchors.add((msg.context["base"], msg.context["mark"])) + if missing_marks: + print( + fill( + " * Add mark glyphs: " + + ", ".join(["\u25cc" + x for x in sorted(missing_marks)]), + subsequent_indent=" ", + width=os.get_terminal_size()[0] - 2, + ) + ) + if missing_bases: + print( + fill( + " * Support characters: " + ", ".join(sorted(missing_bases)), + subsequent_indent=" ", + width=os.get_terminal_size()[0] - 2, + ) + ) + if missing_anchors: + print( + fill( + " * Add anchor attachments: " + + ", ".join( + [base + '/' + mark for base, mark in sorted(missing_anchors)] + ), + subsequent_indent=" ", + width=os.get_terminal_size()[0] - 2, + ) + ) + +def report_csv(langcode, lang, results: Iterable[Result]): + print( + f"{langcode},\"{lang['name']}\",{results.is_success},", end="" + ) + missing_bases = set() + missing_marks = set() + missing_anchors = set() + other_errors = set() + for msg in results: + if msg.result_code == "bases-missing": + missing_bases |= set(msg.context["glyphs"]) + elif msg.result_code == "marks-missing": + missing_marks |= set(msg.context["glyphs"]) + elif msg.result_code == "orphaned-mark": + missing_anchors.add(msg.context["base"] + "/" + msg.context["mark"]) + else: + other_errors.add(msg.result_code) + print(" ".join(sorted(missing_bases)), end=",") + print(" ".join(sorted(missing_marks)), end=",") + print(" ".join(sorted(missing_anchors)), end=",") + print(" ".join(sorted(other_errors)), end="\n") \ No newline at end of file