forked from jorgebastida/glue
-
Notifications
You must be signed in to change notification settings - Fork 0
/
glue.py
executable file
·836 lines (665 loc) · 29.5 KB
/
glue.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
#!/usr/bin/env python
import re
import os
import sys
import copy
import hashlib
import subprocess
import ConfigParser
from optparse import OptionParser, OptionGroup
from PIL import Image as PImage
TRANSPARENT = (255, 255, 255, 0)
CONFIG_FILENAME = 'sprite.conf'
DEFAULT_SETTINGS = {'padding': '0',
'algorithm': 'maxside',
'namespace': 'sprite',
'crop': False,
'url': '',
'less': False,
'optipng': False,
'ignore_filename_paddings': True,
'size': True}
class MultipleImagesWithSameNameError(Exception):
"""Raised if two images are going to have the same css class name."""
pass
class SourceImagesNotFoundError(Exception):
"""Raised if one folder doesn't contain any valid image."""
pass
class NoSpritesFoldersFoundError(Exception):
"""Raised if there is not any valid sprites folder."""
pass
class InvalidImageOrderingAlgorithmError(Exception):
"""Raised if the ordering algorithm is invalid."""
pass
class Node(object):
def __init__(self, x=0, y=0, width=0, height=0, used=False,
down=None, right=None):
"""Node constructor.
:param x: X coordinate.
:param y: Y coordinate.
:param width: Image width.
:param height: Image height.
:param used: Flag to determine if the node is used.
:param down: Down :class:`~Node`.
:param right Right :class:`~Node`.
"""
self.x = x
self.y = y
self.width = width
self.height = height
self.used = used
self.right = right
self.down = down
def find(self, node, width, height):
"""Find a node to allocate this image size (width, height).
:param node: Node to search in.
:param width: Amount of pixel to grow down (width).
:param height: Amount of pixel to grow down (height).
"""
if node.used:
return self.find(node.right, width, height) or \
self.find(node.down, width, height)
elif node.width >= width and node.height >= height:
return node
return None
def grow(self, width, height):
""" Grow the canvas to the more appropriate direction.
:param width: Amount of pixel to grow down (width).
:param height: Amount of pixel to grow down (height).
"""
can_grow_d = width <= self.width
can_grow_r = height <= self.height
should_grow_r = can_grow_r and self.height >= (self.width + width)
should_grow_d = can_grow_d and self.width >= (self.height + height)
if should_grow_r:
return self.grow_right(width, height)
elif should_grow_d:
return self.grow_down(width, height)
elif can_grow_r:
return self.grow_right(width, height)
elif can_grow_d:
return self.grow_down(width, height)
return None
def grow_right(self, width, height):
"""Grow the canvas to the right.
:param width: Amount of pixel to grow down (width).
:param height: Amount of pixel to grow down (height).
"""
old_self = copy.copy(self)
self.used = True
self.x = self.y = 0
self.width += width
self.down = old_self
self.right = Node(x=old_self.width,
y=0,
width=width,
height=self.height)
node = self.find(self, width, height)
if node:
return self.split(node, width, height)
return None
def grow_down(self, width, height):
"""Grow the canvas down.
:param width: Amount of pixel to grow down (width).
:param height: Amount of pixel to grow down (height).
"""
old_self = copy.copy(self)
self.used = True
self.x = self.y = 0
self.height += height
self.right = old_self
self.down = Node(x=0,
y=old_self.height,
width=self.width,
height=height)
node = self.find(self, width, height)
if node:
return self.split(node, width, height)
return None
def split(self, node, width, height):
"""Split the node to allocate a new one of this size.
:param node: Node to be splitted.
:param width: New node width.
:param height: New node height.
"""
node.used = True
node.down = Node(x=node.x,
y=node.y + height,
width=node.width,
height=node.height - height)
node.right = Node(x=node.x + width,
y=node.y,
width=node.width - width,
height=height)
return node
class Image(object):
ORDERINGS = ['maxside', 'width', 'height', 'area']
def __init__(self, name, sprite):
""" Image constructor
:param name: Image name.
:param sprite: :class:`~Sprite` instance for this image."""
self.name = name
self.sprite = sprite
self.filename, self.format = name.rsplit('.', 1)
image_path = os.path.join(sprite.path, name)
image_file = open(image_path, "rb")
try:
self.image = PImage.open(image_file)
self.image.load()
except IOError, e:
sys.stderr.write(("ERROR: PIL %s decoder isn't available. "
"Please read the documentation and "
"install it before spriting this kind of "
"images.\n" % e.args[0].split()[1]))
sys.exit(1)
image_file.close()
if self.sprite.config.crop:
self._crop_image()
self.width, self.height = self.image.size
self.width += self.padding[1] + self.padding[3]
self.height += self.padding[0] + self.padding[2]
self.node = None
def _crop_image(self):
"""Crop the image searching for the smallest possible bounding box
without losing any non-transparent pixel.
This crop is only used if the crop flag is present in the config.
"""
width, height = self.image.size
maxx = maxy = 0
minx = miny = sys.maxint
for x in xrange(width):
for y in xrange(height):
if y > miny and y < maxy and maxx == x:
continue
if self.image.getpixel((x, y)) != TRANSPARENT:
if x < minx:
minx = x
if x > maxx:
maxx = x
if y < miny:
miny = y
if y > maxy:
maxy = y
self.image = self.image.crop((minx, miny, maxx + 1, maxy + 1))
def _generate_padding(self, padding):
"""Return a four element list with the desired padding.
:param padding: Padding as a list or a raw string representing
the padding for this image."""
if type(padding) == str:
padding = padding.replace('px', '').split()
if len(padding) == 3:
padding = padding + [padding[1]]
elif len(padding) == 2:
padding = padding * 2
elif len(padding) == 1:
padding = padding * 4
elif not len(padding):
padding = [self.DEFAULT_PADDING] * 4
return map(int, padding)
@property
def class_name(self):
"""Return the css class name for this file.
This css class name will have the following format:
``.[namespace]-[sprite_name]-[image_name]{ ... }``
The image_name will only contain the alphanumeric characters,
``-`` and ``_``. The default namespace is ``sprite``, it but could
be overridden using the ``--namespace`` optional argument.
* ``animals/cat.png`` css class will be ``.sprite-animals-cat``
* ``animals/cow_20.png`` css class will be ``.sprite-animals-cow``
"""
name = self.filename
if not self.sprite.manager.config.ignore_filename_paddings:
padding_info_name = '-'.join(self._padding_info)
if padding_info_name:
padding_info_name = '_%s' % padding_info_name
name = name[:len(padding_info_name) * -1 or None]
name = re.sub(r'[^\w\-_]', '', name)
return '%s-%s' % (self.sprite.namespace, name)
@property
def _padding_info(self):
"""Return the padding information from the filename. """
padding_info = self.filename.rsplit('_', 1)[-1]
if re.match(r"^(\d+-?){,3}\d+$", padding_info):
return padding_info.split('-')
return []
@property
def padding(self):
"""Return the padding for this image based on the filename and
sprite settings file preferences.
* ``filename.png`` will have the default padding ``10px``.
* ``filename_20.png`` -> ``20px`` all around the image.
* ``filename_1-2-3.png`` -> ``1px 2px 3px 2px`` around the image.
* ``filename_1-2-3-4.png`` -> ``1px 2px 3px 4px`` around the image.
"""
padding = self._padding_info
if len(padding) == 0 or \
self.sprite.manager.config.ignore_filename_paddings:
padding = self.sprite.config.padding
return self._generate_padding(padding)
@property
def x(self):
"""Y coordinate for this image."""
return self.node.x + self.padding[3]
@property
def y(self):
"""X coordinate for this image."""
return self.node.y + self.padding[0]
def __lt__(self, img):
"""Use the maxside, width, height or area as ordering algorithm.
:param img: Another :class:`~Image`."""
algorithm = self.sprite.config.algorithm
if algorithm == 'width':
return self.width <= img.width
elif algorithm == 'height':
return self.height <= img.height
elif algorithm == 'area':
return self.width * self.height <= img.width * img.height
else:
return max(self.width, self.height) <= max(img.width, img.height)
class Sprite(object):
DEFAULT_SETTINGS = {'padding': '0',
'algorithm': 'maxside',
'namespace': 'sprite',
'crop': False,
'url': ''}
def __init__(self, name, path, manager):
"""Sprite constructor.
:param name: Sprite name.
:param path: Sprite path
:param manager: Sprite manager. :class:`~MultipleSpriteManager` or
:class:`SimpleSpriteManager`"""
self.name = name
self.manager = manager
self.images = []
self.path = path
self.config = manager.config.extend(get_file_config(self.path))
if self.config.algorithm not in Image.ORDERINGS:
raise InvalidImageOrderingAlgorithmError(self.config.algorithm)
self.process()
def process(self):
"""Process a sprite path searching for all the images and then
allocate all of them in the more appropriate position.
"""
self.images = self._locate_images()
width = self.images[0].width
height = self.images[0].height
root = Node(width=width, height=height)
# Loot all over the images creating a binary tree
for image in self.images:
self.manager.log("\t %s => .%s" % (image.name, image.class_name))
node = root.find(root, image.width, image.height)
if node: # Use this node
image.node = root.split(node, image.width, image.height)
else: # Grow the canvas
image.node = root.grow(image.width, image.height)
def _locate_images(self):
"""Return all the valid images within a folder.
All the files with an extension not included in VALID_IMAGE_EXTENSIONS
(png, jpg, jpeg and gif) or begging with a '.' will be ignored.
If the folder doesn't contain any valid image it will raise
a :class:`~MultipleImagesWithSameNameError`
The list of images will be ordered using the desired ordering
algorithm. The default one is 'maxside'.
"""
extensions = '|'.join(self.manager.VALID_IMAGE_EXTENSIONS)
extension_re = re.compile('.+\.(%s)$' % extensions, re.IGNORECASE)
images = [Image(n, sprite=self) for n in os.listdir(self.path) if \
not n.startswith('.') and \
extension_re.match(n)]
if not len(images):
raise SourceImagesNotFoundError()
# Check if there are duplicate class names
class_names = [i.class_name for i in images]
if len(set(class_names)) != len(images):
dup = [i for i in images if class_names.count(i.class_name) > 1]
raise MultipleImagesWithSameNameError(dup)
return sorted(images, reverse=True)
def save_image(self):
"""Create the image file for this sprite."""
self.manager.log("Creating '%s' image file..." % self.name)
sprite_output_path = self.manager.output_path('img')
# Search for the max x and y (Necessary to generate the canvas).
width = height = 0
for image in self.images:
x = image.node.x + image.width
y = image.node.y + image.height
if width < x:
width = x
if height < y:
height = y
# Create the sprite canvas
canvas = PImage.new('RGBA', (width, height), (0, 0, 0, 0))
# Paste the images inside the canvas
for image in self.images:
canvas.paste(image.image, (image.x, image.y))
# Save png
sprite_filename = '%s.png' % self.filename
sprite_image_path = os.path.join(sprite_output_path, sprite_filename)
save = lambda: canvas.save(sprite_image_path, optimize=True)
save()
if self.config.optipng:
command = ["%s %s" % (self.config.optipngpath,
sprite_image_path)]
error = subprocess.call(command, shell=True, stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
if error:
self.manager.log("Error: optipng has fail, reverting to "
"the original file.")
save()
def save_css(self):
"""Create the css or less file for this sprite."""
format = 'less' if self.config.less else 'css'
self.manager.log("Creating '%s' %s file..." % (self.name, format))
output_path = self.manager.output_path('css')
css_filename = os.path.join(output_path, '%s.%s' % (self.filename,
format))
css_file = open(css_filename, 'w')
for image in self.images:
data = {'namespace': image.sprite.namespace,
'sprite_url': image.sprite.image_url,
'image_class_name': image.class_name,
'top': image.node.y * -1 if image.node.y else 0,
'left': image.node.x * -1 if image.node.x else 0,
'width': image.width,
'height': image.height}
style = (".%(image_class_name)s{ "
"background:url('%(sprite_url)s') no-repeat "
"%(left)ipx %(top)ipx; ")
if self.config.size:
style += "width:%(width)spx; height:%(height)spx;"
style += "}\n"
css_file.write(style % data)
css_file.close()
@property
def namespace(self):
"""Return the namespace for this sprite."""
return '%s-%s' % (self.config.namespace, self.name)
@property
def filename(self):
"""Return the desired filename for this sprite generated files."""
return self.name
@property
def image_path(self):
"""Return the output path for the image file."""
return os.path.join(self.manager.output_path('img'),
'%s.png' % self.filename)
@property
def image_url(self):
"""Return the sprite image url."""
url = os.path.relpath(self.image_path, self.manager.output_path('css'))
if self.config.url:
url = os.path.join(self.config.url, '%s.png' % self.filename)
if self.config.cachebuster:
sprite_file = open(self.image_path, 'rb')
sprite_hash = hashlib.sha1(sprite_file.read()).hexdigest()
sprite_file.close()
url = "%s?%s" % (url, sprite_hash[:6])
return url
class ConfigManager(object):
"""Manage all the available configuration.
If no config is available, return the default one."""
def __init__(self, *args, **kwargs):
"""ConfigManager constructor.
:param *args: List of config dictionaries. The order of this list is
important because as soon as one config property
is available it will be returned.
:param defaults: Dictionary with the default configuration.
:param priority: Dictionary with the command line configuration. This
configuration will override any other from any source.
"""
self.defaults = kwargs.get('defaults', {})
self.priority = kwargs.get('priority', {})
self.sources = list(args)
def extend(self, config):
"""Return a new :class:`~ConfigManager` instance with this new config
inside the sources list.
:param config: Dictionary with the new config.
"""
return self.__class__(config, *self.sources, priority=self.priority,
defaults=self.defaults)
def __getattr__(self, name):
"""Return the first available configuration value for this key. This
method always prioritize the command line configuration. If this key
is not available within any configuration dictionary return the
default value
:param name: Configuration property name.
"""
sources = [self.priority] + self.sources
for source in sources:
value = source.get(name)
if value is not None:
return value
return self.defaults.get(name)
class BaseManager(object):
VALID_IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif']
def __init__(self, path, config, output=None):
""" BaseManager constructor.
:param path: Sprite path.
:param config: :class:`~ConfigManager` instance with all the
configuration for this sprite.
:param output: output dir.
"""
self.path = path
self.config = config
self.output = output
self.sprites = []
def process_sprite(self, path, name):
"""Create a new Sprite using this path and name and append it to the
sprites list.
:param path: Sprite path.
:param name: Sprite name.
"""
self.log("Processing '%s':" % name)
sprite = Sprite(name=name, path=path, manager=self)
self.sprites.append(sprite)
def save(self):
"""Save all the sprites inside this manager."""
for sprite in self.sprites:
sprite.save_image()
sprite.save_css()
def output_path(self, format):
"""Return the path where all the generated files will be saved.
:param format: File format.
"""
if format == 'css' and self.config.css_dir:
sprite_output_path = self.config.css_dir
elif format == 'img' and self.config.img_dir:
sprite_output_path = self.config.img_dir
else:
sprite_output_path = self.output
if not os.path.exists(sprite_output_path):
os.makedirs(sprite_output_path)
return sprite_output_path
def log(self, message):
""" Prints the message if it's necessary. """
if not self.config.quiet:
print(message)
def process(self):
raise NotImplementedError()
class MultipleSpriteManager(BaseManager):
def process(self):
"""Process a path searching for folders that contains images.
Every folder will be a new sprite with all the images inside.
The filename of the image also can contain information about the
padding needed around the image.
* ``filename.png`` wil have the default padding (10px).
* ``filename_20.png`` will have 20px all around the image.
* ``filename_1-2-3.png`` will have 1px 2px 3px 2px around the image.
* ``filename_1-2-3-4.png`` will have 1px 2px 3px 4px around the image.
The generated css file will have a css class for every image found
inside the sprite folder. This css class names will have the
following format:
``.[namespace]-[sprite_name]-[image_name]{ ... }``
The image_name will only contain the alphanumeric characters,
``-`` and ``_``. The default namespace is ``sprite``, it but could be
overridden using the ``--namespace`` optional argument.
* ``animals/cat.png`` css class will be ``.sprite-animals-cat``
* ``animals/cow_20.png`` css class will be ``.sprite-animals-cow``
If two images has the same name, a
:class:`~MultipleImagesWithSameNameError` error will be raised.
"""
for sprite_name in os.listdir(self.path):
# Only process folders
path = os.path.join(self.path, sprite_name)
if os.path.isdir(path):
self.process_sprite(path=path, name=sprite_name)
if not len(self.sprites):
raise NoSpritesFoldersFoundError()
self.save()
class SimpleSpriteManager(BaseManager):
def process(self):
"""Process an unique folder and create one sprite. It works in the
same way than :class:`~MultipleSpriteManager` but for only one folder.
This is not the default manager. It is only used if you use
the ``--simple`` default argument.
"""
self.process_sprite(path=self.path, name=os.path.basename(self.path))
self.save()
def get_file_config(path, section='sprite'):
""" Return as a dictionary all the available configuration inside the
sprite configuration file on this path
:param path: Path where the configuration file is.
:param section: The configuration file section que need to read.
"""
def clean(value):
return {'true': True, 'false': False}.get(value.lower(), value)
config = ConfigParser.RawConfigParser()
config.read(os.path.join(path, CONFIG_FILENAME))
try:
keys = config.options(section)
except ConfigParser.NoSectionError:
return {}
return dict([[k, clean(config.get(section, k))] for k in keys])
def command_exists(command):
""" Check if a command exists running it.
:param command: command name.
"""
try:
subprocess.check_call([command], shell=True, stdin=subprocess.PIPE,
stderr=subprocess.PIPE, stdout=subprocess.PIPE)
except subprocess.CalledProcessError:
return False
return True
#########################################################################
# PIL currently doesn't support full alpha for PNG8 so it's necessary to
# monkey patch PIL to support them.
# http://mail.python.org/pipermail/image-sig/2010-October/006533.html
#########################################################################
from PIL import ImageFile, PngImagePlugin
def patched_chunk_tRNS(self, pos, len):
i16 = PngImagePlugin.i16
s = ImageFile._safe_read(self.fp, len)
if self.im_mode == "P":
self.im_info["transparency"] = map(ord, s)
elif self.im_mode == "L":
self.im_info["transparency"] = i16(s)
elif self.im_mode == "RGB":
self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:])
return s
PngImagePlugin.PngStream.chunk_tRNS = patched_chunk_tRNS
def patched_load(self):
if self.im and self.palette and self.palette.dirty:
apply(self.im.putpalette, self.palette.getdata())
self.palette.dirty = 0
self.palette.rawmode = None
try:
trans = self.info["transparency"]
except KeyError:
self.palette.mode = "RGB"
else:
try:
for i, a in enumerate(trans):
self.im.putpalettealpha(i, a)
except TypeError:
self.im.putpalettealpha(trans, 0)
self.palette.mode = "RGBA"
if self.im:
return self.im.pixel_access(self.readonly)
PImage.Image.load = patched_load
#########################################################################
def main():
parser = OptionParser(usage=("usage: %prog [options] source_dir [<output> "
"| --css=<dir> --img=<dir>]"))
parser.add_option("-s", "--simple", action="store_true", dest="simple",
help="only generate sprites for one folder")
parser.add_option("-c", "--crop", dest="crop", action='store_true',
help="crop images removing unnecessary transparent margins")
parser.add_option("-l", "--less", dest="less", action='store_true',
help="the output stylesheets will be .less and not .css")
parser.add_option("-u", "--url", dest="url", default=None,
help="prepend this url to the sprites filename")
parser.add_option("-q", "--quiet", dest="quiet", action='store_true',
help="suppress all normal output")
parser.add_option("-p", "--padding", dest="padding", default=None,
help="force this padding to all the images")
parser.add_option("-z", "--no-size", action="store_false", dest="size",
help="don't add the image size to the sprite")
group = OptionGroup(parser, "Output Options")
group.add_option("--css", dest="css_dir", default='',
help="output directory for the css files", metavar='DIR')
group.add_option("--img", dest="img_dir", default='', metavar='DIR',
help="output directory for the img files")
parser.add_option_group(group)
group = OptionGroup(parser, "Advanced Options")
group.add_option("-a", "--algorithm", dest="algorithm", default=None,
help=("ordering algorithm: maxside, width, height or "
"area (default: maxside)"), metavar='NAME')
group.add_option("--namespace", dest="namespace", default=None,
help="namespace for the css (default: sprite)")
group.add_option("--ignore-filename-paddings",
dest="ignore_filename_paddings", action='store_true',
help="ignore filename paddings")
parser.add_option_group(group)
group = OptionGroup(parser, "Optipng Options",
"You must install optipng before using this options")
group.add_option("--optipng", dest="optipng", action='store_true',
help="postprocess images using optipng")
group.add_option("--optipngpath", dest="optipngpath", default='optipng',
help="path to optipng (default: optipng)", metavar='PATH')
parser.add_option_group(group)
group = OptionGroup(parser, "Browser Cache Invalidation Options")
group.add_option("--cachebuster", dest="cachebuster",
action='store_true',
help=("use the sprite's sha1 6 first characters as a "
"queryarg everywhere that file is used on the "
"css"))
parser.add_option_group(group)
(options, args) = parser.parse_args()
if not len(args):
parser.error("You must choose the folder that contains the sprites.")
if len(args) == 1 and not (options.css_dir and options.img_dir):
parser.error(("You must choose the output folder using the output "
"argument or --img and --css."))
if len(args) == 2 and (options.css_dir or options.img_dir):
parser.error(("You must choose between using an unique output dir, or "
"using --css and --img."))
if options.optipng and not command_exists(options.optipngpath):
parser.error("'optipng' seems to not be available. You must "
"install it before using the --optipng option or "
"provide a path using --optipngpath.")
source = os.path.abspath(args[0])
output = os.path.abspath(args[1]) if len(args) == 2 else None
if not os.path.isdir(source):
parser.error("Directory not found: '%s'" % source)
if options.simple:
manager_cls = SimpleSpriteManager
else:
manager_cls = MultipleSpriteManager
# Get configuration from file
config = get_file_config(source)
# Convert options to dict
options = options.__dict__
config = ConfigManager(config, priority=options, defaults=DEFAULT_SETTINGS)
manager = manager_cls(path=source, output=output, config=config)
try:
manager.process()
except MultipleImagesWithSameNameError, e:
sys.stderr.write("Error: Some images will have the same class name:\n")
for image in e.args[0]:
sys.stderr.write('\t %s => .%s\n' % (image.name, image.class_name))
except SourceImagesNotFoundError:
sys.stderr.write("Error: No images found.\n")
except NoSpritesFoldersFoundError:
sys.stderr.write("Error: No sprites folders found.\n")
if __name__ == "__main__":
main()