{ "info": { "author": "herr kaste", "author_email": "herr.kaste@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development :: Libraries" ], "description": "The main purpose of an assets management application is to map local paths to urls on the server. Secondly you want to apply filters to sets of files, e.g. you want to merge and minify them. You often want this build-process to be automatic, on-the-fly, just by pressing refresh in your webbrowser. Later, in a production mode of your web application you just want to serve different, specific versions of your files.\r\n\r\nSince I was just too dumb to use miracle2k's `webassets `_ - did three days to write a new filter, new manifest implementation, then the ASSETS tag for jinja didn't liked my multiple environments - I put together this simple stuff.\r\n\r\nIn the following walk-through we use three external filters; you usually need `node `_ with `uglifyjs `_ and/or `lessc `_; to minify css the python package `cssmin `_ is used. You could also use `cleancss `_ and `coffeescript `_ . So you need to install some of these separately, but it's easy to write your own filters as you will see. Please contribute via `github `_. \r\n\r\n::\r\n\r\n\tfrom ass.ets import *\r\n\tfrom ass.ets.filters import *\r\n\r\n\timport os\r\n\there = os.path.dirname( os.path.realpath(__file__) )\r\n\r\n\tenv = Environment(\r\n\t\tmap_from=os.path.join(here, 'static'),\r\n\t\tmap_to='/static',\r\n\t\t# t.i. a local file ./static/lib.js will later be served as /static/lib.js\r\n\t\t\r\n\t\t# use the default implementation of our manifest\r\n\t\t# we don't want the manifest in the static dir, so we provide an absolute path\r\n\t\tmanifest=os.path.join(here, 'assets-manifest'), \r\n\t\t#or provide your own object that answers get(key) and set(key, value)\r\n\t\t\r\n\t\t# in production mode we ask the manifest which file to serve, in this case \r\n\t\t# we need to build at least once before we deploy\r\n\t\tproduction=use_manifest \r\n\t\t#note: use_manifest is just another filter\r\n\t)\r\n\r\n\tjslib = bundle(\r\n\t\t\"jquery.1.7.1.js\", #...\r\n\t\tname='jslib', # the naive manifest implementation uses this name as its key\r\n\t\tenv=env, # the bundle inherits all the settings from env \r\n\r\n\t\t# very explicit chain of filters\r\n\t\tdevelopment=[read, merge, store_as('jslib.js')],\r\n\t\tbuild_=[read, merge, uglifyjs, store_as('jslib-%(version)s.js'), store_manifest],\r\n\t\t# yes ^ thats an underscore, because bundles have a build() method\r\n\t\t# uglifyjs assumes you have node's uglifyjs in your path, see below on how to cutomize this\r\n\t)\r\n\r\n\t# now we could do\r\n\t# env.mode = 'development'\r\n\t# print [url for url in jslib.urls()]\r\n\r\n\t# in our build script we do\r\n\t# env.mode = 'build_'\r\n\t# print [url for url in jslib.build()]\r\n\r\nThere's is no much difference between ``urls()`` and ``build()``. In the above example both pipes - 'development' and '\\built_' - yield relative paths at the end, ``urls()`` just uses ``env.map_to`` to construct a url, where ``build()`` maps to the local path using ``map_from``.\r\nInternally ``build()`` appends the following filter to the chain::\r\n\r\n\t@filter(accepts='filenames', yields='filenames')\r\n\tdef local_path(files, bundle):\r\n\t\tfor file in files:\r\n\t\t\tyield os.path.join(bundle.map_from, file)\r\n\r\nEach ``@filter`` is effectively a ``worker`` from the `useless.pipes `_ package, which provides sugar for chaining generators. The filter-functions have a specific signature: the first argument always is the iterable from the previous filter. In case it's the first filter in the chain, it is the file-list you want to bundle. The second argument is the bundle we currently process. After that you may provide optional keyword arguments.\r\n\r\nOk, add another bundle::\r\n\r\n\tless_styles = Bundle(\r\n\t\t'styles.less', \r\n\t\tname='less_style',\r\n\t\tenv=env,\r\n\t\tdevelopment=as_is,\r\n\t\tbuild_=[read, merge, lessify]\r\n\t)\r\n\tall_styles = Bundle(\r\n\t\tless_styles, 'main.css',\r\n\t\tname='all_styles',\r\n\t\tenv=env,\r\n\t\tdevelopment=as_is,\r\n\t\tbuild_=[read, merge, cssminify, store_as('styles-%(version)s.css'), store_manifest]\r\n\t)\r\n\r\nT.i. in development mode we just spit the files as they are, when we build the less-file gets 'delessed', after that all css-files are merged and stored. Note that the `less_styles.build_` chain yields the css-content. We don't store a temporary file. The current implementation of `read` actually expects nested bundles to yield contents not filenames. \r\n\r\nOk, now we need the less-js file in the development version of our app. We write a simple filter::\r\n\r\n\tdef add(*filenames):\r\n\t\t@filter\r\n\t\tdef add_(items, bundle):\r\n\t\t\tfor item in items:\r\n\t\t\t\tyield item\r\n\r\n\t\t\tfor filename in filenames:\r\n\t\t\t\tyield filename\r\n\r\n\t\treturn add_\t\t\t\r\n\r\n\t# and then\r\n\tless_styles = Bundle(\r\n\t\t'styles.less', \r\n\t\tname='less_style',\r\n\t\tdevelopment=[as_is, add('less-1.2.1.min.js')],\r\n\t\tbuild_=[read, merge, lessify]\r\n\t)\r\n\r\n\r\n\t# all_styles.urls() now yields .css, .less and .js files in development mode and one .css file in built_ or production mode.\r\n\r\nIn jinja we could define two macros::\r\n\r\n\t{%- macro asset(url) %}\r\n\t\t{%- if url.endswith('.js') %}{%- endif %}\r\n\t\t{%- if url.endswith('.css') %}{%- endif %}\r\n\t\t{%- if url.endswith('.less') %}{%- endif %}\r\n\t{%- endmacro %}\r\n\t{%- macro assets_for(bundle) %}\r\n\t\t{%- for url in bundle.urls() %}\r\n\t\t\t{{ asset(url) }}\r\n\t\t{%- endfor %}\r\n\t{%- endmacro %}\r\n\r\nAssume ``Flask`` and ``g.all_styles = all_styles``::\r\n\r\n\t{{ assets_for(g.all_styles) }}\r\n\r\nand we're done.\r\n\r\nAs an example, some builtin filters::\r\n\r\n\tuglifyjs = popens(args=['uglifyjs'])\r\n\tlessify = popens(args=['lessc', '-'])\r\n\tcleancss = popens(args=['cleancss'])\r\n\r\n\t@filter(accepts='contents', yields='contents')\r\n\tdef decaffeinate(files, bundle, bin='coffee', bare=False):\r\n\t\targs = [bin, '-sp' + 'b' if bare else '']\r\n\t\treturn files | popens(bundle, args=args)\r\n\r\n\t# where popens is defined like\r\n\r\n\t@filter(accepts='contents', yields='contents')\r\n\tdef popens(files, bundle, args=None, shell=True if on_windows else False, name=None):\r\n\t\tassert args is not None\r\n\t\tname = name or args[0] # assume we have a good name on the first argument which is the binary\r\n\r\n\t\tfor file in files:\r\n\t\t\tproc = subprocess.Popen(\r\n\t\t\t\targs,\r\n\t\t\t\tstdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,\r\n\t\t\t\tshell=shell)\r\n\t\t\tstdout, stderr = proc.communicate(file)\r\n\r\n\t\t\tif proc.returncode != 0:\r\n\t\t\t\traise FilterError(('%s: subprocess had error: stderr=%s, '+\r\n\t 'stdout=%s, returncode=%s') % (\r\n\t name, stderr, stdout, proc.returncode))\r\n\r\n\t\t\tyield stdout\r\n\r\nThat's the real code. We use keyword arguments to 'customize' a filter, or make a filter from a filter. Say ``uglifyjs`` is not in your path, you could then redefine this filter::\r\n\r\n\tuglifyjs = popens(args=['C:\\\\....'], shell=False, name='uglify')\t\r\n\r\nSome last things; if you often write::\r\n\t\r\n\t[read, merge, uglifyjs, store_as('...'), store_manifest]\r\n\r\nYou could instead write something like this::\r\n\r\n\t# no magic here, just list + list\r\n\tprocess_js = [read, merge, uglifyjs]\r\n\tjslib.build_ = process_js + [store_as('...'), store_manifest]\r\n\r\nOR::\r\n\t\r\n\tdef process_js_and_store(fn):\r\n\t\treturn [read, merge, uglifyjs, store_as(fn), store_manifest]\r\n\tjslib.build_ = process_js_and_store('...')\r\n\r\nA filter that combines other filters by the way looks rather awkward, just to let you know::\r\n\r\n\t@filter\r\n\tdef read_and_merge(items, bundle):\r\n\t\treturn items | read(bundle) | merge(bundle)\r\n\r\n\r\nContribute back to `dev `_ if you like.", "description_content_type": null, "docs_url": null, "download_url": "http://github.com/kaste/ass.ets/tarball/master#egg=ass.ets-dev", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/kaste/ass.ets", "keywords": "", "license": "UNKNOWN", "maintainer": "", "maintainer_email": "", "name": "ass.ets", "package_url": "https://pypi.org/project/ass.ets/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/ass.ets/", "project_urls": { "Download": "http://github.com/kaste/ass.ets/tarball/master#egg=ass.ets-dev", "Homepage": "http://github.com/kaste/ass.ets" }, "release_url": "https://pypi.org/project/ass.ets/0.1.1/", "requires_dist": null, "requires_python": null, "summary": "Assets management for webapps.", "version": "0.1.1" }, "last_serial": 743186, "releases": { "0.0.2": [ { "comment_text": "", "digests": { "md5": "295fd64219ea75b41edf2f28dfc985e6", "sha256": "1654807eba04491576bc545839ab85181be126f53be9b7abf0789cde713b8bbf" }, "downloads": -1, "filename": "ass.ets-0.0.2.zip", "has_sig": false, "md5_digest": "295fd64219ea75b41edf2f28dfc985e6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15139, "upload_time": "2012-04-15T12:34:00", "url": "https://files.pythonhosted.org/packages/f7/d8/0f69205b94440c96cabec5398b7b13e88402b471cadbe31b7c1e6e41c19d/ass.ets-0.0.2.zip" } ], "0.0.3": [ { "comment_text": "", "digests": { "md5": "418f7b80f3053b35d79d1dc7e201e4b3", "sha256": "26d695eff58ed409814a3c1a173ddfefce100b97876a6ee12d9cde28906250a0" }, "downloads": -1, "filename": "ass.ets-0.0.3.zip", "has_sig": false, "md5_digest": "418f7b80f3053b35d79d1dc7e201e4b3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 17458, "upload_time": "2012-04-19T22:53:19", "url": "https://files.pythonhosted.org/packages/f5/e1/b083639b8ce7333d5b119e5f7e813f051c702f9b4610f32fdda880377fcc/ass.ets-0.0.3.zip" } ], "0.0.4": [ { "comment_text": "", "digests": { "md5": "6b21a37c13b8af70f3afc1f65e2da9ca", "sha256": "fc28252dbead1756f1357b385aef16e816bd5ec44a114342233518451cf0f83a" }, "downloads": -1, "filename": "ass.ets-0.0.4.zip", "has_sig": false, "md5_digest": "6b21a37c13b8af70f3afc1f65e2da9ca", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 18892, "upload_time": "2012-04-25T15:04:22", "url": "https://files.pythonhosted.org/packages/27/94/d442052fea808b1571708712e3afae9d4cc21866d090c4b8e7f174049490/ass.ets-0.0.4.zip" } ], "0.0.5": [ { "comment_text": "", "digests": { "md5": "ae170082c37515fa69d22e26c7ce93dd", "sha256": "e347c189a5936a51c4f5b9f2428e70bd698c1359fcbf569339dd5823c98cb717" }, "downloads": -1, "filename": "ass.ets-0.0.5.zip", "has_sig": false, "md5_digest": "ae170082c37515fa69d22e26c7ce93dd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21692, "upload_time": "2012-04-30T14:23:04", "url": "https://files.pythonhosted.org/packages/75/5d/6b509ae5537ecb9bf5ecc3faa6bb1b77f94ff28815fcbbedca672d3117d8/ass.ets-0.0.5.zip" } ], "0.0.6": [ { "comment_text": "", "digests": { "md5": "9a36d0908417beba9c998929000ffc56", "sha256": "b66c374c42995ad610fbd92399f4aa46581b7d938d05de2592b296c38df5e5d5" }, "downloads": -1, "filename": "ass.ets-0.0.6.zip", "has_sig": false, "md5_digest": "9a36d0908417beba9c998929000ffc56", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 22570, "upload_time": "2012-05-01T00:19:50", "url": "https://files.pythonhosted.org/packages/5a/5c/1eadf94d12ace196e3e1d82b0bade3f8277c86cf1aacc11085ecbd472973/ass.ets-0.0.6.zip" } ], "0.1.0": [ { "comment_text": "", "digests": { "md5": "e63bba8a4b247fc81ba42d53f7a99fb0", "sha256": "902b6f9492d50092a9ccc1c3be543159de3175de29aa1df61a65edb905380374" }, "downloads": -1, "filename": "ass.ets-0.1.0.zip", "has_sig": false, "md5_digest": "e63bba8a4b247fc81ba42d53f7a99fb0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23866, "upload_time": "2012-05-04T23:44:15", "url": "https://files.pythonhosted.org/packages/e3/6f/624f3dd3ac336e545768b35f3f29d2bfefee0110b3a8a6cd679a5dd0d215/ass.ets-0.1.0.zip" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "549134c01907b1542708fd409a6f7bb8", "sha256": "4f7469963183a5b1d6abcb39a9d6c60bd7ecc8164b31bfe685a5cd015058d651" }, "downloads": -1, "filename": "ass.ets-0.1.1.zip", "has_sig": false, "md5_digest": "549134c01907b1542708fd409a6f7bb8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23853, "upload_time": "2012-05-06T17:29:50", "url": "https://files.pythonhosted.org/packages/ed/ab/a711d23ee6ec4bac207c1ca4063a6c5e45d396c488d5759a3f285d09698e/ass.ets-0.1.1.zip" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "549134c01907b1542708fd409a6f7bb8", "sha256": "4f7469963183a5b1d6abcb39a9d6c60bd7ecc8164b31bfe685a5cd015058d651" }, "downloads": -1, "filename": "ass.ets-0.1.1.zip", "has_sig": false, "md5_digest": "549134c01907b1542708fd409a6f7bb8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 23853, "upload_time": "2012-05-06T17:29:50", "url": "https://files.pythonhosted.org/packages/ed/ab/a711d23ee6ec4bac207c1ca4063a6c5e45d396c488d5759a3f285d09698e/ass.ets-0.1.1.zip" } ] }