codekingpro/portable-devtools
114k
1#!/usr/bin/env python2"""3qr - Convert stdin (or the first argument) to a QR Code.4 5When stdout is a tty the QR Code is printed to the terminal and when stdout is6a pipe to a file an image is written. The default image format is PNG.7"""8import optparse9import os10import sys11from typing import Dict, Iterable, NoReturn, Optional, Set, Type12 13import qrcode14from qrcode.image.base import BaseImage, DrawerAliases15 16# The next block is added to get the terminal to display properly on MS platforms17if sys.platform.startswith(("win", "cygwin")): # pragma: no cover18 import colorama # type: ignore19 20 colorama.init()21 22default_factories = {23 "pil": "qrcode.image.pil.PilImage",24 "png": "qrcode.image.pure.PyPNGImage",25 "svg": "qrcode.image.svg.SvgImage",26 "svg-fragment": "qrcode.image.svg.SvgFragmentImage",27 "svg-path": "qrcode.image.svg.SvgPathImage",28 # Keeping for backwards compatibility:29 "pymaging": "qrcode.image.pure.PymagingImage",30}31 32error_correction = {33 "L": qrcode.ERROR_CORRECT_L,34 "M": qrcode.ERROR_CORRECT_M,35 "Q": qrcode.ERROR_CORRECT_Q,36 "H": qrcode.ERROR_CORRECT_H,37}38 39 40def main(args=None):41 if args is None:42 args = sys.argv[1:]43 from pkg_resources import get_distribution44 45 version = get_distribution("qrcode").version46 parser = optparse.OptionParser(usage=(__doc__ or "").strip(), version=version)47 48 # Wrap parser.error in a typed NoReturn method for better typing.49 def raise_error(msg: str) -> NoReturn:50 parser.error(msg)51 raise # pragma: no cover52 53 parser.add_option(54 "--factory",55 help="Full python path to the image factory class to "56 "create the image with. You can use the following shortcuts to the "57 f"built-in image factory classes: {commas(default_factories)}.",58 )59 parser.add_option(60 "--factory-drawer",61 help=f"Use an alternate drawer. {get_drawer_help()}.",62 )63 parser.add_option(64 "--optimize",65 type=int,66 help="Optimize the data by looking for chunks "67 "of at least this many characters that could use a more efficient "68 "encoding method. Use 0 to turn off chunk optimization.",69 )70 parser.add_option(71 "--error-correction",72 type="choice",73 choices=sorted(error_correction.keys()),74 default="M",75 help="The error correction level to use. Choices are L (7%), "76 "M (15%, default), Q (25%), and H (30%).",77 )78 parser.add_option(79 "--ascii", help="Print as ascii even if stdout is piped.", action="store_true"80 )81 parser.add_option(82 "--output",83 help="The output file. If not specified, the image is sent to "84 "the standard output.",85 )86 87 opts, args = parser.parse_args(args)88 89 if opts.factory:90 module = default_factories.get(opts.factory, opts.factory)91 try:92 image_factory = get_factory(module)93 except ValueError as e:94 raise_error(str(e))95 else:96 image_factory = None97 98 qr = qrcode.QRCode(99 error_correction=error_correction[opts.error_correction],100 image_factory=image_factory,101 )102 103 if args:104 data = args[0]105 data = data.encode(errors="surrogateescape")106 else:107 data = sys.stdin.buffer.read()108 if opts.optimize is None:109 qr.add_data(data)110 else:111 qr.add_data(data, optimize=opts.optimize)112 113 if opts.output:114 img = qr.make_image()115 with open(opts.output, "wb") as out:116 img.save(out)117 else:118 if image_factory is None and (os.isatty(sys.stdout.fileno()) or opts.ascii):119 qr.print_ascii(tty=not opts.ascii)120 return121 122 kwargs = {}123 aliases: Optional[DrawerAliases] = getattr(124 qr.image_factory, "drawer_aliases", None125 )126 if opts.factory_drawer:127 if not aliases:128 raise_error("The selected factory has no drawer aliases.")129 if opts.factory_drawer not in aliases:130 raise_error(131 f"{opts.factory_drawer} factory drawer not found."132 f" Expected {commas(aliases)}"133 )134 drawer_cls, drawer_kwargs = aliases[opts.factory_drawer]135 kwargs["module_drawer"] = drawer_cls(**drawer_kwargs)136 img = qr.make_image(**kwargs)137 138 sys.stdout.flush()139 img.save(sys.stdout.buffer)140 141 142def get_factory(module: str) -> Type[BaseImage]:143 if "." not in module:144 raise ValueError("The image factory is not a full python path")145 module, name = module.rsplit(".", 1)146 imp = __import__(module, {}, {}, [name])147 return getattr(imp, name)148 149 150def get_drawer_help() -> str:151 help: Dict[str, Set] = {}152 for alias, module in default_factories.items():153 try:154 image = get_factory(module)155 except ImportError: # pragma: no cover156 continue157 aliases: Optional[DrawerAliases] = getattr(image, "drawer_aliases", None)158 if not aliases:159 continue160 factories = help.setdefault(commas(aliases), set())161 factories.add(alias)162 163 return ". ".join(164 f"For {commas(factories, 'and')}, use: {aliases}"165 for aliases, factories in help.items()166 )167 168 169def commas(items: Iterable[str], joiner="or") -> str:170 items = tuple(items)171 if not items:172 return ""173 if len(items) == 1:174 return items[0]175 return f"{', '.join(items[:-1])} {joiner} {items[-1]}"176 177 178if __name__ == "__main__": # pragma: no cover179 main()180 