almost 3 years ago

Дано: папка с картинками.

Требуется: на каждую картинку нанести надпись с именем этой картинки (прямо на изображении нарисовать)

Решение:

# For each JPG file in the current directory

# Place the name of the file (without the extension) to the bottom right corner inside the gray rectangle

# And save this new image inside the "annotated" subdirectory

mkdir annotated ; for i in *.jpg; do convert -background '#00000080' -fill white label:"${i%.*}" miff:- |composite -gravity SouthEast -geometry +0+3  - "$i" "annotated/$i"; done

В этом one-liner'е мы делаем:

  1. Создаём папку annotated для хранения меченых картинок
  2. Проверяем только JPG файлы
  3. Делаем магию при помощи ImageMagick
  4. Сохраняем меченую картинку в annotated

На меченой картинке в нижнем правом углу будет полупрозрачный прямоугольник, на котором белыми буквами будет написано название файла, без расширения.
Имя без расширения извлекается при помощи выражения ${i%.*}, рецепт стырен со stackoverflow.

 
about 3 years ago

Just some loosely related thoughts about the producing software.

Unavoidable

Regardless of whether you're Agile or doing some standard Waterfall practice, you inevitably do the following things:

  1. Determine what the application should do
  2. Write code
  3. Deploy the program
  4. Check that the program does what it should do.

These areas are absolutely necessary.
Even if you think you skip the fourth step, the end user of your application will do it anyway. Because to check that the program does what it should do you obviously have to make it do that.

Artifacts

What we're working with when developing the software application is codebase.

This codebase is expected to be run by some runtime.
Runtime is a general concept here: it can be either software, in case of Java VM, or hardware, in case of compiled C++ code.

When runtime executes the codebase, it can access other programs ("applications") or some OS facilities like file system or network sockets or I/O channels for human/machine interaction.
Together, all this 3rd party stuff is the environment inside which the codebase is being executed, runtime being the part of environment.

Bare codebase cannot be executed in any arbitrary environment.
It either:

  1. Is written for some specific environment and completely useless for other.
  2. Has parts by changing which we can accommodate to different environments.

These parts we call config.
Act of changing config we call configuration.

If config is not explicitly defined as constants or some sort of the structured file holding such a constants, it doesn't really matter, as the seams for changes required to migrate between environments are still there, albeit hidden.

We usually have the following types of configs:

  1. Connection strings for reaching some services
  2. Credentials for getting access to some services
  3. Properties which needed to accommodate to some restrictions or specifics of environment.

Final software product is a codebase after configuration being executed by a runtime inside an environment.

Database contents are environment not code.

Migrations are a config.

Modes of execution

Only two modes of execution of a product are really meaningful:

  1. Production mode
  2. Debug mode

We prefer to use the product in production mode.
We also prefer to test the product in production mode.
We prefer to get diagnostics in debug mode.
We also prefer to actually develop the product in debug mode.

It perfectly aligns with, for example, Debian tradition of packaging software into "just" packages and -dgb packages, which have binaries with debug symbols being not stripped out.
It also has -dev packages and -src packages, but that's not important, as they are on different levels of abstraction from our current standpoint.

No reason to invent other modes of execution.

Production mode is characterized by optimisation for speed of execution and security for both the user data and the product consistency.

Debug mode is characterized by strongest possible bias for transparency in all actions done by the application. All logging, assertion, error messaging facilities available should be open. If it's applicable, application should even unpack it's own source code in this mode.

Testing

Quite easy to see that the target of all application-level tests is the product, not the codebase (and it's dumb to think about testing config).
As a side note, we usually do not test the environment specifically, as the whole purpose of the environment is that we rely on it.

What does the lowest level unit tests do?

Unit tests take small slices of the codebase and treat them as microscopic product inside the minimal environment consisting only of runtime.

This property of unit tests does two things:

  1. Greatly reduces the complexity of testing, as we almost never need to configure the codebase under test.
  2. Greatly reduce the usefulness of test in question, because we check not the feature set of the product, but the feature set of its small slice.

Goal of the testing was already mentioned before: to make sure that the product really provides value for its user.
Value for user is determined by the feature set of the product, which written listing we call specification for the product.

Given the specification, one can check whether the product really provides the features for user.

This process is called testing.

Agent performing the testing can be either human, in which case we're talking about manual testing, or some other program, in which case we're talking about automated testing.

When we're doing the manual testing, specification can be written in any language.
When we're doing the automated testing, specification should be written in a language parsable by the automated tester.
Even if we're doing the automated testing, we usually still need the textual description of the feature set of the product.
Thus, it's rational to have the specification readable by both human and the automated tester.

Here we infer the natural necessity in the languages like Gherkin or Concordion.

Purity

A codebase consists of two parts mainly:

  1. One which does contact with the environment
  2. One which does not contact with the environment

Parts of the codebase which does not contact with the environment can be called pure.

By definition you can cover by unit tests only pure part of the codebase.

Some languages force you to explicitly split your codebase to pure and environment-dependent parts, like Haskell.

Unit tests treat code as a product.
They should be written like set of examples how to use the code they are testing.
This encourages usage of domain-specific languages to reduce duplication in tests setup and teardown.
This, in turn, encourages usage of domain-specific languages in the production code, to maintain the same level of abstraction in unit tests and the general readability.

Here we infer the natural necessity in the DSL.

Deploy

The deploy is an act of transferring the codebase from whatever storage it is in to the target machine, configuring it to the environment of that machine and thus making the product available there.

This term is independent of whether we are talking about compiled languages or not.

More than that, with compiled languages the compilation step is neither a deploy nor a configuration.
Compilation of a codebase is just transforming it to the form understandable by the natural runtime of the machine code: the microprocessor itself, with the OS supporting it.

We can safely skip the compilation if we can afford running the product by the runtime of the interpreter, be it JIT-compiling or line-by-line.

For compiled languages, the natural notion of deploy is the "installation" of the software into OS. The act of configuration of compiled codebase according to new environment is performed by the installer program.

For the scripting languages in the Web application development domain, there's no step of "installation" usually. We just copy the codebase verbatim to the target machine, change manually several lines in script files dedicated to holding the config and consider it done accommodating to the new environment.

Configurator

Well-known problem called "it was working on my machine" raises because of ignorance of the fact that:

  1. There are environments other than your workstation
  2. Your codebase depends on the **environment

Latter being a lot more significant than the former.

The config is just the parts of your codebase, and so it's just plain text.

Therefore, it is suggested that the following installation script will suffice for any codebase out there, no matter what programming language it is in and what language it is written in.

  1. Take the codebase and the config as input.
  2. config is the listing of the commands.
  3. A command either tells installer to change some text token in some file from codebase
  4. Or tells installer to rename/copy/move some file from codebase to some other place.

Such a script does not even deserve to be called a "build system".

Each config file, holding commands, corresponds to one of environments to which the product can be installed. codebase itself will hold only placeholders and possibly "example" files holding the bunches of placeholders which needs to be moved to appropriate places.

Only codebase should be pushed to the source control system.
config files should be published by either more secure (in case of sensitive credentials) or less secure (in case of local development workstations) means.

Build system

It is suggested that there's no real need in the build commands conceptually other than the following:

1. Make the documentation
   1a. Hand-crafted user-level guides.
   1b. Autogenerated API reference.
2. Perform the testing
   2a. Acceptance end-to-end testing.
   2b. Unit testing.
   2c. Any other varieties of testing like integration, performance, security, etc.
3. Static analysis
   3a. Conformance to the coding standards and best practices.
   3b. Statistics and metrics.
4. Deploy
   4a. Packaging for distribution
   4b. Delivering to target machine
   4c. Installation (reconfiguration) to target environment.
 
about 3 years ago

Допустим, вы в жопе и вам надо что-то сделать в командной строке Windows.
Когда вы запускаете cmd.exe, по дефолту он открывает домашний каталог, для Windows 7 это C:\Users\<username>\ в 99.9999% случаев.

Как перейти на папку на диске D:?
Простое:

cd D:\<somefolder>

этого не сделает, я гарантирую это.

Оказывается, в винде для каждого диска есть свой cd, как популярно объяснили coneslayer и JdeBP. Можно даже почитать у Реймонда Чена про историю такого дизайна.

Так что винда ещё более ненормальная, чем казалось.
Для того, чтобы перейти в другую папку на другом диске правильно, нужно либо выполнять cd с флагом /d:

cd /d D:\<somefolder>

либо делать cd как обычно, а потом отдельной командой называть диск:

cd D:\<somefolder>
D:

Вот так-то.

 
about 3 years ago

Here's the canonical method of lazy loading object attribute.

The point of lazy loading is when some attribute of an object is another object, and its construction is costly. As the result, we don't want to create this another object at the time of creating the parent object. Only at the time when we really need this object.

So:

class Lazyloader
{
  private $huge;
  
  public function getHuge()
  {
    if (!$this->huge)
      $this->huge = $this->makeHuge();

    return $this->huge;
  }
  
  public function makeHuge()
  {
    // ... actual creation of the child object ...
  }
}

It's not Haskell, of course, but this idiom is quite simple and more importantly, you can add lazyloading to any attribute, even already existing.

 
over 3 years ago

А вы знали, что в качестве ключа хэш-таблицы в Common Lisp можно использовать любые объекты?

Например, так:

(defparameter *table* (make-hash-table :test #'equal))

; voila: lists
(setf (gethash '(abcde 12345) *table*) 2)

(defclass point ()
  ((x :initarg x) (y :initarg y)))

;voila: objects (!!)
(setf (gethash (make-instance 'point 'x 1 'y 2) *table*) 44)

После этого делаем:

(loop for key being each hash-key of *table* using (hash-value value) 
  doing (format T "~a: ~a~%" key value))

Результат:

(ABCDE 12345): 2
#<POINT #x302000EEB56D>: 44
NIL

Однако, следует учесть, что хотя перечисление элементов и работает на объектах, gethash не найдёт нам элемент по ключу-объекту:

(gethash '(abcde 12345) *table*) => 2 T
(gethash (make-instance 'point 'x 1 'y 2) *table*) => NIL NIL

Так что любой элемент данных, который матчится в самом общем случае по equalp, может теоретически быть использован как ключ в хэш-таблице. По-моему, это просто офигительно.

 
over 3 years ago

Things in the source code I hate perhaps the most in my everyday work

There's things which one can tolerate. And there's things which just choke you down and turn your brain inside out and you suddenly jump up and start screaming and throwing things to other things.
Here's my list.

display_errors, 0

Holy crap, the MOST irritating is to see the following:

init.php
// ...some global init...
ini_set('display_errors', 0);
// ...some other global init...

That's making me facepalm every time I see it.
I mean, what the fuck?
You are DEVELOPER, muthafucka!
You MUST see the errors while you are working, how the hell you are supposed to debug anything without error reporting?!

The only place where you should put such an embarassing shit like the above is the bootstrap script at your PRODUCTION, just ONE of your deployment environments!

Here's how you supposed to do it, you moron!

init.php
if (PRODUCTION_MODE) ini_set('display_errors', 0);

Even better with feature-detecting instead of environment-detecting and proper code style:

init.php
if ($app->isAfraidOfSniffers())
  $app->shutUpErrorReporting();

ALL OTHER TIME IT'S MEANINGLESS TO DISABLE ERROR REPORTING, REMEMBER IT, FUCKER!!
I see even the freaking web frameworks doing this idiotic shit and inventing some crazy nonsense like their own debugging facilities with their own flag constants.
JUST WRITE EVERYTHING YOU CAN TO ME IN CASE OF ERROR, YOU WORTHLESS PIECE OF CRAP.

E_STRICT ignorance

Second is, of course, the following shit.

$arr = ['first' => 1, 'third' => 3];
echo $arr['second'];

BLAM! PHP Notice about access to undefined hashmap key.

YES SONUVABITCH I WORK WITH E_STRICT ENABLED because I do care about any possible sign that I screwed somewhere.

Here's the most usual usage of this shit:

controller.php
public function run()
{
  /** @var array $data */
  $data = $this->makeData();
  $this->render($data);
}

public function render($data)
{
  require ('view.php');
}
view.php
/**
 * Holy crap, we have $data array available here!
 */
... some other shit ...
echo $data['nonexistent key']
... some other crucial UI shit ...

PHP Notice in that view file means the user got the whole page crashed because of a single nonexistend data item, and all we wanted to do with that data item is render it, it wasn't something mission-critical.

If you want to write possibly nonexistent data just prepend a fucking @ that's all!.

Unnecessary strict comparison

That is, $some_fetch_result === null crap.

Yeah, that's right! Why the fuck you want to know whether the result of fetching something from somewhere is exactly null? What if someone who wrote that API method decided to return false in case of empty result? Even if there's an empty array (in which case even I agree that it should not be considered same as null) what are you going to do with it, anyway?

Just forget about this brain-hurting nonsense:

$records = $modelRepository->fetchRecords($query);
if (null === $records) // HOLY CRAP, NO!
    throw LogicException;

Write the fucking !$records and that's all! By doing that === crap you stress out that null is of significant value having a special meaning, when it obviously isn't, you just wanted to check whether we failed to return anything.

I DON'T EVEN START TO TALK ABOUT NOT RETURNING NULL VALUES THERE, YOU MORON STILL STUCK IN 90'S.

Prefixes before object properties

OMG, words can't describe the amount of disgust I experience when seeing the piece of crap like the following:

class Model
{
  public $public;
  private $_private;
}

Freaking degenerates coming to PHP from C/C++ completely missed the whole point with m_ prefix before member variables here. Remember, you brainless ape!

class Counter
{
    private:
    int m_counter;
}

int Counter::count(counter)
{
    int counter = counter++;
  m_counter += counter;
    return counter + m_counter;
}

MEMBER VARIABLES, DO YOU GET THEM NOW?

We can't differentiate between member and other local variables without special cues like the proper naming or FQN, so the m_ convenience was invented. In PHP we always refer to member variables from $this object so we always know what variable is internal to class and what variable conforms to fucking Demeter's Law.

There's nothing uglier in PHP code than $this->_shit. WTF is this ->_ noise, are we in Perl now? HATEHATEHATE

 
over 3 years ago

Дано: сырцы на C/C++, Debian.
Требуется: бинарник, который можно запускать под виндой.

Решение:

# aptitude install mingw-w64
$ cd myapp
$ x86_64-w64-mingw32-gcc myapp_entry_point.c -o myapp.exe
 
over 3 years ago

Есть несколько статей, которые в своё время просто перевернули моё представление о разработке программного обеспечения.
Я считаю их классикой и хочу тут перечислить.
По моему обыкновению, возможно, я когда-нибудь их сведу в один альманах.

  1. Teach Yourself Programming in Ten Years by Peter Norvig

  2. Revenge of the Nerds by Paul Graham

  3. How To Become A Hacker by Eric S. Raymond

  4. Execution in the Kingdom of Nouns by Steve Yegge

  5. The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) by Joel Spolsky

  6. I Pity The Fool Who Doesn't Write Unit Tests by Jeff Artwood

  7. NODB by Robert Martin

  8. Continuous Integration by Martin Fowler

  9. Cool URIs Don't Change supposedly by Tim Bernes-Lee

  10. You're just another carriage return line feed in the wall by Scott Hanselman

  11. Three Steps to a Useful Minimal Feature by J. B. Rainsberger

 
over 3 years ago

Итак, нам нужно запустить что-то очень-очень долгоиграющее на удалённом сервере.
Например, как мне сегодня - скрипт рассылки 14 тысяч писем.

Или очень не хочется терять рабочее окружение (например, у нас там открыт EMACS и в нём штук десять буферов с разными файлами конфигов).

Делается так:

$ ssh SERVERNAME
$ screen -dRR

Теперь мы в отдельной сессии screen на удалённом сервере.
Всё, что мы запустим теперь, будет работать внутри этой сессии.

После нажатия Ctrl-a (работает даже если какая-то долгоиграющая программа уже запущена) мы отключаемся от сессии screen и выходим обратно в сеанс ssh.
При этом всё, что было запущено внутри screen, продолжит работу.
Понятное дело, если сервак выключат, то всё, что было внутри screen помрёт, но это намного, намного лучше, чем если бы оно померло от того, что у нас оборвалось соединение ssh или нам просто понадобилось выключить свой компьютер.

Соответственно, можно выйти из сеанса ssh и делать всё, что угодно, выключить комп и пойти спать, например.
Потом, когда понадобится, мы делаем то же самое:

$ ssh SERVERNAME
$ screen -dRR

И снова попадаем в тот же сеанс, который запустили.
Ключи -dRR нужны для того, чтобы запустить новый сеанс, если ни одного не запущено, и/или подключиться к имеющемуся, если он запущен.

 
over 3 years ago

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 PHAR!

Packaging

Rasmus Schultz made a special script to package the Yii framework into
a PHAR archive
. I forked it to save for a future (at least my GitHub account
will live as long as this blog).

You need just to put this script to the root of Yii codebase (cloned github
repo, for example), and run it as usual:

$ php yii-phar.php

This will create the PHAR archive in the same directory.

Hovewer, beware the catch 1: PHP can refuse to create packed archive, emitting
the following error:

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 

I decided to just remove lines 140 and 142 from the script:

echo "Compressing files ...\n\n";
    
$phar->compressFiles($mode); 

And that's all. I can bear with 20 MB file in repo, and don't really care about
compression.

Using

To connect the resulting PHAR to your Yii application, replace your usual:

require_once('/path/to/your/yii/framework/yii.php'); 

With the following:

new Phar('/path/to/yii.phar'); require_once('phar://yii/yii.php');

Note that in new Phar() invocation you should use real path to your phar
archive file, but second line should be written verbatim, as the PHAR which
becomes created is being made with alias 'yii', using feature described in
the documentation for Phar::__construct
.

However, of course, there's a catch 2: Yii built-in asset manager (CAssetManager)
has too specific publish method, unable to cope with custom PHP streams.
So, we need the fixed version.

I decided to create a descendant of CAssetManager descriptively called PharCompatibleAssetManager
with the following definition exactly:

<?php
/**
 * Created by JetBrains PhpStorm.
 * User: hijarian
 * Date: 20.07.13
 * Time: 17:13
 */

/**
 * 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)));
    }
}

I'm really, really sorry that you had to read this traditionally horrible Yii
code, but that was inevitable... :(

Main change was starting from the $isPhar = strncmp('phar://', $path, 7) ===
0;
part.

Now just link this asset manager instead of built-in one:

config/main.php
'components' => [ 
    'assetManager' => [ 
        'class' => 'your.alias.path.to.PharCompatibleAssetManager'
    ]
]

Congratulations!

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.

 
over 3 years ago

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.

We discuss a lifehack-style solution to this problem: putting the DBMS which
will be used by our test harness completely on a RAM disk, so it'll
operate from much faster memory than the hard drive (even faster than from
SSD).

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 separate DBMS instances
to work on ramdisk, not the ones already installed on your system.

Here we'll look at how to prepare MySQL and MongoDB instances to
work on ramdisk.

End Result

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.

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.

Prequisites

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 tmpfs support. Your nonprivileged
user should be able to mount filesystems, or you'll need to hack the script
to introduce sudo at mount step (assuming your user can sudo).

For isolated MySQL instance you need the MySQL distirbutive downloaded from
the website
. For isolated MongoDB instance you need the MongoDB distirbutive
downloaded from the website
. Note, however, that the whole MongoDB server
is contained in just a single binary file ~8MB in size.

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.

Ramdisk

Making ramdisk is very simple with latest Linux:

# mount -t tmpfs $RAMDISK_NAME $RAMDISK_DIR

as root.

RAMDISK_NAME is some identifier for mountpoints table. RAMDISK_DIR is the
directory which will be turned into RAM-based filesystem.

After this mount action, anything you put into RAMDISK_DIR will be placed
into memory, without interaction with the physical hard drive.

Of course, it means that after unmounting the ramdisk everything which was
in it will be lost
.

Shutting down the ramdisk

Just unmount the created mount point:

# umount $RAMDISK_NAME

as root.

Note that you probably should stop all running services which still use the
ramdisk prior to unmounting!

Isolated MySQL instance

We'll use the MySQL Sandbox project to launch isolated MySQL instances.

For it to work you need the MySQL distirbutive downloaded from the website.

MySQL Sandbox is installed with the following command:

# cpan MySQL::Sandbox

as root, and you'll need to run it as follows:

$ SANDBOX_HOME="$RAMDISK_DIR" make_sandbox "$MYSQL_PACKAGE" -- \
    --sandbox_port="$MYSQL_PORT_DESIRED" --sandbox_directory="$MYSQL_DIRNAME"

It's a one-liner split to two lines for readability.

Note that you need root privileges only to install 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.

We need to set the SANDBOX_HOME 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 $HOME/sandboxes, which is probably not what you need.
Note that RAMDISK_DIR is the same directory that the one we prepared in previous
step.

MYSQL_PACKAGE is a full path to the MySQL distirbutive package downloaded
from website. Please note that MySQL Sandbox will unpack it to the same directory
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.

Remember the MYSQL_PORT_DESIRED value you use here, because you'll need to
use it to configure your test suite to point at correct MySQL instance.

MYSQL_DIRNAME is of least importance here, because it's just a name of a
subfolder under the SANDBOX_HOME in which this particular sandbox will be
put.

After make_sandbox ended it's routine you can check that your sandbox is
indeed working by running:

$ "$RAMDISK_DIR/$MYSQL_DIRNAME/use"

Connection to Isolated MySQL Instance

You should use the following credentials to connect to sandboxed MySQL:

  • host : '127.0.0.1'
  • port : $MYSQL_PORT_DESIRED
  • username : 'msandbox'
  • password : 'msandbox'

Please note that you must use 127.0.0.1 value for host and not a localhost
as usual, because of sandbox internal security configuration.

Shutting Down the Isolated MySQL Instance

To shutdown the sandboxed MySQL, issue the following command:

$ "$RAMDISK_DIR/$MYSQL_DIRNAME/stop"

or more forceful

"$RAMDISK_DIR/$MYSQL_DIRNAME/send_kill"

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.

Isolated MongoDB instance

MongoDB server is contained in just a single binary file so it'll be a lot
more easier compared to MySQL.

You'll need the MongoDB distirbutive downloaded from the website, too.
This time unpack it to some directory.

After that, you can launch a separate instance of MongoDB with the following
command:

$ "$MONGODB_BIN" --dbpath="$MONGODB_DIR" \
    --pidfilepath="$MONGODB_DIR/mongodb.pid \
    --port $MONGODB_PORT_DESIRED \
    --fork --logpath="$MONGODB_DIR/mongodb.log" 

MONGODB_BIN is a /bin/mongod 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, MONGODB_BIN can have a value
of ~/systems/mongodb-linux-x86_64-2.4.4/bin/mongod

MONGODB_DIR is a path to directory under RAMDISK_DIR to which this MongoDB
instance should put it's files. For example, it can be just a $RAMDISK_DIR/mongo.

As with MySQL, MONGODB_PORT_DESIRED 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.

Connecting to Isolated MongoDB Instance

By default MongoDB do not enforce any usernames or passwords so you need to
just use the hostname and port parameters.

  • host : 'localhost'
  • port : $MONGODB_PORT_DESIRED

For example, for PHP Mongo extension, you get a connection to this instance
as follows:

    $connection = new MongoClient("mongodb://localhost:$MONGODB_PORT_DESIRED"); 

Shutting Down the Isolated MongoDB Instance

As you provided the --pidfilepath commandline argument when launching the
MongoDB server, the following command should do the trick:

$ cat "$MONGODB_DIR/mongodb.pid" | xargs kill ; rm "$MONGODB_DIR/mongob.pid"

Essentially we are feeding the kill command with the contents of pidfile
and removing it afterwards.

Bash scripts to automate the sandbox starting and stopping

There is a GitHub repository with the example scripts nicely laid out
along with the comments.

There are three scripts:

  • db_sandbox_properties.sh: this is the all variable parameters you need to properly setup the sandboxes.
  • db_sandbox_start.sh: this script you run before your test suite.

    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.

    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 mount command
    in any way you see sufficient to successfully mount the ramdisk.

  • db_sandbox_stop.sh: 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).

 
almost 4 years ago

Допустим, хочется что-то запускать прямо при старте Debian. Вот необходимые
действия для этого:

# vim /etc/init.d/myscript 
# chmod +x /etc/init.d/myscript 
# insserv myscript 

В файле /etc/init.d/myscript должно быть по минимуму следующее:

#!/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 

Подробности об этой шапке можно прочитать в вики Debian. Название скрипта
myscript должно быть в точности повторено в названии файла скрипта, в поле
Provides и при вызове insserv.

Ранлевелы можно посмотреть здесь: http://wiki.debian.org/RunLevel.

Про dependency-based-boot там же в вики написано.

 
almost 4 years ago

Когда-то написал небольшую программку для сообщества игры Prime World.
Она парсит таблицу рейтингов и составляет по ней линейные графики, плюс
вычислят средний рейтинг (в рейтинге указываются первые 25 мест) по каждому
персонажу. Персонажей 62, соответственно, линий на графике тоже 62. :)

Здесь напишу, как и из чего сделал и как оно работает.

Полный исходный код выложил к себе на GitHub.

Как работает

План был такой: приложение представляет собой одну HTML страницу, с одним файлом
CSS и одним Javascript.

При загрузке страницы яваскрипт сразу делает AJAX-запрос на бэкэнд за данными.

Бэкэнд грабит исходную HTML-страницу, при помощи XPath выбирает значения рейтингов,
имена игроков, места и имена персонажей. Затем кодирует это всё в JSON и отправляет
скрипту обратно.

Скрипт отрисовывает полученные от бэкэнда рейтинги в виде графика при помощи
библиотеки Highcharts. Дополнительно бэкэнд возвращает вычисленные средние
значения рейтинга; они отображаются в виде таблицы, которая обрабатывается
javascript библиотекой DataTables, чтобы добавить сортировку.

Между таблицей и графиком переключаемся кнопками.

Вот само приложение.

Как я уже сказал, приложение достаточно маленькое.

В качестве недостатка можно упомянуть то, что каждый раз, когда приложение
открывается, делается запрос на исходный сайт Нивала. Делать так, конечно,
дурной тон, и стоит прикрутить какое-нибудь кэширование на стороне сервера.
Хотя у них всё равно топ-25 не так часто обновляется, да и посещаемость никакая,
так что для начала сойдёт.

Как собрано

Серверная часть основана на Hunchentoot, то есть, мы сами себе веб-сервер.

Вот все необходимые URL:

  1. /, по которому возвращается HTML страница приложения.
  2. /data, который AJAX endpoint, возвращающий JSON объект с данными
  3. /assets/scripts.js, клиентский скрипт, который раскладывает данные из JSON в график и таблицу
  4. /assets/styles.css, бумажка со стилями (с украшениями я не парился, в стилях только сокрытие некоторых элементов для инициализации интерфейса).
  5. /assets/loading.gif, показываем эту картинку пока /data грузится.
  6. /favicon.ico, иконка до кучи, честно стырил из пакета Html5 Boilerplate.

Хостимся на Heroku.

Извлечение исходных данных

Сам парсинг исходной веб-страницы полностью заключён в одном файле под названием
dno.lisp, и единственная функция, которая оттуда нужна публично, это send-data-from-origin-as-json,
которая генерирует строку в JSON формате, содержащую данные, нужные в scripts.js
для того, чтобы построить графики и таблицу. Сам файл dno.lisp можно посмотреть
в репозитарии на гитхабе
, я здесь его приводить не буду, слишком большой.

В итоге мы можем делать (load "dno.lisp") и получать с этого функцию, которая
даст нам JSON, который мы можем возвращать как результат AJAX запроса, то есть,
то, что нужно для пункта 2.

Отдача статических файлов

В идеологии hunchentoot'а (если не углубляться в детали), инициализация приложения
выглядит так:

  1. Настраиваем все пути в приложении, вызывая hunchentoot:create-folder-dispatcher-and-handler, hunchentoot-create-static-file-dispatcher-and-handler, hunchentoot:define-easy-handler и т. п. Это как раз было упрощено при помощи хелперов в предыдущем пункте.

  2. Создаём экземпляр приложения заклинанием:

    (hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 4242))) 
    

Для начала делаем три хелпера в отдельном файле, для того, чтобы добавление путей в Hunchentoot не было пыткой:

helpers.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)) 

Имея эти три хелпера, файл инициализации, фактически, будет иметь следующий вид:

init.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))) 

Само содержимое файлов index.html, scripts.js и styles.css
можно посмотреть на гитхабе, здесь я описываю только то, как приложение собрано,
а не как оно работает.

Мы не можем сделать (publish-directory "/" "webroot/"), потому что мы хотим дефолтный хэндлер для пути /.

Я завернул старт приложения в отдельную функцию (и не вызываю её сразу же),
потому что так будет удобнее в дальнейшём.

Теперь мы можем делать так:

(load "dno.lisp") 
(load "helpers.lisp") 
(load "init.lisp") 
(run)

После чего открываем браузер по адресу http://localhost:4242/ и наблюдаем готовое
приложение, при условии, что в том же каталоге, что и эти два скрипта, находятся
папки webroot и assets с соответствующими файлами в них.

Структурирование

Теперь запакуем всё в ASD. Вот моё определение приложения:

dno.asd
(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"))))) 
package.lisp
(defpackage #:dno 
  (:export :run) 
  (:use #:cl #:cl-user #:hunchentoot))

Как видно, все три исходника на Common Lisp, описанные выше, были переложены
в отдельный подкаталог src. Понятное дело, во всех файлах теперь первой строчкой
идёт вызов (in-package :dno).

Заметьте, что в ASD прописаны библиотеки, от которых зависит приложение (cl-ppcre,
cl-libxml2, iterate и cl-json используются только в dno.lisp, на самом
деле), однако сам пакет (package.lisp) не подключает эти библиотеки. Это
просто дело вкуса: я предпочитаю использовать символы из чужих пакетов по их
полному имени, не сокращая. Так сразу понятно, в какую документацию лезть.

Теперь у нас такая структура файлов:

assets/ 
    scripts.js 
    styles.css 
webroot/ 
    index.html 
    favicon.ico 
src/ 
    dno.lisp 
        init.lisp 
    helpers.lisp 
dno.asd 
package.lisp 

Запуск на локальной машине

Так как у нас теперь определён ASD пакет, мы можем воспользоваться механизмами
Quicklisp для того, чтобы максимально просто загружать приложение на
локальной машине.

Если в рабочем каталоге Quicklisp в подкаталоге local-projects сделать символическую
ссылку на каталог приложения, то можно будет подключать пакет приложения простым
вызовом (ql:quickload :dno).

Таким образом, кладём в корневой каталог приложения следующий скрипт запуска:

runner.lisp
(in-package :cl-user) 
(ql:quickload :dno) 
(dno:run)

Теперь видно, зачем нужна отдельная функция run, определённая в init.lisp:
для того, чтобы разделить загрузку самого приложения в рантайм и запуск приложения
как веб-сервера.

Всё, готовое веб-приложение можно запустить из консоли, например, так:

$ cd path/to/dno/app 
$ sbcl 
* (load "runner.lisp")

Понятное дело, вместо SBCL может быть любой Лисп на ваш выбор.

Консоль становится неюзабельной после этого. Когда понадобится загасить приложение
— нажатие Ctrl+D убивает приложение вместе с рантаймом лиспа.

Деплой на Heroku

Благодаря специальному buildpack'у для CL появилась возможность хостить
SBCL + Hunchentoot приложения на Heroku.

Для этого нужно подготовить приложение следующим образом:

  1. Настраиваем у себя подключение к Heroku.
  2. heroku create -s cedar --buildpack http://github.com/jsmpereira/heroku-buildpack-cl.git Запоминаем название проги, которое Heroku нам сгенерировало.
  3. heroku labs:enable user-env-compile -a myapp, вместо myapp пишем название проги из п.2.
  4. heroku config:add CL_IMPL=sbcl
  5. heroku config:add CL_WEBSERVER=hunchentoot
  6. heroku config:add LANG=en_US.UTF-8

Теперь в корневом каталоге приложения нужно добавить следующий скрипт, который
ожидает buildpack:

heroku-setup.lisp
(in-package :cl-user) 
(load (merge-pathnames *build-dir* "dno.asd")) 
(ql:quickload :dno)

Крайне важно то, что этот скрипт не выполняет (dno:run), как это делает
runner.lisp, потому что buildpack сам запустит Hunchentoot, так что от init.lisp
требуется только настроить пути.

После того, как этот скрипт добавлен в репозитарий (имя heroku-setup.lisp
имеет значение), можно пушить: git push heroku master, при условии, что
п. 1 выполнен полностью.

Поздравляю

Всё, теперь прога готова, есть как возможность запустить на локальной машине,
так и деплой сразу в Сеть (доменное имя тоже вполне приличное получается).

По тому же принципу можно строить любые другие простые веб-приложения, добавляя
в init.lisp определения для путей в приложении.

Конечно же, если прога сложная, то придётся делать какой-то кустарный роутер,
а также скорее всего, не отдавать статичные HTML файлы, а генерировать страницы
при помощи какого-нибудь шаблонизатора типа CL-WHO, CL-EMB или
чего-то подобного. Яваскрипт и CSS тоже можно генерировать прямо из Common
Lisp, для этого есть проекты Parenscript и css-lite соответственно.

Мне всё это не понадобилось, приложение достаточно простое чтобы сразу отдавать
статичные ассеты и HTML.

 
about 4 years ago

Документ, разбитый на несколько файлов

Допустим, у вас верстается книга достаточно большого объёма и вы структурировали
её двумя уровнями иерархии. Главный файл лежит в корневом каталоге и называется
videogamebook2.tex. Он вызывает глобальные макросы типа \documentclass
и затем подключает главы следующим образом:

...
        
\input{preface}
        
\input{moo2/chapter}
        
\input{planescape/chapter}
        
... 

То есть, каждая отдельная "глава" книги лежит в отдельной папке, и её главный
файл называется chapter.tex.

Эти файлы глав содержат вызовы макросов, локальные для главы, и затем подключают
разделы. Разделы в таком случае нужно подключать так:

...
        
\input{moo2/section-01/section}
        
\input{moo2/section-02/section}
        
... 

Видно, что разделы также лежат каждый в своей подпапке и файлы называются просто
section.tex.

Заметьте, что пути всё ещё начинаются от корневого каталога проекта (!).

Довольно очевидно, что если вы хотите разбивать разделы более низкого уровня
так же по отдельным файлам, вам придётся сохранять такой стиль указания путей.

Иллюстрации, подключаемые во вложенных файлах

Усложним задачу: допустим, мы хотим иллюстрировать наш текст, используя стандартный
пакет graphicx. Иллюстрации, соответствующие разделу, у нас лежат в подкаталоге
этого раздела, рядом с файлом section.tex.

Тогда в заголовке section.tex можно написать, что:

\graphicspath{{./moo2/section-01/}}

а в тексте писать просто

\includegraphics{start-galaxy.jpg}

То есть, путь надо писать опять-таки от корня, и обязательно завершать слешем,
иначе LaTeX вас не поймёт.

 
about 4 years ago

Вот как выглядит совершенно минимальный шаблон для книги, использующий только
стандартные классы, ничего сверхъестественного:

\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}

Пример конечного результата можно посмотреть в моей сборке A Video Game Book.

На обложке будет только title, по центру, и дата сборки, тоже по центру. Если
надо другую обложку, то придётся настроить дополнительную страницу.

 
about 4 years ago

Иногда одной веб-страницы мало. Надо скачать все остальные объекты, доступные
из этой веб-страницы. Оказывается, для этого достаточно одного только wget.

Вот заклинание, которое нужно прочитать (предполагается, что мы находимся в
каталоге, куда нужно скачать сайт):

$ wget -m -k -np -w 1 --random-wait -U "Mozilla" -e robots=off <URL>

Значения параметров:

  • -m Скачать всё, начиная с заданного URL (собственно то, что нужно).
  • -k Исправлять ссылки в скачанных HTML документах, чтобы ссылались не на Сеть, а друг на друга.
  • -np Качать только то, что ниже заданного URL или на его уровне (в частности, не качать ничего с других доменов).
  • -w 1 --random-wait Прикидываться обычным пользователем, делая паузы случайной длины минимум в 1 секунду между каждым скачиваемым файлом.
  • -U "Mozilla" Прикидываться Фаерфоксом (не очень настойчиво: никакой фаерфокс не использует такой User-Agent).
  • -e robots=off Вообще-то, не используйте этот параметр. Он заставляет wget игнорировать правила, описанные в robots.txt.

За инфу спасибо HydTechBlog.

 
about 4 years ago

Составил небольшой сборник из трёх прохождений игр: Вангеров, Freespace 2 и Star Trek: Starfleet Command. Эти прохождения настолько эпичны, что уже практически являются научно-фантастическими рассказами.

Я убеждён, что все три расссказа прекрасны, и заслуживают того, чтобы их читали, невзирая на происхождение. Иначе эти произведения просто сгниют в подшивках старых журналов.

Составил я в PDF, положил себе в Дропбокс.

Про HTML-версию подумаю, меня интересовала только пэдээфка, если честно.

В продолжение темы: на Let's Play Archive есть столь же чудесные рассказы-прохождения для Master of Orion 2, Planescape: Torment и Arcanum: Of Steamworks and Magick Obscura. Однако, они на английском языке, так что A Video Game Book 2 на языке берёз и осин будет недоступна. ;)

 
over 4 years ago

Here's how to fix the default too wide and too "round" fonts rendered in EMACS
under KDE (and maybe other DE, too).

Write this into the ~/.Xdefault file:

Xft.antialias: 1
Xft.hinting: 1
Xft.hintstyle: hintfull
Xft.rgba: rgb

After that, run the following:

$ xrdb -merge ~/.Xdefaults

And after that EMACS will really get the same-looking fonts as the other system
around it. Thank you Atragor for your answer at archlinux.org

 
over 4 years ago

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).

This is how my prompt looks like now:

This is four-line prompt.

  1. Empty line as a separator.
  2. Clean cut with command history number and 72 dashes.
  3. User and host names, current time, jobs count and current directory.
  4. Emoticon showing the result of last command and the traditional 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.

Emoticon at the last line behaves like this:

Big thanks to Make Tech Easier for inspiration.

Here how it was done...

The script

I put the code for constructing PS1 variable in the separate script.

. ~/.bash_colors

## Here we will construct our uber-prompt line


# GIT BRANCH

function parse_git_dirty {
  [[ $(git status 2> /dev/null | tail -n1) != "nothing to commit (working directory clean)" ]] && echo "*"
}
parse_git_branch() {
  git branch 2> /dev/null | sed -e '/^[^*]/d' -e "s/* \(.*\)/ [\1$(parse_git_dirty)]/"
}

# LAST COMMAND RESULT EMOTICON

last_command_result_emoticon="\`if [[ \$? = 0 ]]; then echo \"\[$Green\]^_^\[$Color_Off\]\"; else echo \"\[$Red\]O_O\[$Color_Off\]\"; fi\`"

# set variable identifying the chroot you work in 

if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then
    debian_chroot="\[$Purple\]─(\[$Color_Off\]$(cat /etc/debian_chroot)\[$Purple\])\[$Color_Off\]"
fi

 Separator="[\!]------------------------------------------------------------------------"
 FirstLine="\[$BGreen\]\u:\h$debian_chroot \[$BBlue\]\t \[$BBlack\]jobs: \j \[$Purple\](\[$Color_Off\]\w\[$Purple\])"
SecondLine="\[$Color_Off\]$last_command_result_emoticon\[$Color_Off\]\$(parse_git_branch) \[$BBlue\]\$\[$Color_Off\] "

PS1="\n"$Separator"\n"$FirstLine"\n"$SecondLine

Downloadable script for colors here and for the prompt here.

Installing

To use this script, you should source the .bash_prompt from your .bashrc
file and of course the first line of .bash_prompt should correctly source
the .bash_colors, too.

 
over 4 years ago

Just watched the episodes 6, 7 and 8 of Clean Coders codecasts, namely, "TDD part 2", "Architecture" and "SOLID principles", and just could not stay silent about the awesomeness of this works.

First of all, "TDD part 2" has almost half an hour of peeping over the shoulder of the Uncle Bob while he does the Bowling Kata (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 (IntelliJ IDEA was used there). Don't know what's more inspiring to  hardcore Viperized EMACS user like me.

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).

And third, "SOLID principles" expands "Architecture" even more and adds **a lot **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.

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 "Clean Code" and then only this three episodes. You just don't understand what you miss, honestly.

As for me, I'm ashamed of my early projects now and want to rewrite them all from scratch badly. :(

 
over 4 years ago

That's how you get the name of last tag applied to current branch in Git repo:

$ git describe | grep -Eo '^[^-]+'

We need to meddle with grep because git describe gives us additional info
in the form of

LAST_TAG-COMMITS_SINCE_LAST_TAG-gLAST_COMMIT_HASH

Note the funny literal 'g' before the LAST_COMMIT_HASH.

And that's how you get the list of changes since some COMMIT till the current
state of the working copy, in really pretty format "ISO Date (Author) Commit
text"
:

$ git log --no-merges --pretty=format:'%ai: (%an) %s' COMMIT..HEAD

HEAD is literal "HEAD" there. You can substitute COMMIT token with either
commit hash or tag.

Now you write the following script and place it in the root of the codebase
of your project:

#!/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 

Name it as changelog and then you can do just:

$ ./changelog

And get something like this:

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 

And this will be changes only since last tag applied. Excellent for quick reports
about current upstream.

 
over 4 years ago

It should be noted that true Common 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.

In all following code snippets token input stands for input string.

  1. Trimming string from spaces, tabs and newlines

    ([string-trim][1] '(#\Space #\Newline #\Return #\Linefeed #\Tab) input)) 
    

    All named characters are listed in Hyperspec, 13.1.7 Character Names.

  2. Replacing by regular expressions

    Provided by CL-PPCRE package.

    In next snippet I remove all tokens enclosed in square brackets from the input string:

    (ql:quickload :cl-ppcre) 
    ([cl-ppcre:regex-replace-all][4] "\\[[^]]+\\]" input "")
    

    Honestly, I don't know when you can need simple regex-replace and not regex-replace-all. Also, note the double-escaping of special symbols (\\[ instead of \[).

  3. Splitting string by separator symbol

    Provided by CL-UTILITIES package.

    In next snippet I split the input string by commas:

    (ql:quickload :cl-utilities) 
    ([cl-utilities:split-sequence][6] #\, input)
    
  4. Making same modification on every string in given list

    In next snippet I trim spaces around all strings in list input-list:

    ([map][7] 'list 
      (lambda (input) 
        (string-trim " " input)) 
      input-list)
    

    However, way better is to wrap the transformation for the string in separate function and call the mapping referencing just the name of transformation:

    (defun trim-spaces (input) 
        "Remove trailing and leading spaces from input string" 
        (string-trim '(#\Space) input))
    (map 'list #'trim-spaces input)
    

    Do not forget that string is just a sequence of a characters, and all sequence-operating functions can work on strings in either "abcd" form or '(#\a #\b #\c #\d) form. This applies only to sequence-operating functions, however.

  5. Removing the characters from string by condition

    In the next snippet I leave only the alphanumeric characters in the input string:

    ([remove-if-not][8] #'[alphanumericp][9] input)
    

    There are remove-if also.

    As with map, you can make arbitrary complex predicates either with lambdas or wrapping them in separate functions.

 
over 4 years ago

Во время учёбы в универе мне регулярно приходилось ездить на различные конференции
и делать там доклады. Каждый раз написание короткого содержательного доклада
было настоящим испытанием, потому что нужно было рассказать о своём проекте
в течение 5 минут, причём за это время нужно было вроде как рассказать о нём
всё.

Мучился я почти до самого до пятого курса, когда перед одной из конференций
меня не осенило и я не составил для себя короткий простой план текста доклада
на проект любого типа. С этим планом доклад обычно получался даже короче, чем
на 5 минут. Он успешно опробован на четырёх конференциях, так что я уверен
в его полезности, почему и делюсь им с Сетью. :)

Если кому-то план покажется очевидным, рад за вас. Если нет — то вот он, пожалуйста.

  1. С чем связана работа + что было выполнено. (2 предложения)

    «Моя работа связана с...»

    «В её рамках было разработано...»

    Во введении достаточно лишь назвать ключевые слова, по которым потенциальный слушатель поймёт а) разбирается ли он в теме вообще и б) интересна ли ему эта тема.

  2. Предметная область. (2 предложение)

    Скорее всего, в одно предложение уложиться не удастся, поэтому выделяется два.

    В начале нужно описать контекст работы, потому что доклад очень сжатый, и если сразу начать рассказывать про постановку задачи или методы решения, то слушатель не успеет разобрать вашу мысль.

    Скажем, если написали «Моя работа связана с разбором s-выражений при помощи многопоточных компиляторов на языке Haskell», значит, в одном предложении рассказываем, что такое s-выражения, в другом — что такое многопоточные компиляторы (раскрываем только самые сложные/конкретные концепции, на всё времени нет)

  3. Задача. (1 предложение)

    «Решалась задача...» или «Была поставлена задача...»

    Одним предложением описываем поставленную задачу, так как контекст уже установлен.

    Постановка задачи в идеале должна включать в себя описание конечного результата, потому что когда придёт время говорить о том, что получилось в итоге, крайне важно, чтобы результаты соответствовали задаче.

    Я многократно видел примеры того, как единственным вопросом из зала было: «А как полученные вами результаты соответствуют вашей задаче?».

  4. Актуальность. (1 предложение)

    «Данная задача имеет ценность, потому что...»

    Актуальность означает одно из двух: а) какую пользу принесёт решение вашей задачи или б) какие трудности существуют из-за того, что данная задача до сих пор не была решена.

  5. Методы. (3 предложения)

    «Для решения задачи было использовано...»

    Ни в коем случае нельзя углубляться в подробности, потому что аудитория гарантированно уснёт. Они не занимались этой работой, им априори не так интересны детали.

    Если кому-то станет действительно интересно, он спросит после доклада.

  6. Результаты + тесты. (2 предложения)

    «В качестве конечного результата было получено...»

    «Работа оттестирована на...» или «В качестве тестовых данных были использованы...»

    В качестве конечного результата всегда должен получиться некий артефакт: компьютерная программа, формальная спецификация, механизм/агрегат, какая-то ещё вещь.

    Очень большой интерес всегда вызывает то, на каких данных/экспериментах была опробована работа. Также тот факт, что в процессе работы автор озаботился тем, как он будет проверять её корректность, повышает её ценность.

    К тому же, так как студенческая конференция предназначена для того, чтобы показать своё старание и навыки, очень глупо будет не указать, что для решения поставленной задачи было необходимо что-нибудь вроде анализа динамики статистики рождаемости альбиносов в мире начиная с Рождества Христова.

  7. Выводы. (1 предложение)

    «Полученные результаты позволяют сделать вывод о том, что...»

    Всегда спрашивают о выводе, если сам об этом не сказал.

    Если вывод не сформировался сам собой в процессе работы, то ничего из пальца высасывать не нужно: достаточно просто сказать, что обнаружено, что выбранные методы успешно решают поставленную задачу / непригодны для решения поставленной задачи.

  8. Где может использоваться. (1 предложение)

    «Эта работа может быть использована для...»

    Если ответ на этот вопрос не очевиден из вышесказанного, то его обязательно зададут из зала.

    Увы, если результаты работы или использованные методы решения не могут использоваться нигде, то это автоматически означает, что работа не нужна, а, следовательно, зачем она вообще была выполнена? И потом, если есть актуальность, то почему работу негде использовать?.. В любом случае, вскрывается серьёзная ошибка в постановке задачи.

    В итоге получается 13 предложений (возможно, длинных, это неважно), которые очень легко выучить наизусть (и не читать по бумажке вообще) и содержат совершенно всё, что интересует аудиторию.

 
over 4 years ago

Preamble is this: we have the PHP-based website, and we are testing it with
the Behat+Mink+MinkExtension combo.

Suppose we want to write the following test scenario:

 When I am in the Friends section

… (something there) …

Then I should see “My Friend” in search results 

Let's define this steps in our FeatureContext. First step we can define with
the following regexp: /^I am in the Friends section$/ because we really don’t
need the method of FeatureContext class containing long switch enumerating
every possible section of the site.

<?php
/**
 * @Given /^I am in the "Friends" section$/
 */
public function iAmInTheFriendsSection() {
 return new Given('I am on "/friends"');
}

Second step we can define with the following regexp: /^I(?: should)? see "([^"]*)"
in the search results$/
.

<?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);

}

It should be obvious why we use the custom test step instead of using the predefined test steps and writing something like 'I should see "My Friend" in ".search-wrapper form input[role="search"]" element'.

Then, someday, sure thing, we will want to write the following scenario:

 When I am in the Shop section

… (something there) …

Then I should see “Interesting product” in search results 

And in here, we have another “search results”, which should be found by completely
different selector and which is located on different page.

So, this is the context-dependent statement: 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.

I'll use the abbrev CDTS instead of longer "context-dependent test step".

Fortunately, Behat has a feature with exactly the same purpose: subcontexts.
Unfortunately, it's not working in the way we need to use the CDTS properly.

In an ideal world, we can do this:

<?php
/**
 * @Given /^I am in the Friends section$/
 */
public function iAmInTheFriendsSection() {
 $this->useContext('friends_section', new FriendsSectionContext())
 return new Given('I am on "/friends"');
}

and this would load the FriendsSectionContext and all CDTS definitions in
it, like the following:

<?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(...);
 }
}

We useContext different context class, we get different definition for the
/^I should see "([^"]*)" in the search results$/ test step.

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.

Apart from being horribly ineffective, this prevents us from defining the test
steps having same regexp across several different separate subcontexts.

Workaround for this problem is this:

  1. add the property to the FeatureContext which will hold the reference to current subcontext, name it like "location_context" or so,
  2. make the context-setting ('I am in the "..." section') test step set the "location_context" to the subcontext needed (you can get the subcontext with the call to getSubcontext('alias')),
  3. move the context-dependent logic to “normal” subcontext methods, which should have the same name across all subcontexts,
  4. register all subcontexts with useContext under meaningful aliases like "friends_section", "shop_section", etc,
  5. define the context-dependent test step like 'I should see "..." in search results' in main FeatureContext class,
  6. 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.

So, we need our context-setting test steps to be like this:

<?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"');
}

Assuming 'friends_section' is an alias of the FriendsSectionContext, and
it was set in the constructor, after this test step, our "location_context"
will be FriendsSectionContext, and, say, it's getSearchResultsElement()
will do exactly what we need in the "Friends" section.

Then, the context-depentent test step will be like this, getting the location-dependent
logic from the "location_context" set previously:

<?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(...);
 }
}

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
.feature files, just explicitly name the section needed beforehand somewhere
above in the text. This will make the .feature files a lot more human-readable.

This concludes the explanation about how to use this linguistic technique in
Behat tests.