Created
March 11, 2010 15:41
-
-
Save mikewest/329251 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# encoding: utf-8; | |
import os | |
import errno | |
import sys | |
import re | |
import subprocess | |
from optparse import OptionParser | |
try: | |
from yaml import load, CLoader as Loader | |
except ImportError: | |
from yaml import Loader, Dumper | |
def mkdir_p(path): | |
try: | |
os.makedirs(path) | |
except OSError, exc: | |
if exc.errno == errno.EEXIST: | |
pass | |
else: raise | |
class StaticAssetBuilder(object): | |
def __init__( self, settings ): | |
self.urls_seen = {} | |
self.settings = settings | |
def log( self, message ): | |
if self.settings[ 'VERBOSE' ]: | |
print message | |
def replacer( self, matchobj ): | |
url = matchobj.group(0) | |
if not url in self.urls_seen: | |
cdn = self.settings[ 'CDN_SERVERS' ][ len( self.urls_seen ) % len( self.settings[ 'CDN_SERVERS' ] ) ] | |
self.urls_seen[ url ] = "http://" + cdn + url | |
return self.urls_seen[ url ] | |
def process_urls( self, line ): | |
""" | |
Given a line of text, find all strings of the form | |
`/static_assets/dev/...`, and generate appropriate | |
live URLs. | |
For our purposes, that means: | |
1. Assign the file a CDN server (reusing previously assigned | |
servers if we've already seen the file). | |
2. Strip `/static_assets/dev/`, and replace with `/static_assets/build/` | |
@TODO: Versions. I like Brad's hash idea. | |
""" | |
return re.sub( | |
r'/static_assets/dev/[\-_a-zA-Z\.\/]+', | |
self.replacer, | |
line) | |
def static_combine( self, end_file, to_combine, delimiter="\n/* Begin: %s */\n" ): | |
self.log( "* Building `%s`" % os.path.basename( end_file ) ) | |
if end_file.find( '.css' ) != -1: | |
mode = 'CSS' | |
else: | |
mode = 'JS' | |
processed_output = '' | |
if mode == 'CSS': | |
processed_output = """@charset "UTF-8";\n""" | |
for static_file in to_combine: | |
if os.path.isfile( static_file ): | |
if delimiter: | |
processed_output += delimiter % os.path.split( static_file )[ 1 ] | |
with open( static_file, 'r' ) as f: | |
for line in f: | |
if self.settings[ 'CDN' ]: | |
processed_output += self.process_urls( line ) | |
else: | |
processed_output += line | |
if processed_output: | |
with open( end_file, 'w' ) as combo: | |
combo.write( processed_output ) | |
if self.settings[ 'COMPRESS' ]: | |
try: | |
command = self.settings[ 'COMPRESSION_COMMAND' ] % end_file | |
print command | |
proc = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE ) | |
output = proc.communicate()[ 0 ] | |
with open( re.sub( r'(\.\w+)$', r'.min\1', end_file ), 'w' ) as minified: | |
minified.write( output ) | |
except AttributeError, error: | |
raise CommandError("COMPRESSION_COMMAND not set") | |
except TypeError, error: | |
raise CommandError("No string substitution provided for the input file to be passed to the argument ('cmd %s')") | |
def build( self ): | |
for filetype in [ 'js', 'css' ]: | |
if filetype in self.settings[ 'FILES' ]: | |
to_build = self.settings[ 'FILES' ][ filetype ] | |
build_root = os.path.join( self.settings[ 'OUTPUT_ROOT' ], filetype ) | |
mkdir_p( build_root ) | |
for filename, filelist in to_build.items(): | |
self.static_combine( | |
end_file=os.path.join( build_root, filename ), | |
to_combine=[ os.path.join( self.settings[ 'SOURCE_ROOT' ], filetype, f) for f in filelist ] | |
) | |
def main(argv=None): | |
if argv is None: | |
argv = sys.argv | |
default_root = os.path.dirname(os.path.abspath(__file__)) | |
with open( "%s/build.yaml" % default_root, 'r' ) as f: | |
settings = load( f ) | |
settings[ 'COMPRESSION_COMMAND' ] = settings[ 'COMPRESSION_COMMAND' ] % default_root | |
parser = OptionParser(usage="Usage: %prog [options]", version="%prog 0.1") | |
parser.add_option( "--verbose", | |
action="store_true", dest="verbose_mode", default=False, | |
help="Verbose mode") | |
parser.add_option( "-o", "--output", | |
action="store", | |
dest="output_root", | |
default=os.path.join(default_root, '../build'), | |
help="Directory to which output will be written") | |
parser.add_option( "-s", "--source", | |
action="store", | |
dest="source_root", | |
default=os.path.join(default_root, "../src"), | |
help="Directory from which source files will be read") | |
parser.add_option( "-c", "--compress", | |
action="store_true", | |
dest="compress", | |
default=False, | |
help="Compress the generated files using the specified compression command") | |
parser.add_option( "--cdn", | |
action="store_true", | |
dest="use_cdn", | |
default=False, | |
help="Generate CDN URLs for static assets in generated files.") | |
(options, args) = parser.parse_args() | |
settings[ 'VERBOSE' ] = options.verbose_mode | |
settings[ 'CDN' ] = options.use_cdn | |
settings[ 'COMPRESS'] = options.compress | |
settings[ 'OUTPUT_ROOT' ] = options.output_root | |
settings[ 'SOURCE_ROOT' ] = options.source_root | |
builder = StaticAssetBuilder( settings ) | |
builder.build() | |
if __name__ == "__main__": | |
sys.exit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment