tag:blogger.com,1999:blog-77791673463382184772024-02-19T05:01:11.731+03:00The DiscordГолова дана, чтобы решать задачи. А ещё я в неё ем!Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.comBlogger57125tag:blogger.com,1999:blog-7779167346338218477.post-27216412242711339482020-12-25T15:55:00.001+03:002020-12-25T15:55:20.175+03:00(объявление) Я переехал на отдельный домен<p>После многих лет (буквально) мучительного выбора, где же мне публиковаться, я остановился на варианте с собственным статическим веб-сайтом, где статьи я буду писать на своём компьютере в своём текстовом редакторе, в Markdown.</p><p>Новый адрес моего блога <a href="https://hijarian.info">https://hijarian.info</a>. На блоггере я больше писать не буду.</p><p>Сайт собирается из материалов в разметке Markdown силами утилиты <a href="https://gohugo.io" target="_blank">Hugo</a>. Публикуется на <a href="https://netlify.com">Netlify</a>.</p>Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-37344313673743737242015-03-17T19:08:00.002+03:002015-03-17T19:08:33.604+03:00Как стать хорошим программистом<div dir="ltr" style="text-align: left;" trbidi="on">
Зададимся вопросом: что нужно для того, чтобы стать хорошим программистом? <br />
<br />
Как и везде, нужно развивать качества и навыки.<br />
<br />
Для разработчика есть несколько навыков, которые, фактически, обязательны.<br />
<br />
<ol>
<li> Уметь читать и писать по-английски</li>
<li> Уметь "вслепую" печатать на клавиатуре</li>
<li> Уметь грамотно и связно выражаться</li>
</ol>
<br />
Со стороны качеств - нужно всегда осознавать, что каждый день придётся
узнавать что-то новое, процесс обучения не останавливается никогда.
Нужно также в целом любить читать.<br />
<br />
Со своего опыта могу сказать, что только с такими качествами можно нормально ориентироваться в разработке ПО.<br />
<br />
Есть несколько книг, которые реально полезны для начинающего разработчика, и все они переведены на русский.<br />
<br />
И да, математика в общем не нужна.<br />
<br />
Так как вопрос о том, как стать <i>хорошим</i>
программистом, то я делаю допущение о том, что вопроса "какие именно
программы разрабатывать" не стоит. Нужно осознавать, что в целом можно
выделить четыре типа приложений, и гораздо проще будет "осесть" в
каком-то одном, а не пытаться уметь на мастерском уровне делать всё
(это, на мой взгляд, не достижимо).<br />
<br />
<ol>
<li>Веб-приложения, такие, как, например, этот блог.</li>
<li>Приложения
реального времени, такие, как, например, серьёзные симуляции
виртуальной реальности ("компьютерные игры"), или Google Earth.</li>
<li>Программное
обеспечение контроллеров, встроенное в аппаратуру ("встроенное ПО"),
например, ПО калькулятора, стиральной машины, бионического протеза,
инсулиновой помпы.</li>
<li>"Коробочное" ПО, традиционное, которое условно "устанавливается" на компьютер и позволяет решать какие-то задачи.</li>
</ol>
<br />
Это
разделение лично моё, в разных источниках можно прочитать другие
варианты. Суть в том, что разные программы выглядят, составляются, и
обслуживаются по-разному. Можно ещё условно выделить мобильные
приложения, которые суть нечто среднее между веб-приложениями и
"коробочными".<br />
<br />
Я обещал книги. Вот они:<br />
<br />
<ol>
<li>"Чистый код", Роберт Мартин</li>
<li>"Совершенный код", Стив Макконнелл</li>
<li>"Программист-прагматик. Путь от подмастерья к мастеру", Эндрю Хант et al.</li>
<li>"Идеальный программист", Роберт Мартин</li>
<li>"Алгоритмы. Руководство по разработке", Стивен Скиена</li>
<li>"Человеческий фактор. Успешные проекты и команды", Том ДеМарко et al.</li>
</ol>
<br />
Здесь
перечислены книги, которые образуют фундамент знания разработчика
любого ПО вообще. Они великолепны. Я вот "алгоритмы" ещё сам не дочитал,
но это потому что она просто систематизирует то, что я уже на
собственном опыте узнал из разных источников. После этих книг имеет
смысл почитать хардкор:<br />
<br />
<ol>
<li>"Структура и Интерпретация Компьютерных Программ", Харольд Абельсон et al.</li>
<li>На русском нет: "Concepts, Techniques, and Models of Computer Programming", Peter Van Roy</li>
</ol>
<br />
Есть
ещё несколько книг такого же характера - углубляющих знания и
описывающих хитрые неочевидные приёмы, но я их ещё не читал, поэтому
рекомендовать не буду.<br />
<br />
Ещё есть пять книг, описывающих приемы не разработки, а сопровождения программ.<br />
<br />
<ol>
<li>"Рефакторинг", Мартин Фаулер et al.</li>
<li>"Приемы объектно-ориентированного проектирования. Паттерны проектирования", Эрих Гамма et al.</li>
<li>"Шаблоны корпоративных приложений", Мартин Фаулер et al.</li>
<li>На русском нет: "Specification by Example", Gojko Adzic</li>
<li>"Непрерывное развертывание ПО", Джез Хамбл</li>
</ol>
Весь этот свод из 13 книг - это Веды разработки ПО. Никто не скажет, как стать "хорошим программистом" лучше, чем они.<br />
<br />
Есть,
конечно же, второй способ, более лёгкий, но который нельзя пройти в
одиночку. ;) Можно найти уже установившегося "хорошего программиста" и
работать с ним в паре. Есть техника "парного программирования", но я не
об этом, там нужно, чтобы квалификация была одного уровня. Я о том,
чтобы делать за него какую-нибудь вспомогательную работу, и параллельно
учиться на его примере.<br />
<br />
Помимо фундаментальных знаний, нужны навыки собственно программирования, решения задач составлением программ.<br />
<br />
Для
их развития я лично бы порекомендовал выбрать какой-то один язык
программирования и начать на нём решать какие-нибудь задачи, для начала
простенькие.<br />
<br />
Например, вы решили стать разработчиком веб-приложений.
Освойте платформу Ruby on Rails, пройдя последовательно вот это
руководство: <a href="http://railstutorial.ru/chapters/4_0/beginning">http://railstutorial.ru/chapters/4_0/beginning</a> или
платформу Node.js, начав отсюда:
<a href="https://nodejs.org/documentation/tutorials/">https://nodejs.org/documentation/tutorials/</a> (я предупреждал про
английский язык), или платформу PHP, начав отсюда:
<a href="http://php.net/manual/ru">http://php.net/manual/ru</a>/ (я предупреждал про любовь к чтению). Это в
целом равномощные платформы, разница только в синтаксисе и некоторых
специфических возможностях, трудно реализуемых на смежных платформах.<br />
<br />
Или
вы решили писать встроенное ПО. Тогда стоит начать с проекта Arduino:
<a href="http://www.arduino.cc/">http://www.arduino.cc/</a>, правда придётся вложиться в "железо".<br />
<br />
Или
вы решили писать "коробочное" ПО, тут у вас огромный выбор языков
программирования, например, Haskell: <a href="https://www.haskell.org/">https://www.haskell.org</a>/ или
Erlang: <a href="http://learnyousomeerlang.com/">http://learnyousomeerlang.com/</a> или C#:
<a href="https://msdn.microsoft.com/ru-ru/library/67ef8sbd.aspx">https://msdn.microsoft.com/ru-ru/library/67ef8sbd.aspx</a> или Common Lisp:
<a href="http://www.gigamonkeys.com/book/">http://www.gigamonkeys.com/book/</a><br />
<br />
Кстати, для оценки "хорошести"
программиста можно воспользоваться вот этой "матрицей компетентности
программиста": <a href="http://dev.by/pages/programming_matrix">http://dev.by/pages/programming_matrix</a>
</div>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com1tag:blogger.com,1999:blog-7779167346338218477.post-44585664709819174162014-10-21T18:29:00.004+04:002014-10-31T15:47:34.730+03:00Web Application Development with Yii 2 and PHP book (by me)<div dir="ltr" style="text-align: left;" trbidi="on">
Holy cow, I wrote a book!<br />
<br />
After the terror of 6 months writing, re-reading and re-writing, "<a href="https://www.packtpub.com/web-development/web-application-development-yii-2-and-php" target="_blank">Web Application Development with Yii 2 and PHP</a>" have finally been published at 26th of September.<br />
<br />
It was completely unexpected for me to receive this contract, I have never used version 2 of Yii framework, which was in early beta at the time, and had quite small Web presence to be spotted by any publisher. But it happened (thank you, God).<br />
<br />
Yeah, URL is <a href="https://www.packtpub.com/web-development/web-application-development-yii-2-and-php">https://www.packtpub.com/web-development/web-application-development-yii-2-and-php</a>, and, honestly, without any self-promotion, if you want to develop using Yii 2, go buy this book, because I took a lot of time inspecting this framework so you will not need to do the same. For example, you will not get the exact details of how the error reporting works in Yii from the official documentation. The code is always the last source of truth, and I tried hard to express in plain technical English what it actually does.<br />
<br />
To be honest, I strongly dislike Yii. I think that Yii 2 is enormous leap forward since version 1.1.x but I still dislike this framework. Each book I read about pro-level software development tells me things which I find completely ignored or misinterpreted in Yii. Reading its code was also not a pleasurable experience.<br />
<br />
And trust me, our team at Clevertech is maintaining a ~80KLOC application based on Yii 1.1 idioms (initially) and at second year we came to conclusion that we don't need most of it. For example the whole ActiveRecord layer. I don't even talk about basing the project structure on the conventions, "models-controllers-views" directory triad is a biggest bullshit I saw so far in my career. Our "common/models" directory has like 15 subfolders in it, and we have no other choice, because you cannot stuff everything into ActiveRecord descendant, and there's no "domain_models" directory in the bastard of MVC which Yii implements.<br />
<br />
Err... I went away. The book. Right.<br />
<br />
I wrote it nevertheless. I am working with Yii application (version 1.1 though) every day and actually can tell a thing or two about it. So, publisher asked and I answered.<br />
<br />
I had two main premises when writing.<br />
<br />
<ol style="text-align: left;">
<li>Show how you can use Yii 2 <i>when developing your application</i>, not simply "how to use Yii 2", which is covered already by official documentation.</li>
<li>Show how to work in ATDD manner with CI in mind, even using the framework. There is clearly not enough literature which shows how to properly develop applications right from the start, without rudimentary examples like bowling kata.</li>
</ol>
So, the whole Chapter 2 is a glorious implementation of the point 2. Or so I thought. This chapter ended being probably of the worst quality from the whole book. It contains so much testing harness and deployment machine setup that in the end I separated the whole Appendix out of it and had thrown away around 10 pages which were not related to Yii 2 at all. And it may be still so boring to read (if you wanted to learn Yii 2 and know that stuff already) that probably 9/10 readers will drop the book right there and not go to the really interesting stuff like how Yii 2 extensions work or its event system or new Request/Response model.<br />
<br />
The book uses a single example application, slowly adding more and more features from chapter to chapter. The application is pretty trivial, it's a CRM skeleton, and by the end of the book there are mainly two features: adding the customer and fetching his info given the phone number; and sign in to the system using login/password. There are also a whole Yii 2 extension, though.<br />
<br />
I seriously tried to put all I learned through the professional literature like the DDD, GOOS, Clean Code and such, but I personally think I failed at this quite spectacularly. :) But apart from that, I tried my best to show the pieces of the Yii 2 picture which are not covered (and probably will never be) by the official docs and references. Hope it'll be useful for you.<br />
<br />
Also, there's no other book on this topic yet so you have no choice anyway. ;)<br />
<br />
There were a serious misunderstanding from my part also: I completely missed the point that this book is legally the "second edition" of the previous book, "<a href="https://www.packtpub.com/web-development/web-application-development-yii-and-php" target="_blank">Web Application Development with Yii and PHP</a>" (note the absence of the number 2) by different author, Jeffrey Winesett. The outcome of this is that I become something like a co-author with both names present on the cover. It hurt a bit, as I lived 6 months in permanent crunch mode (book writing is not my fulltime job after all) and Jeff is not related to this book at all, but we both are listed as authors equally.<br />
This does not apply to the royalties, though, so I am not bothered by this so much.<br />
<br />
I did this for greed, not for pride, after all. :) This is not the kind of book I would be proud of. Personally, I would promote Common Lisp-based development, not the Yii 2 framework (doh) over PHP (holy cow).<br />
<br /></div>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com1tag:blogger.com,1999:blog-7779167346338218477.post-66824746300737454352014-10-21T16:00:00.000+04:002014-10-21T16:00:15.094+04:00The (not so great) Return<div dir="ltr" style="text-align: left;" trbidi="on">
Well, no luck with Logdown. Call me greedy for fame, but here in Blogger, integrated with Google+, I have a lot larger audience coverage than at Logdown.<br />
<br />
Also, I am shifting from Markdown to AsciiDoc, so probably generated HTML will be more Blogger-friendly (I think "no way" though).<br />
<br />
So, in next several days (probably) I'll return all my posts written at Logdown to here (there not so many anyway) and will continue to write here.<br />
<br />
By the way, I have written and published a book with Packt. I'll write about it in another post.<br />
<br />
<hr />
<br />
Нафиг Logdown. Называйте меня жадным до славы, но здесь на блоггере с интеграцией в гуглоплюс у меня больше покрытие чем там (какого хрена, мои посты даже в гугле не проиндексированы).<br />
<br />
Также, я теперь пишу не в Markdown а на AsciiDoc, так что, возможно, сгенерированный HTML будет более съедобным для блоггера (хера с два, я подозреваю).<br />
<br />
Так что через насколько дней я всё написанное на Logdown мигрирую обратно сюда и продолжу писать здесь.<br />
<br />
Между прочим, я написал и опубликовал книгу с Packt Publishing. Напишу об этом в другом посте.</div>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com1tag:blogger.com,1999:blog-7779167346338218477.post-26034514537888243012013-09-17T22:26:00.000+04:002013-09-17T22:26:55.731+04:00Переезжаю с Blogger на Logdown / Migrating from Blogger to Logdown<div dir="ltr" style="text-align: left;" trbidi="on">
Ну что ж, пора мучений с HTML-разметкой закончилась. Нашёл блогоплатформу <a href="https://logdown.com/" target="_blank">Logdown</a>, там можно писать в github-flavored markdown, плюс подсветка кода встроенная. Плюс встроенные шаблоны просто чудесные, хотя шрифтам явно не хватает поддержки кириллицы.<br />
<br />
В этом блоге больше писать не буду, буду в logdown теперь. Все старые посты отсюда перенёс туда (разметка, конечно, вся поехала, придётся чинить). Правда, этот блог удалять не буду, мало ли у кого постоянные ссылки сюда есть.<br />
<br />
Новый адрес тут: <a href="http://hijarian.logdown.com/">hijarian.logdown.com</a> (yeah, this is my new address, my dear English-speaking readers).</div>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-41437996495284745212013-07-20T18:14:00.000+04:002013-07-20T18:14:00.840+04:00How to package and use Yii framework in PHAR archive<p>Okay, today's the task: pushing all of 1892 files of Yii framework to the Git repository is a burden, and upgrading it to new version pollutes the git log with changes to files you don't care about.
Let's package it into a <a href="http://www.php.net/manual/en/intro.phar.php">PHAR</a>!</p>
<h2>Packaging</h2>
<p><a href="https://github.com/mindplay-dk">Rasmus Schultz</a> made a <a href="https://gist.github.com/hijarian/6045041">special script to package the Yii framework into a PHAR archive</a>. I forked it to save for a future (at least my GitHub account will live as long as this blog).</p>
<p>You need just to put this script to the root of Yii codebase (cloned github repo, for example), and run it as usual:</p>
<pre><code class="shell">php yii-phar.php
</code></pre>
<p>This will create the PHAR archive in the same directory.</p>
<p>Hovewer, beware the <strong>catch 1</strong>: PHP can refuse to create packed archive, emitting the following error:</p>
<pre>PHP Fatal error: Uncaught exception 'BadMethodCallException'
with message 'unable to create temporary file'
in /path/to/your/yii/root/yii-phar.php:142
Stack trace:
#0 /path/to/your/yii/root/yii-phar.php(142): Phar->compressFiles(4096)
#1 {main}
thrown in /parh/to/your/yii/root/yii-phar.php on line 142
</pre>
<p>I decided to just remove lines 140 and 142 from the script:</p>
<pre>
echo "Compressing files ...\n\n";
$phar->compressFiles($mode);
</pre>
<p>And that's all. I can bear with 20 MB file in repo, and don't really care about compression.</p>
<h2>Using</h2>
<p>To connect the resulting PHAR to your Yii application, replace your usual:</p>
<pre><code class="php">require_once('/path/to/your/yii/framework/yii.php');
</code></pre>
<p>With the following:</p>
<pre><code class="php">new Phar('/path/to/yii.phar');
require_once('phar://yii/yii.php');
</code></pre>
<p>Note that in <code>new Phar()</code> invocation you should use real path to your phar archive file, but <em>second line should be written verbatim</em>, as the PHAR which becomes created is being made with alias 'yii', using feature described <a href="http://www.php.net/manual/en/phar.construct.php">in the documentation for Phar::__construct</a>.</p>
<p>However, of course, there's a <strong>catch 2</strong>: Yii built-in asset manager (<a href="http://www.yiiframework.com/doc/api/1.1/CAssetManager">CAssetManager</a>) has too specific `publish` method, unable to cope with custom PHP streams. So, we need the fixed version.</p>
<p>I decided to create a descendant of `CAssetManager` descriptively called `PharCompatibleAssetManager` with the following definition exactly:</p>
<pre><code class="php">/**
* Class PharCompatibleAssetManager
*
* As we use Yii packaged into .phar archive, we need to make changes into the Asset Manager,
* according to the https://code.google.com/p/yii/issues/detail?id=3104
*
* Details about packaging Yii into .phar archive can be found at
* https://gist.github.com/mindplay-dk/1607318
*/
class PharCompatibleAssetManager extends CAssetManager
{
protected $_published = array();
public function publish($path,$hashByName=false,$level=-1,$forceCopy=null)
{
if($forceCopy===null)
$forceCopy=$this->forceCopy;
if($forceCopy && $this->linkAssets)
throw new CException(Yii::t('yii','The "forceCopy" and "linkAssets" cannot be both true.'));
if(isset($this->_published[$path]))
return $this->_published[$path];
$isPhar = strncmp('phar://', $path, 7) === 0;
$src = $isPhar ? $path : realpath($path);
if ($isPhar && $this->linkAssets)
{
throw new CException(
Yii::t(
'yii',
'The asset "{asset}" cannot be published using symlink, because the file resides in a phar.',
array('{asset}' => $path)
)
);
}
if ($src !== false || $isPhar)
{
$dir=$this->generatePath($src,$hashByName);
$dstDir=$this->getBasePath().DIRECTORY_SEPARATOR.$dir;
if(is_file($src))
{
$fileName=basename($src);
$dstFile=$dstDir.DIRECTORY_SEPARATOR.$fileName;
if(!is_dir($dstDir))
{
mkdir($dstDir,$this->newDirMode,true);
@chmod($dstDir,$this->newDirMode);
}
if($this->linkAssets && !is_file($dstFile)) symlink($src,$dstFile);
elseif(@filemtime($dstFile)<@filemtime($src))
{
copy($src,$dstFile);
@chmod($dstFile,$this->newFileMode);
}
return $this->_published[$path]=$this->getBaseUrl()."/$dir/$fileName";
}
elseif(is_dir($src))
{
if($this->linkAssets && !is_dir($dstDir))
{
symlink($src,$dstDir);
}
elseif(!is_dir($dstDir) || $forceCopy)
{
CFileHelper::copyDirectory($src,$dstDir,array(
'exclude'=>$this->excludeFiles,
'level'=>$level,
'newDirMode'=>$this->newDirMode,
'newFileMode'=>$this->newFileMode,
));
}
return $this->_published[$path]=$this->getBaseUrl().'/'.$dir;
}
}
throw new CException(Yii::t('yii','The asset "{asset}" to be published does not exist.',
array('{asset}'=>$path)));
}
}
</code></pre>
<p>I'm really, really sorry that you had to read this traditionally horrible Yii code, but that was inevitable... :(</p>
<p>Main change was starting from the <code>$isPhar = strncmp('phar://', $path, 7) === 0;</code> part.</p>
<p>Now just link this asset manager instead of built-in one:</p>
<pre><code class="php"><strong>config/main.php:</strong>
'components' => array(
'assetManager' => array(
'class' => 'your.alias.path.to.PharCompatibleAssetManager',
),
)
</code></pre>
<h2>Congratulations!</h2>
<p>Now your Yii web application uses phar archive instead of huge pile of separate files. They say that this increases performance, but my personal reasons was just to reduce the number of files inside the repository.</p>Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-90274986647696999192013-06-29T16:57:00.000+04:002013-06-29T16:57:01.541+04:00Running Database Dependent Tests on Ramdisk<p>Today's guess is this: you have a test harness which utilizes the database, and it has enough test cases in it for full test run to be so slow you cringe at the very thought of launching it.</p>
<p>We discuss a lifehack-style solution to this problem: putting the DBMS which will be used by our test harness <em>completely on a <a href="https://en.wikipedia.org/wiki/RAM_drive">RAM disk</a></em>, so it'll operate from much faster memory than the hard drive (even faster than from SSD).</p>
<p>Main issue is this: as you probably need only a test datasets at ramdisk, and only for a period of running test suite, you will need <em>separate</em> DBMS instances to work on ramdisk, not the ones already installed on your system.</p>
<p>Here we'll look at how to prepare <a href="https://www.mysql.com/">MySQL</a> and <a href="http://www.mongodb.org/">MongoDB</a> instances to work on ramdisk.</p>
<h2>End Result</h2>
<p>In the end you'll get the special preparation script which you should launch before your tests.
After this, your test suite will run with the isolated MySQL and MongoDB instances on top of ramdisk.</p>
<p>If your test suite has large quantities of integration tests using databases, this will greatly increase the speed of test run. It is reported in one particular case that the drop in run time was from 1:30 to 18 seconds, 5 times faster.</p>
<h2>Prequisites</h2>
<p>You should have a *nix system as a MySQL Sandbox (see below) works only there. OSX probably will do, too.
This system should have some bash-like shell (obviously) and Perl installed.
Kernel should have <a href="https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt"><code>tmpfs</code></a> support.
Your nonprivileged user should be able to mount filesystems, or you'll need to hack the script to introduce <code>sudo</code> at mount step (assuming your user can sudo).</p>
<p>For isolated MySQL instance you need the <a href="https://dev.mysql.com/downloads/mysql/">MySQL distirbutive downloaded from the website</a>.
For isolated MongoDB instance you need the <a href="http://www.mongodb.org/downloads">MongoDB distirbutive downloaded from the website</a>. Note, however, that the whole MongoDB server is contained in just a single binary file ~8MB in size.</p>
<p>Of course as we will work completely in memory you have to be sure that you have enough RAM to store your (presumably test) datasets. </p>
<h2>Ramdisk</h2>
<p>Making ramdisk is very simple with latest Linux:</p>
<pre><code>mount -t tmpfs $RAMDISK_NAME $RAMDISK_DIR
</code></pre>
<p>as root.</p>
<p><code>RAMDISK_NAME</code> is some identifier for mountpoints table.
<code>RAMDISK_DIR</code> is the directory which will be turned into RAM-based filesystem.</p>
<p>After this <code>mount</code> action, anything you put into <code>RAMDISK_DIR</code> will be placed into memory, without interaction with the physical hard drive.</p>
<p>Of course, it means that <em>after unmounting the ramdisk everything which was in it will be lost</em>.</p>
<h3>Shutting down the ramdisk</h3>
<p>Just unmount the created mount point:</p>
<pre><code>umount $RAMDISK_NAME
</code></pre>
<p>as root.</p>
<p>Note that you probably should stop all running services which still use the ramdisk prior to unmounting!</p>
<h2>Isolated MySQL instance</h2>
<p>We'll use the <a href="http://mysqlsandbox.net/">MySQL Sandbox</a> project to launch isolated MySQL instances.</p>
<p>For it to work you need the <a href="https://dev.mysql.com/downloads/mysql/">MySQL distirbutive downloaded from the website</a>.</p>
<p>MySQL Sandbox is installed with the following command:</p>
<pre><code># cpan MySQL::Sandbox
</code></pre>
<p>as root, and you'll need to run it as follows:</p>
<pre><code>SANDBOX_HOME="$RAMDISK_DIR" make_sandbox "$MYSQL_PACKAGE" -- \
--sandbox_port="$MYSQL_PORT_DESIRED" --sandbox_directory="$MYSQL_DIRNAME"
</code></pre>
<p>It's a one-liner split to two lines for readability.</p>
<p>Note that you need root privileges only to <em>install</em> the MySQL Sandbox application itself, all further communication with it will be done from unprivileged account, most possibly the same under which you launch the test suite.</p>
<p>We need to set the <code>SANDBOX_HOME</code> variable prior to launching the sandbox factory because that's how we control where it'll put the sandboxed MySQL instance. By default it'll use <code>$HOME/sandboxes</code>, which is probably not what you need.
Note that <code>RAMDISK_DIR</code> is the same directory that the one we prepared in previous step.</p>
<p><code>MYSQL_PACKAGE</code> is a full path to the MySQL distirbutive package downloaded from website.
Please note that MySQL Sandbox <em>will unpack it to the same directory</em> and will essentially use this unpacked contents to launch the sandboxed MySQL.
So, probably, you'll need to move the package to ramdisk, too, to increase performance of actually launching and running the MySQL server itself, however, note that unpacked 5.6.0 contents are 1GB in size.</p>
<p>Remember the <code>MYSQL_PORT_DESIRED</code> value you use here, because you'll need to use it to configure your test suite to point at correct MySQL instance.</p>
<p><code>MYSQL_DIRNAME</code> is of least importance here, because it's just a name of a subfolder under the <code>SANDBOX_HOME</code> in which this particular sandbox will be put.</p>
<p>After <code>make_sandbox</code> ended it's routine you can check that your sandbox is indeed working by running:</p>
<pre><code>"$RAMDISK_DIR/$MYSQL_DIRNAME/use"
</code></pre>
<h3>Connection to Isolated MySQL Instance</h3>
<p>You should use the following credentials to connect to sandboxed MySQL:</p>
<pre><code>* host : '127.0.0.1'
* port : $MYSQL_PORT_DESIRED
* username : 'msandbox'
* password : 'msandbox'
</code></pre>
<p>Please note that you must use <code>127.0.0.1</code> value for host and not a <code>localhost</code> as usual, because of sandbox internal security configuration.</p>
<h3>Shutting Down the Isolated MySQL Instance</h3>
<p>To shutdown the sandboxed MySQL, issue the following command:</p>
<pre><code>"$RAMDISK_DIR/$MYSQL_DIRNAME/stop"
</code></pre>
<p>or more forceful</p>
<pre><code>"$RAMDISK_DIR/$MYSQL_DIRNAME/send_kill"
</code></pre>
<p>This commands are needed mostly to stop the working daemon; after the unmounting of ramdisk all of sandbox data will be purged out of existence.</p>
<h2>Isolated MongoDB instance</h2>
<p>MongoDB server is contained in just a single binary file so it'll be a lot more easier compared to MySQL.</p>
<p>You'll need the <a href="http://www.mongodb.org/downloads">MongoDB distirbutive downloaded from the website</a>, too.
This time unpack it to some directory.</p>
<p>After that, you can launch a separate instance of MongoDB with the following command:</p>
<pre><code>"$MONGODB_BIN" --dbpath="$MONGODB_DIR" \
--pidfilepath="$MONGODB_DIR/mongodb.pid \
--port $MONGODB_PORT_DESIRED \
--fork --logpath="$MONGODB_DIR/mongodb.log"
</code></pre>
<p><code>MONGODB_BIN</code> is a <code>/bin/mongod</code> path preceded by the full path to the unpacked MongoDB distributive.
Here you can even use your system MongoDB package, in case you have it installed.
As a full example, <code>MONGODB_BIN</code> can have a value of <code>~/systems/mongodb-linux-x86_64-2.4.4/bin/mongod</code></p>
<p><code>MONGODB_DIR</code> is a path to directory under <code>RAMDISK_DIR</code> to which this MongoDB instance should put it's files.
For example, it can be just a <code>$RAMDISK_DIR/mongo</code>.</p>
<p>As with MySQL, <code>MONGODB_PORT_DESIRED</code> is a crucial parameter to specify the correct MongoDB instance to connect to.
Remember it as you will need to set it up in your test suite.</p>
<h3>Connecting to Isolated MongoDB Instance</h3>
<p>By default MongoDB do not enforce any usernames or passwords so you need to just use the hostname and port parameters.</p>
<pre><code>* host : 'localhost'
* port : $MONGODB_PORT_DESIRED
</code></pre>
<p>For example, for PHP Mongo extension, you get a connection to this instance as follows:</p>
<pre><code>$connection = new MongoClient("mongodb://localhost:$MONGODB_PORT_DESIRED");
</code></pre>
<h3>Shutting Down the Isolated MongoDB Instance</h3>
<p>As you provided the <code>--pidfilepath</code> commandline argument when launching the MongoDB server, the following command should do the trick:</p>
<pre><code>cat "$MONGODB_DIR/mongodb.pid" | xargs kill ; rm "$MONGODB_DIR/mongob.pid"
</code></pre>
<p>Essentially we are feeding the <code>kill</code> command with the contents of pidfile and removing it afterwards.</p>
<h1>Bash scripts to automate the sandbox starting and stopping</h1>
<p>There is a <a href="https://github.com/hijarian/DB-sandbox">GitHub repository with the example scripts</a> nicely laid out along with the comments.</p>
<p>There are three scripts:</p>
<ul>
<li><code>db_sandbox_properties.sh</code>: this is the all variable parameters you need to properly setup the sandboxes.</li>
<li><p><code>db_sandbox_start.sh</code>: this script you run before your test suite. </p>
<p>It was updated with the command to copy the tables schema from source MySQL instance to sandbox MySQL instance, so, if you have an accessible up-to-date MySQL instance with the schema for your tests, this script will copy schema to sandbox so you will have a DB ready to accept test cases. </p>
<p>NOTE that you will need the sudo rights for your unprivileged user to successfully mount the ramdisk.
If you do not have them, you can hack the <code>mount</code> command in any way you see sufficient to successfully mount the ramdisk.</p></li>
<li><p><code>db_sandbox_stop.sh</code>: this script you run when you don't need the sandbox anymore. It'll stop both MySQL and MongoDB and unmount the ramdisk (note that you'll need the sudo rights for this, too).</p></li>
</ul>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-64332668626956415052013-06-12T10:24:00.000+04:002013-06-12T10:24:09.284+04:00Как запускать что-либо при запуске Debian<p>Допустим, хочется что-то запускать прямо при старте Debian. Вот необходимые действия для этого:</p>
<pre># vim /etc/init.d/myscript
# chmod +x /etc/init.d/myscript
# insserv myscript</pre>
<p>В файле <code>/etc/init.d/myscript</code> должно быть по минимуму следующее:
<pre><code class="shell bash">#!/bin/sh
### BEGIN INIT INFO
# Provides: myscript
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Run some stuff
# Description: Some descriptions
### END INIT INFO
</code></pre>
<p><a href="http://wiki.debian.org/LSBInitScripts">Подробности об этой шапке можно прочитать в вики Debian</a>. Название скрипта <code>myscript</code> должно быть в точности повторено в названии файла скрипта, в поле <code>Provides</code> и при вызове <code>insserv</code>. </p>
<p>Ранлевелы можно посмотреть здесь: <a href="http://wiki.debian.org/RunLevel">http://wiki.debian.org/RunLevel</a>.</p>
<p><a href="http://wiki.debian.org/LSBInitScripts/DependencyBasedBoot">Про dependency-based-boot там же в вики написано</a>.</p>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-91025136756470934632013-06-06T13:20:00.000+04:002013-06-06T13:20:17.585+04:00Как написать веб-утилиту небольшого размера на Common Lisp
<p>Когда-то написал небольшую программку для сообщества игры <a href="http://ru.playpw.com">Prime World</a>.
Она парсит <a href="http://ru.playpw.com/ratings.html">таблицу рейтингов</a> и составляет по ней линейные графики, плюс вычислят средний рейтинг (в рейтинге указываются первые 25 мест) по каждому персонажу.
Персонажей 62, соответственно, линий на графике тоже 62. :)</p>
<p>Здесь напишу, как и из чего сделал и как оно работает.</p>
<p><a href="https://github.com/hijarian/Prime-World-Dno">Полный исходный код выложил к себе на GitHub</a>.</p>
<h2>Как работает</h2>
<p>План был такой: приложение представляет собой одну HTML страницу, с одним файлом CSS и одним Javascript.</p>
<p>При загрузке страницы яваскрипт сразу делает AJAX-запрос на бэкэнд за данными.</p>
<p>Бэкэнд грабит исходную HTML-страницу, при помощи XPath выбирает значения рейтингов, имена игроков, места и имена персонажей.
Затем кодирует это всё в JSON и отправляет скрипту обратно.</p>
<p>Скрипт отрисовывает полученные от бэкэнда рейтинги в виде графика при помощи библиотеки <a href="http://www.highcharts.com/">Highcharts</a>.
Дополнительно бэкэнд возвращает вычисленные средние значения рейтинга; они отображаются в виде таблицы, которая обрабатывается javascript библиотекой <a href="http://www.datatables.net/">DataTables</a>, чтобы добавить сортировку.</p>
<p>Между таблицей и графиком переключаемся кнопками.</p>
<p><a href="https://pw-dno.herokuapp.com/">Вот само приложение</a>.</p>
<p>Как я уже сказал, приложение достаточно маленькое.</p>
<p>В качестве недостатка можно упомянуть то, что каждый раз, когда приложение открывается, делается запрос на исходный сайт Нивала.
Делать так, конечно, дурной тон, и стоит прикрутить какое-нибудь кэширование на стороне сервера.
Хотя у них всё равно топ-25 не так часто обновляется, да и посещаемость никакая, так что для начала сойдёт.</p>
<h2>Как собрано</h2>
<p>Серверная часть основана на <a href="http://weitz.de/hunchentoot/">Hunchentoot</a>, то есть, мы сами себе веб-сервер.</p>
<p>Вот все необходимые URL:</p>
<ol>
<li><code>/</code>, по которому возвращается HTML страница приложения.</li>
<li><code>/data</code>, который AJAX endpoint, возвращающий JSON объект с данными</li>
<li><code>/assets/scripts.js</code>, клиентский скрипт, который раскладывает данные из JSON в график и таблицу</li>
<li><code>/assets/styles.css</code>, бумажка со стилями (с украшениями я не парился, в стилях только сокрытие некоторых элементов для инициализации интерфейса).</li>
<li><code>/assets/loading.gif</code>, показываем эту картинку пока <code>/data</code> грузится.</li>
<li><code>/favicon.ico</code>, иконка до кучи, честно стырил из пакета <a href="http://html5boilerplate.com/">Html5 Boilerplate</a>.</li>
</ol>
<p>Хостимся на <a href="https://www.heroku.com/">Heroku</a>.</p>
<h3>Извлечение исходных данных</h3>
<p>Сам парсинг исходной веб-страницы полностью заключён в одном файле под названием <code>dno.lisp</code>, и единственная функция, которая оттуда нужна публично, это <code>send-data-from-origin-as-json</code>, которая генерирует строку в JSON формате, содержащую данные, нужные в <code>scripts.js</code> для того, чтобы построить графики и таблицу.
<a href="https://github.com/hijarian/Prime-World-Dno/blob/master/src/dno.lisp">Сам файл <code>dno.lisp</code> можно посмотреть в репозитарии на гитхабе</a>, я здесь его приводить не буду, слишком большой.</p>
<p>В итоге мы можем делать <code>(load "dno.lisp")</code> и получать с этого функцию, которая даст нам JSON, который мы можем возвращать как результат AJAX запроса, то есть, то, что нужно для пункта 2.</p>
<h3>Отдача статических файлов</h3>
<p>В идеологии hunchentoot'а (если не углубляться в детали), инициализация приложения выглядит так:</p>
<ol>
<li><p>Настраиваем все пути в приложении, вызывая <code>hunchentoot:create-folder-dispatcher-and-handler</code>, <code>hunchentoot-create-static-file-dispatcher-and-handler</code>, <code>hunchentoot:define-easy-handler</code> и т. п.
Это как раз было упрощено при помощи хелперов в предыдущем пункте.</p></li>
<li><p>Создаём экземпляр приложения заклинанием:</p>
<pre><code class="lisp">(hunchentoot:start (make-instance 'hunchentoot:easy-acceptor
:port 4242)))
</code></pre></li>
</ol>
<p>Для начала делаем три хелпера в отдельном файле, для того, чтобы добавление путей в Hunchentoot не было пыткой:</p>
<p><strong>helpers.lisp</strong> </p>
<pre><code class="lisp">(defun publish-directory (uri dirname)
(push (hunchentoot:create-folder-dispatcher-and-handler uri dirname)
hunchentoot:*dispatch-table*))
(defun publish-file (uri filename)
(push (hunchentoot:create-static-file-dispatcher-and-handler uri filename)
hunchentoot:*dispatch-table*))
(defmacro publish-ajax-endpoint (uri name params &body body)
`(hunchentoot:define-easy-handler (,name :uri ,uri) ,params
(setf (hunchentoot:content-type*) "application/json")
,@body))
</code></pre>
<p>Имея эти три хелпера, файл инициализации, фактически, будет иметь следующий вид:</p>
<p><strong>init.lisp</strong></p>
<pre><code class="lisp">;; Webroot
(publish-file "/" "webroot/index.html")
;; Favicon, just because
(publish-file "/favicon.ico" "webroot/favicon.ico")
;; Images, CSS and JS files referenced from index.html
(publish-directory "/assets/" "assets/")
;; AJAX endpoint to grab data to display in chart
(publish-ajax-endpoint "/data" data () (send-data-from-origin-as-json))
(defun run ()
"Launch Hunchentoot web server instance on default port"
(hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 4242)))
</code></pre>
<p>Само содержимое файлов <a href="https://github.com/hijarian/Prime-World-Dno/blob/master/webroot/index.html"><code>index.html</code></a>, <a href="https://github.com/hijarian/Prime-World-Dno/blob/master/assets/scripts.js"><code>scripts.js</code></a> и <a href="https://github.com/hijarian/Prime-World-Dno/blob/master/assets/styles.css"><code>styles.css</code></a> можно посмотреть на гитхабе, здесь я описываю только то, как приложение собрано, а не как оно работает.</p>
<p>Мы не можем сделать <code>(publish-directory "/" "webroot/")</code>, потому что мы хотим дефолтный хэндлер для пути <code>/</code>.</p>
<p>Я завернул старт приложения в отдельную функцию (и не вызываю её сразу же), потому что так будет удобнее в дальнейшём.</p>
<p>Теперь мы можем делать так:</p>
<pre><code class="lisp">(load "dno.lisp")
(load "helpers.lisp")
(load "init.lisp")
(run)
</code></pre>
<p>После чего открываем браузер по адресу http://localhost:4242/ и наблюдаем готовое приложение, при условии, что в том же каталоге, что и эти два скрипта, находятся папки <code>webroot</code> и <code>assets</code> с соответствующими файлами в них.</p>
<h3>Структурирование</h3>
<p>Теперь запакуем всё в ASD.
Вот моё определение приложения:</p>
<p><strong>dno.asd</strong></p>
<pre><code class="lisp">(asdf:defsystem #:dno
:serial t
:description "Presents ratings of players of Prime World as charts."
:author "Mark Safronov <hijarian@gmail.com>"
:license "Public Domain"
:depends-on (#:drakma
#:cl-ppcre
#:cl-libxml2
#:iterate
#:cl-json
#:hunchentoot)
:components ((:file "package")
(:module :src
:serial t
:components ((:file "helpers")
(:file "dno")
(:file "init")))))
</code></pre>
<p><strong>package.lisp</strong></p>
<pre><code class="lisp">(defpackage #:dno
(:export :run)
(:use #:cl
#:cl-user
#:hunchentoot))
</code></pre>
<p>Как видно, все три исходника на Common Lisp, описанные выше, были переложены в отдельный подкаталог <code>src</code>.
Понятное дело, во всех файлах теперь первой строчкой идёт вызов <code>(in-package :dno)</code>.</p>
<p>Заметьте, что в ASD прописаны библиотеки, от которых зависит приложение (<code>cl-ppcre</code>, <code>cl-libxml2</code>, <code>iterate</code> и <code>cl-json</code> используются только в <code>dno.lisp</code>, на самом деле), однако сам пакет (<code>package.lisp</code>) не подключает эти библиотеки.
Это просто дело вкуса: я предпочитаю использовать символы из чужих пакетов по их полному имени, не сокращая.
Так сразу понятно, в какую документацию лезть.</p>
<p>Теперь у нас такая структура файлов:</p>
<pre><code>assets/
scripts.js
styles.css
webroot/
index.html
favicon.ico
src/
dno.lisp
init.lisp
helpers.lisp
dno.asd
package.lisp
</code></pre>
<h3>Запуск на локальной машине</h3>
<p>Так как у нас теперь определён ASD пакет, мы можем воспользоваться механизмами <a href="http://www.quicklisp.org">Quicklisp</a> для того, чтобы максимально просто загружать приложение на локальной машине.</p>
<p>Если в рабочем каталоге Quicklisp в подкаталоге local-projects сделать символическую ссылку на каталог приложения, то можно будет подключать пакет приложения простым вызовом <code>(ql:quickload :dno)</code>.</p>
<p>Таким образом, кладём в корневой каталог приложения следующий скрипт запуска:</p>
<p><strong>runner.lisp</strong></p>
<pre><code class="lisp">(in-package :cl-user)
(ql:quickload :dno)
(dno:run)
</code></pre>
<p>Теперь видно, зачем нужна отдельная функция <code>run</code>, определённая в <code>init.lisp</code>: для того, чтобы разделить загрузку самого приложения в рантайм и запуск приложения как веб-сервера.</p>
<p>Всё, готовое веб-приложение можно запустить из консоли, например, так:</p>
<pre><code class="shell">$ cd path/to/dno/app
$ sbcl
* (load "runner.lisp")
</code></pre>
<p>Понятное дело, вместо SBCL может быть любой Лисп на ваш выбор.</p>
<p>Консоль становится неюзабельной после этого.
Когда понадобится загасить приложение — нажатие Ctrl+D убивает приложение вместе с рантаймом лиспа.</p>
<h3>Деплой на Heroku</h3>
<p>Благодаря <a href="http://github.com/jsmpereira/heroku-buildpack-cl.git">специальному buildpack'у для CL</a> появилась возможность хостить SBCL + Hunchentoot приложения на Heroku.</p>
<p>Для этого нужно подготовить приложение следующим образом:</p>
<ol>
<li><a href="https://devcenter.heroku.com/articles/quickstart">Настраиваем у себя подключение к Heroku</a>.</li>
<li><code>heroku create -s cedar --buildpack http://github.com/jsmpereira/heroku-buildpack-cl.git</code>. Запоминаем название проги, которое Heroku нам сгенерировало.</li>
<li><code>heroku labs:enable user-env-compile -a myapp</code>, вместо myapp пишем название проги из п.2.</li>
<li><code>heroku config:add CL_IMPL=sbcl</code></li>
<li><code>heroku config:add CL_WEBSERVER=hunchentoot</code></li>
<li><code>heroku config:add LANG=en_US.UTF-8</code></li>
</ol>
<p>Теперь в корневом каталоге приложения нужно добавить следующий скрипт, который ожидает buildpack:</p>
<p><strong>heroku-setup.lisp</strong></p>
<pre><code class="lisp">(in-package :cl-user)
(load (merge-pathnames *build-dir* "dno.asd"))
(ql:quickload :dno)
</code></pre>
<p>Крайне важно то, что этот скрипт <em>не</em> выполняет <code>(dno:run)</code>, как это делает <code>runner.lisp</code>, потому что buildpack сам запустит Hunchentoot, так что от <code>init.lisp</code> требуется только настроить пути.</p>
<p>После того, как этот скрипт добавлен в репозитарий (имя <code>heroku-setup.lisp</code> <em>имеет</em> значение), можно пушить: <code>git push heroku master</code>, при условии, что п. 1 выполнен полностью.</p>
<h2>Поздравляю</h2>
<p>Всё, теперь прога готова, есть как возможность запустить на локальной машине, так и деплой сразу в Сеть (доменное имя тоже вполне приличное получается).</p>
<p>По тому же принципу можно строить любые другие простые веб-приложения, добавляя в <code>init.lisp</code> определения для путей в приложении.</p>
<p>Конечно же, если прога сложная, то придётся делать какой-то кустарный роутер, а также скорее всего, не отдавать статичные HTML файлы, а генерировать страницы при помощи какого-нибудь шаблонизатора типа <a href="http://weitz.de/cl-who/">CL-WHO</a>, <a href="http://common-lisp.net/project/cl-emb/">CL-EMB</a> или чего-то подобного.
Яваскрипт и CSS тоже можно генерировать прямо из Common Lisp, для этого есть проекты <a href="http://common-lisp.net/project/parenscript/">Parenscript</a> и <a href="https://github.com/paddymul/css-lite">css-lite</a> соответственно. </p>
<p>Мне всё это не понадобилось, приложение достаточно простое чтобы сразу отдавать статичные ассеты и HTML.</p>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com2tag:blogger.com,1999:blog-7779167346338218477.post-76080007701989996512013-01-16T15:31:00.002+04:002013-01-16T15:31:41.609+04:00Относительные пути в LaTeX<h2>Документ, разбитый на несколько файлов</h2>
<p>Допустим, у вас верстается книга достаточно большого объёма и вы структурировали её двумя уровнями иерархии. Главный файл лежит в корневом каталоге и называется <code>videogamebook2.tex</code>. Он вызывает глобальные макросы типа <code class="latex">\documentclass</code> и затем подключает главы следующим образом:</p>
<pre><code class="latex">...
\input{preface}
\input{moo2/chapter}
\input{planescape/chapter}
...
</code></pre>
<p>То есть, каждая отдельная "глава" книги лежит в отдельной папке, и её главный файл называется <code>chapter.tex</code>.</p>
<p>Эти файлы глав содержат вызовы макросов, локальные для главы, и затем подключают разделы. Разделы в таком случае нужно подключать так:</p>
<pre><code class="latex">...
\input{moo2/section-01/section}
\input{moo2/section-02/section}
...
</code></pre>
<p>Видно, что разделы также лежат каждый в своей подпапке и файлы называются просто <code>section.tex</code>.</p>
<p>Заметьте, что пути всё ещё начинаются от корневого каталога проекта <strong>(!)</strong>.</p>
<p>Довольно очевидно, что если вы хотите разбивать разделы более низкого уровня так же по отдельным файлам, вам придётся сохранять такой стиль указания путей.</p>
<h2>Иллюстрации, подключаемые во вложенных файлах</h2>
<p>Усложним задачу: допустим, мы хотим иллюстрировать наш текст, используя стандартный пакет <code>graphicx</code>. Иллюстрации, соответствующие разделу, у нас лежат в подкаталоге этого раздела, рядом с файлом <code>section.tex</code>.</p>
<p>Тогда в заголовке <code>section.tex</code> можно написать, что:</p>
<pre><code class="latex">\graphicspath{{./moo2/section-01/}}</code></pre>
<p>а в тексте писать просто</p>
<pre><code class="latex">\includegraphics{start-galaxy.jpg}</code></pre>
<p>То есть, путь надо писать опять-таки от корня, и <em>обязательно завершать слешем</em>, иначе LaTeX вас не поймёт.</p>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-89011081777135531152013-01-15T17:27:00.000+04:002013-01-15T17:27:01.550+04:00Минимальный шаблон книги в LaTeX<p>Вот как выглядит совершенно минимальный шаблон для книги, использующий только стандартные классы, ничего сверхъестественного:</p>
<pre><code class="latex">
\documentclass{book}
% Кодировка XXI века
\usepackage[utf8]{inputenc}
% Если книга на русском, то включаем эти три строчки
\usepackage[russian]{babel}
\usepackage{indentfirst}
\usepackage{misccorr}
% Включаем какой-нибудь другой шрифт, а не стандартный
\usepackage{dejavu}
% Колонтитулы с названием главы и нумерация
\usepackage{fancyhdr}
\pagestyle{fancy}
% Если хочется, чтобы в оглавлении были только главы, и ничего ниже
\setcounter{secnumdepth}{-1}
% Если есть картинки
\usepackage{graphicx}
\DeclareGraphicsExtensions{.jpg,.png,.gif}
% Если хочется гиперссылок в книге
% (в макете для печатного издания они бессмысленны, конечно).
\usepackage{hyperref}
\begin{document}
\title{A Book Title}
\maketitle
\tableofcontents
\include{preface}
\include{chapter-1}
\include{chapter-2}
% ...
\include{chapter-n}
\end{document}
</code></pre>
<p>Пример конечного результата можно посмотреть в моей сборке <a href="http://hijarian-sys.blogspot.ru/2013/01/blog-post.html">A Video Game Book</a>.</p>
<p>На обложке будет только title, по центру, и дата сборки, тоже по центру. Если надо другую обложку, то придётся настроить дополнительную страницу.</p>Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-61100414935419980852013-01-07T20:25:00.000+04:002013-01-07T20:25:43.125+04:00Как скачать сайт целиком в Debian используя только командную строку<p>Иногда одной веб-страницы мало. Надо скачать все остальные объекты, доступные из этой веб-страницы. Для этого понадобится один только <code>wget</code>.</p>
<p>Вот заклинание, которое нужно прочитать (предполагается, что мы находимся в каталоге, куда нужно скачать сайт):</p>
<pre><code class="bash shell">wget -m -k -np -w 1 --random-wait -U "Mozilla" -e robots=off <URL>
</code></pre>
<p>Значения параметров:</p>
<ul>
<li><strong><code>-m</code></strong> Скачать всё, начиная с заданного URL (собственно то, что нужно).</li>
<li><strong><code>-k</code></strong> Исправлять ссылки в скачанных HTML документах, чтобы ссылались не на Сеть, а друг на друга.</li>
<li><strong><code>-np</code></strong> Качать только то, что ниже заданного URL или на его уровне (в частности, не качать ничего с других доменов).</li>
<li><strong><code>-w 1 --random-wait</code></strong> Прикидываться обычным пользователем, делая паузы случайной длины минимум в 1 секунду между каждым скачиваемым файлом.</li>
<li><strong><code>-U "Mozilla"</code></strong> Прикидываться Фаерфоксом (не очень настойчиво: никакой фаерфокс не использует такой User-Agent).</li>
<li><strong><code>-e robots=off</code></strong> Вообще-то, не используйте этот параметр. Он заставляет <code>wget</code> игнорировать правила, описанные в <code>robots.txt</code>.</li>
</ul>
<p>
За инфу спасибо <a href="http://hydtechblog.com/tag/offline-browser-linux/">HydTechBlog</a>.
</p>Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-21607135197094582502013-01-06T20:53:00.002+04:002013-08-24T22:21:10.718+04:00Прохождения игр как научно-фантастическая литература<div dir="ltr" style="text-align: left;" trbidi="on">
Составил небольшой сборник из трёх прохождений игр: Вангеров, Freespace 2 и Star Trek: Starfleet Command. Эти прохождения настолько эпичны, что уже практически являются научно-фантастическими рассказами.<br />
<br />
Я убеждён, что все три расссказа прекрасны, и заслуживают того, чтобы их читали, невзирая на происхождение. Иначе эти произведения просто сгниют в подшивках старых журналов.<br />
<a href="https://dl.dropboxusercontent.com/u/45609250/videogamebook.pdf" target="_blank">Составил я в PDF, положил себе в Дропбокс</a>.<br />
<br />
Про HTML-версию подумаю, меня интересовала только пэдээфка, если честно.<br />
<br />
В продолжение темы: на Let's Play Archive есть столь же чудесные рассказы-прохождения для <a href="http://lparchive.org/Master-of-Orion-2/" target="_blank">Master of Orion 2</a>, <a href="http://lparchive.org/Planescape-Torment/" target="_blank">Planescape: Torment</a> и <a href="http://lparchive.org/Arcanum/" target="_blank">Arcanum: Of Steamworks and Magick Obscura</a>. Однако, они на английском языке, так что A Video Game Book 2 на языке берёз и осин будет недоступна. ;)</div>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-38670462612309292742012-11-11T18:02:00.001+04:002012-11-11T18:09:57.207+04:00How to set EMACS fonts rendering the same as the other KDE applications<p>Here's how to fix the default too wide and too "round" fonts rendered in EMACS under KDE (and maybe other DE, too).</p>
<p>Write this into the <code>~/.Xdefault</code> file:</p>
<pre><code class="shell bash">Xft.antialias: 1
Xft.hinting: 1
Xft.hintstyle: hintfull
Xft.rgba: rgb
</code></pre>
<p>After that, run the following: </p>
<pre><code class="shell bash">$ xrdb -merge ~/.Xdefaults</code></pre>
<p>And after that EMACS will really get the same-looking fonts as the other system around it. Thank you Atragor for your <a href="https://bbs.archlinux.org/viewtopic.php?pid=757303#p757303">answer at archlinux.org</a></p>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-68227626310010498592012-11-11T17:15:00.002+04:002012-11-11T17:26:37.976+04:00Über prompt string for modern commandline<p>Okay. Today's XXI century already. You have colored commandlines everywhere and you have basically single-user *nix OSes. Now it's time to shift from default commandline prompt strings to something more useful (and fancy, of course).</p>
<p>This is how my prompt looks like now:</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0ANMoa7146iKs9X7iCi0YAXLJGv9SJ-TBRHnMUB1TCzhrnqkh08DFaR2_mILdihyphenhyphen9SEbDU0LYjIPEhScEj8cNq17pUcv59i2p6wNm_nroND7-Vr4UTtkuQ7-TQn1pWikTbm5RESTOBg/s1600/commandline_prompt.png" imageanchor="1" style="clear:left;margin-right:1em; margin-bottom:1em"><img border="0" height="43" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0ANMoa7146iKs9X7iCi0YAXLJGv9SJ-TBRHnMUB1TCzhrnqkh08DFaR2_mILdihyphenhyphen9SEbDU0LYjIPEhScEj8cNq17pUcv59i2p6wNm_nroND7-Vr4UTtkuQ7-TQn1pWikTbm5RESTOBg/s320/commandline_prompt.png"></a></div>
<p>This is four-line prompt.</p>
<ol>
<li>Empty line as a separator.</li>
<li>Clean cut with command history number and 72 dashes.</li>
<li>User and host names, current time, jobs count and current directory.</li>
<li>Emoticon showing the result of last command and the traditional <code>$</code> symbol (which bears no meaning here, really). If we currently are in the Git repo, then between this tokens the current branch name is being shown in square brackets.</li>
</ol>
<p>Emoticon at the last line behaves like this:</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHYHPavCgIZHhwjpo4jg3Er1YTwozUBird_d2GwNnCUCwY8LZShHfFPvjHS-6HL_z7PgoyBWahaDYmhDj-Q1OQmDVAUnP4op5fn7fFFUWqs-RbOyhG8kwAenPfaGuZ7uev4MnR84R1BQ/s1600/prompt_reaction.png" imageanchor="1" style="clear:left; margin-right:1em; margin-bottom:1em"><img border="0" height="73" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHYHPavCgIZHhwjpo4jg3Er1YTwozUBird_d2GwNnCUCwY8LZShHfFPvjHS-6HL_z7PgoyBWahaDYmhDj-Q1OQmDVAUnP4op5fn7fFFUWqs-RbOyhG8kwAenPfaGuZ7uev4MnR84R1BQ/s320/prompt_reaction.png"></a></div>
<p>Big thanks to <a href="http://maketecheasier.com/8-useful-and-interesting-bash-prompts/2009/09/04">Make Tech Easier</a> for inspiration.</p>
<p>Here how it was done...</p>
<a href="http://hijarian-sys.blogspot.com/2012/11/uber-prompt-string-for-modern.html#more">Дальше »</a>Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-40492731203799365772012-10-31T16:15:00.000+04:002012-10-31T16:15:36.328+04:00On Clean Coders episodes 6 through 8<div dir="ltr" style="text-align: left;" trbidi="on">
Just watched the episodes 6, 7 and 8 of <a href="http://www.cleancoders.com/" target="_blank">Clean Coders</a> codecasts, namely, "TDD part 2", "Architecture" and "SOLID principles", and just could not stay silent about the awesomeness of this works.<br />
<br />
First of all, "TDD part 2" has almost half an hour of peeping over the shoulder of the Uncle Bob while he does the <a href="http://lmgtfy.com/?q=bowling+game+kata" target="_blank">Bowling Kata</a> (yeah, I know that Bowling Kata is boring to death this days, because it's just like everywhere, but nevertheless) and this is just awesome: you can not only watch the proper TDD in action, but the workflow with modern auto-refactoring IDE (<a href="http://www.jetbrains.com/idea/" target="_blank">IntelliJ IDEA</a> was used there). Don't know what's more inspiring to hardcore Viperized EMACS user like me.<br />
<br />
Second, "Architecture" has the greatest and cleanest explanation of clean functional separation between different parts of modern-style application built with OO principles in mind. I even redrawn it on paper to learn it by heart, but, I suppose, I cannot reproduce it because of legal issues (judging by the licences for codecasts, Uncle Bob is pretty strict about legal issues).<br />
<br />
And third, "SOLID principles" expands "Architecture" even more and adds <b>a lot </b>of details to the methods of designing programs cleanly. If you (like me) never worked in serious IT consulting before (say, you're a fresh graduate from some university), you'll be able to get many, many good advices for building/maintaining your next application.<br />
<br />
I really think that this three episodes are really core to the whole series and if you cannot afford all of 14 episodes, then go buy "<a href="http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882/ref=sr_1_1?ie=UTF8&qid=1351685638&sr=8-1&keywords=Clean+Code" target="_blank">Clean Code</a>" and then only this three episodes. You just don't understand what you miss, honestly.<br />
<br />
As for me, I'm ashamed of my early projects now and want to rewrite them all from scratch badly. :(</div>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-5446971905842681002012-09-15T00:51:00.000+04:002012-09-15T00:51:05.126+04:00Building changelog from Git log output<p>That's how you get the name of last tag applied to current branch in Git repo:</p>
<pre><code class="bash shell">git describe | grep -Eo '^[^-]+'
</code></pre>
<p>We need to meddle with <code>grep</code> because <code>git describe</code> gives us additional info in the form of </p>
<pre>LAST_TAG-COMMITS_SINCE_LAST_TAG-gLAST_COMMIT_HASH
</pre>
<p>Note the funny literal 'g' before the <code>LAST_COMMIT_HASH</code>.</p>
<p>And that's how you get the list of changes since some <code>COMMIT</code> till the current state of the working copy, in really pretty format <code>"ISO Date (Author) Commit text"</code>:</p>
<pre><code class="shell bash">git log --no-merges --pretty=format:'%ai: (%an) %s' COMMIT..HEAD
</code></pre>
<p><code>HEAD</code> is literal "HEAD" there. You can substitute <code>COMMIT</code> token with either commit hash or tag.</p>
<p>Now you write the following script and place it in the root of the codebase of your project:</p>
<pre><code class="bash shell">#!/bin/sh
# Get the list of changes made since the last tag
LAST_TAG=`git describe | grep -Eo '^[^-]+'`
git log --no-merges --pretty=format:'%ai: (%an) %s' $LAST_TAG..HEAD
</code></pre>
<p>Name it as <code>changelog</code> and then you can do just:</p>
<pre><code class="bash shell">./changelog
</code></pre>
<p>And get something like this:</p>
<pre>
2012-09-14 23:50:43 +0400: (E. T.) Some stuff for ticket 1584. re #1584
2012-09-14 23:45:05 +0400: (A. Y.) Some stuff for ticket 1584. test #1584
2012-09-14 15:44:49 -0400: (A. Y.) Refactored some old stuff
2012-09-14 22:24:04 +0300: (D. M.) Improved tests
</pre>
<p>And this will be changes only since last tag applied. Excellent for quick reports about current upstream.</p>Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-67250951916905521322012-09-10T15:45:00.001+04:002012-09-10T15:47:22.563+04:00Essential string processing functions in Common Lisp<p>It should be noted that true <em>Common</em> Lisp somewhat lacks in several important parts of string-processing, and it shows sometime. Today I needed to heavily process large body of regular text and will write here some functions which are AFAIK considered "standard" in modern languages and which not so easily accessible and/or amazingly intuitive in CL.</p>
<p>In all following code snippets token <code>input</code> stands for input string.</p>
<ol>
<li>
<p><strong>Trimming string from spaces, tabs and newlines</strong></p>
<pre><code class="lisp">(<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_stg_tr.htm#string-trim">string-trim</a> '(#\Space #\Newline #\Return #\Linefeed #\Tab) input))</code></pre>
<p>All named characters are listed in <a href="http://www.lispworks.com/documentation/HyperSpec/Body/13_ag.htm">Hyperspec, 13.1.7 Character Names</a>.</p>
</li>
<li>
<p><strong>Replacing by regular expressions</strong></p>
<p>Provided by <a href="http://weitz.de/cl-ppcre/">CL-PPCRE</a> package.</p>
<p>In next snippet I remove all tokens enclosed in square brackets from the input string:</p>
<pre><code class="lisp">(ql:quickload :cl-ppcre)
(<a href="http://weitz.de/cl-ppcre/#regex-replace-all">cl-ppcre:regex-replace-all</a> "\\[[^]]+\\]" input "")</code></pre>
<p>Honestly, I don't know when you can need simple <code>regex-replace</code> and not <code>regex-replace-all</code>. Also, note the double-escaping of special symbols (<code>\\[</code> instead of <code>\[</code>).</p>
</li>
<li>
<p><strong>Splitting string by separator symbol</strong></p>
<p>Provided by <a href="http://common-lisp.net/project/cl-utilities/">CL-UTILITIES</a> package.</p>
<p>In next snippet I split the input string by commas:</p>
<pre><code class="lisp">(ql:quickload :cl-utilities)
(<a href="http://common-lisp.net/project/cl-utilities/doc/split-sequence.html">cl-utilities:split-sequence</a> #\, input)</code></pre>
</li>
<li>
<p><strong>Making same modification on every string in given list</strong></p>
<p>In next snippet I trim spaces around all strings in list <code>input-list</code>:</p>
<pre><code class="lisp">(<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_map.htm#map">map</a> 'list
(lambda (input) (string-trim " " input))
input-list)</code></pre>
<p>However, way better is to wrap the transformation for the string in separate function and call the mapping referencing just the name of transformation:</p>
<pre><code class="lisp">(defun trim-spaces (input)
"Remove trailing and leading spaces from input string"
(string-trim '(#\Space) input))
(map 'list #'trim-spaces input)</code></pre>
<p>Do not forget that string is just a sequence of a characters, and all sequence-operating functions can work on strings in either <code>"abcd"</code> form or <code>'(#\a #\b #\c #\d)</code> form. This applies only to sequence-operating functions, however.</p>
</li>
<li><p><strong>Removing the characters from string by condition</strong></p>
<p>In the next snippet I leave only the alphanumeric characters in the input string:</p>
<pre><code class="lisp">(<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_rm_rm.htm#remove-if-not">remove-if-not</a> #'<a href="http://www.lispworks.com/documentation/HyperSpec/Body/f_alphan.htm#alphanumericp">alphanumericp</a> input)</code></pre>
<p>There are <code>remove-if</code> also.</p>
<p>As with <code>map</code>, you can make arbitrary complex predicates either with lambdas or wrapping them in separate functions.</p>
</li>
</ol>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-85725565010022924222012-09-09T13:11:00.000+04:002012-09-09T13:12:29.510+04:00Как написать пятиминутный доклад на студенческую конференцию<p>Во время учёбы в универе мне регулярно приходилось ездить на различные конференции и делать там доклады. Каждый раз написание короткого содержательного доклада было настоящим испытанием, потому что нужно было рассказать о своём проекте в течение 5 минут, причём за это время нужно было вроде как рассказать о нём <em>всё</em>.</p>
<p>Мучился я почти до самого до пятого курса, когда перед одной из конференций меня не осенило и я не составил для себя короткий простой план текста доклада на проект любого типа. С этим планом доклад обычно получался даже короче, чем на 5 минут. Он успешно опробован на четырёх конференциях, так что я уверен в его полезности, почему и делюсь им с Сетью. :)</p>
<p>Если кому-то план покажется очевидным, рад за вас. Если нет — то вот он, пожалуйста.</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSv55sM8I2p-TmeZY0c7rKDqfjLiW1UgvQKvFjrcuR3fRooVRuYMr7VBiLM4aUkU8rAFiQ-DdUUqB2B-G9Kd1bpC93MtucJPK1q8sh8VSups8F8OVoK8nOtGWxz7fvfMPWMwrYsMGK1Q/s1600/%25D0%25A8%25D0%25BF%25D0%25B0%25D1%2580%25D0%25B3%25D0%25B0%25D0%25BB%25D0%25BA%25D0%25B0+%25D0%25B4%25D0%25BB%25D1%258F+%25D1%2582%25D0%25B5%25D0%25B7%25D0%25B8%25D1%2581%25D0%25BE%25D0%25B2+%25D0%25B4%25D0%25BE%25D0%25BA%25D0%25BB%25D0%25B0%25D0%25B4%25D0%25B0+%255BRGB+A5+white%255D.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="320" width="226" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSv55sM8I2p-TmeZY0c7rKDqfjLiW1UgvQKvFjrcuR3fRooVRuYMr7VBiLM4aUkU8rAFiQ-DdUUqB2B-G9Kd1bpC93MtucJPK1q8sh8VSups8F8OVoK8nOtGWxz7fvfMPWMwrYsMGK1Q/s320/%25D0%25A8%25D0%25BF%25D0%25B0%25D1%2580%25D0%25B3%25D0%25B0%25D0%25BB%25D0%25BA%25D0%25B0+%25D0%25B4%25D0%25BB%25D1%258F+%25D1%2582%25D0%25B5%25D0%25B7%25D0%25B8%25D1%2581%25D0%25BE%25D0%25B2+%25D0%25B4%25D0%25BE%25D0%25BA%25D0%25BB%25D0%25B0%25D0%25B4%25D0%25B0+%255BRGB+A5+white%255D.png" /></a></div>
<ol>
<li><p><strong>С чем связана работа + что было выполнено.</strong> (2 предложения)</p>
<p><em>«Моя работа связана с...»</em></p>
<p><em>«В её рамках было разработано...»</em></p>
<p>Во введении достаточно лишь назвать ключевые слова, по которым потенциальный слушатель поймёт а) разбирается ли он в теме вообще и б) интересна ли ему эта тема.</p></li>
<li><p><strong>Предметная область.</strong> (2 предложение)</p>
<p>Скорее всего, в одно предложение уложиться не удастся, поэтому выделяется два. </p>
<p>В начале нужно описать <em>контекст</em> работы, потому что доклад очень сжатый, и если сразу начать рассказывать про постановку задачи или методы решения, то слушатель не успеет разобрать вашу мысль.</p>
<p><em>Скажем, если написали «Моя работа связана с разбором s-выражений при помощи многопоточных компиляторов на языке Haskell», значит, в одном предложении рассказываем, что такое s-выражения, в другом — что такое многопоточные компиляторы (раскрываем только самые сложные/конкретные концепции, на всё времени нет)</em></p></li>
<li><p><strong>Задача.</strong> (1 предложение)</p>
<p><em>«Решалась задача...»</em> или <em>«Была поставлена задача...»</em></p>
<p>Одним предложением описываем поставленную задачу, так как контекст уже установлен.</p>
<p>Постановка задачи в идеале должна включать в себя описание конечного результата, потому что когда придёт время говорить о том, что получилось в итоге, крайне важно, чтобы результаты соответствовали задаче.</p>
<p>Я многократно видел примеры того, как единственным вопросом из зала было: <em>«А как полученные вами результаты соответствуют вашей задаче?»</em>.</p></li>
<li><p><strong>Актуальность.</strong> (1 предложение)</p>
<p><em>«Данная задача имеет ценность, потому что...»</em></p>
<p>Актуальность означает одно из двух: а) какую пользу принесёт решение вашей задачи или б) какие трудности существуют из-за того, что данная задача до сих пор не была решена.</p></li>
<li><p><strong>Методы.</strong> (3 предложения)</p>
<p><em>«Для решения задачи было использовано...»</em></p>
<p>Ни в коем случае нельзя углубляться в подробности, потому что аудитория гарантированно уснёт. Они не занимались этой работой, им априори не так интересны детали.</p>
<p>Если кому-то станет действительно интересно, он спросит после доклада.</p></li>
<li><p><strong>Результаты + тесты.</strong> (2 предложения)</p>
<p><em>«В качестве конечного результата было получено...»</em></p>
<p><em>«Работа оттестирована на...»</em> или <em>«В качестве тестовых данных были использованы...»</em></p>
<p>В качестве конечного результата всегда должен получиться некий артефакт: компьютерная программа, формальная спецификация, механизм/агрегат, какая-то ещё вещь.</p>
<p>Очень большой интерес всегда вызывает то, на каких данных/экспериментах была опробована работа. Также тот факт, что в процессе работы автор озаботился тем, как он будет проверять её корректность, повышает её ценность.</p>
<p>К тому же, так как студенческая конференция предназначена для того, чтобы показать своё старание и навыки, очень глупо будет не указать, что для решения поставленной задачи было необходимо что-нибудь вроде анализа динамики статистики рождаемости альбиносов в мире начиная с Рождества Христова.</p></li>
<li><p><strong>Выводы.</strong> (1 предложение)</p>
<p><em>«Полученные результаты позволяют сделать вывод о том, что...»</em></p>
<p>Всегда спрашивают о выводе, если сам об этом не сказал.</p>
<p>Если вывод не сформировался сам собой в процессе работы, то ничего из пальца высасывать не нужно: достаточно просто сказать, что обнаружено, что <em>выбранные методы успешно решают поставленную задачу</em> / <em>непригодны для решения поставленной задачи</em>.</p></li>
<li><p><strong>Где может использоваться.</strong> (1 предложение)</p>
<p><em>«Эта работа может быть использована для...»</em></p>
<p>Если ответ на этот вопрос не очевиден из вышесказанного, то его обязательно зададут из зала.</p>
<p>Увы, если результаты работы или использованные методы решения не могут использоваться нигде, то это автоматически означает, что работа не нужна, а, следовательно, зачем она вообще была выполнена? И потом, если есть актуальность, то почему работу негде использовать?.. В любом случае, вскрывается серьёзная логическая ошибка в постановке задачи.</p></li>
</ol>
<p>В итоге получается 13 предложений (возможно, длинных, это неважно), которые очень легко выучить наизусть (и не читать по бумажке вообще) и содержат совершенно всё, что интересует аудиторию. </p>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-66436655280835203292012-08-28T18:11:00.000+04:002012-08-28T18:11:41.625+04:00Context-dependent Behat tests steps<p>Preamble is this: we have the PHP-based website, and we are testing it with the <a href="http://docs.behat.org/">Behat</a>+<a href="http://mink.behat.org/">Mink</a>+<a href="http://extensions.behat.org/mink/">MinkExtension</a> combo.</p>
<p>Suppose we want to write the following test scenario:</p>
<pre>
When I am in the Friends section
… (something there) …
Then I should see “My Friend” in search results
</pre>
<p>Let's define this steps in our <code>FeatureContext</code>. First step we can define with the following regexp: <code>/^I am in the Friends section$/</code> because we really don’t need the method of <code>FeatureContext</code> class containing long switch enumerating every possible section of the site.</p>
<pre><code class="php">
/**
* @Given /^I am in the "Friends" section$/
*/
public function iAmInTheFriendsSection() {
return new Given('I am on "/friends"');
}
</code></pre>
<p>Second step we can define with the following regexp: <code>/^I(?: should)? see "([^"]*)" in the search results$/</code>.</p>
<pre><code class="php">
/**
* @Then /^I should see "([^"]*)" in the search results$/
*/
public function iShouldSeeInTheSearchResults($search_term) {
// separate helper function to search the "search results" HTML element
$search_results = $this->getSearchResultsElement();
// separate helper function to search the $search_term text in $search_results element
$this->trySearchTextOnDomElement($search_term, $search_results);
}
</code></pre>
<p>It should be obvious why we use the custom test step instead of using the predefined test steps and writing something like <code>'I should see "My Friend" in ".search-wrapper form input[role="search"]" element'</code>.</p>
<p>Then, someday, sure thing, we will want to write the following scenario:</p>
<pre>
When I am in the Shop section
… (something there) …
Then I should see “Interesting product” in search results
</pre>
<p>And in here, we have <em>another</em> “search results”, which should be found by completely different selector and which is located on different page.</p>
<p>So, this is the <em>context-dependent statement</em>: term “search results” depends on what “section” we mentioned previously. This is right from the linguistics. To be able to use this natural-language feature we need to implement it somehow.</p>
<p>I'll use the abbrev <em>CDTS</em> instead of longer "context-dependent test step".</p>
<p>Fortunately, Behat has a feature with exactly the same purpose: <a href="http://docs.behat.org/guides/4.context.html#using-subcontexts">subcontexts</a>. Unfortunately, it's not working in the way we need to use the <em>CDTS</em> properly.</p>
<p>In an ideal world, we can do this:</p>
<pre><code class="php">
/**
* @Given /^I am in the Friends section$/
*/
public function iAmInTheFriendsSection() {
$this->useContext('friends_section', new FriendsSectionContext())
return new Given('I am on "/friends"');
}
</code></pre>
<p>and this would load the <code>FriendsSectionContext</code> and all CDTS definitions in it, like the following:</p>
<pre><code class="php">
// in FriendsSectionContext class
/**
* @Then /^I should see "([^"]*)" in the search results$/
*/
public function iShouldSeeInTheSearchResults($search_term) {
// This selector is valid only in the context of "Friends" section.
$search_results_selector = '#PeopleFinder #pf_all .results';
// Logic to check if the given $search_term is the present in anything called "search results" in the context of "Friends" section.
$search_results = $this->getSession()->getPage()->find('css', $search_results_selector);
$search_term_present = strpos($search_results->getHtml(), $search_term);
if ($search_term_present === false) {
throw new Exception(...);
}
}
</code></pre>
<p>We <code>useContext</code> different context class, we get different definition for the <code>/^I should see "([^"]*)" in the search results$/</code> test step.</p>
<p>Unfortunately, Behat cannot load the test step definitions from subcontexts at runtime.
Apparently, it’s because it should parse the regexps in docblocks corresponding to definitions or something like that. So, are forced to load all our subcontexts right in our constructor.</p>
<p>Apart from being horribly ineffective, this prevents us from defining the test steps having same regexp across several different separate subcontexts.</p>
<p>Workaround for this problem is this:</p>
<ol>
<li>add the property to the <code>FeatureContext</code> which will hold the reference to current subcontext, name it like "location_context" or so,</li>
<li>make the context-<code>setting</code> ('I am in the "..." section') test step set the "location_context" to the subcontext needed (you can get the subcontext with the call to <code>getSubcontext('alias')</code>),</li>
<li>move the context-dependent logic to “normal” subcontext methods, which should have the same name across all subcontexts,</li>
<li>register all subcontexts with <code>useContext</code> under meaningful aliases like "friends_section", "shop_section", etc,</li>
<li>define the context-dependent test step like 'I should see "..." in search results' in main <code>FeatureContext</code> class,</li>
<li>in the definition of this step, get the context-dependent logic needed by calling the relevant method on the subcontext the "location_context" property currently points at.</li>
</ol>
<p>So, we need our context-<em>setting</em> test steps to be like this:</p>
<pre><code class="php">
/**
* @Given /^I am in the "Friends" section$/
*/
public function iAmInTheFriendsSection() {
$this->location_context = $this->getSubcontext('friends_section');
return new Given('I am on "/people"');
}
</code></pre>
<p>Assuming 'friends_section' is an alias of the <code>FriendsSectionContext</code>, and it was set in the constructor, after this test step, our "location_context" will be <code>FriendsSectionContext</code>, and, say, it's <code>getSearchResultsElement()</code> will do exactly what we need in the "Friends" section.</p>
<p>Then, the context-<em>depentent</em> test step will be like this, getting the location-dependent logic from the "location_context" set previously:</p>
<pre><code class="php">
/**
* @Then /^I should see "([^"]*)" in the search results$/
*/
public function iShouldSeeInTheSearchResults($search_term) {
// getSearchResultsElement() is defined in subcontext which was set before in $this->location_context
$search_results = $this->location_context->getSearchResultsElement();
$search_term_present = strpos($search_results->getHtml(), $search_term);
if ($search_term_present === false) {
throw new Exception(...);
}
}
</code></pre>
<p>Main point is this: we want to check if something should appear in the "search results" entity in some different page → we can use the same test step in our <code>.feature</code> files, just explicitly name the section needed beforehand somewhere above in the text. This will make the <code>.feature</code> files a lot more human-readable.</p>
<p>This concludes the explanation about how to use this linguistic technique in Behat tests.</p>
Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-59991528510535009482012-07-03T08:18:00.001+04:002012-07-03T08:19:22.563+04:00Как добавить поддержку Markdown в Kate, KWrite и встроенный редактор Krusader (F4)<p>Лично я для работы с PHP проектами уже давно пользуюсь в качестве IDE исключительно Krusader'ом и его встроенным редактором. И встроенным поиском. И встроенным архиватором. Ну, в общем, понятно. Встроенный в Krusader редактор является всего лишь <a href="http://kate-editor.org/">Kate</a>, который запихнули в контейнер. И этот редактор меня устраивает практически во всём, кроме одной вещи: там нет встроенной поддержки Markdown.</p>
<p>Однако, <a href="http://www.dyeager.org/blog/2008/06/markdown-color-syntax-highlighting-kate.html" title="Блог, автор которого выложил плагин поддержки markdown в kate">как выяснилось гуглением по англоязычной Сети</a>, это быстро решается.</p>
<a href="http://hijarian-sys.blogspot.com/2012/07/markdown-kate-kwrite-krusader-f4.html#more">Дальше »</a>Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-17788254454969064842012-07-02T11:57:00.000+04:002012-07-02T11:57:45.406+04:00Использование Qt для реализации GUI в Common Lisp<p>У меня нет никаких предрассудков насчёт Qt и на десктопе у меня KDE, так что для первых шагов в GUI я воспользовался проектом под названием <a href="http://common-lisp.net/project/commonqt/">CommonQt</a>. Это <a href="http://common-lisp.net/project/cffi/">CFFI</a>-биндинги к libqt и libsmoke.</p>
<p>Здесь я запишу, как настроить окружение для того, чтобы писать Qt-приложения. Ниже скриншот конечного результата. ;)</p>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5tiFJ98nv8t1fqIjAqD1JsRBlcxfY83sUtAz3nBQz9KZKXi9RJu0toZ8IPgluIqr6CUeTmP7diInpz09_rCvFyDiLjK2bxoRMQfhFykowBYd3hBcTDwGuzIFD652eC6PuTfMduA7ZWg/s1600/qt_app.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="180" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5tiFJ98nv8t1fqIjAqD1JsRBlcxfY83sUtAz3nBQz9KZKXi9RJu0toZ8IPgluIqr6CUeTmP7diInpz09_rCvFyDiLjK2bxoRMQfhFykowBYd3hBcTDwGuzIFD652eC6PuTfMduA7ZWg/s320/qt_app.png"></a></div>
<a href="http://hijarian-sys.blogspot.com/2012/07/qt-gui-common-lisp.html#more">Дальше »</a>Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-65913590457628987002012-06-14T11:18:00.002+04:002012-07-03T02:35:56.477+04:00Простое и сложное в Common Lisp<p>Сегодня развлекался с моим любимым Common Lisp и обнаружил интересную деталь.</p>
<p>Допустим, мы хотим написать простую функцию для определения среднего арифметического набора заданных чисел. Чтобы она работала вот так:</p>
<pre><code class="lisp">(average 1 2 3 4)
5/2
</code></pre>
<p>...ну хорошо, хорошо:</p>
<pre><code class="lisp">(coerce (average 1 2 3 4) 'float)
2.5
</code></pre>
<p>Заметьте то, что я не хочу передавать в <code>average</code> список, я хочу кортеж чисел. Интересная деталь вот в чём: самая простая (в смысле, лаконичная) реализация, которая пришла мне в голову, это такая:</p>
<pre><code class="lisp">(defun average (&rest args)
(/ (reduce #'+ args) (list-length args)))
</code></pre>
<p>Дело в том, что определение среднего арифметического — это задачка для средней школы, и мы вполне можем её дать в качестве задания по программированию на второй паре (на первой мы изучим весь синтаксис лиспа вместе с макросами <code>defun</code> и <code>defvar</code>). Но на самом деле мы не можем её дать, потому что у нас есть вот эта форма в решении: <code>(reduce #'+ args)</code>, и, чтобы студенты смогли ей бегло пользоваться, нужно, чтобы они знали как минимум:</p>
<ul>
<li>о разделении пространства имён переменных и пространства имён функций;</li>
<li>как передавать в функцию имя другой функции;</li>
<li>о назначении и работе <code>reduce</code>, что для не знакомого с функциональными методами, вообще говоря, стопроцентная магия (особенно если их уже обработали алголоподобными языками);</li>
<li>о том, что параметры, переданные через <code>&rest</code>, оборачиваются в список.</li>
</ul>
<p>Не то, чтобы я здесь докапывался до Лиспа, но всё же факт интересный: организация этого языка такова, что для решения подобной простой задачи нам требуется знать пол-языка и ещё минимум одну функциональную концепцию.</p>
<p>Конечно, мы можем соорудить нечто работающее из <code>let</code> и <code>dotimes</code>, но тогда зачем нам Common Lisp вообще? :)</p>Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-84457947785718366952012-06-07T13:20:00.001+04:002012-06-21T15:02:21.261+04:00Паттерн «Веб-функция»<p>Допустим, вам нужно написать endpoint для аякс-запроса. Или обычный обработчик обычного POST-запроса от веб-формы. Тогда основные действия, которые обязательно должны быть в процессе обработки, будут такие:</p>
<ul>
<li>Фильтрация входных данных (это <em>НЕ</em> валидация бизнес-правилами, только лишь такие действия, как укорачивание, очистка от окружающих пробелов, исключение непечатных символов, приведение к нижнему регистру и т. д.). Или мы можем даже сыграть роль адаптера и переименовать некоторые параметры запроса в вид, ожидаемый обработчиком.</li>
<li>Обработка запроса. Здесь мы собственно делаем то, зачем существуем. Мы ожидаем уже более-менее очищенный от мусора массив входных параметров, возможно, среди которых есть ошибочные (например, числовое значение вне допустимого диапазона, или передан массив вместо скаляра).</li>
<li>Форматирование результата для отклика клиенту. Например, мы можем возвращать JSON, или генерировать HTML страницу по шаблону. Или генерировать изображения. Или отдавать файлы с диска.</li>
</ul>
<p>Если записать в функциональном стиле на PHP, то можно получить следующий шаблон хэндлера:</p>
<pre><code class="php">
<?php
echo format(process(clean($_REQUEST)));
/**
* Здесь очистка суперглобального массива $_REQUEST и генерация массива $request
*
* @param array $request Данные запроса из массива $_POST или $_GET (или $_REQUEST).
* @return array Очищенные данные из запроса, которые ожидает функция process.
*/
function clean($request)
{
// TODO
return $request;
}
/**
* Здесь выполнение действий согласно запросу $request
*
* @param array $request Параметры запроса, очищенные функцией clean.
* @return array $result Результат работы со всей информацией, необходимой для форматирования ответа клиенту.
*/
function process($request)
{
// TODO
return $request;
}
/**
* Здесь форматирование результата работы для выдачи клиенту.
*
* @param array $result Результат работы, как он сгенерирован процессором. Нам не дозволяется использовать какие-либо другие данные.
* @return string Форматированный ответ, готовый к отправке клиенту через echo.
*/
function format($result)
{
// TODO
return json_encode($result);
}
?>
</code></pre>
<p>Если <code>process</code> сталкивается с ошибкой, он пишет об этом в результат своей работы (например, в поле <code>'error'</code>) и сразу возвращает результат в <code>format</code>.</p>
<p>Если <code>format</code> должен генерировать большой HTML документ, то ничего страшного, он может и делать <code>echo</code> внутри себя, вместо того, чтобы возвращать строку. Тогда вызов всей цепочки будет без echo в начале, конечно же.</p>Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0tag:blogger.com,1999:blog-7779167346338218477.post-29943169409896432872012-06-07T11:37:00.000+04:002012-06-07T11:37:26.894+04:00Как запускать программы на Common Lisp как консольные скрипты<p>Допустим, у вас есть классная маленькая программка на Common Lisp под названием <code>filter</code>. Она занимает всего один файл исходного кода под названием <code>filter.lisp</code> примерно в 200 строк длиной. В этом файле вот такой заголовок:</p>
<pre><code class="lisp">
(defpackage :localhost.me.filter
(:use :common-lisp)
(:export :run))
(in-package :localhost.me.filter)
</code></pre>
<p>И дальше собственно сама программа. Допустим также, что вы пользуетесь своей программой <em>часто</em>. Загвоздка в том, что для того, чтобы её запустить, вам необходимо сделать слишком много действий:</p>
<pre><code class="shell">
$ cd $PROGRAMDIR
$ sbcl
CL-USER> (load "filter.lisp")
CL-USER> (:localhost.me.filter:run)
</code></pre>
<p>Рассмотрим два решения, одно простое, но не всегда поможет, другое посложнее и поможет уже в большем числе случаев.</p>
<h2>Решение первое</h2>
<p>SBCL умеет запускать программы на CL в режиме скриптов командной строки, для этого используется специальный флаг <code>--script</code>. Более того, благодаря этому можно написать лисп-программу, добавить к ней типичный юниксовый шебанг, вызывающий SBCL с этим флагом, и она будет работать как любой другой шелл-скрипт (за исключением того, что рантайм SBCL весит 50MB бгг). Так что напишем этот скрипт:</p>
<pre><code class="shell">
#!/usr/bin/sbcl --script
(load "filter.lisp")
(:localhost.me.filter:run)
</code></pre>
<p>Теперь положим этот скрипт в ту же директорию, что и <code>filter.lisp</code>, в файл с именем <code>run</code> и можно будет запускать нашу программу так:</p>
<pre><code class="shell">
$ ./run
</code></pre>
<p>Что и требовалось.</p>
<h2>Решение второе</h2>
<p>Однако, у флага <code>--script</code> есть одна особенность: SBCL не загружает никакие пользовательские скрипты инициализации при старте, так что скрипт будет выполняться в 100% дефолтном рантайме. Это плохо, если, допустим, вы используете <a href="http://www.quicklisp.org/beta/index.html">Quicklisp</a> и у вас в <code>filter.lisp</code> есть такой вызов:</p>
<pre><code class="lisp">
...
(ql:quickload :CL-FAD)
(use-package :CL-FAD)
...
</code></pre>
<p>Ну или вызов любой другой библиотеки, неважно. Если запустить такой скрипт первым способом, то SBCL будет грязно ругаться по поводу того, что он не знает, что такое <code>ql:quickload</code>.</p>
<p>Для решения этой проблемы мы смухлюем, подменив бинарник SBCL таким бинарником, у которого все нужные библиотеки уже загружены. В SBCL есть функция <code>save-lisp-and-die</code>, которая выгружает текущее состояние рантайма в файл с указанным именем. Дальше этот файл можно использовать как обычный бинарник SBCL, в том числе, и для вызовов с использованием <code>--script</code>. Поэтому если запустить SBCL и сразу выгрузить его в файл, то полученный бинарник будет содержать всё, что SBCL подключил при загрузке, основываясь на пользовательских конфигах.</p>
<pre><code class="lisp">
(save-lisp-and-die "/абсолютный/путь/до/файла")
</code></pre>
<p>Теперь в скрипте <code>run</code>, который написан по первому варианту, заменяем путь до системного SBCL путём до выгруженного бинарника, и скрипт начнёт работать как должен.</p>Mark Safronovhttp://www.blogger.com/profile/17240002023355611513noreply@blogger.com0