Moving to spec modules
This commit is contained in:
parent
ebc088198f
commit
5b09007939
25
README.md
25
README.md
@ -5,10 +5,27 @@ Generate labels for printing using HTML rendering.
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Customizable for most label sheet layouts.
|
* Customizable for most label sheet layouts.
|
||||||
|
* Partial sheet printing
|
||||||
* Custom data for each label
|
* Custom data for each label
|
||||||
* Uses HTML templates for easy customization
|
* Uses HTML templates easy label design.
|
||||||
|
|
||||||
## TODO
|
## Usage
|
||||||
|
|
||||||
|
The simplest use case is some static labels:
|
||||||
|
|
||||||
|
python -m pylabel -t examples/simple.html -p avery.L7157 -c 10 > output.html
|
||||||
|
|
||||||
|
Then, when you need some more just pop the same sheet back in the printer and offset the start
|
||||||
|
|
||||||
|
python -m pylabel -t examples/simple.html -p avery.L7157 -c 10 > output.html
|
||||||
|
|
||||||
|
To do some mail merge operations create a list of names:
|
||||||
|
|
||||||
|
Andy Andrews
|
||||||
|
Bob Brown
|
||||||
|
Charlie Chaplin
|
||||||
|
|
||||||
|
and use a `{data[text]}` element in your template. Note we've shifted to an external stylesheet now as well.
|
||||||
|
|
||||||
|
python -m pylabel -t examples/name_tags.html -p avery.L7157 -d examples/names.txt -s examples/hello.css > output.html
|
||||||
|
|
||||||
* Switch to profiles for command line usage
|
|
||||||
* Switch to
|
|
||||||
0
examples/__init__.py
Normal file
0
examples/__init__.py
Normal file
33
examples/hello.css
Normal file
33
examples/hello.css
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
DIV.label {
|
||||||
|
background-color: red;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
DIV.hello-tag {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-family: Arial;
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.greeting {
|
||||||
|
font-size: 20pt;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-family: 'Times New Roman', Times, serif;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20pt;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: white;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-bottom: 2mm;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
}
|
||||||
5
examples/name_tags.html
Normal file
5
examples/name_tags.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<div class="hello-tag";">
|
||||||
|
<div class="greeting">Hello</div>
|
||||||
|
<div>my name is</div>
|
||||||
|
<div class="name">{data[text]}</div>
|
||||||
|
</div>
|
||||||
3
examples/names.txt
Normal file
3
examples/names.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Andy Andrews
|
||||||
|
Bob Brown
|
||||||
|
Charlie Chaplin
|
||||||
5
examples/simple.html
Normal file
5
examples/simple.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<div style="height: 100%; display: flex; align-items: center; flex-direction: column; justify-content: space-around; font-family: Arial; font-size: 10pt;">
|
||||||
|
<div>Property of</div>
|
||||||
|
<div style="font-size: 20pt"><strong>Andy Andrews</strong></div>
|
||||||
|
<div>Please return to office if found</div>
|
||||||
|
</div>
|
||||||
15
examples/simple.py
Normal file
15
examples/simple.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from pylabel import avery
|
||||||
|
|
||||||
|
profile = avery.L6009
|
||||||
|
|
||||||
|
data = range(10)
|
||||||
|
|
||||||
|
template = """
|
||||||
|
Hello World
|
||||||
|
"""
|
||||||
|
|
||||||
|
style = """
|
||||||
|
.content {
|
||||||
|
font-family: Arial;
|
||||||
|
}
|
||||||
|
"""
|
||||||
@ -21,7 +21,7 @@ class LabelSheet(object):
|
|||||||
logger.debug(profile)
|
logger.debug(profile)
|
||||||
|
|
||||||
self.size = profile['size']
|
self.size = profile['size']
|
||||||
logger.debug(self.size)
|
logger.debug("Label size: %r", self.size)
|
||||||
|
|
||||||
# get the pagesize and rotate if landscape
|
# get the pagesize and rotate if landscape
|
||||||
pagesize = profile.get('pagesize', 'a4')
|
pagesize = profile.get('pagesize', 'a4')
|
||||||
@ -57,10 +57,20 @@ class LabelSheet(object):
|
|||||||
self.layout = layout
|
self.layout = layout
|
||||||
|
|
||||||
# Calculate gaps between labels
|
# Calculate gaps between labels
|
||||||
|
|
||||||
|
pitch = profile.get('pitch')
|
||||||
|
if pitch:
|
||||||
|
logger.debug("Pitch: %r", pitch)
|
||||||
|
self.gaps = (
|
||||||
|
pitch[0] - self.size[0],
|
||||||
|
pitch[1] - self.size[1],
|
||||||
|
)
|
||||||
|
else:
|
||||||
self.gaps = (
|
self.gaps = (
|
||||||
round((self.printarea[0] - layout[0]*self.size[0]) / (layout[0]-1), 1),
|
round((self.printarea[0] - layout[0]*self.size[0]) / (layout[0]-1), 1),
|
||||||
round((self.printarea[1] - layout[1]*self.size[1]) / (layout[1]-1), 1)
|
round((self.printarea[1] - layout[1]*self.size[1]) / (layout[1]-1), 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug("Gaps: %r", self.gaps)
|
logger.debug("Gaps: %r", self.gaps)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -87,7 +97,7 @@ DIV.label {{
|
|||||||
|
|
||||||
def render_page(self, template, data, skip=0):
|
def render_page(self, template, data, skip=0):
|
||||||
|
|
||||||
label_template = f'<div class="label"><div class="content">{template}</div></div>'
|
label_template = f'<div class="label"><div class="printable"><div class="content">{template}</div></div></div>'
|
||||||
|
|
||||||
blank_labels = "".join([ '<div class="label"><div class="content"> </div></div>' for x in range(skip) ])
|
blank_labels = "".join([ '<div class="label"><div class="content"> </div></div>' for x in range(skip) ])
|
||||||
|
|
||||||
|
|||||||
@ -3,30 +3,46 @@ import sys
|
|||||||
import logging
|
import logging
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
from . import LabelSheet
|
from pylabel import LabelSheet
|
||||||
|
|
||||||
|
class EmptySpec:
|
||||||
|
profile = None
|
||||||
|
template = None
|
||||||
|
data = None
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Generate a page of labels")
|
parser = argparse.ArgumentParser(description="Generate a page of labels")
|
||||||
|
|
||||||
parser.add_argument("profile", help="Profile to load")
|
parser.add_argument('--count', '-c', type=int, default=0, help="Number of labels to generate")
|
||||||
|
|
||||||
parser.add_argument('--style', '-s', type=argparse.FileType('r'), help='Additional stylesheet')
|
|
||||||
parser.add_argument('--skip', '-n', type=int, default=0, help="Skip first n labels")
|
parser.add_argument('--skip', '-n', type=int, default=0, help="Skip first n labels")
|
||||||
|
parser.add_argument('--profile', '-p', help='Label profile')
|
||||||
parser.add_argument('--template', '-t', type=argparse.FileType('r'), help='Template for label')
|
parser.add_argument('--template', '-t', type=argparse.FileType('r'), help='Template for label')
|
||||||
parser.add_argument('--json', '-j', action='store_true', help='Parse lines as json')
|
parser.add_argument('--style', '-s', type=argparse.FileType('r'), help='Additional stylesheet')
|
||||||
|
parser.add_argument('--data', '-d', type=argparse.FileType('r'), help="Pre-generated data, one entry per line")
|
||||||
|
parser.add_argument('--json', '-j', action='store_true', help='Parse data lines as json')
|
||||||
parser.add_argument('--logging', '-l', default="info", help="Logging level")
|
parser.add_argument('--logging', '-l', default="info", help="Logging level")
|
||||||
|
|
||||||
|
parser.add_argument("spec", nargs="?", help="Specification module")
|
||||||
|
|
||||||
options = parser.parse_args()
|
options = parser.parse_args()
|
||||||
|
|
||||||
logging.basicConfig(level=getattr(logging, options.logging.upper(), logging.INFO))
|
logging.basicConfig(level=getattr(logging, options.logging.upper(), logging.INFO))
|
||||||
|
|
||||||
logger.debug(options)
|
logger.debug(options)
|
||||||
|
|
||||||
|
if options.spec:
|
||||||
|
spec = importlib.import_module(options.spec)
|
||||||
|
else:
|
||||||
|
spec = EmptySpec
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if options.profile:
|
||||||
module, profile = options.profile.rsplit('.', 1)
|
module, profile = options.profile.rsplit('.', 1)
|
||||||
|
if module.startswith('avery'):
|
||||||
|
module = 'pylabel.avery'
|
||||||
m = importlib.import_module(module)
|
m = importlib.import_module(module)
|
||||||
p = getattr(m, profile)
|
spec.profile = getattr(m, profile)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RuntimeError("Expected profile in the form module.profile")
|
raise RuntimeError("Expected profile in the form module.profile")
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
@ -34,24 +50,31 @@ except ModuleNotFoundError:
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise RuntimeError(f"Unable to file profile {profile} in module {module}")
|
raise RuntimeError(f"Unable to file profile {profile} in module {module}")
|
||||||
|
|
||||||
sheet = LabelSheet(p)
|
|
||||||
|
|
||||||
extra_style=""
|
|
||||||
if options.style is not None:
|
|
||||||
extra_style = options.style.read()
|
|
||||||
options.style.close()
|
|
||||||
|
|
||||||
template = '<b>{data[text]}</b>'
|
|
||||||
if options.template is not None:
|
if options.template is not None:
|
||||||
template = options.template.read()
|
spec.template = options.template.read()
|
||||||
options.template.close()
|
options.template.close()
|
||||||
|
|
||||||
lines = [ l.strip() for l in sys.stdin.readlines() if l != '\n' ]
|
|
||||||
|
|
||||||
if options.json:
|
if options.style is not None:
|
||||||
|
spec.style = options.style.read()
|
||||||
|
options.style.close()
|
||||||
|
|
||||||
|
if options.data is not None:
|
||||||
|
lines = [ l.strip() for l in options.data.readlines() if l != '\n' ]
|
||||||
|
options.data.close()
|
||||||
|
if options.json:
|
||||||
import json
|
import json
|
||||||
data = [ json.loads(line) for line in lines ]
|
spec.data = [ json.loads(line) for line in lines ]
|
||||||
else:
|
else:
|
||||||
data = [ {'text': line} for line in lines ]
|
spec.data = [ {'text': line} for line in lines ]
|
||||||
|
|
||||||
sys.stdout.write(sheet.render_html(template, data, options.skip, extra_style))
|
if options.count:
|
||||||
|
spec.data = range(options.count)
|
||||||
|
|
||||||
|
for required in ('profile', 'template', 'data'):
|
||||||
|
if not getattr(spec, required):
|
||||||
|
parser.error(f"Missing {required}")
|
||||||
|
|
||||||
|
sheet = LabelSheet(spec.profile)
|
||||||
|
|
||||||
|
sys.stdout.write(sheet.render_html(spec.template, spec.data, options.skip, getattr(spec, 'style', '')))
|
||||||
@ -1,4 +1,11 @@
|
|||||||
|
|
||||||
|
L6009 = {
|
||||||
|
"size": (45.7, 21.2),
|
||||||
|
"layout": (4, 12),
|
||||||
|
"papersize": "a4",
|
||||||
|
"margins": (9.8, 21.4),
|
||||||
|
}
|
||||||
|
|
||||||
L7157 = {
|
L7157 = {
|
||||||
"size": (64, 24.3),
|
"size": (64, 24.3),
|
||||||
"layout": (3, 11),
|
"layout": (3, 11),
|
||||||
|
|||||||
@ -22,6 +22,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HTML, BODY {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
DIV.label {
|
DIV.label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
@ -29,5 +34,7 @@ DIV.label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DIV.content {
|
DIV.content {
|
||||||
margin: 2mm; /* Minimum to be safe */
|
/*margin: 2mm; /* Minimum to be safe */
|
||||||
|
height: 100%;
|
||||||
|
margin: 0 2mm;
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user