Most, hogy már van egy virtuális szerverünk, kellene kezdeni vele valami mókás dolgot. Lehetne mondjuk egy távolról is vezérelhető build rendszerünk (a "távolról" mondjuk ebben az esetben talán egy kicsit túlzás :)), amit a kedvenc szövegszerkesztőnk automatikusan tudna vezérelni. Az úgy kellőképp mókásan hangzik ahhoz, hogy belevágjunk. :)

Ahhoz, hogy legyen értelme a build rendszernek, nem árt, ha van mit build-elni. Tegyük fel mondjuk a példa kedvéért, hogy CSS helyett szeretnénk Sass-t, JavaScript helyett pedig CoffeeScript-et használni. Természetesen a rendszer képességei nem merülnek ki ennyiben, lehetne mondjuk olyat is csinálni, hogy a sablon fájlokat fordítsa le PHP-ra, vagy a konfigurációs XML-ekből gyártson tömböket, esetleg minden unit tesztet lefuttasson. A lehetőségek száma, ahogy mondani szokás, végtelen, de maradjunk az eredeti kettőnél, a többi a kedves olvasó fantáziájára van bízva. Kezdjük is rögtön a telepítéssel:

Sass

sudo apt-get install ruby rubygems
sudo gem install sass

A Sass [Syntactically Awesome Stylesheets] nevezetű előfeldolgozó néhány hasznos kiegészítéssel segít ráncba szedni az elvadult CSS kóddzsungelt.

CoffeeScript

sudo apt-get install nodejs npm
sudo npm install coffee-script

A CoffeeScript a funkcionális programozási nyelvekre és talán egy csöppet a Ruby-ra emlékeztető nyelv a JavaScript egy komolyabb újragondolása (a Sass-CSS viszonylathoz képest), de mentségére legyen mondva, hogy egy az egyben valid JavaScript-re fordul.

Építkezzünk

A build-elésben segítségünkre lesz a vicces nevű PHP-s build eszköz, a Phing. A következő módon érhetjük el, hogy feltelepüljön:

sudo apt-get install php-pear
sudo pear channel-discover pear.phing.info
sudo pear install phing/phing

A példához a következő minimális projekt könyvtárszerkezetet fogjuk használni:

  • assets/
    • coffee/
    • sass/
  • www/
    • css/
    • js/
  • build.xml

Az assets/ könyvtárban vannak a nyers fájlok, a www/ könytárba mennek a generált fájlok, amiket nem árt kívülről is elérni. A kulcs pedig a build.xml, ami a Phing számára tartalmaz utasításokat. Kezdetnek valami ilyesmi például megteszi:

<?xml version="1.0" encoding="UTF-8"?>
<project name="MyProject" default="assets">
    <property name="sass_dir" value="${project.basedir}/assets/sass/" />
    <property name="coffee_dir" value="${project.basedir}/assets/coffee/" />
    
    <property name="css_target" value="${project.basedir}/www/css/" />
    <property name="js_target" value="${project.basedir}/www/js/" />
    
    <target name="sass">
        <exec command="sass --no-cache --update &quot;${sass_dir}:${css_target}&quot;" />
    </target>
    
    <target name="coffee">
        <exec command="coffee -o &quot;${js_target}&quot; &quot;${coffee_dir}&quot;" />
    </target>
    
    <target name="assets">
        <phingcall target="sass" />
        <phingcall target="coffee" />
    </target>
</project>

A projekt könyvtárában (egy szinten a build.xml-lel) állva ki tudjuk adni a következő három parancsot, amivel a Sass-t, CoffeeScript-et és mindkettőt egyszerre lefordíthatjuk (ha nem adunk meg semmit a phing után, akkor a default assets fog lefutni):

phing sass
phing coffee
phing assets

Az alkalmazás

Már csak azt kellene elérni, hogy a fájlok módosítása után a megfelelő parancs automatikusan lefusson. Bár mind a Sass, mind a CoffeeScript rendelkezik megoldással a problémára (--watch), a későbbiekre is gondolva inkább valami általánosabb megoldásra lenne szükség. Mondjuk egy webes minialkalmazás, ami a kapott paraméterek függvényében meghívja a megfelelő parancsot. A kódszerkesztő alkalmazásunk pedig ezt a paraméterezett URL-t hívogathatná. Mondjuk valami ilyesmit:

http://builder.vbox/index.php?project=projektneve&target=target

Az a bizonyos index.php pedig valami ilyen kis egyszerű dolog lenne (az egyszerűség ára az, hogy élünk azzal a feltételezéssel, hogy az egyes projektek a /var/www/projektneve.vbox/ könyvtárban vannak és eltekintünk az olyan dolgoktól, mint például a target valódiságának az ellenőrzése):

$dir = '/var/www/' . get('project') . '.vbox/';
if (!file_exists($dir . 'build.xml')) {
    exit('invalid_project');
}

$target = get('target');
if (!$target) {
    exit('no_target');
}

chdir($dir);
shell_exec('phing ' . escapeshellcmd($target));
exit('ok');

function get($name) {
    if (empty($_GET[$name])) {
        return '';
    }
    return preg_replace('/[^a-z0-9\-\.]/i', '', $_GET[$name]);
}

A plugin

Ezzel meg is van a távolról is vezérelhető egyszerű kis build rendszerünk. A kedvenc szerkesztőnkhöz írhatunk egy plugin-t, ami mentésre meghívja a megfelelő URL-t, hogy legenerálódjanak a CSS/JS fájlok (vagy akár tetszőleges billentyűkombinációra tetszőleges build parancsot meghívhatunk). Sublime Text 2 esetén valahogy így nézne ki a dolog:

import sublime, sublime_plugin
import urllib, urllib2
import os, threading

class BuilderThread(threading.Thread):
    def __init__(self, project, target):
        threading.Thread.__init__(self)
        self.project = project
        self.target = target
    def run(self):
        try:
            data = urllib.urlencode({
                'project': self.project,
                'target': self.target,
            })
            request = urllib2.Request('http://builder.vbox/index.php?' + data)
            response = urllib2.urlopen(request).read()
            return
        except (urllib2.HTTPError) as (e):
            err = '%s: HTTP error %s contacting API' % (__name__, str(e.code))
        except (urllib2.URLError) as (e):
            err = '%s: URL error %s contacting API' % (__name__, str(e.reason)) 
        
        sublime.error_message(err)

class BuilderEventListener(sublime_plugin.EventListener):
    def __init__(self):
        sublime_plugin.EventListener.__init__(self)
    
    def on_post_save(self, view):
        name, ext = os.path.splitext(view.file_name())
        project = view.settings().get('builder_project_name', False)
        
        if (False == project):
            return
        
        if (ext == '.coffee'):
            BuilderThread(project, 'coffee').start()
        elif (ext == '.scss' or ext == '.sass'):
            BuilderThread(project, 'sass').start()

A plugin használ egy builder_project_name nevű beállítást, amit az éppen aktuális projekt .sublime-project fájljában érdemes megadni:

{
    "settings": {
        "builder_project_name": "test-project"
    }
}

Ezzel meg is volnánk, kellemes építkezést.