root/galaxy-central/static/june_2007_style/process_css.py @ 2

リビジョン 2, 9.2 KB (コミッタ: hatakeyama, 14 年 前)

import galaxy-central

  • 属性 svn:executable の設定値 *
行番号 
1#!/usr/bin/env python
2
3"""
4CSS processor for Galaxy style sheets. Supports the following features:
5
6- Nested rule definition
7- Mixins
8- Variable substitution in values
9
10"""
11
12import sys, string, os.path, os
13
14new_path = [ os.path.join( os.getcwd(), '..', '..', "lib" ) ]
15new_path.extend( sys.path[1:] ) # remove scripts/ from the path
16sys.path = new_path
17
18from galaxy import eggs
19import pkg_resources
20from galaxy.util.odict import odict
21
22from pyparsing import *
23#from odict import odict
24
25try:
26    import Image
27except ImportError:
28    from PIL import Image
29
30def cross_lists(*sets):
31    """
32    Return the cross product of the arguments
33    """
34    wheels = map(iter, sets)
35    digits = [it.next() for it in wheels]
36    while True:
37        yield digits[:]
38        for i in range(len(digits)-1, -1, -1):
39            try:
40                digits[i] = wheels[i].next()
41                break
42            except StopIteration:
43                wheels[i] = iter(sets[i])
44                digits[i] = wheels[i].next()
45        else:
46            break
47       
48def find_file( path, fname ):
49    # Path can be a single directory or a ':' separated list
50    if ':' in path:
51        paths = path.split( ':' )
52    else:
53        paths = [ path ]
54    # Check in each directory
55    for path in paths:
56        fullname = os.path.join( path, fname )
57        if os.path.exists( fullname ):
58            return fullname
59    # Not found
60    raise IOError( "File '%s' not found in path '%s'" % ( fname, paths ) )
61
62def build_stylesheet_parser():
63    """
64    Returns a PyParsing parser object for CSS
65    """
66
67    # Forward declerations for recursion
68    rule = Forward()
69   
70    # Structural syntax, supressed from parser output
71    lbrace = Literal("{").suppress()
72    rbrace = Literal("}").suppress()
73    colon  = Literal(":").suppress()
74    semi   = Literal(";").suppress()
75   
76    ident = Word( alphas + "_", alphanums + "_-" )
77   
78    # Properties
79    prop_name  = Word( alphas + "_-*", alphanums + "_-" )
80    prop_value  = CharsNotIn( ";" )  # expand this as needed
81    property_def = Group( prop_name + colon + prop_value + semi ).setResultsName( "property_def" )
82   
83    # Selectors
84    #   Just match anything that looks like a selector, including element, class,
85    #   id, attribute, and pseudoclass. Attributes are not handled properly (spaces,
86    #   and even newlines in the quoted string are legal).
87    simple_selector = Word( alphanums + "@.#*:()[]|=\"'_-" )
88    combinator = Literal( ">" ) | Literal( "+" )
89    selector = Group( simple_selector + ZeroOrMore( Optional( combinator ) + simple_selector ) )
90    selectors = Group( delimitedList( selector ) )
91   
92    selector_mixin = Group( selector + semi ).setResultsName( "selector_mixin" )
93   
94    # Rules
95    rule << Group( selectors +
96                   lbrace +
97                   Group( ZeroOrMore( property_def | rule | selector_mixin ) ) +
98                   rbrace ).setResultsName( "rule" )
99   
100    # A whole stylesheet
101    stylesheet = ZeroOrMore( rule )
102   
103    # C-style comments should be ignored, as should "##" comments
104    stylesheet.ignore( cStyleComment )
105    stylesheet.ignore( "##" + restOfLine )
106   
107    return stylesheet
108
109stylesheet_parser = build_stylesheet_parser()
110
111class CSSProcessor( object ):
112   
113    def process( self, file, out, variables, image_dir, out_dir ):
114        # Build parse tree
115        results = stylesheet_parser.parseFile( sys.stdin, parseAll=True )
116        # Expand rules (elimimate recursion and resolve mixins)
117        rules = self.expand_rules( results )
118        # Expand variables (inplace)
119        self.expand_variables( rules, variables )
120        # Do sprites
121        self.make_sprites( rules, image_dir, out_dir )
122        # Print
123        self.print_rules( rules, out )
124       
125    def expand_rules( self, parse_results ):
126        mixins = {}
127        rules = []
128        # Visitor for recursively expanding rules
129        def visitor( r, selector_prefixes ):
130            # Concatenate combinations and build list of expanded selectors
131            selectors = [ " ".join( s ) for s in r[0] ]
132            full_selector_list = selector_prefixes + [selectors]
133            full_selectors = []
134            for l in cross_lists( *full_selector_list ):
135                full_selectors.append(  " ".join( l ) )
136            # Separate properties from recursively defined rules
137            properties = []
138            children = []
139            for dec in r[1]:
140                type = dec.getName()
141                if type == "property_def":
142                    properties.append( dec )
143                elif type == "selector_mixin":
144                    properties.extend( mixins[dec[0][0]] )
145                else:
146                    children.append( dec )
147            rules.append( ( full_selectors, properties ) )
148            # Save by name for mixins (not smart enough to combine rules!)       
149            for s in full_selectors:
150                mixins[ s ] = properties;
151            # Visit children
152            for child in children:
153                visitor( child, full_selector_list )
154        # Call at top level
155        for p in parse_results:
156            visitor( p, [] )
157        # Return the list of expanded rules
158        return rules
159           
160    def expand_variables( self, rules, context ):
161        for selectors, properties in rules:
162            for p in properties:
163                p[1] = string.Template( p[1] ).substitute( context ).strip()
164   
165    def make_sprites( self, rules, image_dir, out_dir ):
166       
167        pad = 10
168       
169        class SpriteGroup( object ):
170            def __init__( self, name ):
171                self.name = name
172                self.offset = 0
173                self.sprites = odict()
174            def add_or_get_sprite( self, fname ):
175                if fname in self.sprites:
176                    return self.sprites[fname]
177                else:
178                    sprite = self.sprites[fname] = Sprite( fname, self.offset )
179                    self.offset += sprite.image.size[1] + pad
180                    return sprite
181       
182        class Sprite( object ):
183            def __init__( self, fname, offset ):
184                self.fname = fname
185                self.image = Image.open( find_file( image_dir, fname ) )
186                self.offset = offset
187               
188        sprite_groups = {}
189       
190        for i in range( len( rules ) ):
191            properties = rules[i][1]
192            new_properties = []
193            # Find sprite properties (and remove them). Last takes precedence
194            sprite_group_name = None
195            sprite_filename = None
196            sprite_horiz_position = "0px"
197            for name, value in properties:
198                if name == "-sprite-group":
199                    sprite_group_name = value
200                elif name == "-sprite-image":
201                    sprite_filename = value
202                elif name == "-sprite-horiz-position":
203                    sprite_horiz_position = value
204                else:
205                    new_properties.append( ( name, value ) )
206            # If a sprite filename was found, deal with it...
207            if sprite_group_name and sprite_filename:
208                if sprite_group_name not in sprite_groups:
209                    sprite_groups[sprite_group_name] = SpriteGroup( sprite_group_name )
210                sprite_group = sprite_groups[sprite_group_name]
211                sprite = sprite_group.add_or_get_sprite( sprite_filename )
212                new_properties.append( ( "background", "url(%s.png) no-repeat %s -%dpx" % ( sprite_group.name, sprite_horiz_position, sprite.offset ) ) )
213            # Save changed properties
214            rules[i] = ( rules[i][0], new_properties )
215       
216        # Generate new images
217        for group in sprite_groups.itervalues():
218            w = 0
219            h = 0
220            for sprite in group.sprites.itervalues():
221                sw, sh = sprite.image.size
222                w = max( w, sw )
223                h += sh + pad
224            master = Image.new( mode='RGBA', size=(w, h), color=(0,0,0,0) )
225            offset = 0
226            for sprite in group.sprites.itervalues():
227                master.paste( sprite.image, (0,offset) )
228                offset += sprite.image.size[1] + pad
229            master.save( os.path.join( out_dir, group.name + ".png" ) )
230           
231    def print_rules( self, rules, file ):
232        for selectors, properties in rules:
233            file.write( ",".join( selectors ) )
234            file.write( "{" )
235            for name, value in properties:
236                file.write( "%s:%s;" % ( name, value ) )
237            file.write( "}\n" )
238
239def main():
240
241    # Read variable definitions from a (sorta) ini file
242    context = dict()
243    for line in open( sys.argv[1] ):
244        if line.startswith( '#' ):
245            continue
246        key, value = line.rstrip("\r\n").split( '=' )
247        if value.startswith( '"' ) and value.endswith( '"' ):
248            value = value[1:-1]
249        context[key] = value
250       
251    image_dir = sys.argv[2]
252    out_dir = sys.argv[3]
253
254    try:
255       
256        processor = CSSProcessor()
257        processor.process( sys.stdin, sys.stdout, context, image_dir, out_dir )
258       
259    except ParseException, e:
260       
261        print >> sys.stderr, "Error:", e
262        print >> sys.stderr, e.markInputline()
263        sys.exit( 1 )
264   
265
266if __name__ == "__main__":
267    main()
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。