[
  {
    "path": ".flake8",
    "content": "[flake8]\nmax-line-length = 88\nexclude =\n    .tox\n    node_modules\n    *env/\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n    - '*'\n\njobs:\n  build:\n    if: github.repository == 'jazzband/django-pipeline'\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Set up Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.9'\n\n      - name: Install dependencies\n        run: |\n          python -m pip install -U pip\n          python -m pip install -U twine build setuptools-scm\n\n      - name: Build package\n        run: |\n          python -m setuptools_scm\n          python -m build\n          twine check --strict dist/*\n\n      - name: Upload packages to Jazzband\n        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')\n        uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          user: jazzband\n          password: ${{ secrets.JAZZBAND_RELEASE_KEY }}\n          repository_url: https://jazzband.co/projects/django-pipeline/upload\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\non: [push, pull_request]\n\njobs:\n  build:\n    name: build (Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }})\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.10']\n        django-version: ['4.1', '4.2', '5.0', '5.1', '5.2', 'main']\n        exclude:\n          - python-version: '3.9'\n            django-version: '5.0'\n          - python-version: '3.9'\n            django-version: '5.1'\n          - python-version: '3.9'\n            django-version: '5.2'\n          - python-version: '3.9'\n            django-version: 'main'\n          - python-version: 'pypy-3.10'\n            django-version: '4.1'\n          - python-version: 'pypy-3.10'\n            django-version: '4.2'\n          - python-version: 'pypy-3.10'\n            django-version: '5.0'\n          - python-version: 'pypy-3.10'\n            django-version: '5.1'\n          - python-version: 'pypy-3.10'\n            django-version: '5.2'\n          - python-version: 'pypy-3.10'\n            django-version: 'main'\n          - python-version: '3.12'\n            django-version: '4.1'\n          - python-version: '3.13'\n            django-version: '4.1'\n          - python-version: '3.13'\n            django-version: '4.2'\n          - python-version: '3.13'\n            django-version: '5.0'\n          - python-version: '3.13'\n            django-version: '5.1'\n\n    steps:\n    - uses: actions/checkout@v4\n    - uses: actions/setup-java@v4\n      with:\n        distribution: 'temurin'\n        java-version: '21'\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Set up Node\n      uses: actions/setup-node@v4\n      with:\n        node-version: '16'\n\n    - name: Install Node dependencies\n      run: npm install\n\n    - name: Get pip cache dir\n      id: pip-cache\n      run: echo \"dir=$(pip cache dir)\" >> $GITHUB_OUTPUT\n\n    - name: Cache\n      uses: actions/cache@v4\n      with:\n        path: ${{ steps.pip-cache.outputs.dir }}\n        key:\n          ${{ matrix.python-version }}-v1-${{ hashFiles('**/pyproject.toml') }}-${{ hashFiles('**/tox.ini') }}\n        restore-keys: |\n          ${{ matrix.python-version }}-v1-\n\n    - name: Install Python dependencies\n      run: |\n        python -m pip install --upgrade pip\n        python -m pip install --upgrade tox tox-gh-actions\n\n    - name: Tox tests\n      run: |\n        tox -v\n      env:\n        DJANGO: ${{ matrix.django-version }}\n\n    - name: Upload coverage\n      uses: codecov/codecov-action@v5\n      with:\n        name: Python ${{ matrix.python-version }}\n\n  ruff:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - run: pip install --user ruff==0.12.5\n    - run: ruff check . --extend-select=C4,C9,I,PLC,PLE,PLR,U --ignore=C414,I001,PLR0913,UP007,UP032 --target-version=py39\n"
  },
  {
    "path": ".gitignore",
    "content": ".AppleDouble\n*.pyc\n:2e_*\n*.tmproj\n.*.swp\n*.swo\nbuild\ndist\nMANIFEST\ndocs/_build/\n*.egg-info\n.coverage\ncoverage/\ncoverage.xml\ntests/static/\ntests/assets/js/dummy.js\ntests/node_modules/\n.tox/\n.DS_Store\n.idea\n.venv\n.vscode\n.project\n.pydevproject\n.ropeproject\n__pycache__\nnpm-debug.log\ntests/npm-cache\ndjango-pipeline-*/\n.tags\nnode_modules/\npackage-lock.json\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/psf/black\n    rev: 24.8.0\n    hooks:\n      - id: black\n\n  - repo: https://github.com/PyCQA/isort\n    rev: 5.13.2\n    hooks:\n      - id: isort\n\n  - repo: https://github.com/PyCQA/flake8\n    rev: 7.1.1\n    hooks:\n      - id: flake8\n\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v5.0.0\n    hooks:\n      - id: check-merge-conflict\n      - id: check-yaml\n\nci:\n  autoupdate_schedule: quarterly\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\nversion: 2\n\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.10\"\n\nsphinx:\n  configuration: docs/conf.py\n"
  },
  {
    "path": "AUTHORS",
    "content": "Pipeline is a fork of django-compress which was originally created by\nAndreas Pelme <andreas@pelme.se> in 2008.\n\nThese people have provided bug fixes, new features, improved the documentation\nor just made Pipeline more awesome.\n\n * Adam Charnock <acharnock@gmail.com>\n * Alan Lu <gotoalanlu@gmail.com>\n * Aleksey Porfirov <lexqt@yandex.ru>\n * Alex Gavrișco <alexandr@gavrisco.com>\n * Alexander Artemenko <svetlyak40wt>\n * Alexander Pugachev <alexander.pugachev@gmail.com>\n * Alexis Svinartchouk <zvin@free.fr>\n * Allard Stijnman <a.g.stijnman@gmail.com>\n * Alvin Mites <alvin@mitesdesign.com>\n * Andreas Cederström <andreas@klydd.se>\n * Andrew Choi <andrew@choi.com>\n * Andy Kish <agkish@gmail.com>\n * Ara Anjargolian <ara818@gmail.com>\n * Arnar Yngvason <arnar@hvitahusid.is>\n * Austin Pua <pua.austin.anderson@gmail.com>\n * Axel Haustant <noirbizarre@gmail.com>\n * Balazs Kossovics <balazs.kossovics@e-loue.com>\n * Ben Spaulding <ben@spaulding.im>\n * Ben Vinegar <ben@benv.ca>\n * Brad Pitcher <bradpitcher@gmail.com>\n * Brant Young <brant.young@gmail.com>\n * Brawaga <brawaga@gmail.com>\n * Brian Montgomery <brianm@appliedsec.com>\n * Bryan Chow <bryan@fullfactor.com>\n * Buky <buky.dev@outlook.com>\n * Caio Ariede <caio.ariede@gmail.com>\n * Camilo Nova <camilo.nova@gmail.com>\n * Carl Meyer <carl@oddbird.net>\n * Casey Greene <csgreene@princeton.edu>\n * Chad Miller <chad.miller@canonical.com>\n * Chris Applegate <chris.applegate@wearesocial.net>\n * Chris Reeves <hello@chris.reeves.io>\n * Christian Hammond <chipx86@chipx86.com>\n * Christofer Bertonha <christoferbertonha@gmail.com>\n * Christopher Dilorenzo <dilorenzo.christopher@gmail.com>\n * Collin Stedman <cstedman@princeton.edu>\n * Corey Farwell <coreyf@rwell.org>\n * Corrado Primier <cp@corradoprimier.it>\n * Danielle Madeley <danielle@madeley.id.au>\n * David Charbonnier <d.charbonnier@oxys.net>\n * David Cramer <dcramer@gmail.com>\n * David Hughes <d@vidhughes.com>\n * David Trowbridge <trowbrds@gmail.com>\n * Denis V Seleznyov <code@xy2.ru>\n * DJ Sharkey <dj@newscred.com>\n * Edwin Lunando <edwinlunando@gmail.com>\n * Eric Hamiter <ehamiter@gmail.com>\n * Evan Myller <eMyller@7ws.co>\n * Fabian Büchler <fabian.buechler@gmail.com>\n * Feanil Patel <feanil@edx.org>\n * Felix Last <mail@felixlast.de>\n * Florent Messa <florent.messa@gmail.com>\n * Frankie Dintino <fdintino@theatlantic.com>\n * Hannes Ljungberg <hannes.ljungberg@gmail.com>\n * Idan Zalzberg <idanzalz@gmail.com>\n * Jacob Haslehurst <jacob@haslehurst.net>\n * James Keys <skolsuper@gmail.com>\n * Jannis Leidel <jannis@leidel.info>\n * Jared Scott <jscott@convertro.com>\n * Jaromir Fojtu <jaromir.fojtu@gmail.com>\n * Jeff Held <jheld135@gmail.com>\n * John Whitlock <John-Whitlock@ieee.org> (@jwhitlock)\n * Jon Dufresne <jon.dufresne@gmail.com>\n * Josh Braegger <rckclmbr@gmail.com>\n * Joshua Kehn <josh@kehn.us>\n * Julien Hartmann <julien@etherdream.org>\n * Kevin Fox <kevin_fox@me.com> (@KFoxder)\n * Kristian Glass <git@doismellburning.co.uk>\n * Kyle MacFarlane <kyle@deletethetrees.com>\n * Leonardo Orozco <leonardoorozcop@gmail.com>\n * Luke Yu-Po Chen <nuemail@gmail.com>\n * Mark Sandstrom <mark@deliciouslynerdy.com>\n * Matt Dennewitz <mattdennewitz@gmail.com>\n * Matthieu Gallet <matthieu.gallet@19pouces.net>\n * Max Klymyshyn <klymyshyn@gmail.com>\n * Melvin Laplanche <melvin.laplanche+dev@gmail.com>\n * Michael Weibel <michael.weibel@gmail.com>\n * Michał Górny <mgorny@gentoo.org>\n * Miguel Araujo Perez <miguel.araujo.perez@gmail.com>\n * Mike Gilbert <floppym@gentoo.org>\n * Miroslav Shubernetskiy <miroslav@miki725.com>\n * Natal Ngetal <natal.ngetal@novapost.fr>\n * Nathan Cox <akujin@akujin.com>\n * Nathan Shafer <nate@torzo.com>\n * Patrick Altman <paltman@gmail.com>\n * Peter Baumgartner <pete@lincolnloop.com>\n * Peyman Salehi <slh.peyman@gmail.com>\n * Philipp Wollermann <philipp.wollermann@gmail.com>\n * Pierre Drescher <pierre.drescher@gmail.com>\n * Rajiv Bose <nerd.bose@gmail.com>\n * Rami Chowdhury <rami.chowdhury@gmail.com>\n * Remco Wendt <remco@maykinmedia.nl>\n * Remy Sanchez <remy.sanchez@hyperthese.net>\n * Sam Thomson <sammthomson@gmail.com>\n * Sander Smits <jhmsmits@gmail.com>\n * Sander Steffann <sander@steffann.nl>\n * Sassan Haradji (@sassanh)\n * Sayed Raianul Kabir <raian@newscred.com>\n * Simon Lydell <simon.lydell@gmail.com>\n * Sirex <sirexas@gmail.com>\n * Sławek Ehlert <slafs@op.pl>\n * Stefano Brentegani <sbrentegani@gmail.com>\n * Stephan Wienczny <stephan@wienczny.de>\n * Steven Cummings <estebistec@gmail.com>\n * Tadas Dailyda <tadas@dailyda.com>\n * Teo Klestrup Röijezon <teo@nullable.se>\n * Thomas Parslow <tom@almostobsolete.net>\n * Tiago Espinha <tiago@espinha.nl>\n * Timothée Peignier <timothee.peignier@tryphon.org>\n * Tom Yam <tomyam1@gmail.com>\n * Tomek Paczkowski <tomek@hauru.eu>\n * Trey Smith <trey.smith@nasa.gov>\n * Vadym S. Khondar (@vskh)\n * Venelin Stoykov <venelin@magicsolutions.bg>\n * Victor Shnayder <victor@mitx.mit.edu>\n * Wictor Olseryd <wictor@olseryd.se>\n * Wismill\n * Zachary Kazanski <kazanski.zachary@gmail.com>\n * Zenobius Jiricek <zenobius.jiricek@gmail.com>\n * Zeus Kronion\n "
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nAs contributors and maintainers of the Jazzband projects, and in the interest of\nfostering an open and welcoming community, we pledge to respect all people who\ncontribute through reporting issues, posting feature requests, updating documentation,\nsubmitting pull requests or patches, and other activities.\n\nWe are committed to making participation in the Jazzband a harassment-free experience\nfor everyone, regardless of the level of experience, gender, gender identity and\nexpression, sexual orientation, disability, personal appearance, body size, race,\nethnicity, age, religion, or nationality.\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery\n- Personal attacks\n- Trolling or insulting/derogatory comments\n- Public or private harassment\n- Publishing other's private information, such as physical or electronic addresses,\n  without explicit permission\n- Other unethical or unprofessional conduct\n\nThe Jazzband roadies have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are not\naligned to this Code of Conduct, or to ban temporarily or permanently any contributor\nfor other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\nBy adopting this Code of Conduct, the roadies commit themselves to fairly and\nconsistently applying these principles to every aspect of managing the jazzband\nprojects. Roadies who do not follow or enforce the Code of Conduct may be permanently\nremoved from the Jazzband roadies.\n\nThis code of conduct applies both within project spaces and in public spaces when an\nindividual is representing the project or its community.\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by\ncontacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and\ninvestigated and will result in a response that is deemed necessary and appropriate to\nthe circumstances. Roadies are obligated to maintain confidentiality with regard to the\nreporter of an incident.\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version\n1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version]\n\n[homepage]: https://contributor-covenant.org\n[version]: https://contributor-covenant.org/version/1/3/0/\n"
  },
  {
    "path": "CONTRIBUTING.rst",
    "content": ".. image:: https://jazzband.co/static/img/jazzband.svg\n   :target: https://jazzband.co/\n   :alt: Jazzband\n\nThis is a `Jazzband <https://jazzband.co>`_ project. By contributing you agree to abide by the `Contributor Code of Conduct <https://jazzband.co/docs/conduct>`_ and follow the `guidelines <https://jazzband.co/docs/guidelines>`_.\n\nContribute\n==========\n\n#. Check for open issues or open a fresh issue to start a discussion around a\n   feature idea or a bug. There is a **contribute!** tag for issues that should be\n   ideal for people who are not very familiar with the codebase yet.\n#. Fork the repository on Github to start making your changes on a topic branch.\n#. Write a test which shows that the bug was fixed or that the feature works as expected.\n#. Send a pull request and bug the maintainer until it gets merged and published.\n   Make sure to add yourself to *AUTHORS*.\n\nOtherwise, if you simply wants to suggest a feature or report a bug, create an issue :\nhttps://github.com/jazzband/django-pipeline/issues\n\n\nRunning tests\n=============\n\nWe use tox to run the test suite on different versions locally (and GitHub Actions\nto automate the check for PRs).\n\nTo tun the test suite locally, please make sure your python environment has\ntox and django installed::\n\n    python3.7 -m pip install tox\n\nSince we use a number of node.js tools, one should first install the node\ndependencies. We recommend using [nvm](https://github.com/nvm-sh/nvm#installation-and-update) , tl;dr::\n\n    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash\n    nvm install node\n    nvm use node\n\nAnd then simply execute tox to run the whole test matrix::\n\n    tox\n"
  },
  {
    "path": "HISTORY.rst",
    "content": ".. :changelog:\n\nHistory\n=======\n\n4.1.0\n=====\n* Add support for Python 3.13\n* Add support for Django 5.2\n\n4.0.0\n=====\n* Drop support for Python 3.8\n* Confirm support for Django 5.1 and drop support for Django 3.2\n* Use pyproject.toml\n\n3.1.0\n=====\n\n* Replace deprecated .warn method with .warning\n* Update sourcemap paths when concatenating source files\n* Ensure correct compiler error styling and strip ANSI escape sequences\n\n3.0.0\n=====\n\n* Use Pypy 3.10\n* Drop support for Python 3.7\n* Drop support for Django 2\n* Add Python 3.12 support\n* Add Django 4.2 support\n* Add Django 5.0 support\n\n2.1.0\n=====\n\n* Update README.rst and add Pipeline overview image.\n* Add TypeScript compiler support.\n* Drop support for ``manifesto`` package.\n* Add support for Python 3.11 and Django 4.1\n\n\n2.0.9\n=====\n\n* Fixed some typos in the docs.\n* Fixed string type of errors reported from compilers and compressors.\n* Updated github actions matrix for host and django support.\n* Updated github actions configuration to use modern versions of third-party\n  actions.\n* Improved the packager to copy files to (S3) storage if it does not exist\n  (#502).\n\n\n2.0.8\n=====\n\n* Added **Django 4.0** compatibility. Thanks to @kevinmarsh (#760)\n* Add tests for **Django 4.0**,  **Python 3.9** and **Python 3.10**.\n  Thank to @kevinmarsh (#739)\n* Introduce CODE_OF_CONDUCT.md for the project. Thank to @hugovk (#758)\n* Add precision in the documentation for PipelineCachedStorage.\n  Thank to @gatsinski (#739)\n* Drop support for slimit compressor (#765) due to package not released\n  an official version for Python 3 and not any new package release from 2013.\n* Edit github actions matrix: django 3.2.9 support python 3.10, remove\n  python 4.0 (doesn't exist) and exclude pypy-3.8 for django-main.\n* Add .pre-commit-config.yaml. Thanks to @hugovk (#762)\n* Update package.json due to CoffeeScript on NPM has moved to \"coffeescript\"\n* Update setup.py with Django 4.0 and Python 3.10\n\n2.0.7\n=====\n\n* Added **Django 3.2** compatibility (Thanks to @jramnai in #751)\n\n2.0.6\n======\n\n* Added terser (JS compressor for ES5 and ES6) (Thanks to @felix-last in #696)\n* Moved tests to GitHub Actions: https://github.com/jazzband/django-pipeline/actions (#738)\n* Fixed deprecation warnings from Django (Thanks to @edelvalle in #731)\n\n2.0.5\n======\n\n* Adding **Django 3.1** compatibility.\n* CachedStaticFilesStorage is removed from Django. Add a check\n  of the current version to prevent error while importing. Thank to @vmsp\n* Context in django.template.base is removed from Django and\n  not used anymore in django-pipeline.\n* Fixing widgets tests of django-pipeline due to Media.render_js change in\n  Django. More information in Django ticket #31892\n\n2.0.4\n======\n\n* Adding **css-html-js-minify** support to compress JS and CSS.\n* Update compressors documentation with css-html-js-minify.\n* Create tests for css-html-js-minify compressor.\n* Optimization by grouping the tests yuglify compressor.\n\n2.0.3\n======\n\n* Remove futures from pipeline **setup.py** requirements.\n\n2.0.2\n=====\n\n* Fix Middleware to properly decode HTML. Thank to @gatsinski\n* Keep mimetypes as str. Thank to @benspaulding\n* Based on #642 add 'NonPackagingPipelineManifestStorage' and update\n  the documentation: **storages.rst**. Thank to @kronion\n\n2.0.1\n=====\n\n* Add subclass of ManifestStaticFilesStorage. Thank to @jhpinson\n* Change the documentation to use PipelineManifestStorage in configuration\n  instead of PipelineCachedStorage now deprecated.\n* Change import MutableMapping from collections.abc. Thank to @colons\n\n2.0.0\n=====\n\n* **Definitely drop the support of Python 2**.\n* Drop support for Python 3.5 (not compatible with PEP 498).\n* Remove 'decorator.py' how was used for backward compatibility\n  between python 2 and 3 for metaclass inheritance on PipelineFormMedia.\n* Replace 'format' by 'fstring' (PEP 498: Literal String Interpolation).\n* Remove of old imports form 'django.utils.six' and these fixes (1.7.0).\n* Remove tests of uncovered versions of Python and Django.\n* Replace tests for Pypy by Pypy3.\n* Explicitly specify when files are read / write in binary mode.\n* Set opening files for tests to deal with universal newlines.\n* Upgrade documentation version to 2.0 to follow the project version.\n\n1.7.0\n=====\n\n* Release the last major version of django-pipeline working on Python 2.\n* Thank you for all the modifications made since version 1.6.14, which we cannot quote.\n* Apply an optimization to save time during development. Thank to @blankser\n* Edit setup.py to follow the recommendation of the documentation. Thank to @shaneikennedy\n* Add tests for Django 3.0 and Python 3.8\n* Add alternatives imports for django.utils.six, who has been removed in Django 3.0\n\n1.6.14\n======\n\n* Fix packaging issues.\n\n1.6.13\n======\n\n* Fix forward-slashed paths on Windows. Thanks to @etiago\n* Fix CSS URL detector to match quotes correctly. Thanks to @vskh\n* Add a compiler_options dict to compile, to allow passing options to custom\n  compilers. Thanks to @sassanh\n* Verify support for Django 1.11. Thanks to @jwhitlock\n\n1.6.12\n======\n\n* Supports Django 1.11\n* Fix a bug with os.rename on windows. Thanks to @wismill\n* Fix to view compile error if happens. Thanks to @brawaga\n* Add support for Pipeline CSS/JS packages in forms and widgets. Thanks to @chipx86\n\n1.6.11\n======\n\n* Fix performance regression. Thanks to Christian Hammond.\n\n1.6.10\n======\n\n* Added Django 1.10 compatiblity issues. Thanks to Austin Pua and Silvan Spross.\n* Documentation improvements. Thanks to Chris Streeter.\n\n1.6.9\n=====\n\n* Various build improvements.\n* Improved setup.py classifiers. Thanks to Sobolev Nikita.\n* Documentation improvements. Thanks to Adam Chainz.\n\n1.6.8\n=====\n\n* Made templatetags easier to subclass for special rendering behavior. Thanks\n  to Christian Hammond.\n* Updated the link to readthedocs. Thanks to Corey Farwell.\n* Fixed some log messages to correctly refer to the new PIPELINE settings\n  tructure. Thanks to Alvin Mites.\n* Changed file outdated checks to use os.path methods directly, avoiding\n  potential SuspiciousFileOperation errors which could appear with some django\n  storage configurations.\n\n1.6.7\n=====\n\n* Add a view for collecting static files before serving them. This behaves like\n  django's built-in ``static`` view and allows running the collector for\n  images, fonts, and other static files that do not need to be compiled. Thanks\n  to Christian Hammond.\n* Update documentation for the ES6Compiler to clarify filename requirements.\n  Thanks to Nathan Cox.\n* Add error output for compiler errors within the browser. This provides for a\n  much better experience when compiling files from the devserver. Thanks to\n  Christian Hammond.\n* Make unit tests run against Django 1.6 and 1.7. Thanks to Sławek Ehlert.\n\n1.6.6\n=====\n\n* Fix filtering-out of files which require a finder to locate.\n* Allow compilers to override the output path.\n* Fix error reporting when a compiler fails to execute.\n* Fix IOErrors when running collectstatic with some nodejs-based compilers and\n  compressors. Thanks to Frankie Dintino.\n* Fix compatibility of unit tests when running on Windows. Thanks to Frankie\n  Dintino.\n* Add unit tests for compilers and compressors. Thanks to Frankie Dintino.\n\n1.6.5\n=====\n\n* Fix Django < 1.8 compatibility. Thanks to David Trowbridge.\n* Allow to disable collector during development. Thanks to Leonardo Orozco.\n\n1.6.4\n=====\n\n* Fix compressor subprocess calls.\n\n1.6.3\n=====\n\n* Fix compressor command flattening.\n\n1.6.2\n=====\n\n* Remove subprocess32 usage since it breaks universal support.\n\n1.6.1\n=====\n\n* Fix path quoting issues. Thanks to Chad Miller.\n* Use subprocess32 package when possible.\n* Documentation fixes. Thanks to Sławek Ehlert and Jannis Leidel.\n\n1.6.0\n=====\n\n* Add full support for Django 1.9.\n* Drop support for Django 1.7.\n* Drop support for Python 2.6.\n* **BACKWARD INCOMPATIBLE** : Change configuration settings.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (©) 2008 Andreas Pelme <andreas@pelme.se>\nCopyright (©) 2011-2018 Timothée Peignier <timothee.peignier@tryphon.org>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "MANIFEST.in",
    "content": "recursive-include pipeline/templates *.html *.jinja\nrecursive-include pipeline/jinja2 *.html *.jinja\ninclude AUTHORS LICENSE README.rst HISTORY.rst CONTRIBUTING.rst\nrecursive-include tests *\nrecursive-exclude tests *.pyc *.pyo\nrecursive-exclude tests/node_modules *\nrecursive-exclude tests/npm-cache *\nrecursive-exclude tests/npm *\ninclude docs/Makefile docs/make.bat docs/conf.py\nrecursive-include docs *.rst\nexclude package.json requirements.txt tox.ini\n"
  },
  {
    "path": "README.rst",
    "content": "Pipeline\n========\n\n.. image:: https://jazzband.co/static/img/badge.svg\n    :alt: Jazzband\n    :target: https://jazzband.co/\n\n.. image:: https://github.com/jazzband/django-pipeline/workflows/Test/badge.svg\n   :target: https://github.com/jazzband/django-pipeline/actions\n   :alt: GitHub Actions\n\n.. image:: https://codecov.io/gh/jazzband/django-pipeline/branch/master/graph/badge.svg\n   :target: https://codecov.io/gh/jazzband/django-pipeline\n   :alt: Coverage\n\n.. image:: https://readthedocs.org/projects/django-pipeline/badge/?version=latest\n    :alt: Documentation Status\n    :target: https://django-pipeline.readthedocs.io/en/latest/?badge=latest\n\n\nPipeline is an asset packaging library for Django, providing both CSS and\nJavaScript concatenation and compression, built-in JavaScript template support,\nand optional data-URI image and font embedding.\n\n.. image:: https://github.com/jazzband/django-pipeline/raw/master/img/django-pipeline.svg\n   :alt: Django Pipeline Overview\n\n\nInstallation\n------------\n\nTo install it, simply:\n\n.. code-block:: bash\n\n    pip install django-pipeline\n\n\nQuickstart\n----------\n\nPipeline compiles and compress your assets files from\n``STATICFILES_DIRS`` to your ``STATIC_ROOT`` when you run Django's\n``collectstatic`` command.\n\nThese simple steps add Pipeline to your project to compile multiple ``.js`` and\n``.css`` file into one and compress them.\n\nAdd Pipeline to your installed apps:\n\n.. code-block:: python\n\n    # settings.py\n    INSTALLED_APPS = [\n        ...\n        'pipeline',\n    ]\n\n\nUse Pipeline specified classes for ``STATICFILES_FINDERS`` and ``STATICFILES_STORAGE``:\n\n.. code-block:: python\n\n    STATICFILES_STORAGE = 'pipeline.storage.PipelineManifestStorage'\n\n    STATICFILES_FINDERS = (\n        'django.contrib.staticfiles.finders.FileSystemFinder',\n        'django.contrib.staticfiles.finders.AppDirectoriesFinder',\n        'pipeline.finders.PipelineFinder',\n    )\n\n\nConfigure Pipeline:\n\n.. code-block:: python\n\n    # The folowing config merges CSS files(main.css, normalize.css)\n    # and JavaScript files(app.js, script.js) and compress them using\n    # `yuglify` into `css/styles.css` and `js/main.js`\n    # NOTE: Pipeline only works when DEBUG is False\n    PIPELINE = {\n        'STYLESHEETS': {\n            'css_files': {\n                'source_filenames': (\n                    'css/main.css',\n                    'css/normalize.css',\n                ),\n                'output_filename': 'css/styles.css',\n                'extra_context': {\n                    'media': 'screen,projection',\n                },\n            },\n        },\n        'JAVASCRIPT': {\n            'js_files': {\n                'source_filenames': (\n                    'js/app.js',\n                    'js/script.js',\n                ),\n                'output_filename': 'js/main.js',\n            }\n        }\n    }\n\n\nThen, you have to install compilers and compressors binary manually.\n\nFor example, you can install them using `NPM <https://www.npmjs.com/>`_\nand address them from ``node_modules`` directory in your project path:\n\n.. code-block:: python\n\n    PIPELINE.update({\n        'YUGLIFY_BINARY': path.join(BASE_DIR, 'node_modules/.bin/yuglify'),\n    })\n    # For a list of all supported compilers and compressors see documentation\n\n\nLoad static files in your template:\n\n.. code-block::\n\n    {% load pipeline %}\n    {% stylesheet 'css_files' %}\n    {% javascript 'js_files' %}\n\n\nDocumentation\n-------------\n\nFor documentation, usage, and examples, see:\nhttps://django-pipeline.readthedocs.io\n\n\nIssues\n------\nYou can report bugs and discuss features on the `issues page <https://github.com/jazzband/django-pipeline/issues>`_.\n\n\nChangelog\n---------\n\nSee `HISTORY.rst <https://github.com/jazzband/django-pipeline/blob/master/HISTORY.rst>`_.\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\t-rm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/django-pipeline.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/django-pipeline.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/django-pipeline\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-pipeline\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\tmake -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n"
  },
  {
    "path": "docs/compilers.rst",
    "content": ".. _ref-compilers:\n\n=========\nCompilers\n=========\n\nTypeScript compiler\n======================\n\nThe TypeScript compiler uses `TypeScript <https://www.typescriptlang.org/>`_\nto compile your TypeScript code to JavaScript.\n\nTo use it add this to your ``PIPELINE['COMPILERS']`` ::\n\n  PIPELINE['COMPILERS'] = (\n    'pipeline.compilers.typescript.TypeScriptCompiler',\n  )\n\n``TYPE_SCRIPT_BINARY``\n---------------------------------\n\n  Command line to execute for TypeScript program.\n  You will most likely change this to the location of ``tsc`` on your system.\n\n  Defaults to ``'/usr/bin/env tsc'``.\n\n``TYPE_SCRIPT_ARGUMENTS``\n------------------------------------\n\n  Additional arguments to use when ``tsc`` is called.\n\n  Defaults to ``''``.\n\nCoffee Script compiler\n======================\n\nThe Coffee Script compiler uses `Coffee Script <http://jashkenas.github.com/coffeescript/>`_\nto compile your javascript.\n\nTo use it add this to your ``PIPELINE['COMPILERS']`` ::\n\n  PIPELINE['COMPILERS'] = (\n    'pipeline.compilers.coffee.CoffeeScriptCompiler',\n  )\n\n``COFFEE_SCRIPT_BINARY``\n---------------------------------\n\n  Command line to execute for coffee program.\n  You will most likely change this to the location of coffee on your system.\n\n  Defaults to ``'/usr/bin/env coffee'``.\n\n``COFFEE_SCRIPT_ARGUMENTS``\n------------------------------------\n\n  Additional arguments to use when coffee is called.\n\n  Defaults to ``''``.\n\nLive Script compiler\n======================\n\nThe LiveScript compiler uses `LiveScript <https://github.com/gkz/LiveScript>`_\nto compile your javascript.\n\nTo use it add this to your ``PIPELINE['COMPILERS']`` ::\n\n  PIPELINE['COMPILERS'] = (\n    'pipeline.compilers.livescript.LiveScriptCompiler',\n  )\n\n``LIVE_SCRIPT_BINARY``\n---------------------------------\n\n  Command line to execute for LiveScript program.\n  You will most likely change this to the location of lsc on your system.\n\n  Defaults to ``'/usr/bin/env lsc'``.\n\n``LIVE_SCRIPT_ARGUMENTS``\n------------------------------------\n\n  Additional arguments to use when lsc is called.\n\n  Defaults to ``''``.\n\nLESS compiler\n=============\n\nThe LESS compiler uses `LESS <http://lesscss.org/>`_\nto compile your stylesheets.\n\nTo use it add this to your ``PIPELINE['COMPILERS']`` ::\n\n  PIPELINE['COMPILERS'] = (\n    'pipeline.compilers.less.LessCompiler',\n  )\n\n``LESS_BINARY``\n------------------------\n\n  Command line to execute for lessc program.\n  You will most likely change this to the location of lessc on your system.\n\n  Defaults to ``'/usr/bin/env lessc'``.\n\n``LESS_ARGUMENTS``\n---------------------------\n\n  Additional arguments to use when lessc is called.\n\n  Defaults to ``''``.\n\nSASS compiler\n=============\n\nThe SASS compiler uses `SASS <http://sass-lang.com/>`_\nto compile your stylesheets.\n\nTo use it add this to your ``PIPELINE['COMPILERS']`` ::\n\n  PIPELINE['COMPILERS'] = (\n    'pipeline.compilers.sass.SASSCompiler',\n  )\n\n\n``SASS_BINARY``\n------------------------\n\n  Command line to execute for sass program.\n  You will most likely change this to the location of sass on your system.\n\n  Defaults to ``'/usr/bin/env sass'``.\n\n``SASS_ARGUMENTS``\n---------------------------\n\n  Additional arguments to use when sass is called.\n\n  Defaults to ``''``.\n\n\nStylus compiler\n===============\n\nThe Stylus compiler uses `Stylus <http://learnboost.github.com/stylus/>`_\nto compile your stylesheets.\n\nTo use it add this to your ``PIPELINE['COMPILERS']`` ::\n\n  PIPELINE['COMPILERS'] = (\n      'pipeline.compilers.stylus.StylusCompiler',\n  )\n\n\n``STYLUS_BINARY``\n--------------------------\n\n  Command line to execute for stylus program.\n  You will most likely change this to the location of stylus on your system.\n\n  Defaults to ``'/usr/bin/env stylus'``.\n\n``STYLUS_ARGUMENTS``\n-----------------------------\n\n  Additional arguments to use when stylus is called.\n\n  Defaults to ``''``.\n\n\nES6 compiler\n============\n\nThe ES6 compiler uses `Babel <https://babeljs.io>`_\nto convert ES6+ code into vanilla ES5.\n\nNote that for files to be transpiled properly they must have the file extension **.es6**\n\nTo use it add this to your ``PIPELINE['COMPILERS']`` ::\n\n  PIPELINE['COMPILERS'] = (\n      'pipeline.compilers.es6.ES6Compiler',\n  )\n  \n\n``BABEL_BINARY``\n--------------------------\n\n  Command line to execute for babel program.\n  You will most likely change this to the location of babel on your system.\n\n  Defaults to ``'/usr/bin/env babel'``.\n\n``BABEL_ARGUMENTS``\n-----------------------------\n\n  Additional arguments to use when babel is called.\n\n  Defaults to ``''``.\n\n\nWrite your own compiler class\n=============================\n\nYou can write your own compiler class, for example if you want to implement other types\nof compilers.\n\nTo do so, you just have to create a class that inherits from ``pipeline.compilers.CompilerBase``\nand implements ``match_file`` and ``compile_file`` when needed.\n\nFinally, specify it in the tuple of compilers ``PIPELINE['COMPILERS']`` in the settings.\n\nExample\n-------\n\nA custom compiler for an imaginary compiler called jam ::\n\n  from pipeline.compilers import CompilerBase\n\n  class JamCompiler(CompilerBase):\n    output_extension = 'js'\n\n    def match_file(self, filename):\n      return filename.endswith('.jam')\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n      if not outdated and not force:\n        return  # No need to recompiled file\n      return jam.compile(infile, outfile)\n\n\n3rd Party Compilers\n===================\n\nHere is an (in)complete list of 3rd party compilers that integrate with django-pipeline\n\nCompass (requires RubyGem)\n--------------------------\n\n:Creator:\n    `Mila Labs <https://github.com/mila-labs>`_\n:Description:\n    Compass compiler for django-pipeline using the original Ruby gem.\n:Link:\n    `https://github.com/mila-labs/django-pipeline-compass-rubygem`\n\nCompass (standalone)\n--------------------\n\n:Creator:\n    `Vitaly Babiy <https://github.com/vbabiy>`_\n:Description:\n    django-pipeline-compass is a compiler for `django-pipeline <https://github.com/jazzband/django-pipeline>`_. Making it really easy to use scss and compass with out requiring the compass gem.\n:Link:\n    `https://github.com/vbabiy/django-pipeline-compass`\n\nLibsass (standalone)\n--------------------\n\n:Creator:\n    `Johanderson Mogollon <https://github.com/sonic182>`_\n:Description:\n    libsasscompiler is a compiler for `django-pipeline <https://github.com/jazzband/django-pipeline>`_. Making it really easy to use scss/sass with the super fast libsass library.\n:Link:\n    `https://github.com/sonic182/libsasscompiler`\n"
  },
  {
    "path": "docs/compressors.rst",
    "content": ".. _ref-compressors:\n\n===========\nCompressors\n===========\n\n\nYuglify compressor\n==================\n\nThe Yuglify compressor uses `yuglify <http://github.com/yui/yuglify>`_\nfor compressing javascript and stylesheets.\n\nTo use it for your stylesheets add this to your ``PIPELINE['CSS_COMPRESSOR']`` ::\n\n  PIPELINE['CSS_COMPRESSOR'] = 'pipeline.compressors.yuglify.YuglifyCompressor'\n\nTo use it for your javascripts add this to your ``PIPELINE['JS_COMPRESSOR']`` ::\n\n  PIPELINE['JS_COMPRESSOR'] = 'pipeline.compressors.yuglify.YuglifyCompressor'\n\n\n``YUGLIFY_BINARY``\n---------------------------\n\n  Command line to execute for the Yuglify program.\n  You will most likely change this to the location of yuglify on your system.\n\n  Defaults to ``'/usr/bin/env yuglify'``.\n\n``YUGLIFY_CSS_ARGUMENTS``\n----------------------------------\n\n  Additional arguments to use when compressing CSS.\n\n  Defaults to ``'--terminal'``.\n\n``YUGLIFY_JS_ARGUMENTS``\n---------------------------------\n\n  Additional arguments to use when compressing JavaScript.\n\n  Defaults to ``'--terminal'``.\n\n\nYUI Compressor compressor\n=========================\n\nThe YUI compressor uses `yui-compressor <http://developer.yahoo.com/yui/compressor/>`_\nfor compressing javascript and stylesheets.\n\nTo use it for your stylesheets add this to your ``PIPELINE['CSS_COMPRESSOR']`` ::\n\n  PIPELINE['CSS_COMPRESSOR'] = 'pipeline.compressors.yui.YUICompressor'\n\nTo use it for your javascripts add this to your ``PIPELINE['JS_COMPRESSOR']`` ::\n\n  PIPELINE['JS_COMPRESSOR'] = 'pipeline.compressors.yui.YUICompressor'\n\n\n``YUI_BINARY``\n-----------------------\n\n  Command line to execute for the YUI program.\n  You will most likely change this to the location of yui-compressor on your system.\n\n  Defaults to ``'/usr/bin/env yuicompressor'``.\n\n.. warning::\n  Don't point to ``yuicompressor.jar`` directly, we expect to find a executable script.\n\n\n``YUI_CSS_ARGUMENTS``\n------------------------------\n\n  Additional arguments to use when compressing CSS.\n\n  Defaults to ``''``.\n\n``YUI_JS_ARGUMENTS``\n-----------------------------\n\n  Additional arguments to use when compressing JavaScript.\n\n  Defaults to ``''``.\n\n\nClosure Compiler compressor\n===========================\n\nThe Closure compressor uses `Google Closure Compiler <http://code.google.com/closure/compiler/>`_\nto compress javascripts.\n\nTo use it add this to your ``PIPELINE['JS_COMPRESSOR']`` ::\n\n  PIPELINE['JS_COMPRESSOR'] = 'pipeline.compressors.closure.ClosureCompressor'\n\n\n``CLOSURE_BINARY``\n---------------------------\n\n  Command line to execute for the Closure Compiler program.\n  You will most likely change this to the location of closure on your system.\n\n  Default to ``'/usr/bin/env closure'``\n\n.. warning::\n  Don't point to ``compiler.jar`` directly, we expect to find a executable script.\n\n\n``CLOSURE_ARGUMENTS``\n------------------------------\n\n  Additional arguments to use when closure is called.\n\n  Default to ``''``\n\n\nUglifyJS compressor\n===================\n\nThe UglifyJS compressor uses `UglifyJS <https://github.com/mishoo/UglifyJS2/>`_ to\ncompress javascripts.\n\nTo use it add this to your ``PIPELINE['JS_COMPRESSOR']`` ::\n\n  PIPELINE['JS_COMPRESSOR'] = 'pipeline.compressors.uglifyjs.UglifyJSCompressor'\n\n\n``UGLIFYJS_BINARY``\n----------------------------\n\n  Command line to execute for the UglifyJS program.\n  You will most likely change this to the location of uglifyjs on your system.\n\n  Defaults to ``'/usr/bin/env uglifyjs'``.\n\n``UGLIFYJS_ARGUMENTS``\n-------------------------------\n\n  Additional arguments to use when uglifyjs is called.\n\n  Default to ``''``\n\n\nJSMin compressor\n================\n\nThe jsmin compressor uses Douglas Crockford jsmin tool to\ncompress javascripts.\n\nTo use it add this to your ``PIPELINE['JS_COMPRESSOR']`` ::\n\n  PIPELINE['JS_COMPRESSOR'] = 'pipeline.compressors.jsmin.JSMinCompressor'\n\nInstall the jsmin library with your favorite Python package manager ::\n\n  pip install jsmin\n\n\nTerser compressor\n===================\n\n`Terser <https://github.com/terser/terser>`_ is a JavaScript parser and \nmangler/compressor toolkit for ES6+. It has been designed as a successor of\n``uglify-es`` and ``uglify-js``. The compressor works with ES5 and ES6 and \nregular ``.js`` file endings.\n\nTo use it add this to your ``PIPELINE['JS_COMPRESSOR']`` ::\n\n  PIPELINE['JS_COMPRESSOR'] = 'pipeline.compressors.terser.TerserCompressor'\n\n\n``TERSER_BINARY``\n----------------------------\n\n  Command line to execute for the terser program.\n  You will most likely change this to the location of terser on your system.\n\n  Defaults to ``'/usr/bin/env terser'``.\n\n``TERSER_ARGUMENTS``\n-------------------------------\n\n  Additional arguments to use when terser is called.\n\n  Default to ``'--compress'``\n\n\nCSSTidy compressor\n==================\n\nThe CSStidy compressor uses `CSStidy <http://csstidy.sourceforge.net/>`_ to compress\nstylesheets.\n\nTo us it for your stylesheets add this to your ``PIPELINE['CSS_COMPRESSOR']`` ::\n\n  PIPELINE['CSS_COMPRESSOR'] = 'pipeline.compressors.csstidy.CSSTidyCompressor'\n\n``CSSTIDY_BINARY``\n---------------------------\n\n  Command line to execute for csstidy program.\n  You will most likely change this to the location of csstidy on your system.\n\n  Defaults to ``'/usr/bin/env csstidy'``\n\n``CSSTIDY_ARGUMENTS``\n------------------------------\n\n  Additional arguments to use when csstidy is called.\n\n  Default to ``'--template=highest'``\n\n\nCSSMin compressor\n=================\n\nThe cssmin compressor uses the `cssmin <https://github.com/jbleuzen/node-cssmin>`_\ncommand to compress stylesheets. To use it, add this to your ``PIPELINE['CSS_COMPRESSOR']`` ::\n\n  PIPELINE['CSS_COMPRESSOR'] = 'pipeline.compressors.cssmin.CSSMinCompressor'\n\n``CSSMIN_BINARY``\n---------------------------\n\n  Command line to execute for cssmin program.\n  You will most likely change this to the location of cssmin on your system.\n\n  Defaults to ``'/usr/bin/env cssmin'``\n\n``CSSMIN_ARGUMENTS``\n------------------------------\n\n  Additional arguments to use when cssmin is called.\n\n  Default to ``''``\n\n\ncss-html-js-minify compressor\n=============================\n\nThe css-html-js-minify is full Python compressor using `css-html-js-minify <https://github.com/ciotto/css-html-js-minify>`_\nfor compressing javascript and stylesheets.\n\nTo use it for your stylesheets add this to your ``PIPELINE['CSS_COMPRESSOR']`` ::\n\n  PIPELINE['CSS_COMPRESSOR'] = 'pipeline.compressors.csshtmljsminify.CssHtmlJsMinifyCompressor'\n\nTo use it for your javascripts add this to your ``PIPELINE['JS_COMPRESSOR']`` ::\n\n  PIPELINE['JS_COMPRESSOR'] = 'pipeline.compressors.csshtmljsminify.CssHtmlJsMinifyCompressor'\n\nInstall the css-html-js-minify library with your favorite Python package manager ::\n\n  pip install css-html-js-minify\n\n\nNo-Op Compressors\n=================\n\nThe No-Op compressor don't perform any operation, when used, only concatenation occurs.\nThis is useful for debugging faulty concatenation due to poorly written javascript and other errors.\n\nTo use it, add this to your settings ::\n\n  PIPELINE['CSS_COMPRESSOR'] = 'pipeline.compressors.NoopCompressor'\n  PIPELINE['JS_COMPRESSOR'] = 'pipeline.compressors.NoopCompressor'\n\n\nWrite your own compressor class\n===============================\n\nYou can write your own compressor class, for example if you want to implement other types\nof compressors.\n\nTo do so, you just have to create a class that inherits from ``pipeline.compressors.CompressorBase``\nand implements ``compress_css`` and/or a ``compress_js`` when needed.\n\nFinally, add it to ``PIPELINE['CSS_COMPRESSOR']`` or\n``PIPELINE['JS_COMPRESSOR']`` settings (see :doc:`configuration` for more information).\n\nExample\n-------\n\nA custom compressor for an imaginary compressor called jam ::\n\n  from pipeline.compressors import CompressorBase\n\n  class JamCompressor(CompressorBase):\n    def compress_js(self, js):\n      return jam.compress(js)\n\n    def compress_css(self, css):\n      return jam.compress(css)\n\n\nAdd it to your settings ::\n\n  PIPELINE['CSS_COMPRESSOR'] = 'jam.compressors.JamCompressor'\n  PIPELINE['JS_COMPRESSOR'] = 'jam.compressors.JamCompressor'\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# Pipeline documentation build configuration file, created by\n# sphinx-quickstart on Sat Apr 30 17:47:55 2011.\n#\n# This file is execfile()d with the current directory set to its containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n# sys.path.insert(0, os.path.abspath('.'))\nfrom datetime import datetime\n\nfrom pipeline import __version__ as pipeline_version\n\n# -- General configuration -----------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = []\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix of source filenames.\nsource_suffix = \".rst\"\n\n# The encoding of source files.\n# source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# General information about the project.\nproject = \"django-pipeline\"\ncurrent_year = datetime.now().year\ncopyright = \"2011-{}, Timothée Peignier\".format(current_year)\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The full version, including alpha/beta/rc tags.\nrelease = pipeline_version\n# The short X.Y version.\nversion = \".\".join(release.split(\".\")[:2])\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n# language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n# today = ''\n# Else, today_fmt is used as the format for a strftime call.\n# today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = [\"_build\"]\n\n# The reST default role (used for this markup: `text`) to use for all documents.\n# default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n# add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n# add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n# show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = \"sphinx\"\n\n# A list of ignored prefixes for module index sorting.\n# modindex_common_prefix = []\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = \"default\"\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n# html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n# html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n# html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n# html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n# html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n# html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\n# html_static_path = ['_static']\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n# html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n# html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n# html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n# html_additional_pages = {}\n\n# If false, no module index is generated.\n# html_domain_indices = True\n\n# If false, no index is generated.\n# html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n# html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n# html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n# html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n# html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n# html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n# html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"django-pipelinedoc\"\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\n# The paper size ('letter' or 'a4').\n# latex_paper_size = 'letter'\n\n# The font size ('10pt', '11pt' or '12pt').\n# latex_font_size = '10pt'\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n    (\n        \"index\",\n        \"django-pipeline.tex\",\n        \"Pipeline Documentation\",\n        \"Timothée Peignier\",\n        \"manual\",\n    ),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n# latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n# latex_use_parts = False\n\n# If true, show page references after internal links.\n# latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n# latex_show_urls = False\n\n# Additional stuff for the LaTeX preamble.\n# latex_preamble = ''\n\n# Documents to append as an appendix to all manuals.\n# latex_appendices = []\n\n# If false, no module index is generated.\n# latex_domain_indices = True\n\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    (\"index\", \"django-pipeline\", \"Pipeline Documentation\", [\"Timothée Peignier\"], 1)\n]\n"
  },
  {
    "path": "docs/configuration.rst",
    "content": ".. _ref-configuration:\n\n=============\nConfiguration\n=============\n\n\nConfiguration and list of available settings for Pipeline. Pipeline settings are namespaced in a PIPELINE dictionary in your project settings, e.g.: ::\n\n  PIPELINE = {\n      'PIPELINE_ENABLED': True,\n      'JAVASCRIPT': {\n          'stats': {\n              'source_filenames': (\n                'js/jquery.js',\n                'js/d3.js',\n                'js/collections/*.js',\n                'js/application.js',\n              ),\n              'output_filename': 'js/stats.js',\n          }\n      }\n  }\n\n\nSpecifying files\n================\n\nYou specify groups of files to be compressed in your settings. You can use glob\nsyntax to select multiples files.\n\nThe basic syntax for specifying CSS/JavaScript groups files is ::\n\n  PIPELINE = {\n      'STYLESHEETS': {\n          'colors': {\n              'source_filenames': (\n                'css/core.css',\n                'css/colors/*.css',\n                'css/layers.css'\n              ),\n              'output_filename': 'css/colors.css',\n              'extra_context': {\n                  'media': 'screen,projection',\n              },\n          },\n      },\n      'JAVASCRIPT': {\n          'stats': {\n              'source_filenames': (\n                'js/jquery.js',\n                'js/d3.js',\n                'js/collections/*.js',\n                'js/application.js',\n              ),\n              'output_filename': 'js/stats.js',\n          }\n      }\n  }\n\nGroup options\n-------------\n\n``source_filenames``\n....................\n\n**Required**\n\nIs a tuple with the source files to be compressed.\nThe files are concatenated in the order specified in the tuple.\n\n\n``output_filename``\n...................\n\n**Required**\n\nIs the filename of the (to be) compressed file.\n\n``variant``\n...........\n\n**Optional**\n\nIs the variant you want to apply to your CSS. This allow you to embed images\nand fonts in CSS with data-URI.\nAllowed values are : ``None`` and ``datauri``.\n\nDefaults to ``None``.\n\n``template_name``\n.................\n\n**Optional**\n\nName of the template used to render ``<script>`` for js package or ``<link>`` for css package.\n\nDefaults to ``None``.\n\n``extra_context``\n.................\n\n**Optional**\n\nIs a dictionary of values to add to the template context,\nwhen generating the HTML for the HTML-tags with the templatetags.\n\nFor CSS, if you do not specify ``extra_context``/``media``, the default media in\nthe ``<link>`` output will be ``media=\"all\"``.\n\nFor JS, the default templates support the ``async`` and ``defer`` tag attributes which are controlled via ``extra_context``: ::\n\n  'extra_context': {\n      'async': True,\n  },\n\n``manifest``\n............\n\n**Optional**\n\nIndicate if you want this group to appear in your cache manifest.\n\nDefaults to ``True``.\n\n``compiler_options``\n....................\n\n**Optional**\n\nA dictionary passed to compiler's ``compile_file`` method as kwargs. None of default compilers use it currently. It's to be used by custom compilers in case they need some special parameters.\n\nDefaults to ``{}``.\n\n\nOther settings\n--------------\n\n``PIPELINE_ENABLED``\n....................\n\n``True`` if assets should be compressed, ``False`` if not.\n\nDefaults to ``not settings.DEBUG``.\n\n``PIPELINE_COLLECTOR_ENABLED``\n..............................\n\n``True`` if assets should be collected in develop , ``False`` if not.\n\nDefaults to ``True``\n\n.. note::\n\n  This only applies when ``PIPELINE_ENABLED`` is ``False``.\n\n``SHOW_ERRORS_INLINE``\n......................\n\n``True`` if errors compiling CSS/JavaScript files should be shown inline at\nthe top of the browser window, or ``False`` if they should trigger exceptions\n(the older behavior).\n\nThis only applies when compiling through the ``{% stylesheet %}`` or\n``{% javascript %}`` template tags. It won't impact ``collectstatic``.\n\nDefaults to ``settings.DEBUG``.\n\n``CSS_COMPRESSOR``\n..................\n\nCompressor class to be applied to CSS files.\n\nIf empty or ``None``, CSS files won't be compressed.\n\nDefaults to ``'pipeline.compressors.yuglify.YuglifyCompressor'``.\n\n``JS_COMPRESSOR``\n.................\n\nCompressor class to be applied to JavaScript files.\n\nIf empty or ``None``, JavaScript files won't be compressed.\n\nDefaults to ``'pipeline.compressors.yuglify.YuglifyCompressor'``\n\n.. note::\n\n  Please note that in order to use Yuglify compressor, you need to install Yuglify (see :doc:`installation` for more details).\n\n``TEMPLATE_NAMESPACE``\n......................\n\nObject name where all of your compiled templates will be added, from within your browser.\nTo access them with your own JavaScript namespace, change it to the object of your choice.\n\nDefaults to ``\"window.JST\"``\n\n\n``TEMPLATE_EXT``\n................\n\nThe extension for which Pipeline will consider the file as a Javascript template.\nTo use a different extension, like ``.mustache``, set this settings to ``.mustache``.\n\nDefaults to ``\".jst\"``\n\n``TEMPLATE_FUNC``\n.................\n\nJavaScript function that compiles your JavaScript templates.\nPipeline doesn't bundle a javascript template library, but the default\nsetting is to use the\n`underscore <http://documentcloud.github.com/underscore/>`_ template function.\n\nDefaults to ``\"_.template\"``\n\n``TEMPLATE_SEPARATOR``\n......................\n\nCharacter chain used by Pipeline as replacement for directory separator.\n\nDefaults to ``\"_\"``\n\n\n``MIMETYPES``\n.............\n\nTuple that match file extension with their corresponding mimetypes.\n\nDefaults to ::\n\n  (\n    ('text/coffeescript', '.coffee'),\n    ('text/less', '.less'),\n    ('text/javascript', '.js'),\n    ('text/x-sass', '.sass'),\n    ('text/x-scss', '.scss')\n  )\n\n.. warning::\n  If you support Internet Explorer version 8 and below, you should\n  declare javascript files as ``text/javascript``.\n\n\nEmbedding fonts and images\n==========================\n\nYou can embed fonts and images directly in your compiled css, using Data-URI in\nmodern browsers.\n\nTo do so, setup variant group options to the method you wish to use : ::\n\n  'STYLESHEETS' = {\n      'master': {\n          'source_filenames': (\n            'css/core.css',\n            'css/button/*.css',\n          ),\n          'output_filename': 'css/master.css',\n          'variant': 'datauri',\n      },\n  }\n\nImages and fonts are embedded following these rules :\n\n- If asset is under **32 kilobytes** to avoid rendering delay or not rendering\n  at all in Internet Explorer 8.\n- If asset path contains a directory named \"**embed**\".\n\nOverriding embedding settings\n-----------------------------\n\nYou can override these rules using the following settings:\n\n``EMBED_MAX_IMAGE_SIZE``\n........................\n\nSetting that controls the maximum image size (in bytes) to embed in CSS using Data-URIs.\nInternet Explorer 8 has issues with assets over 32 kilobytes.\n\nDefaults to ``32700``\n\n``EMBED_PATH``\n..............\n\nSetting the directory that an asset needs to be in so that it is embedded\n\nDefaults to ``r'[/]?embed/'``\n\n\nRewriting CSS urls\n==================\n\nIf the source CSS contains relative URLs (i.e. relative to current file),\nthose URLs will be converted to full relative path.\n\n\nWrapped javascript output\n=========================\n\nAll javascript output is wrapped in an anonymous function : ::\n\n  (function(){\n    //JS output...\n  })();\n\nThis safety wrapper, make it difficult to pollute the global namespace by accident and improve performance.\n\nYou can override this behavior by setting ``DISABLE_WRAPPER`` to ``True``. If you want to use your own wrapper, change\nthe ``JS_WRAPPER`` setting. For example: ::\n\n  JS_WRAPPER = \"(function(){stuff();%s})();\"\n"
  },
  {
    "path": "docs/index.rst",
    "content": "Introduction\n============\n\nPipeline is an asset packaging library for Django, providing\nboth CSS and JavaScript concatenation and compression, built-in JavaScript\ntemplate support, and optional data-URI image and font embedding.\n\nYou can report bugs and discuss features on the `issues page <https://github.com/jazzband/django-pipeline/issues>`_.\n\nYou can discuss features or ask questions on the IRC channel on freenode : `#django-pipeline <irc://irc.freenode.net/django-pipeline>`_\n\n\nTable Of Contents\n=================\n\n.. toctree::\n   :maxdepth: 2\n\n   installation\n   configuration\n   usage\n   compressors\n   compilers\n   templates\n   storages\n   signals\n   using\n\n\nIndices and tables\n==================\n\n* :ref:`search`\n"
  },
  {
    "path": "docs/installation.rst",
    "content": ".. _ref-installation:\n\n============\nInstallation\n============\n\n1. Either check out Pipeline from GitHub_ or to pull a release off PyPI_ ::\n\n       pip install django-pipeline\n\n\n2. Add 'pipeline' to your ``INSTALLED_APPS`` ::\n\n       INSTALLED_APPS = (\n           'pipeline',\n       )\n\n3. Use a pipeline storage for ``STATICFILES_STORAGE`` ::\n\n        STATICFILES_STORAGE = 'pipeline.storage.PipelineManifestStorage'\n\n4. Add the ``PipelineFinder`` to ``STATICFILES_FINDERS`` ::\n\n        STATICFILES_FINDERS = (\n            'django.contrib.staticfiles.finders.FileSystemFinder',\n            'django.contrib.staticfiles.finders.AppDirectoriesFinder',\n            'pipeline.finders.PipelineFinder',\n        )\n\n\n.. note::\n  You need to use ``Django>=1.11`` to be able to use this version of pipeline.\n\n.. _GitHub: http://github.com/jazzband/django-pipeline\n.. _PyPI: http://pypi.python.org/pypi/django-pipeline\n\nUpgrading from 1.3\n==================\n\nTo upgrade from pipeline 1.3, you will need to follow these steps:\n\n1. Update templates to use the new syntax\n\n    .. code-block:: python\n\n        {# pipeline<1.4 #}\n        {% load compressed %}\n        {% compressed_js 'group' %}\n        {% compressed_css 'group' %}\n\n    .. code-block:: python\n\n        {# pipeline>=1.4 #}\n        {% load pipeline %}\n        {% javascript 'group' %}\n        {% stylesheet 'group' %}\n\n2. Add the ``PipelineFinder`` to ``STATICFILES_FINDERS`` ::\n\n        STATICFILES_FINDERS = (\n            'django.contrib.staticfiles.finders.FileSystemFinder',\n            'django.contrib.staticfiles.finders.AppDirectoriesFinder',\n            'pipeline.finders.PipelineFinder',\n        )\n\n\nUpgrading from 1.5\n==================\n\nTo upgrade from pipeline 1.5, you will need update all your ``PIPELINE_*``\nsettings and move them under the new ``PIPELINE`` setting.\nSee :ref:`ref-configuration`.\n\nRecommendations\n===============\n\nPipeline's default CSS and JS compressor is Yuglify.\nYuglify wraps UglifyJS and cssmin, applying the default YUI configurations to them.\nIt can be downloaded from: https://github.com/yui/yuglify/.\n\nIf you do not install yuglify, make sure to disable the compressor in your settings.\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUILDDIR=_build\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\nif NOT \"%PAPER%\" == \"\" (\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\n)\n\nif \"%1\" == \"\" goto help\n\nif \"%1\" == \"help\" (\n\t:help\n\techo.Please use `make ^<target^>` where ^<target^> is one of\n\techo.  html       to make standalone HTML files\n\techo.  dirhtml    to make HTML files named index.html in directories\n\techo.  singlehtml to make a single large HTML file\n\techo.  pickle     to make pickle files\n\techo.  json       to make JSON files\n\techo.  htmlhelp   to make HTML files and a HTML help project\n\techo.  qthelp     to make HTML files and a qthelp project\n\techo.  devhelp    to make HTML files and a Devhelp project\n\techo.  epub       to make an epub\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\n\techo.  text       to make text files\n\techo.  man        to make manual pages\n\techo.  changes    to make an overview over all changed/added/deprecated items\n\techo.  linkcheck  to check all external links for integrity\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\n\tgoto end\n)\n\nif \"%1\" == \"clean\" (\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\n\tdel /q /s %BUILDDIR%\\*\n\tgoto end\n)\n\nif \"%1\" == \"html\" (\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\n\tgoto end\n)\n\nif \"%1\" == \"dirhtml\" (\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\n\tgoto end\n)\n\nif \"%1\" == \"singlehtml\" (\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\n\tgoto end\n)\n\nif \"%1\" == \"pickle\" (\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the pickle files.\n\tgoto end\n)\n\nif \"%1\" == \"json\" (\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the JSON files.\n\tgoto end\n)\n\nif \"%1\" == \"htmlhelp\" (\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run HTML Help Workshop with the ^\n.hhp project file in %BUILDDIR%/htmlhelp.\n\tgoto end\n)\n\nif \"%1\" == \"qthelp\" (\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\n.qhcp project file in %BUILDDIR%/qthelp, like this:\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\django-pipeline.qhcp\n\techo.To view the help file:\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\django-pipeline.ghc\n\tgoto end\n)\n\nif \"%1\" == \"devhelp\" (\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished.\n\tgoto end\n)\n\nif \"%1\" == \"epub\" (\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\n\tgoto end\n)\n\nif \"%1\" == \"latex\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"text\" (\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The text files are in %BUILDDIR%/text.\n\tgoto end\n)\n\nif \"%1\" == \"man\" (\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\n\tgoto end\n)\n\nif \"%1\" == \"changes\" (\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.The overview file is in %BUILDDIR%/changes.\n\tgoto end\n)\n\nif \"%1\" == \"linkcheck\" (\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Link check complete; look for any errors in the above output ^\nor in %BUILDDIR%/linkcheck/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"doctest\" (\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of doctests in the sources finished, look at the ^\nresults in %BUILDDIR%/doctest/output.txt.\n\tgoto end\n)\n\n:end\n"
  },
  {
    "path": "docs/signals.rst",
    "content": ".. _ref-signals:\n\n=======\nSignals\n=======\n\nList of all signals sent by pipeline.\n\ncss_compressed\n--------------\n\n**pipeline.signals.css_compressed**\n\n\tWhenever a css package is compressed, this signal is sent after the compression.\n\n\tArguments sent with this signal :\n\t\n\t\t:sender:\n\t\t\tThe ``Packager`` class that compressed the group.\n\t\t\n\t\t:package:\n\t\t\tThe package actually compressed.\n\n\njs_compressed\n--------------\n\n**pipeline.signals.js_compressed**\n\n\tWhenever a js package is compressed, this signal is sent after the compression.\n\t\n\tArguments sent with this signal :\n\t\n\t\t:sender:\n\t\t\tThe ``Packager`` class that compressed the group.\n\t\t\n\t\t:package:\n\t\t\tThe package actually compressed.\n"
  },
  {
    "path": "docs/storages.rst",
    "content": ".. _ref-storages:\n\n========\nStorages\n========\n\n\nUsing with staticfiles\n======================\n\nPipeline is providing a storage for `staticfiles app <https://docs.djangoproject.com/en/dev/howto/static-files/>`_,\nto use it configure ``STATICFILES_STORAGE`` like so ::\n\n  STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage'\n\nAnd if you want versioning use ::\n\n  STATICFILES_STORAGE = 'pipeline.storage.PipelineManifestStorage'\n\nThere is also non-packing storage available, that allows you to run ``collectstatic`` command\nwithout packaging your assets. Useful for production when you don't want to run compressor or compilers ::\n\n  STATICFILES_STORAGE = 'pipeline.storage.NonPackagingPipelineStorage'\n\nAlso available if you want versioning ::\n\n  STATICFILES_STORAGE = 'pipeline.storage.NonPackagingPipelineManifestStorage'\n\nIf you use staticfiles with ``DEBUG = False`` (i.e. for integration tests\nwith `Selenium <http://docs.seleniumhq.org/>`_) you should install the finder\nthat allows staticfiles to locate your outputted assets : ::\n\n  STATICFILES_FINDERS = (\n      'django.contrib.staticfiles.finders.FileSystemFinder',\n      'django.contrib.staticfiles.finders.AppDirectoriesFinder',\n      'pipeline.finders.PipelineFinder',\n  )\n\nIf you use ``PipelineCachedStorage`` you may also like the ``CachedFileFinder``,\nwhich allows you to use integration tests with cached file URLs.\n\nKeep in mind that ``PipelineCachedStorage`` is only available for Django versions\nbefore 3.1.\n\nIf you want to exclude Pipelinable content from your collected static files,\nyou can also use Pipeline's ``FileSystemFinder`` and ``AppDirectoriesFinder``.\nThese finders will also exclude `unwanted` content like READMEs, tests and\nexamples, which are particularly useful if you're collecting content from a\ntool like Bower. ::\n\n  STATICFILES_FINDERS = (\n      'pipeline.finders.FileSystemFinder',\n      'pipeline.finders.AppDirectoriesFinder',\n      'pipeline.finders.CachedFileFinder',\n      'pipeline.finders.PipelineFinder',\n  )\n\nGZIP compression\n================\n\nPipeline can also creates a gzipped version of your collected static files,\nso that you can avoid compressing them on the fly. ::\n\n  STATICFILES_STORAGE = 'your.app.GZIPCachedStorage'\n\nThe storage need to inherit from ``GZIPMixin``: ::\n\n  from django.contrib.staticfiles.storage import CachedStaticFilesStorage\n\n  from pipeline.storage import GZIPMixin\n\n\n  class GZIPCachedStorage(GZIPMixin, CachedStaticFilesStorage):\n      pass\n\n\nUsing with other storages\n=========================\n\nYou can also use your own custom storage, for example, if you want to use S3 for your assets : ::\n\n  STATICFILES_STORAGE = 'your.app.S3PipelineManifestStorage'\n\nYour storage only needs to inherit from ``PipelineMixin`` and ``ManifestFilesMixin`` or ``CachedFilesMixin``.\n\nIn Django 1.7+ you should use `ManifestFilesMixin <https://docs.djangoproject.com/en/1.7/ref/contrib/staticfiles/#manifeststaticfilesstorage>`_\nunless you don't have access to the local filesystem in which case you should use ``CachedFilesMixin``. ::\n\n  from django.contrib.staticfiles.storage import CachedFilesMixin, ManifestFilesMixin\n\n  from pipeline.storage import PipelineMixin\n\n  from storages.backends.s3boto import S3BotoStorage\n\n  class S3PipelineManifestStorage(PipelineMixin, ManifestFilesMixin, S3BotoStorage):\n      pass\n\n  class S3PipelineCachedStorage(PipelineMixin, CachedFilesMixin, S3BotoStorage):\n      pass\n\n\nUsing Pipeline with Bower\n=========================\n\n`Bower <http://bower.io/>`_ is a `package manager for the web` that allows\nyou to easily include frontend components with named versions. Integrating\nBower with Pipeline is straightforward.\n\nAdd your Bower directory to your ``STATICFILES_DIRS`` : ::\n\n  STATICFILES_DIRS = (\n    os.path.join(os.path.dirname(__file__), '..', 'bower_components'),\n  )\n\nThen process the relevant content through Pipeline : ::\n\n  PIPELINE['JAVASCRIPT'] = {\n    'components': {\n      'source_filenames': (\n        'jquery/jquery.js',\n        # you can choose to be specific to reduce your payload\n        'jquery-ui/ui/*.js',\n      ),\n      'output_filename': 'js/components.js',\n    },\n  }\n\n``pipeline.finders.FileSystemFinder`` will help you by excluding much of the\nextra content that Bower includes with its components, such as READMEs, tests\nand examples, while still including images, fonts, CSS fragments etc.\n"
  },
  {
    "path": "docs/templates.rst",
    "content": ".. _ref-templates:\n\n====================\nJavascript Templates\n====================\n\nPipeline allows you to use javascript templates along with your javascript views.\nTo use your javascript templates, just add them to your ``JAVASCRIPT`` group ::\n\n  PIPELINE['JAVASCRIPT'] = {\n    'application': {\n      'source_filenames': (\n        'js/application.js',\n        'js/templates/**/*.jst',\n      ),\n      'output_filename': 'js/application.js'\n    }\n  }\n\nFor example, if you have the following template ``js/templates/photo/detail.jst`` ::\n\n  <div class=\"photo\">\n   <img src=\"<%= src %>\" />\n   <div class=\"caption\">\n    <%= caption %>\n   </div>\n  </div>\n\nIt will be available from your javascript code via window.JST ::\n\n  JST.photo_detail({ src:\"images/baby-panda.jpg\", caption:\"A baby panda is born\" });\n\n\nConfiguration\n-------------\n\nTemplate function\n.................\n\nBy default, Pipeline uses a variant of `Micro Templating <http://ejohn.org/blog/javascript-micro-templating/>`_ to compile the templates, but you can choose your preferred JavaScript templating engine by changing ``PIPELINE['TEMPLATE_FUNC']`` ::\n\n  PIPELINE['TEMPLATE_FUNC'] = 'template'\n\nTemplate namespace\n..................\n\nYour templates are made available in a top-level object, by default ``window.JST``,\nbut you can choose your own via ``PIPELINE['TEMPLATE_NAMESPACE']`` ::\n\n  PIPELINE['TEMPLATE_NAMESPACE'] = 'window.Template'\n\n\nTemplate extension\n..................\n\nTemplates are detected by their extension, by default ``.jst``, but you can use\nyour own extension via ``PIPELINE['TEMPLATE_EXT']`` ::\n\n  PIPELINE['TEMPLATE_EXT'] = '.mustache'\n\nTemplate separator\n..................\n\nTemplates identifier are built using a replacement for directory separator,\nby default ``_``, but you specify your own separator via ``PIPELINE['TEMPLATE_SEPARATOR']`` ::\n\n  PIPELINE['TEMPLATE_SEPARATOR'] = '/'\n\n\nUsing it with your favorite template library\n--------------------------------------------\n\nMustache\n........\n\nTo use it with `Mustache <https://github.com/janl/mustache.js>`_ you will need\nsome extra javascript ::\n\n  Mustache.template = function(templateString) {\n    return function() {\n      if (arguments.length < 1) {\n        return templateString;\n      } else {\n        return Mustache.to_html(templateString, arguments[0], arguments[1]);\n      }\n    };\n  };\n\nAnd use these settings ::\n\n PIPELINE['TEMPLATE_EXT'] = '.mustache'\n PIPELINE['TEMPLATE_FUNC'] = 'Mustache.template'\n\nHandlebars\n..........\n\nTo use it with `Handlebars <http://handlebarsjs.com/>`_, use the following settings ::\n\n PIPELINE['TEMPLATE_EXT'] = '.handlebars'\n PIPELINE['TEMPLATE_FUNC'] = 'Handlebars.compile'\n PIPELINE['TEMPLATE_NAMESPACE'] = 'Handlebars.templates'\n\nEmber.js + Handlebars\n.....................\n\nTo use it with `Ember.js <http://emberjs.com/>`_, use the following settings ::\n\n PIPELINE['TEMPLATE_EXT'] = '.handlebars'\n PIPELINE['TEMPLATE_FUNC'] = 'Ember.Handlebars.compile'\n PIPELINE['TEMPLATE_NAMESPACE'] = 'window.Ember.TEMPLATES'\n PIPELINE['TEMPLATE_SEPARATOR'] = '/'\n\nPrototype\n.........\n\nTo use it with `Prototype <http://www.prototypejs.org/>`_, just setup your\n``PIPELINE['TEMPLATE_FUNC']`` ::\n\n  PIPELINE['TEMPLATE_FUNC'] = 'new Template'\n\n"
  },
  {
    "path": "docs/usage.rst",
    "content": ".. _ref-usage:\n\n=====\nUsage\n=====\n\nDescribes how to use Pipeline when it is installed and configured.\n\nTemplatetags\n============\n\nPipeline includes two template tags: ``stylesheet`` and ``javascript``,\nin a template library called ``pipeline``.\n\nThey are used to output the ``<link>`` and ``<script>``-tags for the\nspecified CSS/JavaScript-groups (as specified in the settings).\nThe first argument must be the name of the CSS/JavaScript group.\n\nWhen ``settings.DEBUG`` is set to ``True`` the use of these template tags will\nresult in a separate tag for each resource in a given group (i.e., the\ncombined, compressed files will not be used), in order to make local debugging\neasy. When ``settings.DEBUG`` is set to ``False`` the opposite is true. You can\noverride the default behavior by setting ``settings.PIPELINE['PIPELINE_ENABLED']``\nmanually. When set to ``True`` or ``False`` this enables or disables,\nrespectively, the usage of the combined, compressed file for each resource\ngroup. This can be useful, if you encounter errors in your compressed code that\ndon't occur in your uncompressed code and you want to debug them locally.\n\nIf you need to change the output of the HTML-tags generated from the templatetags,\nthis can be done by overriding the templates ``pipeline/css.html`` and ``pipeline/js.html``.\n\nExample\n-------\n\nIf you have specified the CSS-groups “colors” and “stats” and a JavaScript-group\nwith the name “scripts”, you would use the following code to output them all ::\n\n   {% load pipeline %}\n   {% stylesheet 'colors' %}\n   {% stylesheet 'stats' %}\n   {% javascript 'scripts' %}\n\n\nForm Media\n==========\n\nDjango forms and widgets can specify individual CSS or JavaScript files to\ninclude on a page by defining a ``Form.Media`` class with ``css`` and ``js``\nattributes.\n\nPipeline builds upon this by allowing packages to be listed in\n``css_packages`` and ``js_packages``. This is equivalent to manually including\nthese packages in a page's template using the template tags.\n\nTo use these, just have your form or widget's ``Media`` class inherit from\n``pipeline.forms.PipelineFormMedia`` and define ``css_packages`` and\n``js_packages``. You can also continue to reference individual CSS/JavaScript\nfiles using the original ``css``/``js`` attributes, if needed.\n\nNote that unlike the template tags, you cannot customize the HTML for\nreferencing these files. The ``pipeline/css.html`` and ``pipeline/js.html``\nfiles will not be used. Django takes care of generating the HTML for form and\nwidget media.\n\n\nExample\n-------\n\n.. code-block:: python\n\n    from django import forms\n    from pipeline.forms import PipelineFormMedia\n\n\n    class MyForm(forms.Form):\n        ...\n\n        class Media(PipelineFormMedia):\n            css_packages = {\n                'all': ('my-styles',)\n            }\n            js_packages = ('my-scripts',)\n            js = ('https://cdn.example.com/some-script.js',)\n\n\nCollect static\n==============\n\nPipeline integrates with staticfiles, you just need to setup ``STATICFILES_STORAGE`` to ::\n\n    STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage'\n\nThen when you run ``collectstatic`` command, your CSS and your javascripts will be compressed at the same time ::\n\n    $ python manage.py collectstatic\n\nCache-busting\n-------------\n\nPipeline 1.2+ no longer provides its own cache-busting URL support (using e.g. the ``PIPELINE_VERSIONING`` setting) but uses\nDjango's built-in staticfiles support for this. To set up cache-busting in conjunction with ``collectstatic`` as above, use ::\n\n    STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'\n\nThis will handle cache-busting just as ``staticfiles``'s built-in ``CachedStaticFilesStorage`` does.\n\nMiddleware\n==========\n\nTo enable HTML compression add ``pipeline.middleware.MinifyHTMLMiddleware``,\nto your ``MIDDLEWARE_CLASSES`` settings.\n\nEnsure that it comes after any middleware which modifies your HTML, like ``GZipMiddleware`` ::\n\n   MIDDLEWARE_CLASSES = (\n      'django.middleware.gzip.GZipMiddleware',\n      'pipeline.middleware.MinifyHTMLMiddleware',\n   )\n\n\nJinja\n=====\n\nPipeline also includes Jinja2 support and is used almost identically to the Django Template tags implementation.\nYou just need to pass ``pipeline.jinja2.PipelineExtension`` to your Jinja2 environment::\n\n\n    {\n        'BACKEND': 'django.template.backends.jinja2.Jinja2',\n        'DIRS': [],\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'environment': 'myproject.jinja2.environment',\n            'extensions': ['pipeline.jinja2.PipelineExtension']\n        }\n    }\n\n\nTemplates\n---------\n\nUnlike the Django template tag implementation the Jinja2 implementation uses different templates, so if you wish to override them please override pipeline/css.jinja and pipeline/js.jinja.\n"
  },
  {
    "path": "docs/using.rst",
    "content": ".. _ref-using:\n\n====================\nSites using Pipeline\n====================\n\nThe following sites are a partial list of people using Pipeline.\n\nAre you using pipeline and not being in this list? Drop us a line. \n\n20 Minutes\n----------\n\nFor their internal tools: http://www.20minutes.fr\n\nBorsala\n-------\n\nBorsala is the social investment platform. You can follow stock markets that are traded in Turkey: http://borsala.com\n\n\nCroisé dans le Métro\n--------------------\n\nFor their main and mobile website:\n\n* http://www.croisedanslemetro.com\n* http://m.croisedanslemetro.com\n\nTeachoo\n-----------------\n\nTeachoo uses pipeline for compressing all its static files - https://www.teachoo.com\n\nThe Molly Project\n-----------------\n\nMolly is a framework for the rapid development of information and service\nportals targeted at mobile internet devices: https://github.com/mollyproject/mollyproject\n\nIt powers the University of Oxford's mobile portal: http://m.ox.ac.uk/\n\nMozilla\n-------\n\n* `mozilla.org <https://mozilla.org>`_ (https://github.com/mozilla/bedrock)\n* `Mozilla Developer Network <https://developer.mozilla.org>`_ (https://github.com/mozilla/kuma)\n\nNovapost\n--------\n\nFor PeopleDoc suite products: http://www.people-doc.com/\n\nSophicware\n----------\n\nSophicware offers web hosting and DevOps as a service: http://sophicware.com\n\nUlule\n-----\n\nFor their main and forum website:\n\n* http://www.ulule.com\n* http://vox.ulule.com\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"django-pipeline-tests\",\n  \"private\": true,\n  \"version\": \"1.0.0\",\n  \"description\": \"Pipeline is an asset packaging library for Django.\",\n  \"author\": \"Timothée Peignier <timothee.peignier@tryphon.org>\",\n  \"license\": \"MIT\",\n  \"readmeFilename\": \"../README.rst\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/jazzband/django-pipeline.git\"\n  },\n  \"dependencies\": {\n    \"babel-cli\": \"latest\",\n    \"babel-preset-es2015\": \"latest\",\n    \"coffeescript\": \"latest\",\n    \"less\": \"latest\",\n    \"livescript\": \"latest\",\n    \"sass\": \"latest\",\n    \"stylus\": \"latest\",\n    \"cssmin\": \"latest\",\n    \"google-closure-compiler\": \"latest\",\n    \"terser\": \"latest\",\n    \"uglify-js\": \"latest\",\n    \"yuglify\": \"1.0.x\",\n    \"yuicompressor\": \"latest\",\n    \"typescript\": \"latest\"\n  }\n}\n"
  },
  {
    "path": "pipeline/__init__.py",
    "content": "PackageNotFoundError = None\nDistributionNotFound = None\ntry:\n    from importlib.metadata import PackageNotFoundError\n    from importlib.metadata import version as get_version\nexcept ImportError:\n    get_version = None\n    PackageNotFoundError = None\nif get_version is None:\n    try:\n        from pkg_resources import DistributionNotFound, get_distribution\n\n        def get_version(x):\n            return get_distribution(x).version\n\n    except ImportError:\n        get_version = None\n        DistributionNotFound = None\n        get_distribution = None\n\n__version__ = None\nif get_version is not None:\n    try:\n        __version__ = get_version(\"django-pipeline\")\n    except PackageNotFoundError:\n        pass\n    except DistributionNotFound:\n        pass\n"
  },
  {
    "path": "pipeline/collector.py",
    "content": "import os\n\nimport django\nfrom django.contrib.staticfiles import finders\nfrom django.contrib.staticfiles.storage import staticfiles_storage\n\nfrom pipeline.finders import PipelineFinder\n\n\nclass Collector:\n    request = None\n\n    def __init__(self, storage=None):\n        if storage is None:\n            storage = staticfiles_storage\n        self.storage = storage\n\n    def _get_modified_time(self, storage, prefixed_path):\n        if django.VERSION[:2] >= (1, 10):\n            return storage.get_modified_time(prefixed_path)\n        return storage.modified_time(prefixed_path)\n\n    def clear(self, path=\"\"):\n        dirs, files = self.storage.listdir(path)\n        for f in files:\n            fpath = os.path.join(path, f)\n            self.storage.delete(fpath)\n        for d in dirs:\n            self.clear(os.path.join(path, d))\n\n    def collect(self, request=None, files=[]):\n        if self.request and self.request is request:\n            return\n        self.request = request\n        found_files = {}\n        for finder in finders.get_finders():\n            # Ignore our finder to avoid looping\n            if isinstance(finder, PipelineFinder):\n                continue\n            for path, storage in finder.list([\"CVS\", \".*\", \"*~\"]):\n                # Prefix the relative path if the source storage contains it\n                if getattr(storage, \"prefix\", None):\n                    prefixed_path = os.path.join(storage.prefix, path)\n                else:\n                    prefixed_path = path\n\n                if prefixed_path not in found_files and (\n                    not files or prefixed_path in files\n                ):\n                    found_files[prefixed_path] = (storage, path)\n                    self.copy_file(path, prefixed_path, storage)\n\n                if files and len(files) == len(found_files):\n                    break\n\n        return found_files.keys()\n\n    def copy_file(self, path, prefixed_path, source_storage):\n        # Delete the target file if needed or break\n        if not self.delete_file(path, prefixed_path, source_storage):\n            return\n        # Finally start copying\n        with source_storage.open(path) as source_file:\n            self.storage.save(prefixed_path, source_file)\n\n    def delete_file(self, path, prefixed_path, source_storage):\n        if self.storage.exists(prefixed_path):\n            try:\n                # When was the target file modified last time?\n                target_last_modified = self._get_modified_time(\n                    self.storage,\n                    prefixed_path,\n                )\n            except (OSError, NotImplementedError, AttributeError):\n                # The storage doesn't support ``modified_time`` or failed\n                pass\n            else:\n                try:\n                    # When was the source file modified last time?\n                    source_last_modified = self._get_modified_time(source_storage, path)\n                except (OSError, NotImplementedError, AttributeError):\n                    pass\n                else:\n                    # Skip the file if the source file is younger\n                    # Avoid sub-second precision\n                    if target_last_modified.replace(\n                        microsecond=0\n                    ) >= source_last_modified.replace(microsecond=0):\n                        return False\n            # Then delete the existing file if really needed\n            self.storage.delete(prefixed_path)\n        return True\n\n\ndefault_collector = Collector()\n"
  },
  {
    "path": "pipeline/compilers/__init__.py",
    "content": "import os\nimport shutil\nimport subprocess\nfrom tempfile import NamedTemporaryFile\n\nfrom django.contrib.staticfiles import finders\nfrom django.contrib.staticfiles.storage import staticfiles_storage\nfrom django.core.files.base import ContentFile\nfrom django.utils.encoding import force_str\n\nfrom pipeline.conf import settings\nfrom pipeline.exceptions import CompilerError\nfrom pipeline.utils import set_std_streams_blocking, to_class\n\n\nclass Compiler:\n    def __init__(self, storage=None, verbose=False):\n        if storage is None:\n            storage = staticfiles_storage\n        self.storage = storage\n        self.verbose = verbose\n\n    @property\n    def compilers(self):\n        return [to_class(compiler) for compiler in settings.COMPILERS]\n\n    def compile(self, paths, compiler_options={}, force=False):\n        def _compile(input_path):\n            for compiler in self.compilers:\n                compiler = compiler(verbose=self.verbose, storage=self.storage)\n                if compiler.match_file(input_path):\n                    try:\n                        infile = self.storage.path(input_path)\n                    except NotImplementedError:\n                        infile = finders.find(input_path)\n                    project_infile = finders.find(input_path)\n                    outfile = compiler.output_path(infile, compiler.output_extension)\n                    outdated = compiler.is_outdated(project_infile, outfile)\n                    compiler.compile_file(\n                        project_infile,\n                        outfile,\n                        outdated=outdated,\n                        force=force,\n                        **compiler_options,\n                    )\n\n                    return compiler.output_path(input_path, compiler.output_extension)\n            else:\n                return input_path\n\n        try:\n            import multiprocessing  # noqa: PLC0415\n            from concurrent import futures  # noqa: PLC0415\n        except ImportError:\n            return list(map(_compile, paths))\n        else:\n            with futures.ThreadPoolExecutor(\n                max_workers=multiprocessing.cpu_count()\n            ) as executor:\n                return list(executor.map(_compile, paths))\n\n\nclass CompilerBase:\n    def __init__(self, verbose, storage):\n        self.verbose = verbose\n        self.storage = storage\n\n    def match_file(self, filename):\n        raise NotImplementedError\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        raise NotImplementedError\n\n    def save_file(self, path, content):\n        return self.storage.save(path, ContentFile(content))\n\n    def read_file(self, path):\n        file = self.storage.open(path, \"rb\")\n        content = file.read()\n        file.close()\n        return content\n\n    def output_path(self, path, extension):\n        path = os.path.splitext(path)\n        return \".\".join((path[0], extension))\n\n    def is_outdated(self, infile, outfile):\n        if not os.path.exists(outfile):\n            return True\n\n        try:\n            return os.path.getmtime(infile) > os.path.getmtime(outfile)\n        except OSError:\n            return True\n\n\nclass SubProcessCompiler(CompilerBase):\n    def execute_command(self, command, cwd=None, stdout_captured=None):\n        \"\"\"Execute a command at cwd, saving its normal output at\n        stdout_captured. Errors, defined as nonzero return code or a failure\n        to start execution, will raise a CompilerError exception with a\n        description of the cause. They do not write output.\n\n        This is file-system safe (any valid file names are allowed, even with\n        spaces or crazy characters) and OS agnostic (existing and future OSes\n        that Python supports should already work).\n\n        The only thing weird here is that any incoming command arg item may\n        itself be a tuple. This allows compiler implementations to look clean\n        while supporting historical string config settings and maintaining\n        backwards compatibility. Thus, we flatten one layer deep.\n         ((env, foocomp), infile, (-arg,)) -> (env, foocomp, infile, -arg)\n        \"\"\"\n        argument_list = []\n        for flattening_arg in command:\n            if isinstance(flattening_arg, (str,)):\n                argument_list.append(flattening_arg)\n            else:\n                argument_list.extend(flattening_arg)\n\n        # The first element in argument_list is the program that will be\n        # executed; if it is '', then a PermissionError will be raised.\n        # Thus empty arguments are filtered out from argument_list\n        argument_list = list(filter(None, argument_list))\n        stdout = None\n        try:\n            # We always catch stdout in a file, but we may not have a use for it.\n            temp_file_container = (\n                cwd or os.path.dirname(stdout_captured or \"\") or os.getcwd()\n            )\n            with NamedTemporaryFile(\n                \"wb\", delete=False, dir=temp_file_container\n            ) as stdout:\n                compiling = subprocess.Popen(\n                    argument_list, cwd=cwd, stdout=stdout, stderr=subprocess.PIPE\n                )\n                _, stderr = compiling.communicate()\n                set_std_streams_blocking()\n\n            if compiling.returncode != 0:\n                stdout_captured = None  # Don't save erroneous result.\n                raise CompilerError(\n                    f\"{argument_list!r} exit code {compiling.returncode}\\n{stderr}\",\n                    command=argument_list,\n                    error_output=force_str(stderr),\n                )\n\n            # User wants to see everything that happened.\n            if self.verbose:\n                with open(stdout.name, \"rb\") as out:\n                    print(out.read())\n                print(stderr)\n        except OSError as e:\n            stdout_captured = None  # Don't save erroneous result.\n            raise CompilerError(e, command=argument_list, error_output=str(e))\n        finally:\n            # Decide what to do with captured stdout.\n            if stdout:\n                if stdout_captured:\n                    shutil.move(\n                        stdout.name, os.path.join(cwd or os.curdir, stdout_captured)\n                    )\n                else:\n                    os.remove(stdout.name)\n"
  },
  {
    "path": "pipeline/compilers/coffee.py",
    "content": "from pipeline.compilers import SubProcessCompiler\nfrom pipeline.conf import settings\n\n\nclass CoffeeScriptCompiler(SubProcessCompiler):\n    output_extension = \"js\"\n\n    def match_file(self, path):\n        return path.endswith(\".coffee\") or path.endswith(\".litcoffee\")\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        if not outdated and not force:\n            return  # File doesn't need to be recompiled\n        command = (\n            settings.COFFEE_SCRIPT_BINARY,\n            \"-cp\",\n            settings.COFFEE_SCRIPT_ARGUMENTS,\n            infile,\n        )\n        return self.execute_command(command, stdout_captured=outfile)\n"
  },
  {
    "path": "pipeline/compilers/es6.py",
    "content": "from pipeline.compilers import SubProcessCompiler\nfrom pipeline.conf import settings\n\n\nclass ES6Compiler(SubProcessCompiler):\n    output_extension = \"js\"\n\n    def match_file(self, path):\n        return path.endswith(\".es6\")\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        if not outdated and not force:\n            return  # File doesn't need to be recompiled\n        command = (\n            settings.BABEL_BINARY,\n            settings.BABEL_ARGUMENTS,\n            infile,\n            \"-o\",\n            outfile,\n        )\n        return self.execute_command(command)\n"
  },
  {
    "path": "pipeline/compilers/less.py",
    "content": "from os.path import dirname\n\nfrom pipeline.compilers import SubProcessCompiler\nfrom pipeline.conf import settings\n\n\nclass LessCompiler(SubProcessCompiler):\n    output_extension = \"css\"\n\n    def match_file(self, filename):\n        return filename.endswith(\".less\")\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        # Pipe to file rather than provide outfile arg due to a bug in lessc\n        command = (\n            settings.LESS_BINARY,\n            settings.LESS_ARGUMENTS,\n            infile,\n        )\n        return self.execute_command(\n            command, cwd=dirname(infile), stdout_captured=outfile\n        )\n"
  },
  {
    "path": "pipeline/compilers/livescript.py",
    "content": "from pipeline.compilers import SubProcessCompiler\nfrom pipeline.conf import settings\n\n\nclass LiveScriptCompiler(SubProcessCompiler):\n    output_extension = \"js\"\n\n    def match_file(self, path):\n        return path.endswith(\".ls\")\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        if not outdated and not force:\n            return  # File doesn't need to be recompiled\n        command = (\n            settings.LIVE_SCRIPT_BINARY,\n            \"-cp\",\n            settings.LIVE_SCRIPT_ARGUMENTS,\n            infile,\n        )\n        return self.execute_command(command, stdout_captured=outfile)\n"
  },
  {
    "path": "pipeline/compilers/sass.py",
    "content": "from os.path import dirname\n\nfrom pipeline.compilers import SubProcessCompiler\nfrom pipeline.conf import settings\n\n\nclass SASSCompiler(SubProcessCompiler):\n    output_extension = \"css\"\n\n    def match_file(self, filename):\n        return filename.endswith((\".scss\", \".sass\"))\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        command = (settings.SASS_BINARY, settings.SASS_ARGUMENTS, infile, outfile)\n        return self.execute_command(command, cwd=dirname(infile))\n"
  },
  {
    "path": "pipeline/compilers/stylus.py",
    "content": "from os.path import dirname\n\nfrom pipeline.compilers import SubProcessCompiler\nfrom pipeline.conf import settings\n\n\nclass StylusCompiler(SubProcessCompiler):\n    output_extension = \"css\"\n\n    def match_file(self, filename):\n        return filename.endswith(\".styl\")\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        command = (settings.STYLUS_BINARY, settings.STYLUS_ARGUMENTS, infile)\n        return self.execute_command(command, cwd=dirname(infile))\n"
  },
  {
    "path": "pipeline/compilers/typescript.py",
    "content": "from pipeline.compilers import SubProcessCompiler\nfrom pipeline.conf import settings\n\n\nclass TypeScriptCompiler(SubProcessCompiler):\n    output_extension = \"js\"\n\n    def match_file(self, path):\n        return path.endswith(\".ts\")\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        if not outdated and not force:\n            return\n        command = (\n            settings.TYPE_SCRIPT_BINARY,\n            settings.TYPE_SCRIPT_ARGUMENTS,\n            infile,\n            \"--outFile\",\n            outfile,\n        )\n        return self.execute_command(command)\n"
  },
  {
    "path": "pipeline/compressors/__init__.py",
    "content": "from __future__ import annotations\n\nimport base64\nimport os\nimport posixpath\nimport re\nimport subprocess\nimport warnings\nfrom collections.abc import Iterator, Sequence\nfrom itertools import takewhile\n\nfrom django.contrib.staticfiles.storage import staticfiles_storage\nfrom django.utils.encoding import force_str, smart_bytes\n\nfrom pipeline.conf import settings\nfrom pipeline.exceptions import CompressorError\nfrom pipeline.utils import relpath, set_std_streams_blocking, to_class\n\n# Regex matching url(...), url('...'), and url(\"...\") patterns.\n#\n# Replacements will preserve the quotes and any whitespace contained within\n# the pattern, transforming only the filename.\n#\n# Verbose and documented, to ease future maintenance.\n_CSS_URL_REWRITE_PATH_RE_STR = r\"\"\"\n    (?P<url_prefix>\n      url\\(                 # The opening `url(`.\n      (?P<url_quote>['\"]?)  # Optional quote (' or \").\n      \\s*\n    )\n    (?P<url_path>.*?)       # The path to capture.\n    (?P<url_suffix>\n      (?P=url_quote)        # The quote found earlier, if any.\n      \\s*\n      \\)                    # The end `)`, completing `url(...)`.\n    )\n\"\"\"\n\n\n# Regex matching `//@ sourceMappingURL=...` and variants.\n#\n# This will capture sourceMappingURL and sourceURL keywords, both\n# `//@` and `//#` variants, and both `//` and `/* ... */` comment types.\n#\n# Verbose and documented, to ease future maintenance.\n_SOURCEMAP_REWRITE_PATH_RE_STR = r\"\"\"\n    (?P<sourcemap_prefix>\n      /(?:/|(?P<sourcemap_mlcomment>\\*))  # Opening comment (`//#`, `//@`,\n      [#@]\\s+                             # `/*@`, `/*#`).\n      source(?:Mapping)?URL=              # The sourcemap indicator.\n      \\s*\n    )\n    (?P<sourcemap_path>.*?)               # The path to capture.\n    (?P<sourcemap_suffix>\n      \\s*\n      (?(sourcemap_mlcomment)\\*/\\s*)      # End comment (`*/`)\n    )\n    $                                     # The line should now end.\n\"\"\"\n\n\n# Implementation of the above regexes, for CSS and JavaScript.\nCSS_REWRITE_PATH_RE = re.compile(\n    f\"{_CSS_URL_REWRITE_PATH_RE_STR}|{_SOURCEMAP_REWRITE_PATH_RE_STR}\", re.X | re.M\n)\nJS_REWRITE_PATH_RE = re.compile(_SOURCEMAP_REWRITE_PATH_RE_STR, re.X | re.M)\n\n\nURL_REPLACER = re.compile(r\"\"\"url\\(__EMBED__(.+?)(\\?\\d+)?\\)\"\"\")\nNON_REWRITABLE_URL = re.compile(r\"^(#|http:|https:|data:|//)\")\n\nDEFAULT_TEMPLATE_FUNC = \"template\"\nTEMPLATE_FUNC = r\"\"\"var template = function(str){var fn = new Function('obj', 'var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push(\\''+str.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\").replace(/<%=([\\s\\S]+?)%>/g,function(match,code){return \"',\"+code.replace(/\\\\'/g, \"'\")+\",'\";}).replace(/<%([\\s\\S]+?)%>/g,function(match,code){return \"');\"+code.replace(/\\\\'/g, \"'\").replace(/[\\r\\n\\t]/g,' ')+\"__p.push('\";}).replace(/\\r/g,'\\\\r').replace(/\\n/g,'\\\\n').replace(/\\t/g,'\\\\t')+\"');}return __p.join('');\");return fn;};\"\"\"  # noqa\n\nMIME_TYPES = {\n    \".png\": \"image/png\",\n    \".jpg\": \"image/jpeg\",\n    \".jpeg\": \"image/jpeg\",\n    \".gif\": \"image/gif\",\n    \".tif\": \"image/tiff\",\n    \".tiff\": \"image/tiff\",\n    \".ttf\": \"font/truetype\",\n    \".otf\": \"font/opentype\",\n    \".woff\": \"font/woff\",\n}\nEMBED_EXTS = MIME_TYPES.keys()\nFONT_EXTS = [\".ttf\", \".otf\", \".woff\"]\n\n\nclass Compressor:\n    asset_contents = {}\n\n    def __init__(self, storage=None, verbose=False):\n        if storage is None:\n            storage = staticfiles_storage\n        self.storage = storage\n        self.verbose = verbose\n\n    @property\n    def js_compressor(self):\n        return to_class(settings.JS_COMPRESSOR)\n\n    @property\n    def css_compressor(self):\n        return to_class(settings.CSS_COMPRESSOR)\n\n    def compress_js(\n        self,\n        paths: Sequence[str],\n        templates: Sequence[str] | None = None,\n        *,\n        output_filename: str | None = None,\n        **kwargs,\n    ) -> str:\n        \"\"\"Concatenate and compress JS files\"\"\"\n        # Note how a semicolon is added between the two files to make sure that\n        # their behavior is not changed. '(expression1)\\n(expression2)' calls\n        # `expression1` with `expression2` as an argument! Superfluous\n        # semicolons are valid in JavaScript and will be removed by the\n        # minifier.\n        js = self.concatenate(\n            paths,\n            file_sep=\";\",\n            output_filename=output_filename,\n            rewrite_path_re=JS_REWRITE_PATH_RE,\n        )\n\n        if templates:\n            js = js + self.compile_templates(templates)\n\n        if not settings.DISABLE_WRAPPER:\n            js = settings.JS_WRAPPER % js\n\n        compressor = self.js_compressor\n        if compressor:\n            js = getattr(compressor(verbose=self.verbose), \"compress_js\")(js)\n\n        return js\n\n    def compress_css(self, paths, output_filename, variant=None, **kwargs):\n        \"\"\"Concatenate and compress CSS files\"\"\"\n        css = self.concatenate(\n            paths,\n            file_sep=\"\",\n            rewrite_path_re=CSS_REWRITE_PATH_RE,\n            output_filename=output_filename,\n            variant=variant,\n        )\n        compressor = self.css_compressor\n        if compressor:\n            css = getattr(compressor(verbose=self.verbose), \"compress_css\")(css)\n        if not variant:\n            return css\n        elif variant == \"datauri\":\n            return self.with_data_uri(css)\n        else:\n            raise CompressorError(f'\"{variant}\" is not a valid variant')\n\n    def compile_templates(self, paths):\n        compiled = []\n        if not paths:\n            return \"\"\n        namespace = settings.TEMPLATE_NAMESPACE\n        base_path = self.base_path(paths)\n        for path in paths:\n            contents = self.read_text(path)\n            contents = re.sub(\"\\r?\\n\", \"\\\\\\\\n\", contents)\n            contents = re.sub(\"'\", \"\\\\'\", contents)\n            name = self.template_name(path, base_path)\n            compiled.append(\n                \"{}['{}'] = {}('{}');\\n\".format(\n                    namespace, name, settings.TEMPLATE_FUNC, contents\n                )\n            )\n        if settings.TEMPLATE_FUNC == DEFAULT_TEMPLATE_FUNC:\n            compiler = TEMPLATE_FUNC\n        else:\n            compiler = \"\"\n        return \"\\n\".join(\n            [\n                \"{namespace} = {namespace} || {{}};\".format(namespace=namespace),\n                compiler,\n                \"\".join(compiled),\n            ]\n        )\n\n    def base_path(self, paths):\n        def names_equal(name):\n            return all(n == name[0] for n in name[1:])\n\n        directory_levels = zip(*[p.split(os.sep) for p in paths])\n        return os.sep.join(x[0] for x in takewhile(names_equal, directory_levels))\n\n    def template_name(self, path, base):\n        \"\"\"Find out the name of a JS template\"\"\"\n        if not base:\n            path = os.path.basename(path)\n        if path == base:\n            base = os.path.dirname(path)\n        name = re.sub(\n            r\"^{}[\\/\\\\]?(.*){}$\".format(\n                re.escape(base), re.escape(settings.TEMPLATE_EXT)\n            ),\n            r\"\\1\",\n            path,\n        )\n        return re.sub(r\"[\\/\\\\]\", settings.TEMPLATE_SEPARATOR, name)\n\n    def concatenate_and_rewrite(self, paths, output_filename, variant=None):\n        \"\"\"Concatenate together files and rewrite urls\"\"\"\n        warnings.warn(\n            \"Compressor.concatenate_and_rewrite() is deprecated. Please \"\n            \"call concatenate() instead.\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n\n        return self.concatenate(\n            paths=paths,\n            file_sep=\"\",\n            rewrite_path_re=CSS_REWRITE_PATH_RE,\n            output_filename=output_filename,\n            variant=variant,\n        )\n\n    def concatenate(\n        self,\n        paths: Sequence[str],\n        *,\n        file_sep: str | None = None,\n        output_filename: str | None = None,\n        rewrite_path_re: re.Pattern | None = None,\n        variant: str | None = None,\n    ) -> str:\n        \"\"\"Concatenate together a list of files.\n\n        The caller can specify a delimiter between files and any regexes\n        used to normalize relative paths. Path normalization is important for\n        ensuring that local resources or sourcemaps can be updated in time\n        for Django's static media post-processing phase.\n        \"\"\"\n\n        def _reconstruct(\n            m: re.Match,\n            source_path: str,\n        ) -> str:\n            groups = m.groupdict()\n            asset_path: str | None = None\n            prefix = \"\"\n            suffix = \"\"\n\n            for prefix in (\"sourcemap\", \"url\"):\n                asset_path = groups.get(f\"{prefix}_path\")\n\n                if asset_path is not None:\n                    asset_path = asset_path.strip()\n                    prefix, suffix = m.group(f\"{prefix}_prefix\", f\"{prefix}_suffix\")\n                    break\n\n            if asset_path is None:\n                # This is empty. Return the whole match as-is.\n                return m.group()\n\n            if asset_path and not NON_REWRITABLE_URL.match(asset_path):\n                asset_path = self.construct_asset_path(\n                    asset_path=asset_path,\n                    source_path=source_path,\n                    output_filename=output_filename,\n                    variant=variant,\n                )\n\n            return f\"{prefix}{asset_path}{suffix}\"\n\n        def _iter_files() -> Iterator[str]:\n            if not output_filename or not rewrite_path_re:\n                # This is legacy call, which does not support sourcemap-aware\n                # asset rewriting. Pipeline itself won't invoke this outside\n                # of tests, but it maybe important for third-parties who\n                # are specializing these classes.\n                warnings.warn(\n                    \"Compressor.concatenate() was called without passing \"\n                    \"rewrite_path_re_= or output_filename=. If you are \"\n                    \"specializing Compressor, please update your call \"\n                    \"to remain compatible with future changes.\",\n                    DeprecationWarning,\n                    stacklevel=3,\n                )\n\n                return (self.read_text(path) for path in paths)\n\n            # Now that we can attempt the modern support for concatenating\n            # files, handling rewriting of relative assets in the process.\n            return (\n                rewrite_path_re.sub(\n                    lambda m: _reconstruct(m, path), self.read_text(path)\n                )\n                for path in paths\n            )\n\n        if file_sep is None:\n            warnings.warn(\n                \"Compressor.concatenate() was called without passing \"\n                \"file_sep=. If you are specializing Compressor, please \"\n                \"update your call to remain compatible with future changes. \"\n                \"Defaulting to JavaScript behavior for \"\n                \"backwards-compatibility.\",\n                DeprecationWarning,\n                stacklevel=2,\n            )\n            file_sep = \";\"\n\n        return f\"\\n{file_sep}\".join(_iter_files())\n\n    def construct_asset_path(\n        self, asset_path, source_path, output_filename, variant=None\n    ):\n        \"\"\"Return a rewritten asset URL for a stylesheet or JavaScript file.\"\"\"\n        public_path = self.absolute_path(\n            asset_path,\n            os.path.dirname(source_path).replace(\"\\\\\", \"/\"),\n        )\n        if self.embeddable(public_path, variant):\n            return f\"__EMBED__{public_path}\"\n        if not posixpath.isabs(asset_path):\n            asset_path = self.relative_path(public_path, output_filename)\n        return asset_path\n\n    def embeddable(self, path, variant):\n        \"\"\"Is the asset embeddable ?\"\"\"\n        name, ext = os.path.splitext(path)\n        font = ext in FONT_EXTS\n        if not variant:\n            return False\n        if not (\n            re.search(settings.EMBED_PATH, path.replace(\"\\\\\", \"/\"))\n            and self.storage.exists(path)\n        ):\n            return False\n        if ext not in EMBED_EXTS:\n            return False\n        if not (\n            font or len(self.encoded_content(path)) < settings.EMBED_MAX_IMAGE_SIZE\n        ):\n            return False\n        return True\n\n    def with_data_uri(self, css):\n        def datauri(match):\n            path = match.group(1)\n            mime_type = self.mime_type(path)\n            data = self.encoded_content(path)\n            return f'url(\"data:{mime_type};charset=utf-8;base64,{data}\")'\n\n        return URL_REPLACER.sub(datauri, css)\n\n    def encoded_content(self, path):\n        \"\"\"Return the base64 encoded contents\"\"\"\n        if path in self.__class__.asset_contents:\n            return self.__class__.asset_contents[path]\n        data = self.read_bytes(path)\n        self.__class__.asset_contents[path] = force_str(base64.b64encode(data))\n        return self.__class__.asset_contents[path]\n\n    def mime_type(self, path):\n        \"\"\"Get mime-type from filename\"\"\"\n        name, ext = os.path.splitext(path)\n        return MIME_TYPES[ext]\n\n    def absolute_path(self, path, start):\n        \"\"\"\n        Return the absolute public path for an asset,\n        given the path of the stylesheet that contains it.\n        \"\"\"\n        if posixpath.isabs(path):\n            path = posixpath.join(staticfiles_storage.location, path)\n        else:\n            path = posixpath.join(start, path)\n        return posixpath.normpath(path)\n\n    def relative_path(self, absolute_path, output_filename):\n        \"\"\"Rewrite paths relative to the output stylesheet path\"\"\"\n        absolute_path = posixpath.join(settings.PIPELINE_ROOT, absolute_path)\n        output_path = posixpath.join(\n            settings.PIPELINE_ROOT, posixpath.dirname(output_filename)\n        )\n        return relpath(absolute_path, output_path)\n\n    def read_bytes(self, path):\n        \"\"\"Read file content in binary mode\"\"\"\n        file = staticfiles_storage.open(path)\n        content = file.read()\n        file.close()\n        return content\n\n    def read_text(self, path):\n        content = self.read_bytes(path)\n        return force_str(content)\n\n\nclass CompressorBase:\n    def __init__(self, verbose):\n        self.verbose = verbose\n\n    def filter_css(self, css):\n        raise NotImplementedError\n\n    def filter_js(self, js):\n        raise NotImplementedError\n\n\nclass SubProcessCompressor(CompressorBase):\n    def execute_command(self, command, content):\n        argument_list = []\n        for flattening_arg in command:\n            if isinstance(flattening_arg, (str,)):\n                argument_list.append(flattening_arg)\n            else:\n                argument_list.extend(flattening_arg)\n\n        pipe = subprocess.Popen(\n            argument_list,\n            stdout=subprocess.PIPE,\n            stdin=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n        )\n        if content:\n            content = smart_bytes(content)\n        stdout, stderr = pipe.communicate(content)\n        set_std_streams_blocking()\n        if stderr.strip() and pipe.returncode != 0:\n            raise CompressorError(force_str(stderr))\n        elif self.verbose:\n            print(force_str(stderr))\n        return force_str(stdout)\n\n\nclass NoopCompressor(CompressorBase):\n    def compress_js(self, js):\n        return js\n\n    def compress_css(self, css):\n        return css\n"
  },
  {
    "path": "pipeline/compressors/closure.py",
    "content": "from pipeline.compressors import SubProcessCompressor\nfrom pipeline.conf import settings\n\n\nclass ClosureCompressor(SubProcessCompressor):\n    def compress_js(self, js):\n        command = (settings.CLOSURE_BINARY, settings.CLOSURE_ARGUMENTS)\n        return self.execute_command(command, js)\n"
  },
  {
    "path": "pipeline/compressors/csshtmljsminify.py",
    "content": "from pipeline.compressors import CompressorBase\n\n\nclass CssHtmlJsMinifyCompressor(CompressorBase):\n    \"\"\"\n    CSS, HTML and JS compressor based on the Python library css-html-js-minify\n    (https://pypi.org/project/css-html-js-minify/).\n    \"\"\"\n\n    def compress_css(self, css):\n        from css_html_js_minify import css_minify  # noqa: PLC0415\n\n        return css_minify(css)\n\n    def compress_js(self, js):\n        from css_html_js_minify import js_minify  # noqa: PLC0415\n\n        return js_minify(js)\n"
  },
  {
    "path": "pipeline/compressors/cssmin.py",
    "content": "from pipeline.compressors import SubProcessCompressor\nfrom pipeline.conf import settings\n\n\nclass CSSMinCompressor(SubProcessCompressor):\n    def compress_css(self, css):\n        command = (settings.CSSMIN_BINARY, settings.CSSMIN_ARGUMENTS)\n        return self.execute_command(command, css)\n"
  },
  {
    "path": "pipeline/compressors/csstidy.py",
    "content": "from django.core.files import temp as tempfile\n\nfrom pipeline.compressors import SubProcessCompressor\nfrom pipeline.conf import settings\n\n\nclass CSSTidyCompressor(SubProcessCompressor):\n    def compress_css(self, css):\n        output_file = tempfile.NamedTemporaryFile(suffix=\".pipeline\")\n\n        command = (\n            settings.CSSTIDY_BINARY,\n            \"-\",\n            settings.CSSTIDY_ARGUMENTS,\n            output_file.name,\n        )\n        self.execute_command(command, css)\n\n        filtered_css = output_file.read()\n        output_file.close()\n        return filtered_css\n"
  },
  {
    "path": "pipeline/compressors/jsmin.py",
    "content": "from pipeline.compressors import CompressorBase\n\n\nclass JSMinCompressor(CompressorBase):\n    \"\"\"\n    JS compressor based on the Python library jsmin\n    (http://pypi.python.org/pypi/jsmin/).\n    \"\"\"\n\n    def compress_js(self, js):\n        from jsmin import jsmin  # noqa: PLC0415\n\n        return jsmin(js)\n"
  },
  {
    "path": "pipeline/compressors/terser.py",
    "content": "from pipeline.compressors import SubProcessCompressor\nfrom pipeline.conf import settings\n\n\nclass TerserCompressor(SubProcessCompressor):\n    def compress_js(self, js):\n        command = (settings.TERSER_BINARY, settings.TERSER_ARGUMENTS)\n        if self.verbose:\n            command += \" --verbose\"\n        return self.execute_command(command, js)\n"
  },
  {
    "path": "pipeline/compressors/uglifyjs.py",
    "content": "from pipeline.compressors import SubProcessCompressor\nfrom pipeline.conf import settings\n\n\nclass UglifyJSCompressor(SubProcessCompressor):\n    def compress_js(self, js):\n        command = (settings.UGLIFYJS_BINARY, settings.UGLIFYJS_ARGUMENTS)\n        if self.verbose:\n            command += \" --verbose\"\n        return self.execute_command(command, js)\n"
  },
  {
    "path": "pipeline/compressors/yuglify.py",
    "content": "from pipeline.compressors import SubProcessCompressor\nfrom pipeline.conf import settings\n\n\nclass YuglifyCompressor(SubProcessCompressor):\n    def compress_common(self, content, compress_type, arguments):\n        command = (settings.YUGLIFY_BINARY, f\"--type={compress_type}\", arguments)\n        return self.execute_command(command, content)\n\n    def compress_js(self, js):\n        return self.compress_common(js, \"js\", settings.YUGLIFY_JS_ARGUMENTS)\n\n    def compress_css(self, css):\n        return self.compress_common(css, \"css\", settings.YUGLIFY_CSS_ARGUMENTS)\n"
  },
  {
    "path": "pipeline/compressors/yui.py",
    "content": "from pipeline.compressors import SubProcessCompressor\nfrom pipeline.conf import settings\n\n\nclass YUICompressor(SubProcessCompressor):\n    def compress_common(self, content, compress_type, arguments):\n        command = (settings.YUI_BINARY, f\"--type={compress_type}\", arguments)\n        return self.execute_command(command, content)\n\n    def compress_js(self, js):\n        return self.compress_common(js, \"js\", settings.YUI_JS_ARGUMENTS)\n\n    def compress_css(self, css):\n        return self.compress_common(css, \"css\", settings.YUI_CSS_ARGUMENTS)\n"
  },
  {
    "path": "pipeline/conf.py",
    "content": "import os\nimport shlex\nfrom collections.abc import MutableMapping\n\nfrom django.conf import settings as _settings\nfrom django.core.signals import setting_changed\nfrom django.dispatch import receiver\n\nDEFAULTS = {\n    \"PIPELINE_ENABLED\": not _settings.DEBUG,\n    \"PIPELINE_COLLECTOR_ENABLED\": True,\n    \"PIPELINE_ROOT\": _settings.STATIC_ROOT,\n    \"PIPELINE_URL\": _settings.STATIC_URL,\n    \"SHOW_ERRORS_INLINE\": _settings.DEBUG,\n    \"CSS_COMPRESSOR\": \"pipeline.compressors.yuglify.YuglifyCompressor\",\n    \"JS_COMPRESSOR\": \"pipeline.compressors.yuglify.YuglifyCompressor\",\n    \"COMPILERS\": [],\n    \"STYLESHEETS\": {},\n    \"JAVASCRIPT\": {},\n    \"TEMPLATE_NAMESPACE\": \"window.JST\",\n    \"TEMPLATE_EXT\": \".jst\",\n    \"TEMPLATE_FUNC\": \"template\",\n    \"TEMPLATE_SEPARATOR\": \"_\",\n    \"DISABLE_WRAPPER\": False,\n    \"JS_WRAPPER\": \"(function() {\\n%s\\n}).call(this);\",\n    \"CSSTIDY_BINARY\": \"/usr/bin/env csstidy\",\n    \"CSSTIDY_ARGUMENTS\": \"--template=highest\",\n    \"YUGLIFY_BINARY\": \"/usr/bin/env yuglify\",\n    \"YUGLIFY_CSS_ARGUMENTS\": \"--terminal\",\n    \"YUGLIFY_JS_ARGUMENTS\": \"--terminal\",\n    \"YUI_BINARY\": \"/usr/bin/env yuicompressor\",\n    \"YUI_CSS_ARGUMENTS\": \"\",\n    \"YUI_JS_ARGUMENTS\": \"\",\n    \"CLOSURE_BINARY\": \"/usr/bin/env closure\",\n    \"CLOSURE_ARGUMENTS\": \"\",\n    \"UGLIFYJS_BINARY\": \"/usr/bin/env uglifyjs\",\n    \"UGLIFYJS_ARGUMENTS\": \"\",\n    \"TERSER_BINARY\": \"/usr/bin/env terser\",\n    \"TERSER_ARGUMENTS\": \"--compress\",\n    \"CSSMIN_BINARY\": \"/usr/bin/env cssmin\",\n    \"CSSMIN_ARGUMENTS\": \"\",\n    \"COFFEE_SCRIPT_BINARY\": \"/usr/bin/env coffee\",\n    \"COFFEE_SCRIPT_ARGUMENTS\": \"\",\n    \"BABEL_BINARY\": \"/usr/bin/env babel\",\n    \"BABEL_ARGUMENTS\": \"\",\n    \"LIVE_SCRIPT_BINARY\": \"/usr/bin/env lsc\",\n    \"LIVE_SCRIPT_ARGUMENTS\": \"\",\n    \"TYPE_SCRIPT_BINARY\": \"/usr/bin/env tsc\",\n    \"TYPE_SCRIPT_ARGUMENTS\": \"\",\n    \"SASS_BINARY\": \"/usr/bin/env sass\",\n    \"SASS_ARGUMENTS\": \"\",\n    \"STYLUS_BINARY\": \"/usr/bin/env stylus\",\n    \"STYLUS_ARGUMENTS\": \"\",\n    \"LESS_BINARY\": \"/usr/bin/env lessc\",\n    \"LESS_ARGUMENTS\": \"\",\n    \"MIMETYPES\": (\n        ((\"text/coffeescript\"), (\".coffee\")),\n        ((\"text/less\"), (\".less\")),\n        ((\"text/javascript\"), (\".js\")),\n        ((\"text/typescript\"), (\".ts\")),\n        ((\"text/x-sass\"), (\".sass\")),\n        ((\"text/x-scss\"), (\".scss\")),\n    ),\n    \"EMBED_MAX_IMAGE_SIZE\": 32700,\n    \"EMBED_PATH\": r\"[/]?embed/\",\n}\n\n\nclass PipelineSettings(MutableMapping):\n    \"\"\"\n    Container object for pipeline settings\n    \"\"\"\n\n    def __init__(self, wrapped_settings):\n        self.settings = DEFAULTS.copy()\n        self.settings.update(wrapped_settings)\n\n    def __getitem__(self, key):\n        value = self.settings[key]\n        if key.endswith((\"_BINARY\", \"_ARGUMENTS\")):\n            if isinstance(value, (str,)):\n                return tuple(shlex.split(value, posix=(os.name == \"posix\")))\n            return tuple(value)\n        return value\n\n    def __setitem__(self, key, value):\n        self.settings[key] = value\n\n    def __delitem__(self, key):\n        del self.store[key]\n\n    def __iter__(self):\n        return iter(self.settings)\n\n    def __len__(self):\n        return len(self.settings)\n\n    def __getattr__(self, name):\n        return self.__getitem__(name)\n\n\nsettings = PipelineSettings(_settings.PIPELINE)\n\n\n@receiver(setting_changed)\ndef reload_settings(**kwargs):\n    if kwargs[\"setting\"] == \"PIPELINE\":\n        settings.update(kwargs[\"value\"])\n"
  },
  {
    "path": "pipeline/exceptions.py",
    "content": "class PipelineException(Exception):\n    pass\n\n\nclass PackageNotFound(PipelineException):\n    pass\n\n\nclass CompilerError(PipelineException):\n    def __init__(self, msg, command=None, error_output=None):\n        super().__init__(msg)\n\n        self.command = command\n        self.error_output = error_output.strip()\n\n\nclass CompressorError(PipelineException):\n    pass\n"
  },
  {
    "path": "pipeline/finders.py",
    "content": "from itertools import chain\nfrom os.path import normpath\n\nfrom django.contrib.staticfiles.finders import (\n    AppDirectoriesFinder as DjangoAppDirectoriesFinder,\n)\nfrom django.contrib.staticfiles.finders import BaseFinder, BaseStorageFinder\nfrom django.contrib.staticfiles.finders import (\n    FileSystemFinder as DjangoFileSystemFinder,\n)\nfrom django.contrib.staticfiles.finders import find\nfrom django.contrib.staticfiles.storage import staticfiles_storage\nfrom django.utils._os import safe_join\n\nfrom pipeline.conf import settings\n\n\nclass PipelineFinder(BaseStorageFinder):\n    storage = staticfiles_storage\n\n    def find(self, path, **kwargs):\n        if not settings.PIPELINE_ENABLED:\n            return super().find(path, **kwargs)\n        else:\n            return []\n\n    def list(self, ignore_patterns):\n        return []\n\n\nclass ManifestFinder(BaseFinder):\n    def find(self, path, **kwargs):\n        \"\"\"\n        Looks for files in PIPELINE.STYLESHEETS and PIPELINE.JAVASCRIPT\n        \"\"\"\n        matches = []\n        for elem in chain(settings.STYLESHEETS.values(), settings.JAVASCRIPT.values()):\n            if normpath(elem[\"output_filename\"]) == normpath(path):\n                match = safe_join(settings.PIPELINE_ROOT, path)\n                if not kwargs.get(\"find_all\", kwargs.get(\"all\", False)):\n                    return match\n                matches.append(match)\n        return matches\n\n    def list(self, *args):\n        return []\n\n\nclass CachedFileFinder(BaseFinder):\n    def find(self, path, **kwargs):\n        \"\"\"\n        Work out the uncached name of the file and look that up instead\n        \"\"\"\n        try:\n            start, _, extn = path.rsplit(\".\", 2)\n        except ValueError:\n            return []\n        path = \".\".join((start, extn))\n        return find(path, **kwargs) or []\n\n    def list(self, *args):\n        return []\n\n\nclass PatternFilterMixin:\n    ignore_patterns = []\n\n    def get_ignored_patterns(self):\n        return list(set(self.ignore_patterns))\n\n    def list(self, ignore_patterns):\n        if ignore_patterns:\n            ignore_patterns = ignore_patterns + self.get_ignored_patterns()\n        return super().list(ignore_patterns)\n\n\nclass AppDirectoriesFinder(PatternFilterMixin, DjangoAppDirectoriesFinder):\n    \"\"\"\n    Like AppDirectoriesFinder, but doesn't return any additional ignored\n    patterns.\n\n    This allows us to concentrate/compress our components without dragging\n    the raw versions in via collectstatic.\n    \"\"\"\n\n    ignore_patterns = [\n        \"*.js\",\n        \"*.css\",\n        \"*.less\",\n        \"*.scss\",\n        \"*.styl\",\n    ]\n\n\nclass FileSystemFinder(PatternFilterMixin, DjangoFileSystemFinder):\n    \"\"\"\n    Like FileSystemFinder, but doesn't return any additional ignored patterns\n\n    This allows us to concentrate/compress our components without dragging\n    the raw versions in too.\n    \"\"\"\n\n    ignore_patterns = [\n        \"*.js\",\n        \"*.css\",\n        \"*.less\",\n        \"*.scss\",\n        \"*.styl\",\n        \"*.sh\",\n        \"*.html\",\n        \"*.md\",\n        \"*.markdown\",\n        \"*.php\",\n        \"*.txt\",\n        \"README*\",\n        \"LICENSE*\",\n        \"*examples*\",\n        \"*test*\",\n        \"*bin*\",\n        \"*samples*\",\n        \"*docs*\",\n        \"*build*\",\n        \"*demo*\",\n        \"Makefile*\",\n        \"Gemfile*\",\n        \"node_modules\",\n    ]\n"
  },
  {
    "path": "pipeline/forms.py",
    "content": "\"\"\"Support for referencing Pipeline packages in forms and widgets.\"\"\"\n\nfrom django.contrib.staticfiles.storage import staticfiles_storage\nfrom django.utils.functional import cached_property\n\nfrom .collector import default_collector\nfrom .conf import settings\nfrom .packager import Packager\n\n\nclass PipelineFormMediaProperty:\n    \"\"\"A property that converts Pipeline packages to lists of files.\n\n    This is used behind the scenes for any Media classes that subclass\n    :py:class:`PipelineFormMedia`. When accessed, it converts any Pipeline\n    packages into lists of media files and returns or forwards on lookups to\n    that list.\n    \"\"\"\n\n    def __init__(self, get_media_files_func, media_cls, extra_files):\n        \"\"\"Initialize the property.\n\n        Args:\n            get_media_files_func (callable):\n                The function to call to generate the media files.\n\n            media_cls (type):\n                The Media class owning the property.\n\n            extra_files (object):\n                Files listed in the original ``css`` or ``js`` attribute on\n                the Media class.\n        \"\"\"\n        self._get_media_files_func = get_media_files_func\n        self._media_cls = media_cls\n        self._extra_files = extra_files\n\n    @cached_property\n    def _media_files(self):\n        \"\"\"The media files represented by the property.\"\"\"\n        return self._get_media_files_func(self._media_cls, self._extra_files)\n\n    def __get__(self, *args, **kwargs):\n        \"\"\"Return the media files when accessed as an attribute.\n\n        This is called when accessing the attribute directly through the\n        Media class (for example, ``Media.css``). It returns the media files\n        directly.\n\n        Args:\n            *args (tuple, unused):\n                Unused positional arguments.\n\n            **kwargs (dict, unused):\n                Unused keyword arguments.\n\n        Returns:\n            object:\n            The list or dictionary containing the media files definition.\n        \"\"\"\n        return self._media_files\n\n    def __getattr__(self, attr_name):\n        \"\"\"Return an attribute on the media files definition.\n\n        This is called when accessing an attribute that doesn't otherwise\n        exist in the property's dictionary. The call is forwarded onto the\n        media files definition.\n\n        Args:\n            attr_name (unicode):\n                The attribute name.\n\n        Returns:\n            object:\n            The attribute value.\n\n        Raises:\n            AttributeError:\n                An attribute with this name could not be found.\n        \"\"\"\n        return getattr(self._media_files, attr_name)\n\n    def __iter__(self):\n        \"\"\"Iterate through the media files definition.\n\n        This is called when attempting to iterate over this property. It\n        iterates over the media files definition instead.\n\n        Yields:\n            object:\n            Each entry in the media files definition.\n        \"\"\"\n        return iter(self._media_files)\n\n\nclass PipelineFormMediaMetaClass(type):\n    \"\"\"Metaclass for the PipelineFormMedia class.\n\n    This is responsible for converting CSS/JavaScript packages defined in\n    Pipeline into lists of files to include on a page. It handles access to the\n    :py:attr:`css` and :py:attr:`js` attributes on the class, generating a\n    list of files to return based on the Pipelined packages and individual\n    files listed in the :py:attr:`css`/:py:attr:`css_packages` or\n    :py:attr:`js`/:py:attr:`js_packages` attributes.\n    \"\"\"\n\n    def __new__(cls, name, bases, attrs):\n        \"\"\"Construct the class.\n\n        Args:\n            name (bytes):\n                The name of the class.\n\n            bases (tuple):\n                The base classes for the class.\n\n            attrs (dict):\n                The attributes going into the class.\n\n        Returns:\n            type:\n            The new class.\n        \"\"\"\n        new_class = super().__new__(cls, name, bases, attrs)\n\n        # If we define any packages, we'll need to use our special\n        # PipelineFormMediaProperty class. We use this instead of intercepting\n        # in __getattribute__ because Django does not access them through\n        # normal property access. Instead, grabs the Media class's __dict__\n        # and accesses them from there. By using these special properties, we\n        # can handle direct access (Media.css) and dictionary-based access\n        # (Media.__dict__['css']).\n        if \"css_packages\" in attrs:\n            new_class.css = PipelineFormMediaProperty(\n                cls._get_css_files, new_class, attrs.get(\"css\") or {}\n            )\n\n        if \"js_packages\" in attrs:\n            new_class.js = PipelineFormMediaProperty(\n                cls._get_js_files, new_class, attrs.get(\"js\") or []\n            )\n\n        return new_class\n\n    def _get_css_files(cls, extra_files):\n        \"\"\"Return all CSS files from the Media class.\n\n        Args:\n            extra_files (dict):\n                The contents of the Media class's original :py:attr:`css`\n                attribute, if one was provided.\n\n        Returns:\n            dict:\n            The CSS media types and files to return for the :py:attr:`css`\n            attribute.\n        \"\"\"\n        packager = Packager()\n        css_packages = getattr(cls, \"css_packages\", {})\n\n        return {\n            media_target: cls._get_media_files(\n                packager=packager,\n                media_packages=media_packages,\n                media_type=\"css\",\n                extra_files=extra_files.get(media_target, []),\n            )\n            for media_target, media_packages in css_packages.items()\n        }\n\n    def _get_js_files(cls, extra_files):\n        \"\"\"Return all JavaScript files from the Media class.\n\n        Args:\n            extra_files (list):\n                The contents of the Media class's original :py:attr:`js`\n                attribute, if one was provided.\n\n        Returns:\n            list:\n            The JavaScript files to return for the :py:attr:`js` attribute.\n        \"\"\"\n        return cls._get_media_files(\n            packager=Packager(),\n            media_packages=getattr(cls, \"js_packages\", {}),\n            media_type=\"js\",\n            extra_files=extra_files,\n        )\n\n    def _get_media_files(cls, packager, media_packages, media_type, extra_files):\n        \"\"\"Return source or output media files for a list of packages.\n\n        This will go through the media files belonging to the provided list\n        of packages referenced in a Media class and return the output files\n        (if Pipeline is enabled) or the source files (if not enabled).\n\n        Args:\n            packager (pipeline.packager.Packager):\n                The packager responsible for media compilation for this type\n                of package.\n\n            media_packages (list of unicode):\n                The list of media packages referenced in Media to compile or\n                return.\n\n            extra_files (list of unicode):\n                The list of extra files to include in the result. This would\n                be the list stored in the Media class's original :py:attr:`css`\n                or :py:attr:`js` attributes.\n\n        Returns:\n            list:\n            The list of media files for the given packages.\n        \"\"\"\n        source_files = list(extra_files)\n\n        if not settings.PIPELINE_ENABLED and settings.PIPELINE_COLLECTOR_ENABLED:\n            default_collector.collect()\n\n        for media_package in media_packages:\n            package = packager.package_for(media_type, media_package)\n\n            if settings.PIPELINE_ENABLED:\n                source_files.append(staticfiles_storage.url(package.output_filename))\n            else:\n                source_files += packager.compile(package.paths)\n\n        return source_files\n\n\nclass PipelineFormMedia(metaclass=PipelineFormMediaMetaClass):\n    \"\"\"Base class for form or widget Media classes that use Pipeline packages.\n\n    Forms or widgets that need custom CSS or JavaScript media on a page can\n    define a standard :py:class:`Media` class that subclasses this class,\n    listing the CSS or JavaScript packages in :py:attr:`css_packages` and\n    :py:attr:`js_packages` attributes. These are formatted the same as the\n    standard :py:attr:`css` and :py:attr:`js` attributes, but reference\n    Pipeline package names instead of individual source files.\n\n    If Pipeline is enabled, these will expand to the output files for the\n    packages. Otherwise, these will expand to the list of source files for the\n    packages.\n\n    Subclasses can also continue to define :py:attr:`css` and :py:attr:`js`\n    attributes, which will be returned along with the other output/source\n    files.\n\n    Example:\n\n        from django import forms\n        from pipeline.forms import PipelineFormMedia\n\n\n        class MyForm(forms.Media):\n            ...\n\n            class Media(PipelineFormMedia):\n                css_packages = {\n                    'all': ('my-form-styles-package',\n                            'other-form-styles-package'),\n                    'print': ('my-form-print-styles-package',),\n                }\n\n                js_packages = ('my-form-scripts-package',)\n                js = ('some-file.js',)\n    \"\"\"\n"
  },
  {
    "path": "pipeline/glob.py",
    "content": "import fnmatch\nimport os\nimport re\n\nfrom django.contrib.staticfiles.storage import staticfiles_storage\n\n__all__ = [\"glob\", \"iglob\"]\n\n\ndef glob(pathname):\n    \"\"\"Return a list of paths matching a pathname pattern.\n\n    The pattern may contain simple shell-style wildcards a la fnmatch.\n\n    \"\"\"\n    return sorted(list(iglob(pathname)))\n\n\ndef iglob(pathname):\n    \"\"\"Return an iterator which yields the paths matching a pathname pattern.\n\n    The pattern may contain simple shell-style wildcards a la fnmatch.\n\n    \"\"\"\n    if not has_magic(pathname):\n        yield pathname\n        return\n    dirname, basename = os.path.split(pathname)\n    if not dirname:\n        for name in glob1(dirname, basename):\n            yield name\n        return\n    if has_magic(dirname):\n        dirs = iglob(dirname)\n    else:\n        dirs = [dirname]\n    if has_magic(basename):\n        glob_in_dir = glob1\n    else:\n        glob_in_dir = glob0\n    for dirname in dirs:\n        for name in glob_in_dir(dirname, basename):\n            yield os.path.join(dirname, name)\n\n\n# These 2 helper functions non-recursively glob inside a literal directory.\n# They return a list of basenames. `glob1` accepts a pattern while `glob0`\n# takes a literal basename (so it only has to check for its existence).\n\n\ndef glob1(dirname, pattern):\n    try:\n        directories, files = staticfiles_storage.listdir(dirname)\n        names = directories + files\n    except Exception:\n        # We are not sure that dirname is a real directory\n        # and storage implementations are really exotic.\n        return []\n    if pattern[0] != \".\":\n        names = [x for x in names if x[0] != \".\"]\n    return fnmatch.filter(names, pattern)\n\n\ndef glob0(dirname, basename):\n    if staticfiles_storage.exists(os.path.join(dirname, basename)):\n        return [basename]\n    return []\n\n\nmagic_check = re.compile(\"[*?[]\")\n\n\ndef has_magic(s):\n    return magic_check.search(s) is not None\n"
  },
  {
    "path": "pipeline/jinja2/__init__.py",
    "content": "from django.contrib.staticfiles.storage import staticfiles_storage\nfrom jinja2 import TemplateSyntaxError, nodes\nfrom jinja2.ext import Extension\n\nfrom ..packager import PackageNotFound\nfrom ..templatetags.pipeline import PipelineMixin\nfrom ..utils import guess_type\n\n\nclass PipelineExtension(PipelineMixin, Extension):\n    tags = {\"stylesheet\", \"javascript\"}\n\n    def parse(self, parser):\n        tag = next(parser.stream)\n\n        package_name = parser.parse_expression()\n        if not package_name:\n            raise TemplateSyntaxError(\"Bad package name\", tag.lineno)\n\n        args = [package_name]\n        if tag.value == \"stylesheet\":\n            return nodes.CallBlock(\n                self.call_method(\"package_css\", args), [], [], []\n            ).set_lineno(tag.lineno)\n\n        if tag.value == \"javascript\":\n            return nodes.CallBlock(\n                self.call_method(\"package_js\", args), [], [], []\n            ).set_lineno(tag.lineno)\n\n        return []\n\n    def package_css(self, package_name, *args, **kwargs):\n        try:\n            package = self.package_for(package_name, \"css\")\n        except PackageNotFound:\n            # fail silently, do not return anything if an invalid group is specified\n            return \"\"\n        return self.render_compressed(package, package_name, \"css\")\n\n    def render_css(self, package, path):\n        template_name = package.template_name or \"pipeline/css.jinja\"\n        context = package.extra_context\n        context.update(\n            {\"type\": guess_type(path, \"text/css\"), \"url\": staticfiles_storage.url(path)}\n        )\n        template = self.environment.get_template(template_name)\n        return template.render(context)\n\n    def render_individual_css(self, package, paths, **kwargs):\n        tags = [self.render_css(package, path) for path in paths]\n        return \"\\n\".join(tags)\n\n    def package_js(self, package_name, *args, **kwargs):\n        try:\n            package = self.package_for(package_name, \"js\")\n        except PackageNotFound:\n            # fail silently, do not return anything if an invalid group is specified\n            return \"\"\n        return self.render_compressed(package, package_name, \"js\")\n\n    def render_js(self, package, path):\n        template_name = package.template_name or \"pipeline/js.jinja\"\n        context = package.extra_context\n        context.update(\n            {\n                \"type\": guess_type(path, \"text/javascript\"),\n                \"url\": staticfiles_storage.url(path),\n            }\n        )\n        template = self.environment.get_template(template_name)\n        return template.render(context)\n\n    def render_inline(self, package, js):\n        context = package.extra_context\n        context.update({\"source\": js})\n        template = self.environment.get_template(\"pipeline/inline_js.jinja\")\n        return template.render(context)\n\n    def render_individual_js(self, package, paths, templates=None):\n        tags = [self.render_js(package, js) for js in paths]\n        if templates:\n            tags.append(self.render_inline(package, templates))\n        return \"\\n\".join(tags)\n"
  },
  {
    "path": "pipeline/jinja2/pipeline/css.jinja",
    "content": "<link href=\"{{ url }}\" rel=\"stylesheet\" type=\"{{ type }}\"{% if media %} media=\"{{ media }}\"{% endif %}{% if charset %} charset=\"{{ charset }}\"{% endif %} />\n"
  },
  {
    "path": "pipeline/jinja2/pipeline/inline_js.jinja",
    "content": "<script{% if async %} async{% endif %}{% if defer %} defer{% endif %} type=\"text/javascript\">\n  {{ source|safe }}\n</script>\n"
  },
  {
    "path": "pipeline/jinja2/pipeline/js.jinja",
    "content": "<script{% if async %} async{% endif %}{% if defer %} defer{% endif %} type=\"{{ type }}\" src=\"{{ url }}\" charset=\"utf-8\"></script>\n"
  },
  {
    "path": "pipeline/middleware.py",
    "content": "from django.core.exceptions import MiddlewareNotUsed\nfrom django.utils.deprecation import MiddlewareMixin\nfrom django.utils.encoding import DjangoUnicodeDecodeError\nfrom django.utils.html import strip_spaces_between_tags as minify_html\n\nfrom pipeline.conf import settings\n\n\nclass MinifyHTMLMiddleware(MiddlewareMixin):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if not settings.PIPELINE_ENABLED:\n            raise MiddlewareNotUsed\n\n    def process_response(self, request, response):\n        if (\n            response.has_header(\"Content-Type\")\n            and \"text/html\" in response[\"Content-Type\"]\n        ):\n            try:\n                response.content = minify_html(response.content.decode(\"utf-8\").strip())\n                response[\"Content-Length\"] = str(len(response.content))\n            except DjangoUnicodeDecodeError:\n                pass\n        return response\n"
  },
  {
    "path": "pipeline/packager.py",
    "content": "from django.contrib.staticfiles.finders import find, get_finders\nfrom django.contrib.staticfiles.storage import staticfiles_storage\nfrom django.core.files.base import ContentFile\nfrom django.utils.encoding import smart_bytes\n\nfrom pipeline.compilers import Compiler\nfrom pipeline.compressors import Compressor\nfrom pipeline.conf import settings\nfrom pipeline.exceptions import PackageNotFound\nfrom pipeline.glob import glob\nfrom pipeline.signals import css_compressed, js_compressed\n\n\nclass Package:\n    def __init__(self, config):\n        self.config = config\n        self._sources = []\n\n    @property\n    def sources(self):\n        if not self._sources:\n            paths = []\n            for pattern in self.config.get(\"source_filenames\", []):\n                for path in glob(pattern):\n                    if path not in paths and find(path):\n                        paths.append(str(path))\n            self._sources = paths\n        return self._sources\n\n    @property\n    def paths(self):\n        return [\n            path for path in self.sources if not path.endswith(settings.TEMPLATE_EXT)\n        ]\n\n    @property\n    def templates(self):\n        return [path for path in self.sources if path.endswith(settings.TEMPLATE_EXT)]\n\n    @property\n    def output_filename(self):\n        return self.config.get(\"output_filename\")\n\n    @property\n    def extra_context(self):\n        return self.config.get(\"extra_context\", {})\n\n    @property\n    def template_name(self):\n        return self.config.get(\"template_name\")\n\n    @property\n    def variant(self):\n        return self.config.get(\"variant\")\n\n    @property\n    def manifest(self):\n        return self.config.get(\"manifest\", True)\n\n    @property\n    def compiler_options(self):\n        return self.config.get(\"compiler_options\", {})\n\n\nclass Packager:\n    def __init__(\n        self,\n        storage=None,\n        verbose=False,\n        css_packages=None,\n        js_packages=None,\n    ):\n        if storage is None:\n            storage = staticfiles_storage\n        self.storage = storage\n        self.verbose = verbose\n        self.compressor = Compressor(storage=storage, verbose=verbose)\n        self.compiler = Compiler(storage=storage, verbose=verbose)\n        if css_packages is None:\n            css_packages = settings.STYLESHEETS\n        if js_packages is None:\n            js_packages = settings.JAVASCRIPT\n        self.packages = {\n            \"css\": self.create_packages(css_packages),\n            \"js\": self.create_packages(js_packages),\n        }\n\n    def package_for(self, kind, package_name):\n        try:\n            return self.packages[kind][package_name]\n        except KeyError:\n            raise PackageNotFound(\n                \"No corresponding package for {} package name : {}\".format(\n                    kind, package_name\n                )\n            )\n\n    def individual_url(self, filename):\n        return self.storage.url(filename)\n\n    def pack_stylesheets(self, package, **kwargs):\n        return self.pack(\n            package,\n            self.compressor.compress_css,\n            css_compressed,\n            output_filename=package.output_filename,\n            variant=package.variant,\n            **kwargs,\n        )\n\n    def compile(self, paths, compiler_options={}, force=False):\n        paths = self.compiler.compile(\n            paths,\n            compiler_options=compiler_options,\n            force=force,\n        )\n        for path in paths:\n            if not self.storage.exists(path):\n                if self.verbose:\n                    e = (\n                        \"Compiled file '%s' cannot be \"\n                        \"found with packager's storage. Locating it.\"\n                    )\n                    print(e % path)\n\n                source_storage = self.find_source_storage(path)\n                if source_storage is not None:\n                    with source_storage.open(path) as source_file:\n                        if self.verbose:\n                            print(f\"Saving: {path}\")\n                        self.storage.save(path, source_file)\n                else:\n                    raise OSError(f\"File does not exist: {path}\")\n        return paths\n\n    def pack(self, package, compress, signal, **kwargs):\n        output_filename = package.output_filename\n        if self.verbose:\n            print(f\"Saving: {output_filename}\")\n        paths = self.compile(\n            package.paths,\n            compiler_options=package.compiler_options,\n            force=True,\n        )\n        content = compress(paths, **kwargs)\n        self.save_file(output_filename, content)\n        signal.send(sender=self, package=package, **kwargs)\n        return output_filename\n\n    def pack_javascripts(self, package, **kwargs):\n        return self.pack(\n            package,\n            self.compressor.compress_js,\n            js_compressed,\n            output_filename=package.output_filename,\n            templates=package.templates,\n            **kwargs,\n        )\n\n    def pack_templates(self, package):\n        return self.compressor.compile_templates(package.templates)\n\n    def save_file(self, path, content):\n        return self.storage.save(path, ContentFile(smart_bytes(content)))\n\n    def find_source_storage(self, path):\n        for finder in get_finders():\n            for short_path, storage in finder.list(\"\"):\n                if short_path == path:\n                    if self.verbose:\n                        print(f\"Found storage: {str(self.storage)}\")\n                    return storage\n        return None\n\n    def create_packages(self, config):\n        packages = {}\n        if not config:\n            return packages\n        for name in config:\n            packages[name] = Package(config[name])\n        return packages\n"
  },
  {
    "path": "pipeline/signals.py",
    "content": "from django.dispatch import Signal\n\ncss_compressed = Signal()\njs_compressed = Signal()\n"
  },
  {
    "path": "pipeline/storage.py",
    "content": "import gzip\nfrom io import BytesIO\n\nfrom django import get_version as django_version\nfrom django.contrib.staticfiles.storage import (\n    ManifestStaticFilesStorage,\n    StaticFilesStorage,\n)\nfrom django.contrib.staticfiles.utils import matches_patterns\nfrom django.core.files.base import File\n\n_CACHED_STATIC_FILES_STORAGE_AVAILABLE = django_version() < \"3.1\"\n\nif _CACHED_STATIC_FILES_STORAGE_AVAILABLE:\n    from django.contrib.staticfiles.storage import CachedStaticFilesStorage\n\n\nclass PipelineMixin:\n    packing = True\n\n    def post_process(self, paths, dry_run=False, **options):\n        if dry_run:\n            return\n\n        from pipeline.packager import Packager  # noqa: PLC0415\n\n        packager = Packager(storage=self)\n        for package_name in packager.packages[\"css\"]:\n            package = packager.package_for(\"css\", package_name)\n            output_file = package.output_filename\n            if self.packing:\n                packager.pack_stylesheets(package)\n            paths[output_file] = (self, output_file)\n            yield output_file, output_file, True\n        for package_name in packager.packages[\"js\"]:\n            package = packager.package_for(\"js\", package_name)\n            output_file = package.output_filename\n            if self.packing:\n                packager.pack_javascripts(package)\n            paths[output_file] = (self, output_file)\n            yield output_file, output_file, True\n\n        super_class = super()\n        if hasattr(super_class, \"post_process\"):\n            yield from super_class.post_process(paths.copy(), dry_run, **options)\n\n    def get_available_name(self, name, max_length=None):\n        if self.exists(name):\n            self.delete(name)\n        return name\n\n\nclass GZIPMixin:\n    gzip_patterns = (\"*.css\", \"*.js\")\n\n    def _compress(self, original_file):\n        content = BytesIO()\n        gzip_file = gzip.GzipFile(mode=\"wb\", fileobj=content)\n        gzip_file.write(original_file.read())\n        gzip_file.close()\n        content.seek(0)\n        return File(content)\n\n    def post_process(self, paths, dry_run=False, **options):\n        super_class = super()\n        if hasattr(super_class, \"post_process\"):\n            for name, hashed_name, processed in super_class.post_process(\n                paths.copy(), dry_run, **options\n            ):\n                if hashed_name != name:\n                    paths[hashed_name] = (self, hashed_name)\n                yield name, hashed_name, processed\n\n        if dry_run:\n            return\n\n        for path in paths:\n            if path:\n                if not matches_patterns(path, self.gzip_patterns):\n                    continue\n                original_file = self.open(path)\n                gzipped_path = f\"{path}.gz\"\n                if self.exists(gzipped_path):\n                    self.delete(gzipped_path)\n                gzipped_file = self._compress(original_file)\n                gzipped_path = self.save(gzipped_path, gzipped_file)\n                yield gzipped_path, gzipped_path, True\n\n\nclass NonPackagingMixin:\n    packing = False\n\n\nclass PipelineStorage(PipelineMixin, StaticFilesStorage):\n    pass\n\n\nclass NonPackagingPipelineStorage(NonPackagingMixin, PipelineStorage):\n    pass\n\n\nif _CACHED_STATIC_FILES_STORAGE_AVAILABLE:\n\n    class PipelineCachedStorage(PipelineMixin, CachedStaticFilesStorage):\n        # Deprecated since Django 2.2\n        # Removed in Django 3.1\n        pass\n\n    class NonPackagingPipelineCachedStorage(NonPackagingMixin, PipelineCachedStorage):\n        # Deprecated since Django 2.2\n        # Removed in Django 3.1\n        pass\n\n\nclass PipelineManifestStorage(PipelineMixin, ManifestStaticFilesStorage):\n    pass\n\n\nclass NonPackagingPipelineManifestStorage(\n    NonPackagingMixin, ManifestStaticFilesStorage\n):\n    pass\n"
  },
  {
    "path": "pipeline/templates/pipeline/compile_error.html",
    "content": "<div id=\"django-pipeline-error-{{package_name}}\" class=\"django-pipeline-error\"\n     style=\"display: none; border: 2px #DD0000 solid; margin: 1em; padding: 1em; background: white; color: black;\">\n <h1>Error compiling {{package_type}} package \"{{package_name}}\"</h1>\n <p><strong>Command:</strong></p>\n <pre style=\"white-space: pre-wrap;\">{{command}}</pre>\n <p><strong>Errors:</strong></p>\n <pre style=\"white-space: pre-wrap;\">{{errors}}</pre>\n</div>\n\n<script>\ndocument.addEventListener('readystatechange', function() {\n    var el,\n        container;\n\n    if (document.readyState !== 'interactive') {\n        return;\n    }\n\n    el = document.getElementById('django-pipeline-error-{{package_name}}');\n    container = document.getElementById('django-pipeline-errors');\n\n    if (!container) {\n        container = document.createElement('div');\n        container.id = 'django-pipeline-errors';\n        document.body.insertBefore(container, document.body.firstChild);\n    }\n\n    container.appendChild(el);\n    el.style.display = 'block';\n});\n</script>\n"
  },
  {
    "path": "pipeline/templates/pipeline/css.html",
    "content": "<link href=\"{{ url }}\" rel=\"stylesheet\" type=\"{{ type }}\" media=\"{{ media|default:\"all\" }}\"{% if title %} title=\"{{ title }}\"{% endif %}{% if charset %} charset=\"{{ charset }}\"{% endif %} />"
  },
  {
    "path": "pipeline/templates/pipeline/css.jinja",
    "content": "<link href=\"{{ url }}\" rel=\"stylesheet\" type=\"{{ type }}\"{% if media %} media=\"{{ media }}\"{% endif %}{% if charset %} charset=\"{{ charset }}\"{% endif %} />\n"
  },
  {
    "path": "pipeline/templates/pipeline/inline_js.html",
    "content": "<script{% if async %} async{% endif %}{% if defer %} defer{% endif %} type=\"text/javascript\">\n  {{ source|safe }}\n</script>"
  },
  {
    "path": "pipeline/templates/pipeline/inline_js.jinja",
    "content": "<script{% if async %} async{% endif %}{% if defer %} defer{% endif %} type=\"text/javascript\">\n  {{ source|safe }}\n</script>\n"
  },
  {
    "path": "pipeline/templates/pipeline/js.html",
    "content": "<script{% if async %} async{% endif %}{% if defer %} defer{% endif %} type=\"{{ type }}\" src=\"{{ url }}\" charset=\"utf-8\"></script>"
  },
  {
    "path": "pipeline/templates/pipeline/js.jinja",
    "content": "<script{% if async %} async{% endif %}{% if defer %} defer{% endif %} type=\"{{ type }}\" src=\"{{ url }}\" charset=\"utf-8\"></script>\n"
  },
  {
    "path": "pipeline/templatetags/__init__.py",
    "content": ""
  },
  {
    "path": "pipeline/templatetags/pipeline.py",
    "content": "import logging\nimport re\nimport subprocess\n\nfrom django import template\nfrom django.contrib.staticfiles.storage import staticfiles_storage\nfrom django.template.base import VariableDoesNotExist\nfrom django.template.loader import render_to_string\nfrom django.utils.safestring import mark_safe\n\nfrom ..collector import default_collector\nfrom ..conf import settings\nfrom ..exceptions import CompilerError\nfrom ..packager import PackageNotFound, Packager\nfrom ..utils import guess_type\n\nlogger = logging.getLogger(__name__)\n\nregister = template.Library()\n\n\nclass PipelineMixin:\n    request = None\n    _request_var = None\n\n    @property\n    def request_var(self):\n        if not self._request_var:\n            self._request_var = template.Variable(\"request\")\n        return self._request_var\n\n    def package_for(self, package_name, package_type):\n        package = {\n            \"js\": getattr(settings, \"JAVASCRIPT\", {}).get(package_name, {}),\n            \"css\": getattr(settings, \"STYLESHEETS\", {}).get(package_name, {}),\n        }[package_type]\n\n        if package:\n            package = {package_name: package}\n\n        packager = {\n            \"js\": Packager(css_packages={}, js_packages=package),\n            \"css\": Packager(css_packages=package, js_packages={}),\n        }[package_type]\n\n        return packager.package_for(package_type, package_name)\n\n    def render(self, context):\n        try:\n            self.request = self.request_var.resolve(context)\n        except VariableDoesNotExist:\n            pass\n\n    def render_compressed(self, package, package_name, package_type):\n        \"\"\"Render HTML for the package.\n\n        If ``PIPELINE_ENABLED`` is ``True``, this will render the package's\n        output file (using :py:meth:`render_compressed_output`). Otherwise,\n        this will render the package's source files (using\n        :py:meth:`render_compressed_sources`).\n\n        Subclasses can override this method to provide custom behavior for\n        determining what to render.\n        \"\"\"\n        if settings.PIPELINE_ENABLED:\n            return self.render_compressed_output(package, package_name, package_type)\n        else:\n            return self.render_compressed_sources(package, package_name, package_type)\n\n    def render_compressed_output(self, package, package_name, package_type):\n        \"\"\"Render HTML for using the package's output file.\n\n        Subclasses can override this method to provide custom behavior for\n        rendering the output file.\n        \"\"\"\n        method = getattr(self, f\"render_{package_type}\")\n\n        return method(package, package.output_filename)\n\n    def render_compressed_sources(self, package, package_name, package_type):\n        \"\"\"Render HTML for using the package's list of source files.\n\n        Each source file will first be collected, if\n        ``PIPELINE_COLLECTOR_ENABLED`` is ``True``.\n\n        If there are any errors compiling any of the source files, an\n        ``SHOW_ERRORS_INLINE`` is ``True``, those errors will be shown at\n        the top of the page.\n\n        Subclasses can override this method to provide custom behavior for\n        rendering the source files.\n        \"\"\"\n        if settings.PIPELINE_COLLECTOR_ENABLED:\n            default_collector.collect(self.request)\n\n        packager = Packager()\n        method = getattr(self, f\"render_individual_{package_type}\")\n\n        try:\n            paths = packager.compile(package.paths)\n        except CompilerError as e:\n            if settings.SHOW_ERRORS_INLINE:\n                method = getattr(self, f\"render_error_{package_type}\")\n                return method(package_name, e)\n            else:\n                raise\n\n        templates = packager.pack_templates(package)\n\n        return method(package, paths, templates=templates)\n\n    def render_error(self, package_type, package_name, e):\n        # Remove any ANSI escape sequences in the output.\n        error_output = re.sub(\n            re.compile(r\"(?:\\x1B[@-_]|[\\x80-\\x9F])[0-?]*[ -/]*[@-~]\"),\n            \"\",\n            e.error_output,\n        )\n\n        return render_to_string(\n            \"pipeline/compile_error.html\",\n            {\n                \"package_type\": package_type,\n                \"package_name\": package_name,\n                \"command\": subprocess.list2cmdline(e.command),\n                \"errors\": error_output,\n            },\n        )\n\n\nclass StylesheetNode(PipelineMixin, template.Node):\n    def __init__(self, name):\n        self.name = name\n\n    def render(self, context):\n        super().render(context)\n        package_name = template.Variable(self.name).resolve(context)\n\n        try:\n            package = self.package_for(package_name, \"css\")\n        except PackageNotFound:\n            w = \"Package %r is unknown. Check PIPELINE['STYLESHEETS'] in your settings.\"\n            logger.warning(w, package_name)\n            # fail silently, do not return anything if an invalid group is specified\n            return \"\"\n        return self.render_compressed(package, package_name, \"css\")\n\n    def render_css(self, package, path):\n        template_name = package.template_name or \"pipeline/css.html\"\n        context = package.extra_context\n        context.update(\n            {\n                \"type\": guess_type(path, \"text/css\"),\n                \"url\": mark_safe(staticfiles_storage.url(path)),\n            }\n        )\n        return render_to_string(template_name, context)\n\n    def render_individual_css(self, package, paths, **kwargs):\n        tags = [self.render_css(package, path) for path in paths]\n        return \"\\n\".join(tags)\n\n    def render_error_css(self, package_name, e):\n        return super().render_error(\"CSS\", package_name, e)\n\n\nclass JavascriptNode(PipelineMixin, template.Node):\n    def __init__(self, name):\n        self.name = name\n\n    def render(self, context):\n        super().render(context)\n        package_name = template.Variable(self.name).resolve(context)\n\n        try:\n            package = self.package_for(package_name, \"js\")\n        except PackageNotFound:\n            w = \"Package %r is unknown. Check PIPELINE['JAVASCRIPT'] in your settings.\"\n            logger.warning(w, package_name)\n            # fail silently, do not return anything if an invalid group is specified\n            return \"\"\n        return self.render_compressed(package, package_name, \"js\")\n\n    def render_js(self, package, path):\n        template_name = package.template_name or \"pipeline/js.html\"\n        context = package.extra_context\n        context.update(\n            {\n                \"type\": guess_type(path, \"text/javascript\"),\n                \"url\": mark_safe(staticfiles_storage.url(path)),\n            }\n        )\n        return render_to_string(template_name, context)\n\n    def render_inline(self, package, js):\n        context = package.extra_context\n        context.update({\"source\": js})\n        return render_to_string(\"pipeline/inline_js.html\", context)\n\n    def render_individual_js(self, package, paths, templates=None):\n        tags = [self.render_js(package, js) for js in paths]\n        if templates:\n            tags.append(self.render_inline(package, templates))\n        return \"\\n\".join(tags)\n\n    def render_error_js(self, package_name, e):\n        return super().render_error(\"JavaScript\", package_name, e)\n\n\n@register.tag\ndef stylesheet(parser, token):\n    try:\n        tag_name, name = token.split_contents()\n    except ValueError:\n        e = (\n            \"%r requires exactly one argument: the name \"\n            \"of a group in the PIPELINE.STYLESHEETS setting\"\n        )\n        raise template.TemplateSyntaxError(e % token.split_contents()[0])\n    return StylesheetNode(name)\n\n\n@register.tag\ndef javascript(parser, token):\n    try:\n        tag_name, name = token.split_contents()\n    except ValueError:\n        e = (\n            \"%r requires exactly one argument: the name \"\n            \"of a group in the PIPELINE.JAVASVRIPT setting\"\n        )\n        raise template.TemplateSyntaxError(e % token.split_contents()[0])\n    return JavascriptNode(name)\n"
  },
  {
    "path": "pipeline/utils.py",
    "content": "try:\n    import fcntl\nexcept ImportError:\n    # windows\n    fcntl = None\n\nimport importlib\nimport mimetypes\nimport os\nimport posixpath\nimport sys\nfrom urllib.parse import quote\n\nfrom django.utils.encoding import smart_str\n\nfrom pipeline.conf import settings\n\n\ndef to_class(class_str):\n    if not class_str:\n        return None\n\n    module_bits = class_str.split(\".\")\n    module_path, class_name = \".\".join(module_bits[:-1]), module_bits[-1]\n    module = importlib.import_module(module_path)\n    return getattr(module, class_name, None)\n\n\ndef filepath_to_uri(path):\n    if path is None:\n        return path\n    return quote(smart_str(path).replace(\"\\\\\", \"/\"), safe=\"/~!*()'#?\")\n\n\ndef guess_type(path, default=None):\n    for type, ext in settings.MIMETYPES:\n        mimetypes.add_type(type, ext)\n    mimetype, _ = mimetypes.guess_type(path)\n    if not mimetype:\n        return default\n    return smart_str(mimetype)\n\n\ndef relpath(path, start=posixpath.curdir):\n    \"\"\"Return a relative version of a path\"\"\"\n    if not path:\n        raise ValueError(\"no path specified\")\n\n    start_list = posixpath.abspath(start).split(posixpath.sep)\n    path_list = posixpath.abspath(path).split(posixpath.sep)\n\n    # Work out how much of the filepath is shared by start and path.\n    i = len(posixpath.commonprefix([start_list, path_list]))\n\n    rel_list = [posixpath.pardir] * (len(start_list) - i) + path_list[i:]\n    if not rel_list:\n        return posixpath.curdir\n    return posixpath.join(*rel_list)\n\n\ndef set_std_streams_blocking():\n    \"\"\"\n    Set stdout and stderr to be blocking.\n\n    This is called after Popen.communicate() to revert stdout and stderr back\n    to be blocking (the default) in the event that the process to which they\n    were passed manipulated one or both file descriptors to be non-blocking.\n    \"\"\"\n    if not fcntl:\n        return\n    for f in (sys.__stdout__, sys.__stderr__):\n        fileno = f.fileno()\n        flags = fcntl.fcntl(fileno, fcntl.F_GETFL)\n        fcntl.fcntl(fileno, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)\n"
  },
  {
    "path": "pipeline/views.py",
    "content": "from django.conf import settings as django_settings\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.views.static import serve\n\nfrom .collector import default_collector\nfrom .conf import settings\n\n\ndef serve_static(request, path, insecure=False, **kwargs):\n    \"\"\"Collect and serve static files.\n\n    This view serves up static files, much like Django's\n    :py:func:`~django.views.static.serve` view, with the addition that it\n    collects static files first (if enabled). This allows images, fonts, and\n    other assets to be served up without first loading a page using the\n    ``{% javascript %}`` or ``{% stylesheet %}`` template tags.\n\n    You can use this view by adding the following to any :file:`urls.py`::\n\n        urlpatterns += static('static/', view='pipeline.views.serve_static')\n    \"\"\"\n    # Follow the same logic Django uses for determining access to the\n    # static-serving view.\n    if not django_settings.DEBUG and not insecure:\n        raise ImproperlyConfigured(\n            \"The staticfiles view can only be used in \"\n            \"debug mode or if the --insecure \"\n            \"option of 'runserver' is used\"\n        )\n\n    if not settings.PIPELINE_ENABLED and settings.PIPELINE_COLLECTOR_ENABLED:\n        # Collect only the requested file, in order to serve the result as\n        # fast as possible. This won't interfere with the template tags in any\n        # way, as those will still cause Django to collect all media.\n        default_collector.collect(request, files=[path])\n\n    return serve(request, path, document_root=django_settings.STATIC_ROOT, **kwargs)\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools>=64\", \"setuptools_scm[toml]>=8\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"django-pipeline\"\nrequires-python = \">=3.9\"\nversion = \"4.1.0\"\ndescription = \"Pipeline is an asset packaging library for Django.\"\nreadme = \"README.rst\"\nauthors = [{ \"name\" = \"Timothée Peignier\", \"email\" = \"timothee.peignier@tryphon.org\" }]\nlicense = { text = \"MIT\" }\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Environment :: Web Environment\",\n    \"Framework :: Django\",\n    \"Framework :: Django :: 4.0\",\n    \"Framework :: Django :: 4.1\",\n    \"Framework :: Django :: 4.2\",\n    \"Framework :: Django :: 5.0\",\n    \"Framework :: Django :: 5.1\",\n    \"Framework :: Django :: 5.2\",\n    \"Intended Audience :: Developers\",\n    \"License :: OSI Approved :: MIT License\",\n    \"Operating System :: OS Independent\",\n    \"Programming Language :: Python\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3 :: Only\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: Implementation :: PyPy\",\n    \"Topic :: Utilities\",\n    \"Topic :: Software Development :: Libraries :: Python Modules\",\n    \"Topic :: Internet :: WWW/HTTP\",\n    \"Topic :: Internet :: WWW/HTTP :: Dynamic Content\",\n]\nkeywords = [\n    \"django\",\n    \"pipeline\",\n    \"asset\",\n    \"compiling\",\n    \"concatenation\",\n    \"compression\",\n    \"packaging\",\n]\ndependencies = [\n    # indirect dependencies\n    \"setuptools\",\n    \"wheel\",\n]\n\n[project.optional-dependencies]\ntesting = [\n    \"coveralls\",\n    \"tox\",\n    \"wheel\",\n    \"django\",\n]\n\n[project.urls]\nhomepage = \"https://github.com/jazzband/django-pipeline/\"\ndocumentation = \"https://django-pipeline.readthedocs.io/\"\nrepository = \"https://github.com/jazzband/django-pipeline\"\nchangelog = \"https://github.com/jazzband/django-pipeline/blob/master/HISTORY.rst\"\n\n[tool.setuptools]\ninclude-package-data = true\n\n[tool.setuptools.packages.find]\nexclude = [\"tests\", \"tests.tests\"]\n\n[tool.setuptools_scm]\nlocal_scheme = \"dirty-tag\"\n\n[tool.black]\nline-length = 88\ntarget-version = [\"py39\"]\n\n[tool.isort]\nprofile = \"black\"\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/assets/compilers/coffee/expected.js",
    "content": "(function() {\n  var cube, square;\n\n  square = function(x) {\n    return x * x;\n  };\n\n  cube = function(x) {\n    return square(x) * x;\n  };\n\n}).call(this);\n"
  },
  {
    "path": "tests/assets/compilers/coffee/input.coffee",
    "content": "square = (x) -> x * x\ncube   = (x) -> square(x) * x\n"
  },
  {
    "path": "tests/assets/compilers/es6/expected.js",
    "content": "\"use strict\";\n\n// Expression bodies\nvar odds = evens.map(function (v) {\n  return v + 1;\n});\nvar nums = evens.map(function (v, i) {\n  return v + i;\n});\n\n// Statement bodies\nnums.forEach(function (v) {\n  if (v % 5 === 0) fives.push(v);\n});\n\n// Lexical this\nvar bob = {\n  _name: \"Bob\",\n  _friends: [],\n  printFriends: function printFriends() {\n    var _this = this;\n\n    this._friends.forEach(function (f) {\n      return console.log(_this._name + \" knows \" + f);\n    });\n  }\n};\n"
  },
  {
    "path": "tests/assets/compilers/es6/input.es6",
    "content": "// Expression bodies\nvar odds = evens.map(v => v + 1);\nvar nums = evens.map((v, i) => v + i);\n\n// Statement bodies\nnums.forEach(v => {\n  if (v % 5 === 0)\n    fives.push(v);\n});\n\n// Lexical this\nvar bob = {\n  _name: \"Bob\",\n  _friends: [],\n  printFriends() {\n    this._friends.forEach(f =>\n      console.log(this._name + \" knows \" + f));\n  }\n};\n"
  },
  {
    "path": "tests/assets/compilers/less/expected.css",
    "content": ".a {\n  width: 1px;\n}\n"
  },
  {
    "path": "tests/assets/compilers/less/input.less",
    "content": "@a: 1;\n\n.a {\n    width: (@a + 0px);\n}\n"
  },
  {
    "path": "tests/assets/compilers/livescript/expected.js",
    "content": "(function(){\n  var times;\n  times = function(x, y){\n    return x * y;\n  };\n}).call(this);\n"
  },
  {
    "path": "tests/assets/compilers/livescript/input.ls",
    "content": "times = (x, y) ->\n  x * y\n"
  },
  {
    "path": "tests/assets/compilers/scss/expected.css",
    "content": ".a .b {\n  display: none;\n}\n\n.c .d {\n  display: block;\n}\n\n/*# sourceMappingURL=input.css.map */\n"
  },
  {
    "path": "tests/assets/compilers/scss/input.scss",
    "content": ".a {\n    .b {\n        display: none;\n    }\n}\n.c {\n    .d {\n        display: block;\n    }\n}\n"
  },
  {
    "path": "tests/assets/compilers/stylus/expected.css",
    "content": ".a {\n  color: #000;\n}\n"
  },
  {
    "path": "tests/assets/compilers/stylus/input.styl",
    "content": ".a\n    color: black"
  },
  {
    "path": "tests/assets/compilers/typescript/expected.js",
    "content": "function getName(u) {\n    return \"\".concat(u.firstName, \" \").concat(u.lastName);\n}\nvar userName = getName({ firstName: \"Django\", lastName: \"Pipeline\" });\n"
  },
  {
    "path": "tests/assets/compilers/typescript/input.ts",
    "content": "type FullName = string;\n\ninterface User {\n    firstName: string;\n    lastName: string;\n}\n\n\nfunction getName(u: User): FullName {\n    return `${u.firstName} ${u.lastName}`;\n}\n\nlet userName: FullName = getName({firstName: \"Django\", lastName: \"Pipeline\"});\n"
  },
  {
    "path": "tests/assets/compressors/closure.js",
    "content": "(function(){(function(){window.concat=function(){console.log(arguments)}})();(function(){window.cat=function(){console.log(\"hello world\")}})()}).call(this);\n"
  },
  {
    "path": "tests/assets/compressors/csshtmljsminify.css",
    "content": "@charset \"utf-8\";.concat{display:none}.concatenate{display:block}"
  },
  {
    "path": "tests/assets/compressors/csshtmljsminify.js",
    "content": "(function(){(function(){window.concat=function(){console.log(arguments);}}());(function(){window.cat=function(){console.log(\"hello world\");}}());}).call(this);"
  },
  {
    "path": "tests/assets/compressors/cssmin.css",
    "content": ".concat{display:none}.concatenate{display:block}"
  },
  {
    "path": "tests/assets/compressors/csstidy.css",
    "content": ".concat{display:none;}.concatenate{display:block;}"
  },
  {
    "path": "tests/assets/compressors/jsmin.js",
    "content": "(function(){(function(){window.concat=function(){console.log(arguments);}}());(function(){window.cat=function(){console.log(\"hello world\");}}());}).call(this);"
  },
  {
    "path": "tests/assets/compressors/slimit.js",
    "content": "(function(){(function(){window.concat=function(){console.log(arguments);};}());(function(){window.cat=function(){console.log(\"hello world\");};}());}).call(this);"
  },
  {
    "path": "tests/assets/compressors/terser.js",
    "content": "(function(){window.concat=function(){console.log(arguments)},window.cat=function(){console.log(\"hello world\")}}).call(this);\n"
  },
  {
    "path": "tests/assets/compressors/uglifyjs.js",
    "content": "(function(){(function(){window.concat=function(){console.log(arguments)}})();(function(){window.cat=function(){console.log(\"hello world\")}})()}).call(this);\n"
  },
  {
    "path": "tests/assets/compressors/yuglify.css",
    "content": ".concat{display:none}.concatenate{display:block}\n"
  },
  {
    "path": "tests/assets/compressors/yuglify.js",
    "content": "(function(){!function(){window.concat=function(){console.log(arguments)}}(),function(){window.cat=function(){console.log(\"hello world\")}}()}).call(this);\n"
  },
  {
    "path": "tests/assets/compressors/yui.css",
    "content": ".concat{display:none}.concatenate{display:block}"
  },
  {
    "path": "tests/assets/compressors/yui.js",
    "content": "(function(){(function(){window.concat=function(){console.log(arguments)}}());(function(){window.cat=function(){console.log(\"hello world\")}}())}).call(this);"
  },
  {
    "path": "tests/assets/css/first.css",
    "content": ".concat {\n  display: none;\n}\n"
  },
  {
    "path": "tests/assets/css/nested/nested.css",
    "content": ".data-url {\n  background-image: url(data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2212px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2012%2014%22%20style%3D%22enable-background%3Anew%200%200%2012%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M11%2C6V5c0-2.762-2.239-5-5-5S1%2C2.238%2C1%2C5v1H0v8h12V6H11z%20M6.5%2C9.847V12h-1V9.847C5.207%2C9.673%2C5%2C9.366%2C5%2C9%20c0-0.553%2C0.448-1%2C1-1s1%2C0.447%2C1%2C1C7%2C9.366%2C6.793%2C9.673%2C6.5%2C9.847z%20M9%2C6H3V5c0-1.657%2C1.343-3%2C3-3s3%2C1.343%2C3%2C3V6z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E);\n}\n.data-url-quoted {\n  background-image: url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2212px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2012%2014%22%20style%3D%22enable-background%3Anew%200%200%2012%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M11%2C6V5c0-2.762-2.239-5-5-5S1%2C2.238%2C1%2C5v1H0v8h12V6H11z%20M6.5%2C9.847V12h-1V9.847C5.207%2C9.673%2C5%2C9.366%2C5%2C9%20c0-0.553%2C0.448-1%2C1-1s1%2C0.447%2C1%2C1C7%2C9.366%2C6.793%2C9.673%2C6.5%2C9.847z%20M9%2C6H3V5c0-1.657%2C1.343-3%2C3-3s3%2C1.343%2C3%2C3V6z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E');\n}\n"
  },
  {
    "path": "tests/assets/css/second.css",
    "content": ".concatenate {\n  display: block;\n}\n"
  },
  {
    "path": "tests/assets/css/sourcemap.css",
    "content": "div {\n  display: inline;\n}\n\nspan {\n  display: block;\n}\n\n\n//#  sourceMappingURL=sourcemap1.css.map\n\n//@ sourceMappingURL=sourcemap2.css.map  \n\n/*#  sourceMappingURL=sourcemap3.css.map */\n\n/*@ sourceMappingURL=sourcemap4.css.map  */\n\n//#  sourceURL=sourcemap5.css.map\n\n//@ sourceURL=sourcemap6.css.map  \n\n/*#  sourceURL=sourcemap7.css.map */\n\n/*@ sourceURL=sourcemap8.css.map  */\n"
  },
  {
    "path": "tests/assets/css/unicode.css",
    "content": ".some_class {\n  // Some unicode\n  content: \"áéíóú\";\n}\n"
  },
  {
    "path": "tests/assets/css/urls.css",
    "content": ".embedded-url-svg {\n  background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath      stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%     3C/svg%3E\");\n}\n@font-face {\n  font-family: 'Pipeline';\n  src: url('../fonts/pipeline.eot');\n  src: url('../fonts/pipeline.eot?#iefix') format('embedded-opentype');\n  src: local('☺'), url('../fonts/pipeline.woff') format('woff'), url('../fonts/pipeline.ttf') format('truetype'), url('../fonts/pipeline.svg#IyfZbseF') format('svg');\n  font-weight: normal;\n  font-style: normal;\n}\n.relative-url {\n  background-image: url(../images/sprite-buttons.png);\n}\n.relative-url-querystring {\n  background-image: url(../images/sprite-buttons.png?v=1.0#foo=bar);\n}\n.absolute-url {\n  background-image: url(/images/sprite-buttons.png);\n}\n.absolute-full-url {\n  background-image: url(http://localhost/images/sprite-buttons.png);\n}\n.no-protocol-url {\n  background-image: url(//images/sprite-buttons.png);\n}\n.anchor-tag-url {\n  background-image: url(#image-gradient);\n}\n@font-face{src:url(../fonts/pipeline.eot);src:url(../fonts/pipeline.eot?#iefix) format('embedded-opentype'),url(../fonts/pipeline.woff) format('woff'),url(../fonts/pipeline.ttf) format('truetype');}\n"
  },
  {
    "path": "tests/assets/js/application.js",
    "content": "function test() {\n  alert('this is a test');\n}\n"
  },
  {
    "path": "tests/assets/js/dummy.coffee",
    "content": "square = (x) -> x * x\ncube   = (x) -> square(x) * x\n"
  },
  {
    "path": "tests/assets/js/first.js",
    "content": "(function() {\n  window.concat = function() {\n    console.log(arguments);\n  }\n}()) // No semicolon\n"
  },
  {
    "path": "tests/assets/js/second.js",
    "content": "(function() {\n  window.cat = function() {\n    console.log(\"hello world\");\n  }\n}());\n"
  },
  {
    "path": "tests/assets/js/sourcemap.js",
    "content": "const abc = 123;\n\n\n//#  sourceMappingURL=sourcemap1.js.map\n\n//@ sourceMappingURL=sourcemap2.js.map  \n\n/*#  sourceMappingURL=sourcemap3.js.map */\n\n/*@ sourceMappingURL=sourcemap4.js.map  */\n\n//#  sourceURL=sourcemap5.js.map\n\n//@ sourceURL=sourcemap6.js.map  \n\n/*#  sourceURL=sourcemap7.js.map */\n\n/*@ sourceURL=sourcemap8.js.map  */\n"
  },
  {
    "path": "tests/assets/templates/photo/detail.jst",
    "content": "<div class=\"photo\">\n <img src=\"<%= src %>\" />\n <div class=\"caption\">\n  <%= caption %> by <%= author %>\n </div>\n</div>"
  },
  {
    "path": "tests/assets/templates/photo/list.jst",
    "content": "<div class=\"photo\">\n <img src=\"<%= src %>\" />\n <div class=\"caption\">\n  <%= caption %>\n </div>\n</div>"
  },
  {
    "path": "tests/assets/templates/video/detail.jst",
    "content": "<div class=\"video\">\n <video src=\"<%= src %>\" />\n <div class=\"caption\">\n  <%= description %>\n </div>\n</div>"
  },
  {
    "path": "tests/models.py",
    "content": ""
  },
  {
    "path": "tests/settings.py",
    "content": "import glob\nimport os\nimport shutil\n\n\ndef local_path(path):\n    return os.path.join(os.path.dirname(__file__), path)\n\n\nDATABASES = {\n    \"default\": {\"ENGINE\": \"django.db.backends.sqlite3\", \"TEST_NAME\": \":memory:\"}\n}\n\nDEBUG = False\n\nSITE_ID = 1\n\nINSTALLED_APPS = [\n    \"django.contrib.contenttypes\",\n    \"django.contrib.messages\",\n    \"django.contrib.sites\",\n    \"django.contrib.sessions\",\n    \"django.contrib.staticfiles\",\n    \"django.contrib.auth\",\n    \"django.contrib.admin\",\n    \"pipeline\",\n    \"tests.tests\",\n]\n\n\nROOT_URLCONF = \"tests.urls\"\n\nMIDDLEWARE = [\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n    \"django.middleware.common.CommonMiddleware\",\n    \"django.middleware.csrf.CsrfViewMiddleware\",\n]\n\nMEDIA_URL = \"/media/\"\n\nMEDIA_ROOT = local_path(\"media\")\n\nSTATICFILES_STORAGE = \"pipeline.storage.PipelineStorage\"\nSTATIC_ROOT = local_path(\"static/\")\nSTATIC_URL = \"/static/\"\nSTATICFILES_DIRS = ((\"pipeline\", local_path(\"assets/\")),)\nSTATICFILES_FINDERS = (\n    \"django.contrib.staticfiles.finders.FileSystemFinder\",\n    \"django.contrib.staticfiles.finders.AppDirectoriesFinder\",\n    \"pipeline.finders.PipelineFinder\",\n)\n\nSECRET_KEY = \"django-pipeline\"\n\nPIPELINE = {\n    \"PIPELINE_ENABLED\": True,\n    \"JS_COMPRESSOR\": None,\n    \"CSS_COMPRESSOR\": None,\n    \"STYLESHEETS\": {\n        \"screen\": {\n            \"source_filenames\": (\n                \"pipeline/css/first.css\",\n                \"pipeline/css/second.css\",\n                \"pipeline/css/urls.css\",\n            ),\n            \"output_filename\": \"screen.css\",\n        },\n        \"screen_media\": {\n            \"source_filenames\": (\n                \"pipeline/css/first.css\",\n                \"pipeline/css/second.css\",\n                \"pipeline/css/urls.css\",\n            ),\n            \"output_filename\": \"screen_media.css\",\n            \"extra_context\": {\n                \"media\": \"screen and (min-width:500px)\",\n            },\n        },\n        \"screen_title\": {\n            \"source_filenames\": (\n                \"pipeline/css/first.css\",\n                \"pipeline/css/second.css\",\n                \"pipeline/css/urls.css\",\n            ),\n            \"output_filename\": \"screen_title.css\",\n            \"extra_context\": {\n                \"title\": \"Default Style\",\n            },\n        },\n    },\n    \"JAVASCRIPT\": {\n        \"scripts\": {\n            \"source_filenames\": (\n                \"pipeline/js/first.js\",\n                \"pipeline/js/second.js\",\n                \"pipeline/js/application.js\",\n                \"pipeline/templates/**/*.jst\",\n            ),\n            \"output_filename\": \"scripts.js\",\n        },\n        \"scripts_async\": {\n            \"source_filenames\": (\n                \"pipeline/js/first.js\",\n                \"pipeline/js/second.js\",\n                \"pipeline/js/application.js\",\n                \"pipeline/templates/**/*.jst\",\n            ),\n            \"output_filename\": \"scripts_async.js\",\n            \"extra_context\": {\n                \"async\": True,\n            },\n        },\n        \"scripts_defer\": {\n            \"source_filenames\": (\n                \"pipeline/js/first.js\",\n                \"pipeline/js/second.js\",\n                \"pipeline/js/application.js\",\n                \"pipeline/templates/**/*.jst\",\n            ),\n            \"output_filename\": \"scripts_defer.js\",\n            \"extra_context\": {\n                \"defer\": True,\n            },\n        },\n        \"scripts_async_defer\": {\n            \"source_filenames\": (\n                \"pipeline/js/first.js\",\n                \"pipeline/js/second.js\",\n                \"pipeline/js/application.js\",\n                \"pipeline/templates/**/*.jst\",\n            ),\n            \"output_filename\": \"scripts_async_defer.js\",\n            \"extra_context\": {\n                \"async\": True,\n                \"defer\": True,\n            },\n        },\n    },\n}\n\nNODE_MODULES_PATH = local_path(\"../node_modules\")\nNODE_BIN_PATH = os.path.join(NODE_MODULES_PATH, \".bin\")\nNODE_EXE_PATH = shutil.which(\"node\")\nJAVA_EXE_PATH = shutil.which(\"java\")\nCSSTIDY_EXE_PATH = shutil.which(\"csstidy\")\nHAS_NODE = bool(NODE_EXE_PATH)\nHAS_JAVA = bool(JAVA_EXE_PATH)\nHAS_CSSTIDY = bool(CSSTIDY_EXE_PATH)\n\nif HAS_NODE:\n\n    def node_exe_path(command):\n        exe_ext = \".cmd\" if os.name == \"nt\" else \"\"\n        return os.path.join(NODE_BIN_PATH, \"{}{}\".format(command, exe_ext))\n\n    PIPELINE.update(\n        {\n            \"SASS_BINARY\": node_exe_path(\"sass\"),\n            \"COFFEE_SCRIPT_BINARY\": node_exe_path(\"coffee\"),\n            \"COFFEE_SCRIPT_ARGUMENTS\": [\"--no-header\"],\n            \"LESS_BINARY\": node_exe_path(\"lessc\"),\n            \"BABEL_BINARY\": node_exe_path(\"babel\"),\n            \"BABEL_ARGUMENTS\": [\"--presets\", \"es2015\"],\n            \"STYLUS_BINARY\": node_exe_path(\"stylus\"),\n            \"LIVE_SCRIPT_BINARY\": node_exe_path(\"lsc\"),\n            \"LIVE_SCRIPT_ARGUMENTS\": [\"--no-header\"],\n            \"YUGLIFY_BINARY\": node_exe_path(\"yuglify\"),\n            \"UGLIFYJS_BINARY\": node_exe_path(\"uglifyjs\"),\n            \"TERSER_BINARY\": node_exe_path(\"terser\"),\n            \"CSSMIN_BINARY\": node_exe_path(\"cssmin\"),\n            \"TYPE_SCRIPT_BINARY\": node_exe_path(\"tsc\"),\n        }\n    )\n\nif HAS_NODE and HAS_JAVA:\n    PIPELINE.update(\n        {\n            \"CLOSURE_BINARY\": [\n                JAVA_EXE_PATH,\n                \"-jar\",\n                os.path.join(\n                    NODE_MODULES_PATH,\n                    \"google-closure-compiler-java\",\n                    \"compiler.jar\",\n                ),\n            ],\n            \"YUI_BINARY\": [\n                JAVA_EXE_PATH,\n                \"-jar\",\n                glob.glob(\n                    os.path.join(NODE_MODULES_PATH, \"yuicompressor\", \"build\", \"*.jar\")\n                )[0],\n            ],\n        }\n    )\n\nif HAS_CSSTIDY:\n    PIPELINE.update({\"CSSTIDY_BINARY\": CSSTIDY_EXE_PATH})\n\nTEMPLATES = [\n    {\n        \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n        \"APP_DIRS\": True,\n        \"DIRS\": [local_path(\"templates\")],\n        \"OPTIONS\": {\n            \"context_processors\": [\n                \"django.template.context_processors.request\",\n                \"django.contrib.auth.context_processors.auth\",\n                \"django.contrib.messages.context_processors.messages\",\n            ]\n        },\n    },\n    {\n        \"BACKEND\": \"django.template.backends.jinja2.Jinja2\",\n        \"APP_DIRS\": True,\n        \"DIRS\": [local_path(\"templates\")],\n        \"OPTIONS\": {\"extensions\": [\"pipeline.jinja2.PipelineExtension\"]},\n    },\n]\n\nLOGGING = {\n    \"version\": 1,\n    \"disable_existing_loggers\": False,\n    \"handlers\": {\n        \"console\": {\n            \"class\": \"logging.StreamHandler\",\n        },\n    },\n    \"loggers\": {\n        \"pipeline.templatetags.pipeline\": {\n            \"handlers\": [\"console\"],\n            \"level\": \"ERROR\",\n        },\n    },\n}\n"
  },
  {
    "path": "tests/templates/empty.html",
    "content": " "
  },
  {
    "path": "tests/templates/index.html",
    "content": "{% load pipeline %}\n<html>\n <head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <title>Pipeline</title>\n  {% stylesheet 'screen' %}\n  {% javascript 'scripts' %}\n </head>\n</html>\n"
  },
  {
    "path": "tests/tests/__init__.py",
    "content": "import os\nimport sys\n\nif sys.platform.startswith(\"win\"):\n    os.environ.setdefault(\"NUMBER_OF_PROCESSORS\", \"1\")\n\n\nfrom .test_collector import *  # noqa\nfrom .test_compiler import *  # noqa\nfrom .test_compressor import *  # noqa\nfrom .test_glob import *  # noqa\nfrom .test_middleware import *  # noqa\nfrom .test_packager import *  # noqa\nfrom .test_storage import *  # noqa\nfrom .test_template import *  # noqa\nfrom .test_utils import *  # noqa\nfrom .test_views import *  # noqa\n"
  },
  {
    "path": "tests/tests/models.py",
    "content": ""
  },
  {
    "path": "tests/tests/test_collector.py",
    "content": "import os\n\nfrom django.contrib.staticfiles import finders\nfrom django.core.files.storage import FileSystemStorage\nfrom django.test import TestCase\n\nfrom pipeline.collector import default_collector\nfrom pipeline.finders import PipelineFinder\n\n\ndef local_path(path):\n    return os.path.abspath(os.path.join(os.path.dirname(__file__), \"..\", path))\n\n\nclass CollectorTest(TestCase):\n    def tearDown(self):\n        super().tearDown()\n\n        default_collector.clear()\n\n    def test_collect(self):\n        self.assertEqual(\n            set(default_collector.collect()), set(self._get_collectable_files())\n        )\n\n    def test_collect_with_files(self):\n        self.assertEqual(\n            set(\n                default_collector.collect(\n                    files=[\n                        \"pipeline/js/first.js\",\n                        \"pipeline/js/second.js\",\n                    ]\n                )\n            ),\n            {\n                \"pipeline/js/first.js\",\n                \"pipeline/js/second.js\",\n            },\n        )\n\n    def test_delete_file_with_modified(self):\n        list(default_collector.collect())\n\n        storage = FileSystemStorage(local_path(\"assets\"))\n        new_mtime = os.path.getmtime(storage.path(\"js/first.js\")) - 1000\n        os.utime(\n            default_collector.storage.path(\"pipeline/js/first.js\"),\n            (new_mtime, new_mtime),\n        )\n\n        self.assertTrue(\n            default_collector.delete_file(\n                \"js/first.js\", \"pipeline/js/first.js\", storage\n            )\n        )\n\n    def test_delete_file_with_unmodified(self):\n        list(default_collector.collect(files=[\"pipeline/js/first.js\"]))\n\n        self.assertFalse(\n            default_collector.delete_file(\n                \"js/first.js\",\n                \"pipeline/js/first.js\",\n                FileSystemStorage(local_path(\"assets\")),\n            )\n        )\n\n    def _get_collectable_files(self):\n        for finder in finders.get_finders():\n            if not isinstance(finder, PipelineFinder):\n                for path, storage in finder.list([\"CVS\", \".*\", \"*~\"]):\n                    if getattr(storage, \"prefix\", None):\n                        yield os.path.join(storage.prefix, path)\n                    else:\n                        yield path\n"
  },
  {
    "path": "tests/tests/test_compiler.py",
    "content": "import sys\nfrom unittest import skipIf, skipUnless\n\nfrom django.conf import settings\nfrom django.contrib.staticfiles.storage import staticfiles_storage\nfrom django.test import TestCase\nfrom django.test.client import RequestFactory\n\nfrom pipeline.collector import default_collector\nfrom pipeline.compilers import Compiler, CompilerBase, SubProcessCompiler\nfrom pipeline.exceptions import CompilerError\nfrom pipeline.utils import to_class\nfrom tests.utils import _, pipeline_settings\n\n\nclass FailingCompiler(SubProcessCompiler):\n    output_extension = \"junk\"\n\n    def match_file(self, path):\n        return path.endswith(\".coffee\")\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        command = (\n            (\n                \"/usr/bin/env\",\n                \"false\",\n            ),\n        )\n        return self.execute_command(command)\n\n\nclass InvalidCompiler(SubProcessCompiler):\n    output_extension = \"junk\"\n\n    def match_file(self, path):\n        return path.endswith(\".coffee\")\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        command = (\n            (\"this-exists-nowhere-as-a-command-and-should-fail\",),\n            infile,\n            outfile,\n        )\n        return self.execute_command(command)\n\n\nclass CompilerWithEmptyFirstArg(SubProcessCompiler):\n    output_extension = \"junk\"\n\n    def match_file(self, path):\n        return path.endswith(\".coffee\")\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        command = (\n            (\"\", \"/usr/bin/env\", \"cat\"),\n            infile,\n        )\n        return self.execute_command(command, stdout_captured=outfile)\n\n\nclass CopyingCompiler(SubProcessCompiler):\n    output_extension = \"junk\"\n\n    def match_file(self, path):\n        return path.endswith(\".coffee\")\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        command = (\"cp\", infile, outfile)\n        return self.execute_command(command)\n\n\nclass LineNumberingCompiler(SubProcessCompiler):\n    output_extension = \"junk\"\n\n    def match_file(self, path):\n        return path.endswith(\".coffee\")\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        command = (\n            (\"/usr/bin/env\", \"cat\"),\n            (\"-n\",),\n            infile,\n        )\n        return self.execute_command(command, stdout_captured=outfile)\n\n\nclass DummyCompiler(CompilerBase):\n    output_extension = \"js\"\n\n    def match_file(self, path):\n        return path.endswith(\".coffee\")\n\n    def compile_file(self, infile, outfile, outdated=False, force=False):\n        return\n\n\n@pipeline_settings(COMPILERS=[\"tests.tests.test_compiler.DummyCompiler\"])\nclass DummyCompilerTest(TestCase):\n    def setUp(self):\n        default_collector.collect()\n        self.compiler = Compiler()\n\n    def test_output_path(self):\n        compiler_class = self.compiler.compilers[0]\n        compiler = compiler_class(\n            verbose=self.compiler.verbose,\n            storage=self.compiler.storage,\n        )\n        output_path = compiler.output_path(\"js/helpers.coffee\", \"js\")\n        self.assertEqual(output_path, \"js/helpers.js\")\n\n    def test_compilers_class(self):\n        compilers_class = self.compiler.compilers\n        self.assertEqual(compilers_class[0], DummyCompiler)\n\n    def test_compile(self):\n        paths = self.compiler.compile(\n            [\n                _(\"pipeline/js/dummy.coffee\"),\n                _(\"pipeline/js/application.js\"),\n            ]\n        )\n        self.assertEqual(\n            [_(\"pipeline/js/dummy.js\"), _(\"pipeline/js/application.js\")],\n            list(paths),\n        )\n\n    def tearDown(self):\n        default_collector.clear()\n\n\n@skipIf(sys.platform.startswith(\"win\"), \"requires posix platform\")\n@pipeline_settings(COMPILERS=[\"tests.tests.test_compiler.LineNumberingCompiler\"])\nclass CompilerStdoutTest(TestCase):\n    def setUp(self):\n        default_collector.collect()\n        self.compiler = Compiler()\n\n    def test_output_path(self):\n        compiler_class = self.compiler.compilers[0]\n        compiler = compiler_class(\n            verbose=self.compiler.verbose,\n            storage=self.compiler.storage,\n        )\n        output_path = compiler.output_path(\"js/helpers.coffee\", \"js\")\n        self.assertEqual(output_path, \"js/helpers.js\")\n\n    def test_compile(self):\n        paths = self.compiler.compile([_(\"pipeline/js/dummy.coffee\")])\n        self.assertEqual([_(\"pipeline/js/dummy.junk\")], list(paths))\n\n    def tearDown(self):\n        default_collector.clear()\n\n\n@skipIf(sys.platform.startswith(\"win\"), \"requires posix platform\")\n@pipeline_settings(COMPILERS=[\"tests.tests.test_compiler.CopyingCompiler\"])\nclass CompilerSelfWriterTest(TestCase):\n    def setUp(self):\n        default_collector.collect()\n        self.compiler = Compiler()\n\n    def test_output_path(self):\n        compiler_class = self.compiler.compilers[0]\n        compiler = compiler_class(\n            verbose=self.compiler.verbose,\n            storage=self.compiler.storage,\n        )\n        output_path = compiler.output_path(\"js/helpers.coffee\", \"js\")\n        self.assertEqual(output_path, \"js/helpers.js\")\n\n    def test_compile(self):\n        paths = self.compiler.compile([_(\"pipeline/js/dummy.coffee\")])\n        default_collector.collect()\n        self.assertEqual([_(\"pipeline/js/dummy.junk\")], list(paths))\n\n    def tearDown(self):\n        default_collector.clear()\n\n\n@pipeline_settings(COMPILERS=[\"tests.tests.test_compiler.CompilerWithEmptyFirstArg\"])\nclass CompilerWithEmptyFirstArgTest(TestCase):\n    def setUp(self):\n        default_collector.collect()\n        self.compiler = Compiler()\n\n    def test_compile(self):\n        paths = self.compiler.compile([_(\"pipeline/js/dummy.coffee\")])\n        default_collector.collect()\n        self.assertEqual([_(\"pipeline/js/dummy.junk\")], list(paths))\n\n    def tearDown(self):\n        default_collector.clear()\n\n\n@pipeline_settings(COMPILERS=[\"tests.tests.test_compiler.InvalidCompiler\"])\nclass InvalidCompilerTest(TestCase):\n    def setUp(self):\n        default_collector.collect()\n        self.compiler = Compiler()\n\n    def test_compile(self):\n        with self.assertRaises(CompilerError) as cm:\n            self.compiler.compile([_(\"pipeline/js/dummy.coffee\")])\n            e = cm.exception\n            self.assertEqual(\n                e.command,\n                [\n                    \"this-exists-nowhere-as-a-command-and-should-fail\",\n                    \"pipeline/js/dummy.coffee\",\n                    \"pipeline/js/dummy.junk\",\n                ],\n            )\n            self.assertEqual(e.error_output, \"\")\n\n    def tearDown(self):\n        default_collector.clear()\n\n\n@skipIf(sys.platform.startswith(\"win\"), \"requires posix platform\")\n@pipeline_settings(COMPILERS=[\"tests.tests.test_compiler.FailingCompiler\"])\nclass FailingCompilerTest(TestCase):\n    def setUp(self):\n        default_collector.collect()\n        self.compiler = Compiler()\n\n    def test_compile(self):\n        with self.assertRaises(CompilerError) as cm:\n            self.compiler.compile([_(\"pipeline/js/dummy.coffee\")])\n\n            e = cm.exception\n            self.assertEqual(e.command, [\"/usr/bin/env\", \"false\"])\n            self.assertEqual(e.error_output, \"\")\n\n    def tearDown(self):\n        default_collector.clear()\n\n\n@skipUnless(settings.HAS_NODE, \"requires node\")\nclass CompilerImplementation(TestCase):\n    def setUp(self):\n        self.compiler = Compiler()\n        default_collector.collect(RequestFactory().get(\"/\"))\n\n    def tearDown(self):\n        default_collector.clear()\n\n    def _test_compiler(self, compiler_cls_str, infile, expected):\n        compiler_cls = to_class(compiler_cls_str)\n        compiler = compiler_cls(verbose=False, storage=staticfiles_storage)\n        infile_path = staticfiles_storage.path(infile)\n        outfile_path = compiler.output_path(infile_path, compiler.output_extension)\n        compiler.compile_file(_(infile_path), _(outfile_path), force=True)\n        with open(outfile_path) as f:\n            result = f.read()\n        with staticfiles_storage.open(expected, \"r\") as f:\n            expected = f.read()\n        self.assertEqual(result, expected)\n\n    def test_sass(self):\n        self._test_compiler(\n            \"pipeline.compilers.sass.SASSCompiler\",\n            \"pipeline/compilers/scss/input.scss\",\n            \"pipeline/compilers/scss/expected.css\",\n        )\n\n    def test_coffeescript(self):\n        self._test_compiler(\n            \"pipeline.compilers.coffee.CoffeeScriptCompiler\",\n            \"pipeline/compilers/coffee/input.coffee\",\n            \"pipeline/compilers/coffee/expected.js\",\n        )\n\n    def test_less(self):\n        self._test_compiler(\n            \"pipeline.compilers.less.LessCompiler\",\n            \"pipeline/compilers/less/input.less\",\n            \"pipeline/compilers/less/expected.css\",\n        )\n\n    def test_es6(self):\n        self._test_compiler(\n            \"pipeline.compilers.es6.ES6Compiler\",\n            \"pipeline/compilers/es6/input.es6\",\n            \"pipeline/compilers/es6/expected.js\",\n        )\n\n    def test_typescript(self):\n        self._test_compiler(\n            \"pipeline.compilers.typescript.TypeScriptCompiler\",\n            \"pipeline/compilers/typescript/input.ts\",\n            \"pipeline/compilers/typescript/expected.js\",\n        )\n\n    def test_stylus(self):\n        self._test_compiler(\n            \"pipeline.compilers.stylus.StylusCompiler\",\n            \"pipeline/compilers/stylus/input.styl\",\n            \"pipeline/compilers/stylus/expected.css\",\n        )\n\n    def test_livescript(self):\n        self._test_compiler(\n            \"pipeline.compilers.livescript.LiveScriptCompiler\",\n            \"pipeline/compilers/livescript/input.ls\",\n            \"pipeline/compilers/livescript/expected.js\",\n        )\n"
  },
  {
    "path": "tests/tests/test_compressor.py",
    "content": "import base64\nimport os\nimport sys\n\ntry:\n    from unittest.mock import patch\nexcept ImportError:\n    from unittest.mock import patch  # noqa\n\nfrom unittest import skipIf, skipUnless\n\nfrom django.conf import settings\nfrom django.test import TestCase\nfrom django.test.client import RequestFactory\n\nfrom pipeline.collector import default_collector\nfrom pipeline.compressors import (\n    CSS_REWRITE_PATH_RE,\n    JS_REWRITE_PATH_RE,\n    TEMPLATE_FUNC,\n    Compressor,\n    SubProcessCompressor,\n)\nfrom pipeline.compressors.yuglify import YuglifyCompressor\nfrom tests.utils import _, pipeline_settings\n\n\n@pipeline_settings(\n    CSS_COMPRESSOR=\"pipeline.compressors.yuglify.YuglifyCompressor\",\n    JS_COMPRESSOR=\"pipeline.compressors.yuglify.YuglifyCompressor\",\n)\nclass CompressorTest(TestCase):\n    def setUp(self):\n        self.maxDiff = None\n        self.compressor = Compressor()\n        default_collector.collect()\n\n    def test_js_compressor_class(self):\n        self.assertEqual(self.compressor.js_compressor, YuglifyCompressor)\n\n    def test_css_compressor_class(self):\n        self.assertEqual(self.compressor.css_compressor, YuglifyCompressor)\n\n    def test_concatenate_and_rewrite(self):\n        css = self.compressor.concatenate_and_rewrite(\n            [_(\"pipeline/css/first.css\"), _(\"pipeline/css/second.css\")],\n            \"css/screen.css\",\n        )\n        expected = \"\"\".concat {\\n  display: none;\\n}\\n\\n.concatenate {\\n  display: block;\\n}\\n\"\"\"  # noqa\n        self.assertEqual(expected, css)\n\n    def test_concatenate(self):\n        js = self.compressor.concatenate(\n            [_(\"pipeline/js/first.js\"), _(\"pipeline/js/second.js\")]\n        )\n        expected = \"\"\"(function() {\\n  window.concat = function() {\\n    console.log(arguments);\\n  }\\n}()) // No semicolon\\n\\n;(function() {\\n  window.cat = function() {\\n    console.log(\"hello world\");\\n  }\\n}());\\n\"\"\"  # noqa\n        self.assertEqual(expected, js)\n\n    @patch.object(base64, \"b64encode\")\n    def test_encoded_content(self, mock):\n        self.compressor.asset_contents.clear()\n        self.compressor.encoded_content(_(\"pipeline/images/arrow.png\"))\n        self.assertTrue(mock.called)\n        mock.reset_mock()\n        self.compressor.encoded_content(_(\"pipeline/images/arrow.png\"))\n        self.assertFalse(mock.called)\n\n    def test_encoded_content_output(self):\n        self.compressor.asset_contents.clear()\n        encoded = self.compressor.encoded_content(_(\"pipeline/images/arrow.png\"))\n        expected = (\n            \"iVBORw0KGgoAAAANSUhEUgAAAAkAAAAGCAYAAAARx7TFAAAAMk\"\n            \"lEQVR42oXKwQkAMAxC0Q7rEk5voSEepCHC9/SOpLV3JPULgArV\"\n            \"RtDIMEEiQ4NECRNdciCfK3K3wvEAAAAASUVORK5CYII=\"\n        )\n        self.assertEqual(encoded, expected)\n\n    def test_relative_path(self):\n        relative_path = self.compressor.relative_path(\n            \"images/sprite.png\",\n            \"css/screen.css\",\n        )\n        self.assertEqual(relative_path, \"../images/sprite.png\")\n\n    def test_base_path(self):\n        base_path = self.compressor.base_path(\n            [_(\"js/templates/form.jst\"), _(\"js/templates/field.jst\")]\n        )\n        self.assertEqual(base_path, _(\"js/templates\"))\n\n    def test_absolute_path(self):\n        absolute_path = self.compressor.absolute_path(\n            \"../../images/sprite.png\", \"css/plugins/\"\n        )\n        self.assertEqual(absolute_path, \"images/sprite.png\")\n        absolute_path = self.compressor.absolute_path(\n            \"/images/sprite.png\", \"css/plugins/\"\n        )\n        self.assertEqual(absolute_path, \"/images/sprite.png\")\n\n    def test_template_name(self):\n        name = self.compressor.template_name(\"templates/photo/detail.jst\", \"templates/\")\n        self.assertEqual(name, \"photo_detail\")\n        name = self.compressor.template_name(\"templates/photo_edit.jst\", \"\")\n        self.assertEqual(name, \"photo_edit\")\n        name = self.compressor.template_name(\n            r\"templates\\photo\\detail.jst\",  # noqa\n            \"templates\\\\\",\n        )\n        self.assertEqual(name, \"photo_detail\")\n\n    @pipeline_settings(TEMPLATE_SEPARATOR=\"/\")\n    def test_template_name_separator(self):\n        name = self.compressor.template_name(\"templates/photo/detail.jst\", \"templates/\")\n        self.assertEqual(name, \"photo/detail\")\n        name = self.compressor.template_name(\"templates/photo_edit.jst\", \"\")\n        self.assertEqual(name, \"photo_edit\")\n        name = self.compressor.template_name(\n            r\"templates\\photo\\detail.jst\",  # noqa\n            \"templates\\\\\",\n        )\n        self.assertEqual(name, \"photo/detail\")\n\n    def test_compile_templates(self):\n        templates = self.compressor.compile_templates(\n            [_(\"pipeline/templates/photo/list.jst\")]\n        )\n        self.assertEqual(\n            templates,\n            \"\"\"window.JST = window.JST || {};\\n%s\\nwindow.JST[\\'list\\'] = template(\\'<div class=\"photo\">\\\\n <img src=\"<%%= src %%>\" />\\\\n <div class=\"caption\">\\\\n  <%%= caption %%>\\\\n </div>\\\\n</div>\\');\\n\"\"\"  # noqa\n            % TEMPLATE_FUNC,\n        )\n        templates = self.compressor.compile_templates(\n            [\n                _(\"pipeline/templates/video/detail.jst\"),\n                _(\"pipeline/templates/photo/detail.jst\"),\n            ]\n        )\n        self.assertEqual(\n            templates,\n            \"\"\"window.JST = window.JST || {};\\n%s\\nwindow.JST[\\'video_detail\\'] = template(\\'<div class=\"video\">\\\\n <video src=\"<%%= src %%>\" />\\\\n <div class=\"caption\">\\\\n  <%%= description %%>\\\\n </div>\\\\n</div>\\');\\nwindow.JST[\\'photo_detail\\'] = template(\\'<div class=\"photo\">\\\\n <img src=\"<%%= src %%>\" />\\\\n <div class=\"caption\">\\\\n  <%%= caption %%> by <%%= author %%>\\\\n </div>\\\\n</div>\\');\\n\"\"\"  # noqa\n            % TEMPLATE_FUNC,\n        )\n\n    def test_embeddable(self):\n        self.assertFalse(\n            self.compressor.embeddable(_(\"pipeline/images/sprite.png\"), None)\n        )\n        self.assertFalse(\n            self.compressor.embeddable(_(\"pipeline/images/arrow.png\"), \"datauri\")\n        )\n        self.assertTrue(\n            self.compressor.embeddable(_(\"pipeline/images/embed/arrow.png\"), \"datauri\")\n        )\n        self.assertFalse(\n            self.compressor.embeddable(_(\"pipeline/images/arrow.dat\"), \"datauri\")\n        )\n\n    def test_construct_asset_path(self):\n        asset_path = self.compressor.construct_asset_path(\n            \"../../images/sprite.png\", \"css/plugins/gallery.css\", \"css/gallery.css\"\n        )\n        self.assertEqual(asset_path, \"../images/sprite.png\")\n        asset_path = self.compressor.construct_asset_path(\n            \"/images/sprite.png\", \"css/plugins/gallery.css\", \"css/gallery.css\"\n        )\n        self.assertEqual(asset_path, \"/images/sprite.png\")\n\n    def test_concatenate_with_url_rewrite(self) -> None:\n        output = self.compressor.concatenate(\n            [\n                _(\"pipeline/css/urls.css\"),\n            ],\n            file_sep=\"\",\n            output_filename=\"css/screen.css\",\n            rewrite_path_re=CSS_REWRITE_PATH_RE,\n        )\n\n        self.assertEqual(\n            \"\"\".embedded-url-svg {\n  background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath      stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%     3C/svg%3E\");\n}\n@font-face {\n  font-family: 'Pipeline';\n  src: url('../pipeline/fonts/pipeline.eot');\n  src: url('../pipeline/fonts/pipeline.eot?#iefix') format('embedded-opentype');\n  src: local('☺'), url('../pipeline/fonts/pipeline.woff') format('woff'), url('../pipeline/fonts/pipeline.ttf') format('truetype'), url('../pipeline/fonts/pipeline.svg#IyfZbseF') format('svg');\n  font-weight: normal;\n  font-style: normal;\n}\n.relative-url {\n  background-image: url(../pipeline/images/sprite-buttons.png);\n}\n.relative-url-querystring {\n  background-image: url(../pipeline/images/sprite-buttons.png?v=1.0#foo=bar);\n}\n.absolute-url {\n  background-image: url(/images/sprite-buttons.png);\n}\n.absolute-full-url {\n  background-image: url(http://localhost/images/sprite-buttons.png);\n}\n.no-protocol-url {\n  background-image: url(//images/sprite-buttons.png);\n}\n.anchor-tag-url {\n  background-image: url(#image-gradient);\n}\n@font-face{src:url(../pipeline/fonts/pipeline.eot);src:url(../pipeline/fonts/pipeline.eot?#iefix) format('embedded-opentype'),url(../pipeline/fonts/pipeline.woff) format('woff'),url(../pipeline/fonts/pipeline.ttf) format('truetype');}\n\"\"\",  # noqa\n            output,\n        )\n\n    def test_concatenate_with_url_rewrite_data_uri(self):\n        output = self.compressor.concatenate(\n            [\n                _(\"pipeline/css/nested/nested.css\"),\n            ],\n            file_sep=\"\",\n            output_filename=\"pipeline/screen.css\",\n            rewrite_path_re=CSS_REWRITE_PATH_RE,\n        )\n\n        self.assertEqual(\n            \"\"\".data-url {\n  background-image: url(data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2212px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2012%2014%22%20style%3D%22enable-background%3Anew%200%200%2012%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M11%2C6V5c0-2.762-2.239-5-5-5S1%2C2.238%2C1%2C5v1H0v8h12V6H11z%20M6.5%2C9.847V12h-1V9.847C5.207%2C9.673%2C5%2C9.366%2C5%2C9%20c0-0.553%2C0.448-1%2C1-1s1%2C0.447%2C1%2C1C7%2C9.366%2C6.793%2C9.673%2C6.5%2C9.847z%20M9%2C6H3V5c0-1.657%2C1.343-3%2C3-3s3%2C1.343%2C3%2C3V6z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E);\n}\n.data-url-quoted {\n  background-image: url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2212px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2012%2014%22%20style%3D%22enable-background%3Anew%200%200%2012%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M11%2C6V5c0-2.762-2.239-5-5-5S1%2C2.238%2C1%2C5v1H0v8h12V6H11z%20M6.5%2C9.847V12h-1V9.847C5.207%2C9.673%2C5%2C9.366%2C5%2C9%20c0-0.553%2C0.448-1%2C1-1s1%2C0.447%2C1%2C1C7%2C9.366%2C6.793%2C9.673%2C6.5%2C9.847z%20M9%2C6H3V5c0-1.657%2C1.343-3%2C3-3s3%2C1.343%2C3%2C3V6z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E');\n}\n\"\"\",  # noqa\n            output,\n        )\n\n    def test_concatenate_css_with_sourcemap(self) -> None:\n        output = self.compressor.concatenate(\n            [\n                _(\"pipeline/css/sourcemap.css\"),\n            ],\n            file_sep=\"\",\n            output_filename=\"css/sourcemap-bundle.css\",\n            rewrite_path_re=CSS_REWRITE_PATH_RE,\n        )\n\n        self.assertEqual(\n            output,\n            \"div {\\n\"\n            \"  display: inline;\\n\"\n            \"}\\n\"\n            \"\\n\"\n            \"span {\\n\"\n            \"  display: block;\\n\"\n            \"}\\n\"\n            \"\\n\"\n            \"\\n\"\n            \"//#  sourceMappingURL=../pipeline/css/sourcemap1.css.map\\n\"\n            \"\\n\"\n            \"//@ sourceMappingURL=../pipeline/css/sourcemap2.css.map  \\n\"\n            \"\\n\"\n            \"/*#  sourceMappingURL=../pipeline/css/sourcemap3.css.map */\\n\"\n            \"\\n\"\n            \"/*@ sourceMappingURL=../pipeline/css/sourcemap4.css.map  */\\n\"\n            \"\\n\"\n            \"//#  sourceURL=../pipeline/css/sourcemap5.css.map\\n\"\n            \"\\n\"\n            \"//@ sourceURL=../pipeline/css/sourcemap6.css.map  \\n\"\n            \"\\n\"\n            \"/*#  sourceURL=../pipeline/css/sourcemap7.css.map */\\n\"\n            \"\\n\"\n            \"/*@ sourceURL=../pipeline/css/sourcemap8.css.map  */\\n\",\n        )\n\n    def test_concatenate_js_with_sourcemap(self) -> None:\n        output = self.compressor.concatenate(\n            [\n                _(\"pipeline/js/sourcemap.js\"),\n            ],\n            file_sep=\";\",\n            output_filename=\"js/sourcemap-bundle.js\",\n            rewrite_path_re=JS_REWRITE_PATH_RE,\n        )\n\n        self.assertEqual(\n            output,\n            \"const abc = 123;\\n\"\n            \"\\n\"\n            \"\\n\"\n            \"//#  sourceMappingURL=../pipeline/js/sourcemap1.js.map\\n\"\n            \"\\n\"\n            \"//@ sourceMappingURL=../pipeline/js/sourcemap2.js.map  \\n\"\n            \"\\n\"\n            \"/*#  sourceMappingURL=../pipeline/js/sourcemap3.js.map */\\n\"\n            \"\\n\"\n            \"/*@ sourceMappingURL=../pipeline/js/sourcemap4.js.map  */\\n\"\n            \"\\n\"\n            \"//#  sourceURL=../pipeline/js/sourcemap5.js.map\\n\"\n            \"\\n\"\n            \"//@ sourceURL=../pipeline/js/sourcemap6.js.map  \\n\"\n            \"\\n\"\n            \"/*#  sourceURL=../pipeline/js/sourcemap7.js.map */\\n\"\n            \"\\n\"\n            \"/*@ sourceURL=../pipeline/js/sourcemap8.js.map  */\\n\",\n        )\n\n    def test_concatenate_without_rewrite_path_re(self) -> None:\n        message = (\n            \"Compressor.concatenate() was called without passing \"\n            \"rewrite_path_re_= or output_filename=. If you are \"\n            \"specializing Compressor, please update your call \"\n            \"to remain compatible with future changes.\"\n        )\n\n        with self.assertWarnsMessage(DeprecationWarning, message):\n            output = self.compressor.concatenate(\n                [\n                    _(\"pipeline/js/sourcemap.js\"),\n                ],\n                file_sep=\";\",\n                output_filename=\"js/sourcemap-bundle.js\",\n            )\n\n        self.assertEqual(\n            output,\n            \"const abc = 123;\\n\"\n            \"\\n\"\n            \"\\n\"\n            \"//#  sourceMappingURL=sourcemap1.js.map\\n\"\n            \"\\n\"\n            \"//@ sourceMappingURL=sourcemap2.js.map  \\n\"\n            \"\\n\"\n            \"/*#  sourceMappingURL=sourcemap3.js.map */\\n\"\n            \"\\n\"\n            \"/*@ sourceMappingURL=sourcemap4.js.map  */\\n\"\n            \"\\n\"\n            \"//#  sourceURL=sourcemap5.js.map\\n\"\n            \"\\n\"\n            \"//@ sourceURL=sourcemap6.js.map  \\n\"\n            \"\\n\"\n            \"/*#  sourceURL=sourcemap7.js.map */\\n\"\n            \"\\n\"\n            \"/*@ sourceURL=sourcemap8.js.map  */\\n\",\n        )\n\n    def test_concatenate_without_output_filename(self) -> None:\n        message = (\n            \"Compressor.concatenate() was called without passing \"\n            \"rewrite_path_re_= or output_filename=. If you are \"\n            \"specializing Compressor, please update your call \"\n            \"to remain compatible with future changes.\"\n        )\n\n        with self.assertWarnsMessage(DeprecationWarning, message):\n            output = self.compressor.concatenate(\n                [\n                    _(\"pipeline/js/sourcemap.js\"),\n                ],\n                file_sep=\";\",\n                rewrite_path_re=JS_REWRITE_PATH_RE,\n            )\n\n        self.assertEqual(\n            output,\n            \"const abc = 123;\\n\"\n            \"\\n\"\n            \"\\n\"\n            \"//#  sourceMappingURL=sourcemap1.js.map\\n\"\n            \"\\n\"\n            \"//@ sourceMappingURL=sourcemap2.js.map  \\n\"\n            \"\\n\"\n            \"/*#  sourceMappingURL=sourcemap3.js.map */\\n\"\n            \"\\n\"\n            \"/*@ sourceMappingURL=sourcemap4.js.map  */\\n\"\n            \"\\n\"\n            \"//#  sourceURL=sourcemap5.js.map\\n\"\n            \"\\n\"\n            \"//@ sourceURL=sourcemap6.js.map  \\n\"\n            \"\\n\"\n            \"/*#  sourceURL=sourcemap7.js.map */\\n\"\n            \"\\n\"\n            \"/*@ sourceURL=sourcemap8.js.map  */\\n\",\n        )\n\n    def test_concatenate_without_file_sep(self) -> None:\n        message = (\n            \"Compressor.concatenate() was called without passing \"\n            \"file_sep=. If you are specializing Compressor, please \"\n            \"update your call to remain compatible with future changes. \"\n            \"Defaulting to JavaScript behavior for \"\n            \"backwards-compatibility.\"\n        )\n\n        with self.assertWarnsMessage(DeprecationWarning, message):\n            output = self.compressor.concatenate(\n                [\n                    _(\"pipeline/js/first.js\"),\n                    _(\"pipeline/js/second.js\"),\n                ],\n                output_filename=\"js/sourcemap-bundle.js\",\n                rewrite_path_re=JS_REWRITE_PATH_RE,\n            )\n\n        self.assertEqual(\n            output,\n            \"(function() {\\n\"\n            \"  window.concat = function() {\\n\"\n            \"    console.log(arguments);\\n\"\n            \"  }\\n\"\n            \"}()) // No semicolon\\n\"\n            \"\\n\"\n            \";(function() {\\n\"\n            \"  window.cat = function() {\\n\"\n            '    console.log(\"hello world\");\\n'\n            \"  }\\n\"\n            \"}());\\n\",\n        )\n\n    def test_legacy_concatenate_and_rewrite(self) -> None:\n        message = (\n            \"Compressor.concatenate_and_rewrite() is deprecated. Please \"\n            \"call concatenate() instead.\"\n        )\n\n        with self.assertWarnsMessage(DeprecationWarning, message):\n            output = self.compressor.concatenate_and_rewrite(\n                [\n                    _(\"pipeline/css/urls.css\"),\n                ],\n                \"css/screen.css\",\n            )\n\n        self.assertEqual(\n            \"\"\".embedded-url-svg {\n  background-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath      stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%     3C/svg%3E\");\n}\n@font-face {\n  font-family: 'Pipeline';\n  src: url('../pipeline/fonts/pipeline.eot');\n  src: url('../pipeline/fonts/pipeline.eot?#iefix') format('embedded-opentype');\n  src: local('☺'), url('../pipeline/fonts/pipeline.woff') format('woff'), url('../pipeline/fonts/pipeline.ttf') format('truetype'), url('../pipeline/fonts/pipeline.svg#IyfZbseF') format('svg');\n  font-weight: normal;\n  font-style: normal;\n}\n.relative-url {\n  background-image: url(../pipeline/images/sprite-buttons.png);\n}\n.relative-url-querystring {\n  background-image: url(../pipeline/images/sprite-buttons.png?v=1.0#foo=bar);\n}\n.absolute-url {\n  background-image: url(/images/sprite-buttons.png);\n}\n.absolute-full-url {\n  background-image: url(http://localhost/images/sprite-buttons.png);\n}\n.no-protocol-url {\n  background-image: url(//images/sprite-buttons.png);\n}\n.anchor-tag-url {\n  background-image: url(#image-gradient);\n}\n@font-face{src:url(../pipeline/fonts/pipeline.eot);src:url(../pipeline/fonts/pipeline.eot?#iefix) format('embedded-opentype'),url(../pipeline/fonts/pipeline.woff) format('woff'),url(../pipeline/fonts/pipeline.ttf) format('truetype');}\n\"\"\",  # noqa\n            output,\n        )\n\n    def test_legacy_concatenate_and_rewrite_with_data_uri(self) -> None:\n        message = (\n            \"Compressor.concatenate_and_rewrite() is deprecated. Please \"\n            \"call concatenate() instead.\"\n        )\n\n        with self.assertWarnsMessage(DeprecationWarning, message):\n            output = self.compressor.concatenate_and_rewrite(\n                [\n                    _(\"pipeline/css/nested/nested.css\"),\n                ],\n                \"pipeline/screen.css\",\n            )\n\n        self.assertEqual(\n            \"\"\".data-url {\n  background-image: url(data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2212px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2012%2014%22%20style%3D%22enable-background%3Anew%200%200%2012%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M11%2C6V5c0-2.762-2.239-5-5-5S1%2C2.238%2C1%2C5v1H0v8h12V6H11z%20M6.5%2C9.847V12h-1V9.847C5.207%2C9.673%2C5%2C9.366%2C5%2C9%20c0-0.553%2C0.448-1%2C1-1s1%2C0.447%2C1%2C1C7%2C9.366%2C6.793%2C9.673%2C6.5%2C9.847z%20M9%2C6H3V5c0-1.657%2C1.343-3%2C3-3s3%2C1.343%2C3%2C3V6z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E);\n}\n.data-url-quoted {\n  background-image: url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22iso-8859-1%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11.dtd%22%3E%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20%20width%3D%2212px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2012%2014%22%20style%3D%22enable-background%3Anew%200%200%2012%2014%3B%22%20xml%3Aspace%3D%22preserve%22%3E%3Cpath%20d%3D%22M11%2C6V5c0-2.762-2.239-5-5-5S1%2C2.238%2C1%2C5v1H0v8h12V6H11z%20M6.5%2C9.847V12h-1V9.847C5.207%2C9.673%2C5%2C9.366%2C5%2C9%20c0-0.553%2C0.448-1%2C1-1s1%2C0.447%2C1%2C1C7%2C9.366%2C6.793%2C9.673%2C6.5%2C9.847z%20M9%2C6H3V5c0-1.657%2C1.343-3%2C3-3s3%2C1.343%2C3%2C3V6z%22%2F%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E');\n}\n\"\"\",  # noqa\n            output,\n        )\n\n    @skipIf(sys.platform.startswith(\"win\"), \"requires posix platform\")\n    def test_compressor_subprocess_unicode(self):\n        path = os.path.dirname(os.path.dirname(__file__))\n        content = open(path + \"/assets/css/unicode.css\", encoding=\"utf-8\").read()\n        output = SubProcessCompressor(False).execute_command((\"cat\",), content)\n        self.assertEqual(\n            \"\"\".some_class {\n  // Some unicode\n  content: \"áéíóú\";\n}\n\"\"\",\n            output,\n        )\n\n    def tearDown(self):\n        default_collector.clear()\n\n\nclass CompressorImplementationTest(TestCase):\n    maxDiff = None\n\n    def setUp(self):\n        self.compressor = Compressor()\n        default_collector.collect(RequestFactory().get(\"/\"))\n\n    def tearDown(self):\n        default_collector.clear()\n\n    def _test_compressor(self, compressor_cls, compress_type, expected_file):\n        override_settings = {\n            (f\"{compress_type.upper()}_COMPRESSOR\"): compressor_cls,\n        }\n        with pipeline_settings(**override_settings):\n            if compress_type == \"js\":\n                result = self.compressor.compress_js(\n                    [_(\"pipeline/js/first.js\"), _(\"pipeline/js/second.js\")]\n                )\n            else:\n                result = self.compressor.compress_css(\n                    [_(\"pipeline/css/first.css\"), _(\"pipeline/css/second.css\")],\n                    os.path.join(\"pipeline\", \"css\", os.path.basename(expected_file)),\n                )\n        with self.compressor.storage.open(expected_file, \"r\") as f:\n            expected = f.read()\n        self.assertEqual(result, expected)\n\n    def test_jsmin(self):\n        self._test_compressor(\n            \"pipeline.compressors.jsmin.JSMinCompressor\",\n            \"js\",\n            \"pipeline/compressors/jsmin.js\",\n        )\n\n    def test_csshtmljsminify(self):\n        self._test_compressor(\n            \"pipeline.compressors.csshtmljsminify.CssHtmlJsMinifyCompressor\",\n            \"css\",\n            \"pipeline/compressors/csshtmljsminify.css\",\n        )\n        self._test_compressor(\n            \"pipeline.compressors.csshtmljsminify.CssHtmlJsMinifyCompressor\",\n            \"js\",\n            \"pipeline/compressors/csshtmljsminify.js\",\n        )\n\n    @skipUnless(settings.HAS_NODE, \"requires node\")\n    def test_uglifyjs(self):\n        self._test_compressor(\n            \"pipeline.compressors.uglifyjs.UglifyJSCompressor\",\n            \"js\",\n            \"pipeline/compressors/uglifyjs.js\",\n        )\n\n    @skipUnless(settings.HAS_NODE, \"requires node\")\n    def test_terser(self):\n        self._test_compressor(\n            \"pipeline.compressors.terser.TerserCompressor\",\n            \"js\",\n            \"pipeline/compressors/terser.js\",\n        )\n\n    @skipUnless(settings.HAS_NODE, \"requires node\")\n    def test_yuglify(self):\n        self._test_compressor(\n            \"pipeline.compressors.yuglify.YuglifyCompressor\",\n            \"css\",\n            \"pipeline/compressors/yuglify.css\",\n        )\n        self._test_compressor(\n            \"pipeline.compressors.yuglify.YuglifyCompressor\",\n            \"js\",\n            \"pipeline/compressors/yuglify.js\",\n        )\n\n    @skipUnless(settings.HAS_NODE, \"requires node\")\n    def test_cssmin(self):\n        self._test_compressor(\n            \"pipeline.compressors.cssmin.CSSMinCompressor\",\n            \"css\",\n            \"pipeline/compressors/cssmin.css\",\n        )\n\n    @skipUnless(settings.HAS_NODE, \"requires node\")\n    @skipUnless(settings.HAS_JAVA, \"requires java\")\n    def test_closure(self):\n        self._test_compressor(\n            \"pipeline.compressors.closure.ClosureCompressor\",\n            \"js\",\n            \"pipeline/compressors/closure.js\",\n        )\n\n    @skipUnless(settings.HAS_NODE, \"requires node\")\n    @skipUnless(settings.HAS_JAVA, \"requires java\")\n    def test_yui_js(self):\n        self._test_compressor(\n            \"pipeline.compressors.yui.YUICompressor\",\n            \"js\",\n            \"pipeline/compressors/yui.js\",\n        )\n\n    @skipUnless(settings.HAS_NODE, \"requires node\")\n    @skipUnless(settings.HAS_JAVA, \"requires java\")\n    def test_yui_css(self):\n        self._test_compressor(\n            \"pipeline.compressors.yui.YUICompressor\",\n            \"css\",\n            \"pipeline/compressors/yui.css\",\n        )\n\n    @skipUnless(settings.HAS_CSSTIDY, \"requires csstidy\")\n    def test_csstidy(self):\n        self._test_compressor(\n            \"pipeline.compressors.csstidy.CSSTidyCompressor\",\n            \"css\",\n            \"pipeline/compressors/csstidy.css\",\n        )\n"
  },
  {
    "path": "tests/tests/test_conf.py",
    "content": "import sys\nfrom unittest import skipIf, skipUnless\n\nfrom django.test import TestCase\n\nfrom pipeline.conf import PipelineSettings\n\n\nclass TestSettings(TestCase):\n    def test_3unicode(self):\n        s = PipelineSettings({\"FOO_BINARY\": \"env actualprogram\"})\n        self.assertEqual(s.FOO_BINARY, (\"env\", \"actualprogram\"))\n\n    def test_2unicode(self):\n        s = PipelineSettings({\"FOO_BINARY\": \"env actualprogram\"})\n        self.assertEqual(s.FOO_BINARY, (\"env\", \"actualprogram\"))\n\n    def test_2bytes(self):\n        s = PipelineSettings({\"FOO_BINARY\": \"env actualprogram\"})\n        self.assertEqual(s.FOO_BINARY, (\"env\", \"actualprogram\"))\n\n    def test_expected_splitting(self):\n        s = PipelineSettings({\"FOO_BINARY\": \"env actualprogram\"})\n        self.assertEqual(s.FOO_BINARY, (\"env\", \"actualprogram\"))\n\n    @skipIf(sys.platform.startswith(\"win\"), \"requires posix platform\")\n    def test_expected_preservation(self):\n        s = PipelineSettings({\"FOO_BINARY\": r\"actual\\ program\"})\n        self.assertEqual(s.FOO_BINARY, (\"actual program\",))\n\n    @skipUnless(sys.platform.startswith(\"win\"), \"requires windows\")\n    def test_win_path_preservation(self):\n        s = PipelineSettings({\"FOO_BINARY\": \"C:\\\\Test\\\\ActualProgram.exe argument\"})\n        self.assertEqual(s.FOO_BINARY, (\"C:\\\\Test\\\\ActualProgram.exe\", \"argument\"))\n\n    def test_tuples_are_normal(self):\n        s = PipelineSettings({\"FOO_ARGUMENTS\": (\"explicit\", \"with\", \"args\")})\n        self.assertEqual(s.FOO_ARGUMENTS, (\"explicit\", \"with\", \"args\"))\n"
  },
  {
    "path": "tests/tests/test_forms.py",
    "content": "from django import get_version as django_version\nfrom django.forms import Media\nfrom django.test import TestCase\n\nfrom pipeline.forms import PipelineFormMedia\n\nfrom ..utils import pipeline_settings\n\n\n@pipeline_settings(\n    PIPELINE_COLLECTOR_ENABLED=False,\n    STYLESHEETS={\n        \"styles1\": {\n            \"source_filenames\": (\n                \"pipeline/css/first.css\",\n                \"pipeline/css/second.css\",\n            ),\n            \"output_filename\": \"styles1.min.css\",\n        },\n        \"styles2\": {\n            \"source_filenames\": (\"pipeline/css/unicode.css\",),\n            \"output_filename\": \"styles2.min.css\",\n        },\n        \"print\": {\n            \"source_filenames\": (\"pipeline/css/urls.css\",),\n            \"output_filename\": \"print.min.css\",\n        },\n    },\n    JAVASCRIPT={\n        \"scripts1\": {\n            \"source_filenames\": (\n                \"pipeline/js/first.js\",\n                \"pipeline/js/second.js\",\n            ),\n            \"output_filename\": \"scripts1.min.js\",\n        },\n        \"scripts2\": {\n            \"source_filenames\": (\"pipeline/js/application.js\",),\n            \"output_filename\": \"scripts2.min.js\",\n        },\n    },\n)\nclass PipelineFormMediaTests(TestCase):\n    \"\"\"Unit tests for pipeline.forms.PipelineFormMedia.\"\"\"\n\n    @pipeline_settings(PIPELINE_ENABLED=True)\n    def test_css_packages_with_pipeline_enabled(self):\n        \"\"\"Testing PipelineFormMedia.css_packages with PIPELINE_ENABLED=True\"\"\"\n\n        class MyMedia(PipelineFormMedia):\n            css_packages = {\n                \"all\": (\"styles1\", \"styles2\"),\n                \"print\": (\"print\",),\n            }\n\n            css = {\"all\": (\"extra1.css\", \"extra2.css\")}\n\n        media = Media(MyMedia)\n\n        self.assertEqual(\n            MyMedia.css,\n            {\n                \"all\": [\n                    \"extra1.css\",\n                    \"extra2.css\",\n                    \"/static/styles1.min.css\",\n                    \"/static/styles2.min.css\",\n                ],\n                \"print\": [\"/static/print.min.css\"],\n            },\n        )\n        self.assertEqual(MyMedia.css, media._css)\n        expected_regex = [\n            r'<link href=\"{}\"( type=\"text/css\")? media=\"all\" '\n            'rel=\"stylesheet\"( /)?>'.format(path)\n            for path in (\n                \"/static/extra1.css\",\n                \"/static/extra2.css\",\n                \"/static/styles1.min.css\",\n                \"/static/styles2.min.css\",\n            )\n        ] + [\n            r'<link href=\"/static/print.min.css\" (type=\"text/css\" )?'\n            'media=\"print\" rel=\"stylesheet\"( /)?>'\n        ]\n        for rendered_node, expected_node in zip(media.render_css(), expected_regex):\n            self.assertRegex(rendered_node, expected_node)\n\n    @pipeline_settings(PIPELINE_ENABLED=False)\n    def test_css_packages_with_pipeline_disabled(self):\n        \"\"\"Testing PipelineFormMedia.css_packages with PIPELINE_ENABLED=False\"\"\"\n\n        class MyMedia(PipelineFormMedia):\n            css_packages = {\n                \"all\": (\"styles1\", \"styles2\"),\n                \"print\": (\"print\",),\n            }\n\n            css = {\"all\": (\"extra1.css\", \"extra2.css\")}\n\n        media = Media(MyMedia)\n\n        self.assertEqual(\n            MyMedia.css,\n            {\n                \"all\": [\n                    \"extra1.css\",\n                    \"extra2.css\",\n                    \"pipeline/css/first.css\",\n                    \"pipeline/css/second.css\",\n                    \"pipeline/css/unicode.css\",\n                ],\n                \"print\": [\"pipeline/css/urls.css\"],\n            },\n        )\n        self.assertEqual(MyMedia.css, media._css)\n\n        expected_regex = [\n            '<link href=\"{}\"( type=\"text/css\")? media=\"all\" '\n            'rel=\"stylesheet\"( /)?>'.format(path)\n            for path in (\n                \"/static/extra1.css\",\n                \"/static/extra2.css\",\n                \"/static/pipeline/css/first.css\",\n                \"/static/pipeline/css/second.css\",\n                \"/static/pipeline/css/unicode.css\",\n            )\n        ] + [\n            '<link href=\"/static/pipeline/css/urls.css\" (type=\"text/css\" )?'\n            'media=\"print\" rel=\"stylesheet\"( /)?>'\n        ]\n        for rendered_node, expected_node in zip(media.render_css(), expected_regex):\n            self.assertRegex(rendered_node, expected_node)\n\n    @pipeline_settings(PIPELINE_ENABLED=True)\n    def test_js_packages_with_pipeline_enabled(self):\n        \"\"\"Testing PipelineFormMedia.js_packages with PIPELINE_ENABLED=True\"\"\"\n\n        class MyMedia(PipelineFormMedia):\n            js_packages = (\"scripts1\", \"scripts2\")\n            js = (\"extra1.js\", \"extra2.js\")\n\n        media = Media(MyMedia)\n\n        if django_version() < \"3.1\":\n            script_tag = '<script type=\"text/javascript\" src=\"%s\"></script>'\n        else:\n            script_tag = '<script src=\"%s\"></script>'\n\n        self.assertEqual(\n            MyMedia.js,\n            [\n                \"extra1.js\",\n                \"extra2.js\",\n                \"/static/scripts1.min.js\",\n                \"/static/scripts2.min.js\",\n            ],\n        )\n        self.assertEqual(MyMedia.js, media._js)\n        self.assertEqual(\n            media.render_js(),\n            [\n                script_tag % path\n                for path in (\n                    \"/static/extra1.js\",\n                    \"/static/extra2.js\",\n                    \"/static/scripts1.min.js\",\n                    \"/static/scripts2.min.js\",\n                )\n            ],\n        )\n\n    @pipeline_settings(PIPELINE_ENABLED=False)\n    def test_js_packages_with_pipeline_disabled(self):\n        \"\"\"Testing PipelineFormMedia.js_packages with PIPELINE_ENABLED=False\"\"\"\n\n        class MyMedia(PipelineFormMedia):\n            js_packages = (\"scripts1\", \"scripts2\")\n            js = (\"extra1.js\", \"extra2.js\")\n\n        media = Media(MyMedia)\n\n        if django_version() < \"3.1\":\n            script_tag = '<script type=\"text/javascript\" src=\"%s\"></script>'\n        else:\n            script_tag = '<script src=\"%s\"></script>'\n\n        self.assertEqual(\n            MyMedia.js,\n            [\n                \"extra1.js\",\n                \"extra2.js\",\n                \"pipeline/js/first.js\",\n                \"pipeline/js/second.js\",\n                \"pipeline/js/application.js\",\n            ],\n        )\n        self.assertEqual(MyMedia.js, media._js)\n        self.assertEqual(\n            media.render_js(),\n            [\n                script_tag % path\n                for path in (\n                    \"/static/extra1.js\",\n                    \"/static/extra2.js\",\n                    \"/static/pipeline/js/first.js\",\n                    \"/static/pipeline/js/second.js\",\n                    \"/static/pipeline/js/application.js\",\n                )\n            ],\n        )\n"
  },
  {
    "path": "tests/tests/test_glob.py",
    "content": "import os\nimport shutil\n\nfrom django.core.files.base import ContentFile\nfrom django.core.files.storage import FileSystemStorage\nfrom django.test import TestCase\n\nfrom pipeline import glob\n\n\ndef local_path(path):\n    return os.path.join(os.path.dirname(__file__), path)\n\n\nclass GlobTest(TestCase):\n    def normpath(self, *parts):\n        return os.path.normpath(os.path.join(*parts))\n\n    def mktemp(self, *parts):\n        filename = self.normpath(*parts)\n        base, file = os.path.split(filename)\n        base = os.path.join(self.storage.location, base)\n        if not os.path.exists(base):\n            os.makedirs(base)\n        self.storage.save(filename, ContentFile(\"\"))\n\n    def assertSequenceEqual(self, l1, l2):\n        self.assertEqual(set(l1), set(l2))\n\n    def setUp(self):\n        self.storage = FileSystemStorage(local_path(\"glob_dir\"))\n        self.old_storage = glob.staticfiles_storage\n        glob.staticfiles_storage = self.storage\n        self.mktemp(\"a\", \"D\")\n        self.mktemp(\"aab\", \"F\")\n        self.mktemp(\"aaa\", \"zzzF\")\n        self.mktemp(\"ZZZ\")\n        self.mktemp(\"a\", \"bcd\", \"EF\")\n        self.mktemp(\"a\", \"bcd\", \"efg\", \"ha\")\n\n    def glob(self, *parts):\n        if len(parts) == 1:\n            pattern = parts[0]\n        else:\n            pattern = os.path.join(*parts)\n        return glob.glob(pattern)\n\n    def tearDown(self):\n        shutil.rmtree(self.storage.location)\n        glob.staticfiles_storage = self.old_storage\n\n    def test_glob_literal(self):\n        self.assertSequenceEqual(self.glob(\"a\"), [self.normpath(\"a\")])\n        self.assertSequenceEqual(self.glob(\"a\", \"D\"), [self.normpath(\"a\", \"D\")])\n        self.assertSequenceEqual(self.glob(\"aab\"), [self.normpath(\"aab\")])\n\n    def test_glob_one_directory(self):\n        self.assertSequenceEqual(\n            self.glob(\"a*\"), map(self.normpath, [\"a\", \"aab\", \"aaa\"])\n        )\n        self.assertSequenceEqual(self.glob(\"*a\"), map(self.normpath, [\"a\", \"aaa\"]))\n        self.assertSequenceEqual(self.glob(\"aa?\"), map(self.normpath, [\"aaa\", \"aab\"]))\n        self.assertSequenceEqual(\n            self.glob(\"aa[ab]\"), map(self.normpath, [\"aaa\", \"aab\"])\n        )\n        self.assertSequenceEqual(self.glob(\"*q\"), [])\n\n    def test_glob_nested_directory(self):\n        if os.path.normcase(\"abCD\") == \"abCD\":\n            # case-sensitive filesystem\n            self.assertSequenceEqual(\n                self.glob(\"a\", \"bcd\", \"E*\"), [self.normpath(\"a\", \"bcd\", \"EF\")]\n            )\n        else:\n            # case insensitive filesystem\n            self.assertSequenceEqual(\n                self.glob(\"a\", \"bcd\", \"E*\"),\n                [self.normpath(\"a\", \"bcd\", \"EF\"), self.normpath(\"a\", \"bcd\", \"efg\")],\n            )\n        self.assertSequenceEqual(\n            self.glob(\"a\", \"bcd\", \"*g\"), [self.normpath(\"a\", \"bcd\", \"efg\")]\n        )\n\n    def test_glob_directory_names(self):\n        self.assertSequenceEqual(self.glob(\"*\", \"D\"), [self.normpath(\"a\", \"D\")])\n        self.assertSequenceEqual(self.glob(\"*\", \"*a\"), [])\n        self.assertSequenceEqual(\n            self.glob(\"a\", \"*\", \"*\", \"*a\"), [self.normpath(\"a\", \"bcd\", \"efg\", \"ha\")]\n        )\n        self.assertSequenceEqual(\n            self.glob(\"?a?\", \"*F\"),\n            map(self.normpath, [os.path.join(\"aaa\", \"zzzF\"), os.path.join(\"aab\", \"F\")]),\n        )\n\n    def test_glob_directory_with_trailing_slash(self):\n        # We are verifying that when there is wildcard pattern which\n        # ends with os.sep doesn't blow up.\n        paths = glob.glob(\"*\" + os.sep)\n        self.assertEqual(len(paths), 4)\n        self.assertTrue(all(os.sep in path for path in paths))\n"
  },
  {
    "path": "tests/tests/test_middleware.py",
    "content": "from unittest.mock import patch\n\nfrom django.core.exceptions import MiddlewareNotUsed\nfrom django.http import HttpRequest, HttpResponse\nfrom django.test import TestCase\n\nfrom pipeline.middleware import MinifyHTMLMiddleware\n\n\ndef dummy_get_response(request):\n    return None\n\n\nclass MiddlewareTest(TestCase):\n    whitespace = b\"    \"\n\n    def setUp(self):\n        self.req = HttpRequest()\n        self.req.META = {\n            \"SERVER_NAME\": \"testserver\",\n            \"SERVER_PORT\": 80,\n        }\n        self.req.path = self.req.path_info = \"/\"\n        self.resp = HttpResponse()\n        self.resp.status_code = 200\n        self.resp.content = self.whitespace\n\n    def test_middleware_html(self):\n        self.resp[\"Content-Type\"] = \"text/html; charset=UTF-8\"\n\n        response = MinifyHTMLMiddleware(dummy_get_response).process_response(\n            self.req, self.resp\n        )\n        self.assertIn(\"text/html\", response[\"Content-Type\"])\n        self.assertNotIn(self.whitespace, response.content)\n\n    def test_middleware_text(self):\n        self.resp[\"Content-Type\"] = \"text/plain; charset=UTF-8\"\n\n        response = MinifyHTMLMiddleware(dummy_get_response).process_response(\n            self.req, self.resp\n        )\n        self.assertIn(\"text/plain\", response[\"Content-Type\"])\n        self.assertIn(self.whitespace, response.content)\n\n    @patch(\"pipeline.middleware.settings.PIPELINE_ENABLED\", False)\n    def test_middleware_not_used(self):\n        self.assertRaises(MiddlewareNotUsed, MinifyHTMLMiddleware, dummy_get_response)\n"
  },
  {
    "path": "tests/tests/test_packager.py",
    "content": "from django.test import TestCase\n\nfrom pipeline.collector import default_collector\nfrom pipeline.packager import PackageNotFound, Packager\nfrom tests.utils import _\n\n\nclass PackagerTest(TestCase):\n    def setUp(self):\n        default_collector.collect()\n\n    def test_package_for(self):\n        packager = Packager()\n        packager.packages[\"js\"] = packager.create_packages(\n            {\n                \"application\": {\n                    \"source_filenames\": (_(\"pipeline/js/application.js\"),),\n                    \"output_filename\": \"application.js\",\n                }\n            }\n        )\n        try:\n            packager.package_for(\"js\", \"application\")\n        except PackageNotFound:\n            self.fail()\n        try:\n            packager.package_for(\"js\", \"broken\")\n            self.fail()\n        except PackageNotFound:\n            pass\n\n    def test_templates(self):\n        packager = Packager()\n        packages = packager.create_packages(\n            {\n                \"templates\": {\n                    \"source_filenames\": (_(\"pipeline/templates/photo/list.jst\"),),\n                    \"output_filename\": \"templates.js\",\n                }\n            }\n        )\n        self.assertEqual(\n            packages[\"templates\"].templates,\n            [_(\"pipeline/templates/photo/list.jst\")],\n        )\n\n    def tearDown(self):\n        default_collector.clear()\n"
  },
  {
    "path": "tests/tests/test_storage.py",
    "content": "from io import StringIO\n\nimport django\nfrom django.contrib.staticfiles import finders\nfrom django.contrib.staticfiles.storage import staticfiles_storage\nfrom django.core.management import call_command\nfrom django.test import TestCase\nfrom django.test.utils import modify_settings, override_settings\n\nfrom pipeline.collector import default_collector\nfrom pipeline.storage import PipelineStorage\nfrom tests.tests.test_compiler import DummyCompiler\nfrom tests.utils import pipeline_settings\n\n\nclass PipelineNoPathStorage(PipelineStorage):\n    \"\"\"Storage without an implemented path method\"\"\"\n\n    def path(self, *args):\n        raise NotImplementedError()\n\n    def delete(self, *args):\n        return\n\n    def exists(self, *args):\n        return True\n\n    def save(self, *args):\n        return\n\n    def open(self, *args):\n        return StringIO()\n\n    def listdir(self, *args):\n        return []\n\n\nclass DummyCSSCompiler(DummyCompiler):\n    \"\"\"Handles css files\"\"\"\n\n    output_extension = \"css\"\n\n    def match_file(self, path):\n        return path.endswith(\".css\")\n\n\nclass StorageTest(TestCase):\n    def tearDown(self):\n        staticfiles_storage._setup()\n\n    @pipeline_settings(JS_COMPRESSOR=None, CSS_COMPRESSOR=None)\n    def test_post_process_dry_run(self):\n        default_collector.collect()\n        processed_files = PipelineStorage().post_process({}, True)\n        self.assertEqual(list(processed_files), [])\n\n    @pipeline_settings(\n        JS_COMPRESSOR=None,\n        CSS_COMPRESSOR=None,\n        COMPILERS=[\"tests.tests.test_storage.DummyCSSCompiler\"],\n    )\n    def test_post_process(self):\n        default_collector.collect()\n        storage = PipelineStorage()\n        processed_files = storage.post_process({})\n        self.assertTrue((\"screen.css\", \"screen.css\", True) in processed_files)\n        self.assertTrue((\"scripts.js\", \"scripts.js\", True) in processed_files)\n\n    @override_settings(\n        STATICFILES_STORAGE=\"tests.tests.test_storage.PipelineNoPathStorage\",\n    )\n    @pipeline_settings(\n        JS_COMPRESSOR=None,\n        CSS_COMPRESSOR=None,\n        COMPILERS=[\"tests.tests.test_storage.DummyCSSCompiler\"],\n    )\n    def test_post_process_no_path(self):\n        \"\"\"\n        Test post_process with a storage that doesn't implement the path method.\n        \"\"\"\n        staticfiles_storage._setup()\n        try:\n            call_command(\"collectstatic\", verbosity=0, interactive=False)\n        except NotImplementedError:\n            self.fail(\"Received an error running collectstatic\")\n\n    @modify_settings(STATICFILES_FINDERS={\"append\": \"pipeline.finders.PipelineFinder\"})\n    def test_nonexistent_file_pipeline_finder(self):\n        path = finders.find(\"nothing.css\")\n        self.assertIsNone(path)\n\n    @modify_settings(STATICFILES_FINDERS={\"append\": \"pipeline.finders.PipelineFinder\"})\n    def test_nonexistent_file_pipeline_finder_find_all(self):\n        if django.__version__ < \"5.2\":\n            self.skipTest(\"Only applicable to Django 5.2 and up\")\n\n        path = finders.find(\"nothing.css\", find_all=True)\n        self.assertIsNotNone(path)\n        self.assertEqual([], path)\n\n    @modify_settings(STATICFILES_FINDERS={\"append\": \"pipeline.finders.PipelineFinder\"})\n    def test_nonexistent_file_pipeline_finder_all(self):\n        if django.__version__ < \"6.0\":\n            self.skipTest(\"Only applicable to versions of Django before 6.0\")\n\n        path = finders.find(\"nothing.css\", all=True)\n        self.assertIsNotNone(path)\n        self.assertEqual([], path)\n\n    @modify_settings(\n        STATICFILES_FINDERS={\"append\": \"pipeline.finders.CachedFileFinder\"}\n    )\n    def test_nonexistent_file_cached_finder(self):\n        path = finders.find(\"nothing.css\")\n        self.assertIsNone(path)\n\n    @modify_settings(STATICFILES_FINDERS={\"append\": \"pipeline.finders.PipelineFinder\"})\n    def test_nonexistent_double_extension_file_pipeline_finder(self):\n        path = finders.find(\"app.css.map\")\n        self.assertIsNone(path)\n\n    @modify_settings(\n        STATICFILES_FINDERS={\"append\": \"pipeline.finders.CachedFileFinder\"}\n    )\n    def test_nonexistent_double_extension_file_cached_finder(self):\n        path = finders.find(\"app.css.map\")\n        self.assertIsNone(path)\n\n    @modify_settings(STATICFILES_FINDERS={\"append\": \"pipeline.finders.ManifestFinder\"})\n    def test_manifest_finder_finds_stylesheet(self):\n        path = finders.find(\"screen.css\")\n        self.assertIsNotNone(path)\n\n        path = finders.find(\"screen.scss\")\n        self.assertIsNone(path)\n\n    @modify_settings(STATICFILES_FINDERS={\"append\": \"pipeline.finders.ManifestFinder\"})\n    def test_manifest_finder_finds_all_stylesheet(self):\n        paths = finders.find(\"screen.css\", all=True)\n        self.assertIsNotNone(paths)\n        self.assertEqual(1, len(paths))\n\n        paths = finders.find(\"screen.scss\", all=True)\n        self.assertIsNotNone(paths)\n        self.assertEqual([], paths)\n"
  },
  {
    "path": "tests/tests/test_template.py",
    "content": "from django.template import Context, Template\nfrom django.test import TestCase\nfrom jinja2 import Environment, PackageLoader\n\nfrom pipeline.jinja2 import PipelineExtension\nfrom tests.utils import pipeline_settings\n\n\nclass JinjaTest(TestCase):\n    def setUp(self):\n        self.env = Environment(\n            extensions=[PipelineExtension],\n            loader=PackageLoader(\"pipeline\", \"templates\"),\n        )\n\n    def test_no_package(self):\n        template = self.env.from_string(\"\"\"{% stylesheet \"unknow\" %}\"\"\")\n        self.assertEqual(\"\", template.render())\n        template = self.env.from_string(\"\"\"{% javascript \"unknow\" %}\"\"\")\n        self.assertEqual(\"\", template.render())\n\n    def test_package_css(self):\n        template = self.env.from_string(\"\"\"{% stylesheet \"screen\" %}\"\"\")\n        self.assertEqual(\n            '<link href=\"/static/screen.css\" rel=\"stylesheet\" type=\"text/css\" />',\n            template.render(),\n        )\n\n    @pipeline_settings(PIPELINE_ENABLED=False)\n    def test_package_css_disabled(self):\n        template = self.env.from_string(\"\"\"{% stylesheet \"screen\" %}\"\"\")\n        self.assertEqual(\n            \"\"\"<link href=\"/static/pipeline/css/first.css\" rel=\"stylesheet\" type=\"text/css\" />\n<link href=\"/static/pipeline/css/second.css\" rel=\"stylesheet\" type=\"text/css\" />\n<link href=\"/static/pipeline/css/urls.css\" rel=\"stylesheet\" type=\"text/css\" />\"\"\",  # noqa\n            template.render(),\n        )\n\n    def test_package_js(self):\n        template = self.env.from_string(\"\"\"{% javascript \"scripts\" %}\"\"\")\n        self.assertEqual(\n            '<script type=\"text/javascript\" src=\"/static/scripts.js\" charset=\"utf-8\"></script>',  # noqa\n            template.render(),\n        )\n\n    def test_package_js_async(self):\n        template = self.env.from_string(\"\"\"{% javascript \"scripts_async\" %}\"\"\")\n        self.assertEqual(\n            '<script async type=\"text/javascript\" src=\"/static/scripts_async.js\" charset=\"utf-8\"></script>',  # noqa\n            template.render(),\n        )\n\n    def test_package_js_defer(self):\n        template = self.env.from_string(\"\"\"{% javascript \"scripts_defer\" %}\"\"\")\n        self.assertEqual(\n            '<script defer type=\"text/javascript\" src=\"/static/scripts_defer.js\" charset=\"utf-8\"></script>',  # noqa\n            template.render(),\n        )\n\n    def test_package_js_async_defer(self):\n        template = self.env.from_string(\"\"\"{% javascript \"scripts_async_defer\" %}\"\"\")\n        self.assertEqual(\n            '<script async defer type=\"text/javascript\" src=\"/static/scripts_async_defer.js\" charset=\"utf-8\"></script>',  # noqa\n            template.render(),\n        )\n\n\nclass DjangoTest(TestCase):\n    def render_template(self, template):\n        return Template(template).render(Context())\n\n    def test_compressed_empty(self):\n        rendered = self.render_template(\n            \"\"\"{% load pipeline %}{% stylesheet \"unknow\" %}\"\"\",\n        )\n        self.assertEqual(\"\", rendered)\n\n    def test_compressed_css(self):\n        rendered = self.render_template(\n            \"\"\"{% load pipeline %}{% stylesheet \"screen\" %}\"\"\",\n        )\n        self.assertEqual(\n            '<link href=\"/static/screen.css\" rel=\"stylesheet\" type=\"text/css\" media=\"all\" />',  # noqa\n            rendered,\n        )\n\n    def test_compressed_css_media(self):\n        rendered = self.render_template(\n            \"\"\"{% load pipeline %}{% stylesheet \"screen_media\" %}\"\"\",\n        )\n        self.assertEqual(\n            '<link href=\"/static/screen_media.css\" rel=\"stylesheet\" type=\"text/css\" media=\"screen and (min-width:500px)\" />',  # noqa\n            rendered,\n        )\n\n    def test_compressed_css_title(self):\n        rendered = self.render_template(\n            \"\"\"{% load pipeline %}{% stylesheet \"screen_title\" %}\"\"\",\n        )\n        self.assertEqual(\n            '<link href=\"/static/screen_title.css\" rel=\"stylesheet\" type=\"text/css\" media=\"all\" title=\"Default Style\" />',  # noqa\n            rendered,\n        )\n\n    def test_compressed_js(self):\n        rendered = self.render_template(\n            \"\"\"{% load pipeline %}{% javascript \"scripts\" %}\"\"\",\n        )\n        self.assertEqual(\n            '<script type=\"text/javascript\" src=\"/static/scripts.js\" charset=\"utf-8\"></script>',  # noqa\n            rendered,\n        )\n\n    def test_compressed_js_async(self):\n        rendered = self.render_template(\n            \"\"\"{% load pipeline %}{% javascript \"scripts_async\" %}\"\"\",\n        )\n        self.assertEqual(\n            '<script async type=\"text/javascript\" src=\"/static/scripts_async.js\" charset=\"utf-8\"></script>',  # noqa\n            rendered,\n        )\n\n    def test_compressed_js_defer(self):\n        rendered = self.render_template(\n            \"\"\"{% load pipeline %}{% javascript \"scripts_defer\" %}\"\"\",\n        )\n        self.assertEqual(\n            '<script defer type=\"text/javascript\" src=\"/static/scripts_defer.js\" charset=\"utf-8\"></script>',  # noqa\n            rendered,\n        )\n\n    def test_compressed_js_async_defer(self):\n        rendered = self.render_template(\n            \"\"\"{% load pipeline %}{% javascript \"scripts_async_defer\" %}\"\"\",\n        )\n        self.assertEqual(\n            '<script async defer type=\"text/javascript\" src=\"/static/scripts_async_defer.js\" charset=\"utf-8\"></script>',  # noqa\n            rendered,\n        )\n"
  },
  {
    "path": "tests/tests/test_utils.py",
    "content": "import mimetypes\n\nfrom django.test import TestCase\n\nfrom pipeline.utils import guess_type\n\n\nclass UtilTest(TestCase):\n    def test_guess_type(self):\n        self.assertEqual(\"text/css\", guess_type(\"stylesheet.css\"))\n        self.assertEqual(\"text/coffeescript\", guess_type(\"application.coffee\"))\n        self.assertEqual(\"text/less\", guess_type(\"stylesheet.less\"))\n\n    def test_mimetypes_are_str(self):\n        for ext, mtype in mimetypes.types_map.items():\n            self.assertIsInstance(ext, str)\n            self.assertIsInstance(mtype, str)\n"
  },
  {
    "path": "tests/tests/test_views.py",
    "content": "from django.contrib.staticfiles.storage import staticfiles_storage\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.http import Http404\nfrom django.test import RequestFactory, TestCase\nfrom django.test.utils import override_settings\n\nfrom pipeline.collector import default_collector\nfrom pipeline.views import serve_static\nfrom tests.utils import pipeline_settings\n\n\n@override_settings(DEBUG=True)\n@pipeline_settings(PIPELINE_COLLECTOR_ENABLED=True, PIPELINE_ENABLED=False)\nclass ServeStaticViewsTest(TestCase):\n    def setUp(self):\n        super().setUp()\n\n        self.filename = \"pipeline/js/first.js\"\n        self.storage = staticfiles_storage\n        self.request = RequestFactory().get(f\"/static/{self.filename}\")\n\n        default_collector.clear()\n\n    def tearDown(self):\n        super().tearDown()\n\n        default_collector.clear()\n        staticfiles_storage._setup()\n\n    def test_found(self):\n        self._test_found()\n\n    def test_not_found(self):\n        self._test_not_found(\"missing-file\")\n\n    @override_settings(DEBUG=False)\n    def test_debug_false(self):\n        with self.assertRaises(ImproperlyConfigured):\n            serve_static(self.request, self.filename)\n\n        self.assertFalse(self.storage.exists(self.filename))\n\n    @override_settings(DEBUG=False)\n    def test_debug_false_and_insecure(self):\n        self._test_found(insecure=True)\n\n    @pipeline_settings(PIPELINE_ENABLED=True)\n    def test_pipeline_enabled_and_found(self):\n        self._write_content()\n        self._test_found()\n\n    @pipeline_settings(PIPELINE_ENABLED=True)\n    def test_pipeline_enabled_and_not_found(self):\n        self._test_not_found(self.filename)\n\n    @pipeline_settings(PIPELINE_COLLECTOR_ENABLED=False)\n    def test_collector_disabled_and_found(self):\n        self._write_content()\n        self._test_found()\n\n    @pipeline_settings(PIPELINE_COLLECTOR_ENABLED=False)\n    def test_collector_disabled_and_not_found(self):\n        self._test_not_found(self.filename)\n\n    def _write_content(self, content=\"abc123\"):\n        \"\"\"Write sample content to the test static file.\"\"\"\n        with self.storage.open(self.filename, \"w\") as f:\n            f.write(content)\n\n    def _test_found(self, **kwargs):\n        \"\"\"Test that a file can be found and contains the correct content.\"\"\"\n        response = serve_static(self.request, self.filename, **kwargs)\n        self.assertEqual(response.status_code, 200)\n        self.assertTrue(self.storage.exists(self.filename))\n\n        if hasattr(response, \"streaming_content\"):\n            content = b\"\".join(response.streaming_content)\n        else:\n            content = response.content\n\n        with self.storage.open(self.filename) as f:\n            self.assertEqual(f.read(), content)\n\n    def _test_not_found(self, filename):\n        \"\"\"Test that a file could not be found.\"\"\"\n        self.assertFalse(self.storage.exists(filename))\n\n        with self.assertRaises(Http404):\n            serve_static(self.request, filename)\n\n        self.assertFalse(self.storage.exists(filename))\n"
  },
  {
    "path": "tests/urls.py",
    "content": "from django.contrib import admin\nfrom django.urls import path\nfrom django.views.generic import TemplateView\n\nurlpatterns = [\n    path(\"\", TemplateView.as_view(template_name=\"index.html\"), name=\"index\"),\n    path(\"empty/\", TemplateView.as_view(template_name=\"empty.html\"), name=\"empty\"),\n    path(\"admin/\", admin.site.urls),\n]\n"
  },
  {
    "path": "tests/utils.py",
    "content": "import os\n\nimport django\nfrom django.test import override_settings\n\n\ndef _(path):\n    # Make sure the path contains only the correct separator\n    return path.replace(\"/\", os.sep).replace(\"\\\\\", os.sep)\n\n\nclass pipeline_settings(override_settings):\n    def __init__(self, **kwargs):\n        if django.VERSION[:2] >= (1, 10):\n            # Django 1.10's override_settings inherits from TestContextDecorator\n            # and its __init__ method calls its superclass' __init__ method too,\n            # so we must do the same.\n            super().__init__()\n        self.options = {\"PIPELINE\": kwargs}\n"
  },
  {
    "path": "tests/views.py",
    "content": ""
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist =\n  pypy3-dj{41,42}\n  py{39,310,311}-dj41\n  py{39,310,311,312}-dj42\n  py{310,311,312}-dj50\n  py{310,311,312}-dj51\n  py{310,311,312,313}-dj52\n  py{310,311,312,313}-djmain\n  docs\n\n[gh-actions]\npython =\n    3.9: py39, docs\n    3.10: py310\n    3.11: py311\n    3.12: py312\n    3.13: py313\n    pypy3: pypy3\n\n[gh-actions:env]\nDJANGO =\n    4.1: dj41\n    4.2: dj42\n    5.0: dj50\n    5.1: dj51\n    5.2: dj52\n    main: djmain\n\n[testenv]\nbasepython =\n  pypy3: pypy3\n  py39: python3.9\n  py310: python3.10\n  py311: python3.11\n  py312: python3.12\n  py313: python3.13\ndeps =\n  pypy3: mock\n  dj32: Django>=3.2,<3.3\n  dj41: Django>=4.1,<4.2\n  dj42: Django>=4.2,<4.3\n  dj50: Django>=5.0,<5.1\n  dj51: Django>=5.1,<5.2\n  dj52: Django>=5.2,<5.3\n  djmain: https://github.com/django/django/archive/main.tar.gz\n  jinja2\n  coverage\n  jsmin==3.0.0\n  ply==3.4\n  css-html-js-minify==2.5.5\nsetenv =\n    DJANGO_SETTINGS_MODULE = tests.settings\n    PYTHONPATH = {toxinidir}\ncommands =\n    npm install\n    {envbindir}/coverage run --source pipeline {envbindir}/django-admin test {posargs:tests}\n    {envbindir}/coverage report\n    {envbindir}/coverage xml\nwhitelist_externals = npm\nignore_outcome =\n    djmain: True\nignore_errors =\n    djmain: True\nallowlist_externals=npm\n\n[testenv:docs]\nbasepython = python3.9\nchangedir = docs\ndeps = sphinx\ncommands =\n    {envbindir}/sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html\n"
  }
]