#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 - 2019 -- Lars Heuer - Semagia .
# All rights reserved.
#
# License: BSD License
#
"""\
Command line script to generate QR Codes with Segno.
"QR Code" and "Micro QR Code" are registered trademarks of DENSO WAVE INCORPORATED.
"""
from __future__ import absolute_import, unicode_literals
import os
import sys
import argparse
import segno
from segno import writers
# file extension to supported keywords mapping
_EXT_TO_KW_MAPPING = {}
for ext, func in writers._VALID_SERIALISERS.items():
if len(ext) > 3:
continue
# Python 2 vs Python 3
func_code = getattr(func, 'func_code', None) or func.__code__
defaults = getattr(func, 'func_defaults', None) or func.__defaults__
args = func_code.co_varnames[:func_code.co_argcount]
_EXT_TO_KW_MAPPING[ext] = args[-len(defaults):]
del writers
def make_parser():
"""\
Returns the command line parser.
"""
def _convert_scale(val):
val = float(val)
return val if val != int(val) else int(val)
parser = argparse.ArgumentParser(prog='segno',
description='Segno QR Code and Micro QR Code generator version {0}'.format(segno.__version__))
parser.add_argument('--version', '-v', help='(Micro) QR Code version: 1 .. 40 or "M1", "M2", "M3", "M4"',
required=False,)
parser.add_argument('--error', '-e', help='Error correction level: "L": 7%% (default), "M": 15%%, "Q": 25%%, "H": 30%%, "-": no error correction (used for M1 symbols)',
choices=('L', 'M', 'Q', 'H', '-'),
default=None,
type=lambda x: x.upper())
parser.add_argument('--mode', '-m', help='Mode',
choices=('numeric', 'alphanumeric', 'byte', 'kanji'),
default=None,
type=lambda x: x.lower())
parser.add_argument('--pattern', '-p', help='Mask pattern to use',
required=False,
default=None,
type=int)
parser.add_argument('--scale', '-s', help='Scaling factor',
default=1,
type=_convert_scale)
parser.add_argument('--border', '-b', help='Size of the border / quiet zone',
default=None,
type=int)
parser.add_argument('--micro', help='Allow the creation of Micro QR Codes',
dest='micro', action='store_true')
parser.add_argument('--no-micro', help='Disallow creation of Micro QR Codes (default)',
dest='micro', action='store_false')
parser.add_argument('--color', help='Color of the dark modules. Use "transparent" to set the color to None (not supported by all serializers)')
parser.add_argument('--background', help='Color of the light modules. Use "transparent" to set the background to None (not supported by all serializers)')
parser.add_argument('--output', '-o', help='Output file. If not specified, the QR Code is printed to the terminal',
required=False,
)
parser.add_argument('--no-error-boost', help='Disables the automatic error incrementation if a higher error correction level is possible',
dest='boost_error', action='store_false')
parser.add_argument('--seq', help='Creates a sequence of QR Codes (Structured Append mode). Version or symbol count must be provided',
dest='seq', action='store_true')
parser.add_argument('--symbol-count', '-sc', help='Number of symbols to create',
default=None,
type=int)
# SVG
svg_group = parser.add_argument_group('SVG', 'SVG specific options')
svg_group.add_argument('--no-classes', help='Omits the (default) SVG classes',
action='store_true')
svg_group.add_argument('--no-xmldecl', help='Omits the XML declaration header',
dest='xmldecl',
action='store_false')
svg_group.add_argument('--no-namespace', help='Indicates that the SVG document should have no SVG namespace declaration',
dest='svgns',
action='store_false')
svg_group.add_argument('--no-newline', help='Indicates that the SVG document should have no trailing newline',
dest='nl',
action='store_false')
svg_group.add_argument('--title', help='Specifies the title of the SVG document')
svg_group.add_argument('--desc', help='Specifies the description of the SVG document')
svg_group.add_argument('--svgid', help='Indicates the ID of the element')
svg_group.add_argument('--svgclass', help='Indicates the CSS class of the element')
svg_group.add_argument('--lineclass', help='Indicates the CSS class of the element (the dark modules)')
svg_group.add_argument('--no-size', help='Indicates that the SVG document should not have "width" and "height" attributes',
dest='omitsize',
action='store_true')
svg_group.add_argument('--unit', help='Indicates SVG coordinate system unit')
svg_group.add_argument('--svgversion', help='Indicates the SVG version',
type=float)
svg_group.add_argument('--encoding', help='Specifies the encoding of the document',
default='utf-8')
# PNG
png_group = parser.add_argument_group('PNG', 'PNG specific options')
png_group.add_argument('--dpi', help='Sets the DPI value of the PNG file',
type=int)
png_group.add_argument('--no-ad', help='Omits the "Software" comment in the PNG file',
dest='addad',
action='store_false')
parser.add_mutually_exclusive_group().add_argument('--ver', '-V', help="Shows Segno's version",
action='version',
version='Segno {0}'.format(segno.__version__))
parser.add_argument('content', nargs='+', help='The content to encode')
return parser
def parse(args):
"""\
Parses the arguments and returns the result.
"""
parser = make_parser()
if not len(args):
parser.print_help()
sys.exit(1)
parsed_args = parser.parse_args(args)
if parsed_args.error == '-':
parsed_args.error = None
# 'micro' is False by default. If version is set to a Micro QR Code version,
# encoder.encode raises a VersionError.
# Small problem: --version=M4 --no-micro is allowed
version = parsed_args.version
if version is not None:
version = str(version).upper()
if not parsed_args.micro and version in ('M1', 'M2', 'M3', 'M4'):
parsed_args.micro = None
return _AttrDict(vars(parsed_args))
def build_config(config, filename=None):
"""\
Builds a configuration and returns it. The config contains only keywords,
which are supported by the serializer. Unsupported values are ignored.
"""
# Done here since it seems not to be possible to detect if an argument
# was supplied by the user or if it's the default argument.
# If using type=lambda v: None if v in ('transparent', 'trans') else v
# we cannot detect if "None" comes from "transparent" or the default value
for clr in ('color', 'background'):
val = config.pop(clr, None)
if val in ('transparent', 'trans'):
config[clr] = None
elif val:
config[clr] = val
# SVG
for name in ('svgid', 'svgclass', 'lineclass'):
if config.get(name, None) is None:
config.pop(name, None)
if config.pop('no_classes', False):
config['svgclass'] = None
config['lineclass'] = None
if filename is not None:
ext = filename[filename.rfind('.') + 1:].lower()
if ext == 'svgz': # There is no svgz serializer, use same config as svg
ext = 'svg'
supported_args = _EXT_TO_KW_MAPPING.get(ext, ())
# Drop unsupported arguments from config rather than getting a
# "unsupported keyword" exception
for k in list(config):
if k not in supported_args:
del config[k]
return config
def make_code(config):
make = segno.make
kw = dict(mode=config.pop('mode'), error=config.pop('error'),
version=config.pop('version'), mask=config.pop('pattern'),
boost_error=config.pop('boost_error'))
if config.pop('seq'):
make = segno.make_sequence
kw['symbol_count'] = config.pop('symbol_count')
else:
kw['micro'] = config.pop('micro')
return make(' '.join(config.pop('content')), **kw)
def main(args=sys.argv[1:]):
config = parse(args)
try:
qr = make_code(config)
except segno.QRCodeError as ex:
sys.stderr.writelines([str(ex), os.linesep])
return sys.exit(1)
output = config.pop('output')
if output is None:
qr.terminal(border=config['border'])
else:
qr.save(output, **build_config(config, filename=output))
return 0
class _AttrDict(dict):
def __init__(self, *args, **kwargs):
super(_AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
if __name__ == '__main__': # pragma: no cover
main()