Skip to content

Commit 6870ae5

Browse files
committed
v0.3.0: Fix empty-card bug, multi-copy cards, pip packagable
1 parent 3d47e01 commit 6870ae5

File tree

7 files changed

+97
-44
lines changed

7 files changed

+97
-44
lines changed

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,29 @@ Proxy Printer
44
Generates reasonably good-looking HTML proxies for a card game from an ODS spreadsheet. Useful for making card game prototypes. Comes with some basic CSS; you can add your own style rules to make the proxies prettier or better suited to your game. Does some heuristic text resizing so that text of different lengths is more likely to fit without being too small.
55

66

7-
Setup
7+
Installing with pip
88
------
99

10-
* Python 3 required
11-
* Install [pyexcel-ods3](https://github.com/pyexcel/pyexcel-ods3)
10+
(Python 3 required)
11+
12+
```
13+
pip3 install proxyprinter
14+
```
15+
16+
(You might need to use `sudo` or a VirtualEnv depending on your system setup.)
17+
1218

1319
Usage
1420
------
1521

16-
./proxyprinter.py cards.ods > output_file.html
22+
proxyprinter example-cards.ods > output_file.html
1723

18-
Do `./proxyprinter.py --help` for usage statement with all commandline options.
24+
Do `proxyprinter --help` for usage statement with all commandline options.
1925

2026

2127
Input Format
2228
-------------
23-
OpenDocument Spreadsheet (ODF) file. Example: [cards.ods](cards.ods). Each "Sheet" in the document is one type of card in your game. The name of the sheet is the card type. The first row in the sheet lists the titles for each field. Each subsequent row is a card.
29+
OpenDocument Spreadsheet (ODF) file. Example: [example-cards.ods](example-cards.ods). Each "Sheet" in the document is one type of card in your game. The name of the sheet is the card type. The first row in the sheet lists the titles for each field. Each subsequent row is a card.
2430

2531
The following field names are special in some way:
2632

@@ -29,6 +35,7 @@ The following field names are special in some way:
2935
- **Text**: Gets put in a single text_area alongside Flavor Text.
3036
- **Flavor Text**: Gets put in a single text_area alongside Text.
3137
- **Version**: Listed in the footer. Use this with the `-v` switch to only print recently-updated cards.
38+
- **Copies:** If present and a non-negative integer, prints that many copies of the card as part of the overall print sheet. (Otherwise, the print sheet contains 1 copy of this card.)
3239

3340

3441
In-Stylesheet Settings

cards.ods

-13.2 KB
Binary file not shown.

example-cards.ods

10.2 KB
Binary file not shown.

proxyprinter/__init__.py

Whitespace-only changes.
Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,24 @@
66
import argparse
77
import pyexcel_ods3 as pyexcel
88
import hashlib
9+
import logging
910
from cgi import escape
1011
from random import randint
11-
from logging import warning
1212
from collections import OrderedDict
1313
from time import strftime
14+
from pkg_resources import resource_string
1415

1516
SPECIAL_FIELDS = [
1617
"Name", #Title of the card
1718
"Traits", #Comma-separated list of tags/classes
1819
"Text", #Shares a box with flavor text
1920
"Flavor Text", #Italicized, follows text
2021
"Version", #Appears in footer; can be used to print only updated cards
22+
"Copies", #Print the same card this many times
2123
]
2224

23-
DEFAULT_STYLE_FILE = "proxyprinter.css"
25+
DEFAULT_STYLE = resource_string(__name__, "proxyprinter.css").decode('utf-8')
26+
2427
DEFAULT_TEXT_SIZING_THRESHOLDS = {
2528
"*": (30, 50),
2629
"Text": (140, 220),
@@ -41,6 +44,11 @@
4144
SETTING_LABEL_PROCESSPATTERNS = "ProcessPatterns"
4245
SETTING_LABEL_PROCESSREPLACEMENTS = "ProcessReplacements"
4346

47+
48+
#Set up logging
49+
logger = logging.getLogger(__name__)
50+
logger.addHandler(logging.StreamHandler())
51+
4452
def escape_html(s):
4553
#like cgi.escape, but undo escaping  
4654
return escape(s).replace(" ", " ")
@@ -52,16 +60,18 @@ def replace_all(s, replacements):
5260

5361
def twod_array_to_ordered_dict_array(array2d):
5462
if len(array2d) < 2 or type(array2d[0]) != list:
55-
warning("Not a 2d array?")
63+
logger.warning("Not a 2d array?")
5664
return []
5765

5866
keys = array2d[0]
5967
vals = array2d[1:]
6068
od_rows = []
6169
for row in vals:
70+
if not row: #skip empty rows
71+
continue
6272
od = OrderedDict()
6373
if len(keys) != len(row):
64-
warning("Mismatched number of fields in row: %s" % row)
74+
logger.info("Mismatched number of fields in row: %s" % row)
6575

6676
for i in range(0, len(keys)):
6777
if i >= len(row):
@@ -255,17 +265,17 @@ def parse_settings(self):
255265
self.skip_sheets = [SETTING_SHEET_LABEL]
256266
self.size_thresholds = DEFAULT_TEXT_SIZING_THRESHOLDS
257267
if type(self.sheet) != OrderedDict:
258-
warning("Single page sheet (%s); no settings pulled"%type(self.sheet))
268+
logger.info("Single page sheet (%s); no settings pulled"%type(self.sheet))
259269
# Single page sheet; no custom settings defined
260270
return
261271
elif SETTING_SHEET_LABEL not in self.sheet.keys():
262-
warning("No settings sheet found")
272+
logger.info("No settings sheet found")
263273
# No settings sheet; no custom settings defined
264274
return
265275

266276
settings_sheet = self.sheet[SETTING_SHEET_LABEL]
267277
if len(settings_sheet) < 2:
268-
warning("Less than 2 rows in settings sheet")
278+
logger.info("Less than 2 rows in settings sheet")
269279
return
270280

271281
setting_keys = settings_sheet[0]
@@ -277,7 +287,7 @@ def parse_settings(self):
277287
self.addcss = setting_simple_values[
278288
setting_keys.index(SETTING_LABEL_CSSFILE)]
279289
except ValueError:
280-
warning("Failed to get addcss value from settings")
290+
logger.info("Failed to get addcss value from settings")
281291
else:
282292
print("ADDCSS",self.addcss, type(self.addcss))
283293

@@ -287,7 +297,7 @@ def parse_settings(self):
287297
self.copyowner = setting_simple_values[
288298
setting_keys.index(SETTING_LABEL_COPYRIGHT)]
289299
except ValueError:
290-
warning("Failed to get copyright value from settings")
300+
logger.info("Failed to get copyright value from settings")
291301

292302
# Setting: Text Size Thresholds
293303
try:
@@ -296,7 +306,7 @@ def parse_settings(self):
296306
pos_textsizesmall = setting_keys.index(SETTING_LABEL_TEXTSIZETHRESHOLD2)
297307
got_textsize_settings = True
298308
except ValueError:
299-
warning("Failed to get text size thresholds from settings")
309+
logger.info("Failed to get text size thresholds from settings")
300310
got_textsize_settings = False
301311

302312
if got_textsize_settings:
@@ -309,7 +319,7 @@ def parse_settings(self):
309319
else:
310320
threshold_med, threshold_sm = self.size_thresholds["*"]
311321
else:
312-
warning("Text Thresholds: Skipping row %s"%row)
322+
logger.debug("Text Thresholds: Skipping row %s"%row)
313323
continue
314324

315325
if len(row) > pos_textsizemed and row[pos_textsizemed]:
@@ -325,7 +335,7 @@ def parse_settings(self):
325335
pos_richfields = setting_keys.index(SETTING_LABEL_RICHFIELDS)
326336
got_richfield_settings = True
327337
except ValueError:
328-
warning("Failed to get rich text settings")
338+
logger.info("Failed to get rich text settings")
329339
got_richfield_settings = False
330340

331341
if got_richfield_settings:
@@ -343,7 +353,7 @@ def parse_settings(self):
343353
pos_processreplacements = setting_keys.index(SETTING_LABEL_PROCESSREPLACEMENTS)
344354
got_textprocessing_settings = True
345355
except ValueError:
346-
warning("Failed to get text processing settings")
356+
logger.info("Failed to get text processing settings")
347357
got_textprocessing_settings = False
348358

349359
if got_textprocessing_settings:
@@ -404,43 +414,29 @@ def int_from_string_hash(s, min_i, max_i, encoding="utf-8"):
404414
def render_all(self):
405415
s = "<!DOCTYPE html>\n<html>\n<head>\n"
406416
if self.defaultcss:
407-
with open(DEFAULT_STYLE_FILE,"r") as f:
408-
s += "<style type='text/css'>%s</style>" % f.read()
409-
#randomly colorize traits
417+
s += "<style type='text/css'>%s</style>" % DEFAULT_STYLE
418+
#randomly colorize traits
410419
if self.colorize:
411420
s += "<style type='text/css'>%s</style>" % self.trait_colors_css()
412421
if self.addcss:
413422
s += "<link rel='stylesheet' href='%s' />" % self.addcss
414423
s += "</head><body>"
415424

416425
for c in self.cards:
417-
s += c.html()
426+
s_copies = c.fields.get("Copies", 1)
427+
try:
428+
copies = int(s_copies)
429+
except ValueError:
430+
copies = 1
431+
if copies < 0:
432+
copies = 1
433+
s += c.html()*copies
418434

419435
s += "</body></html>"
420436
return s
421437

422-
# def all_cards(spreadsheet, copyowner, version=None, addcss=None, defaultcss=True):
423-
# allcards = []
424-
# spreadsheet_data = pyexcel.get_data(spreadsheet)
425-
# # A single-sheet file comes back as a 2d array;
426-
# # a multi-sheet file comes back as an OrderedDict of sheet names to 2d arrays
427-
# if type(spreadsheet_data) == OrderedDict:
428-
# pages = spreadsheet_data.items()
429-
# else:
430-
# pages = {"-": spreadsheet_data}.items()
431-
# for sheetname, sheetdata in pages:
432-
# cardtype = sheetname
433-
# cardrows = twod_array_to_ordered_dict_array(sheetdata)
434-
# for row in cardrows:
435-
# if cli_args.version:
436-
# if "Version" not in row or str(row["Version"]) != version:
437-
# continue
438-
# c = Card(cardtype=sheetname, fields=row, copyowner=copyowner)
439-
# allcards.append(c)
440438

441-
442-
443-
if __name__ == "__main__":
439+
def main():
444440
parser = argparse.ArgumentParser(
445441
description="Generate card images in HTML from spreadsheet.")
446442
parser.add_argument("spreadsheet", type=str,
@@ -464,3 +460,6 @@ def render_all(self):
464460
version=cli_args.version, defaultcss=defaultcss, addcss=cli_args.css,
465461
colorize=colorize)
466462
print( pp.render_all() )
463+
464+
if __name__ == "__main__":
465+
main()

setup.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""setuptools based setup module"""
2+
3+
from setuptools import setup
4+
5+
# Convert the markdown readme to ReST using Pandoc
6+
try:
7+
import pypandoc
8+
long_description = pypandoc.convert('README.md', 'rst')
9+
except ImportError:
10+
long_description = open('README.md').read()
11+
12+
setup(
13+
name='proxyprinter',
14+
version='0.3.0',
15+
description='Generate card game mockups from .ods spreadsheets',
16+
long_description=long_description,
17+
url='https://github.com/mduo13/proxyprinter',
18+
author='Rome Reginelli',
19+
author_email='mduo13@gmail.com',
20+
license='MIT',
21+
classifiers=[
22+
'Development Status :: 4 - Beta',
23+
'Environment :: Console',
24+
'Intended Audience :: End Users/Desktop',
25+
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
26+
'Operating System :: POSIX',
27+
'Programming Language :: Python :: 3',
28+
'Programming Language :: Python :: 3.4',
29+
'Topic :: Utilities',
30+
'Topic :: Games/Entertainment :: Board Games',
31+
],
32+
keywords='games mockups proxying design',
33+
packages=[
34+
'proxyprinter',
35+
],
36+
entry_points={
37+
'console_scripts': [
38+
'proxyprinter = proxyprinter.proxyprinter:main',
39+
],
40+
},
41+
install_requires=[
42+
'pyexcel-ods3',
43+
],
44+
package_data={
45+
'': ["proxyprinter.css"],
46+
}
47+
)

0 commit comments

Comments
 (0)