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
|
||||
|
||||
* Customizable for most label sheet layouts.
|
||||
* Partial sheet printing
|
||||
* 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)
|
||||
|
||||
self.size = profile['size']
|
||||
logger.debug(self.size)
|
||||
logger.debug("Label size: %r", self.size)
|
||||
|
||||
# get the pagesize and rotate if landscape
|
||||
pagesize = profile.get('pagesize', 'a4')
|
||||
@ -57,10 +57,20 @@ class LabelSheet(object):
|
||||
self.layout = layout
|
||||
|
||||
# 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 = (
|
||||
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)
|
||||
)
|
||||
|
||||
logger.debug("Gaps: %r", self.gaps)
|
||||
|
||||
@property
|
||||
@ -87,7 +97,7 @@ DIV.label {{
|
||||
|
||||
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) ])
|
||||
|
||||
|
||||
@ -3,30 +3,46 @@ import sys
|
||||
import logging
|
||||
import importlib
|
||||
|
||||
from . import LabelSheet
|
||||
from pylabel import LabelSheet
|
||||
|
||||
class EmptySpec:
|
||||
profile = None
|
||||
template = None
|
||||
data = None
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
parser = argparse.ArgumentParser(description="Generate a page of labels")
|
||||
|
||||
parser.add_argument("profile", help="Profile to load")
|
||||
|
||||
parser.add_argument('--style', '-s', type=argparse.FileType('r'), help='Additional stylesheet')
|
||||
parser.add_argument('--count', '-c', type=int, default=0, help="Number of labels to generate")
|
||||
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('--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("spec", nargs="?", help="Specification module")
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
logging.basicConfig(level=getattr(logging, options.logging.upper(), logging.INFO))
|
||||
|
||||
logger.debug(options)
|
||||
|
||||
if options.spec:
|
||||
spec = importlib.import_module(options.spec)
|
||||
else:
|
||||
spec = EmptySpec
|
||||
|
||||
try:
|
||||
if options.profile:
|
||||
module, profile = options.profile.rsplit('.', 1)
|
||||
if module.startswith('avery'):
|
||||
module = 'pylabel.avery'
|
||||
m = importlib.import_module(module)
|
||||
p = getattr(m, profile)
|
||||
spec.profile = getattr(m, profile)
|
||||
except ValueError:
|
||||
raise RuntimeError("Expected profile in the form module.profile")
|
||||
except ModuleNotFoundError:
|
||||
@ -34,24 +50,31 @@ except ModuleNotFoundError:
|
||||
except AttributeError:
|
||||
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:
|
||||
template = options.template.read()
|
||||
spec.template = options.template.read()
|
||||
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
|
||||
data = [ json.loads(line) for line in lines ]
|
||||
else:
|
||||
data = [ {'text': line} for line in lines ]
|
||||
spec.data = [ json.loads(line) for line in lines ]
|
||||
else:
|
||||
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 = {
|
||||
"size": (64, 24.3),
|
||||
"layout": (3, 11),
|
||||
|
||||
@ -22,6 +22,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
HTML, BODY {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
DIV.label {
|
||||
display: inline-block;
|
||||
background-color: #FFF;
|
||||
@ -29,5 +34,7 @@ DIV.label {
|
||||
}
|
||||
|
||||
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