Ne v kontakte Asocial programmer's blog

Обработка множества исключений: решение

Feature image

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

Решение 1, читерское.

В условии было сказано, что надо обрабатывать исключения в методах init(), run() и shutdown(). И хотя это явно не оговаривалось, по логике функционирования эти методы вызываются подряд. Следовательно, можно сделать так:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php

// Code ...


$api = new ApiDispatcher();
try {
	$api->init();
	$api->run();
	$api->shutdown();
} catch(MyException1 $e) {
	// Handle exception
}
// More exception handling...
catch(Exception $e) {
	// Handle exception
}

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

Достоинства решения:

  • простота;
  • минимум дублирования кода;
  • локальность модификации кода.

Недостатки:

  • жестко привязываемся к сценарию последовательного вызова методов init(), run(), shutdown(), в реальной жизни может потребоваться вызывать их из разных частей приложения в разные моменты времени;
  • метод-обработчик исключений должен очень многое знать обо всех возможных исключениях, при появлении нового исключения придется править метод-обработчик.

Решение 2, логичное.

Следующее решение приходит в голову практически сразу после прочтения условия, и оно действительно хорошо подходит для решения задачи.

Именно его в комментариях предложил Vladimir Rusinov, за что ему спасибо:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/env python

class ApiDispatcher(object):

    def _exception_handler(self, e):
        print str(e)
        print type(e)
        if type(e) == IndexError:
             print "Check your indexes!"
        # logging, error messages, etc...

class MyClass(ApiDispatcher):

    def init(self):
        try:
            # do something
            myfunction()
        except Exception, e:
            self._exception_handler(e)

    def run(self):
        try:
            # do something else
            myfunction()
        except Exception, e:
            self._exception_handler(e)

    def shutdown(self):
        try:
            # finish our work
            myfunction()
        except Exception, e:
            self._exception_handler(e)


def myfunction():
    #a = []
    #return a[42]
    raise IndexError('blah-blah-blah')

if __name__ == '__main__':
    c = MyClass()
    c.init()
    c.run()
    c.shutdown()

Достоинства решения:

  • простота,
  • независимость от мест вызова методов (то есть, их не обязательно вызывать подряд),
  • минимум дублирования кода,
  • локальность правок.

Недостатки решения:

  • метод-обработчик исключений должен очень многое знать обо всех возможных исключениях, при появлении нового исключения придется править метод-обработчик,
  • требуется дополнительная логика для прерывания обработки, если упадет init(), либо проброс исключения дальше.

Решение 3, хитрое.

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php

class ApiException {
	public function init() {
		try {
			throw MyException(); // Just exception without error code and message
		} catch(Exception $e) {
			$this->handleException($e);
		}
	}

	public function run() {
		try {
			$operation_name = 'foo';
			throw AnotherException("Failed to perform $operation_name"); // Custom error message
		} catch(Exception $e) {
			$this->handleException($e);
		}
	}
	public function init() {
		try {
			throw OneMoreException(Errors::ERROR_CODE_DATABASE); // Custom error code
		} catch(Exception $e) {
			$this->handleException($e);
		}
	}

	protected function handleException(Exception $e) {
		$error_code = $e->getCode();
		$error_message = $e->getMessage();
		if(empty($error_message)) {
			$error_message = Errors::getMessageByCode($error_code);
		}

		Logger::get()->log($error_code, $error_message);
		// Do other stuff...
	}
}

class MyException extends Exception {
	protected $code = Errors::ERROR_CODE_MYERROR;
}

class AnotherException extends Exception {
	protected $code = Errors::ERROR_CODE_ANOTHERERROR;
}

class OneMoreException extends Exception {
	protected $code = Errors::ERROR_CODE_ONEERROR;
}

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

В PHP все исключения имеют по умолчанию два поля (есть и другие, но они нас не интересуют): $message = null и $code = 0, сообщение и код ошибки соответственно. Во всех наших исключениях мы переопределяем значение по умолчанию для кода ошибки, чтобы оно соответствовало типу исключения.

Теперь, когда мы создаем исключение, мы можем задать ему сообщение, которое описывает причину его возникновения, и, если надо, можем уточнить и код ошибки. Если же мы сообщение вручную не зададим - оно будет получено через класс Errors. В случае, если мы поймаем “не наше” исключение (типа простого Exception), у него код ошибки будет 0, и класс Errors успешно опишет его как “Unknown exception”.

Достоинства решения:

  • гибкость;
  • простота добавления новых исключений (не надо модифицировать метод-обработчик ошибок),
  • независимость от мест вызова методов (то есть, их не обязательно вызывать подряд),
  • минимум дублирования кода.

Недостатки решения

  • требуется дополнительная логика для прерывания обработки, если упадет init(), либо проброс исключения дальше,
  • нелокальность правок при внедрении решения (надо проставить коды ошибок всем существующим исключениям),
  • менее тривиальное решение,
  • возможны сложности в случае, если есть исключения, которым нельзя переопределить код ошибки.

Итоги

Я привел целых три решения задачи и видно, что ни одно из них не является безоговорочно правильным. А это значит, что даже такая простая задача, как описано в этом упражнении, требует внимательного анализа требований к системе и ваших возможностей, как ее разработчика. В моем случае решение 3 оказалось наименьшим злом, и я использовал его. Но запросто может оказаться, что в другой ситуации оптимально “наивное” решение 1.

Упражнение: обработка множества исключений

Feature image

Хочу предложить программирующей части моей аудитории небольшую задачку, что называется, по мотивам реальных событий. Задачка просто на технику программирования, даже не на алгоритмы, к конкретному языку она, в общем-то, не особенно привязана, лишь бы в нем были предусмотрены исключения.

Есть корневой класс, условно назовем его ApiDispatcher, который управляет основным потоком исполнения. В нем есть три основных метода:

  1. init() — инициализирует, необходимые для обработки запроса объекты.
  2. run() — запускает обработку бизнес-логики.
  3. shutdown() — освободжает ресурсы.

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

Так же будем считать, что нам на любом этапе доступен метод Client::reportError($error_code, $error_message), который обеспечивает вывод клиенту сообщения об ошибке и прекращение работы программы.

Теперь собственно формулировка проблемы: необходимо во всех трех методах обеспечить обработку всех возможных исключений, для каждого из исключений надо выдать код ошибки и ее текстовое описание, а потом вызвать Client::reportError() с соответствующими параметрами. Замечу, что у исключения может быть задан текст описания ошибки, и в таком случае желательно его сохранить, поскольку он скорее всего будет более информативен, чем стандартная заглушка. В принципе, вы можете модифицировать весь код системы, в т. ч. создавать новые классы и менять существующие, если это необходимо.

Естественно, решение “в лоб” не подходит, поскольку оно неизбежно приведет к большому количеству дублирующего кода.

Как бы вы решили такую задачу? Языки решения принимаются любые в пределах разумного: Java, PHP, C++. Не возбраняется использование языко-специфичных конструкций, если они действительно удобны — расширение кругозора никому лишним не будет ;-)

Я нашел два приемлемых решения, и опишу оба завтра, если их никто не назовет до меня.

Три top-подобных утилиты, которые должны быть на каждой Linux-машине

Feature image

Практически всем пользователям знакома утилита top, показывающая интерактивный список процессов, отсортированный по нагрузке на процессор. История этой утилиты идет от 1984 года, когда Уильям ЛеФевр написал такую утилиту для BSD 4.1. С тех пор top или его аналог есть практически в каждой UNIX-подобной ОС.

Годами доказав свою практичность, top вдохновил многих других программистов на разработку похожих утилит, относящихся к разряду must-have на любой Linux-машине, поскольку они дают возможность быстро оценить ситуацию в системе.

htop

htop — это логическое развитие top. Его интерфейс сделан с помощью ncurses, и благодаря этому он предоставляет гораздо больше возможностей по визуализации процессов и общей загрузки системы, а так же значительно более интуитивный интерфейс настройки всего этого.

iotop

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

iftop

Эта утилита аналогична предыдущей, но показывает она загрузку сетевого интерфейса. Надо заметить, что в отличие от двух предыдущих программ, она для работы требует прав root’а, и для запуска ей желательно указать имя интерфейса, который ей надо мониторить с помощью ключа -i.

[Бонус] duf

(Добавлено 2020-10-03) Duf — это аналог известной утилиты du, но с более приятным текстовым графическим интерфейсом, который отображает состояние всевозможных устройств хранения, группируя их в удобные таблички по типу. Утилита не интерактивная в том смысле, что она просто печатает текущее состояние файловой системы и выходит, но если сильно хочется, то можно сделать так:

1
$ watch duf -width $(tput cols)

Итог

Все три утилиты (htop, iotop и iftop) позволяют легко и быстро оценить положение дел на вашей машине даже в условиях отсутствия иксов или когда система бодро закапывается в своп и нужно срочно выяснить, почему: в такой ситуации дожидаться запуска графической утилиты — смерти подобно.

Аспекты реализации нового движка блога

Feature image

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

Технические требования

  • Язык программирования: PHP 5.1+ В принципе, я мог бы себе позволить и использовать PHP 5.3, мигрировав блог на мою VPS, но я не вижу в этом большой необходимости, тем более, что хостинг от EOMY за много лет показал себя с лучшей стороны.
  • СУБД: MySQL. Здесь выбор тоже практически очевиден: MySQL есть на любом хостинге и мне привычно с ним работать.
  • Фреймворк: Yii Framework Выбор фреймворка — тема не простая, так что ее я прокомментирую отдельно.
  • Миграция и обратная совместимость: весь контент должен быть перенесен и адаптирован к новому движку, все ссылки в постах должны остаться действующими, со старых URL постов должен быть редирект на соответствующие новые.

Yii Framework

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

Помимо этого есть еще плюсы в его копилку:

  • Фреймворк сам по себе написан очень хорошо и работает быстро
  • Фреймворк быстро развивается и не отягощает себя поддержкой устаревших технологий (камешек в сторону CakePHP)
  • Есть внушительный централизованный репозиторий расширений, которые реализуют наиболее сложные фитчи “из коробки”.
  • Наконец, есть базовый скелет блога на Yii, на написании которого построен официальный тьюториал. Кроме того, я нагуглил довольно много попыток развить эту основу до состояния нормального движка, которые надо внимательно изучить и, возможно, взять за основу.

Этот вечер я посвятил просмотру репозитория расширений и нашел там много полезного для себя и будущего движка :-)

С чего начать?

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

  • Bliig — произвел на меня наиболее положительное впечатление, судя по всему более-менее серьезная разработка.
  • YiiBlog — развитие Yii Blog Demo с официального сайта. Суда по всему, автор, как и я, затачивает его исходя исключительно из собственных пожеланий, которые немного расходятся с моими. Но все же он заслуживает рассмотрения, хотя и во вторую очередь.
  • Yii BlogDemo Extended — другая попытка развить демо-приложение блога. К сожалению, развитие не имеет четкого направления и поэтому в нем появилась такая глупость как флеш-часы. Кроме того, проект уже год никак не развивается, поэтому он пригоден разве что для подсматривания идей решения разных задач, но не как полноценная основа.
  • YiiApp — попытка содать универсальный каркас приложения на Yii. Попытка, судя по всему, достойная, но для меня не очень подходящая.
  • yay-cms — простая CMS на базе Yii. В качестве основы для блога подходит плохо, но в отношении создания обычных сайтов проект интересный, поэтому заслуживает упоминания.

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

Внимательное изучение этих проектов мне, видимо, еще предстоит, но я вижу два наиболее вероятных варианта:

  1. Взять за основу Bliig и танцевать от него.
  2. Взать за основу официальное демо, а найденные проекты использовать как шпаргалки для подсматривания решений.

Open Source

Я до сих пор еще не принял окончательного решения, но скорее всего движок будет приватным по двум причинам:

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

Техзадание для движка блога

Feature image

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

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

Общее назначение движка

Движок предназначен для ведения (преимущественно) однопользовательских персональных блогов.

Пользовательская система

  • Движок должен поддерживать многопользовательскую модель.
  • Контроль доступа с помощью механизма ролей. Набор ролей фиксирован: админ, блоггер, комментатор.
  • Анонимный пользователь имеет роль комментатора.
  • Желательна поддержка авторизации как по логину-паролю, так и через сторонние сайты (Twitter, Google, Open ID). Оптимально, если будет возможность привязки к одной учетке нескольких методов авторизации.

Размещение постов и создание статических страниц

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

  • Пост включает в себя следующие параметры:

    • Заголовок поста
    • Тело поста
    • Дата публикации
    • Автор
    • Состояние: опубликован или черновик
    • Имя поста для URL
    • Поле для традиционного видеоролика в конце поста
    • Теги
  • Визуальный редактор CKEditor полным набором панелей инструментов.

  • Загрузка файлов и изображений через интерфейс редактирования поста. Желательна интеграция с CKEditor.

  • Создание миниатюр изображений. Желательно иметь возможность задавать размер миниатюр, если “умолчальный” размер не подходил, либо просто отключать миниатюры для конкретных изображений.

  • Планирование постов. Если у поста статус “опубликован”, но дата публикации — в будущем, то он не будет отображен до наступления даты публикации.

  • Блоггеры должны видеть неопубликованные записи, все остальные - нет.

  • Черновики и отложенные записи должны визуально отличаться от обычных постов и друг от друга.

  • Включение/отключение комментирования для конкретных постов.

  • Желательно автосохранение черновиков как в WP.

  • Посты должны быть доступны через RSS.

  • Разделение на тизер и основной текст не требуется.

  • Поддержка размещения листингов кода с соответствующей подсветкой.

Комментирование

  • Возможность комментировать посты и страницы, для которых это не было запрещено.

  • Желательна поддержка древовидных комментариев

  • Комментатор может указать свой ник, сайт и email, все, кроме ника - не обязательно.

  • Желательна возможность логиниться прямо при комментировании.

  • Комментатору доступен ограниченный набор html тегов.

  • Форма добавления комментария имеет визуальный редактор с набором инструменов, ограниченным набором разрешенных тегов.

  • В форму комментирования встроен антиспам, невидимый для пользователей со включенным JavaScript.

  • Возможна подписка на комментарии по email.

  • Для блоггеров каждого комментария должны быть дополнительные опции:

    • Редактировать
    • Удалить
    • Удалить ссылку на сайт комментатора (для упрощения выноса спамовых ссылок)
    • Удалить все ссылки из текста поста.
  • Желательно иметь возможность детектирования скрытых ссылок в комментариях.

  • Желательно экспортировать комментарии в RSS

Многоязычность

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

Интерфейс

  • Шаблон должен быть отделен от логики и быть максимально простым.

  • Должна быть поддержка управления верхним меню и меню в сайдбаре.

  • Сайдбар справа.

  • Следующие виджеты должны присутствовать в сайдбаре:

    • Редактируемое меню
    • Облако тегов
    • Блогролл
    • Архив по месяцам
    • Последние комментарии и посты
  • Комментарии располагаются под постом.

  • Форма добавления комментария - под всеми комментариями.

  • У каждого комментария есть кнопки “Ответить” и “цитировать выделенное”.

  • На главной и на страницах отображения тегов должно присутствовать разбиение на страницы.

  • Поддержка вида для печати

  • Желательно редактирование постов и комментариев в упрощенном режиме с помощью ajax

  • Желательно удаление комментариев через ajax

  • Интерфейс должен приемлемо вести себя при отключенном JS или CSS

Требования к ядру

  • Кэширование всего, чего можно.
  • Желательна оптимизация CSS и JavaScript.
  • Желательно правильное и автоматическое расставление мета-тегов <meta name="robots">, <link rel=”alternate” type=”application/rss+xml”> и <link rel="canonical">
  • Генерация xml-карты сайта, содержащей ссылки на:
    • Главную
    • Посты
    • Статические страницы
    • Страницы тегов

Итог

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

Жизнь со вкусом

Feature image

Этот пост несколько выбивается из общей канвы блога, но я просто не могу промолчать. Что нужно настоящему программисту для счастливой жизни? Думаю, все согласятся, что в этот список точно входят интересная работа, комфорт дома и на рабочем месте и, конечно же, вкусная еда, приготовленная любимой девушкой. Вот о последнем и пойдет речь.

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

Извиняюсь перед теми, кто читает и думает «Ну хватит уже душу травить!», и перехожу к содержательной части. С этого момента все те вкусные рецепты, о которых я только что говорил, иллюстрируются и публикуются заботливой Олиной рукой лапой на ее кулинарном блоге. Так что все голодные программисты России могут вооружаться посудой и ссылкой на блог и приступать к изготовлению вкусной и полезной еды для себя любимых. Или, если вам повезло так же, как и мне, вооружать своих девушек. А если вы не будете забывать им помогать (с мытьем посуды, например), то у вас все шансы вкусно кушать не только по большим праздникам :-)

Ну и для затравки опубликую несколько фотографий блюд, приготовленных по этим рецептам (заголовки кликабельны).

π-рог

w_a58301a4.jpg

Песочное печенье «Пески Времени»

w_05790914.jpgw_90344263.jpg

Бисквитный тортик

w_b0163c67.jpg

Мини-тортики по-кошачьи

А это вообще эксклюзив, рецепт пока не опубликован ;-)

w_9cb4c53e.jpgw_c292f04f.jpg

 И просто немного капучино.

w_485ebc31.jpg

Длинная задержка перед запросом пароля в SSH

Микро-заметка для себя и для тех, кто столкнется с аналогичной проблемой.

Симптоматика

При попытке подключиться к удаленному хосту по ssh между установлением соединения и запросом пароля возникает длинная пауза (секунд 15-30). Она может появляться и пропадать в зависимости от того, из какой сети вы подключаетесь, однако никак не корреллирует со скоростью доступа.

Решение

В файле /etc/ssh/sshd_config установить следующие параметры:

GSSAPIAuthentication no

После этого неприятная пауза исчезнет.

Собираем bash, запускающийся где угодно.

Feature image

Ну, если быть честным, то не совсем где угодно, а на любом дистрибутиве линукса. Возможно, так же заработает и на других *NIX системах, хотя я в этом и не уверен: проверить не на чем, а знание матчасти в этом отношении подкачало.

И так, наша цель собрать минималистичный bash свежей версии, без зависимостей и не требующий установки, чтобы потом запускать его с флешки или, как в моем случае, для выполнения скриптов для bash 4 в условиях CentOS 5.5, поставляющегося с ископаемым bash 3.2.

Кстати, только сегодня узнал, что на той неделе вышел CentOS 5.6, с более актуальными версиями софта, но в моем случае это ничего не меняет.

Для пущего осложнения жизни собирать будем 32-битный bash (чтобы запускался и на 32-х и на 64-х битах) в 64-битной Ubuntu.

Далее, пошаговые инструкции:

  1. $ sudo apt-get install build-essential gcc-multilib Это мы устанавливаем инструменты для сборки + библиотеки для кросс-компиляции под x86 (помним, что Ubuntu у нас x86_64 и компилятор в ней по дефолту тоже 64-битный).
  2. $ wget http://ftp.gnu.org/gnu/bash/bash-4.2.tar.gz && tar -xzf bash-4.2.tar.gz && cd cd bash-4.2/ Качаем и распаковываем исходники bash. Я привел ссылку на актуальную на момент написания этого поста версию, однако возможно, вам в будущем захочется bash 4.3 или даже 5.0 ;-)
  3. $ export CC="gcc -m32" CFLAGS="-m32" Это и есть главная хитрость, необходимая для сборки 32-битного bash. То ли я не до конца разобрался, то ли у них Makefile корявый, но каждой из этих опций по отдельности недостаточно для сборки не под текущую архитектуру. Равно как и не работают параметры –host и –target у скрипта ./configure.
  4. $ ./configure --enable-static-link --without-bash-malloc Конфигурируем сборку. Параметр –enable-static-link требует статически линковать исполняемый файл с необходимыми библиотеками, тем самым минимизируя зависимости, а –without-bash-malloc устраняет один не вполне понятный для меня конфликт при линковке. Так же возможно вам захочется добавить свои параметры, включив дополнительный функционал. В моем случае нужен был –enable-array-variables, ради которого все и затевалось.
  5. $ make Собираем :-) Если у вас многоядерный процессор, можно добавить параметр -j N, где N - количество параллельных потоков сборки. Обычно его рекомендуют ставить равным удвоенному количеству реальных ядер. Кстати, в моем случае этот параметр существенно ускорил сборку, так что игнорировать его не стоит.

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

1
2
3
$ file bash

bash: ELF **32-bit** LSB executable, Intel 80386, version 1 (GNU/Linux), **statically linked**, for GNU/Linux 2.6.15, not stripped

Обратите внимание на выделенные жирным фрагменты. Если они отличаются, то значит при сборке что-то пошло не так, и bash собрался 64-х битный, либо со внешними зависимостями.

Если хочется, можно немного уменьшить размер бинарника, сделав $ strip bash.

У меня это дало экономию аж в 202365 байта.

Теперь этот файл можно копировать куда угодно и запускать, а он должен работать.

Прощай, FriendFeed

Удалил нафиг ленту блога из френдфида. Хочу видеть реальное количество подписчиков. А заодно и сам аккаунт на френдфиде снес, все равно пользоваться так и не научился.

Удобная разработка букмарклетов

В процессе работы над Pastemark (пост-анонс) мне понадобилось, во-первых, написать довольно сложный букмарклет и, во-вторых, сделать динамическую генерацию букмарклетов с разными параметрами. Не возьмусь претендовать на новизну, а лишь просто поделюсь найденным мною подходом.

О букмарклетах

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

Спектр применений букмарклетов довольно широк: от простого изменения размера шрифта на текущей странице до анализа ее содержимого и передачи извлеченных данных какому-нибудь третьему сервису. В качестве примера можно привести букмарклет твиттера.

Кроме того, букмарклет — это практически единственный шанс для честного вебмастера выполнить свой код в контексте чужого сайта (-:

 Когда нужно писать букмарклет?

Собственно, ответ уже дан в предыдущем параграфе. Фактически, букмарклет занимает промежуточную роль между скриптами на сайте и расширением браузера: его функциональность все еще ограничена по сравнению с полноценным расширением, но он уже не привязан к какому-то одному сайту. Еще одна приятная сторона — один и тот же букмарклет может работать во всех браузерах сразу, включая те, которые не поддерживают расширения как таковые (реверанс в сторону мобильных браузеров).

Так как же его написать?

Основные концепции написания букмарклетов хорошо изложены в статье на javascript.ru. Я же не буду лишний раз повторяться и сразу перейду к своей методике.

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

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

1
2
3
function my_bookmarklet() {
  /* ... */
}

Мы прибегнем к альтернативному варианту:

1
2
3
var my_bookmarklet = function (arg1, arg2) {
  /* ... */
};

Это связано с тем, что в букмарклете нам нужна не именованная функция, а анонимная, чтобы не засорять пространство имен и избежать конфликтов.

Генерация итогового букмарклета

После того, как мы написали функцию, нам нужно превратить ее в букмарклет. Краткости ради я сразу приведу фрагмент кода, выполняющий эту функцию:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Ключевой момент: делаем из анонимной функции строку с ее исходниками.
var code = my_bookmarklet.toString();
// Вырезаем однострочные и многострочные комментарии.
code = code.replace(/\/\/.*/g, "");
code = code.replace(/\/\*[\s\S]*\*\//, "");
// Заменяем переносы строк пробелами
code = code.replace(/\n/g, " ");
// Вырезаем повторяющиеся пробелы.  Если у вас внутри строковых литералов
// есть повторяющиеся пробелы, то этого лучше не делать.
code = code.replace(/\s+/g, " ");
// Генерируем uri, при этом экранируем спецсимволы.
var link = encodeURI(
  "javascript:void(" +
    code +
    '("' +
    value_for_arg1 +
    '", "' +
    value_for_arg2 +
    '"))'
);

Обратите внимание на переменные value_for_arg1 и value_for_arg2. Если вам нужно (как в моем случае) генерировать параметризованные букмарклеты, то вы можете это легко реализовать, передавая параметры вашей анонимной функции.

Далее с переменной link можно делать все, что хотите — генерировать и вставлять на страницу ссылку с этим значением href, отображать просто так или что вам еще в голову придет.

Грабли

Есть только одни грабли, на которые легко попасться в процессе написания букмарклета таким способом: ни в коем случае нельзя пользоваться какими-либо функциями или переменными, объявленными вне экспортируемой функции, ведь во время исполнения букмарклета (а это почти наверняка произойдет не на вашем сайте и в чужом окружении) все они доступны не будут. Я, кстати, на эти грабли тоже наступил, правда, довольно быстро это обнаружил и исправился.

Итог

В качестве итога еще раз перечислю все бонусы, которые мы получаем при разработке букмарклетов таким способом:

  1. Правильно форматированный код.
  2. Возможность спокойно писать код в вашей любимой IDE.
  3. Простота тестирования - вы можете в считанные секунды сгенерировать тестовый букмарклет и запустить его, не тратя время на его переформатирование оформление.
  4. Возможность динамически генерировать букмакрлеты с разным поведением, за счет использования параметров.

Честно говоря, долго не мог выбрать, какую песню вставить, эту или The Illusionist. Рекомендую обе, они обалденные :-)