...

суббота, 11 января 2014 г.

Замена ctags для Perl в mooedit

В редакторе mooedit есть плагин для вывода имён исходника. Использует он стандартный ctags, у которого с Perl работа, мягко говоря, не фонтан. Находит ctags только имена функций, а хотелось бы большего:




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



ctags -u --fields=afksS --excmd=number -f 'временный_файл' 'файл с исходником'

Никакого явного указания на то, что это Perl, здесь нет. Поэтому отлавливать ситуацию будем с помощью утилиты file. Создадим файл ~/bin/ctags, который будет вызываться вместо системного ctags:



#!/bin/bash

FILE=`file $6 2>&1`
RX='Perl.*'
if [[ "$FILE" =~ $RX ]] ; then
~/bin/perltags $6 > $5
else
/usr/bin/ctags $*
fi


Теперь надо подумать, что из себя будет представлять ~/bin/perltags. В принципе, пользователям vim знакомы утилиты pltags и perltags, но и они меня совсем не удовлетворили. В CPAN нашлась утилита perl-tags. Но для использования вместе с mooedit её всё равно пришлось бы допиливать напильником, поэтому (да и just for fun) решил написать своё.


Сначала разберёмся с форматом. После запуска ctags редактор ожидает такие строки:



имя файл номер_строки;" kind

kind (в терминологии потрохов mooedit) — это тип имени (f — функция, v — переменная, etc).


В хвост номера строки добавлены два символа (;") — это не опечатка, без них редактор просто падает (видимо, ошмёток после --excmd=number).


С этом вроде всё, теперь нужно понять чем именно парсить. Совсем уж глубокий анализ исходников нам не нужен, но и руками разбирать исходник — не комильфо. Поэтому берём PPI, и через какое-то время появляется


вот такой вот скриптик:


#!/usr/bin/perl

# ------------------------------------------------------------------------------
use 5.010;
use strict;
use PPI;

my %variables;
my %scheduled;
my %subs;

# ------------------------------------------------------------------------------
die "Usage: $0 file\n" unless $ARGV[0];
my $doc = PPI::Document->new( $ARGV[0] );
die "'$ARGV[0]', PPI::Document error!\n" unless $doc;

# ------------------------------------------------------------------------------
my @tokens = $doc->children;
foreach my $token ( @tokens )
{
given ( $token->class )
{
process_statement( $token ) when 'PPI::Statement';
process_variable( $token ) when 'PPI::Statement::Variable';
process_sub( $token ) when 'PPI::Statement::Sub';
process_scheduled( $token ) when 'PPI::Statement::Scheduled';
}
}

print_names( \%variables, 'v' );
print_names( \%subs, 'f' );
print_names( \%scheduled, 'p' );

# ------------------------------------------------------------------------------
sub add_name
{
my ( $list, $token, $content ) = @_;

my $name = $token->content;
$list->{$name} = () unless exists $list->{$name};
$list->{$name}->{ $token->line_number } = $content;
}

# ------------------------------------------------------------------------------
sub print_names
{
my ( $list, $type ) = @_;

foreach my $name (
sort {
my $an = $a; $an = $1 if $a =~ /^[\$\%\@](.+)$/;
my $bn = $b; $bn = $1 if $b =~ /^[\$\%\@](.+)$/;
lc $an cmp lc $bn;
} keys $list )
{
foreach my $line ( sort { $a <=> $b } keys $list->{$name} )
{
print "$name:$line\t$ARGV[0]\t$line;\"\t$type\n";
}
}
}

# ------------------------------------------------------------------------------
# @EXPORT = qw(aaa), @EXPORT_OK = qw(bbb);
# ------------------------------------------------------------------------------
sub process_statement
{
my ( $tok ) = @_;

my @tokens = $tok->children;
return unless $#tokens > 0;
foreach my $token ( @tokens )
{
add_name( \%variables, $token, $tok->content )
if $token->class eq 'PPI::Token::Symbol';
}
}

# ------------------------------------------------------------------------------
# sub aaa($$$);
# sub aaa{};
# ------------------------------------------------------------------------------
sub process_sub
{
my ( $tok ) = @_;

my @tokens = $tok->children;
return unless $#tokens > 1;
shift @tokens;
foreach my $token ( @tokens )
{
next
if $token->class eq 'PPI::Token::Whitespace'
or $token->class eq 'PPI::Token::Comment'
or $token->class eq 'PPI::Token::Pod';
return unless $token->class eq 'PPI::Token::Word';
add_name( \%subs, $token, $tok->content );
last;
}
}

# ------------------------------------------------------------------------------
# my $aaa;
# our ($aaa, $bbb);
# ------------------------------------------------------------------------------
sub process_variable
{
my ( $tok ) = @_;

my @tokens = $tok->children;
foreach my $token ( @tokens )
{
process_variable( $token ), next if $token->class eq 'PPI::Structure::List';
process_variable( $token ), next if $token->class eq 'PPI::Statement::Expression';
add_name( \%variables, $token, $tok->content )
if $token->class eq 'PPI::Token::Symbol';
}
}

# ------------------------------------------------------------------------------
# BEGIN {}; CHECK, UNITCHECK, INIT, END
# ------------------------------------------------------------------------------
sub process_scheduled
{
my ( $tok ) = @_;

my @tokens = $tok->children;
return unless $#tokens > 0;
add_name( \%scheduled, $tokens[0], $tok->content );
}

# ------------------------------------------------------------------------------



Что он умеет:



  • Находить имена функций, в том числе и при объявлениях

  • Находить имена глобальных переменных, в том числе и их вхождения в выражения

  • Находить блоки BEGIN, END etc




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


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Пара полезных исключений из правил по форматированию исходного кода

Плоский дизайн (flat design), это сейчас модно и красиво. Внесем же наш маленький вклад в общую тенденцию, применим немного flat-форматированного кода



Отступ лесенкой




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

{
if (условие)
{
for(цикл)
{
}
}
}




Это правило обычно соблюдается абсолютно. Но можно записать код вот так:

{
if (условие)
for(цикл)
{
}
}




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

Double-if




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

if (условие)
if (условие)
{
}




Да-да, вы не поверите, ему все удалось. С тех пор можно писать конструкции

if (a | b | c)
if (d | e)
for(цикл)
if (условие)
{
}




Это не опечатка, два if один за другим и без отступа! double-if, это полный аналог оператора &&.

вместо



if ((a | b | c)
&& (d | e))
{
for(цикл)
{
if (условие)
{
}
}
}




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

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Дайджест интересных материалов из мира веб-разработки и IT за последнюю неделю № 91 (5 — 11 января 2014)

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




Метки лучше разделять запятой. Например: общение, социальные сети, myspace.com, подростки, мердок


или закрыть

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


[Перевод] Как использовать Memcached с Ruby on Rails в Ubuntu 12.04 LTS

Memcached — система для кэширования объектов в памяти, которая работает очень быстро. Использование Memcached может значительно увеличить скорость работы Rails-приложения с минимальными затратами.
Предварительные условия



Предполагается, что в вашей системе уже установлены Ruby on Rails и Memcached. Если это не так, то вам помогут ссылки, приведенные ниже:

Также предполагается, что у вас есть запущенное Rails-приложение, которое вы планируете оптимизировать с помощью Memcached.



Установка гема «Dalli»




Первое, что мы должны сделать — это установить гем Dalli от Mike Perham:

gem install dalli




Если вы используете Bundler, то добавьте строку gem 'dalli' в Gemfile и выполните установку bundle install.

Далее мы проделаем очень короткий путь по настройке взаимодействия Rails и Memcached.

Конфигурирование Rails




В первую очередь, для настройки взаимодействия Rails и Memcached необходимо отредактировать файл config/environments/production.rb. Добавим следующую строку, которая указывает Rails использовать для кэширования Dalli:

config.cache_store = :dalli_store




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

config.action_controller.perform_caching = true




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

Настройка Rails-приложения




Нам необходимо выполнить настройку Rails-приложения для дальнейшей работы с Memcached. Есть два основных способа настройки и о них мы поговорим ниже.
Добавление заголовков (Cache-Control) для управления кэшем



Самый простой способ заключается в добавлении заголовков (Cache-Control) для управления кэшем к одному из ваших экшенов. Это позволит Rack::Cache сохранять результат работы экшена в Memcached. В этом случае экшен в app/controllers/slow_controller.rb должен содержать следующее:

def slow_action
sleep 15
# todo - print something here
end




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

def slow_action
expires_in 5.minutes
sleep 15
# todo - print something here
end




Теперь, когда вы выполните этот экшен повторно, то должны заметить, что скорость работы значительно возросла. Rails будет выполнять код экшена только один раз в пять минут для обновления Rack::Cache.

Обратите внимание на то, что Cache-Control заголовки будут общедоступными. Если у вас есть экшены, которые должны быть видны лишь определенным пользователям, то используйте expires_in 5.minutes, :public => false. Вы также должны определить наиболее оптимальное время кэширования для вашего приложения. Для каждого из приложений это оптимальное время может быть разным и должно быть определено опытным путем.

Подробнее об HTTP-кэшировании можно прочитать в статье Mark Nottingham Руководство по кэшированию для веб-авторов и вебмастеров.


Хранение объектов в Memcached



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

def slow_action
slow_object = create_slow_object
end




Вы можете сохранить результат в Memcached следующим образом:

def slow_action
slow_object = Rails.cache.fetch(:slow_object) do
create_slow_object
end
end




В этом случае Rails будет запрашивать у Memcached объект с ключом 'slow_object'. Если объект уже существует (был ранее сохранен в память), он будет возвращен. В противном случае, выполнится содержимое блока и результат запишется в 'slow_object'.
Кэширование фрагментов



Кэширование фрагментов — это одна из особенностей Rails, позволяющая кэшировать самые динамичные (часто меняющиеся) части вашего приложения. Вы можете кэшировать любые части ваших видов, заключая их в блок cache:

<% # app/views/managers/index.html.erb %>
<% cache manager do %>
Manager s Direct Reports:
<%= render manager.employees %>
<% end %>

<% # app/views/employees/_employee.html.erb %>
<% cache employee do %>
Employee Name: <%= employee.name %>
<%= render employee.incomplete_tasks %>
<% end %>

<% # app/views/tasks/_incomplete_tasks.html.erb %>
<% cache task do %>
Task: <%= task.title %>
Due Date: <%= task.due_date %>
<% end %>




Данная техника кэширования именуется, как Русские матрешки. Rails будет кэшировать все эти фрагменты в Memcached. С тех пор, как мы добавили имя модели в качестве параметра вызова метода cache, ключи этих моделей в кэше будут меняться лишь тогда, когда модель будет изменяться. Эта проблема будет особенно актуальной, если нам нужно обновить модель «task»:

@task.completed!
@task.save!




Итак, мы имеем вложенный кэш объектов и Rails ничего не знает о сроке жизни кэша фрагментов, привязанных к моделям. В этом случае нам поможет ключ ActiveRecord touch:

class Employee < ActiveRecord::Base
belongs_to :manager, touch: true
end

class Task < ActiveRecord::Base
belongs_to :employee, touch: true
end




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

Существует еще одна дополнительная проблема кэширования по типу «Русских матрешек»: при развертывании нового приложения Rails не знает, когда нужно проверять обновление вида. Допустим, вы обновили «task» следующим образом:



<% # app/views/tasks/_incomplete_tasks.html.erb %>
<% cache task do %>
Task: <%= task.title %>
Due Date: <%= task.due_date %>
<p><%= task.notes %></p>
<% end %>




Rails не умеет обновлять кэш фрагментов при использовании частичных шаблонов. Ранее, чтобы обойти эту проблему, необходимо было добавлять номер версии в метод cache. Сейчас эту проблему можно решить с использованием гема cache_digests, который автоматически добавляет MD5-хэш к ключу кэша. После обновления частичного шаблона и перезапуска приложения ключ кэша также обновится и Rails будет вынужден выполнить новую генерацию шаблона вида. Он также умеет обрабатывать зависимости между файлами шаблонов таким образом, что если, допустим, был изменен частичный шаблон _incomplete_tasks.html.erb, то будут обновлены все зависимые шаблоны по цепочке вверх.

Эта особенность была учтена в Rails 4.0. Если вы используете более раннюю версию, то необходимо установить гем, используя команду:



gem install cache_digests




Если вы используете Bundler, то добавьте следующую строку в Gemfile:

gem 'cache_digests'


Расширенная настройка Rails и Memcached




Гем «Dalli» является очень мощным решением и предоставляет возможность работы с ключами в кластере Memcached серверов. Он умеет распределять нагрузку, тем самым увеличивая потенциал Memcached. Если у вас есть несколько серверов, то вы можете установить на каждом из них Memcached, а затем добавить соответствующую настройку в файл конфигурации config/environments/production.rb:

config.cache_store = :dalli_store, 'web1.example.com', 'web2.example.com', 'web3.example.com'




Данная настройка позволит использовать consistent hashing для распределения ключей между доступными Memcached серверами.

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Игра в 30 команд Ассемблера


/*
* Author: godAlex (C) 2014
* License GNU GPL v 3
* param: -h -a -c -sa -sc -sh
*/
#include <cstdlib>
#include "iostream"
#include "string.h"
using namespace std;
//-------------------------------------------

//#define show_debug 1

#define win_x 1
#define win_0 2
#define win_end 3
#define win_gameNext 0

unsigned short KEYBOARD_BYTE_OFFSET[] = {0x40,0x80,0x100,0x8,0x10,0x20,0x1,0x2,0x4 }; //{0b001000000,0b010000000,0b100000000,0b000001000,0b000010000,0b000100000,0b000000001,0b000000010,0b000000100 };
unsigned short BOT_POSITION_PRIORITY[] =
{ 0x10, 0x40, 0x4, 0x100, 0x1, 0x80, 0x2, 0x8, 0x20};
// center bounds other
//{0b000010000, 0b001000000,0b000000100,0b100000000,0b000000001,0b010000000,0b000000010,0b000001000,0b000100000 };

#define CASE_BLOCK_SIZE 9
#define TEXT_BLOCK_SIZE 16
int Text_Block_Size=13; // 13, если завершение строки 13, если 13,10 то 16. Изменяется при выводе asm.
char End_Of_String=0;

unsigned short WIN_MATRIX[] = { 0x1C0, 0x38, 0x7, 0x124, 0x92, 0x49, 0x111, 0x54 }; //{ 0b111000000, 0b000111000, 0b000000111, 0b100100100, 0b010010010, 0b001001001, 0b100010001, 0b001010100 };
unsigned short MATRIX_FULL = 0x1FF; //0b111111111;
unsigned short STRING_ENTER_POS = 0x124;//0b100100100;

int testWon(unsigned short MAP_X,unsigned short MAP_0) {
for (int i=0; i<8; i++) {
if ( (MAP_X & WIN_MATRIX[i]) == WIN_MATRIX[i] ) return win_x;
if ( (MAP_0 & WIN_MATRIX[i]) == WIN_MATRIX[i] ) return win_0;
}
if ( (MAP_X | MAP_0) == MATRIX_FULL ) return win_end;
return win_gameNext;
}
void printField(unsigned short MAP_X,unsigned short MAP_0, char* result) {
//char result[TEXT_BLOCK_SIZE]="...\n...\n...\n";
int p=0;
for (unsigned int bOfs=1; bOfs<MATRIX_FULL; bOfs=bOfs<<1) { // shl TODO test owerflow!
if ( MAP_X & bOfs ) result[p]='X';
else {
if ( MAP_0 & bOfs ) result[p]='0';
else result[p]='.';
}
if ( bOfs & STRING_ENTER_POS ) {
p++;
result[p]='\n';
}
p++;
}
result[p]=End_Of_String;
return result;
}

#define MAX_DATA_SIZE 30000
unsigned int data_addres[MAX_DATA_SIZE][CASE_BLOCK_SIZE]; //= {{0,0,1,0,1,0,1,0},{0,0,1,0,1,0,1,0}};
char text[MAX_DATA_SIZE][TEXT_BLOCK_SIZE]; // = { "Hello","Hello 2" }; // варианты для победы
unsigned int data_pos = 0;

int setVariant(int varID,unsigned int variants[CASE_BLOCK_SIZE],char* txt) //(unsigned int MAP_X,unsigned int MAP_0)
{
int i;
for (i=0;i<CASE_BLOCK_SIZE;i++) data_addres[varID][i]=variants[i]; // TODO memcopy
for (i=0; i<Text_Block_Size && ( txt[i]!=End_Of_String && txt[i]!=0 ) ; i++) text[varID][i]=txt[i];
text[varID][Text_Block_Size-1]=End_Of_String; // если строка не обработана для ассемблера.
#ifdef show_debug
cout<<" set №"<<varID<<" as "<<text[varID]<<endl;
#endif
return varID;
}
int getFreeVar() {
int p=data_pos;
data_pos++;
if (p>MAX_DATA_SIZE) {
cout<<"Owerflow data pos!"<<endl;
p=-1;
}
#ifdef show_debug
else cout<<"New variant №"<<p<<endl;
#endif
return p;
}

int itrGameHod_all(unsigned int MAP_X,unsigned int MAP_0, bool playOn_X, int *doRecord);

/**
* Возвращает номер ячейки для победы или ничьей или хотя бы какую-либо.
* Играет за нолик (0)
* Тип возвращаемой позиции - int [0,8], без по битной адресации.
*/
int getBestBotHod(unsigned int MAP_X,unsigned int MAP_0) { // TODO непобедимый bot был чтобы.
unsigned int lastMAP_X=MAP_X;
unsigned int lastMAP_0=MAP_0;
unsigned int full_map = MAP_X | MAP_0;

int winLevel=-1; // уровень вероятности победы
int winPos=-1;
//*
for (int i=0; i<9; i++) { // победить
unsigned short p = 1<<i;
if ( (full_map & p) == 0 ) {
int w=testWon( MAP_X, MAP_0 | p );
if (w==win_0) {
winLevel=4;
winPos=p;
}
}
}
// TODO ...
//if (winLevel<1)
for (int i=0; i<9; i++) { // все клетки игрового поля
unsigned short p = 1<<i; // TODO BOT_POSITION_PRIORITY[i];
if ( (full_map & p) == 0 ) { // для всех пустых клеток
MAP_0 |= p; // if (playOn_X) MAP_X |= p; else MAP_0 |= p;
int tmpWLvl=0;
int w=testWon(MAP_X,MAP_0);
if ( w!=win_gameNext ) {
if (w==win_0) tmpWLvl=40;
} else {
w=itrGameHod_all(MAP_X,MAP_0, true, 0x00);
if (w & win_0) tmpWLvl+=4;
if (w & win_end) tmpWLvl+=2;
if (w & win_x) tmpWLvl+=0;
}
if (tmpWLvl>winLevel) { //|| (tmpWLvl==winLevel && (rand() % 3 == 1))) {
winLevel=tmpWLvl;
winPos=i;
}
MAP_X=lastMAP_X;
MAP_0=lastMAP_0;
}
}
return winPos;
}

unsigned int winWariantCashe[4]={0,0,0,0}; // Вариантов с победой слишком много, их можно объединить, сократив место.
unsigned int winWariantVer[4]={0,0,0,0}; // Сколько исходов с победой, для статистики
unsigned int setEndGameVariant(unsigned int MAP_X,unsigned int MAP_0, char* txt)
{
unsigned int currentRecordID;
int wonIs = testWon(MAP_X,MAP_0);
winWariantVer[wonIs]++;
if (winWariantCashe[wonIs]==0) {
unsigned int addres[CASE_BLOCK_SIZE]={0,0,0,0,0,0,0,0,0}; // адреса (на каждую кнопку)
currentRecordID=getFreeVar(); // получение свободного адреса
setVariant(currentRecordID,addres, txt);
winWariantCashe[wonIs]=currentRecordID;
}
else currentRecordID=winWariantCashe[wonIs];
/* было
unsigned int addres[CASE_BLOCK_SIZE]={0,0,0,0,0,0,0,0,0}; // адреса (на каждую кнопку)
//currentText= printField (MAP_X,MAP_0); // можно выводить победное поле и текст о победе, заняв 2 и более соседних адреса.
int currentRecordID=getFreeVar();
setVariant(currentRecordID,addres, txt);
//*/
return currentRecordID;
}

/*
* bot играет за 0
* перебор всех вариантов свободных
* playOn_X - 0=за нолик, иначе за крестик
* doRecords - вызывать функцию addNewWariant или только тестировать исходы (==0) Чтобы включить - нужен адрес переменной, в которую запишется номер новой записи
*/
int itrGameHod_all(unsigned int MAP_X,unsigned int MAP_0, bool playOn_X, int *doRecord)
{
unsigned int lastMAP_X=MAP_X;
unsigned int lastMAP_0=MAP_0;
unsigned int full_map = MAP_X | MAP_0;
int winResult=0;

if (playOn_X) { // user, все варианты
for (int i=0; i<CASE_BLOCK_SIZE; i++) {
unsigned int p = 1<<i;
if ( (full_map & p) == 0 ) { // для всех пустых клеток
MAP_X |= p; //if (playOn_X) MAP_X |= p; else MAP_0 |= p;
int w=testWon(MAP_X,MAP_0);
if (w!=win_gameNext) {
winResult |= w;
} else {
w=itrGameHod_all(MAP_X,MAP_0, !playOn_X, 0x00); // iteration
}
MAP_X=lastMAP_X;
MAP_0=lastMAP_0;
}
}
} else { // компьютер, лучший для него вариант
int i=getBestBotHod(MAP_X,MAP_0);
unsigned short p = 1<<i;
if ( (full_map & p) == 0 ) { // для всех пустых клеток
MAP_0 |= p;
int w=testWon(MAP_X,MAP_0);
if (w!=win_gameNext) winResult |= w;
else w=itrGameHod_all(MAP_X,MAP_0, !playOn_X, 0x00); // iteration
MAP_0=lastMAP_0;
}
}

if (doRecord==0) return winResult; // если просто проверка
// TODO в отдельную функцию, или совместить

unsigned int addres[CASE_BLOCK_SIZE]; // адреса (на каждую кнопку)
char currentText[Text_Block_Size]; // текстовое сообщение на этот вариант
printField (MAP_X,MAP_0,currentText);

int currentRecordID=getFreeVar();

if (playOn_X) { // за человека
for (int i=0; i<CASE_BLOCK_SIZE; i++) {
unsigned int p = KEYBOARD_BYTE_OFFSET[i]; //1<<i;
if ( (full_map & p) == 0 ) { // для всех пустых клеток
MAP_X |= p; //if (playOn_X) MAP_X |= p; else MAP_0 |= p;
int w=testWon(MAP_X,MAP_0);
if (w!=win_gameNext) {
// out wo is won
if (w==win_x) addres[i]=setEndGameVariant(MAP_X,MAP_0, "You are won!");
if (w==win_end) addres[i]=setEndGameVariant(MAP_X,MAP_0, "Not win. End");
} else {
int p2Int=getBestBotHod(MAP_X,MAP_0); // за 0, то есть за !playOn_X
// TODO if p2Int != -1
short p2Bit=1<<p2Int;
MAP_0 |= p2Bit;
int w=testWon(MAP_X,MAP_0);
if (w!=win_gameNext) {
// out wo is won
if (w==win_0) addres[i]=setEndGameVariant(MAP_X,MAP_0, "Bot won. 0");
if (w==win_end) addres[i]=setEndGameVariant(MAP_X,MAP_0, "Not win. End");
//addres[i]=0;// TODO addres end game
} else {
// add new wariant
int nextDataAddr;
w=itrGameHod_all(MAP_X,MAP_0, playOn_X, &nextDataAddr); // iteration
addres[i] = nextDataAddr;
}
}
MAP_X=lastMAP_X;
MAP_0=lastMAP_0;
} else {
addres[i]=currentRecordID;// TODO addres current data
}
}
currentRecordID=setVariant(currentRecordID, addres,currentText);
*doRecord=currentRecordID;
} else {
cout<<"Error! Этот вариант вызова не предусмотрен."<<endl;
}
return winResult;
}

void outDataFormatC() {
int minimalCaseSize=1;
if (data_pos>0xff) minimalCaseSize=2;
if (data_pos>0xffff) minimalCaseSize=4;
cout<< "// Out data on C array:"<<endl;

cout<<"int data_pos = 0; // max="<<data_pos<<endl;
cout<<"int CASE_BLOCK_SIZE = "<<CASE_BLOCK_SIZE<<";"<<endl;
cout<<"int TEXT_BLOCK_SIZE = "<<Text_Block_Size<<";"<<endl;

cout<<"unsigned "; //data_addres
if (minimalCaseSize==1) cout<<"char";
if (minimalCaseSize==2) cout<<"short";
if (minimalCaseSize==4) cout<<"int";
cout<<" data_addres["<<data_pos<<"]["<<CASE_BLOCK_SIZE<<"] = {";
for ( int i=0; i<data_pos; i++) {
if (i!=0) cout<<", ";
cout<<"{";
for ( int j=0; j<CASE_BLOCK_SIZE; j++) {
if (j!=0) cout<<", ";
cout<<data_addres[i][j];
}
cout<<"}";
}
cout<<"};"<<endl;

//text
cout<<"char text["<<data_pos<<"]["<<Text_Block_Size<<"] = {";
for ( int i=0; i<data_pos; i++) {
if (i!=0) cout<<", ";
// числами
//cout<<"{";
//for ( int j=0; j<TEXT_BLOCK_SIZE; j++) {
// if (j!=0) cout<<", ";
// cout<< (int)text[i][j];
//}
//cout<<"}";

//cout<< "\""<<text[i]<<"\""<<endl; // вывод строкой без спец сиволов

cout<<"\"";
for ( int j=0; j<Text_Block_Size; j++) {
if (text[i][j]>=30) cout<< text[i][j];
else {
if (text[i][j]=='\n') cout<<"\\n";
else if (text[i][j]==0) cout<<"\\0";
else cout<< "\\("<<(int)text[i][j]<<")";
}
}
cout<<"\"";
}
cout<<endl;
cout<<"// ---- end of data ----"<<endl;

}

void outDataFormatAsm()
{
int minimalCaseSize=1;
if (data_pos>0xff) minimalCaseSize=2;
if (data_pos>0xffff) minimalCaseSize=4;

cout<<"; Out data on assembler data format"<<endl;
cout<<"data_pos DW 0 ; max="<<data_pos<<endl;
cout<<"CASE_BLOCK_SIZE DW "<<CASE_BLOCK_SIZE*minimalCaseSize<<" ;bytes ("<<minimalCaseSize<<" byte per 1 case)"<<endl;
cout<<"TEXT_BLOCK_SIZE DW "<<Text_Block_Size<<endl;
cout<<"data_addres:"<<endl; //data_addres
for ( int i=0; i<data_pos; i++) {
if (minimalCaseSize==1) cout<<"DB ";
if (minimalCaseSize==2) cout<<"DW ";
if (minimalCaseSize==4) cout<<"QW "; // data_pos wrote as DW, see up
for ( int j=0; j<CASE_BLOCK_SIZE; j++) {
if (j!=0) cout<<", ";
cout<<data_addres[i][j];
}
cout<<endl;
}
cout<<endl;
//text
cout<<"text_data: \n";
bool textMarker=false;
for ( int i=0; i<data_pos; i++) {
cout<<"DB ";
int maxOutBytes=Text_Block_Size;
for ( int j=0; j<maxOutBytes; j++) {
if (text[i][j]>=30) {
if (!textMarker) {
if (j!=0) cout<<", ";
cout<<"\"";
textMarker=true;
}
cout<< text[i][j];
}
else {
if (textMarker) {
cout<<"\"";
textMarker=false;
}
if (text[i][j]=='\n') {
cout<<", 13,10";
maxOutBytes--;
}
/*
else if (text[i][j]==0) cout<<", \""<<End_Of_String<<"\""; // FIXME откуда нули?
else // TODO это только под DOS int 21h.
*/
else if (text[i][j]==0) cout<<", \" \""; // FIXME откуда нули?
else // TODO это только под DOS int 21h.
cout<< ", "<<(int)text[i][j];
}
}
if (textMarker) {
cout<<"\"";
textMarker=false;
}
cout<<endl;
}
cout<<"; ---- end of data ----"<<endl;
return;
}

/**
*
* @param showInfo выводить дополнительные сведения о данных
*/
void generator_game_data(bool showInfo) {
if (showInfo) cout<<"Start generation."<<endl;
for ( int i=0; i<data_pos; i++) for ( int j=0; j<Text_Block_Size; j++) text[i][j]=End_Of_String;
int startP;
itrGameHod_all(0,0, true, &startP);
if (showInfo) {
cout<< "Finish generation. Start game position="<<startP<<endl;
cout<< "Data length = "<<data_pos<<endl;
int minimalCaseSize=1;
if (data_pos>0xff) minimalCaseSize=2;
if (data_pos>0xffff) minimalCaseSize=4;
cout<< " key array size is "<<(data_pos*CASE_BLOCK_SIZE*minimalCaseSize)<<" byte ("<<minimalCaseSize<<" byte per case)"<<endl;
cout<< " text array size is "<<(data_pos*Text_Block_Size*sizeof(char))<<" byte"<<endl;
cout<< " Вероятность исходов: ничья "<<winWariantVer[win_end]<<", побед 0 "<<winWariantVer[win_0]<<", X "<<winWariantVer[win_x] <<endl;
}
}
//-------------------------------------------
void outListingC()
{
cout<<"/*"<<endl;
cout<<"* example short command tic-tac-toe. By godAlex generator."<<endl;
cout<<"*/"<<endl;
cout<<"#include <stdio.h>"<<endl;
cout<<"#include <stdlib.h>"<<endl;
outDataFormatC();
cout<<"int main(int argc, char** argv) {"<<endl;
cout<<" while (true) {"<<endl;
cout<<" printf(text[data_pos]);"<<endl;
cout<<" int i;"<<endl;
cout<<" do scanf(\"%i\",&i); while ( i<1 && i>9 );"<<endl;
cout<<" data_pos=data_addres[data_pos][i-1];"<<endl;
cout<<" }"<<endl;
cout<<" return (EXIT_SUCCESS);"<<endl;
cout<<"}"<<endl;
}
void outListingAsm()
{
cout<<"SECTION .text"<<endl;
cout<<"org 0x100 ; .com файл"<<endl;
// cout<<"push cs"<<endl;
// cout<<"pop ds ; без этого тоже работает"<<endl;
cout<<"lblShowVariant: "; //<<endl;
cout<<" mov ax,0x0001 ; clear screen, set graphic mode 40x25, color"<<endl;
cout<<" int 10h"<<endl;
/*
; DOS
; mov ax, [data_pos]
; mov bx, [TEXT_BLOCK_SIZE]
; mul bx
; mov dx, text_data
; add dx,ax ; offset на текст
; mov ah, 0x9 ; print [dx]
; int 0x21 ; dos, dx-указывает на строку, завершающуюся $
*/
//; BIOS
cout<<" mov ax, [data_pos]"<<endl;
cout<<" mov bx, [TEXT_BLOCK_SIZE]"<<endl;
cout<<" mul bx"<<endl;
cout<<" mov bp, text_data"<<endl;
cout<<" add bp,ax ; offset на текст"<<endl;
cout<<" mov cx,[TEXT_BLOCK_SIZE]"<<endl;
cout<<" mov ax,1300h"<<endl;
cout<<" mov bx,0eh ; color"<<endl;
cout<<" mov dx,0500h ; 5 строка 0 позиция для вывода"<<endl;
cout<<" int 10h"<<endl;
cout<<"lReadKey: "; //<<endl;
cout<<" rep nop ; можно убрать, добавлена в надежде снизить нагрузку на ЦП"<<endl;
cout<<" xor ax,ax"<<endl;
cout<<" int 16h ; BIOS read key"<<endl;
cout<<" xor ah,ah"<<endl;
cout<<" sub al,'1' ; в al число запишем, а не символ"<<endl;
cout<<" cmp ax,8"<<endl;
cout<<" ja lReadKey"<<endl;
cout<<" shl ax,1 ; ax=ax*2"<<endl;
cout<<" mov bx, data_addres"<<endl;
cout<<" add bx,ax ; bx = data_addres[key]"<<endl;
cout<<" mov ax, [data_pos]"<<endl;
cout<<" mov cx, [CASE_BLOCK_SIZE]"<<endl;
cout<<" mul cx ; cx = [data_pos]"<<endl;
cout<<" add bx,ax ; bx = data_addres[data_pos][key]"<<endl;
cout<<" mov ax,[bx]"<<endl;
cout<<" mov [data_pos],ax ; переход на новый ключ."<<endl;
cout<<" jmp lblShowVariant"<<endl;
/*
;mov ax, 0x4c00; DOS EXIT, но у нас игра не закончится ))
;int 0x21
*/

cout<<"SECTION .data"<<endl;

// TODO если вставить код 07 в сообщения о победе - будет звуковой сигнал (PC speaker))
outDataFormatAsm();
}
void outListingHTML()
{
cout<<"<html>"<<endl;
cout<<"<head>"<<endl;
cout<<"<!-- 0 строк JS -->"<<endl;
cout<<"<script type=\"text-javascript\">"<<endl;
cout<<"</script>"<<endl;
cout<<"<style>"<<endl;
cout<<" div {"<<endl;
cout<<" height: 100%"<<endl;
cout<<" }"<<endl;
cout<<"</style>"<<endl;
cout<<"</head>"<<endl;
cout<<"<body>"<<endl;
for ( int i=0; i<data_pos; i++) {
cout<<"<div id=\"p"<<i<<"\">"<<endl;
if (text[i][0]=='X' || text[i][0]=='0' || text[i][0]=='.') {
cout<<"<table border=\"border\">"<<endl;
cout<<"<tr>"<<endl;
cout<<" <td><a href=\"#p"<<data_addres[i][0]<<"\">"<<text[i][6+2]<<"</a></td>"<<endl;
cout<<" <td><a href=\"#p"<<data_addres[i][1]<<"\">"<<text[i][7+2]<<"</a></td>"<<endl;
cout<<" <td><a href=\"#p"<<data_addres[i][2]<<"\">"<<text[i][8+2]<<"</a></td>"<<endl;
cout<<"</tr><tr>"<<endl;
cout<<" <td><a href=\"#p"<<data_addres[i][3]<<"\">"<<text[i][3+1]<<"</a></td>"<<endl;
cout<<" <td><a href=\"#p"<<data_addres[i][4]<<"\">"<<text[i][4+1]<<"</a></td>"<<endl;
cout<<" <td><a href=\"#p"<<data_addres[i][5]<<"\">"<<text[i][5+1]<<"</a></td>"<<endl;
cout<<"</tr><tr>"<<endl;
cout<<" <td><a href=\"#p"<<data_addres[i][6]<<"\">"<<text[i][0]<<"</a></td>"<<endl;
cout<<" <td><a href=\"#p"<<data_addres[i][7]<<"\">"<<text[i][1]<<"</a></td>"<<endl;
cout<<" <td><a href=\"#p"<<data_addres[i][8]<<"\">"<<text[i][2]<<"</a></td>"<<endl;
cout<<"</tr>"<<endl;
cout<<"</table>"<<endl;
}
else cout<<"<a href=\"#p"<<data_addres[i][0]<<"\">"<<text[i]<<"</a>"<<endl;
cout<<"</div>"<<endl;
}
cout<<"</body>"<<endl;
cout<<"</html>"<<endl;
}

int main(int argc, char** argv) {
int outFormat=-1; // 0 - assembler data, 1 - C array
if (argc==2) {
if ( strcmp(argv[1],"--help")==0 ) {
cout<<"Неверное количество аргументов. Введите параметр --help для справки."<<endl;
cout<<" --help вывод этой справки."<<endl;
cout<<" -a вывод данных в формате для ассемблера (по умолчанию)"<<endl;
cout<<" -с вывод данных в формате С массива"<<endl;
cout<<" -sa вывод готовой программы на ассемблере"<<endl;
cout<<" -sс вывод готовой программы на C"<<endl;
cout<<" -sh вывод в HTML"<<endl;
return 0;
}
if ( strcmp(argv[1],"-a")==0 ) outFormat=0;
if ( strcmp(argv[1],"-c")==0 ) outFormat=1;
if ( strcmp(argv[1],"-sa")==0 ) outFormat=2;
if ( strcmp(argv[1],"-sc")==0 ) outFormat=3;
if ( strcmp(argv[1],"-sh")==0 ) outFormat=4;
} else outFormat=0; // Установка формата по умолчанию. default - assembler out (0-asm, 1-C array))
if ( argc>2 || outFormat==-1 ) {
cout<<"Неверное количество или значения аргументов. Введите параметр --help для справки."<<endl;
return 1;
}

if ( outFormat==0 || outFormat==2 ) {
//End_Of_String = '$'; // вариант конца строки для DOS
Text_Block_Size=13+3;
}
generator_game_data( outFormat<2);

if (outFormat==0) outDataFormatAsm();
if (outFormat==1) outDataFormatC();

if (outFormat==2) outListingAsm();
if (outFormat==3) outListingC();
if (outFormat==4) outListingHTML();

return 0;
}




This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Удобная генерация URL (ЧПУ). Laravel 4 + сторонние пакеты

Хотелось бы поделиться удобными инструментами для генерации URL и примерами их использования.

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


Ссылки на инструменты


Этого нам вполне хватит.


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




Автоматическая генерация уникальных URL для записей в таблицу БД для доступа к ним по /resource/unique-resource-url вместо /resource/1.


Приступаем




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

Начнем с того, что создадим новый проект:



composer create-project laravel/laravel habr_url --prefer-dist




Далее откываем composer.json в корне habr_url и вносим пакеты в require:

{
"name": "laravel/laravel",
"description": "The Laravel Framework.",
"keywords": ["framework", "laravel"],
"license": "MIT",
"require": {
"laravel/framework": "4.1.*",
"ivanlemeshev/laravel4-cyrillic-slug": "dev-master",
"cviebrock/eloquent-sluggable": "1.0.*",
"way/generators": "dev-master"
},
"autoload": {
"classmap": [
"app/commands",
"app/controllers",
"app/models",
"app/database/migrations",
"app/database/seeds",
"app/tests/TestCase.php"
]
},
"scripts": {
"post-install-cmd": [
"php artisan optimize"
],
"post-update-cmd": [
"php artisan clear-compiled",
"php artisan optimize"
],
"post-create-project-cmd": [
"php artisan key:generate"
]
},
"config": {
"preferred-install": "dist"
},
"minimum-stability": "dev"
}




"way/generators": "dev-master" добавим для быстрого прототипирования.

После выполняем комманду composer update в консоли, а после успешно установленных пакетов вносим изменения в app/config/app.php:



<?php

return array(
// ...
'providers' => array(
// ...
'Ivanlemeshev\Laravel4CyrillicSlug\SlugServiceProvider',
'Cviebrock\EloquentSluggable\SluggableServiceProvider',
'Way\Generators\GeneratorsServiceProvider',
),
// ...
'aliases' => array(
// ...
'Slug' => 'Ivanlemeshev\Laravel4CyrillicSlug\Facades\Slug',
'Sluggable' => 'Cviebrock\EloquentSluggable\Facades\Sluggable',
),
);
?>




Класс Slug даст нам возможность генерировать URL из киррилицы, так как стандартный класс Str умеет работать только с латиницей. О Sluggable я расскажу чуть позже.
Генерируем код


php artisan generate:scaffold create_countries_table --fields="name:string:unique, code:string[2]:unique"
php artisan generate:scaffold create_cities_table --fields="name:string, slug:string:unique, country_id:integer:unsigned"
php artisan generate:scaffold create_products_table --fields="name:string, slug:string:unique, price:integer, city_id:integer:unsigned"




Изменяем новые файлы, добавляя внешних ключей:

// файл app/database/migrations/ХХХХ_ХХ_ХХ_ХХХХХХ_create_cities_table.php
class CreateCitiesTable extends Migration {
// ...
public function up()
{
Schema::create('cities', function(Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('slug')->unique();
$table->integer('country_id')->unsigned()->index();
$table->foreign('country_id')->references('id')->on('countries')->onDelete('cascade');
$table->timestamps();
});
}
// ...
}



// файл app/database/migrations/ХХХХ_ХХ_ХХ_ХХХХХХ_create_products_table.php
class CreateProductsTable extends Migration {
// ...
public function up()
{
Schema::create('products', function(Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('slug')->unique();
$table->integer('price');
$table->integer('city_id')->unsigned()->index();
$table->foreign('city_id')->references('id')->on('cities')->onDelete('cascade');
$table->timestamps();
});
}
// ...
}




А так же добавим несколько Стран и Городов в БД через seeds. Открываем папку app/database/seeds и изменяем два файла:

// файл app/database/seeds/CountriesTableSeeder.php
class CountriesTableSeeder extends Seeder {

public function run()
{
$countries = array(
array('name' => 'Россия', 'code' => 'ru'),
array('name' => 'Украина', 'code' => 'ua')
);

// Uncomment the below to run the seeder
DB::table('countries')->insert($countries);
}

}



// файл app/database/seeds/CitiesTableSeeder.php
class CitiesTableSeeder extends Seeder {

public function run()
{
// Uncomment the below to wipe the table clean before populating
// DB::table('cities')->truncate();

$cities = array(
array('name' => 'Москва', 'slug' => Slug::make('Москва'), 'country_id' => 1),
array('name' => 'Санкт-Петербург', 'slug' => Slug::make('Санкт-Петербург'), 'country_id' => 1),
array('name' => 'Киев', 'slug' => Slug::make('Киев'), 'country_id' => 2),
);

// Uncomment the below to run the seeder
DB::table('cities')->insert($cities);
}

}




Тут используется Slug::make($input), который принимает $input как строку и генерирует из нее что-то на подобии moskva или sankt-peterburg.

Теперь изменяем настройки БД:



// файл app/config/database.php
return array(
// ...
'connections' => array(
// ...
'mysql' => array(
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'habr_url',
'username' => 'habr_url',
'password' => 'habr_url',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
),
),
// ...
);




И вносим схему и данные в БД.

php artisan migrate --seed




И вот что мы получили:



Добавим в модели связей и дополним правила для аттрибутов:



// файл app/models/Product.php
class Product extends Eloquent {
protected $guarded = array();

public static $rules = array(
'name' => 'required|alpha_num|between:2,255',
'slug' => 'required|alpha_num|between:2,255|unique:products,slug',
'price' => 'required|numeric|between:2,255',
'city_id' => 'required|exists:cities,id'
);

public function city()
{
return $this->belongsTo('City');
}
}



// файл app/models/City.php
class City extends Eloquent {
protected $guarded = array();

public static $rules = array(
'name' => 'required|alpha_num|between:2,255',
'slug' => 'required|alpha_num|between:2,255|unique:cities,slug',
'country_id' => 'required|exists:countries,id'
);

public function country()
{
return $this->belongsTo('Country');
}

public function products()
{
return $this->hasMany('Product');
}
}




// файл app/models/Country.php
class Country extends Eloquent {
protected $guarded = array();

public static $rules = array(
'name' => 'required|alpha_num|between:2,255|unique:countries,name',
'code' => 'required|alpha|size:2|unique:countries,code'
);

public function cities()
{
return $this->hasMany('City');
}

public function products()
{
return $this->hasManyThrough('Product', 'City');
}
}




Перепишем методы store в CitiesController и ProductsController.

// файл app/models/CitiesController.php
class CitiesController extends BaseController {
// ...
public function store()
{
$input = Input::all();
$input['slug'] = Slug::make(Input::get('name', '')); // !добавлено
$validation = Validator::make($input, City::$rules);

if ($validation->passes())
{
$this->product->create($input);

return Redirect::route('products.index');
}

return Redirect::route('products.create')
->withInput()
->withErrors($validation)
->with('message', 'There were validation errors.');
}
// ...
}



// файл app/models/ProductsController.php
class ProductsController extends BaseController {
// ...
public function store()
{
$input = Input::all();
$input['slug'] = Slug::make(Input::get('name', '')); // !добавлено
$validation = Validator::make($input, Product::$rules);

if ($validation->passes())
{
$this->product->create($input);

return Redirect::route('products.index');
}

return Redirect::route('products.create')
->withInput()
->withErrors($validation)
->with('message', 'There were validation errors.');
}
// ...
}




И уберем из app/views/cities/create.blade.php, app/views/cities/edit.blade.php, app/views/products/create.blade.php, app/views/products/edit.blade.php соответствующие елементы формы.

Отлично, URL генерируются, но что будет в случает с их дублированием? Возникнет ошибка. А чтобы этого избежать — при совпадении slug нам прийдется добавить префикс, а если префикс ужде есть — то инкрементировать его. Работы много, а элегантности нет. Чтобы избежать этих телодвижений воспользуемся пакетом Eloquent Sluggable.


Первым делом скинем себе в проект конфигурацию для Eloquent Sluggable:



php artisan config:publish cviebrock/eloquent-sluggable




В конфигурационном файле, который находится тут app/config/cviebrock/eloquent-sluggable/config.php изменим опцию 'method' => null на 'method' => array('Slug', 'make'). Таким образом, задача перевода из киррилических символов в транслит и создания URL возложится на класс Slug (вместо стандартного Str, который не умеет работать с киррилицей) и его метод make.

Чем хорош этот пакет? Он работает по такому принцыпу: ожидает, события eloquent.saving*, который отвечает за сохранение записи в БД, и записывает в поле, которое указано в настройках Модели сгенерированный slug. Пример конфигурации:



// файл app/models/City.php
class City extends Eloquent {
protected $guarded = array();

public static $rules = array(
'name' => 'required|alpha_num|between:2,255',
'country_id' => 'required|exists:countries,id'
);

// Настройка генерации
public static $sluggable = array(
'build_from' => 'name',
'save_to' => 'slug',
);

public function country()
{
return $this->belongsTo('Country');
}

public function products()
{
return $this->hasMany('Product');
}
}




При совпадении с уже существующим slug, в новый будет добавлен префикс -1, -2, и так далее. К тому же, мы можем избавиться от не нужного правила для slug и в методе CitiesController@store убрать строчку $input['slug'] = Slug::make(Input::get('name', ''));.

То же сделаем и для Product:



// файл app/models/Product.php
class Product extends Eloquent {
protected $guarded = array();

public static $rules = array(
'name' => 'required|alpha_num|between:2,255',
'price' => 'required|numeric|between:2,255',
'city_id' => 'required|exists:cities,id'
);

public static $sluggable = array(
'build_from' => 'name',
'save_to' => 'slug',
);

public function city()
{
return $this->belongsTo('City');
}
}




Еще более интересную вещь мы можем сделать с этим slug, если перепишем $sluggable в Модели City таким образом:

// файл app/models/City.php
class City extends Eloquent {
protected $guarded = array();

public static $rules = array(
'name' => 'required|alpha_num|between:2,255',
'slug' => 'required|alpha_num|between:2,255|unique:cities,slug',
'country_id' => 'required|exists:countries,id'
);

public static $sluggable = array(
'build_from' => 'name_with_country_code',
'save_to' => 'slug',
);

public function country()
{
return $this->belongsTo('Country');
}

public function products()
{
return $this->hasMany('Product');
}

public function getNameWithCountryCodeAttribute() {
return $this->country->code . ' ' . $this->name;
}
}




Да, мы можем выбрать не существующее поле из Объекта, и добавить его как хелпер.

Немного изменив CitiesTableSeeder добъемся желаемого результата:



// файл app/database/seeds/CitiesTableSeeder.php
class CitiesTableSeeder extends Seeder {

public function run()
{
// Uncomment the below to wipe the table clean before populating
// DB::table('cities')->truncate();

$cities = array(
array('name' => 'Москва', 'country_id' => 1),
array('name' => 'Санкт-Петербург', 'country_id' => 1),
array('name' => 'Киев', 'country_id' => 2),
);

// Uncomment the below to run the seeder
foreach ($cities as $city) {
City::create($city);
}
}

}




Теперь откатим миграции и зальем их по новой вместе с данными:

php artisan migrate:refresh --seed



Добавим немного маршрутов:



// файл app/routes.php
// ...
Route::get('country/{code}', array('as' => 'country', function($code)
{
$country = Country::where('code', '=', $code)->firstOrFail();

return View::make('products', array('products' => $country->products));
}));

Route::get('city/{slug}', array('as' => 'city', function($slug)
{
$city = City::where('slug', '=', $slug)->firstOrFail();

return View::make('products', array('products' => $city->products));
}));

Route::get('product/{slug}', array('as' => 'product', function($slug)
{
$product = Product::where('slug', '=', $slug)->firstOrFail();

return View::make('product', compact('product'));
}));




И добавим несколько шаблонов:

<!-- файл app/views/nav.blade.php -->
<ul class="nav nav-pills">
@foreach(Country::all() as $country)
<li><a href="{{{ route('country', $country->code) }}}">{{{ $country->name }}}</a>
@endforeach
</ul>



<!-- файл app/views/products.blade.php -->
@extends('layouts.scaffold')

@section('main')

@include('nav')

<h1>Products</h1>

@if ($products->count())
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>City</th>
</tr>
</thead>

<tbody>
@foreach ($products as $product)
<tr>
<td><a href="{{{ route('product', $product->slug)}}}">{{{ $product->name }}}</a></td>
<td>{{{ $product->price }}}</td>
<td><a href="{{{ route('city', $product->city->slug) }}}">{{{ $product->city->name }}}</a></td>
</tr>
@endforeach
</tbody>
</table>
@else
There are no products
@endif

@stop



<!-- файл app/views/product.blade.php -->
@extends('layouts.scaffold')

@section('main')

@include('nav')

<h1>Product</h1>

<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>City</th>
</tr>
</thead>

<tbody>
<tr>
<td>{{{ $product->name }}}</td>
<td>{{{ $product->price }}}</td>
<td>{{{ $product->city->name }}}</td>
</tr>
</tbody>
</table>

@stop




На этом все.

Демо и Git


Ошибки, как обычно в личку. Предложения и критику — в комментарии. Спасибо за внимание.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Virgin Galactic провела третий сверхзвуковой полёт, достигнув новой высоты


сегодня в 20:56


image

Космический корабль SpaceShipTwo компании Virgin Galactic во время третьего сверхзвукового полёта достиг новой высоты — 21,6 км, что на 600 метров выше, чем во время прошлого полёта, пишет The Verge. Корабль благополучно вернулся на землю под руководством старшего пилота Дэйва Маккея.


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



Ранее Virgin Galactic обещала запустить свои первые коммерческие космические рейсы в 2014 году, и у неё осталось меньше года для того, чтобы сдержать обещание. Компания однако уверена, что успеет в срок.


«С каждым лётным испытанием мы постепенно приближаемся к нашей цели запустить коммерческую эксплуатацию в 2014 году», — говорит генеральный директор Virgin Galactic Джордж Уайтсайдс.


После запуска корабля основатель компании Ричард Брэнсон также подтвердил обещание, сказав: «2014 год будет годом, когда мы, наконец, отправим наш прекрасный космический корабль в его естественную среду — космос».





Свежий взгляд

на бег


протестируй кроссовки

нового поколения




Developers, stick with Russians – work in London




Переводы с

карты на карту


Переводы

через QR-Код


Новая функция

«Мой контроль»




Возьми Lumia 925 на тест-драйв сейчас.




Впечатляющие возможности

в стильном тонком корпусе из металла




Boomburum

исследует LTE


Эволюция средств связи

в путешествии по России




Проблемы коммуникации внутри бизнеса?



Смотри бесплатные курсы

и выиграй Xbox




Нет времени

на счета?


MasterCard

Mobile



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


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Установка Atlassian STASH, грабли при установке в CentOS

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

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

Фактически данная статья это вольный перевод Getting Started руководства с сайта Atlassian в разделе установки в Linux.


Возможно кому то пригодится.



Примечание:

Все команды ниже вводятся в коммандной строке под пользователем root

Данный вариант установки рекомендован для тестовой эксплуатации! Для запуска сервера в промышленную эксплуатацию необходимо выполнить ряд дополнительных мер. Это всё расписано в статье по установке.

Позже постараюсь дополнить статью порядком перевода сервера в боевой режим


Вначале нам нужно проверить наличие нужных версий дополнительного ПО. Тут мы можем увидеть поддерживаемые версии.


1. Java




Сначала нам предлагается проверить версию Java установленную на вашем сервере


java -version



Поставил OpenJDK версия 1.7.0 (java-1.7.0-openjdk.x86_64), всё было отлично, за исключением того, что сколько я не ждал доступ к web-странице отсутствовал. Просто отображалась белая пустая страница.


Решение проблемы с Java
Пришлось снести OpenJDK и поставить OracleJDK. Его скачиваем отсюда.

Через волшебный Google откопал занятное замечание:


Stash Fails to Startup after installation when Running on OpenJDK

Symptoms

The following exception is reported when starting up Stash:

A fatal error has occurred

The following problem occurred which prevents Atlassian Stash from starting correctly:

OpenJDK 64-Bit Server VM is an unsupported JVM.

Cause

OpenJDK is not a supported environment for Stash.

Resolution

Switch to the latest version of Oracle JDK to help resolve the problem.





Правда у меня то всё стартануло с OpenJDK и никак не руганулось, но всё равно не заработало. Странненько в общем.


2. Git




Так же проверяем версию установленного пакета:


git --version





Вот тут меня ждала засада. В CentOs 6.5 в base репозитории находится git 1.7.1, а минимальное требование: 1.7.6+

Всё было бы отлично, но тут сразу сошлись все факторы моего малого опыта работы с CentOS и yum.

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


rpm --import apt.sw.be/RPM-GPG-KEY.dag.txt

rpm -K rpmforge-release-0.5.3-1.el6.rf.*.rpm

rpm -i rpmforge-release-0.5.3-1.el6.rf.*.rpm





Везде где я находил рекомендации по его добавлению указано, что необходимо перевести rpmforge в enable.

Сделал.

yum update

и ничего не обновилось.

yum install git

git так и остался старой версии.

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

достаточно активировать репозиторий [rpmforge-extras] в файле /etc/yum.repos.d/rpmforge.repo

Выставляем enable=1, и вот удача:


# git --version

git version 1.7.12.4






3.Perl




С этим в CentOS 6.5 оказалось всё в порядке.


# perl --version

This is perl, v5.10.1 (*) built for x86_64-linux-thread-multi





минимальные требования: >= 5.8.8

4.Stash




Теперь ставим сам Stash.

Качаем архив дистрибутивом с этой страницы. И разворачиваем архив в желаемое место установки. Например в /usr/local/stash Это у нас будет \<Stash installation directory\>

Перед запуском нам необходимо поправить файл \<Stash installation directory\>/bin/setenv.sh

Раскомментируем параметр STASH_HOME и укажем место для хранения данных STASH

например



STASH_HOME="/home/stash-home-data"





!!! ВАЖНО!!!

Разработчики советуют не помещать данные в папку \<Stash installation directory\>

Это связано с тем, что когда потребуется обновить STASH, то данные, в папке \<Stash installation directory\> , будут перезаписаны.


Всё, установка завершена. Теперь просто запускаем stash.

перейдите в папку в которую установлен stash (это у нас \<Stash installation directory\>)


bin/start-stash.sh





Если всё запустилось корректно, то мы увидим


# bin/start-stash.sh

To run Stash in the foreground, start the server with start-stash.sh -fg

Starting Atlassian Stash as current user


Detecting JVM PermGen support…

PermGen switch is supported. Setting to 256m\n

Using STASH_HOME: /home/stash-home-data

Using CATALINA_BASE: /usr/local/etc/stash/atlassian-stash-2.10.1

Using CATALINA_HOME: /usr/local/etc/stash/atlassian-stash-2.10.1

Using CATALINA_TMPDIR: /usr/local/etc/stash/atlassian-stash-2.10.1/temp

Using JRE_HOME: /usr

Using CLASSPATH: /usr/local/etc/stash/atlassian-stash-2.10.1/bin/bootstrap.jar:/usr/local/etc/stash/atlassian-stash-2.10.1/bin/tomcat-juli.jar

Using CATALINA_PID: /usr/local/etc/stash/atlassian-stash-2.10.1/work/catalina.pid


Success! You can now use Stash at the following address:


localhost:7990/


If you cannot access Stash at the above location within 3 minutes, or encounter any other issues starting or stopping Atlassian Stash, please see the troubleshooting guide at:


confluence.atlassian.com/display/STASHKB/Troubleshooting+Installation



Как и указано, доступ к web интерфейсу появляется не сразу, а в течении нескольких минут. Это связано с подготовкой данных.

Далее мы открываем http://<ip(dns)_вашего_сервера>:7990/ и следуем через мастер установки. Этот процесс я описывать не буду, т.к. там всё элементарно.


PS: Обсуждение статьи и замечания жду в комментариях.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Парня арестовали за приглашение вступить в Google+


сегодня в 19:30


То, что эксперты по вопросам приватности в интернете называют «наихудшим сценарием», произошло с 32-летним жителем штата Массачусетс (США). Его арестовали и посадили за решётку на основании приглашения присоединиться к сети Google+, отправленного в адрес девушки, для которой действует запретительный судебный приказ. Это означает, что ему запрещено звонить ей, приближаться к дому и прочее — стандартная защита против сексуального преследования.

Томас Ганьон (Thomas Gagnon) уверяет, что не писал никакого письма, а оно отправлено автоматически. Это не спасло его от ночи в камере.



Девушка распечатала приглашение Google+ и с копией запретительного приказа отправилась в полицию. Там согласились, что подобное приглашение является нарушением судебного запрета — и через 90 минут парня арестовали прямо в собственной квартире. После ночи за решёткой судья согласилась выпустить его под залог $500. Она заявила при этом, что не знает, каким образом рассылаются такие приглашения в сети Google+, и допустила, что письмо действительно было выслано автоматически.


Адвокат Ганьона говорит, что его подопечный ни в чём не виноват, письмо было отправлено без его участия. Более того, в этом деле он сам скорее является жертвой: девушка разорвала с ним отношения на следующий день после того, как Томас сделал ей предложение выйти замуж и подарил обручальное кольцо с бриллиантами стоимостью $4000.


Судебный процесс назначен на 6 февраля. Вероятно, к нему привлекут представителей Google, чтобы они прояснили ситуацию. Если электронное письмо действительно было выслано автоматически, то именно компания Google должна нести ответственность.






3771


3






Свежий взгляд

на бег


протестируй кроссовки

нового поколения




Developers, stick with Russians – work in London




Переводы с

карты на карту


Переводы

через QR-Код


Новая функция

«Мой контроль»




Возьми Lumia 925 на тест-драйв сейчас.




Впечатляющие возможности

в стильном тонком корпусе из металла




Boomburum

исследует LTE


Эволюция средств связи

в путешествии по России




Проблемы коммуникации внутри бизнеса?



Смотри бесплатные курсы

и выиграй Xbox




Нет времени

на счета?


MasterCard

Mobile



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


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


[Из песочницы] Программируем Raspberry Pi на голом железе

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

В чем подвох?




На первый взгляд задача кажется тривиальной: скачиваем keil, создаем проект… Но все не так просто. Все среды программирования(keil, IAR, Atolic) поддерживают максимум ARM9.У нас же ARM11. Это связано с негласным правилом, что на голом железе пишут до ARM9, а после на Линуксе. Но все-таки есть одна лазейка: arm-none-eabi-gcc поддерживает любой ARM.

Вторая проблема заключается в том, что под данный процессор(BCM2835) нет никаких конфигурационных файлов, header'ов и т.д. Здесь нам на помощь придет загрузчик Raspberry Pi. И ничего, что он пропритетарный. Он выполняет две функции: инициализирует процессор и его периферию, а также передает управление ядру kernel.img. Мы просто замаскируем свою программу под ядро и загрузчик её запустит.



Что нам понадобится?




1) Сама Raspberry Pi, карта памяти к ней и питание.

2) Даташит на процессор

3) Компьютер с установленным Linux (но может быть можно и на Винде. Не знаю, не пробовал).

4) Кросскомпилятор, установленный на компьютере из пункта 2. Я использую arm-none-eabi-gcc

5) Содержимое этой папочки.

Приготовления.




Нам нужно отформатировать карту памяти в FAT16 и закинуть на нее содержимое этой папки. Это загрузчик плюс ядро. Затем удаляем оттуда файлы kernel.img и kernel_emergency.img. Это ядро Linux, а оно нам не нужно.

Первая программа.




Теперь мы можем приступить к написанию первой программы. Создаем файл main.c и пишем следующий код


int main (void)

{

while(1)

{

}

}

void exit (void)

{

while(1)

{

}

}





Как видите, эта программа ничего не делает. Функция exit зачем-то нужна компилятору.

Теперь соберем её.


arm-none-eabi-gcc -O2 -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s -nostartfiles main.c -o kernel.elf

arm-none-eabi-objcopy kernel.elf -O binary kernel.img





Полученный файл kernel.img кидаем на карту памяти. Готово!

GPIO




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

Для начала объявим адрес, по которому лежит GPIO( это можно прочитать в даташите).


#define GPIO_BASE 0x20200000UL





И объявим переменную, которая определяют, что порт настроен на выход (GPIO_GPFSEL1) и переменную, подающую низкий уровень(то есть лампочка горит) на порт (GPIO_GPCLR0).


#define GPIO_GPFSEL1 1

#define GPIO_GPCLR0 10





Ну и наконец модифицируем главную функцию для зажигания лампочки:


volatile unsigned int* gpio;

int main(void)

{

gpio = (unsigned int*)GPIO_BASE;

gpio[GPIO_GPFSEL1] |= (1 << 16);

gpio[GPIO_GPCLR0] = (1 << 16);

while(1)

{


}

}



Собираем, прошиваем и радуемся.


В следующей части попробуем поиграться с таймерами и прерываниями.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Идиомы С++. Static visitor

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



Visitor




Для начала вспомним как устроен классический Visitor. Мотивация этого паттерна довольно проста. Представьте себе, что нам в программе нужно обработать контейнер (дерево, граф) полиморфных указателей и выполнить для каждого объекта какой-то набор операций, причем этот набор должен быть разным для каждого конкретного типа. Также стоит отметить, что сами объекты ничего не должны знать об алгоритмах их обработки кроме того, что их может “навестить” обработчик.

Например, объекты файловой системы: файлы, папки:

class abstract_file_t
{
public:
virtual std::string name() const = 0;
virtual void accept(visitor_t& v) = 0;
virtual ~abstract_file_t(){}
};

////////////////////////////////////////////////////////////////////////////

class regular_file_t : public abstract_file_t
{
public:
std::string name() const;
void accept(visitor_t& v);
size_t size();
};

////////////////////////////////////////////////////////////////////////////

typedef std::vector<abstract_file_t*> file_vector_t;
class directory_t : public abstract_file_t
{
public:
void accept(visitor_t& v);
std::string name() const;
file_vector_t& files();
};





Как видите, знание объектов файловой системы о том как с ними будут работать состоит лишь в том, что их может “навестить” объект с базовым типом visitor_t. В функции accept мы просто “впускаем посетителя”:

void regular_file_t::accept(visitor_t& v) {v.visit(*this);}




В случае с каталогом, в accept может быть добавлен код для “посещения” всех находящихся в нем файлов.

“Посетитель” устроен следующим образом:

class visitor_t
{
public:
virtual void visit(regular_file_t& f) = 0;
virtual void visit(directory_t& f) = 0;
virtual ~visitor_t(){}
};

class print_info_visitor_t : public visitor_t
{
public:
void visit(regular_file_t& f);
{
std::cout << "visiting concrete file. file name: " << f.name() <<
" file size: " << f.size() << std::endl;
}
void visit(directory_t& dir)
{
std::cout << "visiting directory. directory name: " << dir.name() <<
". contains " << dir.files().size() << “files” << std::endl;
}
};


Static visitor




Суть Static visitor’а также заключается в отделении данных от алгоритмов обработки этих данных. Основное отличие заключается в том, что динамический полиморфизм классического Visitor’а заменяется на статический (отсюда, собственно, и название идиомы). С одной реализацией этого паттерна мы встречаемся практически каждый раз когда используем алгоритмы STL. Действительно, предикаты STL — отличный пример static visitor’а. Чтобы это стало совершенно очевидно рассмотрим следующий небольшой пример:

class person_t
{
public:
person_t(const std::string& name, size_t age)
: name_(name), age_(age){}

template<typename Visitor>
void accept(Visitor& v) {v.visit(*this);}
size_t age() const {return age_;}
private:
std::string name_;
size_t age_;
};
////////////////////////////////////////////////////////////////////////////////
struct person_visitor_t
{
person_visitor_t(size_t age_limit) : age_limit_(age_limit){}
bool operator()(const person_t& p) {return visit(p);}
bool visit(const person_t& p) {return p.age() < age_limit_;}
size_t age_limit_;
};

////////////////////////////////////////////////////////////////////////////////
int main()
{
std::vector<person_t> person_vec;
person_vec.push_back(person_t("Person 1", 43));
person_vec.push_back(person_t("Person 2", 20));

auto it = std::find_if(
person_vec.begin(), person_vec.end(), person_visitor_t(30));
if(it != person_vec.end())
std::cout << it->age() << std::endl;
return 0;
}




Очень похоже на то, что мы видели в первой главе, не правда ли?

Примеры использования




Boost Graph Library



Идею предиката можно развить. Почему бы нам не дать возможность пользователю изменять поведение наших алгоритмов в некоторых ключевых точках с помощью предоставленного пользователем же “посетителя”? Допустим мы пишем библиотеку для работы с графами, состоящую из структур данных для хранения узлов и ребер и алгоритмов для обработки этих структур (Boost Graph Library). Для максимальной гибкости мы можем предоставлять два варианта каждого алгоритма. Один выполняющий действия по умолчанию и другой — позволяющий пользователю влиять на некоторые шаги алгоритма. Упрощенно это можно представить так:

template<typename T>
struct node_t
{
node_t(){}
// Аналог функции accept
template<typename V>
void on_init(V& v) {v.on_init(t_);}
// Еще один accept
template<typename V>
void on_print(V& v) {v.on_print(t_);}
T t_;
};





Алгоритмы. Одна версия по умолчанию и одна с использованием Visitor’a

template<typename T, typename Graph>
void generate_graph(Graph& g, size_t size);

template<typename T, typename Graph, typename Visitor>
void generate_graph(Graph& g, Visitor& v, size_t size)
{
for(size_t i = 0; i < size; ++i)
{
node_t<T> node;
node.on_init(v);
g.push_back(node);
}
}

////////////////////////////////////////////////////////////////////////////////

template<typename Graph>
void print_graph(Graph& g);

template<typename Graph, typename Visitor>
void print_graph(Graph& g, Visitor& v)
{
for(size_t i = 0; i < g.size(); ++i)
{
g[i].on_print(v);
}
}





Теперь код пользователя.

struct person_t
{
std::string name;
int age;
};

////////////////////////////////////////////////////////////////////////////////
// visitor
struct person_visitor_t
{
// visit()
void on_init(person_t& p)
{
p.name = "unknown";
p.age = 0;
}
// visit()
void on_print(const person_t& p)
{
std::cout << p.name << ", " << p.age << std::endl;
}
};

////////////////////////////////////////////////////////////////////////////////

int main()
{
person_visitor_t person_visitor;

typedef std::vector<node_t<person_t> > person_vec_t;
person_vec_t graph;

generate_graph<person_t>(graph, person_visitor, 10);
print_graph(graph, person_visitor);
}





Variant



Еще один крайне интересный пример применения идиомы static visitor можно найти в boost::variant. Variant представляет собой статически типизированный union. Данные любого допустимого типа хранятся в одном и том же массиве байт. И “посещаем” мы по сути всегда этот, хранящийся внутри variant, массив, но “смотрим” на него каждый раз с точки зрения разных типов. Реализовать это можно как-то так (код максимально упрощен и передает лишь основную идею):

template<
typename T1 = default_param1,
typename T2 = default_param2,
typename T3 = default_param3
>
class variant
{
...
public:

// Хорошо уже знакомый нам accept()
template<typename Visitor>
void apply_visitor(const Visitor& v)
{
switch(type_tag_) // Тэг хранящегося в данный момент типа
{
case 1:
apply1(v, T1());
break;
case 2:
apply2(v, T2());
break;
case 3:
apply3(v, T3());
break;
default:
break;
}
}
};





Функции apply могут выглядеть следующим образом

template<typename Visitor, typename U>
void apply1(
const Visitor& v, U u, typename std::enable_if<
!std::is_same<U, default_param1>::value>::type* = 0)
{
// data_ - массив байт.
// В качестве visit() используется operator()
v(*(T1*)(&data_[0]));
}

// Перегрузка для типа по умолчанию.
template<typename Visitor, typename U>
void apply1(
const Visitor& v, U u, typename std::enable_if<
std::is_same<U, default_param1>::value>::type* = 0)
{
}


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



struct visitor_t
{
void operator()(int i)const ;
void operator()(double d)const;
void operator()(const std::string& s)const;
};

variant<int, double> test_v(34);
test_v.apply_visitor(visitor_t());


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.