воскресенье, 11 ноября 2012 г.

Über prompt string for modern commandline

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

среда, 31 октября 2012 г.

On Clean Coders episodes 6 through 8

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. :(

суббота, 15 сентября 2012 г.

Building changelog from Git log output

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.

понедельник, 10 сентября 2012 г.

Essential string processing functions in Common Lisp

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 '(#\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 "\\[[^]]+\\]" 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 #\, 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 '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 #'alphanumericp input)

    There are remove-if also.

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

воскресенье, 9 сентября 2012 г.

Как написать пятиминутный доклад на студенческую конференцию

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

вторник, 28 августа 2012 г.

Context-dependent Behat tests steps

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.


/**
 * @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$/.


/**
 * @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:


/**
* @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:


// 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:


/**
 * @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:


/**
 * @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.

вторник, 3 июля 2012 г.

Как добавить поддержку Markdown в Kate, KWrite и встроенный редактор Krusader (F4)

Лично я для работы с PHP проектами уже давно пользуюсь в качестве IDE исключительно Krusader'ом и его встроенным редактором. И встроенным поиском. И встроенным архиватором. Ну, в общем, понятно. Встроенный в Krusader редактор является всего лишь Kate, который запихнули в контейнер. И этот редактор меня устраивает практически во всём, кроме одной вещи: там нет встроенной поддержки Markdown.

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