allow direct calling of module

This commit is contained in:
Tobias Oberstein 2013-10-26 17:47:42 +02:00
parent 8dc6137373
commit 3353500845
2 changed files with 86 additions and 7 deletions

View file

@ -70,7 +70,7 @@ except ImportError:
pass pass
APP = 'scour' APP = 'scour'
VER = '0.26' VER = '0.27'
COPYRIGHT = 'Copyright Jeff Schiller, Louis Simard, 2010' COPYRIGHT = 'Copyright Jeff Schiller, Louis Simard, 2010'
NS = { 'SVG': 'http://www.w3.org/2000/svg', NS = { 'SVG': 'http://www.w3.org/2000/svg',
@ -2099,6 +2099,8 @@ def cleanPath(element, options) :
numBytesSavedInPathData += ( len(oldPathStr) - len(newPathStr) ) numBytesSavedInPathData += ( len(oldPathStr) - len(newPathStr) )
element.setAttribute('d', newPathStr) element.setAttribute('d', newPathStr)
def parseListOfPoints(s): def parseListOfPoints(s):
""" """
Parse string into a list of points. Parse string into a list of points.
@ -2155,6 +2157,8 @@ def parseListOfPoints(s):
return nums return nums
def cleanPolygon(elem, options): def cleanPolygon(elem, options):
""" """
Remove unnecessary closing point of polygon points attribute Remove unnecessary closing point of polygon points attribute
@ -2171,6 +2175,8 @@ def cleanPolygon(elem, options):
numPointsRemovedFromPolygon += 1 numPointsRemovedFromPolygon += 1
elem.setAttribute('points', scourCoordinates(pts, options, True)) elem.setAttribute('points', scourCoordinates(pts, options, True))
def cleanPolyline(elem, options): def cleanPolyline(elem, options):
""" """
Scour the polyline points attribute Scour the polyline points attribute
@ -2178,6 +2184,8 @@ def cleanPolyline(elem, options):
pts = parseListOfPoints(elem.getAttribute('points')) pts = parseListOfPoints(elem.getAttribute('points'))
elem.setAttribute('points', scourCoordinates(pts, options, True)) elem.setAttribute('points', scourCoordinates(pts, options, True))
def serializePath(pathObj, options): def serializePath(pathObj, options):
""" """
Reserializes the path data with some cleanups. Reserializes the path data with some cleanups.
@ -2186,6 +2194,8 @@ def serializePath(pathObj, options):
# this fixes an issue outlined in Fix https://bugs.launchpad.net/scour/+bug/412754 # this fixes an issue outlined in Fix https://bugs.launchpad.net/scour/+bug/412754
return ''.join([cmd + scourCoordinates(data, options, (cmd == 'a')) for cmd, data in pathObj]) return ''.join([cmd + scourCoordinates(data, options, (cmd == 'a')) for cmd, data in pathObj])
def serializeTransform(transformObj): def serializeTransform(transformObj):
""" """
Reserializes the transform data with some cleanups. Reserializes the transform data with some cleanups.
@ -2197,6 +2207,8 @@ def serializeTransform(transformObj):
for command, numbers in transformObj] for command, numbers in transformObj]
) )
def scourCoordinates(data, options, forceCommaWsp = False): def scourCoordinates(data, options, forceCommaWsp = False):
""" """
Serializes coordinate data with some cleanups: Serializes coordinate data with some cleanups:
@ -2246,6 +2258,8 @@ def scourCoordinates(data, options, forceCommaWsp = False):
return '' return ''
def scourLength(length): def scourLength(length):
""" """
Scours a length. Accepts units. Scours a length. Accepts units.
@ -2254,6 +2268,8 @@ def scourLength(length):
return scourUnitlessLength(length.value) + Unit.str(length.units) return scourUnitlessLength(length.value) + Unit.str(length.units)
def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of a numeric type def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of a numeric type
""" """
Scours the numeric part of a length only. Does not accept units. Scours the numeric part of a length only. Does not accept units.
@ -2288,6 +2304,8 @@ def scourUnitlessLength(length, needsRendererWorkaround=False): # length is of a
else: return nonsci else: return nonsci
else: return nonsci else: return nonsci
def reducePrecision(element) : def reducePrecision(element) :
""" """
Because opacities, letter spacings, stroke widths and all that don't need Because opacities, letter spacings, stroke widths and all that don't need
@ -2333,6 +2351,8 @@ def reducePrecision(element) :
return num return num
def optimizeAngle(angle): def optimizeAngle(angle):
""" """
Because any rotation can be expressed within 360 degrees Because any rotation can be expressed within 360 degrees
@ -2355,6 +2375,7 @@ def optimizeAngle(angle):
return angle return angle
def optimizeTransform(transform): def optimizeTransform(transform):
""" """
Optimises a series of transformations parsed from a single Optimises a series of transformations parsed from a single
@ -2514,6 +2535,8 @@ def optimizeTransform(transform):
else: else:
i += 1 i += 1
def optimizeTransforms(element, options) : def optimizeTransforms(element, options) :
""" """
Attempts to optimise transform specifications on the given node and its children. Attempts to optimise transform specifications on the given node and its children.
@ -2544,6 +2567,8 @@ def optimizeTransforms(element, options) :
return num return num
def removeComments(element) : def removeComments(element) :
""" """
Removes comments from the element and its children. Removes comments from the element and its children.
@ -2566,6 +2591,8 @@ def removeComments(element) :
for subelement in element.childNodes: for subelement in element.childNodes:
removeComments(subelement) removeComments(subelement)
def embedRasters(element, options) : def embedRasters(element, options) :
import base64 import base64
import urllib import urllib
@ -2623,6 +2650,8 @@ def embedRasters(element, options) :
numRastersEmbedded += 1 numRastersEmbedded += 1
del b64eRaster del b64eRaster
def properlySizeDoc(docElement, options): def properlySizeDoc(docElement, options):
# get doc width and height # get doc width and height
w = SVGLength(docElement.getAttribute('width')) w = SVGLength(docElement.getAttribute('width'))
@ -2663,6 +2692,8 @@ def properlySizeDoc(docElement, options):
docElement.removeAttribute('width') docElement.removeAttribute('width')
docElement.removeAttribute('height') docElement.removeAttribute('height')
def remapNamespacePrefix(node, oldprefix, newprefix): def remapNamespacePrefix(node, oldprefix, newprefix):
if node == None or node.nodeType != 1: return if node == None or node.nodeType != 1: return
@ -2698,6 +2729,8 @@ def remapNamespacePrefix(node, oldprefix, newprefix):
for child in node.childNodes : for child in node.childNodes :
remapNamespacePrefix(child, oldprefix, newprefix) remapNamespacePrefix(child, oldprefix, newprefix)
def makeWellFormed(str): def makeWellFormed(str):
xml_ents = { '<':'&lt;', '>':'&gt;', '&':'&amp;', "'":'&apos;', '"':'&quot;'} xml_ents = { '<':'&lt;', '>':'&gt;', '&':'&amp;', "'":'&apos;', '"':'&quot;'}
@ -2711,6 +2744,8 @@ def makeWellFormed(str):
# this list comprehension is short-form for the above for-loop: # this list comprehension is short-form for the above for-loop:
return ''.join([xml_ents[c] if c in xml_ents else c for c in str]) return ''.join([xml_ents[c] if c in xml_ents else c for c in str])
# hand-rolled serialization function that has the following benefits: # hand-rolled serialization function that has the following benefits:
# - pretty printing # - pretty printing
# - somewhat judicious use of whitespace # - somewhat judicious use of whitespace
@ -2809,6 +2844,8 @@ def serializeXML(element, options, ind = 0, preserveWhitespace = False):
return "".join(outParts) return "".join(outParts)
# this is the main method # this is the main method
# input is a string representation of the input XML # input is a string representation of the input XML
# returns a string representation of the output XML # returns a string representation of the output XML
@ -3020,6 +3057,8 @@ def scourString(in_string, options=None):
return total_output return total_output
# used mostly by unit tests # used mostly by unit tests
# input is a filename # input is a filename
# returns the minidom doc representation of the SVG # returns the minidom doc representation of the SVG
@ -3028,6 +3067,8 @@ def scourXmlFile(filename, options=None):
out_string = scourString(in_string, options) out_string = scourString(in_string, options)
return xml.dom.minidom.parseString(out_string.encode('utf-8')) return xml.dom.minidom.parseString(out_string.encode('utf-8'))
# GZ: Seems most other commandline tools don't do this, is it really wanted? # GZ: Seems most other commandline tools don't do this, is it really wanted?
class HeaderedFormatter(optparse.IndentedHelpFormatter): class HeaderedFormatter(optparse.IndentedHelpFormatter):
""" """
@ -3038,6 +3079,8 @@ class HeaderedFormatter(optparse.IndentedHelpFormatter):
return "%s %s\n%s\n%s" % (APP, VER, COPYRIGHT, return "%s %s\n%s\n%s" % (APP, VER, COPYRIGHT,
optparse.IndentedHelpFormatter.format_usage(self, usage)) optparse.IndentedHelpFormatter.format_usage(self, usage))
# GZ: would prefer this to be in a function or class scope, but tests etc need # GZ: would prefer this to be in a function or class scope, but tests etc need
# access to the defaults anyway # access to the defaults anyway
_options_parser = optparse.OptionParser( _options_parser = optparse.OptionParser(
@ -3117,12 +3160,16 @@ _options_parser.add_option("--protect-ids-prefix",
action="store", type="string", dest="protect_ids_prefix", default=None, action="store", type="string", dest="protect_ids_prefix", default=None,
help="Don't change IDs starting with the given prefix") help="Don't change IDs starting with the given prefix")
def maybe_gziped_file(filename, mode="r"): def maybe_gziped_file(filename, mode="r"):
if os.path.splitext(filename)[1].lower() in (".svgz", ".gz"): if os.path.splitext(filename)[1].lower() in (".svgz", ".gz"):
import gzip import gzip
return gzip.GzipFile(filename, mode) return gzip.GzipFile(filename, mode)
return file(filename, mode) return file(filename, mode)
def parse_args(args=None): def parse_args(args=None):
options, rargs = _options_parser.parse_args(args) options, rargs = _options_parser.parse_args(args)
@ -3148,6 +3195,8 @@ def parse_args(args=None):
return options, [infile, outfile] return options, [infile, outfile]
def getReport(): def getReport():
return ' Number of elements removed: ' + str(numElemsRemoved) + os.linesep + \ return ' Number of elements removed: ' + str(numElemsRemoved) + os.linesep + \
' Number of attributes removed: ' + str(numAttrsRemoved) + os.linesep + \ ' Number of attributes removed: ' + str(numAttrsRemoved) + os.linesep + \
@ -3164,7 +3213,20 @@ def getReport():
' Number of bytes saved in transformations: ' + str(numBytesSavedInTransforms) ' Number of bytes saved in transformations: ' + str(numBytesSavedInTransforms)
def run():
def generateDefaultOptions():
## FIXME: clean up this mess/hack and refactor arg parsing to argparse
class Struct:
def __init__(self, **entries):
self.__dict__.update(entries)
d = parse_args()[0].__dict__.copy()
return Struct(**d)
def start(options, input, output):
if sys.platform == "win32": if sys.platform == "win32":
from time import clock as get_tick from time import clock as get_tick
else: else:
@ -3174,8 +3236,6 @@ def run():
start = get_tick() start = get_tick()
options, (input, output) = parse_args()
if not options.quiet: if not options.quiet:
print >>sys.stderr, "%s %s\n%s" % (APP, VER, COPYRIGHT) print >>sys.stderr, "%s %s\n%s" % (APP, VER, COPYRIGHT)
@ -3203,6 +3263,12 @@ def run():
'new file size:', newsize, 'bytes (' + str(sizediff)[:5] + '%)' 'new file size:', newsize, 'bytes (' + str(sizediff)[:5] + '%)'
def run():
options, (input, output) = parse_args()
start(options, input, output)
if __name__ == '__main__': if __name__ == '__main__':
run() run()

View file

@ -18,15 +18,28 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
LONGDESC = """
Scour is a SVG optimizer/sanitizer that can be used to produce SVGs for Web deployment.
Website
- http://www.codedread.com/scour/ (original website)
- https://github.com/oberstet/scour (today)
Authors:
- Jeff Schiller, Louis Simard (original authors)
- Tobias Oberstein (maintainer)
"""
setup ( setup (
name = 'scour', name = 'scour',
version = '0.27', version = '0.27',
description = 'Scour SVG Optimizer', description = 'Scour SVG Optimizer',
long_description = open("README.md").read(), # long_description = open("README.md").read(),
long_description = LONGDESC,
license = 'Apache License 2.0', license = 'Apache License 2.0',
author = 'Jeff Schiller', author = 'Jeff Schiller',
author_email = 'codedread@gmail.com', author_email = 'codedread@gmail.com',
url = 'http://blog.codedread.com/', url = 'https://github.com/oberstet/scour',
platforms = ('Any'), platforms = ('Any'),
install_requires = [], install_requires = [],
packages = find_packages(), packages = find_packages(),