1<?php
2
3/*
4 * This file is part of the Assetic package, an OpenSky project.
5 *
6 * (c) 2010-2014 OpenSky Project Inc
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Assetic\Filter;
13
14use Assetic\Asset\AssetInterface;
15use Assetic\Exception\FilterException;
16use Assetic\Factory\AssetFactory;
17use Assetic\Util\FilesystemUtils;
18
19/**
20 * Runs assets through Sprockets.
21 *
22 * Requires Sprockets 1.0.x.
23 *
24 * @link http://getsprockets.org/
25 * @link http://github.com/sstephenson/sprockets/tree/1.0.x
26 *
27 * @author Kris Wallsmith <kris.wallsmith@gmail.com>
28 */
29class SprocketsFilter extends BaseProcessFilter implements DependencyExtractorInterface
30{
31    private $sprocketsLib;
32    private $rubyBin;
33    private $includeDirs;
34    private $assetRoot;
35
36    /**
37     * Constructor.
38     *
39     * @param string $sprocketsLib Path to the Sprockets lib/ directory
40     * @param string $rubyBin      Path to the ruby binary
41     */
42    public function __construct($sprocketsLib = null, $rubyBin = '/usr/bin/ruby')
43    {
44        $this->sprocketsLib = $sprocketsLib;
45        $this->rubyBin = $rubyBin;
46        $this->includeDirs = array();
47    }
48
49    public function addIncludeDir($directory)
50    {
51        $this->includeDirs[] = $directory;
52    }
53
54    public function setAssetRoot($assetRoot)
55    {
56        $this->assetRoot = $assetRoot;
57    }
58
59    /**
60     * Hack around a bit, get the job done.
61     */
62    public function filterLoad(AssetInterface $asset)
63    {
64        static $format = <<<'EOF'
65#!/usr/bin/env ruby
66
67require %s
68%s
69options = { :load_path    => [],
70            :source_files => [%s],
71            :expand_paths => false }
72
73%ssecretary = Sprockets::Secretary.new(options)
74secretary.install_assets if options[:asset_root]
75print secretary.concatenation
76
77EOF;
78
79        $more = '';
80
81        foreach ($this->includeDirs as $directory) {
82            $more .= 'options[:load_path] << '.var_export($directory, true)."\n";
83        }
84
85        if (null !== $this->assetRoot) {
86            $more .= 'options[:asset_root] = '.var_export($this->assetRoot, true)."\n";
87        }
88
89        if ($more) {
90            $more .= "\n";
91        }
92
93        $tmpAsset = FilesystemUtils::createTemporaryFile('sprockets_asset');
94        file_put_contents($tmpAsset, $asset->getContent());
95
96        $input = FilesystemUtils::createTemporaryFile('sprockets_in');
97        file_put_contents($input, sprintf($format,
98            $this->sprocketsLib
99                ? sprintf('File.join(%s, \'sprockets\')', var_export($this->sprocketsLib, true))
100                : '\'sprockets\'',
101            $this->getHack($asset),
102            var_export($tmpAsset, true),
103            $more
104        ));
105
106        $pb = $this->createProcessBuilder(array(
107            $this->rubyBin,
108            $input,
109        ));
110
111        $proc = $pb->getProcess();
112        $code = $proc->run();
113        unlink($tmpAsset);
114        unlink($input);
115
116        if (0 !== $code) {
117            throw FilterException::fromProcess($proc)->setInput($asset->getContent());
118        }
119
120        $asset->setContent($proc->getOutput());
121    }
122
123    public function filterDump(AssetInterface $asset)
124    {
125    }
126
127    public function getChildren(AssetFactory $factory, $content, $loadPath = null)
128    {
129        // todo
130        return array();
131    }
132
133    private function getHack(AssetInterface $asset)
134    {
135        static $format = <<<'EOF'
136
137module Sprockets
138  class Preprocessor
139    protected
140    def pathname_for_relative_require_from(source_line)
141      Sprockets::Pathname.new(@environment, File.join(%s, location_from(source_line)))
142    end
143  end
144end
145
146EOF;
147
148        if ($dir = $asset->getSourceDirectory()) {
149            return sprintf($format, var_export($dir, true));
150        }
151    }
152}
153