python-escpos/src/escpos/capabilities.py

152 lines
4.1 KiB
Python
Raw Normal View History

import re
from os import environ, path
import pickle
import logging
import time
import six
import yaml
from tempfile import gettempdir
import platform
logging.basicConfig()
logger = logging.getLogger(__name__)
pickle_dir = environ.get('ESCPOS_CAPABILITIES_PICKLE_DIR', gettempdir())
pickle_path = path.join(pickle_dir, '{v}.capabilities.pickle'.format(v=platform.python_version()))
capabilities_path = environ.get(
'ESCPOS_CAPABILITIES_FILE',
path.join(path.dirname(__file__), 'capabilities.json'))
2016-08-30 10:26:09 +00:00
# Load external printer database
t0 = time.time()
logger.debug('Using capabilities from file: %s', capabilities_path)
if path.exists(pickle_path):
if path.getmtime(capabilities_path) > path.getmtime(pickle_path):
logger.debug('Found a more recent capabilities file')
full_load = True
else:
full_load = False
logger.debug('Loading capabilities from pickle in %s', pickle_path)
with open(pickle_path, 'rb') as cf:
CAPABILITIES = pickle.load(cf)
else:
logger.debug('Capabilities pickle file not found: %s', pickle_path)
full_load = True
if full_load:
logger.debug('Loading and pickling capabilities')
with open(capabilities_path) as cp, open(pickle_path, 'wb') as pp:
CAPABILITIES = yaml.load(cp)
pickle.dump(CAPABILITIES, pp, protocol=2)
logger.debug('Finished loading capabilities took %.2fs', time.time() - t0)
2016-08-30 10:26:09 +00:00
PROFILES = CAPABILITIES['profiles']
2016-08-30 10:26:09 +00:00
class NotSupported(Exception):
2018-02-07 21:20:40 +00:00
"""Raised if a requested feature is not supported by the
printer profile.
"""
2016-08-30 10:26:09 +00:00
pass
BARCODE_B = 'barcodeB'
2016-08-30 10:26:09 +00:00
class BaseProfile(object):
"""This respresents a printer profile.
A printer profile knows about the number of columns, supported
features, colors and more.
"""
profile_data = {}
def __getattr__(self, name):
return self.profile_data[name]
2016-08-30 10:26:09 +00:00
def get_font(self, font):
"""Return the escpos index for `font`. Makes sure that
the requested `font` is valid.
"""
font = {'a': 0, 'b': 1}.get(font, font)
if not six.text_type(font) in self.fonts:
raise NotSupported(
'"{}" is not a valid font in the current profile'.format(font))
2016-08-30 10:26:09 +00:00
return font
def get_columns(self, font):
""" Return the number of columns for the given font.
"""
2016-08-30 10:26:09 +00:00
font = self.get_font(font)
return self.fonts[six.text_type(font)]['columns']
def supports(self, feature):
"""Return true/false for the given feature.
"""
return self.features.get(feature)
def get_code_pages(self):
2018-02-07 21:20:40 +00:00
"""Return the support code pages as a ``{name: index}`` dict.
"""
return {v: k for k, v in self.codePages.items()}
def get_profile(name=None, **kwargs):
2016-08-30 10:26:09 +00:00
"""Get the profile by name; if no name is given, return the
default profile.
"""
if isinstance(name, Profile):
return name
clazz = get_profile_class(name or 'default')
return clazz(**kwargs)
CLASS_CACHE = {}
def get_profile_class(name):
2016-08-30 10:26:09 +00:00
"""For the given profile name, load the data from the external
database, then generate dynamically a class.
"""
2017-01-29 23:50:58 +00:00
if name not in CLASS_CACHE:
2016-08-30 10:26:09 +00:00
profile_data = PROFILES[name]
profile_name = clean(name)
class_name = '{}{}Profile'.format(
profile_name[0].upper(), profile_name[1:])
new_class = type(class_name, (BaseProfile,), {'profile_data': profile_data})
CLASS_CACHE[name] = new_class
return CLASS_CACHE[name]
def clean(s):
2017-01-29 23:10:14 +00:00
# Remove invalid characters
s = re.sub('[^0-9a-zA-Z_]', '', s)
# Remove leading characters until we find a letter or underscore
s = re.sub('^[^a-zA-Z_]+', '', s)
return str(s)
2016-08-30 10:26:09 +00:00
class Profile(get_profile_class('default')):
"""
For users, who want to provide their profile
"""
2017-01-29 23:10:14 +00:00
2016-08-30 14:13:38 +00:00
def __init__(self, columns=None, features=None):
super(Profile, self).__init__()
2016-08-30 10:26:09 +00:00
self.columns = columns
2016-08-30 14:13:38 +00:00
self.features = features or {}
2016-08-30 10:26:09 +00:00
def get_columns(self, font):
if self.columns is not None:
2016-08-30 11:27:48 +00:00
return self.columns
2016-08-30 10:26:09 +00:00
return super(Profile, self).get_columns(font)