python-escpos/src/escpos/capabilities.py

157 lines
4.3 KiB
Python
Raw Normal View History

import re
from os import environ, path
import pkg_resources
import pickle
import logging
import time
import six
import yaml
from tempfile import gettempdir
import platform
2020-11-08 22:45:25 +00:00
from typing import Any, Dict
logging.basicConfig()
logger = logging.getLogger(__name__)
2021-10-30 16:15:22 +00:00
pickle_dir = environ.get("ESCPOS_CAPABILITIES_PICKLE_DIR", gettempdir())
pickle_path = path.join(
pickle_dir, "{v}.capabilities.pickle".format(v=platform.python_version())
)
# get a temporary file from pkg_resources if no file is specified in env
2021-10-30 16:15:22 +00:00
capabilities_path = environ.get(
"ESCPOS_CAPABILITIES_FILE",
pkg_resources.resource_filename(__name__, "capabilities.json"),
)
2016-08-30 10:26:09 +00:00
# Load external printer database
t0 = time.time()
2021-10-30 16:15:22 +00:00
logger.debug("Using capabilities from file: %s", capabilities_path)
if path.exists(pickle_path):
if path.getmtime(capabilities_path) > path.getmtime(pickle_path):
2021-10-30 16:15:22 +00:00
logger.debug("Found a more recent capabilities file")
full_load = True
else:
full_load = False
2021-10-30 16:15:22 +00:00
logger.debug("Loading capabilities from pickle in %s", pickle_path)
with open(pickle_path, "rb") as cf:
CAPABILITIES = pickle.load(cf)
else:
2021-10-30 16:15:22 +00:00
logger.debug("Capabilities pickle file not found: %s", pickle_path)
full_load = True
if full_load:
2021-10-30 16:15:22 +00:00
logger.debug("Loading and pickling capabilities")
with open(capabilities_path) as cp, open(pickle_path, "wb") as pp:
CAPABILITIES = yaml.safe_load(cp)
pickle.dump(CAPABILITIES, pp, protocol=2)
2021-10-30 16:15:22 +00:00
logger.debug("Finished loading capabilities took %.2fs", time.time() - t0)
2021-10-30 16:15:22 +00:00
PROFILES: Dict[str, Any] = 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.
"""
2021-10-30 16:15:22 +00:00
2016-08-30 10:26:09 +00:00
pass
2021-10-30 16:15:22 +00:00
BARCODE_B = "barcodeB"
2016-08-30 10:26:09 +00:00
class BaseProfile(object):
"""This represents a printer profile.
2016-08-30 10:26:09 +00:00
A printer profile knows about the number of columns, supported
features, colors and more.
"""
2020-11-08 22:45:25 +00:00
profile_data: Dict[str, Any] = {}
def __getattr__(self, name):
return self.profile_data[name]
2020-11-08 22:45:25 +00:00
def get_font(self, font) -> int:
2016-08-30 10:26:09 +00:00
"""Return the escpos index for `font`. Makes sure that
the requested `font` is valid.
"""
2021-10-30 16:15:22 +00:00
font = {"a": 0, "b": 1}.get(font, font)
2016-08-30 10:26:09 +00:00
if not six.text_type(font) in self.fonts:
raise NotSupported(
2021-10-30 16:15:22 +00:00
'"{}" 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):
2021-10-30 16:15:22 +00:00
"""Return the number of columns for the given font."""
2016-08-30 10:26:09 +00:00
font = self.get_font(font)
2021-10-30 16:15:22 +00:00
return self.fonts[six.text_type(font)]["columns"]
def supports(self, feature):
2021-10-30 16:15:22 +00:00
"""Return true/false for the given feature."""
return self.features.get(feature)
def get_code_pages(self):
2021-10-30 16:15:22 +00:00
"""Return the support code pages as a ``{name: index}`` dict."""
return {v: k for k, v in self.codePages.items()}
2021-10-30 16:15:22 +00:00
def get_profile(name: str = 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
2021-10-30 16:15:22 +00:00
clazz = get_profile_class(name or "default")
return clazz(**kwargs)
CLASS_CACHE = {}
2020-11-08 22:45:25 +00:00
def get_profile_class(name: str):
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)
2021-10-30 16:15:22 +00:00
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
2021-10-30 16:15:22 +00:00
s = re.sub("[^0-9a-zA-Z_]", "", s)
2017-01-29 23:10:14 +00:00
# Remove leading characters until we find a letter or underscore
2021-10-30 16:15:22 +00:00
s = re.sub("^[^a-zA-Z_]+", "", s)
2017-01-29 23:10:14 +00:00
return str(s)
2021-10-30 16:15:22 +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)