Skip to content

Instantly share code, notes, and snippets.

@mikewest
Created March 11, 2010 15:41
Show Gist options
  • Save mikewest/329251 to your computer and use it in GitHub Desktop.
Save mikewest/329251 to your computer and use it in GitHub Desktop.
#!/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