├── test └── example │ ├── __init__.py │ ├── urls.py │ ├── media │ └── css │ │ └── 960 │ │ ├── reset.css │ │ ├── text.css │ │ ├── grid-24.css │ │ └── grid.css │ └── settings.py ├── REQUIREMENTS ├── MANIFEST.in ├── src └── djcssmin │ ├── __init__.py │ ├── utils.py │ └── commands.py ├── .gitignore ├── doc ├── .markdoc.yaml ├── settings.md └── index.md ├── setup.py ├── UNLICENSE ├── README.md └── distribute_setup.py /test/example/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /REQUIREMENTS: -------------------------------------------------------------------------------- 1 | django-boss>=0.3 2 | cssmin>=0.1 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include REQUIREMENTS 2 | include distribute_setup.py 3 | -------------------------------------------------------------------------------- /src/djcssmin/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __version__ = '0.3' 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | *.pyo 4 | .DS_Store 5 | MANIFEST 6 | build 7 | dist 8 | doc/.html 9 | doc/.tmp 10 | test/example/dev.db 11 | test/example/media/css/*.min.css 12 | -------------------------------------------------------------------------------- /doc/.markdoc.yaml: -------------------------------------------------------------------------------- 1 | wiki-name: django-cssmin Documentation 2 | 3 | static-dir: ".static" 4 | wiki-dir: "." 5 | 6 | markdown: 7 | extensions: 8 | - codehilite 9 | - def_list 10 | - headerid 11 | 12 | -------------------------------------------------------------------------------- /test/example/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | # Uncomment the next two lines to enable the admin: 4 | # from django.contrib import admin 5 | # admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | # Example: 9 | # (r'^example/', include('example.foo.urls')), 10 | 11 | # Uncomment the admin/doc line below and add 'django.contrib.admindocs' 12 | # to INSTALLED_APPS to enable admin documentation: 13 | # (r'^admin/doc/', include('django.contrib.admindocs.urls')), 14 | 15 | # Uncomment the next line to enable the admin: 16 | # (r'^admin/', include(admin.site.urls)), 17 | ) 18 | -------------------------------------------------------------------------------- /test/example/media/css/960/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ */ 2 | /* v1.0 | 20080212 */ 3 | 4 | html, body, div, span, applet, object, iframe, 5 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 6 | a, abbr, acronym, address, big, cite, code, 7 | del, dfn, em, font, img, ins, kbd, q, s, samp, 8 | small, strike, strong, sub, sup, tt, var, 9 | b, u, i, center, 10 | dl, dt, dd, ol, ul, li, 11 | fieldset, form, label, legend, 12 | table, caption, tbody, tfoot, thead, tr, th, td { 13 | margin: 0; 14 | padding: 0; 15 | border: 0; 16 | outline: 0; 17 | font-size: 100%; 18 | vertical-align: baseline; 19 | background: transparent; 20 | } 21 | body { 22 | line-height: 1; 23 | } 24 | ol, ul { 25 | list-style: none; 26 | } 27 | blockquote, q { 28 | quotes: none; 29 | } 30 | blockquote:before, blockquote:after, 31 | q:before, q:after { 32 | content: ''; 33 | content: none; 34 | } 35 | 36 | /* remember to define focus styles! */ 37 | :focus { 38 | outline: 0; 39 | } 40 | 41 | /* remember to highlight inserts somehow! */ 42 | ins { 43 | text-decoration: none; 44 | } 45 | del { 46 | text-decoration: line-through; 47 | } 48 | 49 | /* tables still need 'cellspacing="0"' in the markup */ 50 | table { 51 | border-collapse: collapse; 52 | border-spacing: 0; 53 | } -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import re 6 | 7 | from distribute_setup import use_setuptools; use_setuptools() 8 | from setuptools import setup, find_packages 9 | 10 | 11 | rel_file = lambda *args: os.path.join(os.path.dirname(os.path.abspath(__file__)), *args) 12 | 13 | def read_from(filename): 14 | fp = open(filename) 15 | try: 16 | return fp.read() 17 | finally: 18 | fp.close() 19 | 20 | def get_version(): 21 | data = read_from(rel_file('src', 'djcssmin', '__init__.py')) 22 | return re.search(r"__version__ = '([^']+)'", data).group(1) 23 | 24 | def get_requirements(): 25 | data = read_from(rel_file('REQUIREMENTS')) 26 | lines = map(lambda s: s.strip(), data.splitlines()) 27 | return filter(None, lines) 28 | 29 | 30 | setup( 31 | name = 'django-cssmin', 32 | version = get_version(), 33 | author = "Zachary Voase", 34 | author_email = "zacharyvoase@me.com", 35 | url = 'http://github.com/zacharyvoase/django-cssmin', 36 | description = "CSS compression for Django.", 37 | packages = find_packages(where='src'), 38 | package_dir = {'': 'src'}, 39 | install_requires = get_requirements(), 40 | ) 41 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /test/example/media/css/960/text.css: -------------------------------------------------------------------------------- 1 | /* 2 | 960 Grid System ~ Text CSS. 3 | Learn more ~ http://960.gs/ 4 | 5 | Licensed under GPL and MIT. 6 | */ 7 | 8 | /* `Basic HTML 9 | ----------------------------------------------------------------------------------------------------*/ 10 | 11 | body { 12 | font: 13px/1.5 'Helvetica Neue', Arial, 'Liberation Sans', FreeSans, sans-serif; 13 | } 14 | 15 | a:focus { 16 | outline: 1px dotted; 17 | } 18 | 19 | hr { 20 | border: 0 #ccc solid; 21 | border-top-width: 1px; 22 | clear: both; 23 | height: 0; 24 | } 25 | 26 | /* `Headings 27 | ----------------------------------------------------------------------------------------------------*/ 28 | 29 | h1 { 30 | font-size: 25px; 31 | } 32 | 33 | h2 { 34 | font-size: 23px; 35 | } 36 | 37 | h3 { 38 | font-size: 21px; 39 | } 40 | 41 | h4 { 42 | font-size: 19px; 43 | } 44 | 45 | h5 { 46 | font-size: 17px; 47 | } 48 | 49 | h6 { 50 | font-size: 15px; 51 | } 52 | 53 | /* `Spacing 54 | ----------------------------------------------------------------------------------------------------*/ 55 | 56 | ol { 57 | list-style: decimal; 58 | } 59 | 60 | ul { 61 | list-style: disc; 62 | } 63 | 64 | li { 65 | margin-left: 30px; 66 | } 67 | 68 | p, 69 | dl, 70 | hr, 71 | h1, 72 | h2, 73 | h3, 74 | h4, 75 | h5, 76 | h6, 77 | ol, 78 | ul, 79 | pre, 80 | table, 81 | address, 82 | fieldset { 83 | margin-bottom: 20px; 84 | } -------------------------------------------------------------------------------- /test/example/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for example project. 2 | 3 | DEBUG = True 4 | TEMPLATE_DEBUG = DEBUG 5 | 6 | ADMINS = ( 7 | ('Zachary Voase', 'zacharyvoase@me.com'), 8 | ) 9 | 10 | MANAGERS = ADMINS 11 | 12 | DATABASE_ENGINE = 'sqlite3' 13 | DATABASE_NAME = 'dev.db' 14 | DATABASE_USER = '' 15 | DATABASE_PASSWORD = '' 16 | DATABASE_HOST = '' 17 | DATABASE_PORT = '' 18 | TIME_ZONE = 'America/Chicago' 19 | LANGUAGE_CODE = 'en-us' 20 | SITE_ID = 1 21 | USE_I18N = True 22 | MEDIA_ROOT = '' 23 | MEDIA_URL = '' 24 | ADMIN_MEDIA_PREFIX = '/media/' 25 | SECRET_KEY = '1(lavq&ib1(a+=_%(6_)njg0^y$i*2t@e3#0wl1k)zehg*$nf$' 26 | TEMPLATE_LOADERS = ( 27 | 'django.template.loaders.filesystem.load_template_source', 28 | 'django.template.loaders.app_directories.load_template_source', 29 | # 'django.template.loaders.eggs.load_template_source', 30 | ) 31 | MIDDLEWARE_CLASSES = ( 32 | 'django.middleware.common.CommonMiddleware', 33 | 'django.contrib.sessions.middleware.SessionMiddleware', 34 | ) 35 | ROOT_URLCONF = 'example.urls' 36 | TEMPLATE_DIRS = ( 37 | ) 38 | 39 | INSTALLED_APPS = ( 40 | 'django.contrib.contenttypes', 41 | 'django.contrib.sessions', 42 | 'django.contrib.sites', 43 | 'djcssmin', 44 | ) 45 | 46 | CSSMIN_INPUT = [ 47 | # YUI Reset CSS 48 | 'http://yui.yahooapis.com/2.8.0r4/build/reset/reset.css', 49 | ## 960 Grid System 50 | 'media/css/960/reset.css', 51 | 'media/css/960/text.css', 52 | 'media/css/960/grid.css', 53 | # Local CSS Libraries 54 | 'media/css/*.lib.css', 55 | ] 56 | 57 | CSSMIN_OUTPUT = 'media/css/style.min.css' 58 | 59 | 60 | import logging 61 | 62 | logging.root.setLevel(logging.DEBUG) 63 | handler = logging.StreamHandler() 64 | handler.setLevel(logging.DEBUG) 65 | logging.root.addHandler(handler) 66 | -------------------------------------------------------------------------------- /doc/settings.md: -------------------------------------------------------------------------------- 1 | # Settings 2 | 3 | `django-cssmin` will determine how to compress your CSS files based on the 4 | following settings. 5 | 6 | `DEBUG` 7 | : If this is `True`, `django-cssmin` will not (by default) compress the output 8 | CSS. This is to help with interactive debugging during design. It can be 9 | overridden with the options to the command-line interface; see the output of 10 | `djboss cssmin --help` for more information. 11 | 12 | `CSSMIN_INPUT` (required) 13 | : A list of [glob][] patterns or URLs which `django-cssmin` will expand to get 14 | a list of CSS stylesheet filenames. For example: 15 | 16 | [glob]: http://docs.python.org/library/glob.html 17 | 18 | CSSMIN_INPUT = [ 19 | # YUI Reset CSS 20 | 'http://yui.yahooapis.com/2.8.0r4/build/reset/reset.css', 21 | ## 960 Grid System 22 | 'media/css/960/reset.css', 23 | 'media/css/960/text.css', 24 | 'media/css/960/grid.css', 25 | # Local CSS Libraries 26 | 'media/css/*.lib.css', 27 | ] 28 | 29 | First, the YUI CSS Reset library is downloaded from Yahoo’s servers. The 30 | specified CSS files for the 960.gs framework are included in the given 31 | order. Then, all CSS files in the `media/css/` directory ending in 32 | `.lib.css` are included. Globs which do not match anything just resolve to 33 | empty lists. 34 | 35 | All relative paths in globs are first resolved. `django-cssmin` will check 36 | for the following settings, in this order: 37 | 38 | * `CSSMIN_ROOT` 39 | * `PROJECT_DIR` 40 | * `PROJECT_ROOT` 41 | 42 | Finally, if none exist, the directory containing the settings module will be 43 | considered the base path for resolution. 44 | 45 | `CSSMIN_OUTPUT` (required) 46 | : The filename to which the compressed CSS style data will be written. 47 | Relative output filenames will be resolved as specified above. 48 | 49 | `CSSMIN_ROOT` (optional) 50 | : See the paragraph on relative glob resolution above. 51 | 52 | `CSSMIN_PROLOG` (optional) 53 | : A filename (relative or absolute) which contains a piece of text to include 54 | at the beginning of the compressed CSS stylesheet. This will usually be a 55 | comment containing a copyright statement or license information. 56 | -------------------------------------------------------------------------------- /src/djcssmin/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import logging 4 | import os 5 | import subprocess 6 | import tempfile 7 | import urllib2 8 | import urlparse 9 | 10 | 11 | LOG = logging.getLogger('django.djcssmin') 12 | 13 | 14 | ## workaround for Python pre-2.6. 15 | if not hasattr(os.path, 'relpath'): 16 | def relpath(path, start=os.path.curdir): 17 | """Return a relative version of a path""" 18 | 19 | if not path: 20 | raise ValueError("no path specified") 21 | 22 | start_list = os.path.abspath(start).split(os.path.sep) 23 | path_list = os.path.abspath(path).split(os.path.sep) 24 | 25 | # Work out how much of the filepath is shared by start and path. 26 | i = len(os.path.commonprefix([start_list, path_list])) 27 | 28 | rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] 29 | if not rel_list: 30 | return os.path.curdir 31 | return os.path.join(*rel_list) 32 | os.path.relpath = relpath 33 | 34 | 35 | def get_root(settings): 36 | """Get the directory from which to resolve the `CSSMIN_INPUT` globs.""" 37 | 38 | if hasattr(settings, 'CSSMIN_ROOT'): 39 | return settings.CSSMIN_ROOT 40 | elif hasattr(settings, 'PROJECT_DIR'): 41 | return settings.PROJECT_DIR 42 | elif hasattr(settings, 'PROJECT_ROOT'): 43 | return settings.PROJECT_ROOT 44 | return os.path.dirname(os.path.abspath(settings.__file__)) 45 | 46 | 47 | def get_prolog(settings, root): 48 | """Get the prolog data from the `CSSMIN_PROLOG` setting.""" 49 | 50 | if hasattr(settings, 'CSSMIN_PROLOG'): 51 | filename = make_abs(settings.CSSMIN_PROLOG, root) 52 | if not os.path.exists(filename): 53 | LOG.warn("Specified CSSMIN_PROLOG does not exist, continuing anyway") 54 | else: 55 | return read_from(filename) 56 | return '' 57 | 58 | 59 | def make_abs(path, root): 60 | """Ensure a path is absolute.""" 61 | 62 | return path if os.path.isabs(path) else os.path.abspath(os.path.join(root, path)) 63 | 64 | 65 | def temp_fetch(url): 66 | """Fetch a URL and save it in a temporary file, returning the filename.""" 67 | 68 | conn = urllib2.urlopen(url) 69 | try: 70 | fp = tempfile.NamedTemporaryFile(delete=False) 71 | LOG.info("Saving %s to a temporary file" % truncate_url(url)) 72 | try: 73 | fp.write(conn.read()) 74 | finally: 75 | fp.close() 76 | finally: 77 | conn.close() 78 | 79 | LOG.info("Saved %s to %s" % (truncate_url(url), os.path.basename(fp.name))) 80 | return fp.name 81 | 82 | 83 | def truncate_url(url): 84 | """Return a short version of a URL.""" 85 | 86 | split = list(urlparse.urlsplit(url)) 87 | 88 | path = split[2] 89 | if path == '/' or path.count('/') <= 2: 90 | pass 91 | elif path.endswith('/'): 92 | split[2] = '/.../' + '/'.join(path.rsplit('/', 2)[-2:]) 93 | else: 94 | split[2] = '/.../' + path.rsplit('/', 1)[-1] 95 | 96 | return urlparse.urlunsplit(split) 97 | 98 | 99 | def read_from(filename): 100 | fp = open(filename) 101 | try: 102 | return fp.read() 103 | finally: 104 | fp.close() 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `django-cssmin` 2 | 3 | `django-cssmin` is a reusable application for [Django][] which makes it easy to 4 | automatically [compress][] your CSS stylesheets. 5 | 6 | [django]: http://www.djangoproject.com/ 7 | [compress]: http://developer.yahoo.com/yui/compressor/ 8 | 9 | 10 | ## Why compress? 11 | 12 | * Reduce the overhead of multiple HTTP requests for multiple CSS files. By 13 | concatenating them into one file, the browser will only need to make one 14 | HTTP request, reducing server load and bandwidth. 15 | 16 | * Reduce the size of CSS files by stripping unnecessary bytes (whitespace, et 17 | cetera), saving network bandwidth and reducing page load times. 18 | 19 | ## Installation and Setup 20 | 21 | * Install the `django-cssmin` library: 22 | 23 | $ pip install django-cssmin # OR 24 | $ easy_install django-cssmin 25 | 26 | Either of these commands should install all the required dependencies. 27 | 28 | * Add `'djcssmin'` to your `INSTALLED_APPS` setting. 29 | 30 | * Add the necessary settings to your `settings.py` file: 31 | 32 | ## somewhere in settings.py 33 | CSSMIN_ROOT = '/path/to/my/project/' 34 | 35 | CSSMIN_INPUT = [ 36 | # YUI Reset CSS 37 | 'http://yui.yahooapis.com/2.8.0r4/build/reset/reset.css', 38 | # 960 Grid System 39 | 'media/css/960/reset.css', 40 | 'media/css/960/text.css', 41 | 'media/css/960/grid.css', 42 | # Local CSS Libraries 43 | 'media/css/*.lib.css', 44 | ] 45 | 46 | CSSMIN_OUTPUT = 'media/css/style.min.css' 47 | 48 | More information on the available settings can be found 49 | [here](http://github.com/zacharyvoase/django-cssmin/blob/master/doc/settings.md). 50 | 51 | 52 | ## `DEBUG` mode 53 | 54 | If `DEBUG` is set to `True` in your Django project when you run `django-cssmin`, 55 | the CSS input files will only be concatenated to the output file, not 56 | compressed. This allows you to debug your stylesheets with meaningful line 57 | numbers during design, and then use the fully-minified version in production. 58 | You can force specific behaviour with options to the `djboss cssmin` command; 59 | see the output of `djboss cssmin --help` for more information. 60 | 61 | 62 | ## Usage 63 | 64 | `django-cssmin` uses [`django-boss`][djboss], a library/tool for writing and 65 | running Django management commands. This will be installed automatically by 66 | setuptools when you install `django-cssmin`. 67 | 68 | [djboss]: http://github.com/zacharyvoase/django-boss 69 | 70 | Usage is relatively simple: 71 | 72 | $ djboss --log-level DEBUG cssmin --prod-mode 73 | Saving http://yui.yahooapis.com/.../reset.css to a temporary file 74 | Saved http://yui.yahooapis.com/.../reset.css to tmp8QRfOa 75 | Reading tmp8QRfOa 76 | Reading media/css/960/reset.css 77 | Reading media/css/960/text.css 78 | Reading media/css/960/grid.css 79 | Minifying and writing to media/css/style.min.css 80 | Cleaning temporary file tmp8QRfOa 81 | 82 | The compressed CSS stylesheet will be output to the filename given by the 83 | `CSSMIN_OUTPUT` setting, in this case `media/css/style.min.css`. 84 | 85 | 86 | ## (Un)license 87 | 88 | This is free and unencumbered software released into the public domain. 89 | 90 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 91 | software, either in source code form or as a compiled binary, for any purpose, 92 | commercial or non-commercial, and by any means. 93 | 94 | In jurisdictions that recognize copyright laws, the author or authors of this 95 | software dedicate any and all copyright interest in the software to the public 96 | domain. We make this dedication for the benefit of the public at large and to 97 | the detriment of our heirs and successors. We intend this dedication to be an 98 | overt act of relinquishment in perpetuity of all present and future rights to 99 | this software under copyright law. 100 | 101 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 102 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 103 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE 104 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 105 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 106 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 107 | 108 | For more information, please refer to 109 | -------------------------------------------------------------------------------- /src/djcssmin/commands.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from cStringIO import StringIO 4 | import glob 5 | import logging 6 | import os 7 | import os.path as p 8 | 9 | import cssmin as libcssmin 10 | from django.core.exceptions import ImproperlyConfigured 11 | from djboss.commands import * 12 | 13 | from djcssmin import utils 14 | 15 | 16 | LOG = logging.getLogger('django.djcssmin') 17 | 18 | 19 | def resolve_patterns(patterns, root): 20 | """Resolve a list of globs/URLs into absolute filenames.""" 21 | 22 | input_files, temp_files = [], [] 23 | 24 | for pattern in patterns: 25 | # Handle URLs in the CSSMIN_INPUT setting. 26 | if pattern.startswith("http:"): 27 | temp_filename = utils.temp_fetch(pattern) 28 | input_files.append(temp_filename) 29 | temp_files.append(temp_filename) 30 | 31 | else: 32 | # Ensure glob patterns are absolute. 33 | glob_files = glob.glob(utils.make_abs(pattern, root)) 34 | # Sort filenames within the results of a single pattern. 35 | glob_files.sort() 36 | 37 | for filename in glob_files: 38 | # Make sure there are no repetitions. 39 | if filename not in input_files: 40 | input_files.append(filename) 41 | 42 | return input_files, temp_files 43 | 44 | 45 | @command 46 | @argument('-d', '--dev-mode', action='store_true', default=None, dest='development_mode', 47 | help="Don't minify (just concatenate). Defaults to the value of DEBUG.") 48 | @argument('-p', '--prod-mode', action='store_false', dest='development_mode', 49 | help="Compress, even when DEBUG is True.") 50 | def cssmin(args): 51 | """Compress the configured CSS stylesheets.""" 52 | 53 | if not hasattr(args.settings, 'CSSMIN_INPUT'): 54 | raise ImproperlyConfigured("Must provide a CSSMIN_INPUT setting") 55 | elif not hasattr(args.settings, 'CSSMIN_OUTPUT'): 56 | raise ImproperlyConfigured("Must provide a CSSMIN_OUTPUT setting") 57 | 58 | root = utils.get_root(args.settings) 59 | 60 | # Set up development mode. If nothing is specified, this will default to the 61 | # value of `settings.DEBUG`. The `-d` and `-p` options override this value. 62 | if args.development_mode is None: 63 | development_mode = args.settings.DEBUG 64 | else: 65 | development_mode = args.development_mode 66 | 67 | # `temp_files` have to be deleted after processing, whether minification was 68 | # successful or not. 69 | input_files, temp_files = resolve_patterns(args.settings.CSSMIN_INPUT, root) 70 | 71 | try: 72 | # Get an absolute output filename. 73 | output_file = utils.make_abs(args.settings.CSSMIN_OUTPUT, root) 74 | 75 | if output_file in input_files: 76 | # This can happen if you output a '.css' file to the same directory 77 | # you're globbing from. Remove it from the input files. 78 | input_files.remove(output_file) 79 | 80 | input_io = StringIO() 81 | try: 82 | # Populate the input StringIO. 83 | for filename in input_files: 84 | if filename in temp_files: 85 | LOG.info("Reading %s" % p.basename(filename)) 86 | else: 87 | LOG.info("Reading %s" % p.relpath(filename)) 88 | 89 | # The additional whitespace/comments will be filtered out by the 90 | # compressor later on, unless we are in development mode, in 91 | # which case we want the whitespace and comments. 92 | input_io.write("/* FILE: %s */" % filename + (os.linesep * 2)) 93 | input_io.write(utils.read_from(filename)) 94 | input_io.write(os.linesep * 2) 95 | input_io.seek(0) 96 | 97 | output_io = open(output_file, 'w') 98 | try: 99 | output_io.write(utils.get_prolog(args.settings, root)) 100 | 101 | if development_mode: 102 | LOG.info("Writing to %s" % p.relpath(output_file)) 103 | output_io.write(input_io.getvalue()) 104 | else: 105 | # Minify and write the output. 106 | LOG.info("Minifying and writing to %s" % p.relpath(output_file)) 107 | output_io.write(libcssmin.cssmin(input_io.read())) 108 | finally: 109 | output_io.close() # Clean up. 110 | finally: 111 | input_io.close() # Clean up. 112 | 113 | finally: 114 | # Clean up. 115 | for temp_filename in temp_files: 116 | LOG.info("Cleaning temporary file %s" % p.basename(temp_filename)) 117 | os.remove(temp_filename) 118 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # `django-cssmin` 4 | 5 | `django-cssmin` is a reusable application for [Django][] which makes it easy to 6 | automatically [compress][] your CSS stylesheets. 7 | 8 | [django]: http://www.djangoproject.com/ 9 | [compress]: http://developer.yahoo.com/yui/compressor/ 10 | 11 | 12 | ## Why compress? 13 | 14 | * Reduce the overhead of multiple HTTP requests for multiple CSS files. By 15 | concatenating them into one file, the browser will only need to make one 16 | HTTP request, reducing server load and bandwidth. 17 | 18 | * Reduce the size of CSS files by stripping unnecessary bytes (whitespace, et 19 | cetera), saving network bandwidth and reducing page load times. 20 | 21 | ## Installation and Setup 22 | 23 | * Install the `django-cssmin` library: 24 | 25 | :::bash 26 | $ pip install django-cssmin # OR 27 | $ easy_install django-cssmin 28 | 29 | Either of these commands should install all the required dependencies. The 30 | YUI CSS compressor is installed via the [yuicompressor][] PyPI package; one 31 | way or another, `django-cssmin` expects a `yuicompressor` command to be 32 | available. If the PyPI package doesn’t install correctly for you, see 33 | [installing the YUI compressor](#installing-the-yui-compressor). 34 | 35 | [yuicompressor]: http://pypi.python.org/pypi/yuicompressor 36 | 37 | * Add `'djcssmin'` to your `INSTALLED_APPS` setting. 38 | 39 | * Add the necessary settings to your `settings.py` file: 40 | 41 | :::python 42 | ## somewhere in settings.py 43 | CSSMIN_ROOT = '/path/to/my/project/' 44 | 45 | CSSMIN_INPUT = [ 46 | # YUI Reset CSS 47 | 'http://yui.yahooapis.com/2.8.0r4/build/reset/reset.css', 48 | # 960 Grid System 49 | 'media/css/960/reset.css', 50 | 'media/css/960/text.css', 51 | 'media/css/960/grid.css', 52 | # Local CSS Libraries 53 | 'media/css/*.lib.css', 54 | ] 55 | 56 | CSSMIN_OUTPUT = 'media/css/style.min.css' 57 | 58 | More information on the available settings can be found [here](/settings). 59 | 60 | 61 | ## `DEBUG` mode 62 | 63 | If `DEBUG` is set to `True` in your Django project when you run `django-cssmin`, 64 | the CSS input files will only be concatenated to the output file, not 65 | compressed. This allows you to debug your stylesheets with meaningful line 66 | numbers during design, and then use the fully-minified version in production. 67 | You can force specific behaviour with options to the `djboss cssmin` command; 68 | see the output of `djboss cssmin --help` for more information. 69 | 70 | 71 | ## Usage 72 | 73 | `django-cssmin` uses [`django-boss`][djboss], a library/tool for writing and 74 | running Django management commands. This will be installed automatically by 75 | setuptools when you install `django-cssmin`. 76 | 77 | [djboss]: http://github.com/zacharyvoase/django-boss 78 | 79 | Usage is relatively simple: 80 | 81 | :::bash 82 | $ djboss --log-level DEBUG cssmin --prod-mode 83 | Saving http://yui.yahooapis.com/.../reset.css to a temporary file 84 | Saved http://yui.yahooapis.com/.../reset.css to tmp8QRfOa 85 | Reading tmp8QRfOa 86 | Reading media/css/960/reset.css 87 | Reading media/css/960/text.css 88 | Reading media/css/960/grid.css 89 | Minifying and writing to media/css/style.min.css 90 | Cleaning temporary file tmp8QRfOa 91 | 92 | The compressed CSS stylesheet will be output to the filename given by the 93 | `CSSMIN_OUTPUT` setting, in this case `media/css/style.min.css`. 94 | 95 | 96 | ## Installing the YUI Compressor 97 | 98 | * Download the yuicompressor JAR file from 99 | [here](http://yuilibrary.com/downloads/#yuicompressor). 100 | 101 | * Extract the `yuicompressor-x.y.z.jar` file to a path in your `JAVA_HOME` 102 | environment variable. 103 | 104 | * Make `yuicompressor` an alias for `java -jar yuicompressor-x.y.z.jar`, 105 | using a proxy `yuicompressor` script on your path: 106 | 107 | #!/bin/bash 108 | # For example, in /usr/bin/yuicompressor: 109 | java -jar yuicompressor-x.y.z.jar $* 110 | 111 | Ensure this file has the executable bit set (via 112 | `sudo chmod a+x /usr/bin/yuicompressor` or otherwise). 113 | 114 | * Test the installation by running `yuicompressor --help`; you should see this 115 | output: 116 | 117 | :::text 118 | Usage: java -jar yuicompressor-x.y.z.jar [options] [input file] 119 | 120 | Global Options 121 | -h, --help Displays this information 122 | --type Specifies the type of the input file 123 | --charset Read the input file using 124 | --line-break Insert a line break after the specified column number 125 | -v, --verbose Display informational messages and warnings 126 | -o Place the output into . Defaults to stdout. 127 | 128 | JavaScript Options 129 | --nomunge Minify only, do not obfuscate 130 | --preserve-semi Preserve all semicolons 131 | --disable-optimizations Disable all micro optimizations 132 | 133 | If no input file is specified, it defaults to stdin. In this case, the 'type' 134 | option is required. Otherwise, the 'type' option is required only if the input 135 | file extension is neither 'js' nor 'css'. 136 | 137 | 138 | ## License 139 | 140 | `django-cssmin` is licensed under the following MIT/X11-style license: 141 | 142 | > Copyright (c) 2009 Zachary Voase 143 | > 144 | > Permission is hereby granted, free of charge, to any person 145 | > obtaining a copy of this software and associated documentation 146 | > files (the "Software"), to deal in the Software without 147 | > restriction, including without limitation the rights to use, 148 | > copy, modify, merge, publish, distribute, sublicense, and/or sell 149 | > copies of the Software, and to permit persons to whom the 150 | > Software is furnished to do so, subject to the following 151 | > conditions: 152 | > 153 | > The above copyright notice and this permission notice shall be 154 | > included in all copies or substantial portions of the Software. 155 | > 156 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 157 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 158 | > OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 159 | > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 160 | > HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 161 | > WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 162 | > FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 163 | > OTHER DEALINGS IN THE SOFTWARE. 164 | -------------------------------------------------------------------------------- /test/example/media/css/960/grid-24.css: -------------------------------------------------------------------------------- 1 | /* 2 | 960 Grid System ~ Core CSS. 3 | Learn more ~ http://960.gs/ 4 | 5 | Licensed under GPL and MIT. 6 | */ 7 | 8 | /* Container >> 24 Columns 9 | ----------------------------------------------------------------------------------------------------*/ 10 | .container_24 { 11 | margin-left: auto; 12 | margin-right: auto; 13 | width: 960px; 14 | } 15 | 16 | /* Grid >> Global 17 | ----------------------------------------------------------------------------------------------------*/ 18 | 19 | .grid_1, 20 | .grid_2, 21 | .grid_3, 22 | .grid_4, 23 | .grid_5, 24 | .grid_6, 25 | .grid_7, 26 | .grid_8, 27 | .grid_9, 28 | .grid_10, 29 | .grid_11, 30 | .grid_12, 31 | .grid_13, 32 | .grid_14, 33 | .grid_15, 34 | .grid_16, 35 | .grid_17, 36 | .grid_18, 37 | .grid_19, 38 | .grid_20, 39 | .grid_21, 40 | .grid_22, 41 | .grid_23, 42 | .grid_24 { 43 | display: inline; 44 | float: left; 45 | position: relative; 46 | margin-left: 5px; 47 | margin-right: 5px; 48 | } 49 | 50 | /* Grid >> Children (Alpha ~ First, Omega ~ Last) 51 | ----------------------------------------------------------------------------------------------------*/ 52 | 53 | .alpha { 54 | margin-left: 0; 55 | } 56 | 57 | .omega { 58 | margin-right: 0; 59 | } 60 | 61 | /* Grid >> 24 Columns 62 | ----------------------------------------------------------------------------------------------------*/ 63 | 64 | .container_24 .grid_1 { 65 | width: 30px; 66 | } 67 | 68 | .container_24 .grid_2 { 69 | width: 70px; 70 | } 71 | 72 | .container_24 .grid_3 { 73 | width: 110px; 74 | } 75 | 76 | .container_24 .grid_4 { 77 | width: 150px; 78 | } 79 | 80 | .container_24 .grid_5 { 81 | width: 190px; 82 | } 83 | 84 | .container_24 .grid_6 { 85 | width: 230px; 86 | } 87 | 88 | .container_24 .grid_7 { 89 | width: 270px; 90 | } 91 | 92 | .container_24 .grid_8 { 93 | width: 310px; 94 | } 95 | 96 | .container_24 .grid_9 { 97 | width: 350px; 98 | } 99 | 100 | .container_24 .grid_10 { 101 | width: 390px; 102 | } 103 | 104 | .container_24 .grid_11 { 105 | width: 430px; 106 | } 107 | 108 | .container_24 .grid_12 { 109 | width: 470px; 110 | } 111 | 112 | .container_24 .grid_13 { 113 | width: 510px; 114 | } 115 | 116 | .container_24 .grid_14 { 117 | width: 550px; 118 | } 119 | 120 | .container_24 .grid_15 { 121 | width: 590px; 122 | } 123 | 124 | .container_24 .grid_16 { 125 | width: 630px; 126 | } 127 | 128 | .container_24 .grid_17 { 129 | width: 670px; 130 | } 131 | 132 | .container_24 .grid_18 { 133 | width: 710px; 134 | } 135 | 136 | .container_24 .grid_19 { 137 | width: 750px; 138 | } 139 | 140 | .container_24 .grid_20 { 141 | width: 790px; 142 | } 143 | 144 | .container_24 .grid_21 { 145 | width: 830px; 146 | } 147 | 148 | .container_24 .grid_22 { 149 | width: 870px; 150 | } 151 | 152 | .container_24 .grid_23 { 153 | width: 910px; 154 | } 155 | 156 | .container_24 .grid_24 { 157 | width: 950px; 158 | } 159 | 160 | /* Prefix Extra Space >> 24 Columns 161 | ----------------------------------------------------------------------------------------------------*/ 162 | 163 | .container_24 .prefix_1 { 164 | padding-left: 40px; 165 | } 166 | 167 | .container_24 .prefix_2 { 168 | padding-left: 80px; 169 | } 170 | 171 | .container_24 .prefix_3 { 172 | padding-left: 120px; 173 | } 174 | 175 | .container_24 .prefix_4 { 176 | padding-left: 160px; 177 | } 178 | 179 | .container_24 .prefix_5 { 180 | padding-left: 200px; 181 | } 182 | 183 | .container_24 .prefix_6 { 184 | padding-left: 240px; 185 | } 186 | 187 | .container_24 .prefix_7 { 188 | padding-left: 280px; 189 | } 190 | 191 | .container_24 .prefix_8 { 192 | padding-left: 320px; 193 | } 194 | 195 | .container_24 .prefix_9 { 196 | padding-left: 360px; 197 | } 198 | 199 | .container_24 .prefix_10 { 200 | padding-left: 400px; 201 | } 202 | 203 | .container_24 .prefix_11 { 204 | padding-left: 440px; 205 | } 206 | 207 | .container_24 .prefix_12 { 208 | padding-left: 480px; 209 | } 210 | 211 | .container_24 .prefix_13 { 212 | padding-left: 520px; 213 | } 214 | 215 | .container_24 .prefix_14 { 216 | padding-left: 560px; 217 | } 218 | 219 | .container_24 .prefix_15 { 220 | padding-left: 600px; 221 | } 222 | 223 | .container_24 .prefix_16 { 224 | padding-left: 640px; 225 | } 226 | 227 | .container_24 .prefix_17 { 228 | padding-left: 680px; 229 | } 230 | 231 | .container_24 .prefix_18 { 232 | padding-left: 720px; 233 | } 234 | 235 | .container_24 .prefix_19 { 236 | padding-left: 760px; 237 | } 238 | 239 | .container_24 .prefix_20 { 240 | padding-left: 800px; 241 | } 242 | 243 | .container_24 .prefix_21 { 244 | padding-left: 840px; 245 | } 246 | 247 | .container_24 .prefix_22 { 248 | padding-left: 880px; 249 | } 250 | 251 | .container_24 .prefix_23 { 252 | padding-left: 920px; 253 | } 254 | 255 | /* Suffix Extra Space >> 24 Columns 256 | ----------------------------------------------------------------------------------------------------*/ 257 | 258 | .container_24 .suffix_1 { 259 | padding-right: 40px; 260 | } 261 | 262 | .container_24 .suffix_2 { 263 | padding-right: 80px; 264 | } 265 | 266 | .container_24 .suffix_3 { 267 | padding-right: 120px; 268 | } 269 | 270 | .container_24 .suffix_4 { 271 | padding-right: 160px; 272 | } 273 | 274 | .container_24 .suffix_5 { 275 | padding-right: 200px; 276 | } 277 | 278 | .container_24 .suffix_6 { 279 | padding-right: 240px; 280 | } 281 | 282 | .container_24 .suffix_7 { 283 | padding-right: 280px; 284 | } 285 | 286 | .container_24 .suffix_8 { 287 | padding-right: 320px; 288 | } 289 | 290 | .container_24 .suffix_9 { 291 | padding-right: 360px; 292 | } 293 | 294 | .container_24 .suffix_10 { 295 | padding-right: 400px; 296 | } 297 | 298 | .container_24 .suffix_11 { 299 | padding-right: 440px; 300 | } 301 | 302 | .container_24 .suffix_12 { 303 | padding-right: 480px; 304 | } 305 | 306 | .container_24 .suffix_13 { 307 | padding-right: 520px; 308 | } 309 | 310 | .container_24 .suffix_14 { 311 | padding-right: 560px; 312 | } 313 | 314 | .container_24 .suffix_15 { 315 | padding-right: 600px; 316 | } 317 | 318 | .container_24 .suffix_16 { 319 | padding-right: 640px; 320 | } 321 | 322 | .container_24 .suffix_17 { 323 | padding-right: 680px; 324 | } 325 | 326 | .container_24 .suffix_18 { 327 | padding-right: 720px; 328 | } 329 | 330 | .container_24 .suffix_19 { 331 | padding-right: 760px; 332 | } 333 | 334 | .container_24 .suffix_20 { 335 | padding-right: 800px; 336 | } 337 | 338 | .container_24 .suffix_21 { 339 | padding-right: 840px; 340 | } 341 | 342 | .container_24 .suffix_22 { 343 | padding-right: 880px; 344 | } 345 | 346 | .container_24 .suffix_23 { 347 | padding-right: 920px; 348 | } 349 | 350 | /* Push Space >> 24 Columns 351 | ----------------------------------------------------------------------------------------------------*/ 352 | 353 | .container_24 .push_1 { 354 | left: 40px; 355 | } 356 | 357 | .container_24 .push_2 { 358 | left: 80px; 359 | } 360 | 361 | .container_24 .push_3 { 362 | left: 120px; 363 | } 364 | 365 | .container_24 .push_4 { 366 | left: 160px; 367 | } 368 | 369 | .container_24 .push_5 { 370 | left: 200px; 371 | } 372 | 373 | .container_24 .push_6 { 374 | left: 240px; 375 | } 376 | 377 | .container_24 .push_7 { 378 | left: 280px; 379 | } 380 | 381 | .container_24 .push_8 { 382 | left: 320px; 383 | } 384 | 385 | .container_24 .push_9 { 386 | left: 360px; 387 | } 388 | 389 | .container_24 .push_10 { 390 | left: 400px; 391 | } 392 | 393 | .container_24 .push_11 { 394 | left: 440px; 395 | } 396 | 397 | .container_24 .push_12 { 398 | left: 480px; 399 | } 400 | 401 | .container_24 .push_13 { 402 | left: 520px; 403 | } 404 | 405 | .container_24 .push_14 { 406 | left: 560px; 407 | } 408 | 409 | .container_24 .push_15 { 410 | left: 600px; 411 | } 412 | 413 | .container_24 .push_16 { 414 | left: 640px; 415 | } 416 | 417 | .container_24 .push_17 { 418 | left: 680px; 419 | } 420 | 421 | .container_24 .push_18 { 422 | left: 720px; 423 | } 424 | 425 | .container_24 .push_19 { 426 | left: 760px; 427 | } 428 | 429 | .container_24 .push_20 { 430 | left: 800px; 431 | } 432 | 433 | .container_24 .push_21 { 434 | left: 840px; 435 | } 436 | 437 | .container_24 .push_22 { 438 | left: 880px; 439 | } 440 | 441 | .container_24 .push_23 { 442 | left: 920px; 443 | } 444 | 445 | /* Pull Space >> 24 Columns 446 | ----------------------------------------------------------------------------------------------------*/ 447 | 448 | .container_24 .pull_1 { 449 | left: -40px; 450 | } 451 | 452 | .container_24 .pull_2 { 453 | left: -80px; 454 | } 455 | 456 | .container_24 .pull_3 { 457 | left: -120px; 458 | } 459 | 460 | .container_24 .pull_4 { 461 | left: -160px; 462 | } 463 | 464 | .container_24 .pull_5 { 465 | left: -200px; 466 | } 467 | 468 | .container_24 .pull_6 { 469 | left: -240px; 470 | } 471 | 472 | .container_24 .pull_7 { 473 | left: -280px; 474 | } 475 | 476 | .container_24 .pull_8 { 477 | left: -320px; 478 | } 479 | 480 | .container_24 .pull_9 { 481 | left: -360px; 482 | } 483 | 484 | .container_24 .pull_10 { 485 | left: -400px; 486 | } 487 | 488 | .container_24 .pull_11 { 489 | left: -440px; 490 | } 491 | 492 | .container_24 .pull_12 { 493 | left: -480px; 494 | } 495 | 496 | .container_24 .pull_13 { 497 | left: -520px; 498 | } 499 | 500 | .container_24 .pull_14 { 501 | left: -560px; 502 | } 503 | 504 | .container_24 .pull_15 { 505 | left: -600px; 506 | } 507 | 508 | .container_24 .pull_16 { 509 | left: -640px; 510 | } 511 | 512 | .container_24 .pull_17 { 513 | left: -680px; 514 | } 515 | 516 | .container_24 .pull_18 { 517 | left: -720px; 518 | } 519 | 520 | .container_24 .pull_19 { 521 | left: -760px; 522 | } 523 | 524 | .container_24 .pull_20 { 525 | left: -800px; 526 | } 527 | 528 | .container_24 .pull_21 { 529 | left: -840px; 530 | } 531 | 532 | .container_24 .pull_22 { 533 | left: -880px; 534 | } 535 | 536 | .container_24 .pull_23 { 537 | left: -920px; 538 | } 539 | 540 | /* `Clear Floated Elements 541 | ----------------------------------------------------------------------------------------------------*/ 542 | 543 | /* http://sonspring.com/journal/clearing-floats */ 544 | 545 | .clear { 546 | clear: both; 547 | display: block; 548 | overflow: hidden; 549 | visibility: hidden; 550 | width: 0; 551 | height: 0; 552 | } 553 | 554 | /* http://perishablepress.com/press/2009/12/06/new-clearfix-hack */ 555 | 556 | .clearfix:after { 557 | clear: both; 558 | content: ' '; 559 | display: block; 560 | font-size: 0; 561 | line-height: 0; 562 | visibility: hidden; 563 | width: 0; 564 | height: 0; 565 | } 566 | 567 | /* 568 | The following zoom:1 rule is specifically for IE6 + IE7. 569 | Move to separate stylesheet if invalid CSS is a problem. 570 | */ 571 | * html .clearfix, 572 | *:first-child+html .clearfix { 573 | zoom: 1; 574 | } -------------------------------------------------------------------------------- /test/example/media/css/960/grid.css: -------------------------------------------------------------------------------- 1 | /* 2 | 960 Grid System ~ Core CSS. 3 | Learn more ~ http://960.gs/ 4 | 5 | Licensed under GPL and MIT. 6 | */ 7 | 8 | /* `Containers 9 | ----------------------------------------------------------------------------------------------------*/ 10 | 11 | .container_12, 12 | .container_16 { 13 | margin-left: auto; 14 | margin-right: auto; 15 | width: 960px; 16 | } 17 | 18 | /* `Grid >> Global 19 | ----------------------------------------------------------------------------------------------------*/ 20 | 21 | .grid_1, 22 | .grid_2, 23 | .grid_3, 24 | .grid_4, 25 | .grid_5, 26 | .grid_6, 27 | .grid_7, 28 | .grid_8, 29 | .grid_9, 30 | .grid_10, 31 | .grid_11, 32 | .grid_12, 33 | .grid_13, 34 | .grid_14, 35 | .grid_15, 36 | .grid_16 { 37 | display: inline; 38 | float: left; 39 | position: relative; 40 | margin-left: 10px; 41 | margin-right: 10px; 42 | } 43 | 44 | .container_12 .grid_3, 45 | .container_16 .grid_4 { 46 | width: 220px; 47 | } 48 | 49 | .container_12 .grid_6, 50 | .container_16 .grid_8 { 51 | width: 460px; 52 | } 53 | 54 | .container_12 .grid_9, 55 | .container_16 .grid_12 { 56 | width: 700px; 57 | } 58 | 59 | .container_12 .grid_12, 60 | .container_16 .grid_16 { 61 | width: 940px; 62 | } 63 | 64 | /* `Grid >> Children (Alpha ~ First, Omega ~ Last) 65 | ----------------------------------------------------------------------------------------------------*/ 66 | 67 | .alpha { 68 | margin-left: 0; 69 | } 70 | 71 | .omega { 72 | margin-right: 0; 73 | } 74 | 75 | /* `Grid >> 12 Columns 76 | ----------------------------------------------------------------------------------------------------*/ 77 | 78 | .container_12 .grid_1 { 79 | width: 60px; 80 | } 81 | 82 | .container_12 .grid_2 { 83 | width: 140px; 84 | } 85 | 86 | .container_12 .grid_4 { 87 | width: 300px; 88 | } 89 | 90 | .container_12 .grid_5 { 91 | width: 380px; 92 | } 93 | 94 | .container_12 .grid_7 { 95 | width: 540px; 96 | } 97 | 98 | .container_12 .grid_8 { 99 | width: 620px; 100 | } 101 | 102 | .container_12 .grid_10 { 103 | width: 780px; 104 | } 105 | 106 | .container_12 .grid_11 { 107 | width: 860px; 108 | } 109 | 110 | /* `Grid >> 16 Columns 111 | ----------------------------------------------------------------------------------------------------*/ 112 | 113 | .container_16 .grid_1 { 114 | width: 40px; 115 | } 116 | 117 | .container_16 .grid_2 { 118 | width: 100px; 119 | } 120 | 121 | .container_16 .grid_3 { 122 | width: 160px; 123 | } 124 | 125 | .container_16 .grid_5 { 126 | width: 280px; 127 | } 128 | 129 | .container_16 .grid_6 { 130 | width: 340px; 131 | } 132 | 133 | .container_16 .grid_7 { 134 | width: 400px; 135 | } 136 | 137 | .container_16 .grid_9 { 138 | width: 520px; 139 | } 140 | 141 | .container_16 .grid_10 { 142 | width: 580px; 143 | } 144 | 145 | .container_16 .grid_11 { 146 | width: 640px; 147 | } 148 | 149 | .container_16 .grid_13 { 150 | width: 760px; 151 | } 152 | 153 | .container_16 .grid_14 { 154 | width: 820px; 155 | } 156 | 157 | .container_16 .grid_15 { 158 | width: 880px; 159 | } 160 | 161 | /* `Prefix Extra Space >> Global 162 | ----------------------------------------------------------------------------------------------------*/ 163 | 164 | .container_12 .prefix_3, 165 | .container_16 .prefix_4 { 166 | padding-left: 240px; 167 | } 168 | 169 | .container_12 .prefix_6, 170 | .container_16 .prefix_8 { 171 | padding-left: 480px; 172 | } 173 | 174 | .container_12 .prefix_9, 175 | .container_16 .prefix_12 { 176 | padding-left: 720px; 177 | } 178 | 179 | /* `Prefix Extra Space >> 12 Columns 180 | ----------------------------------------------------------------------------------------------------*/ 181 | 182 | .container_12 .prefix_1 { 183 | padding-left: 80px; 184 | } 185 | 186 | .container_12 .prefix_2 { 187 | padding-left: 160px; 188 | } 189 | 190 | .container_12 .prefix_4 { 191 | padding-left: 320px; 192 | } 193 | 194 | .container_12 .prefix_5 { 195 | padding-left: 400px; 196 | } 197 | 198 | .container_12 .prefix_7 { 199 | padding-left: 560px; 200 | } 201 | 202 | .container_12 .prefix_8 { 203 | padding-left: 640px; 204 | } 205 | 206 | .container_12 .prefix_10 { 207 | padding-left: 800px; 208 | } 209 | 210 | .container_12 .prefix_11 { 211 | padding-left: 880px; 212 | } 213 | 214 | /* `Prefix Extra Space >> 16 Columns 215 | ----------------------------------------------------------------------------------------------------*/ 216 | 217 | .container_16 .prefix_1 { 218 | padding-left: 60px; 219 | } 220 | 221 | .container_16 .prefix_2 { 222 | padding-left: 120px; 223 | } 224 | 225 | .container_16 .prefix_3 { 226 | padding-left: 180px; 227 | } 228 | 229 | .container_16 .prefix_5 { 230 | padding-left: 300px; 231 | } 232 | 233 | .container_16 .prefix_6 { 234 | padding-left: 360px; 235 | } 236 | 237 | .container_16 .prefix_7 { 238 | padding-left: 420px; 239 | } 240 | 241 | .container_16 .prefix_9 { 242 | padding-left: 540px; 243 | } 244 | 245 | .container_16 .prefix_10 { 246 | padding-left: 600px; 247 | } 248 | 249 | .container_16 .prefix_11 { 250 | padding-left: 660px; 251 | } 252 | 253 | .container_16 .prefix_13 { 254 | padding-left: 780px; 255 | } 256 | 257 | .container_16 .prefix_14 { 258 | padding-left: 840px; 259 | } 260 | 261 | .container_16 .prefix_15 { 262 | padding-left: 900px; 263 | } 264 | 265 | /* `Suffix Extra Space >> Global 266 | ----------------------------------------------------------------------------------------------------*/ 267 | 268 | .container_12 .suffix_3, 269 | .container_16 .suffix_4 { 270 | padding-right: 240px; 271 | } 272 | 273 | .container_12 .suffix_6, 274 | .container_16 .suffix_8 { 275 | padding-right: 480px; 276 | } 277 | 278 | .container_12 .suffix_9, 279 | .container_16 .suffix_12 { 280 | padding-right: 720px; 281 | } 282 | 283 | /* `Suffix Extra Space >> 12 Columns 284 | ----------------------------------------------------------------------------------------------------*/ 285 | 286 | .container_12 .suffix_1 { 287 | padding-right: 80px; 288 | } 289 | 290 | .container_12 .suffix_2 { 291 | padding-right: 160px; 292 | } 293 | 294 | .container_12 .suffix_4 { 295 | padding-right: 320px; 296 | } 297 | 298 | .container_12 .suffix_5 { 299 | padding-right: 400px; 300 | } 301 | 302 | .container_12 .suffix_7 { 303 | padding-right: 560px; 304 | } 305 | 306 | .container_12 .suffix_8 { 307 | padding-right: 640px; 308 | } 309 | 310 | .container_12 .suffix_10 { 311 | padding-right: 800px; 312 | } 313 | 314 | .container_12 .suffix_11 { 315 | padding-right: 880px; 316 | } 317 | 318 | /* `Suffix Extra Space >> 16 Columns 319 | ----------------------------------------------------------------------------------------------------*/ 320 | 321 | .container_16 .suffix_1 { 322 | padding-right: 60px; 323 | } 324 | 325 | .container_16 .suffix_2 { 326 | padding-right: 120px; 327 | } 328 | 329 | .container_16 .suffix_3 { 330 | padding-right: 180px; 331 | } 332 | 333 | .container_16 .suffix_5 { 334 | padding-right: 300px; 335 | } 336 | 337 | .container_16 .suffix_6 { 338 | padding-right: 360px; 339 | } 340 | 341 | .container_16 .suffix_7 { 342 | padding-right: 420px; 343 | } 344 | 345 | .container_16 .suffix_9 { 346 | padding-right: 540px; 347 | } 348 | 349 | .container_16 .suffix_10 { 350 | padding-right: 600px; 351 | } 352 | 353 | .container_16 .suffix_11 { 354 | padding-right: 660px; 355 | } 356 | 357 | .container_16 .suffix_13 { 358 | padding-right: 780px; 359 | } 360 | 361 | .container_16 .suffix_14 { 362 | padding-right: 840px; 363 | } 364 | 365 | .container_16 .suffix_15 { 366 | padding-right: 900px; 367 | } 368 | 369 | /* `Push Space >> Global 370 | ----------------------------------------------------------------------------------------------------*/ 371 | 372 | .container_12 .push_3, 373 | .container_16 .push_4 { 374 | left: 240px; 375 | } 376 | 377 | .container_12 .push_6, 378 | .container_16 .push_8 { 379 | left: 480px; 380 | } 381 | 382 | .container_12 .push_9, 383 | .container_16 .push_12 { 384 | left: 720px; 385 | } 386 | 387 | /* `Push Space >> 12 Columns 388 | ----------------------------------------------------------------------------------------------------*/ 389 | 390 | .container_12 .push_1 { 391 | left: 80px; 392 | } 393 | 394 | .container_12 .push_2 { 395 | left: 160px; 396 | } 397 | 398 | .container_12 .push_4 { 399 | left: 320px; 400 | } 401 | 402 | .container_12 .push_5 { 403 | left: 400px; 404 | } 405 | 406 | .container_12 .push_7 { 407 | left: 560px; 408 | } 409 | 410 | .container_12 .push_8 { 411 | left: 640px; 412 | } 413 | 414 | .container_12 .push_10 { 415 | left: 800px; 416 | } 417 | 418 | .container_12 .push_11 { 419 | left: 880px; 420 | } 421 | 422 | /* `Push Space >> 16 Columns 423 | ----------------------------------------------------------------------------------------------------*/ 424 | 425 | .container_16 .push_1 { 426 | left: 60px; 427 | } 428 | 429 | .container_16 .push_2 { 430 | left: 120px; 431 | } 432 | 433 | .container_16 .push_3 { 434 | left: 180px; 435 | } 436 | 437 | .container_16 .push_5 { 438 | left: 300px; 439 | } 440 | 441 | .container_16 .push_6 { 442 | left: 360px; 443 | } 444 | 445 | .container_16 .push_7 { 446 | left: 420px; 447 | } 448 | 449 | .container_16 .push_9 { 450 | left: 540px; 451 | } 452 | 453 | .container_16 .push_10 { 454 | left: 600px; 455 | } 456 | 457 | .container_16 .push_11 { 458 | left: 660px; 459 | } 460 | 461 | .container_16 .push_13 { 462 | left: 780px; 463 | } 464 | 465 | .container_16 .push_14 { 466 | left: 840px; 467 | } 468 | 469 | .container_16 .push_15 { 470 | left: 900px; 471 | } 472 | 473 | /* `Pull Space >> Global 474 | ----------------------------------------------------------------------------------------------------*/ 475 | 476 | .container_12 .pull_3, 477 | .container_16 .pull_4 { 478 | left: -240px; 479 | } 480 | 481 | .container_12 .pull_6, 482 | .container_16 .pull_8 { 483 | left: -480px; 484 | } 485 | 486 | .container_12 .pull_9, 487 | .container_16 .pull_12 { 488 | left: -720px; 489 | } 490 | 491 | /* `Pull Space >> 12 Columns 492 | ----------------------------------------------------------------------------------------------------*/ 493 | 494 | .container_12 .pull_1 { 495 | left: -80px; 496 | } 497 | 498 | .container_12 .pull_2 { 499 | left: -160px; 500 | } 501 | 502 | .container_12 .pull_4 { 503 | left: -320px; 504 | } 505 | 506 | .container_12 .pull_5 { 507 | left: -400px; 508 | } 509 | 510 | .container_12 .pull_7 { 511 | left: -560px; 512 | } 513 | 514 | .container_12 .pull_8 { 515 | left: -640px; 516 | } 517 | 518 | .container_12 .pull_10 { 519 | left: -800px; 520 | } 521 | 522 | .container_12 .pull_11 { 523 | left: -880px; 524 | } 525 | 526 | /* `Pull Space >> 16 Columns 527 | ----------------------------------------------------------------------------------------------------*/ 528 | 529 | .container_16 .pull_1 { 530 | left: -60px; 531 | } 532 | 533 | .container_16 .pull_2 { 534 | left: -120px; 535 | } 536 | 537 | .container_16 .pull_3 { 538 | left: -180px; 539 | } 540 | 541 | .container_16 .pull_5 { 542 | left: -300px; 543 | } 544 | 545 | .container_16 .pull_6 { 546 | left: -360px; 547 | } 548 | 549 | .container_16 .pull_7 { 550 | left: -420px; 551 | } 552 | 553 | .container_16 .pull_9 { 554 | left: -540px; 555 | } 556 | 557 | .container_16 .pull_10 { 558 | left: -600px; 559 | } 560 | 561 | .container_16 .pull_11 { 562 | left: -660px; 563 | } 564 | 565 | .container_16 .pull_13 { 566 | left: -780px; 567 | } 568 | 569 | .container_16 .pull_14 { 570 | left: -840px; 571 | } 572 | 573 | .container_16 .pull_15 { 574 | left: -900px; 575 | } 576 | 577 | /* `Clear Floated Elements 578 | ----------------------------------------------------------------------------------------------------*/ 579 | 580 | /* http://sonspring.com/journal/clearing-floats */ 581 | 582 | .clear { 583 | clear: both; 584 | display: block; 585 | overflow: hidden; 586 | visibility: hidden; 587 | width: 0; 588 | height: 0; 589 | } 590 | 591 | /* http://perishablepress.com/press/2009/12/06/new-clearfix-hack */ 592 | 593 | .clearfix:after { 594 | clear: both; 595 | content: ' '; 596 | display: block; 597 | font-size: 0; 598 | line-height: 0; 599 | visibility: hidden; 600 | width: 0; 601 | height: 0; 602 | } 603 | 604 | /* 605 | The following zoom:1 rule is specifically for IE6 + IE7. 606 | Move to separate stylesheet if invalid CSS is a problem. 607 | */ 608 | * html .clearfix, 609 | *:first-child+html .clearfix { 610 | zoom: 1; 611 | } -------------------------------------------------------------------------------- /distribute_setup.py: -------------------------------------------------------------------------------- 1 | #!python 2 | """Bootstrap distribute installation 3 | 4 | If you want to use setuptools in your package's setup.py, just include this 5 | file in the same directory with it, and add this to the top of your setup.py:: 6 | 7 | from distribute_setup import use_setuptools 8 | use_setuptools() 9 | 10 | If you want to require a specific version of setuptools, set a download 11 | mirror, or use an alternate download directory, you can do so by supplying 12 | the appropriate options to ``use_setuptools()``. 13 | 14 | This file can also be run as a script to install or upgrade setuptools. 15 | """ 16 | import os 17 | import sys 18 | import time 19 | import fnmatch 20 | import tempfile 21 | import tarfile 22 | from distutils import log 23 | 24 | try: 25 | from site import USER_SITE 26 | except ImportError: 27 | USER_SITE = None 28 | 29 | try: 30 | import subprocess 31 | 32 | def _python_cmd(*args): 33 | args = (sys.executable,) + args 34 | return subprocess.call(args) == 0 35 | 36 | except ImportError: 37 | # will be used for python 2.3 38 | def _python_cmd(*args): 39 | args = (sys.executable,) + args 40 | # quoting arguments if windows 41 | if sys.platform == 'win32': 42 | def quote(arg): 43 | if ' ' in arg: 44 | return '"%s"' % arg 45 | return arg 46 | args = [quote(arg) for arg in args] 47 | return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 48 | 49 | DEFAULT_VERSION = "0.6.10" 50 | DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" 51 | SETUPTOOLS_FAKED_VERSION = "0.6c11" 52 | 53 | SETUPTOOLS_PKG_INFO = """\ 54 | Metadata-Version: 1.0 55 | Name: setuptools 56 | Version: %s 57 | Summary: xxxx 58 | Home-page: xxx 59 | Author: xxx 60 | Author-email: xxx 61 | License: xxx 62 | Description: xxx 63 | """ % SETUPTOOLS_FAKED_VERSION 64 | 65 | 66 | def _install(tarball): 67 | # extracting the tarball 68 | tmpdir = tempfile.mkdtemp() 69 | log.warn('Extracting in %s', tmpdir) 70 | old_wd = os.getcwd() 71 | try: 72 | os.chdir(tmpdir) 73 | tar = tarfile.open(tarball) 74 | _extractall(tar) 75 | tar.close() 76 | 77 | # going in the directory 78 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) 79 | os.chdir(subdir) 80 | log.warn('Now working in %s', subdir) 81 | 82 | # installing 83 | log.warn('Installing Distribute') 84 | if not _python_cmd('setup.py', 'install'): 85 | log.warn('Something went wrong during the installation.') 86 | log.warn('See the error message above.') 87 | finally: 88 | os.chdir(old_wd) 89 | 90 | 91 | def _build_egg(egg, tarball, to_dir): 92 | # extracting the tarball 93 | tmpdir = tempfile.mkdtemp() 94 | log.warn('Extracting in %s', tmpdir) 95 | old_wd = os.getcwd() 96 | try: 97 | os.chdir(tmpdir) 98 | tar = tarfile.open(tarball) 99 | _extractall(tar) 100 | tar.close() 101 | 102 | # going in the directory 103 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) 104 | os.chdir(subdir) 105 | log.warn('Now working in %s', subdir) 106 | 107 | # building an egg 108 | log.warn('Building a Distribute egg in %s', to_dir) 109 | _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) 110 | 111 | finally: 112 | os.chdir(old_wd) 113 | # returning the result 114 | log.warn(egg) 115 | if not os.path.exists(egg): 116 | raise IOError('Could not build the egg.') 117 | 118 | 119 | def _do_download(version, download_base, to_dir, download_delay): 120 | egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' 121 | % (version, sys.version_info[0], sys.version_info[1])) 122 | if not os.path.exists(egg): 123 | tarball = download_setuptools(version, download_base, 124 | to_dir, download_delay) 125 | _build_egg(egg, tarball, to_dir) 126 | sys.path.insert(0, egg) 127 | import setuptools 128 | setuptools.bootstrap_install_from = egg 129 | 130 | 131 | def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, 132 | to_dir=os.curdir, download_delay=15, no_fake=True): 133 | # making sure we use the absolute path 134 | to_dir = os.path.abspath(to_dir) 135 | was_imported = 'pkg_resources' in sys.modules or \ 136 | 'setuptools' in sys.modules 137 | try: 138 | try: 139 | import pkg_resources 140 | if not hasattr(pkg_resources, '_distribute'): 141 | if not no_fake: 142 | _fake_setuptools() 143 | raise ImportError 144 | except ImportError: 145 | return _do_download(version, download_base, to_dir, download_delay) 146 | try: 147 | pkg_resources.require("distribute>="+version) 148 | return 149 | except pkg_resources.VersionConflict: 150 | e = sys.exc_info()[1] 151 | if was_imported: 152 | sys.stderr.write( 153 | "The required version of distribute (>=%s) is not available,\n" 154 | "and can't be installed while this script is running. Please\n" 155 | "install a more recent version first, using\n" 156 | "'easy_install -U distribute'." 157 | "\n\n(Currently using %r)\n" % (version, e.args[0])) 158 | sys.exit(2) 159 | else: 160 | del pkg_resources, sys.modules['pkg_resources'] # reload ok 161 | return _do_download(version, download_base, to_dir, 162 | download_delay) 163 | except pkg_resources.DistributionNotFound: 164 | return _do_download(version, download_base, to_dir, 165 | download_delay) 166 | finally: 167 | if not no_fake: 168 | _create_fake_setuptools_pkg_info(to_dir) 169 | 170 | def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, 171 | to_dir=os.curdir, delay=15): 172 | """Download distribute from a specified location and return its filename 173 | 174 | `version` should be a valid distribute version number that is available 175 | as an egg for download under the `download_base` URL (which should end 176 | with a '/'). `to_dir` is the directory where the egg will be downloaded. 177 | `delay` is the number of seconds to pause before an actual download 178 | attempt. 179 | """ 180 | # making sure we use the absolute path 181 | to_dir = os.path.abspath(to_dir) 182 | try: 183 | from urllib.request import urlopen 184 | except ImportError: 185 | from urllib2 import urlopen 186 | tgz_name = "distribute-%s.tar.gz" % version 187 | url = download_base + tgz_name 188 | saveto = os.path.join(to_dir, tgz_name) 189 | src = dst = None 190 | if not os.path.exists(saveto): # Avoid repeated downloads 191 | try: 192 | log.warn("Downloading %s", url) 193 | src = urlopen(url) 194 | # Read/write all in one block, so we don't create a corrupt file 195 | # if the download is interrupted. 196 | data = src.read() 197 | dst = open(saveto, "wb") 198 | dst.write(data) 199 | finally: 200 | if src: 201 | src.close() 202 | if dst: 203 | dst.close() 204 | return os.path.realpath(saveto) 205 | 206 | 207 | def _patch_file(path, content): 208 | """Will backup the file then patch it""" 209 | existing_content = open(path).read() 210 | if existing_content == content: 211 | # already patched 212 | log.warn('Already patched.') 213 | return False 214 | log.warn('Patching...') 215 | _rename_path(path) 216 | f = open(path, 'w') 217 | try: 218 | f.write(content) 219 | finally: 220 | f.close() 221 | return True 222 | 223 | 224 | def _same_content(path, content): 225 | return open(path).read() == content 226 | 227 | def _no_sandbox(function): 228 | def __no_sandbox(*args, **kw): 229 | try: 230 | from setuptools.sandbox import DirectorySandbox 231 | def violation(*args): 232 | pass 233 | DirectorySandbox._old = DirectorySandbox._violation 234 | DirectorySandbox._violation = violation 235 | patched = True 236 | except ImportError: 237 | patched = False 238 | 239 | try: 240 | return function(*args, **kw) 241 | finally: 242 | if patched: 243 | DirectorySandbox._violation = DirectorySandbox._old 244 | del DirectorySandbox._old 245 | 246 | return __no_sandbox 247 | 248 | @_no_sandbox 249 | def _rename_path(path): 250 | new_name = path + '.OLD.%s' % time.time() 251 | log.warn('Renaming %s into %s', path, new_name) 252 | os.rename(path, new_name) 253 | return new_name 254 | 255 | def _remove_flat_installation(placeholder): 256 | if not os.path.isdir(placeholder): 257 | log.warn('Unkown installation at %s', placeholder) 258 | return False 259 | found = False 260 | for file in os.listdir(placeholder): 261 | if fnmatch.fnmatch(file, 'setuptools*.egg-info'): 262 | found = True 263 | break 264 | if not found: 265 | log.warn('Could not locate setuptools*.egg-info') 266 | return 267 | 268 | log.warn('Removing elements out of the way...') 269 | pkg_info = os.path.join(placeholder, file) 270 | if os.path.isdir(pkg_info): 271 | patched = _patch_egg_dir(pkg_info) 272 | else: 273 | patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) 274 | 275 | if not patched: 276 | log.warn('%s already patched.', pkg_info) 277 | return False 278 | # now let's move the files out of the way 279 | for element in ('setuptools', 'pkg_resources.py', 'site.py'): 280 | element = os.path.join(placeholder, element) 281 | if os.path.exists(element): 282 | _rename_path(element) 283 | else: 284 | log.warn('Could not find the %s element of the ' 285 | 'Setuptools distribution', element) 286 | return True 287 | 288 | 289 | def _after_install(dist): 290 | log.warn('After install bootstrap.') 291 | placeholder = dist.get_command_obj('install').install_purelib 292 | _create_fake_setuptools_pkg_info(placeholder) 293 | 294 | @_no_sandbox 295 | def _create_fake_setuptools_pkg_info(placeholder): 296 | if not placeholder or not os.path.exists(placeholder): 297 | log.warn('Could not find the install location') 298 | return 299 | pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) 300 | setuptools_file = 'setuptools-%s-py%s.egg-info' % \ 301 | (SETUPTOOLS_FAKED_VERSION, pyver) 302 | pkg_info = os.path.join(placeholder, setuptools_file) 303 | if os.path.exists(pkg_info): 304 | log.warn('%s already exists', pkg_info) 305 | return 306 | 307 | log.warn('Creating %s', pkg_info) 308 | f = open(pkg_info, 'w') 309 | try: 310 | f.write(SETUPTOOLS_PKG_INFO) 311 | finally: 312 | f.close() 313 | 314 | pth_file = os.path.join(placeholder, 'setuptools.pth') 315 | log.warn('Creating %s', pth_file) 316 | f = open(pth_file, 'w') 317 | try: 318 | f.write(os.path.join(os.curdir, setuptools_file)) 319 | finally: 320 | f.close() 321 | 322 | def _patch_egg_dir(path): 323 | # let's check if it's already patched 324 | pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') 325 | if os.path.exists(pkg_info): 326 | if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): 327 | log.warn('%s already patched.', pkg_info) 328 | return False 329 | _rename_path(path) 330 | os.mkdir(path) 331 | os.mkdir(os.path.join(path, 'EGG-INFO')) 332 | pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') 333 | f = open(pkg_info, 'w') 334 | try: 335 | f.write(SETUPTOOLS_PKG_INFO) 336 | finally: 337 | f.close() 338 | return True 339 | 340 | 341 | def _before_install(): 342 | log.warn('Before install bootstrap.') 343 | _fake_setuptools() 344 | 345 | 346 | def _under_prefix(location): 347 | if 'install' not in sys.argv: 348 | return True 349 | args = sys.argv[sys.argv.index('install')+1:] 350 | for index, arg in enumerate(args): 351 | for option in ('--root', '--prefix'): 352 | if arg.startswith('%s=' % option): 353 | top_dir = arg.split('root=')[-1] 354 | return location.startswith(top_dir) 355 | elif arg == option: 356 | if len(args) > index: 357 | top_dir = args[index+1] 358 | return location.startswith(top_dir) 359 | elif option == '--user' and USER_SITE is not None: 360 | return location.startswith(USER_SITE) 361 | return True 362 | 363 | 364 | def _fake_setuptools(): 365 | log.warn('Scanning installed packages') 366 | try: 367 | import pkg_resources 368 | except ImportError: 369 | # we're cool 370 | log.warn('Setuptools or Distribute does not seem to be installed.') 371 | return 372 | ws = pkg_resources.working_set 373 | try: 374 | setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools', 375 | replacement=False)) 376 | except TypeError: 377 | # old distribute API 378 | setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools')) 379 | 380 | if setuptools_dist is None: 381 | log.warn('No setuptools distribution found') 382 | return 383 | # detecting if it was already faked 384 | setuptools_location = setuptools_dist.location 385 | log.warn('Setuptools installation detected at %s', setuptools_location) 386 | 387 | # if --root or --preix was provided, and if 388 | # setuptools is not located in them, we don't patch it 389 | if not _under_prefix(setuptools_location): 390 | log.warn('Not patching, --root or --prefix is installing Distribute' 391 | ' in another location') 392 | return 393 | 394 | # let's see if its an egg 395 | if not setuptools_location.endswith('.egg'): 396 | log.warn('Non-egg installation') 397 | res = _remove_flat_installation(setuptools_location) 398 | if not res: 399 | return 400 | else: 401 | log.warn('Egg installation') 402 | pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') 403 | if (os.path.exists(pkg_info) and 404 | _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): 405 | log.warn('Already patched.') 406 | return 407 | log.warn('Patching...') 408 | # let's create a fake egg replacing setuptools one 409 | res = _patch_egg_dir(setuptools_location) 410 | if not res: 411 | return 412 | log.warn('Patched done.') 413 | _relaunch() 414 | 415 | 416 | def _relaunch(): 417 | log.warn('Relaunching...') 418 | # we have to relaunch the process 419 | args = [sys.executable] + sys.argv 420 | sys.exit(subprocess.call(args)) 421 | 422 | 423 | def _extractall(self, path=".", members=None): 424 | """Extract all members from the archive to the current working 425 | directory and set owner, modification time and permissions on 426 | directories afterwards. `path' specifies a different directory 427 | to extract to. `members' is optional and must be a subset of the 428 | list returned by getmembers(). 429 | """ 430 | import copy 431 | import operator 432 | from tarfile import ExtractError 433 | directories = [] 434 | 435 | if members is None: 436 | members = self 437 | 438 | for tarinfo in members: 439 | if tarinfo.isdir(): 440 | # Extract directories with a safe mode. 441 | directories.append(tarinfo) 442 | tarinfo = copy.copy(tarinfo) 443 | tarinfo.mode = 448 # decimal for oct 0700 444 | self.extract(tarinfo, path) 445 | 446 | # Reverse sort directories. 447 | if sys.version_info < (2, 4): 448 | def sorter(dir1, dir2): 449 | return cmp(dir1.name, dir2.name) 450 | directories.sort(sorter) 451 | directories.reverse() 452 | else: 453 | directories.sort(key=operator.attrgetter('name'), reverse=True) 454 | 455 | # Set correct owner, mtime and filemode on directories. 456 | for tarinfo in directories: 457 | dirpath = os.path.join(path, tarinfo.name) 458 | try: 459 | self.chown(tarinfo, dirpath) 460 | self.utime(tarinfo, dirpath) 461 | self.chmod(tarinfo, dirpath) 462 | except ExtractError: 463 | e = sys.exc_info()[1] 464 | if self.errorlevel > 1: 465 | raise 466 | else: 467 | self._dbg(1, "tarfile: %s" % e) 468 | 469 | 470 | def main(argv, version=DEFAULT_VERSION): 471 | """Install or upgrade setuptools and EasyInstall""" 472 | tarball = download_setuptools() 473 | _install(tarball) 474 | 475 | 476 | if __name__ == '__main__': 477 | main(sys.argv[1:]) 478 | --------------------------------------------------------------------------------