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

31 янв. 2009 г.

T4 - Генератор кода встроенный в Visual Studio

Многие кто сталкивался с DSL Tools и ASP.Net MVC уже знакомы с системой генерации T4 (Text Template Transformation Toolkit), которая в данный момент уже встроена в Visual Studio 2008.

Начнём сразу с примера.

Пример

Открываем студию, создаём проект консольного приложения. Затем создаём файл с именем "MyGenerator.tt"

Примечание: Обращаем внимание на расширение *.tt – это стандартное расширение файлов для T4. Однако, шаблон этого типа файлов отсутствует в стандартном диалоге добавления новых элементов (Add New Item ...), но, наверняка, будет добавлен в новой версии.

После создания можно будет увидеть, что студия файл опознала: появилась иконка в виде текстового документа со стрелочкой (видимо, такая метафора транформации на лету), а также появился вложенный узел с пустым файлом "MyGenerator.cs" - в этом файле по умолчанию будет находится результат генерации.

Если заглянуть в свойства нового файла то можно увидеть такую ситуацию:

С расширением *.tt автоматически связался Custom Tool под названием TextTemplatingFileGenerator.

Примечание: Тем кто занимался созданием своих Custom Tools для студии, известно что  автоматическое связывание определённого расширения файлов с некоторым Custom Tool делается через реестр. В данном случае можно изучить содержимое [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{164B10B9-B200-11D0-8C61-00A0C91E29D5}\.tt] и [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}\.tt].

Напишем небольшой кусочек кода в этом файле:

<#@ template language="C#" hostspecific="true" #>

// Этот файл был сгенерирован.

// Генератор: <#= Host.GetType() #>

// Время генерации: <#= DateTime.Now #>

После сохранения файла, в итоговом файле "MyGenerator.cs":

// Этот файл был сгенерирован.

// Генератор: Microsoft.VisualStudio.TextTemplating.VSHost.TextTemplatingService

// Время генерации: 02/01/2009 00:12:25

Вот и получился минимальный простой генератор кода, который, правда, не делает никакой полезной работы. Как можно заметить, шаблоны T4 пишутся на неком подобии ASP.Net, только используется иной тип скобок, вместо <% %> вставляем <# #>.

Пример генерации C# класса

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

Код шаблона:

<#@ template language="C#v3.5" debug="True" #>

<#@ import namespace="System.Collections.Generic" #>

using System;

using System.Text;

 

namespace GeneratedCode

{

    internal class <#= this.ClassName #>

    {

        public <#= this.ClassName #>()

        {

            Console.WriteLine("ctor");

        }

 

<# foreach(var prop in this.ClassProperties) {#>

        <#= String.Format("public {0} {1} {{ get; set; }}", prop.Key, prop.Value) #>

<#}#>

 

        public override string ToString()

        {

            StringBuilder result = new StringBuilder();

 

<# foreach(var prop in this.ClassProperties) {#>

            result.AppendFormat("{0} = {1}", "<#= prop.Value #>", this.<#= prop.Value #>);

<#}#>       

            return String.Format("<#= this.ClassName #>() {{ {0} }}", result.ToString());

        }

    }

}

<#+ string ClassName = "MyClass"; #>

<#+ Dictionary<string, string> ClassProperties = new Dictionary<string, string>{

    {"string", "StringProperty"},

    {"int", "IntProperty"}

    }; #>

Заглянем "под капот", то есть в код полученного генератора (незначащие детали убраны):

Примечание: Чтобы посмотреть результирующий генератор, необходимо указать в директиве <#@template #> атрибут debug=”True”, затем искать результат в папке %Temp%, там появятся файлы со случайным именем и расширениями *.cs, *.cmdline, *.dll, *.err. *.out. *.pdb.

using System;

using System.Collections.Generic;

using Microsoft.VisualStudio.TextTemplating;

using Microsoft.VisualStudio.TextTemplating.VSHost;

 

namespace Microsoft.VisualStudio.TextTemplatingF99F90EB5000191E2F8B24816A1C3631

{

    public class GeneratedTextTransformation : TextTransformation

    {

        public override string TransformText()

        {

            try

            {

                this.Write("using System;\r\nusing System.Text;\r\n\r\nnamespace GeneratedCode\r\n{\r\n\tinternal class " +

                        "");

                this.Write(ToStringHelper.ToStringWithCulture(this.ClassName));

                this.Write("\r\n\t{\r\n\t\tpublic ");

                this.Write(ToStringHelper.ToStringWithCulture(this.ClassName));

                this.Write("() \r\n\t\t{\r\n\t\t\tConsole.WriteLine(\"ctor\"); \r\n\t\t}\r\n\t\t\r\n");

 

                foreach (var prop in this.ClassProperties)

                {

                    this.Write("\t\t");

                    this.Write(ToStringHelper.ToStringWithCulture(String.Format("public {0} {1} {{ get; set; }}", prop.Key, prop.Value)));

                    this.Write("\r\n");

                }

 

                this.Write("\r\n\t\tpublic override string ToString()\r\n\t\t{\r\n\t\t\tStringBuilder result = new StringB" +

                        "uilder();\r\n\t\t\t\r\n");

 

                foreach (var prop in this.ClassProperties)

                {

                    this.Write("\t\t\tresult.AppendFormat(\"{0} = {1}\", \"");

                    this.Write(ToStringHelper.ToStringWithCulture(prop.Value));

 

                    this.Write("\", this.");

                    this.Write(ToStringHelper.ToStringWithCulture(prop.Value));

                    this.Write(");\r\n");

                }

 

                this.Write("\t\t\r\n\t\t\treturn String.Format(\"");

                this.Write(ToStringHelper.ToStringWithCulture(this.ClassName));

                this.Write("() {{ {0} }}\", result.ToString());\r\n\t\t}\r\n\t}\r\n}\r\n");

            }

            catch (System.Exception e)

            {

                System.CodeDom.Compiler.CompilerError error = new System.CodeDom.Compiler.CompilerError();

                error.ErrorText = e.ToString();

                error.FileName = "ClassGenerator.tt";

                this.Errors.Add(error);

            }

            return this.GenerationEnvironment.ToString();

        }

 

        string ClassName = "MyClass";

        Dictionary<string, string> ClassProperties = new Dictionary<string, string>{

            {"string", "StringProperty"},

            {"int", "IntProperty"}

        };

    }

}

Ничего неожиданного в принципе мы здесь не обнаруживаем. Генератор использует случайное имя пространства имён (подозреваю, что постфикс имени - это SHA1-хэш от текста шаблона). Класс транформации наследуется от стандартного TextTransformation и реализует логику метода TransformText (типичная реализация шаблона Template Method).

Ну и результат генерации:

using System;

using System.Text;

 

namespace GeneratedCode

{

    internal class MyClass

    {

        public MyClass()

        {

            Console.WriteLine("ctor");

        }

 

        public string StringProperty { get; set; }

        public int IntProperty { get; set; }

 

        public override string ToString()

        {

            StringBuilder result = new StringBuilder();

 

            result.AppendFormat("{0} = {1}", "StringProperty", this.StringProperty);

            result.AppendFormat("{0} = {1}", "IntProperty", this.IntProperty);

 

            return String.Format("MyClass() {{ {0} }}", result.ToString());

        }

    }

}

Техника использования без Visual Studio

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

Для использования вне процесса студии имеется консольная утилита TextTransform.exe, которую можно найти в [%CommonProgramFiles%\Microsoft Shared\TextTemplating\1.2\].

Использование, вполне, прямолинейно:

"%CommonProgramFiles%\Microsoft Shared\TextTemplating\1.2\TextTransform.exe"MyGenerator.tt -out MyGenerator.cs

Для генерации во время процесса сборки проекта и/или решения существует MSBuild Task от Элтона Стоунмена: http://geekswithblogs.net/EltonStoneman/archive/2008/07/25/an-msbuild-task-to-execute-t4-templates.aspx.Реализован этот Task довольно прямолинейно/примитивно, и использует вызов процесса TextTransform.exe.

Примечание: Более новая версия этого таска, которая работает и под MSBuild 3.5, доступна по адресу: http://geekswithblogs.net/EltonStoneman/archive/2008/10/04/executet4template-msbuild-task-updated.aspx.

Ссылки для более подробного ознакомления

13 янв. 2009 г.

Решето Эратосфена на Хаскеле

Небольшая иллюстрация элегантности Хаскелля.

Классический алгоритм получения простых чисел методом Решето Эратосфена.

sieve :: (Integral a) => [a] -> [a] -> [a]
sieve (x:xs) (p:ps)
    | x `rem` p == 0 = sieve [y | y <- xs, y `rem` p /= 0] ps
    | otherwise = x : sieve xs (p:ps)

primes :: (Integral a) => [a]
primes = 2 : sieve [3..] primes

main :: IO ()
main = do
    print $ take 10 primes
    putStrLn "Press any key for exit ..."


4 янв. 2009 г.

MD5 пора забывать

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

И вот практика, наконец, дошла до следующего уровня: MD5 considered harmful today: Creating a rogue CA certificate (авторы: Alexander Sotirov, Marc Stevens, Jacob Appelbaum, Arjen Lenstra, David Molnar, Dag Arne Osvik, Benne de Weger). В статье описывается создание поддельного CA-сертификата пригодного, в частности, для использования в качестве SSL-сертификата на веб-сайтах. Доступны для скачивания примеры поддельных сертификатов (#5.5).

P.S.: на публикацию этой статьи уже откликнулись и Mozilla, Microsoft, Verisign.