пятница, 10 июня 2011 г.

AutoCode

Скачав данный инструмент, я был в таком восторге, что решил о нем написать. В галерее расширений Visual Studio было написано, что он бесплатный, на сайте разработчика, что распространяется по лицензии Donate, но отложив написание обзора на некоторое время и снова к нему вернувшись впоследствии, я вдруг обнаружил, что это обычные шаровары(Shareware) и те функции, которые мне понравились больше всего более недоступны. Продолжить написание обзора уже было невозможно, поэтому я это забросил, но потом решил опубликовать, не выкидывать же в конце-то концов. Публикую черновик в неизменном виде(на чем останвоился).

 

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

AutoCode представляет из себя инструмент генерации кода любого уровня сложности. В отличие от Code Snippets( инструмента, входящего в состав Visual Studio), он позволяет не просто вставлять готовый шаблон, а выполнять программный код, генерирующий все, что можно себе только вообразить. Кроме того, в состав готовых команд входят и такие, которые просто выполняют команды “студии”. И хотя ценность таких команд невелика, тем не менее факт их наличия демонстрирует возможности инструмента, которые выходят за рамки простой кодогенерации, а имеют полный доступ модели автоматизации(Visual Studio Automation Model).

В демо-ролике AutoCode, а так же в начальных сведениях на сайте разработчика есть пример с классом Person, в котором, написав всего несколько слов, можно создать код класса с заданным набором свойств.

Пишем в коде

int id string name Person class

Размещаем каретку в конце строки, нажимаем Ctrl+Enter(другой вариант – нажать Ctrl+Enter сразу и вводить строку в открывшемся окошке, затем нажимать просто Enter, таким образом можно получить список доступных команд) и в результате получаем код

1: public class Person
2: {
3:     private int _id;
4:     private string _name;
5:     public Person()
6:     {
7:     }
8:     public int Id
9:     {
10:         get { return _id; }
11:         set { _id = value; }
12:     }    public string Name
13:     {
14:         get { return _name; }
15:         set { _name = value; }
16:     }
17: }

В данном случае class – это имя команды, а все, что ему предшествует – аргументы. Последний аргумент – имя класса, а все предшествующие должны быть парными и в каждой паре первый элемент – имя типа, второй – имя свойства. Таких пар аргументов(тип-имя) может быть достаточно много, правда все должны уместиться на одной строке.


Инструмент можно использовать не только в C#, но и в любых других документах Visual Studio, это еще одно выгодное отличие AutoCode от Code Snippet, который может работать с ограниченным количеством типов документов. Однако большинство шаблонов, генерирующих код, способны генерировать его для конкретного языка и обычно бывают доступны в файлах с кодом, написанном именно на этом языке.


Меня гораздо больше интересует генерация кода на VB.Net нежели на C#, но в наборе команд для этого языка нет ничего подобного тому, что было продемонстрировано в демо-ролике. Однако вся прелесть AutoCode в том, что в нем как и в Code Snippets можно создавать собственные команды. Поэтому первым чем я занялся в моем изучении возможностей этого инструмента, было создание команды для VB.Net, подобной команде class для C#.


Команда представляет из себя XML файл с расширением .autox. Описание формата можно найти здесь. Команды размещаются в отдельной папке при установке AutoCode. Обычно это папка расположена в  папке Документы (или Мои документы) и называется AutoCode 2010 (для посленей версии, естественно). Собственные команды можно размещать в подкаталоге Commands/(My Commands). Установив в этот каталог свою команду, можно автоматически получить к ней доступ( если Visual Studio запущена, то фактически команда будет доступна после обновления AutoCode, для этого надо выполнить команду ref, введя ее в окно команд).


Создание команды class для VB.Net



Итак создаем команду, для этого в указанном каталоге создадим текстовый файл  vbClass.autox. Затем введем в него следующий код

1: <?xml version="1.0" encoding="utf-8" ?>
2: <Commands xmlns="http://schemas.devprojects.net/AutoCode/v4.0">
3:   <Command name="vbClass" version="1.0">
4:     <CommandBehavior>
5:       <CommandLine shortcut="class"/>
6:       <ActiveDocument required="true" extensions=".vb"/>
7:     </CommandBehavior>
8:     <CommandInfo>
9:       <Category>Statements</Category>
10:       <Author>diadiavova</Author>
11:       <LanguageCategory>VB.NET</LanguageCategory>
12:       <Description>
13:         Определяет класс с набором свойств.
14:       </Description>
15:       <Usage>                
16:         <![CDATA[<Type1> <Name1>...<className> class]]>
17:       </Usage>
18:     </CommandInfo>
19:     <CommandCode language="vb">
20:       <Codes>
21:         <Code codeElement="Selection" codePoint="StartOfSelection">
22:           <![CDATA[
23:           Public Class <#=args(args.Length - 1)#>    
24:             <# For i As Integer = 0 To args.Length - 2 Step 2 #>
25:             dim _<#= args(i + 1) #> As <#= args(i) #>
26:             Public Property    <#= args(i + 1) #> As <#= args(i) #>
27:               Get
28:                 Return _<#= args(i + 1) #>            
29:               End Get            
30:               Set(ByVal value As <#= args(i) #>)                
31:                 _<#= args(i + 1) #> = value
32:               End Set        
33:             End Property    
34:             <# Next #>    
35:           End Class                
36:           ]]>                
37:         </Code>
38:       </Codes>
39:     </CommandCode>
40:   </Command>
41: </Commands>

Сохраним документ, после чего в студии вызовем окно AutoCode(Ctrl+Enter), наберем ref и нажмем Enter.


Тепрь в коде VB.Net можно ввести

integer Id string Name Person class


далее Ctrl+Enter и получим код

1: Public Class Person
2: 
3:     Dim _Id As Integer
4: 
5:     Public Property Id As Integer
6:         Get
7:             Return _Id
8:         End Get
9:         Set(ByVal value As Integer)
10:             _Id = value
11:         End Set
12:     End Property
13: 
14:     Dim _Name As String
15: 
16:     Public Property Name As String
17:         Get
18:             Return _Name
19:         End Get
20:         Set(ByVal value As String)
21:             _Name = value
22:         End Set
23:     End Property
24: End Class
Рассмотрим документ немного подробнее. С корневым элементом, как я полагаю, вопросов нет, при установке AutoCode так же устанвливается схема документа, так что при указании пространства имен студия будет выдавать подсказки для элементов и атрибутов.
<Command name="vbClass" version="1.0">

Здесь указывается полное имя команды и версия. Полное имя должно быть более понятным чем шорткат, оно выводится в окнах программы и по нему должно быть понятно, что делает команда. Окна вызываются сочетаниями клавиш Ctrl+Enter(окно IntelliSense) и Ctrl+0(каталог команд). При выделении команды в них выводится информация об этой команде.

<CommandLine shortcut="class"/>


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

<ActiveDocument required="true" extensions=".vb"/>


Атрибут extensions указывает расширения имен файлов документов, в которых доступна команда. Атрибут required указывает, является ли требование к расширению обязательным: если имеет значение true, то команда будет недоступна в других документах. Это полезно, когда команда предназначена для конкретного языка, но в то же время, в других языках может существовать одноименная команда(как в данном случае мы создали для бейсика команду, аналогичную той, которая существует для шарпа и определили для нее такой же шорткат).

<LanguageCategory>VB.NET</LanguageCategory>


Указывает в папке какого языка команда будет отображаться в каталоге команд(Ctrl+0).

<Category>Statements</Category>


Путь к команде в каталоге. Например в данном случае наша команда будет отображаться в разделе VB.NET/Statements, если надо, чтобы она отображалась в более глубоком подразделе(например, если мы захотим разместить ее в разделе VB.NET/Statements/Custom), то разделять подразделы надо слешем, как показано ниже.



<Category>Statements/Custom</Category>



По поводу элементов Author, Description, Usege, а так же Example и HelpUrl, как я полагаю вопросов возникнуть не должно, так что останавливаться на ни подробно не стану.

<CommandCode language="vb">


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

<Code codeElement="Selection" codePoint="StartOfSelection">


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



Ну и наконец сам код(содержимое элемента Code). Для генерации кода используется синтаксис текстовых шаблонов (T4), Сам код заключается в блок CDATA, поскольку содержит символы < и >. Можно использовать маркеры <# #> и <#= #>, остальные игнорируются. Если описать совсем кратко формат шаблонов, то все это маркеры по своему действию похожи на маркеры <%%> и <%=%> в ASP.Net. Аргументы, передаваемые команде, содержатся в массиве args.





Шаблоны в отдельных файлах



Среди доступных команд, поставляемых с AutoCode есть команда ax. Эта команда создает заготовку для новой команды, в которую можно добавить собственную логику и описание, сохранить и пользоваться. Для ее выполнения надо выполнить строку



<ИмяНовойКоманды> ax



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

<Code file=""/>


Фактически содержимое текстового файла вставляется в то место, где раньше у нас был блок CDATA. Редактировать шаблон в отдельном файле удобно, но, если это простой текстовый файл, пользы будет немного. Проблема в том, что шаблон содержит программный код, смешанный с текстом и маркерами, отделяющими одно от другого. Хорошо было бы, если бы программный код подсвечивался как в редакторе кода, маркеры – каким-то особым образом и было сразу видно, где код, а где текст.  Кроме того, неплохо было бы получить поддержку IntelliSence для кода. Все описанные возможности предоставляет расширение для Visual Studio, которое называется tangible T4 Editor. Поискав, я нашел еще Visual T4, но я его пока не пробовал, так что не знаю насколько он хорош.



Подсветка и другие удобства доступны в файлах с расширением .tt, а не .txt. Это не проблема, поскольку элемент Include может ссылаться на файл с любым расширением, а вот с использованием команды ax есть небольшая проблема. Здесь придется либо создавать tt файл вручную в нужном каталоге, либо потребуется собственная команда(аналогичная ax, но создающая вместо текстового файла, файл текстового шаблона). Полагаю, гораздо удобнее будет именно второй вариант, этим мы сейчас и займемся.


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


<ИмяКоманды> open


То есть в нашем случае надо выполнить


ax open


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


Открыв документы лучше сразу сохранить их копии в каталоге (My Commands) с новыми именами, чтобы случайно не изменить оригиналы. Я использовал для всех файлов имя autocodett с соответствующими расширениями.


Здесь помимо autox и txt файлов мы еще имеем cs файл. Вообще все файлы, из которых состоит команда собираются в один класс команды ( об этом я напишу чуть ниже). В autox файле содержатся ссылки на все файлы из которых строится класс команды. Текстовые шаблоны(как уже говорилось выше) добавляются посредством атрибута file элемента Code, а файлы с кодом класса с помощью аналогичного атрибута элемента Include


    <Includes>
      <
Include file="AutoCodeTT.cs"/>
    </
Includes>
Эти ссылки я изменил в соответствии с новыми именами файлов. В текстовом файле содержится шаблон autox файла для новой команды, там я внес небольшие изменения, например сменил язык написания кода новой команды на бейсик., а в cs файле поменял в коде расширение для имени файла создаваемого текстового файла с txt на tt, дописал туда заголовок шаблона, чтобы T4 editor поддерживал VB.Net

<#@ template language="VB" #>
И тому подобные мелкие изменения. Вот что получилось
autocodett.autox


1: <?xml version="1.0"?>
2: <Commands xmlns="http://schemas.devprojects.net/AutoCode/v4.0">
3:   <Command name="AutoCodeTT" version="1.0">
4:     <Includes>
5:       <Include file="AutoCodeTT.cs"/>
6:     </Includes>
7:     <CommandBehavior>
8:       <CommandLine shortcut="axtt" />
9:       <ActiveDocument required="false" extensions="*" />
10:     </CommandBehavior>
11:     <CommandInfo>
12:       <LanguageCategory>Common</LanguageCategory>
13:       <Category>MyCode</Category>
14:       <Usage>
15:         <![CDATA[(Select Text) <name> <shortcut> ax]]>     
16:       </Usage>
17:       <Example>MyCustomCommand mcc ax</Example>
18:       <Description>
19:         Creates a custom AutoCode Command in 
20:         "My Documents\AutoCode" folder with tt-template. 
21:         By default, the command will emit the selected text.
22:       </Description>
23:       <Author>DevProjects</Author>
24:       <HelpUrl>http://help.devprojects.net/gallery/command/autocode</HelpUrl>
25:     </CommandInfo>
26:     <CommandCode language="csharp">
27:       <Codes>
28:         <Code id="Template" file="autocodett.txt" />
29:       </Codes>
30:       <Selection codeElement="Template" codePoint="StartOfElement">
31:         <SelectText><![CDATA[<#=Parameters["Text"]#>]]></SelectText>
32:       </Selection>
33:     </CommandCode>
34:   </Command>
35: </Commands>

autocodett.cs

1: using System;
2:     using System.IO;
3:     using DevProjects.AutoCode.Commands;
4:     public partial class AutoCodeTTCommand : CommandBase
5:     {
6:         protected override bool BeforeExecute(string commandArgs)
7:         {
8:             if (Arguments.Length < 2)
9:             {
10:                 CommandHelper.InvalidArguments("Please, specify the name and shortcut of the command to be created.");
11:                 return false;
12:             }
13:             Parameters["CommandName"] = Arguments[0];
14:             Parameters["Shortcut"] = Arguments[1];
15:             Parameters["Text"] = "<#@ template language=\"VB\" #>\r\n" +
16:                 (String.IsNullOrEmpty(SelectedText) ? "Insert code here." : SelectedText);
17:             return true;
18:         }
19:         protected override void Execute(string commandArgs)
20:         {
21:             string path = Path.Combine(Catalog.CurrentUserPath, "(My Commands)");
22:             if (!Directory.Exists(path))
23:             {
24:                 Directory.CreateDirectory(path);
25:             }
26:             string txtFileName = DTEHelper.GetUniqueFileName(path, Parameters["CommandName"] + ".tt");
27:             using (StreamWriter writer = new StreamWriter(txtFileName))
28:             {
29:                 writer.Write(Parameters["Text"]);
30:             }
31:             string autoxFileName = DTEHelper.GetUniqueFileName(path, Parameters["CommandName"] + ".autox");
32:             using (StreamWriter writer = new StreamWriter(autoxFileName))
33:             {
34:                 string name = Path.GetFileName(txtFileName);
35:                 writer.Write(BuildTemplate(name));
36:             }
37:             DTEHelper.OpenFile(autoxFileName);
38:             System.Threading.Thread.Sleep(100);
39:             ActiveDocument.Save(null);
40:             // Save to refresh catalog
41:             System.Threading.Thread.Sleep(100);
42:             DTEHelper.OpenFile(txtFileName);
43:         }
44:     }
autocodett.txt
<?xml version="1.0"?>
<Commands xmlns="http://schemas.devprojects.net/AutoCode/v4.0">
<Command name="<#=Parameters["CommandName"]#>" version="1.0">
<CommandBehavior>
<CommandLine shortcut="<#=Parameters["Shortcut"]#>" />
<ActiveDocument required="false" extensions="*"/>
</CommandBehavior>

<CommandInfo>
<LanguageCategory>Common</LanguageCategory>
<Category>(My Commands)</Category>
<Usage><#=Parameters["Shortcut"]#></Usage>
<Description>No description supplied.</Description>
<Author>Me</Author>
<HelpUrl></HelpUrl>
</CommandInfo>

<CommandCode language="vb">

<Codes>
<Code id="Template1" file="<#=args[0]#>" />
</Codes>

<SmartFormat>
<Start codeElement="Template1" codePoint="StartOfElement" />
<End codeElement="Template1" codePoint="EndOfElement" />
</SmartFormat>

<Selection codeElement="Template1" codePoint="EndOfElement">
<SelectText></SelectText>
</Selection>

</CommandCode>
</Command>
</Commands>



Теперь попробуем создать новую команду с помощью созданной только что команды. Создадим команду ProppertyList, которая будет генерировать код для набора свойств одного типа. Для этого выполним команду



PropertyList proplist axtt



В текстовый шаблон введем  следующий код:

<#@ template language="VB"   #>

<# For i As Integer = 1 To args.Length - 1 #>
Dim _<#= args(i) #> As <#= args(0) #>
Public Property <#= args(i) #>() As <#= args(0) #>
Get
Return _<#= args(i) #>
End Get
Set(ByVal value As <#= args(0) #>)
_<#= args(i) #> = value
End Set
End Property

<# Next #>


В autox файле потребуется только сменить данные описывающие команду, у меня это выглядит так:

1: <?xml version="1.0"?>
2: <Commands xmlns="http://schemas.devprojects.net/AutoCode/v4.0">
3:   <Command name="PropertyList" version="1.0">
4:     <CommandBehavior>
5:       <CommandLine shortcut="proplist" />
6:       <ActiveDocument required="true" extensions=".vb"/>
7:     </CommandBehavior>
8:     <CommandInfo>
9:       <LanguageCategory>VB.NET</LanguageCategory>
10:       <Category>Members/Properties</Category>
11:       <Usage><![CDATA[<type> <name1> <name2>...]]>proplist</Usage>
12:       <Description>Создает набор свойств одного типа.</Description>
13:       <Author>diadiavova</Author>
14:       <HelpUrl></HelpUrl>
15:     </CommandInfo>
16:     <CommandCode language="vb">
17:       <Codes>
18:         <Code id="Template1" file="PropertyList.tt" />
19:       </Codes>
20:       <SmartFormat>
21:         <Start codeElement="Template1" codePoint="StartOfElement" />
22:         <End codeElement="Template1" codePoint="EndOfElement" />
23:       </SmartFormat>
24:       <Selection codeElement="Template1" codePoint="EndOfElement">
25:         <SelectText></SelectText>
26:       </Selection>
27:     </CommandCode>
28:   </Command>
29: </Commands>


Сохраняем файлы(autox файл надо сохранять последним, поскольку изменения других файлов сохраненные после сохранения главного файла могут оказаться недоступными, может так же понадобиться выполнение команды ref, хотя вряд ли это будет необходимо, поскольку после сохранения autox файла программа autocode обновляет команды), и выполняем в коде класса Person следующее:

string SecondName Surname Email proplist

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




И в коде класса появится следующее:

1: Dim _SecondName As String
2: Public Property SecondName() As String
3:     Get
4:         Return _SecondName
5:     End Get
6:     Set(ByVal value As String)
7:         _SecondName = value
8:     End Set
9: End Property
10: 
11: Dim _Surname As String
12: Public Property Surname() As String
13:     Get
14:         Return _Surname
15:     End Get
16:     Set(ByVal value As String)
17:         _Surname = value
18:     End Set
19: End Property
20: 
21: Dim _Email As String
22: Public Property Email() As String
23:     Get
24:         Return _Email
25:     End Get
26:     Set(ByVal value As String)
27:         _Email = value
28:     End Set
29: End Property

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

Отправить комментарий