From 2cef07af80f41ddf92d770998102cc8f31845b0d Mon Sep 17 00:00:00 2001 From: Simon Cozens Date: Tue, 12 May 2020 06:31:13 +0100 Subject: [PATCH] [doc] Add help options to fonttools CLI (#1920) This adds a `help` verb (and `--help` option) to the `fonttools` command line tool. Submodules will be listed in the help text if they have an importable `main` function with a docstring, and `main`'s docstring will be used as the one-line description for the help text. --- Lib/fontTools/__main__.py | 6 ++-- Lib/fontTools/cffLib/width.py | 15 ++++++---- Lib/fontTools/cu2qu/cli.py | 1 + Lib/fontTools/feaLib/__main__.py | 1 + Lib/fontTools/help.py | 34 ++++++++++++++++++++++ Lib/fontTools/merge.py | 4 +-- Lib/fontTools/mtiLib/__init__.py | 1 + Lib/fontTools/subset/__init__.py | 1 + Lib/fontTools/subset/__main__.py | 1 + Lib/fontTools/ttLib/woff2.py | 3 +- Lib/fontTools/ttx.py | 1 + Lib/fontTools/varLib/__init__.py | 1 + Lib/fontTools/varLib/__main__.py | 1 + Lib/fontTools/varLib/instancer.py | 1 + Lib/fontTools/varLib/interpolatable.py | 1 + Lib/fontTools/varLib/interpolate_layout.py | 1 + Lib/fontTools/varLib/models.py | 1 + Lib/fontTools/varLib/mutator.py | 1 + Lib/fontTools/varLib/varStore.py | 1 + 19 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 Lib/fontTools/help.py diff --git a/Lib/fontTools/__main__.py b/Lib/fontTools/__main__.py index ba09c7fc6..9b978aaa7 100644 --- a/Lib/fontTools/__main__.py +++ b/Lib/fontTools/__main__.py @@ -5,8 +5,6 @@ def main(args=None): if args is None: args = sys.argv[1:] - # TODO Add help output, --help, etc. - # TODO Handle library-wide options. Eg.: # --unicodedata # --verbose / other logging stuff @@ -20,6 +18,10 @@ def main(args=None): # can be added. Should we just try importing the fonttools # module first and try without if it fails? + if len(sys.argv) < 2: + sys.argv.append("help") + if sys.argv[1] == "-h" or sys.argv[1] == "--help": + sys.argv[1] = "help" mod = 'fontTools.'+sys.argv[1] sys.argv[1] = sys.argv[0] + ' ' + sys.argv[1] del sys.argv[0] diff --git a/Lib/fontTools/cffLib/width.py b/Lib/fontTools/cffLib/width.py index d959da1c5..909f1267d 100644 --- a/Lib/fontTools/cffLib/width.py +++ b/Lib/fontTools/cffLib/width.py @@ -146,12 +146,8 @@ def optimizeWidths(widths): return default, nominal - -if __name__ == '__main__': - import sys - if len(sys.argv) == 1: - import doctest - sys.exit(doctest.testmod().failed) +def main(): + """Calculate optimum defaultWidthX/nominalWidthX values""" for fontfile in sys.argv[1:]: font = TTFont(fontfile) hmtx = font['hmtx'] @@ -160,3 +156,10 @@ if __name__ == '__main__': print("glyphs=%d default=%d nominal=%d byteCost=%d" % (len(widths), default, nominal, byteCost(widths, default, nominal))) #default, nominal = optimizeWidthsBruteforce(widths) #print("glyphs=%d default=%d nominal=%d byteCost=%d" % (len(widths), default, nominal, byteCost(widths, default, nominal))) + +if __name__ == '__main__': + import sys + if len(sys.argv) == 1: + import doctest + sys.exit(doctest.testmod().failed) + main() diff --git a/Lib/fontTools/cu2qu/cli.py b/Lib/fontTools/cu2qu/cli.py index 7f3c1ef7b..d4e83b883 100644 --- a/Lib/fontTools/cu2qu/cli.py +++ b/Lib/fontTools/cu2qu/cli.py @@ -59,6 +59,7 @@ def _copytree(input_path, output_path): def main(args=None): + """Convert a UFO font from cubic to quadratic curves""" parser = argparse.ArgumentParser(prog="cu2qu") parser.add_argument( "--version", action="version", version=fontTools.__version__) diff --git a/Lib/fontTools/feaLib/__main__.py b/Lib/fontTools/feaLib/__main__.py index b69307e84..e7db157f0 100644 --- a/Lib/fontTools/feaLib/__main__.py +++ b/Lib/fontTools/feaLib/__main__.py @@ -13,6 +13,7 @@ log = logging.getLogger("fontTools.feaLib") def main(args=None): + """Add features from a feature file (.fea) into a OTF font""" parser = argparse.ArgumentParser( description="Use fontTools to compile OpenType feature files (*.fea).") parser.add_argument( diff --git a/Lib/fontTools/help.py b/Lib/fontTools/help.py new file mode 100644 index 000000000..ff8048d5b --- /dev/null +++ b/Lib/fontTools/help.py @@ -0,0 +1,34 @@ +import pkgutil +import sys +import fontTools +import importlib +import os +from pathlib import Path + + +def main(): + """Show this help""" + path = fontTools.__path__ + descriptions = {} + for pkg in sorted( + mod.name + for mod in pkgutil.walk_packages([fontTools.__path__[0]], prefix="fontTools.") + ): + try: + imports = __import__(pkg, globals(), locals(), ["main"]) + except ImportError as e: + continue + try: + description = imports.main.__doc__ + if description: + pkg = pkg.replace("fontTools.", "").replace(".__main__", "") + descriptions[pkg] = description + except AttributeError as e: + pass + for pkg, description in descriptions.items(): + print("fonttools %-12s %s" % (pkg, description), file=sys.stderr) + + +if __name__ == "__main__": + print("fonttools v%s\n" % fontTools.__version__, file=sys.stderr) + main() diff --git a/Lib/fontTools/merge.py b/Lib/fontTools/merge.py index 6275a0327..82a5e3f75 100644 --- a/Lib/fontTools/merge.py +++ b/Lib/fontTools/merge.py @@ -2,9 +2,6 @@ # # Google Author(s): Behdad Esfahbod, Roozbeh Pournader -"""Font merger. -""" - from fontTools.misc.py23 import * from fontTools.misc.timeTools import timestampNow from fontTools import ttLib, cffLib @@ -1136,6 +1133,7 @@ __all__ = [ @timer("make one with everything (TOTAL TIME)") def main(args=None): + """Merge multiple fonts into one""" from fontTools import configLogger if args is None: diff --git a/Lib/fontTools/mtiLib/__init__.py b/Lib/fontTools/mtiLib/__init__.py index f0f0a1336..cf264a85c 100644 --- a/Lib/fontTools/mtiLib/__init__.py +++ b/Lib/fontTools/mtiLib/__init__.py @@ -1151,6 +1151,7 @@ def build(f, font, tableTag=None): def main(args=None, font=None): + """Convert a FontDame OTL file to TTX XML""" import sys from fontTools import configLogger from fontTools.misc.testTools import MockFont diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py index 0119a92bd..237fda660 100644 --- a/Lib/fontTools/subset/__init__.py +++ b/Lib/fontTools/subset/__init__.py @@ -2778,6 +2778,7 @@ def usage(): @timer("make one with everything (TOTAL TIME)") def main(args=None): + """OpenType font subsetter and optimizer""" from os.path import splitext from fontTools import configLogger diff --git a/Lib/fontTools/subset/__main__.py b/Lib/fontTools/subset/__main__.py index 3f3d894a0..93549d5d1 100644 --- a/Lib/fontTools/subset/__main__.py +++ b/Lib/fontTools/subset/__main__.py @@ -2,5 +2,6 @@ from fontTools.misc.py23 import * import sys from fontTools.subset import main + if __name__ == '__main__': sys.exit(main()) diff --git a/Lib/fontTools/ttLib/woff2.py b/Lib/fontTools/ttLib/woff2.py index 07d1f95e5..b56355923 100644 --- a/Lib/fontTools/ttLib/woff2.py +++ b/Lib/fontTools/ttLib/woff2.py @@ -1390,6 +1390,7 @@ def decompress(input_file, output_file): def main(args=None): + """Compress and decompress WOFF2 fonts""" import argparse from fontTools import configLogger from fontTools.ttx import makeOutputFileName @@ -1404,7 +1405,7 @@ def main(args=None): parser = argparse.ArgumentParser( prog="fonttools ttLib.woff2", - description="Compress and decompress WOFF2 fonts", + description=main.__doc__ ) parser_group = parser.add_subparsers(title="sub-commands") diff --git a/Lib/fontTools/ttx.py b/Lib/fontTools/ttx.py index faacbedbe..9522c625f 100644 --- a/Lib/fontTools/ttx.py +++ b/Lib/fontTools/ttx.py @@ -384,6 +384,7 @@ def waitForKeyPress(): def main(args=None): + """Convert OpenType fonts to XML and back""" from fontTools import configLogger if args is None: diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py index f8ae30e29..acc929b00 100644 --- a/Lib/fontTools/varLib/__init__.py +++ b/Lib/fontTools/varLib/__init__.py @@ -1007,6 +1007,7 @@ class MasterFinder(object): def main(args=None): + """Build a variable font from a designspace file and masters""" from argparse import ArgumentParser from fontTools import configLogger diff --git a/Lib/fontTools/varLib/__main__.py b/Lib/fontTools/varLib/__main__.py index 29657401d..4b3a0f532 100644 --- a/Lib/fontTools/varLib/__main__.py +++ b/Lib/fontTools/varLib/__main__.py @@ -1,5 +1,6 @@ import sys from fontTools.varLib import main + if __name__ == '__main__': sys.exit(main()) diff --git a/Lib/fontTools/varLib/instancer.py b/Lib/fontTools/varLib/instancer.py index 97e8bfeb5..2d22d622f 100644 --- a/Lib/fontTools/varLib/instancer.py +++ b/Lib/fontTools/varLib/instancer.py @@ -1375,6 +1375,7 @@ def parseArgs(args): def main(args=None): + """Partially instantiate a variable font.""" infile, axisLimits, options = parseArgs(args) log.info("Restricting axes: %s", axisLimits) diff --git a/Lib/fontTools/varLib/interpolatable.py b/Lib/fontTools/varLib/interpolatable.py index d4d7eeda7..1e326c9b0 100644 --- a/Lib/fontTools/varLib/interpolatable.py +++ b/Lib/fontTools/varLib/interpolatable.py @@ -158,6 +158,7 @@ def test(glyphsets, glyphs=None, names=None): # print(x) def main(args): + """Test for interpolatability issues between fonts""" filenames = args glyphs = None #glyphs = ['uni08DB', 'uniFD76'] diff --git a/Lib/fontTools/varLib/interpolate_layout.py b/Lib/fontTools/varLib/interpolate_layout.py index d008e1cef..1403818c8 100644 --- a/Lib/fontTools/varLib/interpolate_layout.py +++ b/Lib/fontTools/varLib/interpolate_layout.py @@ -58,6 +58,7 @@ def interpolate_layout(designspace, loc, master_finder=lambda s:s, mapped=False) def main(args=None): + """Interpolate GDEF/GPOS/GSUB tables for a point on a designspace""" from fontTools import configLogger import sys diff --git a/Lib/fontTools/varLib/models.py b/Lib/fontTools/varLib/models.py index d6837ee62..aa181bb22 100644 --- a/Lib/fontTools/varLib/models.py +++ b/Lib/fontTools/varLib/models.py @@ -423,6 +423,7 @@ def piecewiseLinearMap(v, mapping): def main(args): + """Normalize locations on a given designspace""" from fontTools import configLogger args = args[1:] diff --git a/Lib/fontTools/varLib/mutator.py b/Lib/fontTools/varLib/mutator.py index bd8882494..b5954f8b9 100644 --- a/Lib/fontTools/varLib/mutator.py +++ b/Lib/fontTools/varLib/mutator.py @@ -399,6 +399,7 @@ def instantiateVariableFont(varfont, location, inplace=False, overlap=True): def main(args=None): + """Instantiate a variation font""" from fontTools import configLogger import argparse diff --git a/Lib/fontTools/varLib/varStore.py b/Lib/fontTools/varLib/varStore.py index 7239e9545..de114b19d 100644 --- a/Lib/fontTools/varLib/varStore.py +++ b/Lib/fontTools/varLib/varStore.py @@ -545,6 +545,7 @@ ot.VarStore.optimize = VarStore_optimize def main(args=None): + """Optimize a font's GDEF variation store""" from argparse import ArgumentParser from fontTools import configLogger from fontTools.ttLib import TTFont