almost 5 years ago

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

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

Добавление поддержки Markdown в Kate

Поддержка того или иного синтаксиса в Kate делается при помощи .xml конфигов
в домашнем каталоге пользователя. Всё, что нужно сделать — скачать markdown.xml
с сайта автора или с моего зеркала и положить его в каталог ~/.kde/share/apps/katepart/syntax/.
Если такого каталога нет — ничего страшного, его можно создать.

 
almost 5 years ago

У меня нет никаких предрассудков насчёт Qt и на десктопе у меня KDE, так что
для первых шагов в GUI я воспользовался проектом под названием CommonQt.
Это CFFI-биндинги к libqt и libsmoke.

Здесь я запишу, как настроить окружение для того, чтобы писать Qt-приложения.
Ниже скриншот конечного результата. ;)

Установка библиотек Qt

Как написано в инструкции по установке некоей Eccada (я действительно
без понятия, что это такое), нужно сначала установить девелоперские библиотеки
Qt:

  • libqt4-dev
  • libsmokeqt4-dev
  • libqt4-webkit

Между прочим, для того, чтобы поставить эти библиотеки себе на гибрид Squeeze+Wheezy,
мне пришлось целиком обновиться до Wheezy. :)

Установка лисповых биндингов

С quicklisp всё делается на раз-два: (ql:quickload :qt) и всё. На этом шаге
как раз и можно определить, все ли нужные библиотеки были установлены.

Минимальная программа прямо в REPL

Для проверки, всё ли работает правильно, пишем первый пример со страницы приветствия
документации Qt
в Slime REPL:

(ql:quickload :qt)
(in-package :qt)

;; Включаем reader macro '#_' 
(enable-syntax) 

(defvar *qapp* (make-qapplication)) 
(defvar *editor* (#_new QTextEdit)) 
(#_show *editor*) 
(#_exec *qapp*) 

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

 
almost 5 years ago

Сегодня развлекался с моим любимым Common Lisp и обнаружил интересную деталь.

Допустим, мы хотим написать простую функцию для определения среднего арифметического
набора заданных чисел. Чтобы она работала вот так:

> (average 1 2 3 4) 
> 5/2

...ну хорошо, хорошо:

> (coerce (average 1 2 3 4) 'float) 
> 2.5

Заметьте то, что я не хочу передавать в average список, я хочу кортеж чисел.
Интересная деталь вот в чём: самая простая (в смысле, лаконичная) реализация,
которая пришла мне в голову, это такая:

(defun average (&rest args) 
  (/ (reduce #'+ args) (list-length args)))

Дело в том, что определение среднего арифметического — это задачка для средней
школы, и мы вполне можем её дать в качестве задания по программированию на
второй паре (на первой мы изучим весь синтаксис лиспа вместе с макросами defun
и defvar). Но на самом деле мы не можем её дать, потому что у нас есть вот
эта форма в решении: (reduce #'+ args), и, чтобы студенты смогли ей бегло
пользоваться, нужно, чтобы они знали как минимум:

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

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

Конечно, мы можем соорудить нечто работающее из let и dotimes, но тогда
зачем нам Common Lisp вообще? :)

 
almost 5 years ago

Допустим, вам нужно написать endpoint для аякс-запроса. Или обычный обработчик
обычного POST-запроса от веб-формы. Тогда основные действия, которые обязательно
должны быть в процессе обработки, будут такие:

  • Фильтрация входных данных (это НЕ валидация бизнес-правилами, только лишь такие действия, как укорачивание, очистка от окружающих пробелов, исключение непечатных символов, приведение к нижнему регистру и т. д.). Или мы можем даже сыграть роль адаптера и переименовать некоторые параметры запроса в вид, ожидаемый обработчиком.
  • Обработка запроса. Здесь мы собственно делаем то, зачем существуем. Мы ожидаем уже более-менее очищенный от мусора массив входных параметров, возможно, среди которых есть ошибочные (например, числовое значение вне допустимого диапазона, или передан массив вместо скаляра).
  • Форматирование результата для отклика клиенту. Например, мы можем возвращать JSON, или генерировать HTML страницу по шаблону. Или генерировать изображения. Или отдавать файлы с диска.

Если записать в функциональном стиле на PHP, то можно получить следующий шаблон
хэндлера:

<?php

echo format(process(clean($_REQUEST)));

/**
 * Здесь очистка суперглобального массива $_REQUEST и генерация массива $request
 * 
 * @param array $request Данные запроса из массива $_POST или $_GET (или $_REQUEST).
 * @return array Очищенные данные из запроса, которые ожидает функция process.
 */
function clean($request)
{
  // TODO
  return $request;
}

/**
 * Здесь выполнение действий согласно запросу $request
 * 
 * @param array $request Параметры запроса, очищенные функцией clean.
 * @return array $result Результат работы со всей информацией, необходимой для форматирования ответа клиенту.
 */
function process($request)
{
  // TODO
  return $request;
}

/**
 * Здесь форматирование результата работы для выдачи клиенту.
 * 
 * @param array $result Результат работы, как он сгенерирован процессором. Нам не дозволяется использовать какие-либо другие данные.
 * @return string Форматированный ответ, готовый к отправке клиенту через echo.
 */
function format($result)
{
  // TODO
  return json_encode($result);
}
?>

Если process сталкивается с ошибкой, он пишет об этом в результат своей работы
(например, в поле 'error') и сразу возвращает результат в format.

Если format должен генерировать большой HTML документ, то ничего страшного,
он может и делать echo внутри себя, вместо того, чтобы возвращать строку.
Тогда вызов всей цепочки будет без echo в начале, конечно же.

 
almost 5 years ago

Допустим, у вас есть классная маленькая программка на Common Lisp под названием
filter. Она занимает всего один файл исходного кода под названием filter.lisp
примерно в 200 строк длиной. В этом файле вот такой заголовок:

(defpackage :localhost.me.filter
  (:use :common-lisp)
  (:export :run))
(in-package :localhost.me.filter) 

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

$ cd $PROGRAMDIR 
$ sbcl 
CL-USER> (load "filter.lisp") CL-USER> (:localhost.me.filter:run) 

Рассмотрим два решения, одно простое, но не всегда поможет, другое посложнее
и поможет уже в большем числе случаев.

Решение первое

SBCL умеет запускать программы на CL в режиме скриптов командной строки, для
этого используется специальный флаг --script. Более того, благодаря этому
можно написать лисп-программу, добавить к ней типичный юниксовый шебанг, вызывающий
SBCL с этим флагом, и она будет работать как любой другой шелл-скрипт (за исключением
того, что рантайм SBCL весит 50MB бгг). Так что напишем этот скрипт:

#!/usr/bin/sbcl --script
(load "filter.lisp")
(:localhost.me.filter:run)

Теперь положим этот скрипт в ту же директорию, что и filter.lisp, в файл
с именем run и можно будет запускать нашу программу так:

$ ./run

Что и требовалось.

Решение второе

Однако, у флага --script есть одна особенность: SBCL не загружает никакие
пользовательские скрипты инициализации при старте, так что скрипт будет выполняться
в 100% дефолтном рантайме. Это плохо, если, допустим, вы используете Quicklisp
и у вас в filter.lisp есть такой вызов:

... (ql:quickload :CL-FAD) (use-package :CL-FAD) ...

Ну или вызов любой другой библиотеки, неважно. Если запустить такой скрипт
первым способом, то SBCL будет грязно ругаться по поводу того, что он не знает,
что такое ql:quickload.

Для решения этой проблемы мы смухлюем, подменив бинарник SBCL таким бинарником,
у которого все нужные библиотеки уже загружены. В SBCL есть функция save-lisp-and-die,
которая выгружает текущее состояние рантайма в файл с указанным именем. Дальше
этот файл можно использовать как обычный бинарник SBCL, в том числе, и для
вызовов с использованием --script. Поэтому если запустить SBCL и сразу выгрузить
его в файл, то полученный бинарник будет содержать всё, что SBCL подключил
при загрузке, основываясь на пользовательских конфигах.

(save-lisp-and-die "/абсолютный/путь/до/файла")

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

 
almost 5 years ago

В свете работы над моими старыми программами из КГУ понадобилось покрывать
код юнит-тестами. Как выяснилось после гуглопоиска, для Free Pascal, которым
я компилирую свою переработку, существует проект под названием FPTest,
представляющий собой каркас для написания юнит-тестов на этом замечательном
языке.

Документации по этому проекту довольно немного, и официальная вики, как и README,
сильно Lazarus-ориентированы. Поэтому расскажу здесь, как подключить FPTest
к существующему консольному проекту и собрать тесты, написанные с его помощью.

Допустим, у нас есть программа

Я разберу первое задание из курса «алгоритмизация и программирование» в КГУ
за 2006 год.

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

program Lesson01;
uses
   SysUtils;

var
   x: real;
   answer: real;
   
begin
   writeln('Enter 0.1 =< x <= 0.6 here:');
   repeat
      readln(x)
   until
      (x>=0.1) and (x<=0.6);
   
   answer:= MyFunction(x);
   
   writeln('Answer is ',answer:12:3);
end.

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

Итого, состав файлов конечного решения задачи выглядит так:

  1. Lesson01.pas — точка входа основной программы.
  2. Tests01.pas — точка входа юнит-тестов.
  3. L01Code.pas — код программы, проверяемый тестами.
  4. L01Tests.pas — код самих юнит-тестов (а вы как думали? :) ).

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

Разнесём код по отдельным файлам

Приведу полные листинги всех четырёх файлов, это важно, потому что boilerplate
code, нужный для нормальной работы FPTest, не особо очевиден:

Lesson01.pas
program Lesson01;
uses
   SysUtils,
   L01Code;

var
   x: real;
   answer: real;
   
begin
   writeln('Enter 0.1 =< x <= 0.6 here:');
   repeat
      readln(x)
   until
      (x>=0.1) and (x<=0.6);
   
   answer:= MyFunction(x);
   
   writeln('Answer is ',answer:12:3);
end.
Tests01.pas
program Tests01;

uses
   TextTestRunner,
   L01Tests;

begin
   L01Tests.RegisterTests;
   RunRegisteredTests;
end.
L01Tests.pas
unit L01Tests;

interface 

uses
   TestFramework, L01Code;

type
   Lesson1Tests = class(TTestCase)
   published    
      procedure TestValidInput;
      procedure TestNegativeXShouldThrowError;
      procedure TestMinimumAllowedX;
      procedure TestMaximumAllowedX;
   end;

procedure RegisterTests;

implementation
uses
   math;

const
   precision = 0.0001;

procedure RegisterTests;
begin
   TestFramework.RegisterTest(Lesson1Tests.Suite);
end;

procedure Lesson1Tests.TestValidInput;
begin
   Check(MyFunction(0.1) - 12.4244944 < precision, 'On the left edge function should be calculable');
   Check(MyFunction(0.35) - 4.8590140 < precision, 'Function should be calculable in the middle of interval');
   Check(MyFunction(0.6)  - 4.8087379 < precision, 'On the right edge function should be calculable');
end; 

procedure Lesson1Tests.TestNegativeXShouldThrowError;
begin
   StartExpectingException(EInvalidArgument);
   MyFunction(-1);
   StopExpectingException();
end;

procedure Lesson1Tests.TestMinimumAllowedX;
begin
   StartExpectingException(EInvalidArgument);
   MyFunction(0.1 - precision);
   StopExpectingException();
end;   

procedure Lesson1Tests.TestMaximumAllowedX;
begin
   StartExpectingException(EInvalidArgument);
   MyFunction(0.6 + precision);
   StopExpectingException();
end;   

               
initialization
end.
L01Code.pas
unit L01Code;

interface

function MyFunction(x: real) : real;

implementation

uses
   Math;

function MyFunction(x: real) : real;
begin
   if (x < 0.1) then
      raise EInvalidArgument.Create('Expected x in [0.1, 0.6]');
   if (x > 0.6) then
      raise EInvalidArgument.Create('Expected x in [0.1, 0.6]');
   MyFunction := abs(sin(power(10.5*x, 1/2))) / (power(x, 2/3) - 0.143) + 2*pi*x;
end;

initialization
end.

Как обычно, тестов больше, чем кода.

Связывание вместе

Теперь, когда у нас все эти файлы счастливо лежат в одной папке, нам нужно
собирать программу с тестами, указывая компилятору, где лежит папка с исходниками
FPTest. Я вот не знал, что для этого нужно использовать параметр -Fu:

$ fpc -Fu"ПУТЬ ДО FPTEST" -Mobjfpc Tests01.pas

Путь можно относительный. Обязательно использовать флаг -Mobjfcp, потому
что FPTest у себя внутри использует объектную модель, так что без -Mobjfpc
тесты не соберутся.

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

$ fpc Lesson01.pas
 
almost 5 years ago

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

После некоторого продолжительного поиска в Сети был найден ответ на ServerFault:
выполнение phar-архивов блокирует suhosin patch.

Для решения проблемы нужно раскомментировать следующую строку в /etc/php5/cli/conf.d/suhosin.ini:

;suhosin.executor.include.whitelist =

И заменить её на

suhosin.executor.include.whitelist = "phar"

Одноразовое решение выглядит так:

$ php -d suhosin.executor.include.whitelist="phar" MYPHAR.phar
 
almost 5 years ago

У нас в набережночелнинском филиале КГУ на факультете прикладной математики и информационных технологий преподавали несколько программистских курсов. На них студентам задавали задания в духе "перемножьте каждый четвертый член массива чисел полученной от пользователя длины", и их нужно было запрограммировать. В первом семестре - на Delphi. Потом - на C++. После третьего курса можно было писать на чём хочешь.

Пожалуй, это были единственные учебные курсы из всех пяти лет обучения, которые хоть что-то дали лично мне как разработчику. Во всяком случае, я узнал об ООП в стиле C++ и о такой вещи, как указатели.

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

Изменения, которые я хочу внести, следующие:

  1. Структура каждой программы должна быть как можно проще. Всё должно компилироваться из командной строки. Всю IDE машинерию нужно убрать.
  2. Для сборки будет использоваться GNU Make, для программ на любом языке.
  3. Покрыть код юнит-тестами.  Чистый TDD использовать уже поздно, всё-таки код уже написан.
  4. Код единообразно переформатировать, очистить от "попахивающих" мест и снабдить формальными комментариями.
  5. Текст заданий изъять изо всяких PDF и DJVU файлов, в которых он сейчас находится, и перенести в текстовые README.md.

Кроме того, для разработки на первых курсах активно использовался homemade
хак для того, чтобы выводить кириллический текст в консоль. Отдельный хак для
программ на Паскале, отдельный для программ на Си. Общаться с пользователем
по-русски не так важно, так что для простоты все сообщения от программ будут
переведены на английский.

В первом семестре использовался Delphi, с помощью которого компилировались
консольные приложения. Вместо Delphi будет использоваться Free Pascal в
объектном режиме. Всю машинерию, связанную с Delphi, нужно убрать.

Для тестов программ на Паскале будем использовать FPTest.

Начиная со второго семестра и дальше использовался C++. Для компиляции точно
буду использовать g++ вместо Visual Studio, а вот какой каркас для тестов использовать,
пока не знаю (просто пока не пробовал xUnit-каркас для C++).

Исходники уже выложил на ГитХабе. Пока более-менее нормально обработано только первое задание.

Думаю, когда закончу задания первого семестра, напишу письмо в КГУ. :)

 
about 5 years ago

Сегодня второй раз в жизни пишу код согласно чистому TDD, прямо как заповедали
такие мастера, как Роберт Мартин, Эндрю Хант и Майкл Физерс. Один случай меня
заставил серьёзно задуматься о разнице в подходах к разработке.

Третий шаг в TDD гласит: «напиши ровно столько кода, сколько нужно для того,
чтобы проваливающийся ранее тест начал завершаться успешно
». (Первые два это
«напиши проваливающийся тест» и «заставь этот тест компилироваться»). Несмотря
на то, что он кажется довольно простым, пункт насчёт «ровно столько, сколько
нужно» невероятно важен в рамках этой методологии.

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

Только после раздумья длиной что-то около полудня я понял, почему так важно
соблюдать это правило, хотя ответ очень прост. Если ты напишешь код, который
не предусматривают твои тесты, пусть даже он будет выполнять какую-то идейно
полезную работу (а он, конечно же, будет выполнять идейно полезную работу),
он не будет покрыт тестами. А код, который не покрыт тестами — это legacy код,
которому положено гореть в аду. Более того, если ты оставишь весь этот код
среди другого, чистого и покрытого тестами, он будет, как ржавая арматура,
мешать добавлять новую функциональность. Так как он не покрыт тестами, а ты
в целом стараешься следовать TDD, значит, все эти буквы становятся просто шумом,
про полезность и надёжность которого ты не знаешь ничего.

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

 
about 5 years ago

О-хо-хо, как же я давно не писал!..

Есть важная особенность при настройке VPN соединения к Windows-серверу из Debian.
Это если не считать того, что на самом деле это называется не VPN, а PPTP подключение,
ну да простим Майкрософту эту мелочь.

При настройке подключения, следует указать, что разрешён только один метод
аутентификации: MSCHAPv2. И включить MPPE шифрование. Спасибо orbnajes
с форумов Fedora
за объяснение.

Без этих действий VPN подключение будет молча обрываться, и сообщение об ошибке
в syslog'е мало поможет.

update: Ещё, возможно, придётся уменьшить MTU на сетевой карте, или подключению
будет очень дурно, и оно регулярно будет обрываться. Для того сервера, который
мне был нужен, сработало уменьшение MTU с 1500 до 800.

В /etc/ppp/options.pptp это будет выглядеть так:

refuse-pap 
refuse-eap 
refuse-chap 
refuse-mschap 
require-mppe

В Network Manager'е нужно оставить только один разрешённый метод аутентификации
(MSCHAPv2) и убрать все остальные. Плюс включить MPPE шифрование.

Уменьшаем MTU следующим образом:

# ifconfig eth0 mtu 800
 
over 5 years ago

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

Воспользовавшись учёткой на гуглопочте и руководством в Debian Wiki, мне
удалось всё настроить за ~5 минут, чего и вам желаю. Авторы руководства, большое
вам человеческое спасибо.

Напомню, что в стандартной поставке Debian Squeeze (который уже я частично
переделал на Wheezy) устанавливается MTA exim4, настроенный на исключительно
локальную доставку почты.

 
over 5 years ago

После переезда на новый Dell Inspiron N5110 выяснилась неприятная новость:
в нём установлены две видеокарты, работающие по технологии NVidia Optimus,
и в Debian она официально не поддерживается. Все эти приятные вещи я узнал
из поста на форуме убунтоводов насчёт NVidia Optimus.

Я отключил себе видеокарту NVidia, следуя [рекомендациям robbyx.net (пункт

3)]5, и буду следить за жизнью проекта Bumblebee, посвящённому реализации

NVidia Optimus на Linux. Возможно, придётся даже включиться в разработку этого
проекта, а то мне очень хочется быстрый OpenGL у себя на ноуте в Debian.

 
over 5 years ago

Сегодня сбылась мечта идиота: нашёл пакет для графики в Common Lisp, с помощью
которого можно рисовать игры с графикой и звуком. Это не GUI пакет с виджетами,
это 2D/3D канва и инициализаторы для звука, джойстика и CDROM. Пакет называется
lispbuilder-sdl, и он крут.

Как обычно, напомню для себя, как lispbuilder-sdl подключается в SBCL.

Написание программы, использующей lispbuilder-sdl

На странице Using Lispbuilder SDL приведён пример того, как оформлять
игровую main loop, используя этот пакет. Однако, полезным будет напомнить процесс
собственно работы с этим кодом до того момента, когда на экране, наконец-то,
появится окно с демо-игрой.

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

Во-вторых, нужно разместить код примера в файле. Допустим, файл называется
sdl-trivial.lisp. После этого, в начале файла нужно приписать загрузку lispbuilder-sdl
и инициализацию пакета, внутри которого разместим наш код:

(ql:quickload :lispbuilder-sdl)
(defpackage :sdl-trivial (:use :lispbuilder-sdl :common-lisp))
(in-package :sdl-trivial)
;; Код примера вставляется сюда ;; 

Всё, можно запускать. Переходим в каталог с файлом sdl-trivial.lisp и запускаем
в этом каталоге свой лисп, у меня это sbcl:

$ cd путь/до/sdl-trivial.lisp 
$ sbcl

Переходить в каталог с файлом нужно, чтобы не пришлось писать полный путь до
него внутри самого sbcl.

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

(load "sdl-trivial.lisp")
(in-package :sdl-trivial)
(mouse-rect-2d)

Как посмотреть на возможности lispbuilder-sdl двумя строчками кода

На странице мануала lispbuilder-sdl есть раздел с готовыми примерами. Если
неохота возиться с примером, описанным выше, то можно просто сделать внутри
лиспа следующее (при наличии установленного quicklisp):

(ql:quickload :lispbuilder-sdl-examples)
(SDL-EXAMPLES:DISTANCE-2D)
 
over 5 years ago

Сегодня меня вконец достала дефолтная настройка на latin1 в MySQL, и я пошёл
курить маны. Хочется, естественно, перевести всё в utf8. Решается, как оказалось,
всё тремя строчками в конфиге.

Открываем my.cnf (в Debian testing это /etc/mysql/my.cnf) и пишем там следующее:

[client] 
default-character-set = utf8


[mysqld] 
character-set-server = utf8 

collation-server = utf8_general_ci 

Понятное дело, надо дописать эти строчки в существующие секции [client] и
[mysqld], а не создавать новые.

После сих чудесных заклинаний нужно перезапустить сервер MySQL, в Debian это
делается так:

$ /etc/init.d/mysql restart

Проверяется mysql-клиентом следующим образом (после запроса показан вывод,
который должен получиться):

mysql> show variables like 'character_set%'; 
+--------------------------+----------------------------+ 
| Variable_name | Value | 
+--------------------------+----------------------------+ 
| character_set_client | utf8 | 
| character_set_connection | utf8 | 
| character_set_database | utf8 | 
| character_set_filesystem | binary | 
| character_set_results | utf8 | 
| character_set_server | utf8 | 
| character_set_system | utf8 | 
| character_sets_dir | /usr/share/mysql/charsets/ | 
+--------------------------+----------------------------+ 
8 rows in set (0.00 sec) 

Напомню следующие две вещи:

  1. уже имеющиеся текстовые данные в БД нужно конвертировать другими командами, которые мне сейчас не очень интересны;
  2. при импорте БД из SQL-дампа, нужно следить за тем, чтобы текст в файле дампа действительно был в UTF-8 ;).
 
over 5 years ago

Сегодня, после года практики в Битриксе мне открылось поистине тайное знание.

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

<?php
...
global ${$arParams['FILTER_NAME']}

или, во всяком случае, аналог этого кода. А раз используется поиск переменной средствами ключевого слова global, то без шаманства можно забыть о таких интересных вещах, как динамическая генерация фильтра внутри объектов классов или вообще внутри любого lexical scope, отличающегося от глобального.

Сегодня же до меня дошёл смысл этого global. Фильтр надо просто сохранять
как элемент массива $GLOBALS, вместе со всеми остальными глобальными переменными.
Это, по-видимому, самый надёжный способ генерировать фильтр и передавать его
имя в компонент.

Следующие две инструкции могут быть теперь вообще где угодно, как угодно глубоко
внутри иерархии классов и/или стека вызовов:

<?php
...
$GLOBALS['similar_items'] = array(
    '=EXTERNAL_ID' => getSimilarItems($arResult) 
); 
$APPLICATION->IncludeComponent( 
    "bitrix:catalog.section", 
    "", 
    Array( ... 
            "FILTER_NAME" => 'similar_items', 
        ... 
    ) 
); 

Я знаю, вы всегда мечтали хранить временные данные в глобальной области видимости,
не правда ли?

Пиздец.

 
over 5 years ago

Just posted two packages to the Hackage. My intentions to it was that
from now everyone from Haskell community can simply google for "Haskell integration
of complex functions" and It will be clear as a day that package complex-integrate
provides them with needed functionality. Second package is a library with implementation
of Theta functions. Hope it helps someone. Details below.

Integration of complex functions

Module for integration of complex functions, which I wrote from scratch because
every numeric library I found (including numeric-tools) could calculate
only integrals of real-valued functions. I placed it under Data.Complex.Integrate,
but maybe there are more suitable place to such a module. It exports a single
function called integrate. Package was called complex-integrate. Today I uploaded
it to http://hackage.haskell.org/package/complex-integrate

Implementation of Theta-functions

Module with implementation of theta-functions, special complex function of
two variables. Details can be found at http://en.wikipedia.org/wiki/Theta_function.
I placed it under Numeric.Functions.Theta. It exports four theta-functions
and a small helper to calculate their second parameter. Package was called
theta-functions. Today I uploaded it to http://hackage.haskell.org/package/theta-functions

 
over 5 years ago

Кстати, а вы знаете, что если в MySQL в названии базы данных есть дефис, то
вы просто так не сможете её удалить? %)

mysql> drop database xxx-dev; 
ERROR 1064 (42000): You have an error in your 
SQL syntax; check the manual that corresponds to your MySQL server version 
for the right syntax to use near '-dev' at line 1 

Надо, оказывается, окружать название БД back-tick'ами:

mysql> drop database `xxx-dev`; 
Query OK, 55 rows affected (0.13 sec)

Решение найдено в описании MySQL Bug #461. Проблема в том, что создаются базы данных с дефисами
в названии без проблем.

 
over 5 years ago

Today I am packaging some modules used by AFCALC and my own writings.

In the AFCALC GitHub repo I am placing the Wiki page which lists every
external link relevant to the project
. Also I plan to write a short article
explaining what's going on in AFCALC to the wide public.

Initial paper which was used as an article accompanying my diploma project
was uploaded to Scribd: Numerical Modelling of the 2D Explosion of the Elliptic
Charge using the Multithreading

Integration module which bundled with AFCALC will be published with the name
Data.Complex.Integrate to the HackageDB.

Theta module which bundled with AFCALC and used by the reference model will
be published with the name Numeric.Functions.Theta to the HackageDB.

I'll cabalize Integrate and Theta right after finishing this post.

 
over 5 years ago

Собственно, как я и ожидал, учёт логинов в MySQL базе данных ужасно неудобен,
когда надо срочно быстро вспомнить логин/пароль для какого-то сайта. Даже при
наличии дополнительной функции find_login всё равно нужно сначала запускать
mysql-клиента, заходить в базу данных... долго. Надо быстрее.

Создание агента для получения логинов/паролей из базы данных

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

Добавляем нового пользователя в MySQL-сессии:

create user fetcher; 
grant execute on my.* to fetcher;

Здесь я подразумеваю, что таблица logins находится в базе данных my и я буду использовать пользователя fetcher для доступа к каким-нибудь ещё таблицам, буде таковые появятся в my

Скрипт для получения логинов/паролей из базы данных используя агента

Я хочу уметь делать в консоли так:

$ fl myalias
+------+-------------+-------+----------+------------+
| type | hostname    | login | password | address    |
+------+-------------+-------+----------+------------+
| ftp  | *******.com | ***** | ******** | /somewhere |
+------+-------------+-------+----------+------------+

Вызываю скрипт fl и передаю ему часть алиаса для записи в таблице logins => получаю список найденных записей прямо в консоль.

Для чего пишу в консоли следующее:

$ cat > ~/bin/fl << "EOF"
#!/bin/sh
mysql -u fetcher -e "use my; call find_login('$1');"
EOF
$ chmod 755 ~/bin/fl

Каталог ~/bin у меня в $PATH. Собственно, на этом всё, цель достигнута.

 
over 5 years ago

Well, AFCALC development goes smoothly and I reached the important milestone.
Code base substantially shrunk — I was mostly fixing bugs, not adding features.

AFCALC now equipped with one testing model with exp() as transforming function
and one real-world model of explosion made in 1970's by Kotlyar L. M..
It's successfully plotting the simplified version of Kotlyar's model, and it
is very promising result.

I want to pass the following milestones:

  1. Plotting of full model of Kotlyar's model. It is very important, because there exists the process of calculating the series of additional coefficients for correction function and I want it decoupled from model and added to AFCALC core.
  2. Simultaneous building of three binaries with make: one with autotests, one with interactive session and one which plots the whole bunch of charts with different model parameters.

Tomorrow will be the day which gives me plot of full Kotlyar's model.

 
over 5 years ago

While working on various web projects written completely in PHP, I collected
some custom procedures for more satisfying work.

Text trimming

Often you are in need of trimming some text by set number of characters, but
do not want to trim mid-word. This is a function which cuts text between words
trying to reduce string length to be less than provided value.

<?php
// Cut text at set length, 
// preserving words and appending 
// ellipsis at the end of trimmed string
function my_ellipsis_trim($text, $length) {
    $count = strlen($text);
    $tlen = $length - 3;
    // If text is shorter than needed length, 
  //  return it untouched
 if ($tlen > $count) {
        return $text;
    } else {
  // Searching index of last space in the string
  // So text will be cut at last whole word
     for ($cutidx = $tlen; $cutidx >= 0; $cutidx--) {
            if ($text{$cutidx} == " ") break;
        }
        return substr($text, 0, $cutidx) . " ...";
    }
}  

Pretty-printing values of variables

So, you are a web developer. Why are you have to use such stdout-oriented
functions as echo or print? We will dump variables of any complexity using
HTML format.

Additionally, as we know about the existence of highlight.js, we want
to colorize the output if it is sufficiently complex.

<?php
// $data is a value to dump
// $caption will be printed as a header before the $data
// $method controls selection of internal printing function
//   choice is between 'print_r' and 'var_dump'
// $is_code marks if the data should be wrapped in <code> tag 
//   for processing it with highlight.js
// $escape controls if the data should be processed 
//   by htmlspecialchars
// returns string containing formatted $data dump
function my_report(
  $data, 
  $caption = 'Data', 
  $method = 'print_r', 
  $is_code = false, 
  $escape = true) {
    $result = '<pre><strong>'.$caption.' : </strong></pre><pre>';
    $result .= $is_code ? '<code>' : '';
    ob_start();
    switch ($method){
        case 'print_r':
            print_r($data); break;
        case 'var_dump':
            var_dump($data); break;
        default:
            print_r($data);
    }
    $a = ob_get_contents();
    ob_end_clean();
// Escape every HTML special chars (especially &rt; and < )
 $b = $escape ? htmlspecialchars($a, ENT_QUOTES) : $a;
    $result .= $b;
    $result .= $is_code ? '</code>' : '';
    $result .= '</pre>';
    return $result;
} 

MIME encoding of strings

When sending e-mail letters in real multi-national world you need to declare
character encoding on almost anything. To do this in e-mail headers, you should
wrap your header content in special encoding declaration and convert it to
Base64. To speed-up this process, I wrote small helper function.

<?php
function my_mime_header($string, $encoding=null) {
    if(!$encoding)
    $encoding = mb_internal_encoding();
    return "=?$encoding?B?" . base64_encode($string) . "?=";
}

Inspired by comment to mb_encode_mimeheader() at php.net.

 
over 5 years ago

В 1С:Битрикс используется относительно удобный способ отправки почтовых сообщений
в ответ на различные события в системе. Фактически, функция CEvent::Send()
заменяет собой встроенный в PHP mail().

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

Для того, чтобы добраться до этого списка, нужно покопаться напрямую в таблицах
БД Битрикса. В админке в разделе Настройки → Инструменты → SQL-запрос,
нужно выполнить следующий запрос:

select * from b_event where event_name like '%form%' order by date_insert desc 

via: Bitrix FAQ

 
over 5 years ago

Here I save my EMACS config file for myself as backup and for everyone to see.

;;; Hijarian's .emacs config file

;;; 2011-08-31



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; 1: MODES


;;; Enable Viper-mode by default

(setq viper-mode t)
(require 'viper)

;;; Autoload nXHTML-mode

(load "~/.emacs.d/nxhtml/autostart.el")

;;; Autoload SLIME

(add-to-list 'load-path "~/.emacs.d/slime/")
(require 'slime)
(slime-setup '(slime-fancy))
(setq slime-net-coding-system 'utf-8-unix)
;; Replace "sbcl" with the path to your implementation

(setq inferior-lisp-program "sbcl")
;;;Autoload quicklisp

(load (expand-file-name "~/systems/quicklisp/slime-helper.el"))

;;; Autoload CEDET

(load-file "~/.emacs.d/cedet/common/cedet.el")
;; Enabling projects

(global-ede-mode t)
;; Enabling code helpers

(semantic-load-enable-gaudy-code-helpers)
;; Enabling additional features for names completion

(require 'semantic-ia)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; 2: SCROLLING


;;; Buffer corrections

(setq scroll-conservatively 5)
(setq scroll-margin 5)
(setq scroll-preserve-screen-position 1)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; 3: INDENTATION RULES


;;; To set the mod-2 indentation used when you hit the TAB key

(setq c-basic-offset 2)
;;; To cause the TAB file-character to be interpreted as mod-4 indentation

(setq tab-width 4)
;;; To cause TAB characters to not be used in the file for compression, and for only spaces to be used

(setq-default indent-tabs-mode nil)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; 4: KILL RING INTEGRATION IN X CLIPBOARD


; (transient-mark-mode 1)  ; Now on by default: makes the region act quite like the text "highlight" in many apps.

; (setq shift-select-mode t) ; Now on by default: allows shifted cursor-keys to control the region.

(setq mouse-drag-copy-region nil)  ; stops selection with a mouse being immediately injected to the kill ring

(setq x-select-enable-primary nil)  ; stops killing/yanking interacting with primary X11 selection

(setq x-select-enable-clipboard t)  ; makes killing/yanking interact with clipboard X11 selection


;; these will probably be already set to these values, leave them that way if so!

(setf interprogram-cut-function 'x-select-text)
(setf interprogram-paste-function 'x-cut-buffer-or-selection-value)

; You need an emacs with bug #902 fixed for this to work properly. It has now been fixed in CVS HEAD.

; it makes "highlight/middlebutton" style (X11 primary selection based) copy-paste work as expected

; if you're used to other modern apps (that is to say, the mere act of highlighting doesn't

; overwrite the clipboard or alter the kill ring, but you can paste in merely highlighted

; text with the mouse if you want to)

(setq select-active-regions t) ;  active region sets primary X11 selection

(global-set-key [mouse-2] 'mouse-yank-primary)  ; make mouse middle-click only paste from primary X11 selection, not clipboard and kill ring.


;; with this, doing an M-y will also affect the X11 clipboard, making emacs act as a sort of clipboard history, at

;; least of text you've pasted into it in the first place.

(setq yank-pop-change-selection t)  ; makes rotating the kill ring change the X11 clipboard.


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; 5: GENERIC INTERFACE UPDATES


;;; Disable toolbar

(tool-bar-mode -1)

;;; No startup message

(setq inhibit-startup-message t)

;;; Filenames in frametitle

(setq frame-title-format "%b")

;;; NO BEEPING MOTHERFUCKER

(setq visible-bell t)

;;; Show matching parentesis

(show-paren-mode 1)

;; Column & line numbers in mode bar

(column-number-mode t)
(line-number-mode t)

;;; Call find-file-dialog with C-x M-f

(defadvice find-file-read-args (around find-file-read-args-always-use-dialog-box act)
  "Simulate invoking menu item as if by the mouse; see `use-dialog-box'."
  (let ((last-nonmenu-event nil))
    ad-do-it))
(global-set-key (kbd "C-x M-f") 'menu-find-file-existing)

(custom-set-variables
  ;; custom-set-variables was added by Custom.

  ;; If you edit it by hand, you could mess it up, so be careful.

  ;; Your init file should contain only one such instance.

  ;; If there is more than one, they won't work right.

 '(initial-scratch-message nil))
(custom-set-faces
  ;; custom-set-faces was added by Custom.

  ;; If you edit it by hand, you could mess it up, so be careful.

  ;; Your init file should contain only one such instance.

  ;; If there is more than one, they won't work right.

 '(default ((t (:inherit nil :stipple nil :background "#ffffff" :foreground "#000000" :inverse-video nil :box nil :strike-through nil :overline nil :underline nil :slant normal :weight normal :height 87 :width normal :foundry "unknown" :family "Liberation Mono")))))
(put 'upcase-region 'disabled nil)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
over 5 years ago

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

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

Решение данной проблемы довольно простое. Спасибо aackose за статью "Large
CSS background moves on resize, in IE 8
"

Допустим, у нас есть такая разметка:

<...> 
<body> 
    <div class="page-wrapper"> 
    <div class="page"> 
        <... контент здесь...> 
        </div> 
    </div> 
    <...> 
</body> 
<...> 

И такие стили:

.page-wrapper { 
    width: 100%; 
    background: white url('../img/bkg.png') center top no-repeat; 
} 
.page { 
    width: 960px; 
    margin: 0 auto; 
}

При такой разметке как раз и наблюдается данный баг. Если окно браузера явно
шире чем 960px, то всё выглядит так, как надо — полоса .page расположена
посередине фонового изображения. Если окно браузера уже, то .page начинает
выравниваться по левому краю, а фоновая картинка продолжает выравниваться по
центру viewport'а.

Чтобы избавиться раз и навсегда от этой проблемы, надо добавить тот же фон к .page:

.page { 
    width: 960px; 
    margin: 0 auto; 
    background: white url('../img/bkg.png') center top no-repeat; 
} 

Трюк в том, что у блока-обёртки выставлена ширина 100%, а у полосы контента — 960px.