<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0">
  <id>http://solovyov.net/</id>
  <title>solovyov.net</title>
  <updated>2012-02-11T20:20:51Z</updated>
  <author><name>Alexander Solovyov</name></author>
  <link href="http://solovyov.net/" rel="alternate" />
  <generator uri="http://solovyov.net/cyrax/">cyrax</generator>
  <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/AmazonByteflow" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="amazonbyteflow" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry>
    <id>/blog/2012/01/13/showkr/</id>
    <author><name>Alexander Solovyov</name></author>
    <title type="html">Showkr - приложение в браузере</title>
    <updated>2012-01-13T00:00:00Z</updated>
    <published>2012-01-13T00:00:00Z</published>
    <category term="javascript" /><category term="programming" /><category term="project" /><category term="showkr" />
    <link href="http://solovyov.net/blog/2012/01/13/showkr/" rel="alternate" />
    <content type="html">&lt;p&gt;Я за последние пару-тройку недель по вечерам и выходным понемногу сделал дикий,
но симпатичный (и, может, полезный) сайт &lt;a class="reference external" href="http://showkr.org/"&gt;Showkr&lt;/a&gt;. И решил рассказать миру о
том, зачем и как я это сделал - я думаю, что вторая часть может пригодиться,
учитывая растущую популярность приложений в браузере.&lt;/p&gt;
&lt;div class="section" id="id2"&gt;
&lt;h2&gt;Зачем&lt;/h2&gt;
&lt;p&gt;У фликра довольно тяжелые страницы, и когда смотришь целый сет фотографий -
утомительно ждать, пока каждая следующая загрузится и отрисуется (я вот только
что попробовал, от нажатия кнопки до рендера где-то до секунды проходит на
быстром интернете).&lt;/p&gt;
&lt;p&gt;А тут, получается, &lt;a class="reference external" href="http://showkr.org/#72157625002065401"&gt;открыл&lt;/a&gt;, дождался ответа апи фликра, и сиди себе
просматривай, оно всë на одной странице. Тем более, что созданы все удобства -
фотки, комментарии и хоткеи: поддерживается всë, что имеет хоть какой-то смысл -
&lt;tt class="docutils literal"&gt;j&lt;/tt&gt;/&lt;tt class="docutils literal"&gt;k&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;up&lt;/tt&gt;/&lt;tt class="docutils literal"&gt;down&lt;/tt&gt;, &lt;tt class="docutils literal"&gt;space&lt;/tt&gt;/&lt;tt class="docutils literal"&gt;shift+space&lt;/tt&gt;. Welcome!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id4"&gt;
&lt;h2&gt;Как&lt;/h2&gt;
&lt;p&gt;Первый момент, который мне сохранил кучу времени - это &lt;a class="reference external" href="http://twitter.github.com/bootstrap/"&gt;Twitter
Bootstrap&lt;/a&gt;. Тут мне рассказывать особенно нечего, если вы его не знаете -
теперь будете знать. Хороший CSS framework, экономит тучу времени.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="make"&gt;
&lt;h2&gt;Make&lt;/h2&gt;
&lt;p&gt;Второй момент - &lt;tt class="docutils literal"&gt;GNU Make&lt;/tt&gt;. Я никогда толком не умел писать мейкфайлы - был
испуган в детстве результатами запусков autoconf/automake. Но какое-то время
назад я начал юзать мейкфайлы, как рубисты юзают рейк - для каких-то мелких
задачек. Чисто как организатор шелл-команд, короче.&lt;/p&gt;
&lt;p&gt;Но какое-то время назад я понял, что пришла пора. CoffeeScript хочет стать
джаваскриптом, темплейты хотят стать джаваскриптом, индекс.хтмл хочет быть
разным для разработки и работающего сайта, и все они не хотят это делать, когда
не нужно (например, во время отдачи сайта - зачем, если всë можно сделать
заранее?).&lt;/p&gt;
&lt;p&gt;И вырос прекрасный и довольно понятный мейкфайл. И сейчас я процитирую основные
моменты, чтоб не только я, но и другие такие же необразованные (кто не знает
классики - необразован, правда?) могли проникнуться полезностью.&lt;/p&gt;
&lt;div class="section" id="id5"&gt;
&lt;h3&gt;Основы&lt;/h3&gt;
&lt;p&gt;Итак, у меня есть директория с кофескриптовыми файлами, я хочу их превратить в
джаваскрипт (включать кофескрипт на клиент-сайд - моветон имхо, зачем нам лишние
тормоза). Начнëм с того, что у нас есть эти самые файлы:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nv"&gt;SOURCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;wildcard app/*.coffee&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;И правило, чтоб их скомпилить:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nf"&gt;build/%.js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;app/%.coffee&lt;/span&gt;
    @mkdir -p &lt;span class="k"&gt;$(&lt;/span&gt;@D&lt;span class="k"&gt;)&lt;/span&gt;
    coffee -pc &lt;span class="nv"&gt;$&amp;lt;&lt;/span&gt; &amp;gt; &lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Всë это выглядит немного стрëмно, но я сейчас объясню, а с внешним видом можно
жить - это на самом деле довольно неплохой DSL, хотя можно и поприятнее сделать
было бы. В мейкфайле есть:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;переменные&lt;/li&gt;
&lt;li&gt;функции&lt;/li&gt;
&lt;li&gt;правила&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Всë остальное пока не волнует. У нас здесь есть переменная &lt;tt class="docutils literal"&gt;SOURCE&lt;/tt&gt;, которой
присваивается результат исполнения функции &lt;tt class="docutils literal"&gt;wildcard&lt;/tt&gt;. И переменные, и функции
раскрываются с помощью оборачивания в конструкцию &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;$(...)&lt;/span&gt;&lt;/tt&gt; (исключая
однобуквенные переменные, тогда просто &lt;tt class="docutils literal"&gt;$x&lt;/tt&gt;). Функции, конечно, еще параметров
хотят.&lt;/p&gt;
&lt;p&gt;Нечто с двоеточием и строками с отступами - это правило. Говорит нам, что файл,
который заканчивается на &lt;tt class="docutils literal"&gt;.js&lt;/tt&gt; и находится в директории &lt;tt class="docutils literal"&gt;build/&lt;/tt&gt;, зависит от
файла с точно таким же именем, только в директории &lt;tt class="docutils literal"&gt;app/&lt;/tt&gt; и с расширением
&lt;tt class="docutils literal"&gt;.coffee&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;В правиле есть две инструкции. Эти инструкции - это вызов обычных
команд, причëм каждая инструкция запускается в своëм инстансе шелла (переменные
не сохраняются). Каждая инструкция во время выполнения выводится на экран как
команда, или, если она начинается с &lt;tt class="docutils literal"&gt;&amp;#64;&lt;/tt&gt; (как наш мкдир) - не выводится.&lt;/p&gt;
&lt;p&gt;Еще мейк внутри правила даëт какое-то количество &lt;a class="reference external" href="http://www.gnu.org/software/make/manual/make.html#Automatic-Variables"&gt;переменных&lt;/a&gt; с видом разной
степени стрëмности. &lt;tt class="docutils literal"&gt;$&amp;#64;&lt;/tt&gt; - файл-цель (который мы хотим получить), &lt;tt class="docutils literal"&gt;$&amp;lt;&lt;/tt&gt; - его
первая (здесь - и единственная) зависимость. &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;$(&amp;#64;D)&lt;/span&gt;&lt;/tt&gt; - родительская директория
файла-цели. Я забил на слежение за тем, чтоб директории были созданы
заранее, и просто создаю их в каждом правиле, которое пишет в файлы, первой
строкой. Паттерн &amp;quot;хватит беспокоиться&amp;quot;. ;)&lt;/p&gt;
&lt;p&gt;И теперь мажорный аккорд, правило, которое заставит это работать:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;$(patsubst app/%.coffee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt; build/%.js&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt; $(SOURCE))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Это правило идëт первым, чтоб запуск просто &lt;tt class="docutils literal"&gt;make&lt;/tt&gt; запускал его, и говорит
нам, что правило &lt;tt class="docutils literal"&gt;all&lt;/tt&gt; зависит от таких-то файлов (а правило для постройки
этих файлов мы определили выше по тексту). От каких файлов - от всего в
&lt;tt class="docutils literal"&gt;$(SOURCE)&lt;/tt&gt;, только надо заменить &lt;tt class="docutils literal"&gt;app&lt;/tt&gt; на &lt;tt class="docutils literal"&gt;build&lt;/tt&gt;, а &lt;tt class="docutils literal"&gt;coffee&lt;/tt&gt; на
&lt;tt class="docutils literal"&gt;js&lt;/tt&gt; - ну, понятно, компиляция зависит от того, чтоб в директории билд были
все нужные джаваскриптовые файлы. А каждый файл зависит уже (определили раньше)
от кофескриптового.&lt;/p&gt;
&lt;p&gt;Теперь запуск &lt;tt class="docutils literal"&gt;make&lt;/tt&gt; в директории скомпилирует каждый файл в
джаваскриптовый. Кроме того, если еще раз запустить &lt;tt class="docutils literal"&gt;make&lt;/tt&gt;, то он запустит
обработку только тех файлов, которые изменились - он смотрит на время изменения
файла и не делает лишних движений.&lt;/p&gt;
&lt;p&gt;Казалось бы, зачем это надо, если &lt;tt class="docutils literal"&gt;coffee &lt;span class="pre"&gt;-bco&lt;/span&gt; build/ app/&lt;/tt&gt; сделает то же
самое. Ну, во-первых, то же самое он не сделает - он не следит за временем
изменения, а компилирует всë (и всего может случайно стать много), а во-вторых,
не кофескриптом единым! Но не будем забегать вперëд.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id7"&gt;
&lt;h3&gt;Зачистка&lt;/h3&gt;
&lt;p&gt;Итак, у нас есть первая инкарнация мейк-файла:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nv"&gt;SOURCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;wildcard app/*.coffee&lt;span class="k"&gt;)&lt;/span&gt;

all: &lt;span class="k"&gt;$(&lt;/span&gt;patsubst app/%.coffee, build/%.js, &lt;span class="k"&gt;$(&lt;/span&gt;SOURCE&lt;span class="k"&gt;))&lt;/span&gt;

build/%.js: app/%.coffee
    @mkdir -p &lt;span class="k"&gt;$(&lt;/span&gt;@D&lt;span class="k"&gt;)&lt;/span&gt;
    coffee -pc &lt;span class="nv"&gt;$&amp;lt;&lt;/span&gt; &amp;gt; &lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Что тут неплохо бы подчистить? Ну, нам не нужен список исходных файлов. Только
результатов, поэтому заменим начало на такое:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nv"&gt;SOURCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;patsubst app/%.coffee, build/%.js, &lt;span class="k"&gt;$(&lt;/span&gt;wildcard app/*.coffee&lt;span class="k"&gt;))&lt;/span&gt;

all: &lt;span class="k"&gt;$(&lt;/span&gt;SOURCE&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="eee"&gt;
&lt;h3&gt;Ещëëë&lt;/h3&gt;
&lt;p&gt;Теперь проще понять, чего хочет главное правило - оно хочет исходники! Ок,
понятно. Чего еще нам надо? Нам надо вот это всë динамически сгенерированное
запихать в &lt;tt class="docutils literal"&gt;index.html&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Маленькое отступление: фактически в showkr'e у меня не используется
&lt;a class="reference external" href="http://requirejs.org/"&gt;require.js&lt;/a&gt;, потому что мне лень скрещивать &lt;a class="reference external" href="http://ender.no.de/"&gt;ender&lt;/a&gt; с ним, а потому загрузка
модулей синхронна и все файлы хотят быть загружены прямо из индекса. В ином
случае этого бы момента не было и индекс был бы статическим, но, мне кажется,
это хороший повод порисовать еще правил. Итак.&lt;/p&gt;
&lt;p&gt;Для начала наше главное правило захочет еще &lt;tt class="docutils literal"&gt;index.html&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;$(SOURCE) build/index.html&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Что делать с индексом? Я решил не ломать себе мозги, а взять &lt;tt class="docutils literal"&gt;awk&lt;/tt&gt; (еще одна
штука, про которую стоит знать) и... В общем, индекс выглядит как-то так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;...
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
...
&lt;span class="c"&gt;&amp;lt;!-- js-deps --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
...
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;И у меня есть прекрасный скрипт на &lt;tt class="docutils literal"&gt;awk&lt;/tt&gt;, который берëт переменную &lt;tt class="docutils literal"&gt;DEPS&lt;/tt&gt; из
окружения (со списком зависимостей) и влепляет в хтмл:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;/&amp;lt;!-- js-deps --&amp;gt;/ {
    split(ENVIRON[&amp;quot;DEPS&amp;quot;], DEPS)
    # this way it goes from 1 to 9 instead of random ordering
    for (i = 1; DEPS[i]; i++)
        printf(&amp;quot;&amp;lt;script type=\&amp;quot;text/javascript\&amp;quot; src=\&amp;quot;%s\&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;\n&amp;quot;, DEPS[i])
    next
}

1 # print everything else
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Я мог бы расписывать, как работает авк, но давайте вы лучше почитаете &lt;a class="reference external" href="http://www.grymoire.com/Unix/Awk.html"&gt;на
английском&lt;/a&gt;, &lt;a class="reference external" href="http://www.lissyara.su/doc/programming/awk/"&gt;на русском&lt;/a&gt;, или вообще &lt;a class="reference external" href="http://www.google.com.ua/search?q=awk+basics"&gt;что-нибудь еще&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Правило при этом для постройки индекса выглядит так:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nf"&gt;build/index.html&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;index.html $(SOURCE)&lt;/span&gt;
    @mkdir -p &lt;span class="k"&gt;$(&lt;/span&gt;@D&lt;span class="k"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;DEPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;$(SOURCE:build/%=%)&amp;quot;&lt;/span&gt; awk -f build.awk &lt;span class="nv"&gt;$&amp;lt;&lt;/span&gt; &amp;gt; &lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Что у нас новенького? Ну, убираем имя директории, ссылаясь на переменную &lt;a class="reference external" href="http://www.gnu.org/software/make/manual/make.html#Substitution-Refs"&gt;с
заменой&lt;/a&gt; (аналогично тому &lt;tt class="docutils literal"&gt;$(patsubst &lt;span class="pre"&gt;...)&lt;/span&gt;&lt;/tt&gt;, что мы использовали
раньше). Вроде всë, создали директорию, авк прочитал файл, изменил, мы его
направили в нашу цель (&lt;tt class="docutils literal"&gt;$&amp;#64; == build/index.html&lt;/tt&gt;). Красота.&lt;/p&gt;
&lt;p&gt;Теперь &lt;tt class="docutils literal"&gt;make&lt;/tt&gt; при запуске сначала скомпилирует наш кофескрипт (если надо), а
потом &lt;tt class="docutils literal"&gt;index.html&lt;/tt&gt;. Ура.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id12"&gt;
&lt;h3&gt;Публичная версия&lt;/h3&gt;
&lt;p&gt;А теперь надо собрать версию для сайта - один джаваскриптовый файл. Отлично:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nf"&gt;prod&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;all prod/app.js prod/index.html&lt;/span&gt;

&lt;span class="nf"&gt;prod/index.html&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;index.html&lt;/span&gt;
    @mkdir -p &lt;span class="k"&gt;$(&lt;/span&gt;@D&lt;span class="k"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;DEPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app.js&amp;quot;&lt;/span&gt; awk -f build.awk &lt;span class="nv"&gt;$&amp;lt;&lt;/span&gt; &amp;gt; &lt;span class="nv"&gt;$@&lt;/span&gt;

&lt;span class="nf"&gt;prod/app.js&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;$(SOURCE:build/%=prod/%)&lt;/span&gt;
    @mkdir -p &lt;span class="k"&gt;$(&lt;/span&gt;@D&lt;span class="k"&gt;)&lt;/span&gt;
    cat &lt;span class="nv"&gt;$^&lt;/span&gt; | uglifyjs &amp;gt; &lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Теперь &lt;tt class="docutils literal"&gt;make prod&lt;/tt&gt; возьмëт все зависимости &lt;tt class="docutils literal"&gt;prod/app.js&lt;/tt&gt; (вспомните,
&lt;tt class="docutils literal"&gt;$^&lt;/tt&gt; - это все зависимости правила) и минифицирует их в нужный нам файлик. И
скомпилирует еще &lt;tt class="docutils literal"&gt;index.html&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Надо сказать, что меня эти замены директорий в переменных серьëзно раздражают,
поэтому мы сейчас этот момент зачистим. Итак, результат трудов вместе с
зачисткой:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nv"&gt;SOURCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;patsubst app/%.coffee, %.js, &lt;span class="k"&gt;$(&lt;/span&gt;wildcard app/*.coffee&lt;span class="k"&gt;))&lt;/span&gt;

all: &lt;span class="k"&gt;$(&lt;/span&gt;addprefix build/, &lt;span class="k"&gt;$(&lt;/span&gt;SOURCE&lt;span class="k"&gt;)&lt;/span&gt; index.html&lt;span class="k"&gt;)&lt;/span&gt;

build/%.js: app/%.coffee
    @mkdir -p &lt;span class="k"&gt;$(&lt;/span&gt;@D&lt;span class="k"&gt;)&lt;/span&gt;
    coffee -pc &lt;span class="nv"&gt;$&amp;lt;&lt;/span&gt; &amp;gt; &lt;span class="nv"&gt;$@&lt;/span&gt;

build/index.html: index.html &lt;span class="k"&gt;$(&lt;/span&gt;addprefix build/, &lt;span class="k"&gt;$(&lt;/span&gt;SOURCE&lt;span class="k"&gt;))&lt;/span&gt;
    @mkdir -p &lt;span class="k"&gt;$(&lt;/span&gt;@D&lt;span class="k"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;DEPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;$(SOURCE:build/%=%)&amp;quot;&lt;/span&gt; awk -f build.awk &lt;span class="nv"&gt;$&amp;lt;&lt;/span&gt; &amp;gt; &lt;span class="nv"&gt;$@&lt;/span&gt;

prod: all &lt;span class="k"&gt;$(&lt;/span&gt;addprefix prod/, app.js index.html&lt;span class="k"&gt;)&lt;/span&gt;

prod/index.html: index.html
    @mkdir -p &lt;span class="k"&gt;$(&lt;/span&gt;@D&lt;span class="k"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;DEPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app.js&amp;quot;&lt;/span&gt; awk -f build.awk &lt;span class="nv"&gt;$&amp;lt;&lt;/span&gt; &amp;gt; &lt;span class="nv"&gt;$@&lt;/span&gt;

prod/app.js: &lt;span class="k"&gt;$(&lt;/span&gt;addprefix prod/, &lt;span class="k"&gt;$(&lt;/span&gt;SOURCE&lt;span class="k"&gt;))&lt;/span&gt;
    @mkdir -p &lt;span class="k"&gt;$(&lt;/span&gt;@D&lt;span class="k"&gt;)&lt;/span&gt;
    cat &lt;span class="nv"&gt;$^&lt;/span&gt; | uglifyjs &amp;gt; &lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id13"&gt;
&lt;h3&gt;Может, еще немножко?&lt;/h3&gt;
&lt;p&gt;Вот такой отличный мейкфайл. А теперь добавим сюда темплейты! Они лежат в
директории &lt;tt class="docutils literal"&gt;app/templates&lt;/tt&gt; и имеют расширение &lt;tt class="docutils literal"&gt;.eco&lt;/tt&gt;, а результаты будут
иметь расширение &lt;tt class="docutils literal"&gt;.eco.js&lt;/tt&gt; (чтоб отличать от просто &lt;tt class="docutils literal"&gt;.js&lt;/tt&gt;).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nv"&gt;TEMPLATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;patsubst app/%, %.js, &lt;span class="k"&gt;$(&lt;/span&gt;wildcard app/templates/*.eco&lt;span class="k"&gt;))&lt;/span&gt;

all: &lt;span class="k"&gt;$(&lt;/span&gt;addprefix build/, &lt;span class="k"&gt;$(&lt;/span&gt;TEMPLATES&lt;span class="k"&gt;)&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;SOURCE&lt;span class="k"&gt;)&lt;/span&gt; index.html&lt;span class="k"&gt;)&lt;/span&gt;

build/templates/%.js: app/templates/%
        @mkdir -p &lt;span class="k"&gt;$(&lt;/span&gt;@D&lt;span class="k"&gt;)&lt;/span&gt;
    ./eco.js &lt;span class="nv"&gt;$&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;&amp;lt;:app/%&lt;span class="o"&gt;=&lt;/span&gt;%&lt;span class="k"&gt;)&lt;/span&gt; &amp;gt; &lt;span class="nv"&gt;$@&lt;/span&gt;

prod/app.js: &lt;span class="k"&gt;$(&lt;/span&gt;addprefix prod/, &lt;span class="k"&gt;$(&lt;/span&gt;TEMPLATES&lt;span class="k"&gt;)&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;SOURCE&lt;span class="k"&gt;))&lt;/span&gt;
    @mkdir -p &lt;span class="k"&gt;$(&lt;/span&gt;@D&lt;span class="k"&gt;)&lt;/span&gt;
    cat &lt;span class="nv"&gt;$^&lt;/span&gt; | uglifyjs &amp;gt; &lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Здесь &lt;tt class="docutils literal"&gt;./eco.js&lt;/tt&gt; - самописный скрипт для вызова компиляции эко-темплейтов,
который применяет к результату нужную мне обëртку. Первым параметром у него путь
к файлу, а вторым - имя, под которым темплейт будет известен
(&lt;tt class="docutils literal"&gt;templates/something.eco&lt;/tt&gt;). Темплейты будут сминифицированы в один файл с
приложением.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id14"&gt;
&lt;h3&gt;Важные моменты&lt;/h3&gt;
&lt;p&gt;У меня важен порядок файлов джаваскриптовых, поэтому я просто задаю их руками:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="nv"&gt;SOURCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;patsubst %,%.js,util api models viewing browsing showkr&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;А выше показан вариант относительно того, когда устраивает сортировка по
алфавиту.&lt;/p&gt;
&lt;p&gt;Функция &lt;tt class="docutils literal"&gt;wildcard&lt;/tt&gt; не умеет рекурсивно находить файлы, поэтому если есть
поддиректории в структуре, то я использую &lt;tt class="docutils literal"&gt;$(shell find &lt;span class="pre"&gt;...)&lt;/span&gt;&lt;/tt&gt; - обычный
&lt;tt class="docutils literal"&gt;find&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Ну вот и всë, надеюсь, что какие-то базовые основы я рассказал понятно, а полный
код &lt;a class="reference external" href="https://github.com/piranha/showkr/blob/e8c4d838e0ee6c6dbf99e161b06e6e453a6e3793/Makefile"&gt;Makefile&lt;/a&gt;'а (ссылка стоит на ту версию, которая существовала на момент
написания статьи) можно найти в &lt;a class="reference external" href="http://github.com/piranha/showkr/"&gt;репозитории&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id16"&gt;
&lt;h2&gt;Архитектура&lt;/h2&gt;
&lt;p&gt;Вернëмся к собственно самому приложению. Оно построено на &lt;a class="reference external" href="http://backbonejs.org/"&gt;backbone.js&lt;/a&gt;,
который сейчас самая модная библиотека для мвц на джаваскрипте, наверное. Бэкбон
стоит того - он не пытается скрыть детали имплементации (как эмбер, например -
его я тоже пробовал), но организует всë отлично.&lt;/p&gt;
&lt;div class="section" id="id17"&gt;
&lt;h3&gt;Ядро&lt;/h3&gt;
&lt;p&gt;Центральная часть приложения - &lt;a class="reference external" href="http://backbonejs.org/#Router"&gt;Router&lt;/a&gt; &lt;tt class="docutils literal"&gt;Showkr&lt;/tt&gt;. С его инициализацией
запускается приложение.&lt;/p&gt;
&lt;p&gt;Основная функция, помимо роутинга (вызова нужной функции по адресу в хеше), -
управление вьюхами. Роутер умеет создавать &lt;a class="reference external" href="http://backbonejs.org/#View"&gt;View&lt;/a&gt; по уникальному идентификатору
и переключаться между уже созданными. Раз созданные, вьюхи не уничтожаются, чтоб
второй раз за сессию не ждать от фликра одних и тех же данных.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id18"&gt;
&lt;h3&gt;Остальное&lt;/h3&gt;
&lt;p&gt;А дальше всë банально - вьюхи инициализируют модели и внутренние вьюхи, модели
качают данные с фликра (используя переопределëнные методы &lt;tt class="docutils literal"&gt;sync&lt;/tt&gt; и
&lt;tt class="docutils literal"&gt;parse&lt;/tt&gt;).&lt;/p&gt;
&lt;p&gt;У большинства моделей есть какая-нибудь вложенная коллекция, поэтому получилась
иерархия &lt;tt class="docutils literal"&gt;User &lt;span class="pre"&gt;-&amp;gt;&lt;/span&gt; SetList &lt;span class="pre"&gt;-&amp;gt;&lt;/span&gt; Set &lt;span class="pre"&gt;-&amp;gt;&lt;/span&gt; PhotoList &lt;span class="pre"&gt;-&amp;gt;&lt;/span&gt; Photo &lt;span class="pre"&gt;-&amp;gt;&lt;/span&gt; CommentList &lt;span class="pre"&gt;-&amp;gt;&lt;/span&gt;
Comment&lt;/tt&gt;. Вложенные коллекции инициализируются в инициализации модели,
&lt;tt class="docutils literal"&gt;fetch&lt;/tt&gt; запускается там, где это имеет больше смысла - фотки качаются сразу
после того, как скачался сет, а комментарии - после того, как отрисовалась
фотография.&lt;/p&gt;
&lt;p&gt;Честно говоря, писать подробный туториал по бэкбону желания особенного нет - их
уже много. Так что, если интересно, то стоит пойти посмотреть на &lt;a class="reference external" href="https://github.com/piranha/showkr"&gt;исходники&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id20"&gt;
&lt;h2&gt;Эпилог&lt;/h2&gt;
&lt;p&gt;У меня были мысли приделать поддержку Пикасы еще, но немного лениво - я сам ею
не пользуюсь, а работы хватает, привести два довольно разных апи к общему
знаменателю... Ну и это не тема этой статьи (хотя если кому-то хочется, патчи я
с радостью принимаю).&lt;/p&gt;
&lt;p&gt;Я хотел сказать, что если вдруг остались или возникли вопросы, пишите мне - я
либо отвечу там, либо дополню статью. Надеюсь, что она была полезна.&lt;/p&gt;
&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/AmazonByteflow/~4/UQbBQ3jvj3Y" height="1" width="1"/&gt;</content>
  </entry>

  <entry>
    <id>/blog/2011/11/14/javascript-programmer-wanted/</id>
    <author><name>Alexander Solovyov</name></author>
    <title type="html">Ищем JavaScript-программиста</title>
    <updated>2011-11-14T00:00:00Z</updated>
    <published>2011-11-14T00:00:00Z</published>
    <category term="javascript" /><category term="work" />
    <link href="http://solovyov.net/blog/2011/11/14/javascript-programmer-wanted/" rel="alternate" />
    <content type="html">&lt;p&gt;Нам в команду нужен опытный JavaScript-программист, в офис, на полный рабочий
день.&lt;/p&gt;
&lt;p&gt;Мы пишем (модное ;) приложение, которое объединяет чат, location-based сервисы и
т.п. Работает на мобильных устройствах и в браузере (сюда и нужен разработчик),
бэкенд на Erlang/XMPP.&lt;/p&gt;
&lt;p&gt;Плюсы:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;офис в центре Киева, на улице Чапаева (м. Золотые Ворота/Университет) - зелено
(летом, хе-хе), тихо, близко ко всему;&lt;/li&gt;
&lt;li&gt;удобные стулья и хорошие столы;&lt;/li&gt;
&lt;li&gt;приятная зарплата (от $2,5k);&lt;/li&gt;
&lt;li&gt;Backbone.js, CoffeeScript;&lt;/li&gt;
&lt;li&gt;основатель - из Малайзии, есть отличная от нуля вероятность командировки;&lt;/li&gt;
&lt;li&gt;нас трое в команде, так что вы не будете мелким незаметным винтиком. ;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Чего хочется от вас:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;знания джаваскрипта - не обязательно знать каждый мелкий подлый момент, но что
такое контекст исполнения функции и как работает объектная система знать
нужно;&lt;/li&gt;
&lt;li&gt;какого-никакого умения написать немного CSS'a - у нас нет выделенного
верстальщика и пока что обходимся своими скромными силами;&lt;/li&gt;
&lt;li&gt;понимания, что такое MVC, для чего оно нужно, etc;&lt;/li&gt;
&lt;li&gt;знание Backbone.js - плюс (я понимаю, что коммерческого опыта может и не быть,
но если вы хотя бы его видели, я буду рад);&lt;/li&gt;
&lt;li&gt;само-собой, какой-никакой английский.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Если вам кажется, что вы подходите, пишите:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="mailto:alexander&amp;#64;solovyov.net"&gt;alexander&amp;#64;solovyov.net&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Если вы опытный программист, но JS - не ваш основной язык, и вам интересно,
пишите всë равно. :) И если у вас есть еще вопросы, тоже пишите.&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/AmazonByteflow/~4/jyV2gu8CfS0" height="1" width="1"/&gt;</content>
  </entry>

  <entry>
    <id>/blog/2011/04/22/basic-sqlalchemy/</id>
    <author><name>Alexander Solovyov</name></author>
    <title type="html">SQLAlchemy: как втянуться</title>
    <updated>2011-04-22T00:00:00Z</updated>
    <published>2011-04-22T00:00:00Z</published>
    <category term="python" /><category term="programming" /><category term="sql" /><category term="db" />
    <link href="http://solovyov.net/blog/2011/04/22/basic-sqlalchemy/" rel="alternate" />
    <content type="html">&lt;p&gt;SQLAlchemy сейчас - очевидный лидер ORM в питоне, но у неë есть один довольно
неприятный недостаток: чтобы начать пользоваться, приходится прочитать немало
документации. Поэтому я решил написать (очень) короткий пост-введение в алхимию.&lt;/p&gt;
&lt;div class="section" id="sql"&gt;
&lt;h2&gt;Уровень 1: SQL руками&lt;/h2&gt;
&lt;p&gt;Первым делом нам нужно соединение к базе, с которым можно что-то делать:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;mysql://user:pass@host/db&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;select * from table where id &amp;lt; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;{u&amp;#39;id&amp;#39;: 1, u&amp;#39;info&amp;#39;: u&amp;#39;first row&amp;#39;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Если хочется именованных параметров, можно использовать &lt;tt class="docutils literal"&gt;text()&lt;/tt&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;select * from table where id &amp;lt; :id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Из объектов, которые получаются итерацией результата - &lt;tt class="docutils literal"&gt;RowProxy&lt;/tt&gt; - данные
можно вытаскивать и индексом, и ключом, и атрибутом:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="go"&gt;    True&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Нужна транзакция?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="c"&gt;# work work work&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;# try/except: c.rollback() по желанию :)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Это уже что-то и так можно жить, тем более что оно экранирует параметры
автоматически.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id1"&gt;
&lt;h2&gt;Уровень 2: SQL-выражения в питоне&lt;/h2&gt;
&lt;p&gt;Можно получить объект таблицы из базы (с автоопределением колонок) и работать с
ним, если так будет удобнее:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MetaData&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MetaData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;table&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="go"&gt;    [(1, u&amp;#39;first row&amp;#39;)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Т.е. абсолютно идентичный запрос, но уже в питоне.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="orm"&gt;
&lt;h2&gt;Уровень 3: ORM&lt;/h2&gt;
&lt;p&gt;Ну и если приятнее с объектами работать, которым можно поведение задавать:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;orm&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;orm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;table&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;
&lt;span class="go"&gt;    u&amp;#39;first row&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Тут уже можно использовать ORM по полной:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Artist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;orm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Artist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;artist&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Album&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;orm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Album&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;album&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Song&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;orm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Song&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;song&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Song&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Album&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Album&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;    12L&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="c"&gt;# Song первый, поэтому его нужно джоинить с альбомом&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Song&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Album&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Album&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Artist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Artist&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;    (u&amp;#39;Hex&amp;#39;, u&amp;#39;Inflikted&amp;#39;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Song&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Album&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Album&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Artist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Artist&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;SELECT song.name AS song_name, album.name AS album_name&lt;/span&gt;
&lt;span class="go"&gt;FROM song JOIN album ON album.id = song.album_id JOIN artist ON artist.id = album.artist_id&lt;/span&gt;
&lt;span class="go"&gt;WHERE artist.id = %(id_1)s&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Еще, если использовать &lt;tt class="docutils literal"&gt;Session.execute()&lt;/tt&gt;, то можно сразу передавать
именованные параметры:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;select * from table where id &amp;lt; :id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="go"&gt;    [(1, u&amp;#39;first row&amp;#39;)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id2"&gt;
&lt;h2&gt;Разное&lt;/h2&gt;
&lt;p&gt;Нужно сказать, что по умолчанию у &lt;tt class="docutils literal"&gt;Engine&lt;/tt&gt; уже есть пул соединений, что
приятно.&lt;/p&gt;
&lt;p&gt;Метаданные с рефлексией и ранним биндингом - не совсем принятый подход,
это только для маленьких наколенных скриптов и работы в шелле, скорее, а так
обычно &lt;tt class="docutils literal"&gt;Engine&lt;/tt&gt; к &lt;tt class="docutils literal"&gt;MetaData&lt;/tt&gt; добавляют где-то отдельно, в чтении настроек,
когда уже все таблицы определены (через &lt;tt class="docutils literal"&gt;meta.bind = e&lt;/tt&gt;).&lt;/p&gt;
&lt;p&gt;Сессия часто напрямик не используется, особенно в многопоточных приложениях -
есть &lt;a class="reference external" href="http://www.sqlalchemy.org/docs/orm/session.html?highlight=scoped_session#sqlalchemy.orm.scoped_session"&gt;orm.scoped_session&lt;/a&gt;, который создаëт тред-локальную сессию.&lt;/p&gt;
&lt;p&gt;Вот в принципе и всë, дальше есть &lt;a class="reference external" href="http://www.sqlalchemy.org/docs/"&gt;документация&lt;/a&gt;. :)&lt;/p&gt;
&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/AmazonByteflow/~4/t6QX0mkhKkE" height="1" width="1"/&gt;</content>
  </entry>

  <entry>
    <id>/blog/2011/04/13/rsync-better-cp/</id>
    <author><name>Alexander Solovyov</name></author>
    <title type="html">rsync вместо cp</title>
    <updated>2011-04-14T08:38:42Z</updated>
    <published>2011-04-13T00:00:00Z</published>
    <category term="nix" />
    <link href="http://solovyov.net/blog/2011/04/13/rsync-better-cp/" rel="alternate" />
    <content type="html">&lt;p&gt;Надо было залить довольно большое количество больших бинарных файлов на сервер,
на котором нет никаких ftp-демонов и прочих извращений. Я обычно пользуюсь
&lt;tt class="docutils literal"&gt;scp&lt;/tt&gt; (с &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-r&lt;/span&gt;&lt;/tt&gt; для директорий), но сейчас файлов было действительно много и
мне было бы лень стартовать несколькочасовую процедуру закачки заново. Поэтому я
наконец-то (впервые в жизни) взял и внимательно просмотрел ман по &lt;tt class="docutils literal"&gt;rsync&lt;/tt&gt;,
чтобы разобраться, как с его помощью это сделать. В принципе всë просто:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="go"&gt;rsync -rP dir server:&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Отлично заливает, с прогресс-баром и докачкой. Но сказать я хочу о другом - я
очень-очень жалею, что не начал им пользоваться когда-то давно, лет 8-9
назад. Потому что тогда он уже сидел бы у меня в мускульной памяти вместо cp. Ведь
можно делать &lt;tt class="docutils literal"&gt;rsync &lt;span class="pre"&gt;-P&lt;/span&gt; file1 file2&lt;/tt&gt; для копирования файла с прогрессбаром и
быть счастливым. Копировать фильмы, музыку, что угодно. Добавь &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;-r&lt;/span&gt;&lt;/tt&gt;, и будет
копировать директории. Если копировать удалëнно - он даже докачивать будет (как
в одну, так и в другую сторону). Счастье.&lt;/p&gt;
&lt;p&gt;Надеюсь, этот пост кому-нибудь еще раскроет глаза так же, как &lt;tt class="docutils literal"&gt;man rsync&lt;/tt&gt;
раскрыл их мне. :) В любом случае:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span class="go"&gt;piranha@gto ~&amp;gt; g rsync .zshrc&lt;/span&gt;
&lt;span class="go"&gt;alias pc=&amp;quot;rsync -P&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;img src="http://feeds.feedburner.com/~r/AmazonByteflow/~4/UTPPFJy9hHE" height="1" width="1"/&gt;</content>
  </entry>

  <entry>
    <id>/blog/2011/03/04/urls-and-shebangs/</id>
    <author><name>Alexander Solovyov</name></author>
    <title type="html">Урлы и #!</title>
    <updated>2011-03-04T00:00:00Z</updated>
    <published>2011-03-04T00:00:00Z</published>
    <category term="web" />
    <link href="http://solovyov.net/blog/2011/03/04/urls-and-shebangs/" rel="alternate" />
    <content type="html">&lt;p&gt;За последнее время проскочило великое множество статей на тему того, какая
плохая штука шебенги (&lt;tt class="docutils literal"&gt;#!&lt;/tt&gt;) и как все срочно должны перестать их
использовать. Меня эта тема немножко волнует и мне тоже хочется высказаться.&lt;/p&gt;
&lt;p&gt;Начнëм с того, почему они плохие. Основные аргументы:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;сайты с ними тормозят&lt;/li&gt;
&lt;li&gt;они ломают Гугл&lt;/li&gt;
&lt;li&gt;они ломают curl/wget&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Очевидно, что первый пункт никакого отношения к шебенгам не имеет вообще. Это
уже вопрос того, насколько авторы сайта решили сделать его крутым, динамическим
и прочее (флеш-подобным ;). Например, новый твиттер может порядочно тормозить -
и даже если бы этих шебенгов не было и он бы перезагружался на каждом клике,
тормозил бы он точно так же.&lt;/p&gt;
&lt;p&gt;Google они не ломают, более того, используется именно шебенг вместо просто хеша
специально для того, чтобы Google &lt;a class="reference external" href="http://code.google.com/web/ajaxcrawling/"&gt;мог проиндексировать&lt;/a&gt; эти страницы.&lt;/p&gt;
&lt;p&gt;Так что единственный реальный аргумент - это то, что они ломают курл. Вот этот
момент мне тоже очень не нравится. Для твиттера, правда, на мой взгляд, есть
оправдание - у него есть API. API с большой степенью нивелирует необходимость
ручного разбирания страниц (да и понадëжнее обычно).&lt;/p&gt;
&lt;p&gt;И еще есть два момента - когда поддержка HTML History API будет более широкой
(вот выйдет ФФ4, большая часть людей до него обновится, и будет счастье и
красота), то можно будет использовать нормальные урлы и не перегружать страницу
всë равно. Никаких проблем с курлом или гуглом, но есть одно маленькое западло -
так невозможно сделать веб-приложение (с акцентом на слове &amp;quot;приложение&amp;quot;), только
веб-сайт.&lt;/p&gt;
&lt;p&gt;Самый обычный сайт, который рендерится на сервере и просто умеет отдавать целую
страницу или кусочек (для замены) - так можно сделать, будет и курл работать, и
гугль не заикнëтся, и частичное обновление страницы будет работать (если браузер
умеет History API, конечно) - для примера можно посмотреть на &lt;a class="reference external" href="https://github.com/blog/760-the-tree-slider"&gt;браузер&lt;/a&gt; по
дереву файлов у гитхаба.&lt;/p&gt;
&lt;p&gt;Но вот веб-приложение рендерингом на сервер-сайде не сделаешь. Такое, как Gmail,
280 Slides и прочие. И почему-то забывают все ругатели хешбенгов и History API,
что с таким приложением всего два выхода - либо URL не меняется, либо
меняется. Вопрос поддержи гугла и курла не стоит, им нечего делать.&lt;/p&gt;
&lt;p&gt;Возможно, с текущим увлечением Node.js, кто-нибудь сделает прокси, которое будет
рендерить такое приложение на бекенде и отдавать курлу (как бы еще при этом
получше определять, что клиент не умеет JS?), но это мне пока кажется
довольно оптимистичной идеей. Хотя, наверное, вариант, который работает хотя бы
для простых случаев, сделать не очень сложно - имплементация DOM'а есть, jQuery
et al работают, так что может и получиться.&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/AmazonByteflow/~4/n82S44Ermkk" height="1" width="1"/&gt;</content>
  </entry>

</feed>

