Проблема

С недавнего времени стал активно использовать в своей работе Zen Coding. Данная технология в разы ускоряет верстку
страниц. Вот только нормального плагина для любимого Vim не было. Очень хотелось, чтобы аббревиатуры разворачивались
по нажатию клавиши tab. Причем все это должно было нормально работать с плагином snipMate, потому как его я тоже
активно применяю.


Решение

Решение оказалось очень простым. Я решил немного переделать код snipMate, чтобы он разворачивал
аббревиатуры Zen Coding.

Реализация


Для начала, качаем сам плагин snipMate отсюда https://github.com/msanders/snipmate.vim/tarball/master.
Устанавливаем плагин согласно документации
Скачиваем Zen Coding отсюда https://github.com/sergeche/zen-coding/tarball/master.
Распакоовываем архив. Копируем папку /python/zencoding из архива в папку, с файлами Vim в подпапку plugin
Далее открываем файл snipMate.vim и добавляем строки

if snippet == ""
py import zencodingvim, vim
py zencodingvim.run(vim.eval("word"))
let col = col('.') - len(word)
sil exe
's/\V'.escape(word,
'/\.').'\%#//'
return snipMate#expandSnip(snippet, col)
endif


после:

for scope in [bufnr('%')] + split(&ft,
'\.') + ['_']
let [trigger,
snippet] = s:GetSnippet(word, scope)
" If word
is a trigger for a snippet,
delete the trigger & expand
" the snippet.
if snippet !=
''
let col =
col('.') - len(trigger)
sil exe
's/\V'.escape(trigger,
'/\.').'\%#//'
return
snipMate#expandSnip(snippet, col)
endif
endfor


Создаем файл zencodingvim.py в той же папке(plugins) с таким содержанием:

import vim , re, zencoding

def add_placeholders(text):
_ix = [ 0]
def get_ix(m):
_ix[ 0] +=
1
return
'${%s}' % _ix[ 0]


return re.sub(zencoding.utils.get_caret_placeholder(),
get_ix, text)


def run(word):
if vim.eval("&ft")
== 'css':
filetype = 'css'
else:
filetype = 'html'

abbr = zencoding.utils.extract_abbreviation(word)
snippet = zencoding.expand_abbreviation(abbr, filetype,
"xhtml")
snippet = add_placeholders(snippet)
vim.command("let snippet = '%s'"
% snippet)




Результат

Теперь по нажатию клавиши TAB, snipMate сначла ищет snippet в своих запасах, а затем использует Zen Coding.

Решение получилось довольно легким. Если будет желание, можно будет написать полноценный плагин, который будет полностью нативно поддерживать Zen Coding.
Приветствую вас, lanav.ru! Хочу рассказать о своем проекте, который я представлял в этом году на различных молодежных научных конференциях, в том числе в рамках научного молодежного форума “Шаг в будущее” (да, я поддержу популяризацию “Шага”). Целью моего проекта является написание среды, позволяющей программировать на русском языке.

Немного отвлекшись от сути, хочу поведать о том, что побудило меня на разработку проекта. Все дело в том, что программирование в моем городе в рамках школы практически не изучается. За мои 10 лет обучения в школе, наивысшей планкой в кодинге было изучение базовых операторов Бейсика. Cитуация не многим лучше творится и в других обычных школах города. Как в песне — Sad but true. А все потому, что для большинства школьников, на их взгляд, изучение данной области является скучным и бесполезным.

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

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

1. Для начала, русскоязычный программный код режется парсером на токены для того, чтобы было проще все это дело обрабатывать в дальнейшем.
2. Далее в ход вступает модуль-транслятор, переводящий набор токенов в язык Pascal.
3. Компилятором bcc32, нагло позаимствованным из Delphi, генерируется исполняемый файл для дальнейшего использования.
Все элементарно. Разработка велась в Borland Delphi 7.


Пример исходного текста на русском выглядит примерно так:

{
вывести “Привет, мир!”
}

или так:

число и;
{
вывести “Введите значение х”;
ввести и;
вывести “Квадрат числа равен “ + и*и;
}


“Колибри” предоставляет возможности консольного программирования. Но в планах прикрутить возможность создавать визуальные приложения для тех, “кому интересно”.

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

Эта гипотеза подтверждается моим личным опытом. В то время, когда я увлекся программированием, то толкового интернета у нас дома не было, а в книжном книг “Для самых маленьких и тупых” не имелось. Тогда приходилось осваивать целину кодинг по сложным для меня книжкам. Мне было 12.

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

Тестирование “Колибри” было проведено на нескольких группах школьников разной возрастной категории. Результаты меня обнадеживают, что я старался не зря — ученики, увлекшись новой игрушкой, быстро нашкодили хеллоуворлдов и других несложных программ.

На этом хочу окончить свое небольшое сочинение. Спасибо за внимание!
Проблема с шифрование/дешифрование строки на Ruby для меня казалась до сегодняшнего дня очень простой задачей. Во всем изветсном любителям и профессионалам по укладке рельсов учебнике «Agile web development Ruby on Rails» описывается как зашифровать строку с помощью соли и функции Digest::SHA1.digest. Но как расшифровать эту строку нигде не описано. Мало того, потратив пол дня в поисках данного решения, я ничего подобного не нашел, кроме одной фразы на каком-то форуме: «это не возможно». Кто читал данную книгу, помнит, что после того как строка зашифрована, её только проверяют с ведённым паролем пользователем, впоследствии зашифрованным таким же способом. Но расшифровать её никто и не пытался. Правильно — ведь это невозможно!

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

#lfield_encriptor.rb
module FieldEncryptor
module ClassMethods
def safe_fields(*args)
args.each do |a|
define_method(a) do
decrypt_field(a)
end
define_method("#{a}=") do |val|
self.write_attribute(a, val)
encrypt_field(a)
end
end
end
end

def self.included(base)
base.extend(ClassMethods)
end

private
def encrypt_field(field)
self.write_attribute(field, Base64.encode64(self.read_attribute(field)))
end

def decrypt_field(field)
Base64.decode64(self.read_attribute(field))
end
end

Ложим его в lib прокта. Далее его можно заюзать в любой модельке:
class User #
>> u.password = 'test'
=> «test»
>> u.save
=> true
>> u
=> #
>> u.password
=> «test»

Как видно из примера все работает как нужно. В базу сохраняется зашифрованная строка, но обращаясь к атрибуту мы имеем незашифрованную строку.

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

PS. Шифровать и дешифровать строку можно многими методами. Например, есть гемы encryptor, attr_encrypted и т. п. Но есть проблема с сохранением зашифрованной строки с помощью алгоритмов, используемыми этими плагинами, в базу (в частности mysql у меня не получилось). В такой строке постоянно появляется какой-то символ (чёрный ромб с вопросиком внутри), после которого все символы в базе просто обрезается и при выборке получается уже неполная строка. Как и из-за чего это происходит разобраться у меня не получилось. Так или иначе это тема не для рассуждений в этой статье. Надеюсь, моя статья была кому-то полезна. Спасибо за внимание.
J2EE проекты не часто отличаются разнообразием. Как правило, разработчики от проекта к проекту повторяют одни и те же рутинные действия, особенно на начальной стадии. Мы описываем предметную область, пишем скрипты к базе данных, затем создаем соответствующие Java-классы, мапим их на таблицы, пишем дао-методы и сервисы. Естественно, встает вопрос о том, как такие действия можно было бы упростить и не писать каждый раз с нуля тонны кода. Для себя я этот вопрос решил, написав простенькую утилиту для генерации кода. Быть может, кому-то на lanav.ru такой подход покажется интересным.



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

# Entities
@Account(userName: string, email: string, password: string256, showName: string);
@Author(name: string);
@Book(title: string, author: @Author);


Здесь приняты обозначения: # комментирует строку, @ объявляет сущность. В скобках идут поля сущности в виде (имя: тип), тип начинается с @ — значит это сложный тип (отношение «многие к одному»).

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

package = com.hiq.test
src.dir = src
db.dir = db
db.type = postgresql
mapping = jpa


Натравив нашу утилиту на этот маленький файлик из 3 строчек, мы получим sql-скрипт для PostgreSQL, все POJO-классы, замапленные на базу при помощи JPA, а так же по дао-классу для каждой таблицы с простейшими методами(save, findById, getAll и т.д.).

create table ACCOUNT (
ID SERIAL,
primary key(ID),
PASSWORD varchar(256),
EMAIL varchar(256),
USER_NAME varchar(256),
SHOW_NAME varchar(256)
);

create table AUTHOR (
ID SERIAL,
primary key(ID),
NAME varchar(256)
);

create table BOOK (
ID SERIAL,
primary key(ID),
AUTHOR_ID bigint,
TITLE varchar(256)
);

package com.hiq.test.entity;

@Entity
@Table(name = "ACCOUNT")
@SequenceGenerator(name = "accountSeq", sequenceName = "account_id_seq", allocationSize = 1)
public class Account implements IEntity {
private Long id;
private String password;
private String email;
private String userName;
private String showName;

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "accountSeq")
@Column(name = "ID")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}

@Column(name = "PASSWORD")
public String getPassword() {
return this.password;
}

public void setPassword(String password) {
this.password = password;
}

// и так далее
}



Чуть более сложные дао-методы можно дописать в файл нашего проекта следующим образом:

# Entities
@Account(userName: string, email: string, password: string256, showName: string);
@Author(name: string);
@Book(title: string, author: @Author);

# DAOs
$Account {
getByEmailAndPassword: (@, unique, [eq: email, eq: password]),
getAllEmails: (email, list, [], [asc: email])
};


Опять же, вся достаточная информация для метода уместилась в одной строчке. Первый параметр — что получаем, @ — указатель в данном случае на Account, второй параметр — ожидаем одно значение или список, третий параметр — условия выборки, четвертый — сортировки. Теперь утилита помимо стандартных методов добавит в AccountDAO еще 2 описанных метода.

Замечу, кстати, что весь генерируемый код лучше создавать в «заглушках», а потом эти «заглушки» наследовать, так как весь генерированные код будет перезатираться, и вы рискуете потереть свой собственный код, написанный поверх. Например, AccountDAOStub < — AccountDao.

Преимущества такого подхода я вижу в следующем:

1) Сокращение времени разработки, по крайней мере на начальном этапе;
2) Концентрация внимания на предметной области, а не на реализации.
3) Борьба с ошибками. Утилита еще до генерации кода проводит проверку файлу, и может, например, выдать, что был использован недопустимый тип или, что сущности @Author не существует в системе;
4) Гибкость. Мы не привязаны ни к каким деталям реализации. В конфигурационном файле можно поменять тип базы данных и получить другие скрипты, поменять тип маппинга на xml, или на что-то еще. Более того никакой привязки к языку тоже нет, и я не вижу причины, почему бы не получать код, например, на ruby.
5) Модульность. Наиболее часто-используемые парадигмы можно выделять в отдельные файлы. Например, во многих системах требуется регистрация и вход в систему. Поэтому можно было бы выделить сущность @Account и его дао в отдельный файл, и импортировать его в других проектах.
javascript — очень красивый и изящный язык. И для того чтобы хорошо владеть им мало просто знать синтаксис, надо также понимать некоторые тонкости работы движка, а также применять красивые синтаксические конструкции.

И так, меньше слов, больше дела, поехали.

Для того чтобы понять принципы работы методов call() и apply() разберем пример, в котором требуется найти минимальное и максимальное значения элементов массива.

У нас есть массив, в котором определенное число элементов. Первое решение которое приходит в голову — цикл, проходим массив от и до и так далее.

Но есть более красивый вариант:

var a = [ 0, 1, 2, 3 ];

var smallest = function( array ) {

return Math.min.apply( Math, array );
}

var largest = function( array ) {

return Math.max.apply( Math, array );
}

smallest( a ); // Мы получим 0

largest( a ); // А здесь 3


Для решения используем встроенные в javascript функции Math.min() и Math.max(). Вызываем Math.min() через apply(), таким образом мы изменяем контекст этой функции на наш объект (в данном случае этим объектом является массив).

Для изменения контекста функции в javascript предусмотрены два метода call() и apply(). Эти методы автоматически становятся доступными после того как функция инициализирована. Они практически идентичны, но есть небольшие отличия. Кстати, контекст обозначет то, на какой объект ссылается оператор this в функции, внутри которой он вызван.

Рассмотрим пример:

var context = function() {

console.log( this ); // Мы получим глобальный объект "Window"
}

context();


А теперь пробуем изменить контекст функции:

var obj = { prop: 1 };

var context = function() {

console.log( this ); // Мы получим наш объект "Object { prop=1 }"
}

context.apply( obj );


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

var add = function( a, b ) {

return a + b;
}

add( 1, 2 ); // Обычный вызов

add.call( this, 1, 2 ); // call принимает каждый аргумент индивидуально и меняет контекст

add.apply( this, [ 1, 2 ] ); // apply принимает массив аргументов также меняя контекст


Наглядная разница между call и apply.

image

По рисунку можно легко понять, какой параметр соответствует какому именно аргументу.

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

Для проверки примеров рекомендую Вам использовать FireBug.
Речь в данной статье пойдет о системе электронной коммерции с открытым исходным кодом Magento. Эта платформа отличается с одной стороны своей функциональностью, а с другой — своей сложностью. Каждый, кто когда-либо пытался для нее писать, знает, что это сделать не просто. А с наскоку просто невозможно. Необходимо потратить от недели до двух на то, чтобы написать первый модуль, создать первую тему, исправить что-то, что нужно исправить в нужном месте. Хочу поделиться секретом, как это сделать очень быстро и красиво с использованием исключительно встроенных механизмов.
Люди, знакомые с Magento знают, что тема для Magento построена из кусочков темплейтов, за каждым из которых стоит отдельный класс. Это может быть встроенный класс или класс стороннего разработчика. Из-за того, что тот или иной класс на рабочем магазине зачастую может быть перекрыт модулями расширения, то определить, какой блок отвечает за подготовку информации для вывода пользователю, и какой файл темы отвечает за тот или иной участок страницы, становится крайне обременительно по времени.

Мало кто знает, что Magento сама может рассказать какой файл и какой класс отвечает за интересующий вас участок страницы. Для этого нужно пойти в backend по адресу System -> Config -> вкладка Developer -> группа Debug и включить там опции «Template Path Hints» и «Add Block Names to Hints». Хитрость в том, что по умолчанию, вы в этом (как и в любом другом) месте этих опций не найдете. А они появятся там, если вы пойдете в файл:

app/code/core/Mage/Core/etc/system.xml

Там отыскать кусочек кода:



select
adminhtml/system_config_source_yesno
20
0
1
1



select
adminhtml/system_config_source_yesno
21
0
1
1




И поменять его на:



select
adminhtml/system_config_source_yesno
20
1
1
1



select
adminhtml/system_config_source_yesno
21
1
1
1




В вышеописанном месте появятся две нужные нам опции. Что было, что стало? Свойство для этих опций было выставлено в 0. Что значит, что они никогда не покажутся. Мы разрешили им появиться. Включаете и наслаждаетесь. Мне это знание сэкономило очень много трудочасов, которые я рисковал потратить на поиск нужного файла, вместо того, чтобы писать код.
К счастью, для сотруднников техподдержки и тех, тко работает с живыми магазинами, этот рецепт тоже там работает. Только не забывайте ограничивать по ip те адреса, с которых может быть просмотрена отладочная информация.


Спасибо за внимание.
Доброго времени суток, юд! Если ты занимаешься веб-разработкой и держишь свои проекты в сетевом репозитории, читай ниже, где мы вместе напишем bash скрипты для удобной работы с кодом.

Статья рассчитана на новичков, коим я сам являюсь, а профессионалам просьба указать на ошибки и помочь улучшить скрипт.


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

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


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

Inception

Создаем файл для скрипта и даем ему права на исполнение.

$ touch sitecreate.sh
$ chmod +x sitecreate.sh

* This source code was highlighted with Source Code Highlighter.



Инициализация

Открой этот файл любимым текстовым редактором и задай значения базовых переменных.

#!/bin/bash
USER_NAME="korodm"
USER_GROUP="korodm"
WWW_DIR="/home/korodm/Documents/sites/"
USER_REPO="korodm"
PASS_REPO="***"
REPO="bitbucket.org/"
SQL_PASS="***"

* This source code was highlighted with Source Code Highlighter.



Не забудь указать свои имя пользователя, группу и рабочий каталог в той системе, где ты работаешь; имя пользователя, пароль и адрес от сетевого репозитория; а также root-пароль от MySQL.

Валидация

Перед тем, как скрипт начнет выполнять свой основной код, он должен проверить, все ли в порядке.

Права рута

Так как придется выполнять операции, требующие повышенных привилегий (например рестарт Апача), необходимо запускать скрипт от имени root'a или же при помощи sudo. Вот и проверим, как был запущен скрипт.

# Проверка на права рута
if [ "${UID}" -ne 0 ]
then
printf "Root:\t\t\t\t\t\033[0;31mнет\033[0m\n\nПриложение необходимо запустить от имени root!\n"
exit
else
printf "Root:\t\t\t\t\t\033[0;32mда\033[0m\n"
fi

* This source code was highlighted with Source Code Highlighter.



Ты можешь спросить, зачем делать вывод в виде "printf «Root:\t\t\t\t\t\033[0;31mнет\033[0m\n\nПриложение необходимо запустить от имени root!\n»" когда можно сделать просто "echo «Приложение необходимо запустить от имени root!»"? Приятно, когда скрипт выдает лог о каждом своем действии.
Вывод лога

Передаваемые параметры

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

# Проверка на наличие передаваемых параметров
if ! [ -n "${1}" ]
then
printf "Параметры:\t\t\t\t\033[0;31mno\033[0m\n\nПриложение необходимо использовать так:\n%s site_name\n" ${0}
exit
else
printf "Параметры:\t\t\t\t\033[0;32m%s\033[0m\n" ${1}
SITE=${1}
fi

* This source code was highlighted with Source Code Highlighter.



Рабочий каталог

на всякий пожарный надо проверить, существует папка, в которой ты собираешься хранить проекты

# Проверка на существование рабочего каталога
if ! [ -d "${WWW_DIR}" ]
then
printf "Рабочий каталог:\t\t\t\033[0;31mне существует\033[0m\n\nИзмените конфцигурацию скрипта.\n"
exit
else
printf "Рабочий каталог:\t\t\t\033[0;32m%s\033[0m\n" ${WWW_DIR}
fi

* This source code was highlighted with Source Code Highlighter.



Каталог сайта

А что, если сайт с таким именем у тебя уже есть? Не хорошо, если 2 проект смешаются друг с другом, и небходимо этому помешать.

# Проверка на существование каталога для сайта в рабочем каталоге
if [ -d "${WWW_DIR}${SITE}" ]
then
printf "Каталог для сайта:\t\t\t\033[0;31mуже существует\033[0m\n\nИзмените назваение сайта.\n"
exit
else
printf "Каталог для сайта:\t\t\t\033[0;32m%s%s\033[0m\n" ${WWW_DIR} ${SITE}
fi

* This source code was highlighted with Source Code Highlighter.



Репозиторий

Теперь нужно проверить, если у тебя в сетевом хранилище такой сайт? Этот пункт скрипта у всех будет разный, в зависимости от выбранного сервиса для репозиториев. Но сам способ проверки, думаю будет примерно таким же.

# Проверка существования репозитария
REPO_TRUE=`curl "https://${REPO}${USER_REPO}/${SITE}" 2> /dev/null | grep -c "Bitbucket_404.png"`
if [[ "${REPO_TRUE}" -eq 1 ]]
then
printf "Репозитарий:\t\t\t\t\033[0;31mне существует\033[0m\n\nСоздайте репозитарий.\n"
exit
else
printf "Репозитарий:\t\t\t\t\033[0;32mhttps://%s%s/%s\033[0m\n" ${REPO} ${USER_REPO} ${SITE}
fi

* This source code was highlighted with Source Code Highlighter.



Все проверено. Можно работать

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

# Клонируем репозиторий
cd ${WWW_DIR}
hg clone "https://${USER_REPO}:${PASS_REPO}@${REPO}${USER_REPO}/${SITE}" > /dev/null
printf "Клонирование репозитария:\t\t\\033[0;32mзавершено\033[0m\n"
mkdir -p ${WWW_DIR}${SITE}/public_html
chown -R ${USER_NAME}:${USER_GROUP} ${WWW_DIR}${SITE}
printf "Локальная папка:\t\t\t\033[0;32m%s%s/public_html\033[0m\n" ${WWW_DIR} ${SITE}

* This source code was highlighted with Source Code Highlighter.



Виртуальный хост

Сначала настройки

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

# Создание виртуального хоста
printf "
ServerAdmin webmaster@localhost
ServerName ${SITE}
DocumentRoot ${WWW_DIR}${SITE}/public_html


Options FollowSymLinks
AllowOverride all



Options Indexes FollowSymLinks MultiViews
AllowOverride all
Order allow,deny
allow from all


ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/

AllowOverride all
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
Order allow,deny
Allow from all


ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/access.log combined
Alias /doc/ \"/usr/share/doc/\"


Options Indexes MultiViews FollowSymLinks
AllowOverride all
Order deny,allow
Deny from all
Allow from 127.0.0.0/255.0.0.0 ::1/128


\n" > /etc/apache2/sites-available/${SITE}

printf "Файл конфигурации Apache:\t\t\033[0;32mсоздан\033[0m\n"

* This source code was highlighted with Source Code Highlighter.



Активация

a2ensite ${SITE} > /dev/nul
printf "Сайт \033[0;32m%s:\t\t\t\tактивирован\033[0m\n" ${SITE}

* This source code was highlighted with Source Code Highlighter.



IP

# Поиск свободного ip адреса
num=1
LIMIT=255

while [ "${num}" -lt "${LIMIT}" ]
do
if [ `grep -c 127.0.1.${num} /etc/hosts` -ne 1 ]
then
echo "127.0.1.${num} ${SITE}" >> /etc/hosts
break
fi
num=$((num+1))
done

printf "Поиск свободного ip адреса:\t\t\033[0;32m127.0.1.%s\033[0m\n" ${num}

* This source code was highlighted with Source Code Highlighter.



Перезагрузка

# Перезагрузка Apache
/etc/init.d/apache2 reload > /dev/null
printf "Apache:\t\t\t\t\t\033[0;32mперезагружен\033[0m\n"

* This source code was highlighted with Source Code Highlighter.



Почти готово

Чтобы проверить работоспособность проекта, нужно залить в него первый код

# Создание index.php
printf "

Сайт %s работает!


%s%s/public_html/index.php


" ${SITE} ${WWW_DIR} ${SITE} > ${WWW_DIR}${SITE}/public_html/index.php

chown ${USER_NAME}:${USER_GROUP} ${WWW_DIR}${SITE}/public_html/index.php

printf "Индексный файл:\t\t\t\t\033[0;32mhttp://%s/index.php\033[0m\n" ${SITE}

* This source code was highlighted with Source Code Highlighter.



MySQL

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

# Создание базы данных
mysql -uroot -p${SQL_PASS} --execute="create database ${SITE};"
mysql -uroot -p${SQL_PASS} --execute="GRANT ALL PRIVILEGES ON ${SITE}.* TO ${SITE}@localhost IDENTIFIED by '${SITE}' WITH GRANT OPTION;"
printf "База данных:\t\t\t\t\033[0;32m%s\033[0m\n" ${SITE}
printf "Имя пользователя:\t\t\t\033[0;32m%s\033[0m\n" ${SITE}
printf "Пароль:\t\t\t\t\t\033[0;32m%s\033[0m\n" ${SITE}

* This source code was highlighted with Source Code Highlighter.



The End

Вот скрипт и готов.
Все началось с того что я решил подтянуть мат часть, и начать писать хороший код.
А как известно хороший код всегда ассоциируется с TDD и хорошим unit тестированием. Хочу рассказать о своем увлекательном опыте работы с flexunit4.

Всем кто хочет писать на flex лучше — под кат.

Описание задачи


Задача которую я поставил себе — реализация алгоритмов сортировок. Bubble, Selection, QSort.

Начал как это принято с создания интерфейса

public interface ISorter
{

/**
* Main function of the ISorter interface. Takes unordered array and returns ordered array.
* Must be implemeted
*/
function sort(executedArray:Array):Array;
}


Все классы которые реализуют сортировку должны реализовывать этот интерфейс. Например реализация пузырьковой сортировки:

public class BubbleSorter implements ISorter
{
/* INTERFACE com.bgh.sorting.ISorter */
/**
* Implements bubble sorting.
* Starts at the begining of the sorting list and compares all items one by one
* compares 2 firts elements and if 1 is greater then 2 it swaps them.
* a[i] ---> temp
* a[i] ---> a[j]
* a[j] <--- temp
*/
public function sort(executedArray:Array):Array
{
var counter:int = 0;
for (var i:int = 0; i <= executedArray.length; i++ )
{
for (var j:int = 0; j <= executedArray.length; j++ )
{
if (executedArray[i] < executedArray[j])
{
var temp:int = executedArray[i];
executedArray[i] = executedArray[j];
executedArray[j] = temp;
}
counter += 1;
}
}
return executedArray;
}

}

Реализацию QSort и SelectionSort можно глянуть в моем репозитории: bitbucket.org/helland/algotest/

Теперь непосредственно о тестировании


По началу я решил задачу просто в лоб. Написал много много тестов такого вида

public class SortingTestCases
{

[Test( description = "Basic positive check of bubble sorting" )]
public function bubbleSorterPositive():void
{
var bubbleSorter:ISorter = new BubbleSorter();
assertEquals( [1,2,3].toString() , bubbleSorter.sort( [2,1,3] ).toString() );
}


[Test (description = "Checks selection sorting. Positive test")]
public function selectionTestPositiveSort():void
{
var selectionSorter:ISorter = new SelectionSorter();
assertEquals( [1,2,3].toString() , selectionSorter.sort( [2,1,3] ).toString() );
}

[Test (description = "Checks QSort. Positive test case")]
public function QSortPositive():void
{
var qsort:ISorter = new QSort();
assertEquals( [1,2,3].toString() , qsort.sort( [2,1,3] ).toString() );
}
}

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

Решение


Все окозалось достаточно просто. Благо есть Parameterized который дает возможность параметризировать запуск тест кейса, и передавать ему обьект типа ISort при запуске. Вот как это выглядит на практике.
[RunWith("org.flexunit.runners.Parameterized")]
public class SortingTestCases
{
private var foo:Parameterized;


[Parameters]
public static function Sorters():Array
{
return [ [new BubbleSorter()], [new SelectionSorter()], [new QSort()] ] ;
}


[Test (description = "Checks how positive integers are sorted with provided sorters")]
public function positiveIntegersArray():void
{
assertEquals([1, 2, 3, 12, 100, 110, 200].toString() , _sorter.sort( [1, 2, 3, 100, 200, 110, 12] ).toString() );
}



Вот как бы и все. Один тест решает все сходные задачи.
Здравствуйте!

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



Перейдем непосредственно к самому слайдеру.

Подключаем библиотеку jQuery.

Для начала зададим разметку для слайдера:












prev
next



Далее немного CSS (Создание дизайна для слайдера — не сама цель, поэтому особо не уделял времени для оформления, задав только самые необходимые стили):

.cf:after {content:"";display:block;height:0;clear:both;visibility:hidden;width:0;}
.cf {display:block;}
.item { float:left; width:289px; height:175px; margin:0 5px;}
.item img { width:289px; height:175px;}
#galery { width:289px; overflow:hidden; height:175px;}
.galleryIn { position:relative; display: inline-block; margin: 0 auto; text-align: left; height:175px;}


Перейдем непосредственно к самому плагину:

(function( $ ){

$.fn.ScrollGallery = function( options ) {
var slider = this;
var settings = {
prev: '.prevLink',
next: '.nextLink',
item: '.im',
auto: false,
interval: 2,
visible: 3,
widthItem: 100,
count: 3,
speed: 1,
fade: false
};

return this.each(function() {
if ( options ) {
$.extend( settings, options );
}

//Настройки
var countSlider = settings.count * options.widthItem + 'px';
var speedSlider = settings.speed * 1000;
var numberItem = $(options.item, slider).size();
options.interval = options.interval * 1000;
slider.parent().width(options.widthItem * options.visible);

//Определяем размер слайдера
slider.width(numberItem * options.widthItem);
var stop = 0;

//функции прокруток

//fadeIn/fadeOut
function fade (slider, countSlider, counts, itemses) {
//stop = 1;
$(slider).fadeOut(500, function () {
$(slider).animate({top:0, left:'-'+countSlider}, 0, function () {
for (i = 0; i<= (counts - 1); i++)
{
$(itemses, slider).eq(0).appendTo(slider);
}
$(slider).animate({top:0, left:'0px'}, 0);
stop = 0;
});
})

setTimeout(function () {
$(slider).fadeIn(500);
}, 520);

return false;
}

//slideNEXT
function slideNext () {
$(slider).animate({top:0, left:'-'+countSlider}, speedSlider, function () {
for (i = 0; i<= (settings.count - 1); i++)
{
$(options.item, slider).eq(0).appendTo(slider);
}
$(slider).animate({top:0, left:'0px'}, 0);
stop = 0;
});
return false;
}

//slidePREV
function slidePrev () {
$(slider).animate({top:0, left:'-'+countSlider}, 0, function () {

for (i = 0; i<= (settings.count - 1); i++)
{
$(options.item, slider).eq(numberItem - 1).prependTo(slider);
}
});
$(slider).animate({top:0, left:0}, speedSlider, function () {
stop = 0;
});
return false;
}


//прокручиваем слайдер

//автопрокрутка
if (settings.auto === true)
{
setInterval(function () {
if (stop == 0)
{
stop = 1;

if (settings.fade === false)
{
slideNext();
}

if (settings.fade === true)
{
fade (slider, countSlider, settings.count, options.item);
}


}
}, options.interval);

}

$(settings.next).click(function () {
if (stop == 0)
{
stop = 1;
slideNext();
}
});

$(settings.prev).click(function () {
if (stop == 0)
{
stop = 1;
slidePrev();
}
});


});

};
})( jQuery );


Теперь разберем слайдер более детально:

Настройки:

prev: '.prevLink',
next: '.nextLink',
item: '.im',
auto: false,
interval: 2,
visible: 3,
widthItem: 100,
count: 3,
speed: 1,
fade: false



prev — объект по нажатию на который слайдер прокручивается назад
next — объект по нажатию на который слайдер прокручивается вперед
item — класы объектов из которых состоит слайдер
widthItem — ширина объектов из которых состоит слайдер
count — количество элементов, которые будут прокручиваться
speed — скорость слайдера
auto — авто прокрутка (true, false)
interval — интервал автопрокрутки
visible — количество видимых элементов
fade — эффект fadeIn / fadeOut (true, false)

Далее определяем ширину области, которую будем прокручивать (влияет на количество элементов, которые хотим «слайдить»)

var countSlider = settings.count * options.widthItem + 'px';
Скорость слайдера:

var speedSlider = settings.speed * 1000;


Определяем суммарную ширину элементов
var numberItem = $(options.item, slider).size();
slider.width(numberItem * options.widthItem);


Интервал при атвопрокрутке

options.interval = options.interval * 1000;


Задаем количество элементов, которые будут отображаться
slider.parent().width(options.widthItem * options.visible);


Далее идут три основные функции, которые будут осуществлять все действия

Функции для эффекта fadeIn / fadeOut

function fade (slider, countSlider, counts, itemses) {

$(slider).fadeOut(500, function () {
$(slider).animate({top:0, left:'-'+countSlider}, 0, function () {
for (i = 0; i<= (counts - 1); i++)
{
$(itemses, slider).eq(0).appendTo(slider);
}
$(slider).animate({top:0, left:'0px'}, 0);
stop = 0;
});
})

setTimeout(function () {
$(slider).fadeIn(500);
}, 520);

return false;
}

function fadePrev (slider, countSlider, counts, itemses) {
stop = 1;
$(slider).fadeOut(500, function () {
$(slider).animate({top:0, left:'-'+countSlider}, 0, function () {
for (i = 0; i<= (counts - 1); i++)
{
$(itemses, slider).eq(numberItem - 1).prependTo(slider);
}
$(slider).animate({top:0, left:'0px'}, 0);
stop = 0;
});
})

setTimeout(function () {
$(slider).fadeIn(500);
}, 520);

return false;
}


Функция для скольжения вперед (почти аналогична с функцией fadeIn / fadeOut )
function slideNext () {
$(slider).animate({top:0, left:'-'+countSlider}, speedSlider, function () {
for (i = 0; i<= (settings.count - 1); i++)
{
$(options.item, slider).eq(0).appendTo(slider);
}
$(slider).animate({top:0, left:'0px'}, 0);
stop = 0;
});
return false;
}


Функция для скольжения назад

function slidePrev () {
$(slider).animate({top:0, left:'-'+countSlider}, 0, function () {

for (i = 0; i<= (settings.count - 1); i++)
{
$(options.item, slider).eq(numberItem - 1).prependTo(slider);
}
});
$(slider).animate({top:0, left:0}, speedSlider, function () {
stop = 0;
});
return false;
}


Следующие функции обрабатывают событие клик или вызывают предыдущие функции в автоматическом режиме, если параметр auto == true

if (settings.auto === true)
{
setInterval(function () {
if (stop == 0)
{
stop = 1;

if (settings.fade === false)
{
slideNext();
}

if (settings.fade === true)
{
fade (slider, countSlider, settings.count, options.item);
}


}
}, options.interval);

}

$(settings.next).click(function () {
if (stop == 0)
{
stop = 1;
if (settings.fade === false) {
slideNext();
}
if (settings.fade === true) {
fade (slider, countSlider, settings.count, options.item);
}
}
});

$(settings.prev).click(function () {
if (stop == 0)
{
stop = 1;
if (settings.fade === false) {
slidePrev();
}
if (settings.fade === true) {
fadePrev (slider, countSlider, settings.count, options.item);
}
}
});


Ну и наконец пришло время инициализировать наш слайдер на странице :

$('.galleryIn').ScrollGallery({
prev: '.prev',
next: '.next',
item: '.item',
widthItem: 299,
count: 1,
speed:1,
auto: false,
interval: 3,
visible: 1,
fade: true
})
Часто в различных системах, возникает необходимость совершать действия в одной части приложения при изменениях в другой. Задача стоит в информировании всех системы о изменениях в одной из ее частей. Например у нас есть пользователи со статусами, при изменении статуса требуется выполнить ряд действий: разослать сообщение другим, сохранить старый статус в историю изменений. Посмотрим как реализовать такую логику в приложении.

У нас есть класс User:

class User
attr_reader :name
attr_accessor :status

def initialize(name, status)
@name = name
@status = status
end

end


И есть класс News, который рассылает сообщения в системе.

class News
def update(user)
puts("#{user.name} changed his status!")
puts("His status is now #{user.status}!")
end
end


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

class User
attr_reader :name , :status

def initialize(name, status, news)
@name = name
@status = status
@news = news
end

def status=(status)
@status = status
@news. update (self)
end
end

class News
def update (user)
puts("#{user.name} changed his status!")
puts("His status is now #{user.status}!")
end
end


Проверим и получим сообщение о изменении в статусе пользователя от класса news:

news = News.new
alex = User.new('Alex', 'Happy', news)
alex.status = "Sad"


Лучший способ информировать систему о изменениях.


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

class User
attr_reader :name
attr_accessor :status

def initialize(name, status)
@name = name
@status = status
@observers = []
end

def status=(status)
@status = status
notify_observers
end

def notify_observers
@observers.each do |observer|
observer.update(self)
end
end

def add_observer(observer)
@observers << observer
end

def delete_observer(observer)
@observers.delete(observer)
end

end

Теперь любой объект, который хочет узнавать о обновлениях, может быть просто добавлен как наблюдатель (observer) обьекта класса User.

news = News.new
alex = User.new('Alex', 'Happy')
alex.add_observer(news)
alex.status = "Sad"


Теперь мы избавились от неявной связи между классами User и News. Код класс User теперь не зависит от количества объектов заинтересованных в информации о изменениях объекта User. Кроме того объекты класса User могут существовать и совсем без наблюдателей (observers).

Немного рефакторинга

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

module Subject

def initialize
@observers=[]
end

def add_observer(observer)
@observers << observer
end

def delete_observer(observer)
@observers.delete(observer)
end

def notify_observers
@observers.each do |observer|
observer.update(self)
end

end
end

Теперь мы можем просто включить модуль Subject в любой класс

class User
attr_reader :name
attr_accessor :status

def initialize(name, status)
super()
@name = name
@status = statusend
end

def status=(status)
@status = status
notify_observers
end
end

Observers in Rails

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

Плохой пример


class Project < ActiveRecord::Base
after_create :send_create_notifications

private
def send_create_notifications
self.members.each do |member|
ProjectMailer.deliver_notification(self, member)
end
end
end

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

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

class Project < ActiveRecord::Base
# nothing here
end

class NotificationObserver < ActiveRecord::Observer
observe Project

def after_create(project)
project.members.each do |member|
ProjectMailer.deliver_notice(project, member)
end
end
end

Теперь код рассылки сообщений вынесен из класса Project и намного удобнее поддерживать код почтовых оповещений в отдельном классе.