Monday, December 22, 2008

Почему программисты должны писать документы в LaTeX

Хочу сразу написать, что в этом небольшом посте мне бы не хотелось рассказывать о преимуществах LaTeX в том виде, как это принято. Вы не найдете здесь похвал в адрес удобства использования, удобства редактирования, изменения оформления, набора формул, составления содержаний и списков литературы - всё это присуще LaTeX, но и упоминалось уже неприличное количество раз. Я бы хотел рассказать о тех забавных преимуществах, которые лично мне в LaTeX показались важными.
В процессе обучения в НАУ им. Н.Е. Жуковского "ХАИ" я дошел до момента, когда нужно писать бакалаврскую работу. Ничего особенного в этом нет, тема была выбрана, "исследования" проведены, программа уже написана. Осталось только написать пояснительную записку.
А это с незапамятных времен было для меня самым сложным. Еще участвуя в МАН, при написании курсовых работ и т.п. я заметил за собой жгучее нежелание писать текст. Это было скучно и неинтересно, не то что создавать программную реализацию.
Но недавно я открыл для себя LaTeX и все переменилось! Теперь я с удовольствием набираю текст, вставляю команды, "компилирую", правлю опечатки и пишу дальше. У меня даже появились "баги"(вызванные, вероятно, моим собственным недопониманием команд), которые я, ругаясь, фикшу. Написание скучного текста превратилось в увлекательнейший труд - вот вам причина, по которой программисту стоить писать документы в LaTeX!
Побочная причина - возможность хранения истории изменений в репозитории. Ведь когда пишешь документ, так и хочется закоммитить, чтобы увековечить свои изменения, а .tex файлы для этого подходят как нельзя лучше.


Напоследок хотелось бы дать несколько интересных ссылок
Также в качестве бесплатной рекламы - эти люди занимаются составлением руководств по верстке дипломов в LaTeX, честь им и хвала.Я собираюсь выложить в общий доступ мои наработки(классы, стили) как только пояснительная записка будет написана. Надеюсь, это окажется полезным для тех, чьи alma mater предъявляют схожие к моему требования.

Wednesday, December 10, 2008

SciTE incremental autocompletion

I am a big fan of these "holy" editors, like vim and emacs (yes, I use both). But I am also a big fan of SciTE - simple text editor for developers. It is not so popular like vim or emacs, but I like it. So I decide to improve one of its features - autocompletion. I called the result "Incremental autcompletion". Look at this video:

Isn't it cool? I think it is :) If you like it, you can use this patchset. To use feature after applying patches and compiling, just add "autocompleteword.incremental=1" to the properties file.
I started a thread in scite-interest group. I hope, this feature will be accepted by Neil Hodgson (SciTE and Scintilla creator), because for me it is acceptable open source alternative for some of Visual Assist autocompletion features.
UPD 1. Don't forget to add all of these lines

autocompleteword.automatic=1
autocomplete.choose.single=0
autocompleteword.incremental=1
to your properties file.
UPD 2. Frank Wunderlich noticed the bug in implementation. Please use the second version of patch

Friday, November 21, 2008

О разделении труда, написании кейсов и их реализации

Прошедшая вчера встреча IT Talk натолкнула меня на интересные воспоминания. Я вспомнил предисловие к роману Джека Лондона "Сердца трёх". А точнее, заметил, что Лондон описывает в нем не только свою работу, но и современные реалии IT.
Предисловие к предисловию. Обычно процесс разработки состоит в преобразовании начальной идеи в кейсы-требования, которые потом преобразуются в код. Т.о., есть два вида работ - написание кейсов и написание кода. В последнее время существует множество мнений на тему, нужно ли написание кейсов:

  • кейсы писать не надо. Нужно, чтобы кодописатели хорошо представляли идею, тогда они сразу преобразуют ее в код
  • кейсы писать надо, они играют основную роль и для тестирования и для разработки
  • надо писать множество детальных кейсов, отдельно для тестирования, отдельно для разработки
  • кейсы писать нужно для коммуникации с заказчиком
  • и т.д., и т.п.
Мнения о том, кто должен выполнять эти работы:
  • Technical writers
  • QA, им потом это тестировать
  • Бизнес аналитик, так как его прямая обязанность - разбираться в начальной идее
  • Разработчики, так как они могут представить программный продукт
  • Отдельный случай - когда в команде нет особого разделения на роли и идеей проникнут каждый - тогда кейсы могут писать все.
Джек Лондон "Сердца Трёх", отрывки из предисловия с комментариями:
..Разделение труда - прежде всего. И вот, связавшись с могущественными газетными объединениями или с отдельными лицами, как это имело место в данном случае, - я имею в виду "Сердца трех", - они заказывают высококвалифицированным сценаристам (даже ради спасения собственной жизни не сумевшим бы написать роман) сценарий, который романисты (даже ради спасения собственной жизни не сумевшие бы написать сценарий) превращают затем в роман...
Комментарий 1. Встречаются люди, которые могут писать только сценарии или только романы. Не нужно заставлять их выполнять оба вида работ.
...Итак, мы работали параллельно, каждый над своим куском. Когда я писал какую-то главу, я, естественно, не мог принимать в расчет того, что происходит в следующей или через двенадцать глав, так как я этого не знал. Не знал этого и м-р Годдард. Отсюда неизбежные последствия: нельзя сказать, чтобы повествование в "Сердцах трех" отличалось особой последовательностью, хотя, оно, безусловно, не лишено логики...
Комментарий 2. И сценарист и романист работают в условиях, когда неизвестно, что будет дальше. Это нормально.
...Представьте себе мое изумление, когда я, будучи на Гавайях, вдруг получаю от м-ра Годдарда по почте из Нью-Йорка сценарий четырнадцатогоэпизода (я же в то время только еще трудился над литературной обработкой десятого эпизода) и вижу, что мой герой женат совсем не на той женщине! И в нашем распоряжении всего только один эпизод, когда можно избавиться от нее и связать моего героя узами законного брака с единственной женщиной, на которой он может и должен жениться. Как это сделано - прошу посмотреть в последней главе или пятнадцатом эпизоде. Можете не сомневаться, что м-р Годдард надоумил меня, как это сделать.
Комментарий 3. Сценарий обычно опережает роман по количеству эпизодов. Задача сценариста - стараться не допускать того, чтобы новые эпизоды влияли на старые.
Комментарий 4. К сценарию нужно относится со всей серьезностью. Если вдруг нужно что-то добавить в сценарий - нужно пытаться сделать это с минимальными изменениями в уже написанном, тогда роман тоже нужно будет минимально переписывать. Т.е., написание кейсов очень сходно с написанием кода. Вместо привычного "unit тесты -> код -> рефакторинг" при написании сценария нужно придерживаться сходного "проверка требования на удовлетворение какой-то части идеи -> написание требования -> рефакторинг"
...Дело в том, что м-р Годдард - мастер по части развития действия и гений по части быстроты. Развитие действия нимало не волнует его. "Изобразить", - спокойно указывает он в авторской ремарке киноактеру. Очевидно,актер "изображает", ибо м-р Годдард тут же начинает нагромождать однодействие на другое. "Изобразить горе!" - приказывает он, или "печаль",или "гнев", или "искреннее сочувствие", или "желание убить", или "стремление покончить жизнь самоубийством". Вот и все. Так и должно быть - иначе, когда же он завершил бы работу и написал свои тысячу триста сцен?
Но можете себе представить, каково пришлось мне, несчастному, который не мог ограничиться волшебным словом "изобразить", а должен был описать - и притом весьма подробно - все те настроения и положения, которые одним росчерком пера наметил м-р Годдард! Черт побери! Диккенсу не казалось чрезмерным излишеством потратить тысячу слов на описание и возможно более тонкую обрисовку горестных переживаний того или иного из своих героев. А вот м-р Годдард говорит: "Изобразить", - и рабы киноаппарата делают все, что нужно...
Комментарий 5. Сценарист не должен вдаваться в подробности. "Изображать" умеет романист, не надо отбирать у него хлеб.
Если в основе этой авантюры, именуемой "Сердца трех", лежит сотрудничество, я восхищен идеей сотрудничества. Но только - увы! - боюсь, что такого коллегу, как м-р Годдард, можно встретить не чаще, чем одного на миллион. Мы ни разу не перебросились даже словом, у нас не было ни одного спора, ни единой дискуссии. Но в таком случае я, должно быть, и сам - не коллега, а мечта! Разве я не позволил ему - без единого намека на жалобу или возражение - "изображать" все, что ему заблагорассудится, на протяжении 15 эпизодов сценария, 1300 сцен и 31000 футов пленки, а затем 111000 слов, составивших роман? И все-таки теперь, когда я кончил сей труд, я очень был бы рад, если бы не начинал его, - по одной простой причине: мне хотелось бы самому прочесть книгу и посмотреть, как она читается. А меня это очень интересует. Очень.
Комментарий 6. Сотрудничество подразумевает под собой профессионализм обеих сторон. Все вышеприведенные комментарии не рассматривают случаи, когда какая-то из сторон - непрофессиональна. Как по мне, глупо пытаться собрать хор из не имеющих слуха людей.
Комментарий 7. Сценарист и романист должны быть удовлетворены результатом. Именно не работой, а результатом.

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

Friday, November 14, 2008

Об отношении к вышестоящим

Этот пост несколько дней лежал неопубликованным, потому что я не решался опубликовать эти субъективные размышления, так как они существенно отличаются от тематики предыдущих публикаций. Но сегодня случайно наткнулся на эту статью. Кому легче читать на русском(статья достаточно эмоциональна, а эмоции легче воспринимаются на родном языке) - вот здесь я видел перевод.
После прочтения статьи я стер всё, что написал ранее, потому что не cмог так точно передать свои мысли. Я прочел в этой статье не ненависть к "проприетарному миру", а увидел в ней свою же ненависть к людям, которые обществом ставятся выше меня. Это и начальство, которое мне как назло попадается недалекое. И различные менеджеры, утратившие(или никогда и не имевшие) способность созидать. И преподаватели, которые откровенно глупы. И вся эта кодла бюрократов, официалов, authorities и проч.
У каждого человека есть своя цена. И как неприятно ощущать, что твои познания о ценообразовании никуда не годны, и в реальном мире стоимость человека определяется по параметрам, которые тебе или непонятны, или, что нередко, отвратительны.

Monday, October 20, 2008

Произносим числа словами, альтернатива SayNumber в Asterisk

Казалось бы, совсем детская задачка - имея на входе число, например, 42, получить на выходе строку символов - "сорок два". Вот и в Asterisk(программная АТС с открытым исходным кодом) есть встроенная функция SayNumber, которая "произносит"(проигрывает) число в канал. На самом деле, это ничем не отличается от простого преобразования числа в последовательность слов - Asterisk просто проигрывает звуковые файлы, которые имеют соответствующие словам названия.
Asterisk предоставляет простейшую функциональность по локализации звуковых файлов - вместо стандартной озвучки можно подставить свою, например русскую. И тогда 42 будет произноситься не как "fourty two", а как "сорок два". Однако, такая локализация не полная - при ближайшем рассмотрении можно заметить, что в русском языке числа произносятся совсем не так, как в английском. Например, 201 произносится как "двести один", а не как "два сотня один". Более того, в русском языке есть еще падежи, единственное/множественное число, мужской/женский/средний род. И когда нужно произнести что-то вроде "одна тысяча четыреста один рубль" или "две тысячи сорок пять рублей", встроенные средства совсем не подходят.
На voip-info.org даже есть ссылка на альтернативную реализацию SayNumber, которая настраивается через конфигурационные файлы(на voip-info.org неправильная ссылка, правильная на данный момент - альтернатива SayNumber от www.beronet.com ). Однако она реализована на С, в виде модуля, так что слабо портируема между версиями Asterisk: я не смог использовать ее в Asterisk 1.4.x. И тогда мне пришла в голову мысль написать свою альтернативную реализацию. В качестве требований выступили:

  • Возможность гибкой настройки(решение: использовать конфигурационные файлы)

  • Переносимость между версиями, удобство установки и использования(решение: использовать механизм AGI)

  • Элегантность (решение: использовать Python :) )
Не затягивая долго, покажу, что в итоге получилось: Исходный код.
Или без подсветки: Plain text.
Я старался комментировать и документировать код, чтобы он был прост и понятен. Также надеюсь, что он оказался достаточно элегантен (любые замечания или исправления приветствуются). Осталось только показать, как его использовать:
exten => _X.,n,AGI(pysaynumber.agi|42r|ru.conf)
, где pysaynumber.agi - название скрипта, 42r - что надо произнести, ru.conf - название конфигурационного файла.
Конфигурационный/ные файлы нужно положить рядом с pysaynumber.agi в agi-bin (его местоположение разнится в зависимости от места установки Asterisk)
Пример конфигурационного файла: здесь
Формат конфигурационного файла. Частично позаимствован из наработок www.beronet.com. Каждая строка состоит из двух частей, разделенных ":". Первая часть - регулярное выражение, без начального "^" и конечного "$"(они подставляются автоматически). Вторая часть - список действий, разделенных ";". Алгоритм прост - идем подряд по всем правилам и если находим match с номером, который мы желаем "произнести", то выполняем подряд все действия по соответствующему списку. Видов действий три. Если строка действия представляет собой конечное "слово" (например, rublei, 40), то мы просто "произносим" его. Если строка действия начинается с number, то это второй тип действия. Параметры указываются в скобках, вот так - (индекс|суффикс). При выполнении этого действия "произносится" конечное "слово", составленное из символа, который находится в исходном номере по индексу и суффикса. Третье действие - recursive. Параметры - (индекс[,индекс...]|суффикс). Запускает весь алгоритм рекурсивно, но уже для номера составленного из символов, которые берутся из исходного номера по индексам, и суффикса.
Приведенный мной код может использоваться не только для Asterisk(проверялось на Asterisk 1.4.18.1), а и для других нужд. Также для того, чтобы ускорить работу скрипта можно использовать FastAGI, тогда можно будет хранить таблицу уже проинициализированной и не запускать интерпретатор каждый раз заново.
UPD. Используйте FastAGI. По непонятной причине подобным образом написанные скрипты приводят к подвисанию Asterisk со 100% загрузкой процессора. С применением FastAGI таких проблем не наблюдалось.

Thursday, August 28, 2008

IPv6 не будет

Слегка адаптированная мной заметка по мотивам http://www.extremetech.com/article2/0,2845,2328258,00.asp:

Arbor Networks измерили объем IPv6 траффика, проходящего через 2400 бэкбонов и роутеров у 87 провайдеров Интернет по всему миру. Учет велся с Июня 2007 по Июль 2008 года. Полученные данные были использованы для исследования, которое компания считает наиболее всеохватывающим исследованием ситуации с IPv6 на сегодняшнее время. В целом Arbor отследил 15 экзабайт траффика - при этом все человеческое знание занимает 4 экзабайта, как заявляют представители компании.

Это исследование показало, насколько плачевна ситуация в стане IPv6. Если верить полученным данным, объем междоменного IPv6 траффика составил всего 0.0026% от объема IPv4 траффика. Были замечены два пика активности между 4 ноября и Рождеством 2007 года, на пике процентное соотношение поднялось до 0.012%. При этом, процент соответствия IPv6 и IPv4 траффика оставался неизменным за все время исследования.

Я уже писал о том, что такое IPv6 и насколько переход на него с устаревшего IPv4 важен. В действительности, если не будет существенных сдвигов в ближайшее время, то по некоторым оценкам уже к 2011 году IPv4 адреса закончатся и развитие сети Интернет остановится. Переход же на IPv6 даст возможность использовать 340 миллиардов миллиардов миллиардов миллиардов адресов, чего должно хватить на ближайшее будущее :)

"Я не думаю, что переход уже начался", - говорит Scott Iekel-Johnson, основной автор исследования и главный специалист по программному обеспечению в компании Arbor, -"Не похоже, что хотя бы в какой-нибудь значительной части из охваченных исследованием регионов были существенные сдвиги"
Как это ни дико, по заявлениям исследователей, один из пиков совпал с собранием рабочей группы проектирования Интернет (IETF) - одной из групп, которая яростно толкает индустрию к использованию IPv6 адресования. Во время собрания, как говорят представители Arbor, участников попросили выключить IPv4 функциональность на своих компьютерах и роутерах и проверить, какие сайты будут доступны. Чтобы протестировать инфраструктуру, они смотрят онлайн-видео, качают файлы больших объемов. Предполагается, что это и вызвало учтенный пик.

Тем не менее, на этом собрании было зарегистрировано только 1168 человек. Т.е., по данным компании, всего чуть более тысячи человек вызвали самый высокий всплеск IPv6 активности за последние 12 месяцев. Это говорит о многом.

Исследователи видят два способа разрешения проблемы. Первый - поддержка идеи компаниями и государственными органами. Второй - проникновение идеи глубоко в массы. Iekel-Johnson делает ставку на первый вариант. Он приводит пример: "Если Comcast(ISP - прим. моё) скажет своим клиентам: 'Окей, вам нужно использовать IPv6, потому что у нас кончаются адреса, а мы хотим добавлять новых пользователей' - это приведет к существенным продвижениям".
Со своей стороны хочется добавить, что это исследование раскрыло мне глаза на то, насколько все плохо с IPv6. Возможно, понимание того, что мы еще в самом начале пути, подстегнет крупные компании, представителей государства и простых пользователей и продвижение пойдет быстрее.
Чтобы избежать обвинения в искажении фактов при адаптации я предлагаю поучаствовать в полном переводе статьи - сам я не смогу довести ее до читабельного вида без существенных правок. Поучаствовать и почитать текущий вариант перевода можно здесь

Friday, August 22, 2008

Использование Boost Python

“...one of the most highly regarded and expertly designed C++ library projects in the world.”
— Herb Sutter and Andrei Alexandrescu, C++ Coding Standards
Действительно, библиотека Boost - это живой пример того, насколько С++ может быть крут. И Boost.Python - не исключение.
Итак, Boost.Python - это библиотека, которая позволяет писать на С++ модули-расширения для Python, а также использовать возможности Python из С++. Зачем это может быть нужно? Например,
  • Переписывание bottlenecks на С/C++
  • Сокрытие части реализации в компилируемом модуле(весьма важно для распространениея коммерческих приложений)
  • Использование существующих библиотек(как на С/С++, так и на Python)
Конечно, можно использовать для решения этих задач Python C API, но, с одной стороны, С++ куда удобней для всего цикла разработки, а с другой - Boost.Python делает разработку на С++ настолько простой, что отказаться от нее невозможно.
Этим постом я не хочу повторять документацию по Boost.Python. Я ставлю целью скорее показать мощность Boost.Python и то, как он преобразует все вокруг :) И так как код - лучшая демонстрация, то я буду использовать фрагменты кода с пояснениями.
Итак, простейший модуль-расширение на С++:
#include <boost/python.hpp>
using namespace boost::python;
struct World
{
World(std::string msg_): msg(msg_) {}
std::string greet()
{
return msg;
}
std::string msg;
};
BOOST_PYTHON_MODULE(имя_модуля)
{
class_<World>("World", init<std::string>())
.def("greet", &World::greet)
;
}
Этот пример должен быть знаком тем, кто заглядывал в документацию. В нем ничего сложного нет - наш модуль включает в себя класс с конструктором и одной функцией. Предлагаю его взять в качестве основы и добавлять в него фрагменты ниже. Но сначала - как собирать этот пример. Предлагаю сохранить этот файл как имя_файла.cpp, после этого сборка может производиться при помощи gcc так:
gcc -shared -Wl,-soname,имя_модуля.so -o имя_модуля.so имя_файла.cpp -I /usr/include/python2.5/ -lboost_python
Обратите внимание, что имя_модуля должно совпадать с именем, указанным в файле. Также я использую python2.5. Тем, кто использует другую версию, нужно также поменять номер версии в пути для include.
Естественно, модуль не обязан состоять из одного файла, однако для сборки больших проектов рекомендую использовать distutils. Документация по нему достаточно подробная, чтобы собрать любой модуль. Самое главное - не забыть в описание Extension включить libraries=['boost_python'], а также указать то же имя Extension, что и внутри файлов используется.
После того, как простейший модуль собран и опробован,
In [1]: from имя_модуля import World
In [2]: w = World("Hello!")
In [3]: w.greet()
Out[3]: 'Hello!'
, можно приступать к самому интересному - к полезным обрывкам кода:
/*добавим две функции к World, "экспорт" их описывать не буду, можно по аналогии*/
void set(std::string msg) { this->msg = msg; }
void anotherWorldSet(World * world, std::string msg)
{
world->set(msg);
}
Проверяем:
In [1]: from hello_ext import World
In [2]: w1 = World("w1")
In [3]: w2 = World("w2")
In [4]: w1.anotherWorldSet(w2,"w2_new")
In [5]: w2.greet()
Out[5]: 'w2_new'
Т.е., Boost.Python прекрасно понимает, когда вы хотите получить объект по значению, а когда по ссылке. Более того, можно использовать и ссылки (void anotherWorldSet(World& world, std::string msg);) - разницы нет. Дальше перейдем к самому интересному - к boost::python::object. Это класс, который эмулирует работу Python объекта. Вот так, например:
void anotherObjectSet(object obj, std::string msg)
{
obj.attr("set")(msg);
}
И это работает, также как и предыдущий вариант. Обратите внимание на метод attr(). Его можно использовать как для вызова методов(см. выше), так и для получения значений:
std::string get_version()
{
/*Так импортятся Python модули. В качестве имени можно использовать и разделенные точкой названия*/
object sys = import("sys");
return extract<std::string>(sys.attr("version"));/*так преобразуются Python объекты в С++ объекты*/
}
std::string get_some(object myObj,std::string str)/*еще один пример*/
{
return extract<std::string>(myObj.attr(str.c_str()));
}
Раз уж зашла речь о Python объектах, то стоит рассказать о Python str и dict объектах:
dict foo_dict()
{
dict result;
result["1"]="2";
result["2"]="3";
return result;
}
object foo_string()
{
char buf[12]={0};
sprintf(buf,"%08XHELP",4242);
str s(buf,12);/*12 - длина строки*/
std::vector m;
m.push_back('1');m.push_back('2');m.push_back('3');m.push_back('4');
s += str(&m[0],m.size());
s += str("super test 2",12);
return s;
}
Т.е., работать с dict и str - одно удовольствие. Идем дальше, одна из пречудеснейших возможностей:
object get_func()
{
return make_function(&World::func);/*make_function - это чистая магия :)*/
}
std::string func()
{
return "I am func!";
}
Использование предыдущих фрагментов я не демонстрировал из Python, так как они очень просты. Представленный выше фрагмент ничуть не сложнее, но я все же покажу, как его использовать:
In [1]: from hello_ext import World
In [2]: w = World("Hello")
In [3]: f = w.get_func()
In [4]: f(w)
Out[4]: 'I am func!'
Обратите внимание на вызов f(w), здесь w - это self.
В общем, основные моменты я осветил. Многое еще можно найти в документации, во многом можно положиться на Boost.Python - он работает именно так, как вы от него ожидаете(например, C++ исключения транслируются в Python и т.д.).
Отдельно хочется упомянуть о нескольких важных вещах, которые могут привести к ошибкам:
  • В документации прекрасно описано наследование в Python от С++ классов. Однако, если вы переопределяете конструктор - обязательно вызывайте конструктор базового класса, даже если в базовом С++ классе он тривиален. Пренебрежение этим может вызвать непонятные ошибки
  • Если вы передаете Python объект по ссылке в С++ модуль, а потом сохраняете его там для дальнейшего использования - помните, что ссылка может стать невалидной - Python GC может и удалить его, если ссылка останется лишь в С++ части
Возможно, я буду обновлять этот список интересных моментов.
Ну, и на десерт. Если вы используете совместно С++ и Python модули (например, С++ модули используются для сокрытия функционала) и если вы используете логгирование, то скорее всего, вам захочется, чтобы логгирование всех модулей велось однотипно. Очень удобно было бы для этого использовать стандартный Python модуль logging. Чтобы было легче использовать этот модуль из С++, я написал небольшой класс:
/*----------------------------------------*/
/*logging.hpp*/
/*----------------------------------------*/
#pragma once
#include <boost/python.hpp>
#include <map>
#include <string>

class Logging
{
public:
static boost::python::object getLogger(const std::string& name);
static boost::python::object debug(const std::string& name);
static boost::python::object info(const std::string& name);
static boost::python::object warning(const std::string& name);
static boost::python::object error(const std::string& name);
static boost::python::object critical(const std::string& name);
static boost::python::object log(const std::string& name);

private:
typedef std::map<std::string,boost::python::object> LoggersMap;
static LoggersMap m_LoggersMap;
};
/*----------------------------------------*/
/*logging.cpp*/
/*----------------------------------------*/
#include "logging.hpp"

Logging::LoggersMap Logging::m_LoggersMap;

boost::python::object Logging::getLogger(const std::string& name)
{
static boost::python::object logging = boost::python::import("logging");
static boost::python::object settings = boost::python::import("conf.settings");/*Допустим, здесь хранятся настройки, в том числе и настройки логгирования*/

LoggersMap::iterator it = m_LoggersMap.find(name);
if(it != m_LoggersMap.end())
{
return (*it).second;
}
else
{
boost::python::object logger = logging.attr("getLogger")(name);
boost::python::object level = settings.attr("LOG_MODULES")[name];/*LOG_MODULES - dict, который ставит имя в соответствие с logging level*/
logger.attr("setLevel")(level);
m_LoggersMap[name] = logger;
return logger;
}
}

boost::python::object Logging::debug(const std::string& name)
{
return getLogger(name).attr("debug");
}

boost::python::object Logging::info(const std::string& name)
{
return getLogger(name).attr("info");
}

boost::python::object Logging::warning(const std::string& name)
{
return getLogger(name).attr("warning");
}

boost::python::object Logging::error(const std::string& name)
{
return getLogger(name).attr("error");
}

boost::python::object Logging::critical(const std::string& name)
{
return getLogger(name).attr("critical");
}

boost::python::object Logging::log(const std::string& name)
{
return getLogger(name).attr("log");
}
Использовать этот код очень просто:
Logging::getLogger("SomeLogger").attr("debug")("1.Logging stuff %s %s\n","1","2");
Logging::info("SomeLogger")("2.Logging stuff %s %s\n","1","2");
Logging::debug("SomeLogger2")("3.Logging stuff %s %s\n","1","2");
Logging::warning("SomeLogger")("4.Logging stuff %s %s\n","1","2");
Logging::error("SomeLogger2")("5.Logging stuff %s %s\n","1","2");
Logging::critical("SomeLogger2")("6.Logging stuff %s %s\n","1","2");

Спасибо за внимание! Буду рад любым исправлениям, корректировкам и дополнениям.

Tuesday, July 29, 2008

Совместное редактирование исходных текстов

Многие знают про технику парного программирования: это когда два программиста работают за одним компьютером, причем один набирает текст, а второй следит за ошибками и поправляет, периодически они меняются. Основной плюс данного подхода - в общении между членами команды (перечислять все преимущества не буду, читайте тут). С другой стороны, при таком подходе частенько хочется отобрать клавиатуру и написать быстренько самому, чем словами объяснять коллеге, что вы хотите.
Решений этой проблемы несколько. Некоторые стараются привнести идею парного программирования в обычную командную разработку, настаивая на регулярных мини-миттингах и "небольших" коммитах. Нельзя сказать, что этот способ плох, но все же он предполагает разделение задач между участниками команды.
В этой статье я хочу описать опыт использования инструмента для совместного редактирования кода Gobby - как по мне, это практически идеальное решение для парного программирования. Возможно, для кого-то из читателей это будет в новинку.
Итак, что такое Gobby - это бесплатный инструмент для совместного редактирования текста, который позволяет работать с несколькими документами одновременно, в котором есть чат и который работает во всех основных операционных системах (Linux, MacOS X, Windows, *nix). В действительности, Gobby - это просто редактор, который поддерживает технику парного(и более) программирования - только теперь вместо одной клавиатуры на всех у каждого программиста есть своя собственная.
Собственно об опыте использования: это невероятно! :) По пунктам:

  1. Еесли твой коллега находится далеко, то не нужно постоянно переключаться из окна IM мессенджера в окно редактора - все видно тут же.
  2. Смотреть, как появляется из ниоткуда исходный текст - это невероятно интересно!
  3. Очень удобно, что можно одновременно проверять работоспособность кода на разных машинах.
  4. Программистов может быть и не два, а больше, при этом так как вклад каждого отображается цветом, не запутаешься (в отличие от систем контроля версий, в которых выяснить, чья же это строчка кода, бывает трудно)
  5. Довольно удобный редактор с подсветкой синтаксиса без лишней функциональности
Из небольших неудобств - пока не нашел, можно ли поставить отображение курсора коллеги. Также стоит с небольшой грустью отметить, что под Windows есть некоторые проблемы с инсталляцией. Возможно, это единичный случай, но все же. Под Gentoo и Ubuntu все работает прекрасно, но так как множество разработчиков все же работает в Windows (по желанию или против оного :) ), то это может стать проблемой.
Скриншоты можно посмотреть здесь: http://gobby.0x539.de/trac/wiki/Screenshots
Чего не хватает: думаю, весьма полезен был бы централизованный сервер, на котором можно было бы любым желающим создавать сессии.
Аналоги: я не задавался целью найти что-нибудь аналогичное Gobby. Насколько мне известно, Eclipse поддерживает совместную работу. За любые указания на другие аналоги буду благодарен.

Friday, June 20, 2008

Использование Google Search API и Google Translate API в Python

Как известно, Google предоставляет множество различных API для доступа к своим сервисам. Список и описание их можно найти здесь. Я же хочу остановиться на двух весьма удобных сервисах от Google - поиске и переводе. Точнее, хочется предложить простенький рецепт по использованию предоставляемых для них API из Python.

Google Search API и Google Translate API представляют собой AJAX API: вызовы функций - это HTTP запросы, ответ возвращается в формате JSON. В связи с последним нам понадобится библиотека simplejson для парсинга ответов. Итак, начнем с перевода.

def translate(text,langpair):
import urllib
import simplejson

query = urllib.urlencode({'q' : text.encode("utf-8"),'langpair':langpair})
url = u'http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&%s'.encode("utf-8") \
% (query)
search_results = urllib.urlopen(url)
json = simplejson.loads(search_results.read())
mess = json['responseData']['translatedText']
return mess

translated = translate("I am feeling lucky","en|ru")
print "Translated:", translated
Не сложнее и API для поиска:
def search(text):
import urllib
import simplejson

query = urllib.urlencode({'q' : text.encode("utf-8")})
url = u'http://ajax.googleapis.com/ajax/services/search/web?v=1.0&%s'.encode("utf-8") \
% (query)
search_results = urllib.urlopen(url)
json = simplejson.loads(search_results.read())
results = json['responseData']['results']
return results

results = search("text to search")
title = results[0]['title']
noh_title = title.replace('', '').replace('', '')
url = results[0]['url']
print noh_title+": "+url # Мне повезет! :)
Для работы API не требуется регистрации, что весьма приятно. Удачного применения!

Monday, June 9, 2008

Апплет для Gnome на Python

Постановка задачи.

Как и многим другим разработчикам, мне приходится контролировать затраченное на работу время - для написания тайм-репортов и даже просто самоконтроля. Конечно, можно было бы использовать готовые приложения, например Gnome Time Tracker, но у всех подобных программ есть один существенный для меня недостаток - они требуют к себе слишком много внимания. К тому же, на моих предпочтениях сказывается и специфика работы - я работаю в офисе на постоянной основе, а отчеты составляю лишь к концу месяца, при этом особая детализация в этих отчетах не требуется - нужно лишь предоставить временные интервалы. Т.о., мне нужно небольшое приложение, при помощи которого я смог бы легко записывать время прихода и ухода. Задача действительно тривиальна, написание подобной утилитки под силу любому школьнику.
Однако, чтобы было интересней, я решил оформить решение в виде апплета для панели Gnome. Так приложение будет всегда на виду и легко доступно, а при желании я смогу и расширить его функциональность. Задача немного усложняется тем, что из тех мануалов о написании апплетов, что я нашел, все были устаревшими, так что многое пришлось подбирать методом проб и ошибок.
Но не будем слишком углубляться. Для начала, нам понадобится подобный .server файл, описывающий фабрику(TimeTrackApplet_Factory.server):
<oaf_info>
<oaf_server iid="OAFIID:TimeTrackApplet_Factory" type="exe"
location="/usr/lib/gnome-panel/timetrackapplet">

<oaf_attribute name="repo_ids" type="stringv">
<item value="IDL:Bonobo/GenericFactory:1.0"/>
<item value="IDL:Bonobo/Unknown:1.0"/>
</oaf_attribute>
<oaf_attribute name="name" type="string" value="Time Track Applet Factory"/>
<oaf_attribute name="description" type="string" value="Factory to create the time track applet"/>
</oaf_server>

<oaf_server iid="OAFIID:TimeTrackApplet" type="factory"
location="OAFIID:TimeTrackApplet_Factory">

<oaf_attribute name="repo_ids" type="stringv">
<item value="IDL:GNOME/Vertigo/PanelAppletShell:1.0"/>
<item value="IDL:Bonobo/Control:1.0"/>
<item value="IDL:Bonobo/Unknown:1.0"/>
</oaf_attribute>
<oaf_attribute name="name" type="string" value="Time Track Applet"/>
<oaf_attribute name="description" type="string" value="TimeTrack applet"/>
<oaf_attribute name="panel:category" type="string" value="Amusements"/>
<oaf_attribute name="panel:icon" type="string" value="myicon.png"/>
</oaf_server>
</oaf_info>
Содержимое файла говорит само за себя. Если кратко - здесь содержится описание нашего апплета и всего, что нужно для его запуска. Стоит отметить, что название фабрики - TimeTrackApplet_Factory должно совпадать с именем файла. Также стоит запомнить путь /usr/lib/gnome-panel/timetrackapplet - это место, где будет размещаться исполняемый файл апплета. Естественно, при желании можно менять любые описания, названия и иконки.
Обычно этот .server файл помещается в каталог /usr/lib/bonobo/servers/. После того, как мы создали и поместили этот файл в нужное место, перейдем к самому апплету. Ниже я приведу полный исходный код, благо он не очень велик:
#! /usr/bin/env python
# -*- coding: UTF-8 -*-

import pygtk
pygtk.require('2.0')
import gtk
import gobject
import gnome
import gnomeapplet

import logging
from datetime import datetime

logging.basicConfig(filename="path/to/applet.log") # я решил, что простого сохранения в лог мне хватит :)
log = logging.getLogger("TimeTrack")
log.setLevel(logging.INFO)

class TimeTrackApplet(gnomeapplet.Applet):# основной класс
def update_tooltip(self, text = None):
if text is None:
self.tooltip.set_tip(self.applet, self.tooltip_text)
else:
self.tooltip_text = text
self.update_tooltip()

def update_text(self,text=None):
if text is None:
self.temp.set_text(self.data)
else:
self.data = text
self.update_text()

def create_applet(self): # создание апплета, самая смыслонагруженная часть
app_window = self.applet
event_box = gtk.EventBox()
event_box.set_events(gtk.gdk.BUTTON_PRESS_MASK |
gtk.gdk.POINTER_MOTION_MASK |
gtk.gdk.POINTER_MOTION_HINT_MASK |
gtk.gdk.CONFIGURE )

self.temp = gtk.Label()
self.data = "TT" # я не стал искать иконку и ограничился надписью TT
self.update_text()

self.inside_applet = gtk.HBox()
self.inside_applet.pack_start(self.temp)

self.tooltip = gtk.Tooltips() # в качестве демонстрации, создадим также всплывающую подсказку
self.tooltip_text = "Middle click for tooltip update"
self.update_tooltip()

event_box.add(self.inside_applet)
app_window.add(event_box)
app_window.show_all()
return event_box

def button_pressed(self, widget, event, *args, **kwargs):
if event.button==2:# средняя кнопка мыши, обновить подсказку
self.update_tooltip(datetime.utcnow().strftime("%Y/%m/%d %H:%M:%S"))
elif event.button==1:# левая кнопка мыши, записать в лог
log.info("Button Pressed: %s",datetime.utcnow().strftime("%Y/%m/%d %H:%M:%S"))

def __init__(self,applet,iid):
self.__gobject_init__()
self.applet = applet

self.box = self.create_applet()
self.applet.connect("button-press-event",self.button_pressed) # добавляем обработчик события клика
log.info("Applet created succesfully")

gobject.type_register(TimeTrackApplet)

DEBUG = False

def timetrack_applet_factory(applet, iid): # Реализация фабрики
log.info("Creating the applet...")
TimeTrackApplet(applet, iid)
return True

log.info("Starting...")
gnomeapplet.bonobo_factory("OAFIID:TimeTrackApplet_Factory",
TimeTrackApplet.__gtype__,
"TimeTrack",
"0.1",
timetrack_applet_factory) # Запуск всего механизма
log.info("Stopping...")
Данный файл нужно скопировать на то место, которое указано в .server файле. Большая часть кода - gnome-specific, поэтому я постарался объяснить только самые общие моменты(экскурс в gnome+python не является моей целью сейчас) при помощи комментариев.
Итак, для тех, кто код прочитал( :) ), понятно, что по щелчку левой кнопкой мыши дата и время щелчка (в UTC) записываются в лог-файл. Для меня это удобно - в конце месяца я смогу восстановить по этому файлу временные интервалы, а если когда забуду щелкнуть, то вряд ли будет трудно разобраться в этом человеку(это объяснение тому, что я не добавил в утилиту возможность автоматического подсчета интервалов).
Надеюсь, этот простой пример поможет кому-нибудь в написании собственных апплетов. Удачи!

Ссылки по теме:

-http://www.gnome.org/projects/ORBit2/appletstutorial.html (примеры на С)
-http://www.pygtk.org/articles/applets_arturogf/ (устаревший пример на Python)
-http://computertemp.berlios.de/ (готовый апплет, наиболее полезным оказался)
-http://www.pygtk.org/docs/pygtk/ (reference для pygtk)

Monday, June 2, 2008

Небольшое дополнение к отладчику pdb

В последнее время почему-то возникают значительные трудности с написанием каких-либо постов, несмотря на то, что тем, которые можно было бы осветить - множество. Так, я забыл написать об Exception #08, от которого у меня остались неизгладимые впечатления(благо, отчеты с Exception уже есть и я не уверен, что у меня получилось бы лучше). Также весьма жалею о том, что не могу осветить некоторые глубоко интересные мне темы, по большей части связанные с программированием на Python.
Дабы хоть как-то обновить наполнение, решил предложить нижеприведенный простенький рецепт.

Итак, несмотря на то, что существует множество весьма удобных IDE, которые предоставляют графические инструменты для отладки Python приложений, иногда бывает намного удобнее воспользоваться отладчиком pdb - он позволяет вам очень быстро окунуться именно в ту часть кода, которая вам нужна. Как известно, самый простой способ использования - это прописать

import pdb
pdb.set_trace()
в то место, где вы хотите прервать выполнение, а после этого командами step(s) и next(n) продвигаться по коду. Также стоит отметить, что неопознанные команды распознаются как Python-команды, что позволяет выполнять какой-либо проверочный код прямо в ходе отладки. Для того, чтобы не заблудиться в отлаживаемом и проверочном коде часто используется команда list (l), которая показывает исходный код места, в котором сейчас остановилось выполнение. Одним из недостатков данной команды можно считать то, что последовательное выполнение нескольких команд list действительно "листает" код, т.е., если два раза выполнить команду list, то будет сначала показано несколько строчек, обрамляющих текущую строку, а потом - следующие несколько строчек. Такое поведение может очень раздражать, если вы хотите просто вспомнить, а где же вы сейчас находитесь, вместо того, чтобы листать код.
Т.о., появляется желание немного дополнить pdb, чтобы добавить возможность просматривать код без пролистыванья. Благодаря динамичности Python, можно предложить такое решение:
def do_ll(self,arg):
return self.do_list(str(self.curframe.f_lineno))

import pdb

pdb.Pdb.do_ll = do_ll
pdb.set_trace()
Этот код добавляет новую команду ll (LL маленькими буквами), которая пользуется возможностями команды list выводить определенные строки кода. При этом очень важно то, что исходный код pdb не меняется.

Проверено на Python 2.4.4

Wednesday, March 26, 2008

Собственные фильтры в админке django (Custom FilterSpecs)

Обновление от 04.05.2012. Данная статья была написана довольно давно, но до сих пор вызывает некоторый интерес. Спешу сообщить, что с 23-го марта 2012 года с релизом Django 1.4 начинать поиск информации про custom фильтры стоит с официальной документации

Админка django - одна из самых убойных фичей этого фреймворка, как признаются и сами авторы. Она позволяет автоматически подключить к вашему сайту функционал по добавлению, редактированию и изменению как встроенных, так и пользователских моделей. Конечно, если логика добавления и изменения информации слишком сложна, то эта админка не подходит, однако, это не означает, что ее не нужно использовать - она вполне может быть дополнением к существующему функционалу. Представьте, что создавая сайт-блог вы добавили функциональность, которая позволяет быстро создавать и редактировать записи. Но это не мешает вам использовать джанговскую админку, когда вам нужно изменить какие-то параметры записи, которые нельзя изменить иначе(подробней об админке можно прочитать в 6ой главе Djangobook)

Существуют также случаи, когда админка Django предоставляет практически весь функционал, который нужен на сайте. Например, если вам нужно приложение для внутреннего использования, которое показывает сохранённую в базе информацию по каким-нибудь событиям(пример из повседневной жизни :) ), то лучше встроенной админки не найдёшь - можно легко настроить, какие поля показывать, по каким полям вести поиск, по каким фильтровать - и всё это - лишь пара строчек кода!
Фильтры в админке - более чем удобная вещь. Например, если у вас есть поля, которые могут принимать значения из фиксированного набора(успех/не успех, номер ошибки, и т.д.), то фильтры позволяют быстро отфильтровать, например, все "успешные" записи, или все записи с упоминанием ошибки 302. Однако, может случиться, что стандартных наборов фильтров вам не хватит, и вы пожелаете добавить свой собственный.

Я постараюсь показать весь процесс добавления нового фильтра на весьма полезном примере - на фильтре, который позволит фильтровать записи по заданному промежутку времени. Например, если у вас есть поле "Время создания", то можно будет отфильтровать все записи, которые были созданы в промежутке между 25 марта 2008 года 00:00 и 26 марта 2008 года 15:00. Итак, начнём.

Все фильтры, которые есть в админке, расположены в django.contrib.admin.filterspecs, все фильтры являются наследниками FilterSpec, у которого есть два особо интересных метода - create и register. Новый фильтр нужно будет зарегистрировать, вызвав функцию FilterSpec.register(test,factory), где test - это функция, принимающая объект ..Field (фильтры создаются автоматически через интроспекцию существующих полей модели) и возвращающая True, если данный фильтр применим для данного поля, а factory - это обычно класс фильтра. Функция register() просто сохраняет эту пару во внутренний список. Функция create() вызывается, когда для какого-нибудь поля нужно создать фильтр - данная функция просматривает список, запускает test функции, и когда находит подходящий фильтр - использует factory для создания объекта и возвращает его. После объявления класса FilterSpec идут сами фильтры, которые сразу же и регистрируются.
Здесь следует важное замечание: фильтры добавляются в список append'ом, поэтому последний добавленный фильтр и будет при create вызван последним. А так как в уже рассмотренном файлике последним добавляется фильтр, который умеет фильтровать любые поля, что означает, что если мы зарегистрируем новый фильтр, то до него при create просто никогда не дойдёт очередь. Такое поведение мне не очень понятно(я привык, что последние добавленные обработчики обрабатываются первыми:) ), к тому же оно сводит на нет прямолинейные попытки добавить свой собственный фильтр.

Однако есть как минимум два способа обойти это недоразумение:
- Тикет #5883 содержит патч, который меняет порядок добавления. Он очень простой и вполне разумный. Я надеюсь, что когда-нибудь он войдёт в основную ветку :)
- Вместо register() можно вставлять нужную запись напрямую в список: FilterSpec.filter_specs.insert(-1, (test, factory))
Что использовать - ваш выбор. Я для себя выбрал путь патча, ибо хоть второй и не требует изменения кода django, однако он противоречит дзену :)

Разобравшись в том, как регистрировать фильтр, перейдём к тому, как он функционирует. Все фильтры рендерятся через шаблон filters.html (django/contrib/admin/templates/admin/filters.html). Для того, чтобы фильтр нормально отобразился, он должен уметь возвращать список опций выбора(choices), которые и будут отображены автоматически. Естественно, это не подходит для нашей цели(как вы помните, мы пытаемся сделать фильтрование по промежутку времени, для задания этого промежутка нам понадобится два текстовых поля), поэтому мы поменяем поведение шаблона(как подменить шаблон админки на свой можно прочитать всё в той же 6ой главе Djangobook). Текст шаблона filters.html ниже:

{% load admin_list %}
{% load i18n %}
{% load filter_tags %}
{% if cl.has_filters %}<div id="changelist-filter">
<h2>{% trans 'Filter' %} </h2>
{% for spec in cl.filter_specs %}
{% if spec.is_datetime_interval_filter %}
{% datetime_interval_filter cl spec %}
{% else%}
{% filter cl spec %}
{% endif %}
{% endfor %}</div>{% endif %}
Хочется обратить внимания на отличия от стандартного файла - подгружаются filter_tags - именно в нём у нас будет находиться inclusion tag datetime_interval_filter, который будет использоваться для всех фильтров, для которых is_datetime_interval_filter есть True(т.е., для нашего фильтра). Теперь посмотрим на новый тэг:
from django import template
register = template.Library()

@register.inclusion_tag('datetime_interval_filter.html')
def datetime_interval_filter(cl, spec):
return spec.get_output_dict(cl)
Ничего интересного в нём нет - он просто возвращает нужный для рендеринга контекст, оставляя процесс генерации его нашему объекту-наследнику FilterSpec(схожим образом действует и стандартный тэг filter). Как видно, он использует темплейт datetime_interval_filter.html - его текст ниже:
<script language='javascript'>
function set_new_location_{{field_name}}()
{
var lte = document.getElementById("{{field_name}}_lte_edit").value;
var gte = document.getElementById("{{field_name}}_gte_edit").value;
document.location.href = '{{query_str}}&{{field_name}}__gte='+gte+'&{{field_name}}__lte='+lte;
}
</script>
<h3>By {{field_title|escape}}:</h3>
<ul>
<li><input id="{{field_name}}_gte_edit" value="{{gte_old_value}}" /></li>
<li><input id="{{field_name}}_lte_edit" value="{{lte_old_value}}" /></li>
<li><a onClick='javascript:set_new_location_{{field_name}}();' style="cursor:pointer;">Filter</a></li>
</ul>
Что есть в темплейте - для каждого нашего фильтра создаётся небольшой скрипт, который займётся обработкой переходов на новую страницу с отфильтрованными данными. Также выводятся два текстовых поля и ссылка для перехода. Для того, чтобы корректно отрендерить этот темплейт, нужно передать:

field_name - имя поля, по которому будем фильтровать.
query_str - строка запроса, которая может содержать и другие значения фильтров.
field_title - название поля.
gte_old_value/lte_old_value - сохранённые значения текстовых полей. Выводятся в те же поля после применения фильтра, что очень удобно для редактирования.

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

Ну, и наконец, сам объект-фильтр:
from django.contrib.admin.filterspecs import FilterSpec
from django.utils.encoding import iri_to_uri

class DateFieldIntervalFilterSpec(FilterSpec):
def __init__(self, f, request, params, model):
super(DateFieldIntervalFilterSpec, self).__init__(f, request, params, model)

is_datetime_interval_filter = True

def get_output_dict(self,cl):
p = cl.params.copy()
return {
'field_name': self.field.name,
'query_str': iri_to_uri(cl.get_query_string(remove=["%s__lte"%self.field.name,"%s__gte"%self.field.name])),
'gte_old_value': p.get('%s__gte'%self.field.name,''),
'lte_old_value': p.get('%s__lte'%self.field.name,''),
'field_title':self.field.verbose_name
}
Небольшие пояснения по коду - как я уже говорил выше - мы наследуемся от FilterSpec. Конструктор не представляет никакого интереса. Флажок is_datetime_interval_filter мы уже упоминали - когда рассматривали темплейт filters.html. Единственная функция, которая требует рассмотрения - это get_output_dict(), которая возвращает словарь, используемый для рендеринга конечного html кода. field_name и field_title не представляют особого интереса - мы просто возвращаем имена поля, для которого работает фильтр. query_str мы получаем из объекта ChangeList - это основной объект, который содержит все фильтры, а также предоставляет доступ к нужным этим фильтрам параметрам(найти его можно в django.contrib.admin.views.main). В качестве параметров функции мы передаём список remove - в нём находятся те значения, которые возвращать не нужно, даже если они указаны. Это позволит нам избежать повторений вида &time__lte=smth1&time__lte=smth2. gte_old_value/lte_old_value мы получаем также из ChangeList - из текущих параметров.

После этого осталось зарегистрировать новый шаблон(для тестирования это можно сделать даже в Urlconf, хотя это и может вызвать некоторые проблемы при автоматической перезагрузке девелопмент сервера):
FilterSpec.register(lambda f: isinstance(f, models.DateField),DateFieldIntervalFilterSpec)
Хочу заметить, что я подменяю все DateTimeField(мне откровенно не нравится стандартный фильтр для DateTime), в то время, как можно добавить проверку на наличие у поля дополнительного атрибута, который добавлять только тем полям в модели, для которых нужен такой фильтр.

Ну, и конечно, хотелось бы посмотреть на результат. Вот скриншшот:На скриншоте - сохраненные Cdr events от Asterisk. Если как-нибудь будет время - я постараюсь осветить тот небольшой код, который позволяет сохранять евенты от Астериска в базе - это потрясающая иллюстрация возможностей Python, Django и Twisted.

Буду рад любым фидбекам и исправлениям. :)

Friday, March 21, 2008

Курс «Как стать успешным фрилансером и остаться человеком»

[info]4annel в своем ЖЖ публикует курс статей "Как стать успешным фрилансером и остаться человеком"

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

P.S. Не могу не поделиться - впервые обратил на этот проект чуть больше внимания: Twisted . Посмотрите и вы - это потрясающий фреймворк!

Thursday, March 13, 2008

Прелести GIT и бэкап SVN репозитория

Натолкнувшись на неведомый доселе GIT сначала здесь, а потом здесь, я, несмотря на большую приверженность SVN, решил опробовать его при случае. Мне хотелось попробовать, как он справится с задачами, приближенными к боевым.
Также не так давно я столкнулся с небольшой проблемой, которую хотелось бы решить. Дело в том, что для одного достаточно объемного проекта(более 9 месяцев труда 5-ти программистов, 2100 ревизий в SVN репозитории) захотелось сделать бэкап. Я вообще трудно представляю себе жизнь без системы контроля версий, которая предоставляет возможность отката, бранчевания и т.д. Именно поэтому простой бэкап HEAD ревизии меня абсолютно не устраивал. Проблема усложнялась тем, что администраторского доступа к репозиторию не было - нужно было думать, как ограничиться стандартным клиентским.
Насколько я знаком с возможностями SVN, клонирование репозитория клиентом в список этих возможностей не входит. Конечно, можно написать простенький скрипт, который будет поревизионно апдейтить код, а после этого коммитить его куда-нибудь в локальный репозиторий, однако не факт, что такое решение будет быстрым и качественным. В сочетании с нехваткой времени и необязательностью подобного бэкапа это привело к тому, что скрипт я так и не написал.
Однако, когда дело дошло до тестирования возможностей GIT, я решил убить сразу двух зайцев - сделать бэкап, а также проверить насколько быстр, удобен и неприхотлив к ОЗУ/ПЗУ вышеназванный GIT, благо он умеет клонировать SVN репозитории. Так что я начал с того, что поставил себе GIT. Все указания по установке можно найти здесь. Для теста поставил на Windows XP SP2( повременю пока с установкой на Ubuntu - всё равно я уверен, что проявит GIT и там себя не менее достойно ). Установочные файлы брал отсюда. Итак, после установки я запустил команду

git-svn clone http://host/path
дабы сбэкапить нужную мне ветку(975 коммитов) и стал ждать, пока он закончит работу. Сразу скажу, что длилось это довольно долго - после 4-х часов работы он сделал только чуть больше половины. Сколько времени это заняло в целом я даже и не знаю - я не выдержал и оставил уомпьютер трудиться на ночь.
На утро можно оценить результаты. Данные из выбранной ветки занимают около 25-ти Мб. GIT в отличие от SVN не плодит множество папочек во всех каталогах, а создает локальный репозиторий, в котором и хранит все данные. Т.о., сами данные хранятся в чистом виде, тогда как в SVN дополнительные папки увеличивают объем до 51-го Мб. Теперь обратим внимание на то, сколько занимает локальный репозиторий GIT, ведь цифра действительно поражает - всего 17 Мб - а ведь здесь хранятся все изменения, которые велись в ветке с начала проекта! Т.о., данные вместе с репозиторием GIT занимают только 42 метра. Конечно, в наше время объемы винчестеров позволяют не обращать внимание на такие мелочи (к тому же, после компиляции 25 Мб всё равно превращаются в 2 Гб:) ), однако всё же приятно...
Идём далее. Оценим грубо скорость чекаута. Для SVN эта величина - 35 секунд(стоит всё же учесть, что checkout при тестировании вёлся по локальной сети). С помощью же GIT чекаут из локального репозитория был сделан за 4 секунды! Это при том, что простое копирование этих же данных идёт около 7 секунд. Впечатляет!

Вывод: GIT - это нечто новое и весьма интересное, на что стоит обратить внимание при выборе системы контроля версий для проектов. Конечно, он не так удобен и привычен(субъективное мнение) как SVN, однако это всё дело времени - ведь GIT это не просто version control system, это инфраструктура для создания идеальной такой системы.
Редакция от 27.08.08. Тем, кому нужен простой бэкап svn репозитория - могу посоветовать svnsync

Tuesday, February 26, 2008

Django urlpatterns для hostname

Я не буду сейчас детально рассказывать, что такое urlpatterns в django, понадеясь на осведомлённость возможных читателей. Однако для тех, кому это внове, дам краткий пример. В Django для сопоставления url`ов коду используются конфигурационные файлы urls.py (Urlconf). Основной смысл этих файлов в подобных строчках:

(r'login/$', 'someapp.views.login')
Данная строка означает, что когда пользователь зайдёт на страницу www.example.com/login/ , то для генерации страницы вызовется функция someapp.views.login . В простейшем случаем первым параметром такой строки является регулярка, вторым - нужная функция. Заметьте, что регулярка сопоставляется только с путём, а хостнейм отбрасывается. Сами urlconf весьма удобны, но такое игнорирование хостнейма не даёт использовать стандартные джанговские подходы для реализации подобных вещей:
  • Вызов различных функций в зависимости от части хостнейма, например, pda.example.com - PDA версия сайта, blog.example.com - блог на сайте. Конечно, это можно реализовать и другими средствами, однако создание разветвлённой структуры всё же затруднено.

  • Передача параметров в функции в зависимости от хостнейма. Например, tilarids.blogspot.com - на самом деле, все такие страницы могут генерироваться одной функцией в зависимости от имени пользователя
Т.о., возникает желание получить механизм, который бы работал аналогично urlconf, но вместо пути работал бы с хостнеймом. После небольшого исследования на эту тему у меня создалось впечатление, что это так нигде и не реализовано. Отсюда и родился подобный код:
class HostnameDispatcher(object):
def __init__(self, view, myregex):
self.regex = re.compile(myregex, re.UNICODE)
self.view_func = view
def __call__(self, request, *args, **kwargs):
current_site = RequestSite(request)
match = self.regex.search(current_site.domain)
print "Current args:",args, kwargs
if match:
new_kwargs = match.groupdict()
if new_kwargs:
new_args = args
else:
new_args = match.groups() + args
new_kwargs.update(kwargs);
return self.view_func(request,*new_args,**new_kwargs)

class HostnameRegexPattern(RegexURLPattern):
def __init__(self, regex, callback, hostname_re,proto=u'http://',default_args=None, name=None):
RegexURLPattern.__init__(self,regex,callback,default_args, name)
self.hostname_regex = hostname_re
self.dispatcher = HostnameDispatcher(self.callback, self.hostname_regex)
self.old_regex = self.regex
self.regex = re.compile(u'\b'+proto+hostname_re+u'/'+regex,re.UNICODE)

def resolve(self, path):
match = self.old_regex.search(path)
if match:
kwargs = match.groupdict()
if kwargs:
args = ()
else:
args = match.groups()
kwargs.update(self.default_args)
return self.dispatcher, args, kwargs

Хочется заметить, что основными требованиями к коду были:
  • Полная совместимость со стандартными способами resolve и reverse (получения функции по адресу и адреса по функции соответственно)
  • Безболезненное встраивание в рабочую систему Django
Возможно, код несколько сумбурный - несмотря на недолгую жизнь, он успел почувствовать на себе процесс развития и переделывания. Постараюсь объяснить, что здесь зачем. Итак:
  • HostnameDispatcher - класс, который позволяет resolv`ить функцию по хостнейму, являсь некой надстройкой над этими самими функциями(вернёмся к терминологии django и будем называть их view). Например,
    disp = HostnameDispatcher(my_view,r'^(.*)$')
    создаёт на основе существующего view новый объект, который при резолве добавляет параметры из хостнейма. Удобно, быстро, но никак не поможет при reverse. А вот reverse - это как раз самая сложная, но и не менее нужная часть
  • HostnameRegexPattern - это замена стандартным урлпаттернам. resolve здесь реализуется через уже упомянутый HostnameDispatcher. А вот с reverse пришлось извратиться. В django нет хорошей возможности изменять стандартное поведение reverse. Т.о., я просто изменил существующую регулярку, и в итоге получил почти правильный результат. Единственное, выскакивает '/' в начале. Для его устранения я не придумал ничего лучше, чем сделать небольшой патч для django(в django.core.urlresolvers):
    def backspace_process(func):
    def reverse_new(viewname, urlconf=None, args=None, kwargs=None):
    ret_val = func(viewname, urlconf, args, kwargs)
    if ret_val[:4]=='/%08':
    return ret_val[4:]
    return ret_val
    return reverse_new

    def reverse(viewname, urlconf=None, args=None, kwargs=None):
    args = args or []
    kwargs = kwargs or {}
    return iri_to_uri(u'/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs))

    reverse = backspace_process(reverse)
    Т.е., добавляется в начале отдельный символ(\b), который потом удаляется при надобности. Такое удаление не должно никак повлиять на другие urlpatterns
Ну и наконец: как это использовать. Нет ничего проще- записываем в urls.py в паттерны такую строчку:
HostnameRegexPattern('test/(\d+)/$', 'blog.views.test',r'^(.*)$'),
, где первый параметр - регулярка для пути, второй - функция, третий - регулярка для хостнейма. Всё! :)

Кто может предложить что-нибудь более интересное, или улучшить уже существующий код - буду весьма благодарен.

Tuesday, February 12, 2008

Как посчитать очень большой факториал?

Не так давно обратил внимание на то, как быстро Maple считает факториалы(встроенный функционал). 500000! - огромное число - он посчитал за пару секунд. Стало интересно - как же это реализовано? В итоге было написано несколько тестовых кусочков кода на ЯП, которые поддерживают большие числа. Первым таким языком был Erlang. Код весьма прост:
[UPD]
fact3(X)->fact3(X,1).

fact3(0,Acc)->Acc;
fact3(X,Acc)->fact3(X-1,Acc*X).
[/UPD]
Erlang соптимизирует рекурсию в цикл и в итоге этот код будет достаточно оптимальным. [UPD]На сравнительно небольших числах он работает отменно. Например, 50000! он посчитал за 6 секунд(особой точности здесь не требуется, поэтому я не старался получить максимально точные данные или тестировать в абсолютно чистой среде). Но 100000! у меня он вообще считать отказался - упал по нехватке памяти.[/UPD]
Вторым языком, который был опробован, стал PHP. Наивный код на PHP на больших числах не стесняясь выдавал INF, но тов. Josser(не знаю, куда поставить ссылку) довёл код до ума:

$targ = 50000;
$res = 1;

$startime = time();
for ($i=1; $i<=$targ; $i++) {
echo $i."\r\n";
$res = bcmul($res, $i);
}
$endtime = time();

echo $res."\r\n";

echo $endtime-$startime."\r\n";

Также Josser и протестировал его:
30000! - 99 секунды
50000! - 242 секунды

Как видим, работает оно медленней, чем вариант на Erlang, но хотя бы работает на 50000!.
Следующим языком стал Python. Код также прост, как и в предыдущих случаях:


def fact(x):
s=1
for i in range(2,x+1):
s*=i
return s

Протестировав, получаем:
30000! - 1.64100003242
50000! - 7.54700016975
100000! - 40.75

Как видим, Python смотрится просто героем на фоне Erlang и PHP. Однако, с увеличением чисел понимаем, что подобраться к заветному 500000! таким образом не получится.
Следующим протестированным языком стал Haskell. Я не особо знаком с этим языком, поэтому не могу похвастать и абсолютно правильным кодом. Главное - работает:

import Text.Printf
import Control.Exception
import System.CPUTime

time :: IO t -> IO t
time a = do
start <- getCPUTime
v <- a
end <- getCPUTime
let diff = (fromIntegral (end - start)) / (10^12)
printf "Computation time: %0.3f sec\n" (diff :: Double)
return v

fac n = if n == 0 then 1 else n * fac (n-1)

main = do
putStrLn "Starting..."
time $ fac 100000 `seq` return ()
putStrLn "Done."

Результаты тестирования:
30000! - 4.828 sec
50000! - 14.531 sec
100000! - 70.625 sec
Получше, чем Erlang и PHP, но хуже, чем Python. Также я вспомнил свои познания из области математики и попробовал организовать вычисление факториала через гамма-функцию. Код для вычисления логарифма гамма-функции:

cof :: [Double]
cof = [76.18009172947146,-86.50532032941677,24.01409824083091,-1.231739572450155,0.001208650973866179,-0.000005395239384953]

ser :: Double
ser = 1.000000000190015

gammaln :: Double -> Double
gammaln xx = let tmp' = (xx+5.5) - (xx+0.5)*log(xx+5.5)
ser' = foldl (+) ser $ map (\(y,c) -> c/(xx+y)) $ zip [1..] cof
in -tmp' + log(2.5066282746310005 * ser' / xx) where

Используется потом так:
exp (gammaln 20001)
Так считается 20000! Но, к сожалению, здесь Haskell прямо ответил - Infinity.

В итоге, после того, как мы опробовали кучу языков программирования, можно сделать вывод, что ни один из них изначально не подходит для решения заветной задачи - нахождения 500000!. Но раз стандартные средства не работают, время поискать нестандартное решение. И оно сразу находится - библиотека GNU MP или GMP. Кому хочется посмотреть, на что способна эта библиотека - попробуйте здесь. Также можно попробовать скачать и установить - даже без тонкой настройки всё равно получаем очень неплохие результаты.
Ну, и финальный аккорд - нахождение искомого 500000! при помощи GNU MP:

The result of executing 500000! is:

computation took 1418 ms
result is about 2632341 digits, not printing it


Итоговый результат: хотите посчитать очень большой факториал? Используйте GNU MP![UPD](который и используется в Хаскеле(?) )[/UPD]

[UPD]
Спасибо lionet, который заметил чушь, которую я назвал хвостовой рекурсией раньше, чем я ее исправил. Также спасибо yorool-gui за развитие Хаскелевского варианта. Я не буду исправлять текущий код - вы можете посмотреть на более оптимальные варианты решения задачи здесь.
[/UPD][UPD]
Кстати, в GMP используется функция mpz_fac_ui для того, чтобы подсчитать факториал. Т.е., это отдельная функция. И именно она даёт такие хорошие результаты. Тогда становится понятным, почему Haskell c GMP не даёт таких результатов, как просто использование данной функции.[/UPD]

Thursday, January 31, 2008

Creative Commons лицензия

Creative Commons - это некоммерческая организация, которая ставит для себя цель предоставить писателям, музыкантам, художникам и т.д. простую возможность управлять правами копирования на свои произведения. Точнее, они предоставляют возможность поделиться на бесплатной основе своими творениями, при этом оберегая от нежелательного использования. Основная идея: CC(общепринятая аббревиатура) поможет вам перейти от "All rights reserved" к "Some rights reserved".
Как не утопично это звучит в нашем мире, где правит стремление к выгоде, и как не сложно в это поверить, но лицензирование по схеме CC получило достаточное распространение, особенно в творческой среде. Феномен этот сродни феномену Open Source Community, однако если участники Open Source движения часто пишут для себя, то CC предназначена для тех, кто не может удержать креативные порывы своей натуры и хочеть поделиться своими шедеврами с миром.
Но, возможно, основная цель CC - это воспитание уважения к чужому труду. Вот такой парадокс - множественные законы о копирайте, многочисленные судебные процессы не вызывают ничего, кроме раздражения, а возможность насладиться творениями других людей абсолютно бесплатно и легально действует как ушат холодной воды. И когда слушаешь какое-нибудь музыкальное произведение, то кроме наслаждения им самим, начинаешь испытывать что-то абсолютно новое - возможно, восхищение людьми, которые это создали.

Для тех, кто хочет окунуться в это прекрасное состояние, рекомендую сайт http://www.jamendo.com/
На этом сайте вы сможете:
- Прослушать и скачать множество альбомов самых различных музыкальных направлений. Удобно, качественно и при этом абсолютно бесплатно!
- Писать и читать рецензии на альбомы
- Пообщаться в сообществе людей, объединённых верой в свободное творчество
При желании также можно сделать пожертвование понравившемуся исполнителю для его поддержки.
В общем, "...не стесняйтесь, заходите на Jamendo и помогайте нам нести в массы бесплатную музыку!"(c)

Thursday, January 24, 2008

Квест "Покупка prepaid карточки Utel в Харькове"

Для тех, кто еще не знает, с недавнего времени(а точнее, с 1-го ноября 2007-го года) компания Utel начала продажу услуг мобильной связи поколения 3G (UMTS/HSDPA). Это не первая 3G сеть в Украине, но всё же она в новинку для украинского рынка - до этого услуги предоставлялись в основном по протоколу CDMA 2000 (тот же PEOPLEnet). Дабы не развивть тему сравнения технологий CDMA 2000 и UMTS предложу лишь обратить внимание на количество устройств, поддерживающих ту или иную технологию. Для меня решающим был как раз тот факт, что UMTS поддерживают большее количество телефонов.
Итак, начнём наш квест. Если вы решитесь его повторить, то о всём, что вам будет нужно в процессе выполнения, вы узнаете из дальнейшего текста. Единственное, что вам нужно изначально - это желание и понимание того, что вы делаете.

Приобрести карточку Utel нельзя в первом попавшемся магазине, как это бывает со стартовыми пакетами украинских GSM операторов. Для того, чтобы приобрести ее, прийдётся отправиться на пл. Конституции(здесь и далее я предполагаю, что читатель знаком с городом Харьковом). Напоминаем себе адрес - пер. Армянский, 1/3. Работают с 8ми и до... К сожалению, этого я не запомнил, но данные всегда можно уточнить по телефону 8-800-500-1188.
Итак, добираемся до пл. Конституции(ст. м. Советская) любыми средствами. Спускаемся до магазина "Мелодия"(вниз, мимо "Детского мира"). И смотрим налево, в тщетной надежде увидеть вывеску Utel. На сегодняшний день ее там нет, и, возможно, очень долго не будет. Горе тому, кто не знает, что Utel скрывается там, где написано "Урктелеком" - ведь найти Армянский переулок достаточно сложно(существует подозрение, что это не переулок, а дом), а даже обнаружив, можно долго ходить вокруг здания, ничего не понимая. Но довольно растеканий - проходим от магазина "Мелодия" в сторону длинного Московского проспекта немного - и заходим в дверь, над которой написано "Укртелеком".
Не думайте, что квест на этом заканчивается. Скорее всего, наличные деньги у вас не примут, но выдадут счёт, который будет нужно оплатить в отделении "Приват-банка". Ближайшее отделение - внутри магазина "Мобилочка" на пл. Розы Люксембург. Выходим из отделения "Укртелекома" и движемся в сторону ЦУМа и площади Розы. Магазин отыскать не сложно, поэтому остановимся лишь на моменте оплаты. Стоит карточка 100 грн(из них 50 грн - на счёт) - со всеми пенсийными и прочими сборами. Или не со всеми, как неожиданно решило руководство "Приват-банка". Вполне возможно, что вам прийдётся заплатить также и комиссию в размере 20 грн. А возможно и нет - при первом прохождении квеста кстати подвернувшийся сотрудник Utel'а быстро решил все недоразумения с комиссией и отправил назад получать карточку.
Возвращаемся и получаем заветную симку. Здесь же на месте можно попросить перевести на другой тариф(если вас не устраивает изначальный U'нион). Происходит это следующим образом - один тариф "выключается", второй "включается", и через 20-30 минут уже можно пользоваться новым тарифом.

Я использовал карточку в WM коммуникаторе Toshiba G900. Нашёл сеть он быстро и бзе проблем, а вот с настройками инета всё было куда более странно. Но это - уже другой квест, который был успешно пройдён(и, возможно, будет освещён в другой статье). В итоге - получаем неплохую связь, приятную скорость data connection'ов, видео звонки и прочие вкусности. Ведь это именно то, чего мы хотели, начиная этот квест?

Wednesday, January 23, 2008

Получение IPv6 адреса - краткое руководство

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

Как можно прочесть в Википедии:

IPv6 — это новая версия протокола IP, призванная решить проблемы, с которыми столкнулась предыдущая версия (IPv4) при её использовании в Интернете
С полным перечнем недостатков IP можно ознакомиться в той же Вики, а я ограничусь напоминанием, как сложно бывает получить реальный IP в наше тяжёлое время. Связано это в первую очередь с малым объёмом 32-битного адресного пространства и банальным недостатком адресов. В том же случае, если вы хотите опробовать IPv6, то вы можете зарегистрировать себе реальный адрес без особых проблем. О том, как это сделать, и пойдёт речь в этой статье.
Данная статья не рассчитана на какую-либо определённую платформу, однако примеры будут приводиться на двух ОС: Windows XP SP2 и Ubuntu 6.06.1 LTS(многократно обновлённой до неузнаваемости). Эти ОС изначально поддерживают IPv6 протокол, но если вы хотите использовать IPv6 на более старых ОС, то это, как правило, не проблема - нужно просто установить обновления(не освещается данным руководством). Для тех, кого не смущает английский язык, могу порекомендовать руководство на http://sixxs.net. Мануал, который вы сейчас читаете является вольным переводом мануала с sixxs.net, дополненным и переработанным.
Итак, начнём. Замечу, что IPv6 адрес будет нами настроен через туннель, который любезно предоставит нам брокер SixXS
1. Для начала, вам понадобится RIPE handle. Если вы не знаете, что это такое - не расстраивайтесь. Вы можете выбрать два пути - или зарегистрировать себе отдельно RIPE handle (довольно простая операция, руководство можно найти здесь) или воспользоваться сервисом прямой регистрации на SixXS.
2. Если у вас уже есть RIPE handle, или вы успешно создали его, вы можете переходить к SixXS Signup. Здесь вас попросят ввести свой RIPE handle и причину, по которой вы желаете зарегистрироваться. Хочется обратить внимание, что SixXS не любит пользователей, которые указали в качестве email адреса адрес, расположенный на бесплатном почтовике. Например, GMail не подходит. Где найти себе "платный" почтовый аккаунт - решать вам.
3. После того, как вы пройдёте шаг SixXS Signup, на указанный email прийдет регистрационное письмо для проверки вашего почтового адреса. Просто перейдите по ссылке, указанной в этом письме. После этого подождите, пока персонал SixXS проверит и подтвердит заявку. Стоит уважать человеческий труд и терпеливо ждать ответа(может прийти как через 15 минут, так и через день).
4. Запросите туннель. (Request a tunnel). После того, как вам будет выслан пароль к вашей учётной записи, зайдите на страницу управления туннелями и закажите туннель. Здесь вам доступно три варианта туннеля: AYIYA, Heartbeat и static. static применим, когда у вас есть статический IPv4 адрес, Heratbeat - когда IPv4 адрес у вас динамический, но вы не находитесь за NAT. Самым безопасным(но наиболее неудобным для серверов SixXS) является AYIYA. Этот вариант работает практически всегда. Также он наиболее часто и используется. После заказа туннеля нужно подождать подтверждения персоналом. Будьте терпеливы!
5. Загрузите приложение AICCU для вашей платформы. Также вам может понадобиться загрузить драйвер для Windows, Mac OS X или NetBSD(на той же странице, что и AICCU, или здесь). Если у вас возникли какие-то проблемы с драйвером, или вы просто хотите получить больше, можно посетить сайт OpenVPN. Ubuntu же, например, из коробки имеет все, что нам нужно для работы AICCU.

  • Для Windows вы получаете небольшое приложение, которое выглядит приблизительно так:


  • Как вы уже наверное, догадались, нужно ввести свой логин и пароль, а в качестве провайдера выбрать SixXS. После этого вам нужно нажать кнопку ОК и выбрать туннель, который вы заказали. Возможно, это будет единственный туннель в списке :) После того, как вы сделаете все это, вы должны увидеть такое информационное окно:
    Кнопочка Enable сделает вас счастливым обладателем реального IPv6 адреса. Обратите внимание, что туннель может быть активирован не сразу после того, как персонал SixXS подтвердит заказ, а в течении часа-двух.
  • Для Ubuntu. Вся процедура намного упрощается тем, что существует уже готовый пакет для установки. Возможно, он потянет за собой какие-то зависимости - однако их разрешение - это удел статьи другой направленности. Также стоит отметить, что все данные, которые в Windows приложении вводятся в GUI окно, нужно ввести еще на этапе инсталляции.
6. Как проверить, что всё это работает? Очень просто - посмотрите в ifconfig (ipconfig в Windows), появился ли у вас интерфейс с IPv6 адресом. Также можно пропинговать этот адрес(ping в Windows и ping6 в Linux). Ну, или зайти на IPv6Calc, чтобы посмотреть, как вас видно из внешнего мира.

Но для Windows пользователей могут возникнуть некоторые затруднения. Например, Firefox для Windows иногда не желает заходить на веб-сайты по IPv6. Также бунтует IExplorer. Хорошая новость для Опероманов - Opera 9.25 прекрасно справляется с IPv6 сайтами. Для Ububntu этот абзац не актуален - всё работает прекрасно и в Firefox тоже. Возможные обходные пути чтобы заставить работать FF в Windows в процессе поиска.

Ну, и последнее, что может быть интересно. SixXS предоставляет простой доступ к IPv4 сайтам - просто добавляйте .sixxs.org к адресу(например, example.com.sixxs.org). Конечно, сейчас сложно поверить, что когда-нибудь это будет единственным путём на сайты, обеспечивающие доступ по устаревшему IPv4 протоколу. Но хочется надеяться, что когда-нибудь это свершится - ведь как удобно было бы иметь свой IP адрес для каждого кремниевого устройства на планете?

Небольшое дополнение для счастливых обладателей статического IPv4 адреса и линуха с поддержкой IPv6, SIT туннелей и установленным пакетом iproute2. Для вас процедура настройки очень упрощается. Достаточно выполнить такие команды:
ip tun add sixxs0 mode sit local _свой_реальный_ipv4_адрес_ remote _ipv4_адрес_который_дали_ 
ip -6 addr add _ipv6_адрес_который_дали_/_маска_ dev sixxs0
ip -6 r add default via _ipv6_шлюз_который_дали

И вуаля - туннель готов. Все нужные параметры можно получить на странице учётной записи. Стоит обратить внимание, что static туннель обязывает вас держать компьютер включенным 24/7 - иначе вас лишат ценных кредитов, которые можно использовать на заказы подсетей и туннелей. Но с другой стороны, если вы будете держать туннель в поднятом состоянии, то кредиты наоборот будут капать. Удачи!
За дополнение спасибо Opium'у.

Friday, January 18, 2008

Django WEB Framework

Не так давно открыл для себя весьма интересный веб-фреймворк для разработки сайтов по концепции MVC - это Django. Радует он хорошо продуманной структурой, документацией, а также выбранным языком программирования - Python обеспечивает ощутимую при разработке мощь. В общем, первое и незабываемое впечатление о Django - этот фреймворк писался для разработчиков, а не для пользователей.

Рекомендую! :)