Merge remote-tracking branch 'origin/refactor'
This commit is contained in:
commit
6aa9c28c80
9
.travis.yml
Normal file
9
.travis.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
language: python
|
||||||
|
python:
|
||||||
|
- "2.7"
|
||||||
|
# command to install dependencies
|
||||||
|
install:
|
||||||
|
- pip install -U setuptools
|
||||||
|
- pip install .
|
||||||
|
# command to run tests
|
||||||
|
script: nosetests
|
||||||
@ -1,7 +1,3 @@
|
|||||||
include dominion_cards.txt
|
include card_db/*/*.json
|
||||||
include dominion_card_extras.txt
|
|
||||||
include *.png
|
|
||||||
include images/*.png
|
include images/*.png
|
||||||
exclude card_images/*
|
|
||||||
exclude old_images/*
|
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
# main package
|
# main package
|
||||||
|
|
||||||
__version__ = '1.9.1'
|
__version__ = '2.0'
|
||||||
|
|||||||
@ -1,515 +0,0 @@
|
|||||||
#!python
|
|
||||||
"""Bootstrap distribute installation
|
|
||||||
|
|
||||||
If you want to use setuptools in your package's setup.py, just include this
|
|
||||||
file in the same directory with it, and add this to the top of your setup.py::
|
|
||||||
|
|
||||||
from distribute_setup import use_setuptools
|
|
||||||
use_setuptools()
|
|
||||||
|
|
||||||
If you want to require a specific version of setuptools, set a download
|
|
||||||
mirror, or use an alternate download directory, you can do so by supplying
|
|
||||||
the appropriate options to ``use_setuptools()``.
|
|
||||||
|
|
||||||
This file can also be run as a script to install or upgrade setuptools.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import fnmatch
|
|
||||||
import tempfile
|
|
||||||
import tarfile
|
|
||||||
from distutils import log
|
|
||||||
|
|
||||||
try:
|
|
||||||
from site import USER_SITE
|
|
||||||
except ImportError:
|
|
||||||
USER_SITE = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
def _python_cmd(*args):
|
|
||||||
args = (sys.executable,) + args
|
|
||||||
return subprocess.call(args) == 0
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# will be used for python 2.3
|
|
||||||
def _python_cmd(*args):
|
|
||||||
args = (sys.executable,) + args
|
|
||||||
# quoting arguments if windows
|
|
||||||
if sys.platform == 'win32':
|
|
||||||
def quote(arg):
|
|
||||||
if ' ' in arg:
|
|
||||||
return '"%s"' % arg
|
|
||||||
return arg
|
|
||||||
args = [quote(arg) for arg in args]
|
|
||||||
return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
|
|
||||||
|
|
||||||
DEFAULT_VERSION = "0.6.28"
|
|
||||||
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
|
|
||||||
SETUPTOOLS_FAKED_VERSION = "0.6c11"
|
|
||||||
|
|
||||||
SETUPTOOLS_PKG_INFO = """\
|
|
||||||
Metadata-Version: 1.0
|
|
||||||
Name: setuptools
|
|
||||||
Version: %s
|
|
||||||
Summary: xxxx
|
|
||||||
Home-page: xxx
|
|
||||||
Author: xxx
|
|
||||||
Author-email: xxx
|
|
||||||
License: xxx
|
|
||||||
Description: xxx
|
|
||||||
""" % SETUPTOOLS_FAKED_VERSION
|
|
||||||
|
|
||||||
|
|
||||||
def _install(tarball, install_args=()):
|
|
||||||
# extracting the tarball
|
|
||||||
tmpdir = tempfile.mkdtemp()
|
|
||||||
log.warn('Extracting in %s', tmpdir)
|
|
||||||
old_wd = os.getcwd()
|
|
||||||
try:
|
|
||||||
os.chdir(tmpdir)
|
|
||||||
tar = tarfile.open(tarball)
|
|
||||||
_extractall(tar)
|
|
||||||
tar.close()
|
|
||||||
|
|
||||||
# going in the directory
|
|
||||||
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
|
|
||||||
os.chdir(subdir)
|
|
||||||
log.warn('Now working in %s', subdir)
|
|
||||||
|
|
||||||
# installing
|
|
||||||
log.warn('Installing Distribute')
|
|
||||||
if not _python_cmd('setup.py', 'install', *install_args):
|
|
||||||
log.warn('Something went wrong during the installation.')
|
|
||||||
log.warn('See the error message above.')
|
|
||||||
finally:
|
|
||||||
os.chdir(old_wd)
|
|
||||||
|
|
||||||
|
|
||||||
def _build_egg(egg, tarball, to_dir):
|
|
||||||
# extracting the tarball
|
|
||||||
tmpdir = tempfile.mkdtemp()
|
|
||||||
log.warn('Extracting in %s', tmpdir)
|
|
||||||
old_wd = os.getcwd()
|
|
||||||
try:
|
|
||||||
os.chdir(tmpdir)
|
|
||||||
tar = tarfile.open(tarball)
|
|
||||||
_extractall(tar)
|
|
||||||
tar.close()
|
|
||||||
|
|
||||||
# going in the directory
|
|
||||||
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
|
|
||||||
os.chdir(subdir)
|
|
||||||
log.warn('Now working in %s', subdir)
|
|
||||||
|
|
||||||
# building an egg
|
|
||||||
log.warn('Building a Distribute egg in %s', to_dir)
|
|
||||||
_python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
|
|
||||||
|
|
||||||
finally:
|
|
||||||
os.chdir(old_wd)
|
|
||||||
# returning the result
|
|
||||||
log.warn(egg)
|
|
||||||
if not os.path.exists(egg):
|
|
||||||
raise IOError('Could not build the egg.')
|
|
||||||
|
|
||||||
|
|
||||||
def _do_download(version, download_base, to_dir, download_delay):
|
|
||||||
egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
|
|
||||||
% (version, sys.version_info[0], sys.version_info[1]))
|
|
||||||
if not os.path.exists(egg):
|
|
||||||
tarball = download_setuptools(version, download_base,
|
|
||||||
to_dir, download_delay)
|
|
||||||
_build_egg(egg, tarball, to_dir)
|
|
||||||
sys.path.insert(0, egg)
|
|
||||||
import setuptools
|
|
||||||
setuptools.bootstrap_install_from = egg
|
|
||||||
|
|
||||||
|
|
||||||
def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
|
|
||||||
to_dir=os.curdir, download_delay=15, no_fake=True):
|
|
||||||
# making sure we use the absolute path
|
|
||||||
to_dir = os.path.abspath(to_dir)
|
|
||||||
was_imported = 'pkg_resources' in sys.modules or \
|
|
||||||
'setuptools' in sys.modules
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
import pkg_resources
|
|
||||||
if not hasattr(pkg_resources, '_distribute'):
|
|
||||||
if not no_fake:
|
|
||||||
_fake_setuptools()
|
|
||||||
raise ImportError
|
|
||||||
except ImportError:
|
|
||||||
return _do_download(version, download_base, to_dir, download_delay)
|
|
||||||
try:
|
|
||||||
pkg_resources.require("distribute>=" + version)
|
|
||||||
return
|
|
||||||
except pkg_resources.VersionConflict:
|
|
||||||
e = sys.exc_info()[1]
|
|
||||||
if was_imported:
|
|
||||||
sys.stderr.write(
|
|
||||||
"The required version of distribute (>=%s) is not available,\n"
|
|
||||||
"and can't be installed while this script is running. Please\n"
|
|
||||||
"install a more recent version first, using\n"
|
|
||||||
"'easy_install -U distribute'."
|
|
||||||
"\n\n(Currently using %r)\n" % (version, e.args[0]))
|
|
||||||
sys.exit(2)
|
|
||||||
else:
|
|
||||||
del pkg_resources, sys.modules['pkg_resources'] # reload ok
|
|
||||||
return _do_download(version, download_base, to_dir,
|
|
||||||
download_delay)
|
|
||||||
except pkg_resources.DistributionNotFound:
|
|
||||||
return _do_download(version, download_base, to_dir,
|
|
||||||
download_delay)
|
|
||||||
finally:
|
|
||||||
if not no_fake:
|
|
||||||
_create_fake_setuptools_pkg_info(to_dir)
|
|
||||||
|
|
||||||
|
|
||||||
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
|
|
||||||
to_dir=os.curdir, delay=15):
|
|
||||||
"""Download distribute from a specified location and return its filename
|
|
||||||
|
|
||||||
`version` should be a valid distribute version number that is available
|
|
||||||
as an egg for download under the `download_base` URL (which should end
|
|
||||||
with a '/'). `to_dir` is the directory where the egg will be downloaded.
|
|
||||||
`delay` is the number of seconds to pause before an actual download
|
|
||||||
attempt.
|
|
||||||
"""
|
|
||||||
# making sure we use the absolute path
|
|
||||||
to_dir = os.path.abspath(to_dir)
|
|
||||||
try:
|
|
||||||
from urllib.request import urlopen
|
|
||||||
except ImportError:
|
|
||||||
from urllib2 import urlopen
|
|
||||||
tgz_name = "distribute-%s.tar.gz" % version
|
|
||||||
url = download_base + tgz_name
|
|
||||||
saveto = os.path.join(to_dir, tgz_name)
|
|
||||||
src = dst = None
|
|
||||||
if not os.path.exists(saveto): # Avoid repeated downloads
|
|
||||||
try:
|
|
||||||
log.warn("Downloading %s", url)
|
|
||||||
src = urlopen(url)
|
|
||||||
# Read/write all in one block, so we don't create a corrupt file
|
|
||||||
# if the download is interrupted.
|
|
||||||
data = src.read()
|
|
||||||
dst = open(saveto, "wb")
|
|
||||||
dst.write(data)
|
|
||||||
finally:
|
|
||||||
if src:
|
|
||||||
src.close()
|
|
||||||
if dst:
|
|
||||||
dst.close()
|
|
||||||
return os.path.realpath(saveto)
|
|
||||||
|
|
||||||
|
|
||||||
def _no_sandbox(function):
|
|
||||||
def __no_sandbox(*args, **kw):
|
|
||||||
try:
|
|
||||||
from setuptools.sandbox import DirectorySandbox
|
|
||||||
if not hasattr(DirectorySandbox, '_old'):
|
|
||||||
def violation(*args):
|
|
||||||
pass
|
|
||||||
DirectorySandbox._old = DirectorySandbox._violation
|
|
||||||
DirectorySandbox._violation = violation
|
|
||||||
patched = True
|
|
||||||
else:
|
|
||||||
patched = False
|
|
||||||
except ImportError:
|
|
||||||
patched = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
return function(*args, **kw)
|
|
||||||
finally:
|
|
||||||
if patched:
|
|
||||||
DirectorySandbox._violation = DirectorySandbox._old
|
|
||||||
del DirectorySandbox._old
|
|
||||||
|
|
||||||
return __no_sandbox
|
|
||||||
|
|
||||||
|
|
||||||
def _patch_file(path, content):
|
|
||||||
"""Will backup the file then patch it"""
|
|
||||||
existing_content = open(path).read()
|
|
||||||
if existing_content == content:
|
|
||||||
# already patched
|
|
||||||
log.warn('Already patched.')
|
|
||||||
return False
|
|
||||||
log.warn('Patching...')
|
|
||||||
_rename_path(path)
|
|
||||||
f = open(path, 'w')
|
|
||||||
try:
|
|
||||||
f.write(content)
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
return True
|
|
||||||
|
|
||||||
_patch_file = _no_sandbox(_patch_file)
|
|
||||||
|
|
||||||
|
|
||||||
def _same_content(path, content):
|
|
||||||
return open(path).read() == content
|
|
||||||
|
|
||||||
|
|
||||||
def _rename_path(path):
|
|
||||||
new_name = path + '.OLD.%s' % time.time()
|
|
||||||
log.warn('Renaming %s into %s', path, new_name)
|
|
||||||
os.rename(path, new_name)
|
|
||||||
return new_name
|
|
||||||
|
|
||||||
|
|
||||||
def _remove_flat_installation(placeholder):
|
|
||||||
if not os.path.isdir(placeholder):
|
|
||||||
log.warn('Unkown installation at %s', placeholder)
|
|
||||||
return False
|
|
||||||
found = False
|
|
||||||
for file in os.listdir(placeholder):
|
|
||||||
if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
if not found:
|
|
||||||
log.warn('Could not locate setuptools*.egg-info')
|
|
||||||
return
|
|
||||||
|
|
||||||
log.warn('Removing elements out of the way...')
|
|
||||||
pkg_info = os.path.join(placeholder, file)
|
|
||||||
if os.path.isdir(pkg_info):
|
|
||||||
patched = _patch_egg_dir(pkg_info)
|
|
||||||
else:
|
|
||||||
patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
|
|
||||||
|
|
||||||
if not patched:
|
|
||||||
log.warn('%s already patched.', pkg_info)
|
|
||||||
return False
|
|
||||||
# now let's move the files out of the way
|
|
||||||
for element in ('setuptools', 'pkg_resources.py', 'site.py'):
|
|
||||||
element = os.path.join(placeholder, element)
|
|
||||||
if os.path.exists(element):
|
|
||||||
_rename_path(element)
|
|
||||||
else:
|
|
||||||
log.warn('Could not find the %s element of the '
|
|
||||||
'Setuptools distribution', element)
|
|
||||||
return True
|
|
||||||
|
|
||||||
_remove_flat_installation = _no_sandbox(_remove_flat_installation)
|
|
||||||
|
|
||||||
|
|
||||||
def _after_install(dist):
|
|
||||||
log.warn('After install bootstrap.')
|
|
||||||
placeholder = dist.get_command_obj('install').install_purelib
|
|
||||||
_create_fake_setuptools_pkg_info(placeholder)
|
|
||||||
|
|
||||||
|
|
||||||
def _create_fake_setuptools_pkg_info(placeholder):
|
|
||||||
if not placeholder or not os.path.exists(placeholder):
|
|
||||||
log.warn('Could not find the install location')
|
|
||||||
return
|
|
||||||
pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
|
|
||||||
setuptools_file = 'setuptools-%s-py%s.egg-info' % \
|
|
||||||
(SETUPTOOLS_FAKED_VERSION, pyver)
|
|
||||||
pkg_info = os.path.join(placeholder, setuptools_file)
|
|
||||||
if os.path.exists(pkg_info):
|
|
||||||
log.warn('%s already exists', pkg_info)
|
|
||||||
return
|
|
||||||
|
|
||||||
if not os.access(pkg_info, os.W_OK):
|
|
||||||
log.warn("Don't have permissions to write %s, skipping", pkg_info)
|
|
||||||
|
|
||||||
log.warn('Creating %s', pkg_info)
|
|
||||||
f = open(pkg_info, 'w')
|
|
||||||
try:
|
|
||||||
f.write(SETUPTOOLS_PKG_INFO)
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
pth_file = os.path.join(placeholder, 'setuptools.pth')
|
|
||||||
log.warn('Creating %s', pth_file)
|
|
||||||
f = open(pth_file, 'w')
|
|
||||||
try:
|
|
||||||
f.write(os.path.join(os.curdir, setuptools_file))
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
_create_fake_setuptools_pkg_info = _no_sandbox(
|
|
||||||
_create_fake_setuptools_pkg_info
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _patch_egg_dir(path):
|
|
||||||
# let's check if it's already patched
|
|
||||||
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
|
|
||||||
if os.path.exists(pkg_info):
|
|
||||||
if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
|
|
||||||
log.warn('%s already patched.', pkg_info)
|
|
||||||
return False
|
|
||||||
_rename_path(path)
|
|
||||||
os.mkdir(path)
|
|
||||||
os.mkdir(os.path.join(path, 'EGG-INFO'))
|
|
||||||
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
|
|
||||||
f = open(pkg_info, 'w')
|
|
||||||
try:
|
|
||||||
f.write(SETUPTOOLS_PKG_INFO)
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
return True
|
|
||||||
|
|
||||||
_patch_egg_dir = _no_sandbox(_patch_egg_dir)
|
|
||||||
|
|
||||||
|
|
||||||
def _before_install():
|
|
||||||
log.warn('Before install bootstrap.')
|
|
||||||
_fake_setuptools()
|
|
||||||
|
|
||||||
|
|
||||||
def _under_prefix(location):
|
|
||||||
if 'install' not in sys.argv:
|
|
||||||
return True
|
|
||||||
args = sys.argv[sys.argv.index('install') + 1:]
|
|
||||||
for index, arg in enumerate(args):
|
|
||||||
for option in ('--root', '--prefix'):
|
|
||||||
if arg.startswith('%s=' % option):
|
|
||||||
top_dir = arg.split('root=')[-1]
|
|
||||||
return location.startswith(top_dir)
|
|
||||||
elif arg == option:
|
|
||||||
if len(args) > index:
|
|
||||||
top_dir = args[index + 1]
|
|
||||||
return location.startswith(top_dir)
|
|
||||||
if arg == '--user' and USER_SITE is not None:
|
|
||||||
return location.startswith(USER_SITE)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def _fake_setuptools():
|
|
||||||
log.warn('Scanning installed packages')
|
|
||||||
try:
|
|
||||||
import pkg_resources
|
|
||||||
except ImportError:
|
|
||||||
# we're cool
|
|
||||||
log.warn('Setuptools or Distribute does not seem to be installed.')
|
|
||||||
return
|
|
||||||
ws = pkg_resources.working_set
|
|
||||||
try:
|
|
||||||
setuptools_dist = ws.find(
|
|
||||||
pkg_resources.Requirement.parse('setuptools', replacement=False)
|
|
||||||
)
|
|
||||||
except TypeError:
|
|
||||||
# old distribute API
|
|
||||||
setuptools_dist = ws.find(
|
|
||||||
pkg_resources.Requirement.parse('setuptools')
|
|
||||||
)
|
|
||||||
|
|
||||||
if setuptools_dist is None:
|
|
||||||
log.warn('No setuptools distribution found')
|
|
||||||
return
|
|
||||||
# detecting if it was already faked
|
|
||||||
setuptools_location = setuptools_dist.location
|
|
||||||
log.warn('Setuptools installation detected at %s', setuptools_location)
|
|
||||||
|
|
||||||
# if --root or --preix was provided, and if
|
|
||||||
# setuptools is not located in them, we don't patch it
|
|
||||||
if not _under_prefix(setuptools_location):
|
|
||||||
log.warn('Not patching, --root or --prefix is installing Distribute'
|
|
||||||
' in another location')
|
|
||||||
return
|
|
||||||
|
|
||||||
# let's see if its an egg
|
|
||||||
if not setuptools_location.endswith('.egg'):
|
|
||||||
log.warn('Non-egg installation')
|
|
||||||
res = _remove_flat_installation(setuptools_location)
|
|
||||||
if not res:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
log.warn('Egg installation')
|
|
||||||
pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
|
|
||||||
if (os.path.exists(pkg_info) and
|
|
||||||
_same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
|
|
||||||
log.warn('Already patched.')
|
|
||||||
return
|
|
||||||
log.warn('Patching...')
|
|
||||||
# let's create a fake egg replacing setuptools one
|
|
||||||
res = _patch_egg_dir(setuptools_location)
|
|
||||||
if not res:
|
|
||||||
return
|
|
||||||
log.warn('Patched done.')
|
|
||||||
_relaunch()
|
|
||||||
|
|
||||||
|
|
||||||
def _relaunch():
|
|
||||||
log.warn('Relaunching...')
|
|
||||||
# we have to relaunch the process
|
|
||||||
# pip marker to avoid a relaunch bug
|
|
||||||
_cmd = ['-c', 'install', '--single-version-externally-managed']
|
|
||||||
if sys.argv[:3] == _cmd:
|
|
||||||
sys.argv[0] = 'setup.py'
|
|
||||||
args = [sys.executable] + sys.argv
|
|
||||||
sys.exit(subprocess.call(args))
|
|
||||||
|
|
||||||
|
|
||||||
def _extractall(self, path=".", members=None):
|
|
||||||
"""Extract all members from the archive to the current working
|
|
||||||
directory and set owner, modification time and permissions on
|
|
||||||
directories afterwards. `path' specifies a different directory
|
|
||||||
to extract to. `members' is optional and must be a subset of the
|
|
||||||
list returned by getmembers().
|
|
||||||
"""
|
|
||||||
import copy
|
|
||||||
import operator
|
|
||||||
from tarfile import ExtractError
|
|
||||||
directories = []
|
|
||||||
|
|
||||||
if members is None:
|
|
||||||
members = self
|
|
||||||
|
|
||||||
for tarinfo in members:
|
|
||||||
if tarinfo.isdir():
|
|
||||||
# Extract directories with a safe mode.
|
|
||||||
directories.append(tarinfo)
|
|
||||||
tarinfo = copy.copy(tarinfo)
|
|
||||||
tarinfo.mode = 448 # decimal for oct 0700
|
|
||||||
self.extract(tarinfo, path)
|
|
||||||
|
|
||||||
# Reverse sort directories.
|
|
||||||
if sys.version_info < (2, 4):
|
|
||||||
def sorter(dir1, dir2):
|
|
||||||
return cmp(dir1.name, dir2.name)
|
|
||||||
directories.sort(sorter)
|
|
||||||
directories.reverse()
|
|
||||||
else:
|
|
||||||
directories.sort(key=operator.attrgetter('name'), reverse=True)
|
|
||||||
|
|
||||||
# Set correct owner, mtime and filemode on directories.
|
|
||||||
for tarinfo in directories:
|
|
||||||
dirpath = os.path.join(path, tarinfo.name)
|
|
||||||
try:
|
|
||||||
self.chown(tarinfo, dirpath)
|
|
||||||
self.utime(tarinfo, dirpath)
|
|
||||||
self.chmod(tarinfo, dirpath)
|
|
||||||
except ExtractError:
|
|
||||||
e = sys.exc_info()[1]
|
|
||||||
if self.errorlevel > 1:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
self._dbg(1, "tarfile: %s" % e)
|
|
||||||
|
|
||||||
|
|
||||||
def _build_install_args(argv):
|
|
||||||
install_args = []
|
|
||||||
user_install = '--user' in argv
|
|
||||||
if user_install and sys.version_info < (2, 6):
|
|
||||||
log.warn("--user requires Python 2.6 or later")
|
|
||||||
raise SystemExit(1)
|
|
||||||
if user_install:
|
|
||||||
install_args.append('--user')
|
|
||||||
return install_args
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv, version=DEFAULT_VERSION):
|
|
||||||
"""Install or upgrade setuptools and EasyInstall"""
|
|
||||||
tarball = download_setuptools()
|
|
||||||
_install(tarball, _build_install_args(argv))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main(sys.argv[1:])
|
|
||||||
433
domdiv/__init__.py
Normal file
433
domdiv/__init__.py
Normal file
@ -0,0 +1,433 @@
|
|||||||
|
from optparse import OptionParser
|
||||||
|
import os
|
||||||
|
import codecs
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import reportlab.lib.pagesizes as pagesizes
|
||||||
|
from reportlab.lib.units import cm
|
||||||
|
|
||||||
|
from cards import Card
|
||||||
|
from draw import DividerDrawer
|
||||||
|
|
||||||
|
LOCATION_CHOICES = ["tab", "body-top", "hide"]
|
||||||
|
NAME_ALIGN_CHOICES = ["left", "right", "centre", "edge"]
|
||||||
|
TAB_SIDE_CHOICES = ["left", "right", "left-alternate", "right-alternate", "full"]
|
||||||
|
|
||||||
|
|
||||||
|
def add_opt(options, option, value):
|
||||||
|
assert not hasattr(options, option)
|
||||||
|
setattr(options, option, value)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_opts(argstring):
|
||||||
|
parser = OptionParser()
|
||||||
|
parser.add_option("--back_offset", type="float", dest="back_offset", default=0,
|
||||||
|
help="Points to offset the back page to the right; needed for some printers")
|
||||||
|
parser.add_option("--back_offset_height", type="float", dest="back_offset_height", default=0,
|
||||||
|
help="Points to offset the back page upward; needed for some printers")
|
||||||
|
parser.add_option("--orientation", type="choice", choices=["horizontal", "vertical"],
|
||||||
|
dest="orientation", default="horizontal",
|
||||||
|
help="horizontal or vertical, default:horizontal")
|
||||||
|
parser.add_option("--sleeved", action="store_true",
|
||||||
|
dest="sleeved", help="use --size=sleeved instead")
|
||||||
|
parser.add_option("--size", type="string", dest="size", default='normal',
|
||||||
|
help="'<%f>x<%f>' (size in cm), or 'normal' = '9.1x5.9', or 'sleeved' = '9.4x6.15'")
|
||||||
|
parser.add_option("--minmargin", type="string", dest="minmargin", default="1x1",
|
||||||
|
help="'<%f>x<%f>' (size in cm, left/right, top/bottom), default: 1x1")
|
||||||
|
parser.add_option("--papersize", type="string", dest="papersize", default=None,
|
||||||
|
help="'<%f>x<%f>' (size in cm), or 'A4', or 'LETTER'")
|
||||||
|
parser.add_option("--tab_name_align", type="choice", choices=NAME_ALIGN_CHOICES + ["center"],
|
||||||
|
dest="tab_name_align", default="left",
|
||||||
|
help="Alignment of text on the tab. choices: left, right, centre (or center), edge."
|
||||||
|
" The edge option will align the card name to the outside edge of the"
|
||||||
|
" tab, so that when using tabs on alternating sides,"
|
||||||
|
" the name is less likely to be hidden by the tab in front"
|
||||||
|
" (edge will revert to left when tab_side is full since there is no edge in that case);"
|
||||||
|
" default:left")
|
||||||
|
parser.add_option("--tab_side", type="choice", choices=TAB_SIDE_CHOICES,
|
||||||
|
dest="tab_side", default="right-alternate",
|
||||||
|
help="Alignment of tab. choices: left, right, left-alternate, right-alternate, full;"
|
||||||
|
" left/right forces all tabs to left/right side;"
|
||||||
|
" left-alternate will start on the left and then toggle between left and right for the tabs;"
|
||||||
|
" right-alternate will start on the right and then toggle between right and left for the tabs;" # noqa
|
||||||
|
" full will force all label tabs to be full width of the divider"
|
||||||
|
" default:right-alternate")
|
||||||
|
parser.add_option("--tabwidth", type="float", default=4,
|
||||||
|
help="width in cm of stick-up tab (ignored if tab_side is full or tabs-only is used)")
|
||||||
|
parser.add_option("--cost", action="append", type="choice",
|
||||||
|
choices=LOCATION_CHOICES, default=[],
|
||||||
|
help="where to display the card cost; may be set to"
|
||||||
|
" 'hide' to indicate it should not be displayed, or"
|
||||||
|
" given multiple times to show it in multiple"
|
||||||
|
" places; valid values are: %s; defaults to 'tab'"
|
||||||
|
% ", ".join("'%s'" % x for x in LOCATION_CHOICES))
|
||||||
|
parser.add_option("--set_icon", action="append", type="choice",
|
||||||
|
choices=LOCATION_CHOICES, default=[],
|
||||||
|
help="where to display the set icon; may be set to"
|
||||||
|
" 'hide' to indicate it should not be displayed, or"
|
||||||
|
" given multiple times to show it in multiple"
|
||||||
|
" places; valid values are: %s; defaults to 'tab'"
|
||||||
|
% ", ".join("'%s'" % x for x in LOCATION_CHOICES))
|
||||||
|
parser.add_option("--expansions", action="append", type="string",
|
||||||
|
help="subset of dominion expansions to produce tabs for")
|
||||||
|
parser.add_option("--cropmarks", action="store_true", dest="cropmarks",
|
||||||
|
help="print crop marks on both sides, rather than tab outlines on one")
|
||||||
|
parser.add_option("--linewidth", type="float", default=.1,
|
||||||
|
help="width of lines for card outlines/crop marks")
|
||||||
|
parser.add_option("--write_json", action="store_true", dest="write_json",
|
||||||
|
help="write json version of card definitions and extras")
|
||||||
|
parser.add_option("--tabs-only", action="store_true", dest="tabs_only",
|
||||||
|
help="draw only tabs to be printed on labels, no divider outlines")
|
||||||
|
parser.add_option("--order", type="choice", choices=["expansion", "global"], dest="order",
|
||||||
|
help="sort order for the cards, whether by expansion or globally alphabetical")
|
||||||
|
parser.add_option("--expansion_dividers", action="store_true", dest="expansion_dividers",
|
||||||
|
help="add dividers describing each expansion set")
|
||||||
|
parser.add_option("--base_cards_with_expansion", action="store_true",
|
||||||
|
help='print the base cards as part of the expansion; ie, a divider for "Silver"'
|
||||||
|
' will be printed as both a "Dominion" card and as an "Intrigue" card; if this'
|
||||||
|
' option is not given, all base cards are placed in their own "Base" expansion')
|
||||||
|
parser.add_option("--centre_expansion_dividers", action="store_true", dest="centre_expansion_dividers",
|
||||||
|
help='centre the tabs on expansion dividers')
|
||||||
|
parser.add_option("--num_pages", type="int", default=-1,
|
||||||
|
help="stop generating after this many pages, -1 for all")
|
||||||
|
parser.add_option("--language", default='en_us', help="language of card texts")
|
||||||
|
parser.add_option("--include_blanks", action="store_true",
|
||||||
|
help="include a few dividers with extra text")
|
||||||
|
parser.add_option("--exclude_events", action="store_true",
|
||||||
|
default=False, help="exclude individual dividers for events")
|
||||||
|
parser.add_option("--special_card_groups", action="store_true",
|
||||||
|
default=False, help="group some cards under special dividers (e.g. Shelters, Prizes)")
|
||||||
|
parser.add_option("--exclude_prizes", action="store_true",
|
||||||
|
default=False, help="exclude individual dividers for prizes (cornucopia)")
|
||||||
|
parser.add_option("--cardlist", type="string", dest="cardlist", default=None,
|
||||||
|
help="Path to file that enumerates each card to be printed on its own line.")
|
||||||
|
parser.add_option("--no-tab-artwork", action="store_true", dest="no_tab_artwork",
|
||||||
|
help="don't show background artwork on tabs")
|
||||||
|
parser.add_option("--no-card-rules", action="store_true", dest="no_card_rules",
|
||||||
|
help="don't print the card's rules on the tab body")
|
||||||
|
parser.add_option("--use-text-set-icon", action="store_true", dest="use_text_set_icon",
|
||||||
|
help="use text/letters to represent a card's set instead of the set icon")
|
||||||
|
parser.add_option("--no-page-footer", action="store_true", dest="no_page_footer",
|
||||||
|
help="don't print the set name at the bottom of the page.")
|
||||||
|
parser.add_option("--no-card-backs", action="store_true", dest="no_card_backs",
|
||||||
|
help="don't print the back page of the card sheets.")
|
||||||
|
|
||||||
|
options, args = parser.parse_args(argstring)
|
||||||
|
if not options.cost:
|
||||||
|
options.cost = ['tab']
|
||||||
|
if not options.set_icon:
|
||||||
|
options.set_icon = ['tab']
|
||||||
|
return options, args
|
||||||
|
|
||||||
|
|
||||||
|
def parseDimensions(dimensionsStr):
|
||||||
|
x, y = dimensionsStr.upper().split('X', 1)
|
||||||
|
return (float(x) * cm, float(y) * cm)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_sample(options):
|
||||||
|
import cStringIO
|
||||||
|
from wand.image import Image
|
||||||
|
buf = cStringIO.StringIO()
|
||||||
|
options.num_pages = 1
|
||||||
|
generate(options, '.', buf)
|
||||||
|
with Image(blob=buf.getvalue()) as sample:
|
||||||
|
sample.format = 'png'
|
||||||
|
sample.save(filename='sample.png')
|
||||||
|
|
||||||
|
|
||||||
|
def parse_papersize(spec):
|
||||||
|
papersize = None
|
||||||
|
if not spec:
|
||||||
|
if os.path.exists("/etc/papersize"):
|
||||||
|
papersize = open("/etc/papersize").readline().upper()
|
||||||
|
else:
|
||||||
|
papersize = 'LETTER'
|
||||||
|
else:
|
||||||
|
papersize = spec.upper()
|
||||||
|
|
||||||
|
try:
|
||||||
|
paperwidth, paperheight = getattr(pagesizes, papersize)
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
paperwidth, paperheight = parseDimensions(papersize)
|
||||||
|
print 'Using custom paper size, %.2fcm x %.2fcm' % (paperwidth / cm, paperheight / cm)
|
||||||
|
except ValueError:
|
||||||
|
paperwidth, paperheight = pagesizes.LETTER
|
||||||
|
return paperwidth, paperheight
|
||||||
|
|
||||||
|
|
||||||
|
def parse_cardsize(spec, sleeved):
|
||||||
|
spec = spec.upper()
|
||||||
|
if spec == 'SLEEVED' or sleeved:
|
||||||
|
dominionCardWidth, dominionCardHeight = (9.4 * cm, 6.15 * cm)
|
||||||
|
print 'Using sleeved card size, %.2fcm x %.2fcm' % (dominionCardWidth / cm,
|
||||||
|
dominionCardHeight / cm)
|
||||||
|
elif spec in ['NORMAL', 'UNSLEEVED']:
|
||||||
|
dominionCardWidth, dominionCardHeight = (9.1 * cm, 5.9 * cm)
|
||||||
|
print 'Using normal card size, %.2fcm x%.2fcm' % (dominionCardWidth / cm,
|
||||||
|
dominionCardHeight / cm)
|
||||||
|
else:
|
||||||
|
dominionCardWidth, dominionCardHeight = parseDimensions(spec)
|
||||||
|
print 'Using custom card size, %.2fcm x %.2fcm' % (dominionCardWidth / cm,
|
||||||
|
dominionCardHeight / cm)
|
||||||
|
return dominionCardWidth, dominionCardHeight
|
||||||
|
|
||||||
|
|
||||||
|
def read_write_card_data(options):
|
||||||
|
data_dir = os.path.join(options.data_path, "card_db", options.language)
|
||||||
|
card_db_filepath = os.path.join(data_dir, "cards.json")
|
||||||
|
with codecs.open(card_db_filepath, "r", "utf-8") as cardfile:
|
||||||
|
cards = json.load(cardfile, object_hook=Card.decode_json)
|
||||||
|
|
||||||
|
language_mapping_filepath = os.path.join(data_dir, "mapping.json")
|
||||||
|
with codecs.open(language_mapping_filepath, 'r', 'utf-8') as mapping_file:
|
||||||
|
Card.language_mapping = json.load(mapping_file)
|
||||||
|
|
||||||
|
if options.write_json:
|
||||||
|
fpath = "cards.json"
|
||||||
|
with codecs.open(fpath, 'w', encoding='utf-8') as ofile:
|
||||||
|
json.dump(cards,
|
||||||
|
ofile,
|
||||||
|
cls=Card.CardJSONEncoder,
|
||||||
|
ensure_ascii=False,
|
||||||
|
indent=True,
|
||||||
|
sort_keys=True)
|
||||||
|
return cards
|
||||||
|
|
||||||
|
|
||||||
|
class CardSorter(object):
|
||||||
|
|
||||||
|
def __init__(self, order, baseCards):
|
||||||
|
self.order = order
|
||||||
|
if order == "global":
|
||||||
|
self.sort_key = self.global_sort_key
|
||||||
|
else:
|
||||||
|
self.sort_key = self.by_expansion_sort_key
|
||||||
|
|
||||||
|
self.baseCards = baseCards
|
||||||
|
|
||||||
|
# When sorting cards, want to always put "base" cards after all
|
||||||
|
# kingdom cards, and order the base cards in a set order - the
|
||||||
|
# order they are listed in the database (ie, all normal treasures
|
||||||
|
# by worth, then potion, then all normal VP cards by worth, then
|
||||||
|
# trash)
|
||||||
|
def baseIndex(self, name):
|
||||||
|
try:
|
||||||
|
return self.baseCards.index(name)
|
||||||
|
except Exception:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def isBaseExpansionCard(self, card):
|
||||||
|
return card.cardset.lower() != 'base' and card.name in self.baseCards
|
||||||
|
|
||||||
|
def global_sort_key(self, card):
|
||||||
|
return int(card.isExpansion()), self.baseIndex(card.name), card.name
|
||||||
|
|
||||||
|
def by_expansion_sort_key(self, card):
|
||||||
|
return card.cardset, int(card.isExpansion()), self.baseIndex(card.name), card.name
|
||||||
|
|
||||||
|
def __call__(self, card):
|
||||||
|
return self.sort_key(card)
|
||||||
|
|
||||||
|
|
||||||
|
def filter_sort_cards(cards, options):
|
||||||
|
|
||||||
|
cardSorter = CardSorter(options.order,
|
||||||
|
[card.name for card in cards if card.cardset.lower() == 'base'])
|
||||||
|
if options.base_cards_with_expansion:
|
||||||
|
cards = [card for card in cards if card.cardset.lower() != 'base']
|
||||||
|
else:
|
||||||
|
cards = [card for card in cards if not cardSorter.isBaseExpansionCard(card)]
|
||||||
|
|
||||||
|
if options.special_card_groups:
|
||||||
|
# Load the card groups file
|
||||||
|
card_groups_file = os.path.join(options.data_dir, "card_groups.json")
|
||||||
|
with codecs.open(card_groups_file, 'r', 'utf-8') as cardgroup_file:
|
||||||
|
card_groups = json.load(cardgroup_file)
|
||||||
|
# pull out any cards which are a subcard, and rename the master card
|
||||||
|
new_cards = []
|
||||||
|
all_subcards = []
|
||||||
|
for subs in [card_groups[x]["subcards"] for x in card_groups]:
|
||||||
|
all_subcards += subs
|
||||||
|
for card in cards:
|
||||||
|
if card.name in card_groups.keys():
|
||||||
|
card.name = card_groups[card.name]["new_name"]
|
||||||
|
elif card.name in all_subcards:
|
||||||
|
continue
|
||||||
|
new_cards.append(card)
|
||||||
|
cards = new_cards
|
||||||
|
|
||||||
|
if options.expansions:
|
||||||
|
options.expansions = [o.lower()
|
||||||
|
for o in options.expansions]
|
||||||
|
reverseMapping = {
|
||||||
|
v: k for k, v in Card.language_mapping.iteritems()}
|
||||||
|
options.expansions = [
|
||||||
|
reverseMapping.get(e, e) for e in options.expansions]
|
||||||
|
filteredCards = []
|
||||||
|
knownExpansions = set()
|
||||||
|
for c in cards:
|
||||||
|
knownExpansions.add(c.cardset)
|
||||||
|
if next((e for e in options.expansions if c.cardset.startswith(e)), None):
|
||||||
|
filteredCards.append(c)
|
||||||
|
unknownExpansions = set(options.expansions) - knownExpansions
|
||||||
|
if unknownExpansions:
|
||||||
|
print "Error - unknown expansion(s): %s" % ", ".join(unknownExpansions)
|
||||||
|
return
|
||||||
|
|
||||||
|
cards = filteredCards
|
||||||
|
|
||||||
|
if options.exclude_events:
|
||||||
|
cards = [card for card in cards if not card.isEvent() or card.name == 'Events']
|
||||||
|
|
||||||
|
if options.exclude_prizes:
|
||||||
|
cards = [card for card in cards if not card.isPrize()]
|
||||||
|
|
||||||
|
if options.cardlist:
|
||||||
|
cardlist = set()
|
||||||
|
with open(options.cardlist) as cardfile:
|
||||||
|
for line in cardfile:
|
||||||
|
cardlist.add(line.strip())
|
||||||
|
if cardlist:
|
||||||
|
cards = [card for card in cards if card.name in cardlist]
|
||||||
|
|
||||||
|
if options.expansion_dividers:
|
||||||
|
cardnamesByExpansion = {}
|
||||||
|
for c in cards:
|
||||||
|
if cardSorter.isBaseExpansionCard(c):
|
||||||
|
continue
|
||||||
|
cardnamesByExpansion.setdefault(
|
||||||
|
c.cardset, []).append(c.name.strip())
|
||||||
|
for exp, names in cardnamesByExpansion.iteritems():
|
||||||
|
c = Card(
|
||||||
|
exp, exp, ("Expansion",), None, ' | '.join(sorted(names)))
|
||||||
|
cards.append(c)
|
||||||
|
|
||||||
|
cards.sort(key=cardSorter)
|
||||||
|
|
||||||
|
return cards
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_layout(options):
|
||||||
|
|
||||||
|
dominionCardWidth, dominionCardHeight = parse_cardsize(options.size, options.sleeved)
|
||||||
|
paperwidth, paperheight = parse_papersize(options.papersize)
|
||||||
|
|
||||||
|
if options.orientation == "vertical":
|
||||||
|
dividerWidth, dividerBaseHeight = dominionCardHeight, dominionCardWidth
|
||||||
|
else:
|
||||||
|
dividerWidth, dividerBaseHeight = dominionCardWidth, dominionCardHeight
|
||||||
|
|
||||||
|
if options.tab_name_align == "center":
|
||||||
|
options.tab_name_align = "centre"
|
||||||
|
|
||||||
|
if options.tab_side == "full" and options.tab_name_align == "edge":
|
||||||
|
# This case does not make sense since there are two tab edges in this case. So picking left edge.
|
||||||
|
print >>sys.stderr, "** Warning: Aligning card name as 'left' for 'full' tabs **"
|
||||||
|
options.tab_name_align == "left"
|
||||||
|
|
||||||
|
fixedMargins = False
|
||||||
|
if options.tabs_only:
|
||||||
|
# fixed for Avery 8867 for now
|
||||||
|
minmarginwidth = 0.86 * cm # was 0.76
|
||||||
|
minmarginheight = 1.37 * cm # was 1.27
|
||||||
|
labelHeight = 1.07 * cm # was 1.27
|
||||||
|
labelWidth = 4.24 * cm # was 4.44
|
||||||
|
horizontalBorderSpace = 0.96 * cm # was 0.76
|
||||||
|
verticalBorderSpace = 0.20 * cm # was 0.01
|
||||||
|
dividerBaseHeight = 0
|
||||||
|
dividerWidth = labelWidth
|
||||||
|
fixedMargins = True
|
||||||
|
else:
|
||||||
|
minmarginwidth, minmarginheight = parseDimensions(
|
||||||
|
options.minmargin)
|
||||||
|
if options.tab_side == "full":
|
||||||
|
labelWidth = dividerWidth
|
||||||
|
else:
|
||||||
|
labelWidth = options.tabwidth * cm
|
||||||
|
labelHeight = .9 * cm
|
||||||
|
horizontalBorderSpace = 0 * cm
|
||||||
|
verticalBorderSpace = 0 * cm
|
||||||
|
|
||||||
|
dividerHeight = dividerBaseHeight + labelHeight
|
||||||
|
|
||||||
|
add_opt(options, 'dividerWidth', dividerWidth)
|
||||||
|
add_opt(options, 'dividerHeight', dividerHeight)
|
||||||
|
add_opt(options, 'dividerWidthReserved', dividerWidth + horizontalBorderSpace)
|
||||||
|
add_opt(options, 'dividerHeightReserved', dividerHeight + verticalBorderSpace)
|
||||||
|
add_opt(options, 'labelWidth', labelWidth)
|
||||||
|
add_opt(options, 'labelHeight', labelHeight)
|
||||||
|
|
||||||
|
# as we don't draw anything in the final border, it shouldn't count towards how many tabs we can fit
|
||||||
|
# so it gets added back in to the page size here
|
||||||
|
numDividersVerticalP = int(
|
||||||
|
(paperheight - 2 * minmarginheight + verticalBorderSpace) / options.dividerHeightReserved)
|
||||||
|
numDividersHorizontalP = int(
|
||||||
|
(paperwidth - 2 * minmarginwidth + horizontalBorderSpace) / options.dividerWidthReserved)
|
||||||
|
numDividersVerticalL = int(
|
||||||
|
(paperwidth - 2 * minmarginwidth + verticalBorderSpace) / options.dividerHeightReserved)
|
||||||
|
numDividersHorizontalL = int(
|
||||||
|
(paperheight - 2 * minmarginheight + horizontalBorderSpace) / options.dividerWidthReserved)
|
||||||
|
|
||||||
|
if ((numDividersVerticalL * numDividersHorizontalL >
|
||||||
|
numDividersVerticalP * numDividersHorizontalP) and not fixedMargins):
|
||||||
|
add_opt(options, 'numDividersVertical', numDividersVerticalL)
|
||||||
|
add_opt(options, 'numDividersHorizontal', numDividersHorizontalL)
|
||||||
|
add_opt(options, 'paperheight', paperwidth)
|
||||||
|
add_opt(options, 'paperwidth', paperheight)
|
||||||
|
add_opt(options, 'minHorizontalMargin', minmarginheight)
|
||||||
|
add_opt(options, 'minVerticalMargin', minmarginwidth)
|
||||||
|
else:
|
||||||
|
add_opt(options, 'numDividersVertical', numDividersVerticalP)
|
||||||
|
add_opt(options, 'numDividersHorizontal', numDividersHorizontalP)
|
||||||
|
add_opt(options, 'paperheight', paperheight)
|
||||||
|
add_opt(options, 'paperwidth', paperwidth)
|
||||||
|
add_opt(options, 'minHorizontalMargin', minmarginheight)
|
||||||
|
add_opt(options, 'minVerticalMargin', minmarginwidth)
|
||||||
|
|
||||||
|
if not fixedMargins:
|
||||||
|
# dynamically max margins
|
||||||
|
add_opt(options, 'horizontalMargin',
|
||||||
|
(options.paperwidth -
|
||||||
|
options.numDividersHorizontal * options.dividerWidthReserved) / 2)
|
||||||
|
add_opt(options, 'verticalMargin',
|
||||||
|
(options.paperheight -
|
||||||
|
options.numDividersVertical * options.dividerHeightReserved) / 2)
|
||||||
|
else:
|
||||||
|
add_opt(options, 'horizontalMargin', minmarginwidth)
|
||||||
|
add_opt(options, 'verticalMargin', minmarginheight)
|
||||||
|
|
||||||
|
|
||||||
|
def generate(options, data_path, f):
|
||||||
|
|
||||||
|
add_opt(options, 'data_path', data_path)
|
||||||
|
|
||||||
|
calculate_layout(options)
|
||||||
|
|
||||||
|
print "Paper dimensions: {:.2f}cm (w) x {:.2f}cm (h)".format(options.paperwidth / cm,
|
||||||
|
options.paperheight / cm)
|
||||||
|
print "Tab dimensions: {:.2f}cm (w) x {:.2f}cm (h)".format(options.dividerWidthReserved / cm,
|
||||||
|
options.dividerHeightReserved / cm)
|
||||||
|
print '{} dividers horizontally, {} vertically'.format(options.numDividersHorizontal,
|
||||||
|
options.numDividersVertical)
|
||||||
|
print "Margins: {:.2f}cm h, {:.2f}cm v\n".format(options.horizontalMargin / cm,
|
||||||
|
options.verticalMargin / cm)
|
||||||
|
|
||||||
|
cards = read_write_card_data(options)
|
||||||
|
cards = filter_sort_cards(cards, options)
|
||||||
|
|
||||||
|
if not f:
|
||||||
|
f = "dominion_dividers.pdf"
|
||||||
|
|
||||||
|
dd = DividerDrawer()
|
||||||
|
dd.draw(f, cards, options)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argstring, data_path):
|
||||||
|
options, args = parse_opts(argstring)
|
||||||
|
fname = None
|
||||||
|
if args:
|
||||||
|
fname = args[0]
|
||||||
|
return generate(options, data_path, fname)
|
||||||
219
domdiv/cards.py
Normal file
219
domdiv/cards.py
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def getType(typespec):
|
||||||
|
return cardTypes[tuple(typespec)]
|
||||||
|
|
||||||
|
setImages = {
|
||||||
|
'dominion': 'base_set.png',
|
||||||
|
'intrigue': 'intrigue_set.png',
|
||||||
|
'seaside': 'seaside_set.png',
|
||||||
|
'prosperity': 'prosperity_set.png',
|
||||||
|
'alchemy': 'alchemy_set.png',
|
||||||
|
'cornucopia': 'cornucopia_set.png',
|
||||||
|
'cornucopia extras': 'cornucopia_set.png',
|
||||||
|
'hinterlands': 'hinterlands_set.png',
|
||||||
|
'dark ages': 'dark_ages_set.png',
|
||||||
|
'dark ages extras': 'dark_ages_set.png',
|
||||||
|
'guilds': 'guilds_set.png',
|
||||||
|
'adventures': 'adventures_set.png',
|
||||||
|
'adventures extras': 'adventures_set.png'
|
||||||
|
}
|
||||||
|
promoImages = {
|
||||||
|
'walled village': 'walled_village_set.png',
|
||||||
|
'stash': 'stash_set.png',
|
||||||
|
'governor': 'governor_set.png',
|
||||||
|
'black market': 'black_market_set.png',
|
||||||
|
'envoy': 'envoy_set.png',
|
||||||
|
'prince': 'prince_set.png'
|
||||||
|
}
|
||||||
|
|
||||||
|
setTextIcons = {
|
||||||
|
'dominion': 'D',
|
||||||
|
'intrigue': 'I',
|
||||||
|
'seaside': 'S',
|
||||||
|
'prosperity': 'P',
|
||||||
|
'alchemy': 'A',
|
||||||
|
'cornucopia': 'C',
|
||||||
|
'cornucopia extras': 'C',
|
||||||
|
'hinterlands': 'H',
|
||||||
|
'dark ages': 'DA',
|
||||||
|
'dark ages extras': 'DA',
|
||||||
|
'guilds': 'G',
|
||||||
|
'adventures': 'Ad',
|
||||||
|
'adventures extras': 'Ad'
|
||||||
|
}
|
||||||
|
|
||||||
|
promoTextIcons = {
|
||||||
|
'walled village': '',
|
||||||
|
'stash': '',
|
||||||
|
'governor': '',
|
||||||
|
'black market': '',
|
||||||
|
'envoy': '',
|
||||||
|
'prince': ''
|
||||||
|
}
|
||||||
|
|
||||||
|
language_mapping = None
|
||||||
|
|
||||||
|
|
||||||
|
class Card(object):
|
||||||
|
|
||||||
|
language_mapping = None
|
||||||
|
|
||||||
|
class CardJSONEncoder(json.JSONEncoder):
|
||||||
|
|
||||||
|
def default(self, obj):
|
||||||
|
if isinstance(obj, Card):
|
||||||
|
return obj.__dict__
|
||||||
|
return json.JSONEncoder.default(self, obj)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decode_json(obj):
|
||||||
|
return Card(**obj)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getSetImage(cls, setName, cardName):
|
||||||
|
if setName in setImages:
|
||||||
|
return setImages[setName]
|
||||||
|
if cardName.lower() in promoImages:
|
||||||
|
return promoImages[cardName.lower()]
|
||||||
|
if setName in cls.language_mapping:
|
||||||
|
trans = cls.language_mapping[setName]
|
||||||
|
if trans in setImages:
|
||||||
|
return setImages[trans]
|
||||||
|
if cardName in cls.language_mapping:
|
||||||
|
trans = cls.language_mapping[cardName]
|
||||||
|
if trans.lower() in promoImages:
|
||||||
|
return promoImages[trans.lower()]
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getSetText(cls, setName, cardName):
|
||||||
|
if setName in cls.setTextIcons:
|
||||||
|
return cls.setTextIcons[setName]
|
||||||
|
if cardName.lower() in cls.promoTextIcons:
|
||||||
|
return cls.promoTextIcons[cardName.lower()]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __init__(self, name, cardset, types, cost, description='', potcost=0, extra=''):
|
||||||
|
self.name = name.strip()
|
||||||
|
self.cardset = cardset.strip()
|
||||||
|
self.types = types
|
||||||
|
self.cost = cost
|
||||||
|
self.potcost = potcost
|
||||||
|
self.description = description
|
||||||
|
self.extra = extra
|
||||||
|
|
||||||
|
def getType(self):
|
||||||
|
return getType(self.types)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '"' + self.name + '"'
|
||||||
|
|
||||||
|
def toString(self):
|
||||||
|
return self.name + ' ' + self.cardset + ' ' + '-'.join(self.types)\
|
||||||
|
+ ' ' + self.cost + ' ' + self.description + ' ' + self.extra
|
||||||
|
|
||||||
|
def isExpansion(self):
|
||||||
|
return self.getType().getTypeNames() == ('Expansion',)
|
||||||
|
|
||||||
|
def isEvent(self):
|
||||||
|
return self.getType().getTypeNames() == ('Event',)
|
||||||
|
|
||||||
|
def isPrize(self):
|
||||||
|
return 'Prize' in self.getType().getTypeNames()
|
||||||
|
|
||||||
|
def setImage(self):
|
||||||
|
setImage = Card.getSetImage(self.cardset, self.name)
|
||||||
|
if setImage is None and self.cardset not in ['base', 'extra'] and not self.isExpansion():
|
||||||
|
print 'warning, no set image for set "%s" card "%s"' % (self.cardset, self.name)
|
||||||
|
setImages[self.cardset] = 0
|
||||||
|
promoImages[self.name.lower()] = 0
|
||||||
|
return setImage
|
||||||
|
|
||||||
|
def setTextIcon(self):
|
||||||
|
setTextIcon = getSetText(self.cardset, self.name)
|
||||||
|
if setTextIcon is None and self.cardset not in ['base', 'extra'] and not self.isExpansion():
|
||||||
|
print 'warning, no set text for set "%s" card "%s"' % (self.cardset, self.name)
|
||||||
|
setTextIcons[self.cardset] = 0
|
||||||
|
promoTextIcons[self.name.lower()] = 0
|
||||||
|
return setTextIcon
|
||||||
|
|
||||||
|
def isBlank(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class BlankCard(Card):
|
||||||
|
|
||||||
|
def __init__(self, num):
|
||||||
|
Card.__init__(self, str(num), 'extra', ('Blank',), 0)
|
||||||
|
|
||||||
|
def isBlank(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class CardType(object):
|
||||||
|
|
||||||
|
def __init__(self, typeNames, tabImageFile, tabTextHeightOffset=0, tabCostHeightOffset=-1):
|
||||||
|
self.typeNames = typeNames
|
||||||
|
self.tabImageFile = tabImageFile
|
||||||
|
self.tabTextHeightOffset = tabTextHeightOffset
|
||||||
|
self.tabCostHeightOffset = tabCostHeightOffset
|
||||||
|
|
||||||
|
def getTypeNames(self):
|
||||||
|
return self.typeNames
|
||||||
|
|
||||||
|
def getTabImageFile(self):
|
||||||
|
if not self.tabImageFile:
|
||||||
|
return None
|
||||||
|
return self.tabImageFile
|
||||||
|
|
||||||
|
def getNoCoinTabImageFile(self):
|
||||||
|
if not self.tabImageFile:
|
||||||
|
return None
|
||||||
|
return ''.join(os.path.splitext(self.tabImageFile)[0] + '_nc' + os.path.splitext(self.tabImageFile)[1])
|
||||||
|
|
||||||
|
def getTabTextHeightOffset(self):
|
||||||
|
return self.tabTextHeightOffset
|
||||||
|
|
||||||
|
def getTabCostHeightOffset(self):
|
||||||
|
return self.tabCostHeightOffset
|
||||||
|
|
||||||
|
cardTypes = [
|
||||||
|
CardType(('Action',), 'action.png'),
|
||||||
|
CardType(('Action', 'Attack'), 'action.png'),
|
||||||
|
CardType(('Action', 'Attack', 'Prize'), 'action.png'),
|
||||||
|
CardType(('Action', 'Reaction'), 'reaction.png'),
|
||||||
|
CardType(('Action', 'Victory'), 'action-victory.png'),
|
||||||
|
CardType(('Action', 'Duration'), 'duration.png'),
|
||||||
|
CardType(('Action', 'Duration', 'Reaction'), 'duration-reaction.png'),
|
||||||
|
CardType(('Action', 'Attack', 'Duration'), 'duration.png'),
|
||||||
|
CardType(('Action', 'Looter'), 'action.png'),
|
||||||
|
CardType(('Action', 'Prize'), 'action.png'),
|
||||||
|
CardType(('Action', 'Ruins'), 'ruins.png', 0, 1),
|
||||||
|
CardType(('Action', 'Shelter'), 'action-shelter.png'),
|
||||||
|
CardType(('Action', 'Attack', 'Looter'), 'action.png'),
|
||||||
|
CardType(('Action', 'Attack', 'Traveller'), 'action.png'),
|
||||||
|
CardType(('Action', 'Reserve'), 'reserve.png'),
|
||||||
|
CardType(('Action', 'Reserve', 'Victory'), 'reserve-victory.png'),
|
||||||
|
CardType(('Action', 'Traveller'), 'action.png'),
|
||||||
|
CardType(('Prize',), 'action.png'),
|
||||||
|
CardType(('Event',), 'event.png'),
|
||||||
|
CardType(('Reaction',), 'reaction.png'),
|
||||||
|
CardType(('Reaction', 'Shelter'), 'reaction-shelter.png'),
|
||||||
|
CardType(('Treasure',), 'treasure.png', 3, 0),
|
||||||
|
CardType(('Treasure', 'Attack'), 'treasure.png'),
|
||||||
|
CardType(('Treasure', 'Victory'), 'treasure-victory.png'),
|
||||||
|
CardType(('Treasure', 'Prize'), 'treasure.png', 3, 0),
|
||||||
|
CardType(('Treasure', 'Reaction'), 'treasure-reaction.png', 0, 1),
|
||||||
|
CardType(('Treasure', 'Reserve'), 'reserve-treasure.png'),
|
||||||
|
CardType(('Victory',), 'victory.png'),
|
||||||
|
CardType(('Victory', 'Reaction'), 'victory-reaction.png', 0, 1),
|
||||||
|
CardType(('Victory', 'Shelter'), 'victory-shelter.png'),
|
||||||
|
CardType(('Curse',), 'curse.png', 3),
|
||||||
|
CardType(('Expansion',), 'expansion.png', 4),
|
||||||
|
CardType(('Blank',), '')
|
||||||
|
]
|
||||||
|
|
||||||
|
cardTypes = dict(((c.getTypeNames(), c) for c in cardTypes))
|
||||||
531
domdiv/draw.py
Normal file
531
domdiv/draw.py
Normal file
@ -0,0 +1,531 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from reportlab.lib.units import cm
|
||||||
|
from reportlab.pdfbase import pdfmetrics
|
||||||
|
from reportlab.lib.styles import getSampleStyleSheet
|
||||||
|
from reportlab.platypus import Paragraph
|
||||||
|
from reportlab.lib.enums import TA_JUSTIFY
|
||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
from reportlab.pdfbase.ttfonts import TTFont
|
||||||
|
|
||||||
|
|
||||||
|
def split(l, n):
|
||||||
|
i = 0
|
||||||
|
while i < len(l) - n:
|
||||||
|
yield l[i:i + n]
|
||||||
|
i += n
|
||||||
|
yield l[i:]
|
||||||
|
|
||||||
|
|
||||||
|
class DividerDrawer(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.odd = True
|
||||||
|
self.canvas = None
|
||||||
|
|
||||||
|
def registerFonts(self):
|
||||||
|
try:
|
||||||
|
dirn = os.path.join(self.options.data_path, 'fonts')
|
||||||
|
self.fontNameRegular = 'MinionPro-Regular'
|
||||||
|
pdfmetrics.registerFont(
|
||||||
|
TTFont(self.fontNameRegular, os.path.join(dirn, 'MinionPro-Regular.ttf')))
|
||||||
|
self.fontNameBold = 'MinionPro-Bold'
|
||||||
|
pdfmetrics.registerFont(
|
||||||
|
TTFont(self.fontNameBold, os.path.join(dirn, 'MinionPro-Bold.ttf')))
|
||||||
|
self.fontNameOblique = 'MinionPro-Oblique'
|
||||||
|
pdfmetrics.registerFont(
|
||||||
|
TTFont(self.fontNameOblique, os.path.join(dirn, 'MinionPro-It.ttf')))
|
||||||
|
except:
|
||||||
|
print >>sys.stderr, "Warning, Minion Pro Font ttf file not found! Falling back on Times"
|
||||||
|
self.fontNameRegular = 'Times-Roman'
|
||||||
|
self.fontNameBold = 'Times-Bold'
|
||||||
|
self.fontNameOblique = 'Times-Oblique'
|
||||||
|
|
||||||
|
def draw(self, fname, cards, options):
|
||||||
|
self.options = options
|
||||||
|
|
||||||
|
self.registerFonts()
|
||||||
|
|
||||||
|
dividerWidth = options.dividerWidth
|
||||||
|
dividerHeight = options.dividerHeight
|
||||||
|
tabLabelWidth = options.labelWidth
|
||||||
|
|
||||||
|
self.tabOutline = [(0, 0, dividerWidth, 0),
|
||||||
|
(dividerWidth, 0, dividerWidth, dividerHeight),
|
||||||
|
(dividerWidth, dividerHeight,
|
||||||
|
dividerWidth - tabLabelWidth, dividerHeight),
|
||||||
|
(dividerWidth - tabLabelWidth,
|
||||||
|
dividerHeight, dividerWidth - tabLabelWidth,
|
||||||
|
dividerHeight),
|
||||||
|
(dividerWidth - tabLabelWidth,
|
||||||
|
dividerHeight, 0, dividerHeight),
|
||||||
|
(0, dividerHeight, 0, 0)]
|
||||||
|
|
||||||
|
self.expansionTabOutline = [(0, 0, dividerWidth, 0),
|
||||||
|
(dividerWidth, 0, dividerWidth,
|
||||||
|
dividerHeight),
|
||||||
|
(dividerWidth, dividerHeight,
|
||||||
|
dividerWidth / 2 + tabLabelWidth / 2, dividerHeight),
|
||||||
|
(dividerWidth / 2 + tabLabelWidth / 2, dividerHeight,
|
||||||
|
dividerWidth / 2 + tabLabelWidth / 2, dividerHeight),
|
||||||
|
(dividerWidth / 2 + tabLabelWidth / 2, dividerHeight,
|
||||||
|
dividerWidth / 2 - tabLabelWidth / 2, dividerHeight),
|
||||||
|
(dividerWidth / 2 - tabLabelWidth / 2, dividerHeight,
|
||||||
|
dividerWidth / 2 - tabLabelWidth / 2, dividerHeight),
|
||||||
|
(dividerWidth / 2 - tabLabelWidth / 2, dividerHeight,
|
||||||
|
0, dividerHeight),
|
||||||
|
(0, dividerHeight, 0, 0)]
|
||||||
|
|
||||||
|
self.canvas = canvas.Canvas(
|
||||||
|
fname, pagesize=(options.paperwidth, options.paperheight))
|
||||||
|
self.drawDividers(cards)
|
||||||
|
self.canvas.save()
|
||||||
|
|
||||||
|
def add_inline_images(self, text, fontsize):
|
||||||
|
path = os.path.join(self.options.data_path, 'images')
|
||||||
|
replace = '<img src='"'%s/coin_small_\\1.png'"' width=%d height='"'100%%'"' valign='"'middle'"'/>' % (
|
||||||
|
path, fontsize * 1.2)
|
||||||
|
text = re.sub('(\d)\s(c|C)oin(s)?', replace, text)
|
||||||
|
replace = '<img src='"'%s/coin_small_question.png'"' width=%d height='"'100%%'"' valign='"'middle'"'/>' % (
|
||||||
|
path, fontsize * 1.2)
|
||||||
|
text = re.sub('\?\s(c|C)oin(s)?', replace, text)
|
||||||
|
replace = '<img src='"'%s/victory_emblem.png'"' width=%d height='"'120%%'"' valign='"'middle'"'/>' % (
|
||||||
|
path, fontsize * 1.5)
|
||||||
|
text = re.sub('\<VP\>', replace, text)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def drawOutline(self, x, y, rightSide, isBack=False, isExpansionDivider=False):
|
||||||
|
# draw outline or cropmarks
|
||||||
|
self.canvas.saveState()
|
||||||
|
self.canvas.setLineWidth(self.options.linewidth)
|
||||||
|
cropmarksright = (x == self.options.numDividersHorizontal - 1)
|
||||||
|
cropmarksleft = (x == 0)
|
||||||
|
if rightSide:
|
||||||
|
self.canvas.translate(self.options.dividerWidth, 0)
|
||||||
|
self.canvas.scale(-1, 1)
|
||||||
|
if not self.options.cropmarks and not isBack:
|
||||||
|
# don't draw outline on back, in case lines don't line up with
|
||||||
|
# front
|
||||||
|
if isExpansionDivider and self.options.centre_expansion_dividers:
|
||||||
|
outline = self.expansionTabOutline
|
||||||
|
else:
|
||||||
|
outline = self.tabOutline
|
||||||
|
self.canvas.lines(outline)
|
||||||
|
elif self.options.cropmarks:
|
||||||
|
cmw = 0.5 * cm
|
||||||
|
|
||||||
|
# Horizontal-line cropmarks
|
||||||
|
mirror = cropmarksright and not rightSide or cropmarksleft and rightSide
|
||||||
|
if mirror:
|
||||||
|
self.canvas.saveState()
|
||||||
|
self.canvas.translate(self.options.dividerWidth, 0)
|
||||||
|
self.canvas.scale(-1, 1)
|
||||||
|
if cropmarksleft or cropmarksright:
|
||||||
|
self.canvas.line(-2 * cmw, 0, -cmw, 0)
|
||||||
|
self.canvas.line(-2 * cmw,
|
||||||
|
self.dividerHeight, -cmw, self.dividerHeight)
|
||||||
|
if y > 0:
|
||||||
|
self.canvas.line(-2 * cmw,
|
||||||
|
self.options.dividerHeight, -cmw, self.options.dividerHeight)
|
||||||
|
if mirror:
|
||||||
|
self.canvas.restoreState()
|
||||||
|
|
||||||
|
# Vertical-line cropmarks
|
||||||
|
|
||||||
|
# want to always draw the right-edge and middle-label-edge lines..
|
||||||
|
# ...and draw the left-edge if this is the first card on the left
|
||||||
|
|
||||||
|
# ...but we need to take mirroring into account, to know "where"
|
||||||
|
# to draw the left / right lines...
|
||||||
|
if rightSide:
|
||||||
|
leftLine = self.options.dividerWidth
|
||||||
|
rightLine = 0
|
||||||
|
else:
|
||||||
|
leftLine = 0
|
||||||
|
rightLine = self.options.dividerWidth
|
||||||
|
middleLine = self.options.dividerWidth - self.options.labelWidth
|
||||||
|
|
||||||
|
if y == 0:
|
||||||
|
self.canvas.line(rightLine, -2 * cmw, rightLine, -cmw)
|
||||||
|
self.canvas.line(middleLine, -2 * cmw, middleLine, -cmw)
|
||||||
|
if cropmarksleft:
|
||||||
|
self.canvas.line(leftLine, -2 * cmw, leftLine, -cmw)
|
||||||
|
if y == self.options.numDividersVertical - 1:
|
||||||
|
self.canvas.line(rightLine, self.options.dividerHeight + cmw,
|
||||||
|
rightLine, self.options.dividerHeight + 2 * cmw)
|
||||||
|
self.canvas.line(middleLine, self.options.dividerHeight + cmw,
|
||||||
|
middleLine, self.options.dividerHeight + 2 * cmw)
|
||||||
|
if cropmarksleft:
|
||||||
|
self.canvas.line(leftLine, self.options.dividerHeight + cmw,
|
||||||
|
leftLine, self.options.dividerHeight + 2 * cmw)
|
||||||
|
|
||||||
|
self.canvas.restoreState()
|
||||||
|
|
||||||
|
def drawCost(self, card, x, y, costOffset=-1):
|
||||||
|
# base width is 16 (for image) + 2 (1 pt border on each side)
|
||||||
|
width = 18
|
||||||
|
|
||||||
|
costHeight = y + costOffset
|
||||||
|
coinHeight = costHeight - 5
|
||||||
|
potHeight = y - 3
|
||||||
|
potSize = 11
|
||||||
|
|
||||||
|
self.canvas.drawImage(os.path.join(self.options.data_path, 'images', 'coin_small.png'),
|
||||||
|
x, coinHeight, 16, 16, preserveAspectRatio=True, mask='auto')
|
||||||
|
if card.potcost:
|
||||||
|
self.canvas.drawImage(os.path.join(self.options.data_path, 'images', 'potion.png'), x + 17,
|
||||||
|
potHeight, potSize, potSize, preserveAspectRatio=True,
|
||||||
|
mask=[255, 255, 255, 255, 255, 255])
|
||||||
|
width += potSize
|
||||||
|
|
||||||
|
self.canvas.setFont(self.fontNameBold, 12)
|
||||||
|
cost = str(card.cost)
|
||||||
|
self.canvas.drawCentredString(x + 8, costHeight, cost)
|
||||||
|
return width
|
||||||
|
|
||||||
|
def drawSetIcon(self, setImage, x, y):
|
||||||
|
# set image
|
||||||
|
self.canvas.drawImage(
|
||||||
|
os.path.join(self.options.data_path, 'images', setImage), x, y, 14, 12, mask='auto')
|
||||||
|
|
||||||
|
def nameWidth(self, name, fontSize):
|
||||||
|
w = 0
|
||||||
|
name_parts = name.split()
|
||||||
|
for i, part in enumerate(name_parts):
|
||||||
|
if i != 0:
|
||||||
|
w += pdfmetrics.stringWidth(' ', self.fontNameRegular, fontSize)
|
||||||
|
w += pdfmetrics.stringWidth(part[0], self.fontNameRegular, fontSize)
|
||||||
|
w += pdfmetrics.stringWidth(part[1:],
|
||||||
|
self.fontNameRegular, fontSize - 2)
|
||||||
|
return w
|
||||||
|
|
||||||
|
def drawTab(self, card, rightSide):
|
||||||
|
# draw tab flap
|
||||||
|
self.canvas.saveState()
|
||||||
|
if card.isExpansion() and self.options.centre_expansion_dividers:
|
||||||
|
self.canvas.translate(self.options.dividerWidth / 2 - self.options.labelWidth / 2,
|
||||||
|
self.options.dividerHeight - self.options.labelHeight)
|
||||||
|
elif not rightSide:
|
||||||
|
self.canvas.translate(self.options.dividerWidth - self.options.labelWidth,
|
||||||
|
self.options.dividerHeight - self.options.labelHeight)
|
||||||
|
else:
|
||||||
|
self.canvas.translate(0, self.options.dividerHeight - self.options.labelHeight)
|
||||||
|
|
||||||
|
# allow for 3 pt border on each side
|
||||||
|
textWidth = self.options.labelWidth - 6
|
||||||
|
textHeight = 7
|
||||||
|
if self.options.no_tab_artwork:
|
||||||
|
textHeight = 4
|
||||||
|
textHeight = self.options.labelHeight / 2 - textHeight + \
|
||||||
|
card.getType().getTabTextHeightOffset()
|
||||||
|
|
||||||
|
# draw banner
|
||||||
|
img = card.getType().getNoCoinTabImageFile()
|
||||||
|
if not self.options.no_tab_artwork and img:
|
||||||
|
self.canvas.drawImage(os.path.join(self.options.data_path, 'images', img), 1, 0,
|
||||||
|
self.options.labelWidth -
|
||||||
|
2, self.options.labelHeight - 1,
|
||||||
|
preserveAspectRatio=False, anchor='n', mask='auto')
|
||||||
|
|
||||||
|
# draw cost
|
||||||
|
if not card.isExpansion() and not card.isBlank():
|
||||||
|
if 'tab' in self.options.cost:
|
||||||
|
textInset = 4
|
||||||
|
textInset += self.drawCost(card, textInset, textHeight,
|
||||||
|
card.getType().getTabCostHeightOffset())
|
||||||
|
else:
|
||||||
|
textInset = 6
|
||||||
|
else:
|
||||||
|
textInset = 13
|
||||||
|
|
||||||
|
# draw set image
|
||||||
|
# always need to offset from right edge, to make sure it stays on
|
||||||
|
# banner
|
||||||
|
textInsetRight = 6
|
||||||
|
if self.options.use_text_set_icon:
|
||||||
|
setImageHeight = card.getType().getTabTextHeightOffset()
|
||||||
|
setText = card.setTextIcon()
|
||||||
|
self.canvas.setFont(self.fontNameOblique, 8)
|
||||||
|
if setText is None:
|
||||||
|
setText = ""
|
||||||
|
|
||||||
|
self.canvas.drawCentredString(self.options.labelWidth - 10, textHeight + 2, setText)
|
||||||
|
textInsetRight = 15
|
||||||
|
else:
|
||||||
|
setImage = card.setImage()
|
||||||
|
if setImage and 'tab' in self.options.set_icon:
|
||||||
|
setImageHeight = 3 + card.getType().getTabTextHeightOffset()
|
||||||
|
|
||||||
|
self.drawSetIcon(setImage, self.options.labelWidth - 20,
|
||||||
|
setImageHeight)
|
||||||
|
|
||||||
|
textInsetRight = 20
|
||||||
|
|
||||||
|
# draw name
|
||||||
|
fontSize = 12
|
||||||
|
name = card.name.upper()
|
||||||
|
|
||||||
|
textWidth -= textInset
|
||||||
|
textWidth -= textInsetRight
|
||||||
|
|
||||||
|
width = self.nameWidth(name, fontSize)
|
||||||
|
while width > textWidth and fontSize > 8:
|
||||||
|
fontSize -= .01
|
||||||
|
# print 'decreasing font size for tab of',name,'now',fontSize
|
||||||
|
width = self.nameWidth(name, fontSize)
|
||||||
|
tooLong = width > textWidth
|
||||||
|
if tooLong:
|
||||||
|
name_lines = name.partition(' / ')
|
||||||
|
if name_lines[1]:
|
||||||
|
name_lines = (name_lines[0] + ' /', name_lines[2])
|
||||||
|
else:
|
||||||
|
name_lines = name.split(None, 1)
|
||||||
|
else:
|
||||||
|
name_lines = [name]
|
||||||
|
# if tooLong:
|
||||||
|
# print name
|
||||||
|
|
||||||
|
for linenum, line in enumerate(name_lines):
|
||||||
|
h = textHeight
|
||||||
|
if tooLong and len(name_lines) > 1:
|
||||||
|
if linenum == 0:
|
||||||
|
h += h / 2
|
||||||
|
else:
|
||||||
|
h -= h / 2
|
||||||
|
|
||||||
|
words = line.split()
|
||||||
|
if (not self.options.tab_name_align == "right") and (self.options.tab_name_align == "centre" or
|
||||||
|
rightSide or
|
||||||
|
not self.options.tab_name_align == "edge"):
|
||||||
|
if self.options.tab_name_align == "centre":
|
||||||
|
w = self.options.labelWidth / 2 - self.nameWidth(line, fontSize) / 2
|
||||||
|
else:
|
||||||
|
w = textInset
|
||||||
|
|
||||||
|
def drawWordPiece(text, fontSize):
|
||||||
|
self.canvas.setFont(self.fontNameRegular, fontSize)
|
||||||
|
if text != ' ':
|
||||||
|
self.canvas.drawString(w, h, text)
|
||||||
|
return pdfmetrics.stringWidth(text, self.fontNameRegular, fontSize)
|
||||||
|
for i, word in enumerate(words):
|
||||||
|
if i != 0:
|
||||||
|
w += drawWordPiece(' ', fontSize)
|
||||||
|
w += drawWordPiece(word[0], fontSize)
|
||||||
|
w += drawWordPiece(word[1:], fontSize - 2)
|
||||||
|
else:
|
||||||
|
# align text to the right if tab is on right side, to make
|
||||||
|
# tabs easier to read when grouped together extra 3pt is for
|
||||||
|
# space between text + set symbol
|
||||||
|
|
||||||
|
w = self.options.labelWidth - textInsetRight - 3
|
||||||
|
words.reverse()
|
||||||
|
|
||||||
|
def drawWordPiece(text, fontSize):
|
||||||
|
self.canvas.setFont(self.fontNameRegular, fontSize)
|
||||||
|
if text != ' ':
|
||||||
|
self.canvas.drawRightString(w, h, text)
|
||||||
|
return -pdfmetrics.stringWidth(text, self.fontNameRegular, fontSize)
|
||||||
|
for i, word in enumerate(words):
|
||||||
|
w += drawWordPiece(word[1:], fontSize - 2)
|
||||||
|
w += drawWordPiece(word[0], fontSize)
|
||||||
|
if i != len(words) - 1:
|
||||||
|
w += drawWordPiece(' ', fontSize)
|
||||||
|
self.canvas.restoreState()
|
||||||
|
|
||||||
|
def drawText(self, card, useExtra=False):
|
||||||
|
usedHeight = 0
|
||||||
|
totalHeight = self.options.dividerHeight - self.options.labelHeight
|
||||||
|
|
||||||
|
drewTopIcon = False
|
||||||
|
if 'body-top' in self.options.cost and not card.isExpansion():
|
||||||
|
self.drawCost(card, cm / 4.0, totalHeight - 0.5 * cm)
|
||||||
|
drewTopIcon = True
|
||||||
|
|
||||||
|
if 'body-top' in self.options.set_icon and not card.isExpansion():
|
||||||
|
setImage = card.setImage()
|
||||||
|
if setImage:
|
||||||
|
self.drawSetIcon(setImage, self.options.dividerWidth - 16,
|
||||||
|
totalHeight - 0.5 * cm - 3)
|
||||||
|
drewTopIcon = True
|
||||||
|
if drewTopIcon:
|
||||||
|
usedHeight += 15
|
||||||
|
|
||||||
|
if self.options.no_card_rules:
|
||||||
|
return
|
||||||
|
|
||||||
|
# draw text
|
||||||
|
if useExtra and card.extra:
|
||||||
|
descriptions = (card.extra,)
|
||||||
|
else:
|
||||||
|
descriptions = re.split("\n", card.description)
|
||||||
|
|
||||||
|
s = getSampleStyleSheet()['BodyText']
|
||||||
|
s.fontName = "Times-Roman"
|
||||||
|
s.alignment = TA_JUSTIFY
|
||||||
|
|
||||||
|
textHorizontalMargin = .5 * cm
|
||||||
|
textVerticalMargin = .3 * cm
|
||||||
|
textBoxWidth = self.options.dividerWidth - 2 * textHorizontalMargin
|
||||||
|
textBoxHeight = totalHeight - usedHeight - 2 * textVerticalMargin
|
||||||
|
spacerHeight = 0.2 * cm
|
||||||
|
minSpacerHeight = 0.05 * cm
|
||||||
|
|
||||||
|
while True:
|
||||||
|
paragraphs = []
|
||||||
|
# this accounts for the spacers we insert between paragraphs
|
||||||
|
h = (len(descriptions) - 1) * spacerHeight
|
||||||
|
for d in descriptions:
|
||||||
|
dmod = self.add_inline_images(d, s.fontSize)
|
||||||
|
p = Paragraph(dmod, s)
|
||||||
|
h += p.wrap(textBoxWidth, textBoxHeight)[1]
|
||||||
|
paragraphs.append(p)
|
||||||
|
|
||||||
|
if h <= textBoxHeight or s.fontSize <= 1 or s.leading <= 1:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
s.fontSize -= 1
|
||||||
|
s.leading -= 1
|
||||||
|
spacerHeight = max(spacerHeight - 1, minSpacerHeight)
|
||||||
|
|
||||||
|
h = totalHeight - usedHeight - textVerticalMargin
|
||||||
|
for p in paragraphs:
|
||||||
|
h -= p.height
|
||||||
|
p.drawOn(self.canvas, textHorizontalMargin, h)
|
||||||
|
h -= spacerHeight
|
||||||
|
|
||||||
|
def drawDivider(self, card, x, y, useExtra=False):
|
||||||
|
# figure out whether the tab should go on the right side or not
|
||||||
|
if self.options.tab_side == "right":
|
||||||
|
rightSide = useExtra
|
||||||
|
elif self.options.tab_side in ["left", "full"]:
|
||||||
|
rightSide = not useExtra
|
||||||
|
else:
|
||||||
|
# alternate the cards
|
||||||
|
if not useExtra:
|
||||||
|
rightSide = not self.odd
|
||||||
|
else:
|
||||||
|
rightSide = self.odd
|
||||||
|
|
||||||
|
# apply the transforms to get us to the corner of the current card
|
||||||
|
self.canvas.resetTransforms()
|
||||||
|
self.canvas.translate(self.options.horizontalMargin, self.options.verticalMargin)
|
||||||
|
if useExtra:
|
||||||
|
self.canvas.translate(self.options.back_offset, self.options.back_offset_height)
|
||||||
|
self.canvas.translate(x * self.options.dividerWidthReserved,
|
||||||
|
y * self.options.dividerHeightReserved)
|
||||||
|
|
||||||
|
# actual drawing
|
||||||
|
if not self.options.tabs_only:
|
||||||
|
self.drawOutline(
|
||||||
|
x, y, rightSide, useExtra, card.getType().getTypeNames() == ('Expansion',))
|
||||||
|
self.drawTab(card, rightSide)
|
||||||
|
if not self.options.tabs_only:
|
||||||
|
self.drawText(card, useExtra)
|
||||||
|
|
||||||
|
def drawSetNames(self, pageCards):
|
||||||
|
# print sets for this page
|
||||||
|
self.canvas.saveState()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# calculate the text height, font size, and orientation
|
||||||
|
maxFontsize = 12
|
||||||
|
minFontsize = 6
|
||||||
|
fontname = self.fontNameRegular
|
||||||
|
font = pdfmetrics.getFont(fontname)
|
||||||
|
fontHeightRelative = (font.face.ascent + abs(font.face.descent)) / 1000.0
|
||||||
|
|
||||||
|
canFit = False
|
||||||
|
|
||||||
|
layouts = [{'rotation': 0,
|
||||||
|
'minMarginHeight': self.options.minVerticalMargin,
|
||||||
|
'totalMarginHeight': self.options.verticalMargin,
|
||||||
|
'width': self.options.paperwidth},
|
||||||
|
{'rotation': 90,
|
||||||
|
'minMarginHeight': self.options.minHorizontalMargin,
|
||||||
|
'totalMarginHeight': self.options.horizontalMargin,
|
||||||
|
'width': self.options.paperheight}]
|
||||||
|
|
||||||
|
for layout in layouts:
|
||||||
|
availableMargin = layout[
|
||||||
|
'totalMarginHeight'] - layout['minMarginHeight']
|
||||||
|
fontsize = availableMargin / fontHeightRelative
|
||||||
|
fontsize = min(maxFontsize, fontsize)
|
||||||
|
if fontsize >= minFontsize:
|
||||||
|
canFit = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not canFit:
|
||||||
|
import warnings
|
||||||
|
warnings.warn("Not enough space to display set names")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.canvas.setFont(fontname, fontsize)
|
||||||
|
|
||||||
|
sets = []
|
||||||
|
for c in pageCards:
|
||||||
|
setTitle = c.cardset.title()
|
||||||
|
if setTitle not in sets:
|
||||||
|
sets.append(setTitle)
|
||||||
|
|
||||||
|
# Centered on page
|
||||||
|
xPos = layout['width'] / 2
|
||||||
|
# Place at the very edge of the margin
|
||||||
|
yPos = layout['minMarginHeight']
|
||||||
|
|
||||||
|
if layout['rotation']:
|
||||||
|
self.canvas.rotate(layout['rotation'])
|
||||||
|
yPos = -yPos
|
||||||
|
|
||||||
|
self.canvas.drawCentredString(xPos, yPos, '/'.join(sets))
|
||||||
|
finally:
|
||||||
|
self.canvas.restoreState()
|
||||||
|
|
||||||
|
def drawDividers(self, cards):
|
||||||
|
# split into pages
|
||||||
|
cards = split(cards, self.options.numDividersVertical * self.options.numDividersHorizontal)
|
||||||
|
|
||||||
|
# Starting with tabs on the left or the right?
|
||||||
|
if self.options.tab_side in ["right-alternate", "right"]:
|
||||||
|
self.odd = True
|
||||||
|
else:
|
||||||
|
# left-alternate, left, full
|
||||||
|
self.odd = False
|
||||||
|
|
||||||
|
for pageNum, pageCards in enumerate(cards):
|
||||||
|
# remember whether we start with odd or even divider for tab
|
||||||
|
# location
|
||||||
|
pageStartOdd = self.odd
|
||||||
|
if not self.options.no_page_footer and (not self.options.tabs_only and self.options.order != "global"):
|
||||||
|
self.drawSetNames(pageCards)
|
||||||
|
for i, card in enumerate(pageCards):
|
||||||
|
# print card
|
||||||
|
x = i % self.options.numDividersHorizontal
|
||||||
|
y = i / self.options.numDividersHorizontal
|
||||||
|
self.canvas.saveState()
|
||||||
|
self.drawDivider(card, x, self.options.numDividersVertical - 1 - y)
|
||||||
|
self.canvas.restoreState()
|
||||||
|
self.odd = not self.odd
|
||||||
|
self.canvas.showPage()
|
||||||
|
if pageNum + 1 == self.options.num_pages:
|
||||||
|
break
|
||||||
|
if self.options.tabs_only or self.options.no_card_backs:
|
||||||
|
# no set names or card backs for label-only sheets
|
||||||
|
continue
|
||||||
|
if not self.options.no_page_footer and self.options.order != "global":
|
||||||
|
self.drawSetNames(pageCards)
|
||||||
|
# start at same oddness
|
||||||
|
self.odd = pageStartOdd
|
||||||
|
for i, card in enumerate(pageCards):
|
||||||
|
# print card
|
||||||
|
x = (self.options.numDividersHorizontal - 1 - i) % self.options.numDividersHorizontal
|
||||||
|
y = i / self.options.numDividersHorizontal
|
||||||
|
self.canvas.saveState()
|
||||||
|
self.drawDivider(
|
||||||
|
card, x, self.options.numDividersVertical - 1 - y, useExtra=True)
|
||||||
|
self.canvas.restoreState()
|
||||||
|
self.odd = not self.odd
|
||||||
|
self.canvas.showPage()
|
||||||
|
if pageNum + 1 == self.options.num_pages:
|
||||||
|
break
|
||||||
6
dominion_dividers.py
Normal file
6
dominion_dividers.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import domdiv
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
domdiv.main(sys.argv[1:], os.path.dirname(__file__))
|
||||||
1101
dominion_tabs.py
1101
dominion_tabs.py
File diff suppressed because it is too large
Load Diff
391
ez_setup.py
Normal file
391
ez_setup.py
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Setuptools bootstrapping installer.
|
||||||
|
|
||||||
|
Run this script to install or upgrade setuptools.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
import optparse
|
||||||
|
import subprocess
|
||||||
|
import platform
|
||||||
|
import textwrap
|
||||||
|
import contextlib
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from distutils import log
|
||||||
|
|
||||||
|
try:
|
||||||
|
from urllib.request import urlopen
|
||||||
|
except ImportError:
|
||||||
|
from urllib2 import urlopen
|
||||||
|
|
||||||
|
try:
|
||||||
|
from site import USER_SITE
|
||||||
|
except ImportError:
|
||||||
|
USER_SITE = None
|
||||||
|
|
||||||
|
DEFAULT_VERSION = "18.5"
|
||||||
|
DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/"
|
||||||
|
DEFAULT_SAVE_DIR = os.curdir
|
||||||
|
|
||||||
|
|
||||||
|
def _python_cmd(*args):
|
||||||
|
"""
|
||||||
|
Execute a command.
|
||||||
|
|
||||||
|
Return True if the command succeeded.
|
||||||
|
"""
|
||||||
|
args = (sys.executable,) + args
|
||||||
|
return subprocess.call(args) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def _install(archive_filename, install_args=()):
|
||||||
|
"""Install Setuptools."""
|
||||||
|
with archive_context(archive_filename):
|
||||||
|
# installing
|
||||||
|
log.warn('Installing Setuptools')
|
||||||
|
if not _python_cmd('setup.py', 'install', *install_args):
|
||||||
|
log.warn('Something went wrong during the installation.')
|
||||||
|
log.warn('See the error message above.')
|
||||||
|
# exitcode will be 2
|
||||||
|
return 2
|
||||||
|
|
||||||
|
|
||||||
|
def _build_egg(egg, archive_filename, to_dir):
|
||||||
|
"""Build Setuptools egg."""
|
||||||
|
with archive_context(archive_filename):
|
||||||
|
# building an egg
|
||||||
|
log.warn('Building a Setuptools egg in %s', to_dir)
|
||||||
|
_python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
|
||||||
|
# returning the result
|
||||||
|
log.warn(egg)
|
||||||
|
if not os.path.exists(egg):
|
||||||
|
raise IOError('Could not build the egg.')
|
||||||
|
|
||||||
|
|
||||||
|
class ContextualZipFile(zipfile.ZipFile):
|
||||||
|
|
||||||
|
"""Supplement ZipFile class to support context manager for Python 2.6."""
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
"""Construct a ZipFile or ContextualZipFile as appropriate."""
|
||||||
|
if hasattr(zipfile.ZipFile, '__exit__'):
|
||||||
|
return zipfile.ZipFile(*args, **kwargs)
|
||||||
|
return super(ContextualZipFile, cls).__new__(cls)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def archive_context(filename):
|
||||||
|
"""
|
||||||
|
Unzip filename to a temporary directory, set to the cwd.
|
||||||
|
|
||||||
|
The unzipped target is cleaned up after.
|
||||||
|
"""
|
||||||
|
tmpdir = tempfile.mkdtemp()
|
||||||
|
log.warn('Extracting in %s', tmpdir)
|
||||||
|
old_wd = os.getcwd()
|
||||||
|
try:
|
||||||
|
os.chdir(tmpdir)
|
||||||
|
with ContextualZipFile(filename) as archive:
|
||||||
|
archive.extractall()
|
||||||
|
|
||||||
|
# going in the directory
|
||||||
|
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
|
||||||
|
os.chdir(subdir)
|
||||||
|
log.warn('Now working in %s', subdir)
|
||||||
|
yield
|
||||||
|
|
||||||
|
finally:
|
||||||
|
os.chdir(old_wd)
|
||||||
|
shutil.rmtree(tmpdir)
|
||||||
|
|
||||||
|
|
||||||
|
def _do_download(version, download_base, to_dir, download_delay):
|
||||||
|
"""Download Setuptools."""
|
||||||
|
egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg'
|
||||||
|
% (version, sys.version_info[0], sys.version_info[1]))
|
||||||
|
if not os.path.exists(egg):
|
||||||
|
archive = download_setuptools(version, download_base,
|
||||||
|
to_dir, download_delay)
|
||||||
|
_build_egg(egg, archive, to_dir)
|
||||||
|
sys.path.insert(0, egg)
|
||||||
|
|
||||||
|
# Remove previously-imported pkg_resources if present (see
|
||||||
|
# https://bitbucket.org/pypa/setuptools/pull-request/7/ for details).
|
||||||
|
if 'pkg_resources' in sys.modules:
|
||||||
|
del sys.modules['pkg_resources']
|
||||||
|
|
||||||
|
import setuptools
|
||||||
|
setuptools.bootstrap_install_from = egg
|
||||||
|
|
||||||
|
|
||||||
|
def use_setuptools(
|
||||||
|
version=DEFAULT_VERSION, download_base=DEFAULT_URL,
|
||||||
|
to_dir=DEFAULT_SAVE_DIR, download_delay=15):
|
||||||
|
"""
|
||||||
|
Ensure that a setuptools version is installed.
|
||||||
|
|
||||||
|
Return None. Raise SystemExit if the requested version
|
||||||
|
or later cannot be installed.
|
||||||
|
"""
|
||||||
|
to_dir = os.path.abspath(to_dir)
|
||||||
|
|
||||||
|
# prior to importing, capture the module state for
|
||||||
|
# representative modules.
|
||||||
|
rep_modules = 'pkg_resources', 'setuptools'
|
||||||
|
imported = set(sys.modules).intersection(rep_modules)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pkg_resources
|
||||||
|
pkg_resources.require("setuptools>=" + version)
|
||||||
|
# a suitable version is already installed
|
||||||
|
return
|
||||||
|
except ImportError:
|
||||||
|
# pkg_resources not available; setuptools is not installed; download
|
||||||
|
pass
|
||||||
|
except pkg_resources.DistributionNotFound:
|
||||||
|
# no version of setuptools was found; allow download
|
||||||
|
pass
|
||||||
|
except pkg_resources.VersionConflict as VC_err:
|
||||||
|
if imported:
|
||||||
|
_conflict_bail(VC_err, version)
|
||||||
|
|
||||||
|
# otherwise, unload pkg_resources to allow the downloaded version to
|
||||||
|
# take precedence.
|
||||||
|
del pkg_resources
|
||||||
|
_unload_pkg_resources()
|
||||||
|
|
||||||
|
return _do_download(version, download_base, to_dir, download_delay)
|
||||||
|
|
||||||
|
|
||||||
|
def _conflict_bail(VC_err, version):
|
||||||
|
"""
|
||||||
|
Setuptools was imported prior to invocation, so it is
|
||||||
|
unsafe to unload it. Bail out.
|
||||||
|
"""
|
||||||
|
conflict_tmpl = textwrap.dedent("""
|
||||||
|
The required version of setuptools (>={version}) is not available,
|
||||||
|
and can't be installed while this script is running. Please
|
||||||
|
install a more recent version first, using
|
||||||
|
'easy_install -U setuptools'.
|
||||||
|
|
||||||
|
(Currently using {VC_err.args[0]!r})
|
||||||
|
""")
|
||||||
|
msg = conflict_tmpl.format(**locals())
|
||||||
|
sys.stderr.write(msg)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
|
||||||
|
def _unload_pkg_resources():
|
||||||
|
del_modules = [
|
||||||
|
name for name in sys.modules
|
||||||
|
if name.startswith('pkg_resources')
|
||||||
|
]
|
||||||
|
for mod_name in del_modules:
|
||||||
|
del sys.modules[mod_name]
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_check(cmd, target):
|
||||||
|
"""
|
||||||
|
Run the command to download target.
|
||||||
|
|
||||||
|
If the command fails, clean up before re-raising the error.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
if os.access(target, os.F_OK):
|
||||||
|
os.unlink(target)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def download_file_powershell(url, target):
|
||||||
|
"""
|
||||||
|
Download the file at url to target using Powershell.
|
||||||
|
|
||||||
|
Powershell will validate trust.
|
||||||
|
Raise an exception if the command cannot complete.
|
||||||
|
"""
|
||||||
|
target = os.path.abspath(target)
|
||||||
|
ps_cmd = (
|
||||||
|
"[System.Net.WebRequest]::DefaultWebProxy.Credentials = "
|
||||||
|
"[System.Net.CredentialCache]::DefaultCredentials; "
|
||||||
|
"(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)"
|
||||||
|
% vars()
|
||||||
|
)
|
||||||
|
cmd = [
|
||||||
|
'powershell',
|
||||||
|
'-Command',
|
||||||
|
ps_cmd,
|
||||||
|
]
|
||||||
|
_clean_check(cmd, target)
|
||||||
|
|
||||||
|
|
||||||
|
def has_powershell():
|
||||||
|
"""Determine if Powershell is available."""
|
||||||
|
if platform.system() != 'Windows':
|
||||||
|
return False
|
||||||
|
cmd = ['powershell', '-Command', 'echo test']
|
||||||
|
with open(os.path.devnull, 'wb') as devnull:
|
||||||
|
try:
|
||||||
|
subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
download_file_powershell.viable = has_powershell
|
||||||
|
|
||||||
|
|
||||||
|
def download_file_curl(url, target):
|
||||||
|
cmd = ['curl', url, '--silent', '--output', target]
|
||||||
|
_clean_check(cmd, target)
|
||||||
|
|
||||||
|
|
||||||
|
def has_curl():
|
||||||
|
cmd = ['curl', '--version']
|
||||||
|
with open(os.path.devnull, 'wb') as devnull:
|
||||||
|
try:
|
||||||
|
subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
download_file_curl.viable = has_curl
|
||||||
|
|
||||||
|
|
||||||
|
def download_file_wget(url, target):
|
||||||
|
cmd = ['wget', url, '--quiet', '--output-document', target]
|
||||||
|
_clean_check(cmd, target)
|
||||||
|
|
||||||
|
|
||||||
|
def has_wget():
|
||||||
|
cmd = ['wget', '--version']
|
||||||
|
with open(os.path.devnull, 'wb') as devnull:
|
||||||
|
try:
|
||||||
|
subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
download_file_wget.viable = has_wget
|
||||||
|
|
||||||
|
|
||||||
|
def download_file_insecure(url, target):
|
||||||
|
"""Use Python to download the file, without connection authentication."""
|
||||||
|
src = urlopen(url)
|
||||||
|
try:
|
||||||
|
# Read all the data in one block.
|
||||||
|
data = src.read()
|
||||||
|
finally:
|
||||||
|
src.close()
|
||||||
|
|
||||||
|
# Write all the data in one block to avoid creating a partial file.
|
||||||
|
with open(target, "wb") as dst:
|
||||||
|
dst.write(data)
|
||||||
|
download_file_insecure.viable = lambda: True
|
||||||
|
|
||||||
|
|
||||||
|
def get_best_downloader():
|
||||||
|
downloaders = (
|
||||||
|
download_file_powershell,
|
||||||
|
download_file_curl,
|
||||||
|
download_file_wget,
|
||||||
|
download_file_insecure,
|
||||||
|
)
|
||||||
|
viable_downloaders = (dl for dl in downloaders if dl.viable())
|
||||||
|
return next(viable_downloaders, None)
|
||||||
|
|
||||||
|
|
||||||
|
def download_setuptools(
|
||||||
|
version=DEFAULT_VERSION, download_base=DEFAULT_URL,
|
||||||
|
to_dir=DEFAULT_SAVE_DIR, delay=15,
|
||||||
|
downloader_factory=get_best_downloader):
|
||||||
|
"""
|
||||||
|
Download setuptools from a specified location and return its filename.
|
||||||
|
|
||||||
|
`version` should be a valid setuptools version number that is available
|
||||||
|
as an sdist for download under the `download_base` URL (which should end
|
||||||
|
with a '/'). `to_dir` is the directory where the egg will be downloaded.
|
||||||
|
`delay` is the number of seconds to pause before an actual download
|
||||||
|
attempt.
|
||||||
|
|
||||||
|
``downloader_factory`` should be a function taking no arguments and
|
||||||
|
returning a function for downloading a URL to a target.
|
||||||
|
"""
|
||||||
|
# making sure we use the absolute path
|
||||||
|
to_dir = os.path.abspath(to_dir)
|
||||||
|
zip_name = "setuptools-%s.zip" % version
|
||||||
|
url = download_base + zip_name
|
||||||
|
saveto = os.path.join(to_dir, zip_name)
|
||||||
|
if not os.path.exists(saveto): # Avoid repeated downloads
|
||||||
|
log.warn("Downloading %s", url)
|
||||||
|
downloader = downloader_factory()
|
||||||
|
downloader(url, saveto)
|
||||||
|
return os.path.realpath(saveto)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_install_args(options):
|
||||||
|
"""
|
||||||
|
Build the arguments to 'python setup.py install' on the setuptools package.
|
||||||
|
|
||||||
|
Returns list of command line arguments.
|
||||||
|
"""
|
||||||
|
return ['--user'] if options.user_install else []
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_args():
|
||||||
|
"""Parse the command line for options."""
|
||||||
|
parser = optparse.OptionParser()
|
||||||
|
parser.add_option(
|
||||||
|
'--user', dest='user_install', action='store_true', default=False,
|
||||||
|
help='install in user site package (requires Python 2.6 or later)')
|
||||||
|
parser.add_option(
|
||||||
|
'--download-base', dest='download_base', metavar="URL",
|
||||||
|
default=DEFAULT_URL,
|
||||||
|
help='alternative URL from where to download the setuptools package')
|
||||||
|
parser.add_option(
|
||||||
|
'--insecure', dest='downloader_factory', action='store_const',
|
||||||
|
const=lambda: download_file_insecure, default=get_best_downloader,
|
||||||
|
help='Use internal, non-validating downloader'
|
||||||
|
)
|
||||||
|
parser.add_option(
|
||||||
|
'--version', help="Specify which version to download",
|
||||||
|
default=DEFAULT_VERSION,
|
||||||
|
)
|
||||||
|
parser.add_option(
|
||||||
|
'--to-dir',
|
||||||
|
help="Directory to save (and re-use) package",
|
||||||
|
default=DEFAULT_SAVE_DIR,
|
||||||
|
)
|
||||||
|
options, args = parser.parse_args()
|
||||||
|
# positional arguments are ignored
|
||||||
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
def _download_args(options):
|
||||||
|
"""Return args for download_setuptools function from cmdline args."""
|
||||||
|
return dict(
|
||||||
|
version=options.version,
|
||||||
|
download_base=options.download_base,
|
||||||
|
downloader_factory=options.downloader_factory,
|
||||||
|
to_dir=options.to_dir,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Install or upgrade setuptools and EasyInstall."""
|
||||||
|
options = _parse_args()
|
||||||
|
archive = download_setuptools(**_download_args(options))
|
||||||
|
return _install(archive, _build_install_args(options))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
14
setup.py
14
setup.py
@ -1,5 +1,5 @@
|
|||||||
from __init__ import __version__
|
from __init__ import __version__
|
||||||
from distribute_setup import use_setuptools
|
from ez_setup import use_setuptools
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
use_setuptools()
|
use_setuptools()
|
||||||
@ -8,15 +8,19 @@ use_setuptools()
|
|||||||
setup(
|
setup(
|
||||||
name="dominiontabs",
|
name="dominiontabs",
|
||||||
version=__version__,
|
version=__version__,
|
||||||
scripts=["dominion_tabs.py"],
|
entry_points={
|
||||||
packages=find_packages(),
|
'console_scripts': [
|
||||||
|
"dominion_dividers = domdiv.main:main"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
packages=find_packages(exclude=['tests']),
|
||||||
install_requires=["reportlab>=2.5",
|
install_requires=["reportlab>=2.5",
|
||||||
"Pillow>=2.1.0"],
|
"Pillow>=2.1.0"],
|
||||||
package_data={
|
package_data={
|
||||||
'': ['*.txt', '*.png']
|
'domdiv': ['images/*.png', 'card_db/*/*.json']
|
||||||
},
|
},
|
||||||
author="Sumpfork",
|
author="Sumpfork",
|
||||||
author_email="sumpfork@mailmight.net",
|
author_email="sumpfork@mailmight.net",
|
||||||
description="Tab Divider Generation for the Dominion Card Game"
|
description="Divider Generation for the Dominion Card Game"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
47
tests/carddb_tests.py
Normal file
47
tests/carddb_tests.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import unittest
|
||||||
|
from .. import domdiv
|
||||||
|
from ..domdiv import cards as domdiv_cards
|
||||||
|
|
||||||
|
|
||||||
|
class TestCardDB(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_cardread(self):
|
||||||
|
options, args = domdiv.parse_opts(['commandname'])
|
||||||
|
options.data_path = '.'
|
||||||
|
cards = domdiv.read_write_card_data(options)
|
||||||
|
self.assertEquals(len(cards), 312)
|
||||||
|
print set(c.cardset for c in cards)
|
||||||
|
valid_cardsets = {
|
||||||
|
u'prosperity',
|
||||||
|
u'cornucopia extras',
|
||||||
|
u'cornucopia',
|
||||||
|
u'promo',
|
||||||
|
u'adventures extras',
|
||||||
|
u'seaside',
|
||||||
|
u'adventures',
|
||||||
|
u'dark ages',
|
||||||
|
u'hinterlands',
|
||||||
|
u'dark ages extras',
|
||||||
|
u'alchemy',
|
||||||
|
u'base',
|
||||||
|
u'dominion',
|
||||||
|
u'guilds',
|
||||||
|
u'intrigue'
|
||||||
|
}
|
||||||
|
for c in cards:
|
||||||
|
self.assertIsInstance(c, domdiv_cards.Card)
|
||||||
|
self.assertIn(c.cardset, valid_cardsets)
|
||||||
|
|
||||||
|
def test_languages(self):
|
||||||
|
# for now, just test that they load
|
||||||
|
options, args = domdiv.parse_opts(['commandname', '--language', 'it'])
|
||||||
|
options.data_path = '.'
|
||||||
|
cards = domdiv.read_write_card_data(options)
|
||||||
|
self.assertTrue(cards, 'Italians cards did not read properly')
|
||||||
|
self.assertIn("Maledizione", [card.name for card in cards])
|
||||||
|
|
||||||
|
options, args = domdiv.parse_opts(['commandname', '--language', 'de'])
|
||||||
|
options.data_path = '.'
|
||||||
|
cards = domdiv.read_write_card_data(options)
|
||||||
|
self.assertTrue(cards, 'German cards did not read properly')
|
||||||
|
self.assertIn("Fluch", [card.name for card in cards])
|
||||||
35
tests/layout_tests.py
Normal file
35
tests/layout_tests.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import unittest
|
||||||
|
from .. import domdiv
|
||||||
|
from reportlab.lib.units import cm
|
||||||
|
|
||||||
|
|
||||||
|
class TestLayout(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_horizontal(self):
|
||||||
|
# should be the default
|
||||||
|
options, args = domdiv.parse_opts(['commandname'])
|
||||||
|
self.assertEquals(options.orientation, 'horizontal')
|
||||||
|
domdiv.calculate_layout(options)
|
||||||
|
self.assertEquals(options.numDividersHorizontal, 2)
|
||||||
|
self.assertEquals(options.numDividersVertical, 3)
|
||||||
|
self.assertEquals(options.dividerWidth, 9.1 * cm)
|
||||||
|
self.assertEquals(options.labelHeight, 0.9 * cm)
|
||||||
|
self.assertEquals(options.dividerHeight, 5.9 * cm + options.labelHeight)
|
||||||
|
|
||||||
|
def test_vertical(self):
|
||||||
|
options, args = domdiv.parse_opts(['commandname', '--orientation', 'vertical'])
|
||||||
|
self.assertEquals(options.orientation, 'vertical')
|
||||||
|
domdiv.calculate_layout(options)
|
||||||
|
self.assertEquals(options.numDividersHorizontal, 3)
|
||||||
|
self.assertEquals(options.numDividersVertical, 2)
|
||||||
|
self.assertEquals(options.dividerWidth, 5.9 * cm)
|
||||||
|
self.assertEquals(options.labelHeight, 0.9 * cm)
|
||||||
|
self.assertEquals(options.dividerHeight, 9.1 * cm + options.labelHeight)
|
||||||
|
|
||||||
|
def test_sleeved(self):
|
||||||
|
options, args = domdiv.parse_opts(['commandname', '--size', 'sleeved'])
|
||||||
|
domdiv.calculate_layout(options)
|
||||||
|
self.assertEquals(options.dividerWidth, 9.4 * cm)
|
||||||
|
self.assertEquals(options.labelHeight, 0.9 * cm)
|
||||||
|
self.assertEquals(options.dividerHeight, 6.15 * cm + options.labelHeight)
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user