@@ -1,7 +1,7 @@ | |||||
| | ||||
Microsoft Visual Studio Solution File, Format Version 12.00 | Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
# Visual Studio 14 | # Visual Studio 14 | ||||
VisualStudioVersion = 14.0.24720.0 | |||||
VisualStudioVersion = 14.0.25420.1 | |||||
MinimumVisualStudioVersion = 10.0.40219.1 | MinimumVisualStudioVersion = 10.0.40219.1 | ||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Docnet", "src\DocNet\Docnet.csproj", "{48CA9947-AF13-459E-9D59-FC451B5C19D7}" | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Docnet", "src\DocNet\Docnet.csproj", "{48CA9947-AF13-459E-9D59-FC451B5C19D7}" | ||||
EndProject | EndProject | ||||
@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownDeep", "src\Markdow | |||||
EndProject | EndProject | ||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownDeepTests", "src\MarkdownDeepTests\MarkdownDeepTests.csproj", "{CD1F5BFF-0118-4994-86A2-92658A36CE1B}" | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownDeepTests", "src\MarkdownDeepTests\MarkdownDeepTests.csproj", "{CD1F5BFF-0118-4994-86A2-92658A36CE1B}" | ||||
EndProject | EndProject | ||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Projbook.Extension", "src\Projbook.Extension\Projbook.Extension.csproj", "{8338B756-0519-4D20-BA04-3A8F4839237A}" | |||||
EndProject | |||||
Global | Global | ||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
Debug|Any CPU = Debug|Any CPU | Debug|Any CPU = Debug|Any CPU | ||||
@@ -27,6 +29,10 @@ Global | |||||
{CD1F5BFF-0118-4994-86A2-92658A36CE1B}.Debug|Any CPU.Build.0 = Debug|Any CPU | {CD1F5BFF-0118-4994-86A2-92658A36CE1B}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
{CD1F5BFF-0118-4994-86A2-92658A36CE1B}.Release|Any CPU.ActiveCfg = Release|Any CPU | {CD1F5BFF-0118-4994-86A2-92658A36CE1B}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
{CD1F5BFF-0118-4994-86A2-92658A36CE1B}.Release|Any CPU.Build.0 = Release|Any CPU | {CD1F5BFF-0118-4994-86A2-92658A36CE1B}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
{8338B756-0519-4D20-BA04-3A8F4839237A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
{8338B756-0519-4D20-BA04-3A8F4839237A}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
{8338B756-0519-4D20-BA04-3A8F4839237A}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
{8338B756-0519-4D20-BA04-3A8F4839237A}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
EndGlobalSection | EndGlobalSection | ||||
GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
@@ -1,6 +1,18 @@ | |||||
<?xml version="1.0" encoding="utf-8" ?> | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<configuration> | <configuration> | ||||
<startup> | <startup> | ||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> | <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> | ||||
</startup> | </startup> | ||||
<runtime> | |||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> | |||||
<dependentAssembly> | |||||
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> | |||||
<bindingRedirect oldVersion="0.0.0.0-1.3.0.0" newVersion="1.3.0.0" /> | |||||
</dependentAssembly> | |||||
<dependentAssembly> | |||||
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> | |||||
<bindingRedirect oldVersion="0.0.0.0-1.2.0.0" newVersion="1.2.0.0" /> | |||||
</dependentAssembly> | |||||
</assemblyBinding> | |||||
</runtime> | |||||
</configuration> | </configuration> |
@@ -80,4 +80,4 @@ | |||||
<Target Name="AfterBuild"> | <Target Name="AfterBuild"> | ||||
</Target> | </Target> | ||||
--> | --> | ||||
</Project> | |||||
</Project> |
@@ -14,8 +14,13 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.IO; | |||||
using System.Linq; | using System.Linq; | ||||
using System.Text; | using System.Text; | ||||
using Projbook.Extension; | |||||
using Projbook.Extension.CSharpExtractor; | |||||
using Projbook.Extension.Spi; | |||||
using Projbook.Extension.XmlExtractor; | |||||
namespace MarkdownDeep | namespace MarkdownDeep | ||||
{ | { | ||||
@@ -1281,23 +1286,26 @@ namespace MarkdownDeep | |||||
{ | { | ||||
return HandleAlertExtension(b); | return HandleAlertExtension(b); | ||||
} | } | ||||
if(DoesMatch("@snippet")) | |||||
{ | |||||
return HandleSnippetExtension(b); | |||||
} | |||||
return false; | return false; | ||||
} | } | ||||
/// <summary> | |||||
/// Handles the alert extension: | |||||
/// @alert type | |||||
/// text | |||||
/// @end | |||||
/// | |||||
/// where text can be anything and has to be handled further. | |||||
/// type is: danger, warning, info or neutral. | |||||
/// </summary> | |||||
/// <param name="b">The b.</param> | |||||
/// <returns></returns> | |||||
private bool HandleAlertExtension(Block b) | |||||
/// <summary> | |||||
/// Handles the alert extension: | |||||
/// @alert type | |||||
/// text | |||||
/// @end | |||||
/// | |||||
/// where text can be anything and has to be handled further. | |||||
/// type is: danger, warning, info or neutral. | |||||
/// </summary> | |||||
/// <param name="b">The b.</param> | |||||
/// <returns></returns> | |||||
private bool HandleAlertExtension(Block b) | |||||
{ | { | ||||
// skip '@alert' | // skip '@alert' | ||||
if(!SkipString("@alert")) | if(!SkipString("@alert")) | ||||
@@ -1438,15 +1446,114 @@ namespace MarkdownDeep | |||||
b.Children = contentProcessor.ScanLines(Input, startContent, endContent - startContent); | b.Children = contentProcessor.ScanLines(Input, startContent, endContent - startContent); | ||||
return true; | return true; | ||||
} | } | ||||
/// <summary> | |||||
/// Handles the font awesome extension, which is available in DocNet mode. FontAwesome extension uses @fa-iconname, where iconname is the name of the fontawesome icon. | |||||
/// Called when '@fa-' has been seen. Current position is on 'f' of 'fa-'. | |||||
/// </summary> | |||||
/// <param name="b">The b.</param> | |||||
/// <returns></returns> | |||||
private bool HandleFontAwesomeExtension(Block b) | |||||
/// <summary> | |||||
/// Handles the snippet extension: | |||||
/// @snippet language [filename] pattern | |||||
/// | |||||
/// where 'language' can be: cs, xml or txt. If something else, txt is used | |||||
/// '[filename]' is evaluated relatively to the document location of the current document. | |||||
/// 'pattern' is the pattern passed to the extractor, which is determined based on the language. This is Projbook code. | |||||
/// | |||||
/// The read snippet is wrapped in a fenced code block with language as language marker, except for txt, which will get 'nohighlight'. | |||||
/// This fenced code block is then parsed again and that result is returned as b's data. | |||||
/// </summary> | |||||
/// <param name="b">The block to handle.</param> | |||||
/// <returns></returns> | |||||
private bool HandleSnippetExtension(Block b) | |||||
{ | |||||
// skip @snippet | |||||
if(!SkipString("@snippet")) | |||||
{ | |||||
return false; | |||||
} | |||||
if(!SkipLinespace()) | |||||
{ | |||||
return false; | |||||
} | |||||
// language | |||||
var language = string.Empty; | |||||
if(!SkipIdentifier(ref language)) | |||||
{ | |||||
return false; | |||||
} | |||||
if(!SkipLinespace()) | |||||
{ | |||||
return false; | |||||
} | |||||
// [filename] | |||||
if(!this.SkipChar('[')) | |||||
{ | |||||
return false; | |||||
} | |||||
// mark start of filename string | |||||
this.Mark(); | |||||
if(!this.Find(']')) | |||||
{ | |||||
return false; | |||||
} | |||||
string filename = this.Extract(); | |||||
if(string.IsNullOrWhiteSpace(filename)) | |||||
{ | |||||
return false; | |||||
} | |||||
if(!SkipChar(']')) | |||||
{ | |||||
return false; | |||||
} | |||||
if(!SkipLinespace()) | |||||
{ | |||||
return false; | |||||
} | |||||
// pattern | |||||
var patternStart = this.Position; | |||||
SkipToEol(); | |||||
var pattern = this.Input.Substring(patternStart, this.Position - patternStart); | |||||
SkipToNextLine(); | |||||
language = language.ToLowerInvariant(); | |||||
ISnippetExtractor extractor = null; | |||||
switch(language) | |||||
{ | |||||
case "cs": | |||||
extractor = new CSharpSnippetExtractor(); | |||||
break; | |||||
case "xml": | |||||
extractor = new XmlSnippetExtractor(); | |||||
break; | |||||
default: | |||||
// text | |||||
language = "nohighlight"; | |||||
extractor = new DefaultSnippetExtractor(); | |||||
break; | |||||
} | |||||
// extract the snippet, then build the fenced block to return. | |||||
var fullFilename = Path.Combine(Path.GetDirectoryName(m_markdown.DocumentLocation) ?? string.Empty, filename); | |||||
var snippetText = extractor.Extract(fullFilename, pattern) ?? string.Empty; | |||||
b.BlockType = BlockType.codeblock; | |||||
b.Data = language; | |||||
var child = CreateBlock(); | |||||
child.BlockType = BlockType.indent; | |||||
child.Buf = snippetText; | |||||
child.ContentStart = 0; | |||||
child.ContentEnd = snippetText.Length; | |||||
b.Children = new List<Block>() { child}; | |||||
return true; | |||||
} | |||||
/// <summary> | |||||
/// Handles the font awesome extension, which is available in DocNet mode. FontAwesome extension uses @fa-iconname, where iconname is the name of the fontawesome icon. | |||||
/// Called when '@fa-' has been seen. Current position is on 'f' of 'fa-'. | |||||
/// </summary> | |||||
/// <param name="b">The b.</param> | |||||
/// <returns></returns> | |||||
private bool HandleFontAwesomeExtension(Block b) | |||||
{ | { | ||||
string iconName = string.Empty; | string iconName = string.Empty; | ||||
int newPosition = this.Position; | int newPosition = this.Position; | ||||
@@ -105,6 +105,12 @@ | |||||
<Install>true</Install> | <Install>true</Install> | ||||
</BootstrapperPackage> | </BootstrapperPackage> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | |||||
<ProjectReference Include="..\Projbook.Extension\Projbook.Extension.csproj"> | |||||
<Project>{8338b756-0519-4d20-ba04-3a8f4839237a}</Project> | |||||
<Name>Projbook.Extension</Name> | |||||
</ProjectReference> | |||||
</ItemGroup> | |||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||||
<PropertyGroup> | <PropertyGroup> | ||||
<PostBuildEvent> | <PostBuildEvent> | ||||
@@ -477,6 +477,12 @@ | |||||
<ItemGroup> | <ItemGroup> | ||||
<EmbeddedResource Include="testfiles\mdtest11\Images%28Titled%29.html" /> | <EmbeddedResource Include="testfiles\mdtest11\Images%28Titled%29.html" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | |||||
<EmbeddedResource Include="testfiles\docnetmode\Snippet%28DocNetMode%29.html" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<EmbeddedResource Include="testfiles\docnetmode\Snippet%28DocNetMode%29.txt" /> | |||||
</ItemGroup> | |||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | <!-- To modify your build process, add your task inside one of the targets below and uncomment it. | ||||
Other similar extension points exist, see Microsoft.Common.targets. | Other similar extension points exist, see Microsoft.Common.targets. | ||||
@@ -132,6 +132,10 @@ namespace MarkdownDeepTests | |||||
{ | { | ||||
md.HtmlClassTitledImages = "figure"; | md.HtmlClassTitledImages = "figure"; | ||||
} | } | ||||
if(md.DocNetMode) | |||||
{ | |||||
md.GitHubCodeBlocks = true; | |||||
} | |||||
string actual = md.Transform(input); | string actual = md.Transform(input); | ||||
string actual_clean = Utils.strip_redundant_whitespace(actual); | string actual_clean = Utils.strip_redundant_whitespace(actual); | ||||
@@ -0,0 +1,21 @@ | |||||
<p>pre</p> | |||||
<p>This is some text with a snippet.</p> | |||||
<pre><code class="cs">[SetUp] | |||||
public void SetUp() | |||||
{ | |||||
m = new Markdown(); | |||||
m.AutoHeadingIDs = true; | |||||
m.ExtraMode = true; | |||||
} | |||||
</code></pre> | |||||
<pre><code class="cs">[Test] | |||||
public void WithPunctuation() | |||||
{ | |||||
// ... | |||||
} | |||||
</code></pre> | |||||
<p>post</p> |
@@ -0,0 +1,9 @@ | |||||
pre | |||||
This is some text with a snippet. | |||||
@snippet cs [..\..\AutoHeaderIDTests.cs] SetUp | |||||
@snippet cs [..\..\AutoHeaderIDTests.cs] =WithPunctuation | |||||
post |
@@ -0,0 +1,90 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> | |||||
<PropertyGroup> | |||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | |||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | |||||
<ProjectGuid>{F5431901-29AC-46D4-A717-DE2A9114E82D}</ProjectGuid> | |||||
<OutputType>Library</OutputType> | |||||
<AppDesignerFolder>Properties</AppDesignerFolder> | |||||
<RootNamespace>Projbook.Extension.CSharpExtractor</RootNamespace> | |||||
<AssemblyName>Projbook.Extension.CSharpExtractor</AssemblyName> | |||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> | |||||
<FileAlignment>512</FileAlignment> | |||||
<TargetFrameworkProfile /> | |||||
</PropertyGroup> | |||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | |||||
<DebugSymbols>true</DebugSymbols> | |||||
<DebugType>full</DebugType> | |||||
<Optimize>false</Optimize> | |||||
<OutputPath>bin\Debug\</OutputPath> | |||||
<DefineConstants>DEBUG;TRACE</DefineConstants> | |||||
<ErrorReport>prompt</ErrorReport> | |||||
<WarningLevel>4</WarningLevel> | |||||
</PropertyGroup> | |||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | |||||
<DebugType>pdbonly</DebugType> | |||||
<Optimize>true</Optimize> | |||||
<OutputPath>bin\Release\</OutputPath> | |||||
<DefineConstants>TRACE</DefineConstants> | |||||
<ErrorReport>prompt</ErrorReport> | |||||
<WarningLevel>4</WarningLevel> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<Reference Include="Microsoft.CodeAnalysis, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | |||||
<HintPath>..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="Microsoft.CodeAnalysis.CSharp, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | |||||
<HintPath>..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="System" /> | |||||
<Reference Include="System.Collections.Immutable, Version=1.1.37.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||||
<HintPath>..\packages\System.Collections.Immutable.1.1.37\lib\dotnet\System.Collections.Immutable.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="System.Core" /> | |||||
<Reference Include="System.Reflection.Metadata, Version=1.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||||
<HintPath>..\packages\System.Reflection.Metadata.1.2.0\lib\portable-net45+win8\System.Reflection.Metadata.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="System.Xml.Linq" /> | |||||
<Reference Include="System.Data.DataSetExtensions" /> | |||||
<Reference Include="Microsoft.CSharp" /> | |||||
<Reference Include="System.Data" /> | |||||
<Reference Include="System.Net.Http" /> | |||||
<Reference Include="System.Xml" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<Compile Include="Projbook\Extension\CSharp\CSharpExtractionMode.cs" /> | |||||
<Compile Include="Projbook\Extension\CSharp\CSharpMatchingRule.cs" /> | |||||
<Compile Include="Projbook\Extension\CSharp\CSharpSnippetExtractor.cs" /> | |||||
<Compile Include="Projbook\Extension\CSharp\CSharpSyntaxMatchingNode.cs" /> | |||||
<Compile Include="Projbook\Extension\CSharp\CSharpSyntaxWalkerMatchingBuilder.cs" /> | |||||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\Projbook.Extension\Projbook.Extension.csproj"> | |||||
<Project>{8338b756-0519-4d20-ba04-3a8f4839237a}</Project> | |||||
<Name>Projbook.Extension</Name> | |||||
</ProjectReference> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<None Include="packages.config"> | |||||
<SubType>Designer</SubType> | |||||
</None> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" /> | |||||
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" /> | |||||
</ItemGroup> | |||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | |||||
Other similar extension points exist, see Microsoft.Common.targets. | |||||
<Target Name="BeforeBuild"> | |||||
</Target> | |||||
<Target Name="AfterBuild"> | |||||
</Target> | |||||
--> | |||||
</Project> |
@@ -0,0 +1,23 @@ | |||||
namespace Projbook.Extension.CSharpExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Represents the extraction mode. | |||||
/// </summary> | |||||
public enum CSharpExtractionMode | |||||
{ | |||||
/// <summary> | |||||
/// Full member: Do not process the snippet and print it as it. | |||||
/// </summary> | |||||
FullMember, | |||||
/// <summary> | |||||
/// Content only: Extract the code block and print this part only. | |||||
/// </summary> | |||||
ContentOnly, | |||||
/// <summary> | |||||
/// Block structure only: Remove the block content and print the code structure only. | |||||
/// </summary> | |||||
BlockStructureOnly | |||||
} | |||||
} |
@@ -0,0 +1,80 @@ | |||||
using Projbook.Extension.Exception; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text.RegularExpressions; | |||||
namespace Projbook.Extension.CSharpExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Represents a matching rule for referencing a C# member. | |||||
/// </summary> | |||||
public class CSharpMatchingRule | |||||
{ | |||||
/// <summary> | |||||
/// The matching chunk to identify which member are the snippet targets. | |||||
/// </summary> | |||||
public string[] MatchingChunks { get; private set; } | |||||
/// <summary> | |||||
/// The snippet extraction mode. | |||||
/// </summary> | |||||
public CSharpExtractionMode ExtractionMode { get; private set; } | |||||
/// <summary> | |||||
/// Defines rule regex used to parse the snippet into chunks. | |||||
/// Expected input format: Path/File.cs [My.Name.Space.Class.Method][(string, string)] | |||||
/// * The first chunk is the file name and will be loaded in <seealso cref="TargetFile"/> | |||||
/// * The optional second chunks are all full qualified name to the member separated by "." | |||||
/// * The optional last chunk is the method parameters if matching a method. | |||||
/// </summary> | |||||
private static Regex ruleRegex = new Regex(@"^([-=])?([^(]+)?\s*(\([^)]*\s*\))?\s*$", RegexOptions.Compiled); | |||||
/// <summary> | |||||
/// Parses the token | |||||
/// </summary> | |||||
/// <param name="pattern"></param> | |||||
/// <returns></returns> | |||||
public static CSharpMatchingRule Parse(string pattern) | |||||
{ | |||||
// Try to match the regex | |||||
pattern = Regex.Replace(pattern, @"\s", string.Empty); | |||||
Match match = CSharpMatchingRule.ruleRegex.Match(pattern); | |||||
if (!match.Success || string.IsNullOrWhiteSpace(match.Groups[0].Value)) | |||||
{ | |||||
throw new SnippetExtractionException("Invalid extraction rule", pattern); | |||||
} | |||||
// Retrieve values from the regex matching | |||||
string extractionOption = match.Groups[1].Value; | |||||
string rawMember = match.Groups[2].Value.Trim(); | |||||
string rawParameters = match.Groups[3].Value.Trim(); | |||||
// Build The matching chunk with extracted data | |||||
List<string> matchingChunks = new List<string>(); | |||||
matchingChunks.AddRange(rawMember.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries)); | |||||
if (rawParameters.Length >= 1) | |||||
{ | |||||
matchingChunks.Add(rawParameters); | |||||
} | |||||
// Read extraction mode | |||||
CSharpExtractionMode extractionMode = CSharpExtractionMode.FullMember; | |||||
switch (extractionOption) | |||||
{ | |||||
case "-": | |||||
extractionMode = CSharpExtractionMode.ContentOnly; | |||||
break; | |||||
case "=": | |||||
extractionMode = CSharpExtractionMode.BlockStructureOnly; | |||||
break; | |||||
} | |||||
// Build the matching rule based on the regex matching | |||||
return new CSharpMatchingRule | |||||
{ | |||||
MatchingChunks = matchingChunks.ToArray(), | |||||
ExtractionMode = extractionMode | |||||
}; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,368 @@ | |||||
using EnsureThat; | |||||
using Microsoft.CodeAnalysis; | |||||
using Microsoft.CodeAnalysis.CSharp; | |||||
using Microsoft.CodeAnalysis.CSharp.Syntax; | |||||
using Microsoft.CodeAnalysis.Text; | |||||
using Projbook.Extension.Exception; | |||||
using Projbook.Extension.Spi; | |||||
using System; | |||||
using System.IO.Abstractions; | |||||
using System.Linq; | |||||
using System.Text; | |||||
namespace Projbook.Extension.CSharpExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Extractor in charge of browsing source directories. load file content and extract requested member. | |||||
/// </summary> | |||||
[Syntax(name: "csharp")] | |||||
public class CSharpSnippetExtractor : DefaultSnippetExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Represents the matching trie used for member matching. | |||||
/// Because of the cost of building the Trie, this value is lazy loaded and kept for future usages. | |||||
/// </summary> | |||||
private CSharpSyntaxMatchingNode syntaxTrie; | |||||
/// <summary> | |||||
/// Extracts a snippet from a given rule pattern. | |||||
/// </summary> | |||||
/// <param name="fileSystemInfo">The file system info.</param> | |||||
/// <param name="memberPattern">The member pattern to extract.</param> | |||||
/// <returns>The extracted snippet.</returns> | |||||
public override Model.Snippet Extract(FileSystemInfoBase fileSystemInfo, string memberPattern) | |||||
{ | |||||
// Return the entire code if no member is specified | |||||
if (string.IsNullOrWhiteSpace(memberPattern)) | |||||
{ | |||||
return base.Extract(fileSystemInfo, memberPattern); | |||||
} | |||||
// Parse the matching rule from the pattern | |||||
CSharpMatchingRule rule = CSharpMatchingRule.Parse(memberPattern); | |||||
// Load the trie for pattern matching | |||||
if (null == this.syntaxTrie) | |||||
{ | |||||
// Load file content | |||||
string sourceCode = base.LoadFile(this.ConvertToFile(fileSystemInfo)); | |||||
// Build a syntax tree from the source code | |||||
SyntaxTree tree = CSharpSyntaxTree.ParseText(sourceCode); | |||||
SyntaxNode root = tree.GetRoot(); | |||||
// Visit the syntax tree for generating a Trie for pattern matching | |||||
CSharpSyntaxWalkerMatchingBuilder syntaxMatchingBuilder = new CSharpSyntaxWalkerMatchingBuilder(); | |||||
syntaxMatchingBuilder.Visit(root); | |||||
// Retrieve the Trie root | |||||
this.syntaxTrie = syntaxMatchingBuilder.Root; | |||||
} | |||||
// Match the rule from the syntax matching Trie | |||||
CSharpSyntaxMatchingNode matchingTrie = syntaxTrie.Match(rule.MatchingChunks); | |||||
if (null == matchingTrie) | |||||
{ | |||||
throw new SnippetExtractionException("Cannot find member", memberPattern); | |||||
} | |||||
// Build a snippet for extracted syntax nodes | |||||
return this.BuildSnippet(matchingTrie.MatchingSyntaxNodes, rule.ExtractionMode); | |||||
} | |||||
/// <summary> | |||||
/// Builds a snippet from extracted syntax nodes. | |||||
/// </summary> | |||||
/// <param name="nodes">The exctracted nodes.</param> | |||||
/// <param name="extractionMode">The extraction mode.</param> | |||||
/// <returns>The built snippet.</returns> | |||||
private Model.Snippet BuildSnippet(SyntaxNode[] nodes, CSharpExtractionMode extractionMode) | |||||
{ | |||||
// Data validation | |||||
Ensure.That(() => nodes).IsNotNull(); | |||||
Ensure.That(() => nodes).HasItems(); | |||||
// Extract code from each snippets | |||||
StringBuilder stringBuilder = new StringBuilder(); | |||||
bool firstSnippet = true; | |||||
foreach (SyntaxNode node in nodes) | |||||
{ | |||||
// Write line return between each snippet | |||||
if (!firstSnippet) | |||||
{ | |||||
stringBuilder.AppendLine(); | |||||
stringBuilder.AppendLine(); | |||||
} | |||||
// Write each snippet line | |||||
string[] lines = node.GetText().Lines.Select(x => x.ToString()).ToArray(); | |||||
int contentPosition = this.DetermineContentPosition(node); | |||||
this.WriteAndCleanupSnippet(stringBuilder, lines, extractionMode, contentPosition); | |||||
// Flag the first snippet as false | |||||
firstSnippet = false; | |||||
} | |||||
// Create the snippet from the exctracted code | |||||
return new Model.PlainTextSnippet(stringBuilder.ToString()); | |||||
} | |||||
/// <summary> | |||||
/// Determines the content's block position depending on the node type. | |||||
/// </summary> | |||||
/// <param name="node">The node to extract the content position from.</param> | |||||
/// <returns>The determined content position or 0 if not found.</returns> | |||||
private int DetermineContentPosition(SyntaxNode node) | |||||
{ | |||||
// Data validation | |||||
Ensure.That(() => node).IsNotNull(); | |||||
// Select the content node element depending on the node type | |||||
TextSpan? contentTextSpan = null; | |||||
switch (node.Kind()) | |||||
{ | |||||
// Accessor list content | |||||
case SyntaxKind.PropertyDeclaration: | |||||
case SyntaxKind.IndexerDeclaration: | |||||
case SyntaxKind.EventDeclaration: | |||||
AccessorListSyntax accessorList = node.DescendantNodes().OfType<AccessorListSyntax>().FirstOrDefault(); | |||||
if (null != accessorList) | |||||
{ | |||||
contentTextSpan = accessorList.FullSpan; | |||||
} | |||||
break; | |||||
// Contains children | |||||
case SyntaxKind.NamespaceDeclaration: | |||||
case SyntaxKind.InterfaceDeclaration: | |||||
case SyntaxKind.ClassDeclaration: | |||||
SyntaxToken token = node.ChildTokens().Where(x => x.Kind() == SyntaxKind.OpenBraceToken).FirstOrDefault(); | |||||
if (null != token) | |||||
{ | |||||
contentTextSpan = token.FullSpan; | |||||
} | |||||
break; | |||||
// Block content | |||||
case SyntaxKind.ConstructorDeclaration: | |||||
case SyntaxKind.DestructorDeclaration: | |||||
case SyntaxKind.MethodDeclaration: | |||||
case SyntaxKind.GetAccessorDeclaration: | |||||
case SyntaxKind.SetAccessorDeclaration: | |||||
case SyntaxKind.AddAccessorDeclaration: | |||||
case SyntaxKind.RemoveAccessorDeclaration: | |||||
BlockSyntax block = node.DescendantNodes().OfType<BlockSyntax>().FirstOrDefault(); | |||||
if (null != block) | |||||
{ | |||||
contentTextSpan = block.FullSpan; | |||||
} | |||||
break; | |||||
// Not processed by projbook csharp extractor | |||||
default: | |||||
break; | |||||
} | |||||
// Compute a line break insensitive position based on the fetched content text span if any is found | |||||
if (null != contentTextSpan) | |||||
{ | |||||
int relativeTextSpanStart = contentTextSpan.Value.Start - node.FullSpan.Start; | |||||
return node | |||||
.ToFullString() | |||||
.Substring(0, relativeTextSpanStart) | |||||
.Replace("\r\n", "") | |||||
.Replace("\n", "").Length; | |||||
} | |||||
// Otherwise return 0 as default value | |||||
return 0; | |||||
} | |||||
/// <summary> | |||||
/// Writes and cleanup line snippets. | |||||
/// Snippets are moved out of their context, for this reason we need to trim lines aroung and remove a part of the indentation. | |||||
/// </summary> | |||||
/// <param name="stringBuilder">The string builder used as output.</param> | |||||
/// <param name="lines">The lines to process.</param> | |||||
/// <param name="extractionMode">The extraction mode.</param> | |||||
/// <param name="contentPosition">The content position.</param> | |||||
private void WriteAndCleanupSnippet(StringBuilder stringBuilder, string[] lines, CSharpExtractionMode extractionMode, int contentPosition) | |||||
{ | |||||
// Data validation | |||||
Ensure.That(() => stringBuilder).IsNotNull(); | |||||
Ensure.That(() => lines).IsNotNull(); | |||||
// Do not process if lines are empty | |||||
if (0 >= lines.Length) | |||||
{ | |||||
return; | |||||
} | |||||
// Compute the index of the first selected line | |||||
int startPos = 0; | |||||
int skippedCharNumber = 0; | |||||
if (CSharpExtractionMode.ContentOnly == extractionMode) | |||||
{ | |||||
// Compute the content position index in the first processed line | |||||
int contentPositionFirstLineIndex = 0; | |||||
for (int totalLinePosition = 0; startPos < lines.Length; ++startPos) | |||||
{ | |||||
// Compute the content position in the current line | |||||
string line = lines[startPos]; | |||||
int relativePosition = contentPosition - totalLinePosition; | |||||
int contentPositionInLine = relativePosition < line.Length ? relativePosition: -1; | |||||
// In expected in the current line | |||||
if (contentPositionInLine >= 0) | |||||
{ | |||||
// Look for the relative index in the current line | |||||
// Save the found index and break the iteration if any open bracket is found | |||||
int indexOf = line.IndexOf('{', contentPositionInLine); | |||||
if (0 <= indexOf) | |||||
{ | |||||
contentPositionFirstLineIndex = indexOf; | |||||
break; | |||||
} | |||||
} | |||||
// Move the total line position after the processed line | |||||
totalLinePosition += lines[startPos].Length; | |||||
} | |||||
// Extract block code if any opening bracket has been found | |||||
if (startPos < lines.Length) | |||||
{ | |||||
int openingBracketPos = lines[startPos].IndexOf('{', contentPositionFirstLineIndex); | |||||
if (openingBracketPos >= 0) | |||||
{ | |||||
// Extract the code before the curly bracket | |||||
if (lines[startPos].Length > openingBracketPos) | |||||
{ | |||||
lines[startPos] = lines[startPos].Substring(openingBracketPos + 1); | |||||
} | |||||
// Skip the current line if empty | |||||
if (string.IsNullOrWhiteSpace(lines[startPos]) && lines.Length > 1 + startPos) | |||||
{ | |||||
++startPos; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
// Skip leading whitespace lines and keep track of the amount of skipped char | |||||
for (; startPos < lines.Length; ++startPos) | |||||
{ | |||||
// Break on non whitespace line | |||||
string line = lines[startPos]; | |||||
if (line.Trim().Length > 0) | |||||
{ | |||||
break; | |||||
} | |||||
// Record skipped char number | |||||
skippedCharNumber += line.Length; | |||||
} | |||||
} | |||||
// Compute the index of the lastselected line | |||||
int endPos = -1 + lines.Length; | |||||
if (CSharpExtractionMode.ContentOnly == extractionMode) | |||||
{ | |||||
for (; 0 <= endPos && !lines[endPos].ToString().Contains('}'); --endPos); | |||||
// Extract block code if any closing bracket has been found | |||||
if (0 <= endPos) | |||||
{ | |||||
int closingBracketPos = lines[endPos].IndexOf('}'); | |||||
if (closingBracketPos >= 0) | |||||
{ | |||||
// Extract the code before the curly bracket | |||||
if (lines[endPos].Length > closingBracketPos) | |||||
lines[endPos] = lines[endPos].Substring(0, closingBracketPos).TrimEnd(); | |||||
} | |||||
// Skip the current line if empty | |||||
if (string.IsNullOrWhiteSpace(lines[endPos]) && lines.Length > -1 + endPos) | |||||
{ | |||||
--endPos; | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
for (; 0 <= endPos && lines[endPos].ToString().Trim().Length == 0; --endPos); | |||||
} | |||||
// Compute the padding to remove for removing a part of the indentation | |||||
int leftPadding = int.MaxValue; | |||||
for (int i = startPos; i <= endPos; ++i) | |||||
{ | |||||
// Ignore empty lines in the middle of the snippet | |||||
if (!string.IsNullOrWhiteSpace(lines[i])) | |||||
{ | |||||
// Adjust the left padding with the available whitespace at the beginning of the line | |||||
leftPadding = Math.Min(leftPadding, lines[i].ToString().TakeWhile(Char.IsWhiteSpace).Count()); | |||||
} | |||||
} | |||||
// Write selected lines to the string builder | |||||
bool firstLine = true; | |||||
for (int i = startPos; i <= endPos; ++i) | |||||
{ | |||||
// Write line return between each line | |||||
if (!firstLine) | |||||
{ | |||||
stringBuilder.AppendLine(); | |||||
} | |||||
// Remove a part of the indentation padding | |||||
if (lines[i].Length > leftPadding) | |||||
{ | |||||
string line = lines[i].Substring(leftPadding); | |||||
// Process the snippet depending on the extraction mode | |||||
switch (extractionMode) | |||||
{ | |||||
// Extract the block structure only | |||||
case CSharpExtractionMode.BlockStructureOnly: | |||||
// Compute the content position in the current line | |||||
int relativePosition = contentPosition - skippedCharNumber; | |||||
int contentPositionInLine = relativePosition < line.Length + leftPadding ? relativePosition : -1; | |||||
// Look for open bracket from the content position in line | |||||
int openingBracketPos = -1; | |||||
if (contentPositionInLine >= 0) | |||||
{ | |||||
openingBracketPos = line.IndexOf('{', Math.Max(0, contentPositionInLine - leftPadding)); | |||||
} | |||||
// Anonymize code content if an open bracket is found | |||||
if (openingBracketPos >= 0) | |||||
{ | |||||
// Extract the code before the curly bracket | |||||
if (line.Length > openingBracketPos) | |||||
line = line.Substring(0, 1 + openingBracketPos); | |||||
// Replace the content and close the block | |||||
line += string.Format("{0} // ...{0}}}", Environment.NewLine); | |||||
// Stop the iteration | |||||
endPos = i; | |||||
} | |||||
break; | |||||
} | |||||
// Append the line | |||||
stringBuilder.Append(line); | |||||
skippedCharNumber += lines[i].Length; | |||||
} | |||||
// Flag the first line as false | |||||
firstLine = false; | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,201 @@ | |||||
using EnsureThat; | |||||
using Microsoft.CodeAnalysis; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
namespace Projbook.Extension.CSharpExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Represents a syntax matching node. | |||||
/// Thie node is used to build a Trie representing possible matching. | |||||
/// Each node contians children and matching syntax nodes. | |||||
/// </summary> | |||||
public class CSharpSyntaxMatchingNode | |||||
{ | |||||
/// <summary> | |||||
/// The public Matching SyntaxNodes. | |||||
/// </summary> | |||||
public SyntaxNode[] MatchingSyntaxNodes | |||||
{ | |||||
get | |||||
{ | |||||
// Return empty array id the nodes are empty | |||||
if (null == this.matchingSyntaxNodes) | |||||
{ | |||||
return new SyntaxNode[0]; | |||||
} | |||||
// Return the matching syntax nodes | |||||
return this.matchingSyntaxNodes.ToArray(); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// The node's children. | |||||
/// </summary> | |||||
private Dictionary<string, CSharpSyntaxMatchingNode> children; | |||||
/// <summary> | |||||
/// The node's maching syntax node. | |||||
/// </summary> | |||||
private List<SyntaxNode> matchingSyntaxNodes; | |||||
/// <summary> | |||||
/// Finds a node from syntax chunk. | |||||
/// </summary> | |||||
/// <param name="chunks">The chunks to match.</param> | |||||
/// <returns></returns> | |||||
public CSharpSyntaxMatchingNode Match(string[] chunks) | |||||
{ | |||||
// Data validation | |||||
Ensure.That(() => chunks).IsNotNull(); | |||||
// Browse the Trie until finding a matching | |||||
CSharpSyntaxMatchingNode matchingNode = this; | |||||
foreach (string fragment in chunks) | |||||
{ | |||||
// Could not find any matching | |||||
if (null == matchingNode.children || !matchingNode.children.TryGetValue(fragment, out matchingNode)) | |||||
{ | |||||
return null; | |||||
} | |||||
} | |||||
// Return the matching node | |||||
return matchingNode; | |||||
} | |||||
/// <summary> | |||||
/// Lookup a node from children and return it. if the node doesn't exist, a new one will be created and added to the children. | |||||
/// </summary> | |||||
/// <param name="name">The node name.</param> | |||||
/// <returns>The node matching the requested name.</returns> | |||||
public CSharpSyntaxMatchingNode EnsureNode(string name) | |||||
{ | |||||
// Data validation | |||||
Ensure.That(() => name).IsNotNullOrWhiteSpace(); | |||||
// Fetch a node from existing children and return it if any is found | |||||
CSharpSyntaxMatchingNode firstLevelNode; | |||||
if (null != this.children && this.children.TryGetValue(name, out firstLevelNode)) | |||||
{ | |||||
return firstLevelNode; | |||||
} | |||||
// Otherwise create a new node and return it | |||||
else | |||||
{ | |||||
// Lazu create the dictionary for storing children | |||||
if (null == this.children) | |||||
{ | |||||
this.children = new Dictionary<string, CSharpSyntaxMatchingNode>(); | |||||
} | |||||
// Assign and return the new node | |||||
return this.children[name] = new CSharpSyntaxMatchingNode(); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Adds a syntax node as matching node. | |||||
/// </summary> | |||||
/// <param name="node"></param> | |||||
public void AddSyntaxNode(SyntaxNode node) | |||||
{ | |||||
// Data validation | |||||
Ensure.That(() => node).IsNotNull(); | |||||
// Lazy create the syntax node list | |||||
if (null == this.matchingSyntaxNodes) | |||||
{ | |||||
this.matchingSyntaxNodes = new List<SyntaxNode>(); | |||||
} | |||||
// Add the node to the known matching node | |||||
this.matchingSyntaxNodes.Add(node); | |||||
} | |||||
/// <summary> | |||||
/// Copies to a given node. | |||||
/// </summary> | |||||
/// <param name="targetNode">The node wherer to copy.</param> | |||||
/// <param name="name">The node name.</param> | |||||
public void CopyTo(CSharpSyntaxMatchingNode targetNode, string name) | |||||
{ | |||||
// Data validation | |||||
Ensure.That(() => name).IsNotNullOrWhiteSpace(); | |||||
Ensure.That(() => targetNode).IsNotNull(); | |||||
// Ensure and retrieve a node the the copy | |||||
CSharpSyntaxMatchingNode newNode = targetNode.EnsureNode(name); | |||||
// Add syntax node to the created node | |||||
if (null != this.matchingSyntaxNodes) | |||||
{ | |||||
// Lazy create the syntax nodes | |||||
if (null == newNode.matchingSyntaxNodes) | |||||
{ | |||||
newNode.matchingSyntaxNodes = new List<SyntaxNode>(); | |||||
} | |||||
// Merge syntax nodes | |||||
int[] indexes = newNode.matchingSyntaxNodes.Select(x => x.Span.Start).ToArray(); | |||||
newNode.matchingSyntaxNodes.AddRange(this.matchingSyntaxNodes.Where(x => !indexes.Contains(x.Span.Start))); | |||||
} | |||||
// Recurse for applying copy to the children | |||||
if (null != this.children && this.children.Count > 0) | |||||
{ | |||||
string[] childrenName = this.children.Keys.ToArray(); | |||||
foreach (string childName in childrenName) | |||||
{ | |||||
this.children[childName].CopyTo(newNode, childName); | |||||
} | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Overrides ToString to renger the internal Trie to a string. | |||||
/// </summary> | |||||
/// <returns>The rendered Trie as string.</returns> | |||||
public override string ToString() | |||||
{ | |||||
StringBuilder strinbBuilder = new StringBuilder(); | |||||
this.Write(strinbBuilder, null, 0); | |||||
return strinbBuilder.ToString(); | |||||
} | |||||
/// <summary> | |||||
/// Writes a node to a string builder and recurse to the children. | |||||
/// </summary> | |||||
/// <param name="stringBuilder">The string builder used as output.</param> | |||||
/// <param name="name">The node name.</param> | |||||
/// <param name="level">The node level.</param> | |||||
private void Write(StringBuilder stringBuilder, string name, int level) | |||||
{ | |||||
// Print the node only if the name is not null in order to ignore the root node for more clarity | |||||
int nextLevel = level; | |||||
if (null != name) | |||||
{ | |||||
++nextLevel; | |||||
stringBuilder.AppendLine(string.Format("{0}{1}", new string('-', level), name)); | |||||
} | |||||
// Print each matching syntax node | |||||
foreach (var matchingSyntaxNode in this.MatchingSyntaxNodes) | |||||
{ | |||||
stringBuilder.AppendLine(string.Format("{0}[{1}]", new string('-', level), matchingSyntaxNode.GetType().Name)); | |||||
} | |||||
// Recurse to children | |||||
if (null != this.children) | |||||
{ | |||||
foreach (string childName in this.children.Keys) | |||||
{ | |||||
this.children[childName].Write(stringBuilder, childName, nextLevel); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,329 @@ | |||||
using Microsoft.CodeAnalysis; | |||||
using Microsoft.CodeAnalysis.CSharp; | |||||
using Microsoft.CodeAnalysis.CSharp.Syntax; | |||||
using System; | |||||
using System.Linq; | |||||
namespace Projbook.Extension.CSharpExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Implements a syntax walker generating a Trie for pattern matching. | |||||
/// </summary> | |||||
public class CSharpSyntaxWalkerMatchingBuilder : CSharpSyntaxWalker | |||||
{ | |||||
/// <summary> | |||||
/// The current Trie root available from the outside. | |||||
/// </summary> | |||||
public CSharpSyntaxMatchingNode Root { get; private set; } | |||||
/// <summary> | |||||
/// The Trie root referencing the root without any reference change. | |||||
/// </summary> | |||||
private CSharpSyntaxMatchingNode internalInvariantRoot; | |||||
/// <summary> | |||||
/// Initializes a new instance of <see cref="CSharpSyntaxWalkerMatchingBuilder"/>. | |||||
/// </summary> | |||||
public CSharpSyntaxWalkerMatchingBuilder() | |||||
{ | |||||
this.internalInvariantRoot = new CSharpSyntaxMatchingNode(); | |||||
this.Root = this.internalInvariantRoot; | |||||
} | |||||
/// <summary> | |||||
/// Visits a namespace declaration. | |||||
/// A namespace may be composed with different segment dot separated, each segment has to be represented by a different node. | |||||
/// However the syntax node is attached to the last node only. | |||||
/// </summary> | |||||
/// <param name="node">The namespace declaration node to visit.</param> | |||||
public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) | |||||
{ | |||||
// Retrieve the namespace name and split segments | |||||
string name = node.Name.ToString(); | |||||
string[] namespaces = name.Split('.'); | |||||
// Keep track of the initial node the restore the root after the visit | |||||
CSharpSyntaxMatchingNode initialNode = this.Root; | |||||
// Browse all namespaces and generate intermediate node for each segment for the copy to the root | |||||
CSharpSyntaxMatchingNode firstNamespaceNode = null; | |||||
foreach (string currentNamespace in namespaces) | |||||
{ | |||||
// Create the node and keep track of the first one | |||||
this.Root = this.Root.EnsureNode(currentNamespace); | |||||
if (null == firstNamespaceNode) | |||||
{ | |||||
firstNamespaceNode = this.Root; | |||||
} | |||||
} | |||||
// Add the syntax node the last segment | |||||
this.Root.AddSyntaxNode(node); | |||||
// Triger member visiting | |||||
base.VisitNamespaceDeclaration(node); | |||||
// Copy the generated sub tree to the Trie root | |||||
firstNamespaceNode.CopyTo(this.internalInvariantRoot, namespaces[0]); | |||||
// Restore the initial root | |||||
this.Root = initialNode; | |||||
} | |||||
/// <summary> | |||||
/// Visits a class declaration. | |||||
/// </summary> | |||||
/// <param name="node">The class declaration to visit.</param> | |||||
public override void VisitClassDeclaration(ClassDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<ClassDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: node.TypeParameterList, | |||||
exctractName: n => node.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitClassDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits an interface declaration. | |||||
/// </summary> | |||||
/// <param name="node">The class declaration to visit.</param> | |||||
public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<InterfaceDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: node.TypeParameterList, | |||||
exctractName: n => node.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitInterfaceDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits an enum declaration. | |||||
/// </summary> | |||||
/// <param name="node">The enum declaration to visit.</param> | |||||
public override void VisitEnumDeclaration(EnumDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<EnumDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => node.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitEnumDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits an enum member declaration. | |||||
/// </summary> | |||||
/// <param name="node">The enum member declaration to visit.</param> | |||||
public override void VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<EnumMemberDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => node.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitEnumMemberDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits a property declaration. | |||||
/// </summary> | |||||
/// <param name="node">The property declaration to visit.</param> | |||||
public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<PropertyDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => n.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitPropertyDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits a field declaration. | |||||
/// </summary> | |||||
/// <param name="node">The field declaration to visit.</param> | |||||
public override void VisitFieldDeclaration(FieldDeclarationSyntax node) | |||||
{ | |||||
// Visit each variable declaration | |||||
foreach(VariableDeclaratorSyntax variableDeclarationSyntax in node.Declaration.Variables) | |||||
{ | |||||
this.Visit<FieldDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => variableDeclarationSyntax.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitFieldDeclaration); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Visits an indexter declaration. | |||||
/// </summary> | |||||
/// <param name="node">The indexter declaration to visit.</param> | |||||
public override void VisitIndexerDeclaration(IndexerDeclarationSyntax node) | |||||
{ | |||||
// Compute suffix for representing generics | |||||
string memberName = string.Empty; | |||||
if (null != node.ParameterList) | |||||
{ | |||||
memberName = string.Format( | |||||
"[{0}]", | |||||
string.Join(",", node.ParameterList.Parameters.Select(x => x.Type.ToString()))); | |||||
} | |||||
// Visit | |||||
this.Visit<IndexerDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => memberName, | |||||
targetNode: n => n, | |||||
visit: base.VisitIndexerDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits an event declaration. | |||||
/// </summary> | |||||
/// <param name="node">The event declaration to visit.</param> | |||||
public override void VisitEventDeclaration(EventDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<EventDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => n.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitEventDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits an accessor declaration. | |||||
/// </summary> | |||||
/// <param name="node">The accessor declaration to visit.</param> | |||||
public override void VisitAccessorDeclaration(AccessorDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<AccessorDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => n.Keyword.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitAccessorDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits a method declaration. | |||||
/// </summary> | |||||
/// <param name="node">The method declaration to visit.</param> | |||||
public override void VisitMethodDeclaration(MethodDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<MethodDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: node.TypeParameterList, | |||||
exctractName: n => n.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitMethodDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits a constructor declaration. | |||||
/// </summary> | |||||
/// <param name="node">The constructor declaration to visit.</param> | |||||
public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<ConstructorDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => "<Constructor>", | |||||
targetNode: n => n, | |||||
visit: base.VisitConstructorDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits a destructor declaration. | |||||
/// </summary> | |||||
/// <param name="node">The destructor declaration to visit.</param> | |||||
public override void VisitDestructorDeclaration(DestructorDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<DestructorDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => "<Destructor>", | |||||
targetNode: n => n, | |||||
visit: base.VisitDestructorDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits parameter list. | |||||
/// </summary> | |||||
/// <param name="node">The parameter list to visit.</param> | |||||
public override void VisitParameterList(ParameterListSyntax node) | |||||
{ | |||||
// Skip parameter list when the parent is a lambda | |||||
if ( | |||||
SyntaxKind.SimpleLambdaExpression == node.Parent.Kind() || | |||||
SyntaxKind.ParenthesizedLambdaExpression == node.Parent.Kind()) | |||||
{ | |||||
return; | |||||
} | |||||
// Visit | |||||
this.Visit<ParameterListSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => string.Format("({0})", string.Join(",", node.Parameters.Select(x => x.Type.ToString()))), | |||||
targetNode: n => n.Parent, | |||||
visit: base.VisitParameterList); | |||||
} | |||||
/// <summary> | |||||
/// Visits a member. | |||||
/// </summary> | |||||
/// <typeparam name="T">The syntax node type to visit.</typeparam> | |||||
/// <param name="node">The node to visit.</param> | |||||
/// <param name="exctractName">Extract the node name.</param> | |||||
/// <param name="typeParameterList">The type parameter list.</param> | |||||
/// <param name="targetNode">Resolved the target node.</param> | |||||
/// <param name="visit">Visit sub nodes.</param> | |||||
private void Visit<T>(T node, Func<T, string> exctractName, TypeParameterListSyntax typeParameterList , Func<T, SyntaxNode> targetNode, Action<T> visit) where T : CSharpSyntaxNode | |||||
{ | |||||
// Retrieve the accessor name | |||||
string name = exctractName(node); | |||||
// Compute suffix for representing generics | |||||
if (null != typeParameterList) | |||||
{ | |||||
name = string.Format( | |||||
"{0}{{{1}}}", | |||||
name, | |||||
string.Join(",", typeParameterList.Parameters.Select(x => x.ToString()))); | |||||
} | |||||
// Keep track of the initial node the restore the root after the visit | |||||
CSharpSyntaxMatchingNode initialNode = this.Root; | |||||
// Create and add the node | |||||
this.Root = this.Root.EnsureNode(name); | |||||
this.Root.AddSyntaxNode(targetNode(node)); | |||||
// Trigger member visiting | |||||
visit(node); | |||||
// Copy the class sub tree to the Trie root | |||||
this.Root.CopyTo(this.internalInvariantRoot, name); | |||||
// Restore the initial root | |||||
this.Root = initialNode; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,36 @@ | |||||
using System.Reflection; | |||||
using System.Runtime.CompilerServices; | |||||
using System.Runtime.InteropServices; | |||||
// General Information about an assembly is controlled through the following | |||||
// set of attributes. Change these attribute values to modify the information | |||||
// associated with an assembly. | |||||
[assembly: AssemblyTitle("Projbook.Extension.CSharpExtractor")] | |||||
[assembly: AssemblyDescription("")] | |||||
[assembly: AssemblyConfiguration("")] | |||||
[assembly: AssemblyCompany("")] | |||||
[assembly: AssemblyProduct("Projbook.Extension.CSharpExtractor")] | |||||
[assembly: AssemblyCopyright("Copyright © 2016")] | |||||
[assembly: AssemblyTrademark("")] | |||||
[assembly: AssemblyCulture("")] | |||||
// Setting ComVisible to false makes the types in this assembly not visible | |||||
// to COM components. If you need to access a type in this assembly from | |||||
// COM, set the ComVisible attribute to true on that type. | |||||
[assembly: ComVisible(false)] | |||||
// The following GUID is for the ID of the typelib if this project is exposed to COM | |||||
[assembly: Guid("f5431901-29ac-46d4-a717-de2a9114e82d")] | |||||
// Version information for an assembly consists of the following four values: | |||||
// | |||||
// Major Version | |||||
// Minor Version | |||||
// Build Number | |||||
// Revision | |||||
// | |||||
// You can specify all the values or you can default the Build and Revision Numbers | |||||
// by using the '*' as shown below: | |||||
// [assembly: AssemblyVersion("1.0.*")] | |||||
[assembly: AssemblyVersion("1.0.0.0")] | |||||
[assembly: AssemblyFileVersion("1.0.0.0")] |
@@ -0,0 +1,9 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<packages> | |||||
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net45" /> | |||||
<package id="Microsoft.CodeAnalysis.Common" version="1.3.2" targetFramework="net45" /> | |||||
<package id="Microsoft.CodeAnalysis.CSharp" version="1.3.2" targetFramework="net45" /> | |||||
<package id="System.Collections.Immutable" version="1.1.37" targetFramework="net45" /> | |||||
<package id="System.IO.Abstractions" version="2.0.0.136" targetFramework="net45" /> | |||||
<package id="System.Reflection.Metadata" version="1.2.0" targetFramework="net45" /> | |||||
</packages> |
@@ -0,0 +1,68 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> | |||||
<PropertyGroup> | |||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | |||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | |||||
<ProjectGuid>{BC3E43EB-2263-49B4-883A-B720EDDF9298}</ProjectGuid> | |||||
<OutputType>Library</OutputType> | |||||
<AppDesignerFolder>Properties</AppDesignerFolder> | |||||
<RootNamespace>Projbook.Extension.XmlExtractor</RootNamespace> | |||||
<AssemblyName>Projbook.Extension.XmlExtractor</AssemblyName> | |||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> | |||||
<FileAlignment>512</FileAlignment> | |||||
<TargetFrameworkProfile /> | |||||
</PropertyGroup> | |||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | |||||
<DebugSymbols>true</DebugSymbols> | |||||
<DebugType>full</DebugType> | |||||
<Optimize>false</Optimize> | |||||
<OutputPath>bin\Debug\</OutputPath> | |||||
<DefineConstants>DEBUG;TRACE</DefineConstants> | |||||
<ErrorReport>prompt</ErrorReport> | |||||
<WarningLevel>4</WarningLevel> | |||||
</PropertyGroup> | |||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | |||||
<DebugType>pdbonly</DebugType> | |||||
<Optimize>true</Optimize> | |||||
<OutputPath>bin\Release\</OutputPath> | |||||
<DefineConstants>TRACE</DefineConstants> | |||||
<ErrorReport>prompt</ErrorReport> | |||||
<WarningLevel>4</WarningLevel> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<Reference Include="System" /> | |||||
<Reference Include="System.Core" /> | |||||
<Reference Include="System.IO.Abstractions, Version=2.0.0.136, Culture=neutral, processorArchitecture=MSIL"> | |||||
<HintPath>..\packages\System.IO.Abstractions.2.0.0.136\lib\net40\System.IO.Abstractions.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="System.Xml.Linq" /> | |||||
<Reference Include="System.Data.DataSetExtensions" /> | |||||
<Reference Include="Microsoft.CSharp" /> | |||||
<Reference Include="System.Data" /> | |||||
<Reference Include="System.Net.Http" /> | |||||
<Reference Include="System.Xml" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<Compile Include="Projbook\Extension\Xml\XmlSnippetExtractor.cs" /> | |||||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<ProjectReference Include="..\Projbook.Extension\Projbook.Extension.csproj"> | |||||
<Project>{8338b756-0519-4d20-ba04-3a8f4839237a}</Project> | |||||
<Name>Projbook.Extension</Name> | |||||
</ProjectReference> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<None Include="packages.config" /> | |||||
</ItemGroup> | |||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | |||||
Other similar extension points exist, see Microsoft.Common.targets. | |||||
<Target Name="BeforeBuild"> | |||||
</Target> | |||||
<Target Name="AfterBuild"> | |||||
</Target> | |||||
--> | |||||
</Project> |
@@ -0,0 +1,159 @@ | |||||
using System; | |||||
using Projbook.Extension.Exception; | |||||
using Projbook.Extension.Spi; | |||||
using System.IO.Abstractions; | |||||
using System.Text; | |||||
using System.Text.RegularExpressions; | |||||
using System.Xml; | |||||
namespace Projbook.Extension.XmlExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Extractor in charge of browsing source directories. load file content and extract requested member. | |||||
/// </summary> | |||||
[Syntax(name: "xml")] | |||||
public class XmlSnippetExtractor : DefaultSnippetExtractor | |||||
{ | |||||
/// <summary> | |||||
/// The regex extracting the document namespaces | |||||
/// </summary> | |||||
private Regex regex = new Regex(@"xmlns:([^=]+)=""([^""]*)""", RegexOptions.Compiled); | |||||
/// <summary> | |||||
/// The lazy loaded xml document. | |||||
/// </summary> | |||||
private XmlDocument xmlDocument; | |||||
/// <summary> | |||||
/// The lazy loaded namespace manager. | |||||
/// </summary> | |||||
private XmlNamespaceManager xmlNamespaceManager; | |||||
/// <summary> | |||||
/// Extracts a snippet from a given rule pattern. | |||||
/// </summary> | |||||
/// <param name="fileSystemInfo">The file system info.</param> | |||||
/// <param name="memberPattern">The member pattern to extract.</param> | |||||
/// <returns>The extracted snippet.</returns> | |||||
public override Extension.Model.Snippet Extract(FileSystemInfoBase fileSystemInfo, string memberPattern) | |||||
{ | |||||
// Return the entire code if no member is specified | |||||
if (string.IsNullOrWhiteSpace(memberPattern)) | |||||
{ | |||||
return base.Extract(fileSystemInfo, memberPattern); | |||||
} | |||||
// Load the xml document for xpath execution | |||||
if (null == this.xmlDocument) | |||||
{ | |||||
// Load file content | |||||
string sourceCode = base.LoadFile(this.ConvertToFile(fileSystemInfo)); | |||||
// Remove default avoiding to define and use a prefix for the default namespace | |||||
// This is not strictly correct in a xml point of view but it's closest to most needs | |||||
sourceCode = Regex.Replace(sourceCode, @"xmlns\s*=\s*""[^""]*""", string.Empty); | |||||
// Parse the file as xml | |||||
this.xmlDocument = new XmlDocument(); | |||||
try | |||||
{ | |||||
// Initialize the document and the namespace manager | |||||
this.xmlDocument.LoadXml(sourceCode); | |||||
this.xmlNamespaceManager = new XmlNamespaceManager(this.xmlDocument.NameTable); | |||||
// Match namespace declaration for filling the namespace manager | |||||
Match match = this.regex.Match(sourceCode); | |||||
while (match.Success) | |||||
{ | |||||
// Collect prefix and namespace value | |||||
string prefix = match.Groups[1].Value.Trim(); | |||||
string ns = match.Groups[2].Value.Trim(); | |||||
// Add namespace declaration to the namespace manager | |||||
xmlNamespaceManager.AddNamespace(prefix, ns); | |||||
// Mode to the next matching | |||||
match = match.NextMatch(); | |||||
} | |||||
} | |||||
// Throw an exception is the file is not loadable as xml document | |||||
catch (System.Exception exception) | |||||
{ | |||||
throw new SnippetExtractionException("Cannot parse xml file", exception.Message); | |||||
} | |||||
} | |||||
// Execute Xpath query | |||||
XmlNodeList xmlNodeList = null; | |||||
try | |||||
{ | |||||
xmlNodeList = this.xmlDocument.SelectNodes(memberPattern, this.xmlNamespaceManager); | |||||
} | |||||
catch | |||||
{ | |||||
throw new SnippetExtractionException("Invalid extraction rule", memberPattern); | |||||
} | |||||
// Ensure we found a result | |||||
if (xmlNodeList.Count <= 0) | |||||
{ | |||||
throw new SnippetExtractionException("Cannot find member", memberPattern); | |||||
} | |||||
// Build a snippet for extracted nodes | |||||
return this.BuildSnippet(xmlNodeList); | |||||
} | |||||
/// <summary> | |||||
/// Builds a snippet from xml node. | |||||
/// </summary> | |||||
/// <param name="xmlNodeList">The xml node list.</param> | |||||
/// <returns>The built snippet.</returns> | |||||
private Extension.Model.Snippet BuildSnippet(XmlNodeList xmlNodeList) | |||||
{ | |||||
// Data validation | |||||
if(xmlNodeList == null) | |||||
{ | |||||
throw new ArgumentNullException(nameof(xmlNodeList)); | |||||
} | |||||
// Extract code from each snippets | |||||
StringBuilder stringBuilder = new StringBuilder(); | |||||
bool firstSnippet = true; | |||||
for (int i = 0; i < xmlNodeList.Count; ++i) | |||||
{ | |||||
// Get the current node | |||||
XmlNode node = xmlNodeList.Item(i); | |||||
// Write line return between each snippet | |||||
if (!firstSnippet) | |||||
{ | |||||
stringBuilder.AppendLine(); | |||||
stringBuilder.AppendLine(); | |||||
} | |||||
// Write each snippet | |||||
XmlWriterSettings settings = new XmlWriterSettings(); | |||||
settings.Indent = true; | |||||
settings.OmitXmlDeclaration = true; | |||||
settings.NewLineOnAttributes = true; | |||||
using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder, settings)) | |||||
{ | |||||
node.WriteTo(xmlWriter); | |||||
} | |||||
// Flag the first snippet as false | |||||
firstSnippet = false; | |||||
} | |||||
// Remove all generate namespace declaration | |||||
// This is produce some output lacking of namespace declaration but it's what is relevant for a xml document extraction | |||||
string output = stringBuilder.ToString(); | |||||
output = Regex.Replace(output, @" ?xmlns\s*(:[^=]+)?\s*=\s*""[^""]*""", string.Empty); | |||||
// Create the snippet from the extracted code | |||||
return new Model.PlainTextSnippet(output); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,36 @@ | |||||
using System.Reflection; | |||||
using System.Runtime.CompilerServices; | |||||
using System.Runtime.InteropServices; | |||||
// General Information about an assembly is controlled through the following | |||||
// set of attributes. Change these attribute values to modify the information | |||||
// associated with an assembly. | |||||
[assembly: AssemblyTitle("Projbook.Extension.XmlExtractor")] | |||||
[assembly: AssemblyDescription("")] | |||||
[assembly: AssemblyConfiguration("")] | |||||
[assembly: AssemblyCompany("")] | |||||
[assembly: AssemblyProduct("Projbook.Extension.XmlExtractor")] | |||||
[assembly: AssemblyCopyright("Copyright © 2016")] | |||||
[assembly: AssemblyTrademark("")] | |||||
[assembly: AssemblyCulture("")] | |||||
// Setting ComVisible to false makes the types in this assembly not visible | |||||
// to COM components. If you need to access a type in this assembly from | |||||
// COM, set the ComVisible attribute to true on that type. | |||||
[assembly: ComVisible(false)] | |||||
// The following GUID is for the ID of the typelib if this project is exposed to COM | |||||
[assembly: Guid("bc3e43eb-2263-49b4-883a-b720eddf9298")] | |||||
// Version information for an assembly consists of the following four values: | |||||
// | |||||
// Major Version | |||||
// Minor Version | |||||
// Build Number | |||||
// Revision | |||||
// | |||||
// You can specify all the values or you can default the Build and Revision Numbers | |||||
// by using the '*' as shown below: | |||||
// [assembly: AssemblyVersion("1.0.*")] | |||||
[assembly: AssemblyVersion("1.0.0.0")] | |||||
[assembly: AssemblyFileVersion("1.0.0.0")] |
@@ -0,0 +1,5 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<packages> | |||||
<package id="Ensure.That" version="5.0.0" targetFramework="net45" /> | |||||
<package id="System.IO.Abstractions" version="2.0.0.136" targetFramework="net45" /> | |||||
</packages> |
@@ -0,0 +1,53 @@ | |||||
using System.Text; | |||||
using System; | |||||
using Projbook.Extension.Spi; | |||||
using System.IO; | |||||
namespace Projbook.Extension | |||||
{ | |||||
/// <summary> | |||||
/// Extractor in charge of browsing source directories. load file content and extract requested member. | |||||
/// </summary> | |||||
public class DefaultSnippetExtractor : ISnippetExtractor | |||||
{ | |||||
/// <summary> | |||||
/// File target type. | |||||
/// </summary> | |||||
public TargetType TargetType { get { return TargetType.File; } } | |||||
/// <summary> | |||||
/// Extracts a snippet. | |||||
/// </summary> | |||||
/// <param name="fullFilename">The full filename (with path) to load and to extract the snippet from.</param> | |||||
/// <param name="pattern">The extraction pattern, never used for this implementation.</param> | |||||
/// <returns> | |||||
/// The extracted snippet. | |||||
/// </returns> | |||||
/// <exception cref="System.ArgumentNullException">fileSystemInfo</exception> | |||||
public virtual string Extract(string fullFilename, string pattern) | |||||
{ | |||||
if(string.IsNullOrEmpty(fullFilename)) | |||||
{ | |||||
throw new ArgumentNullException(nameof(fullFilename)); | |||||
} | |||||
return this.LoadFile(fullFilename) ?? string.Empty; | |||||
} | |||||
/// <summary> | |||||
/// Loads a file from the file name. | |||||
/// </summary> | |||||
/// <param name="fullFilename">The full filename.</param> | |||||
/// <returns> | |||||
/// The file's content. | |||||
/// </returns> | |||||
/// <exception cref="System.ArgumentNullException">fileInfo</exception> | |||||
protected string LoadFile(string fullFilename) | |||||
{ | |||||
if(string.IsNullOrEmpty(fullFilename)) | |||||
{ | |||||
throw new ArgumentNullException(nameof(fullFilename)); | |||||
} | |||||
return File.ReadAllText(fullFilename, Encoding.UTF8); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,25 @@ | |||||
namespace Projbook.Extension.Exception | |||||
{ | |||||
/// <summary> | |||||
/// Represents a snippet extraction exception. | |||||
/// </summary> | |||||
public class SnippetExtractionException : System.Exception | |||||
{ | |||||
/// <summary> | |||||
/// The pattern the exception is about. | |||||
/// </summary> | |||||
public string Pattern { get; private set; } | |||||
/// <summary> | |||||
/// Initializes a new instance of <see cref="ProjbookEngine"/>. | |||||
/// </summary> | |||||
/// <param name="message">Initializes the required <see cref="Message"/>.</param> | |||||
/// <param name="pattern">Initializes the required <see cref="Pattern"/>.</param> | |||||
public SnippetExtractionException(string message, string pattern) | |||||
: base(message) | |||||
{ | |||||
// Initialize | |||||
this.Pattern = pattern; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,23 @@ | |||||
namespace Projbook.Extension.CSharpExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Represents the extraction mode. | |||||
/// </summary> | |||||
public enum CSharpExtractionMode | |||||
{ | |||||
/// <summary> | |||||
/// Full member: Do not process the snippet and print it as it. | |||||
/// </summary> | |||||
FullMember, | |||||
/// <summary> | |||||
/// Content only: Extract the code block and print this part only. | |||||
/// </summary> | |||||
ContentOnly, | |||||
/// <summary> | |||||
/// Block structure only: Remove the block content and print the code structure only. | |||||
/// </summary> | |||||
BlockStructureOnly | |||||
} | |||||
} |
@@ -0,0 +1,80 @@ | |||||
using Projbook.Extension.Exception; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text.RegularExpressions; | |||||
namespace Projbook.Extension.CSharpExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Represents a matching rule for referencing a C# member. | |||||
/// </summary> | |||||
public class CSharpMatchingRule | |||||
{ | |||||
/// <summary> | |||||
/// The matching chunk to identify which member are the snippet targets. | |||||
/// </summary> | |||||
public string[] MatchingChunks { get; private set; } | |||||
/// <summary> | |||||
/// The snippet extraction mode. | |||||
/// </summary> | |||||
public CSharpExtractionMode ExtractionMode { get; private set; } | |||||
/// <summary> | |||||
/// Defines rule regex used to parse the snippet into chunks. | |||||
/// Expected input format: Path/File.cs [My.Name.Space.Class.Method][(string, string)] | |||||
/// * The first chunk is the file name and will be loaded in <seealso cref="TargetFile"/> | |||||
/// * The optional second chunks are all full qualified name to the member separated by "." | |||||
/// * The optional last chunk is the method parameters if matching a method. | |||||
/// </summary> | |||||
private static Regex ruleRegex = new Regex(@"^([-=])?([^(]+)?\s*(\([^)]*\s*\))?\s*$", RegexOptions.Compiled); | |||||
/// <summary> | |||||
/// Parses the token | |||||
/// </summary> | |||||
/// <param name="pattern"></param> | |||||
/// <returns></returns> | |||||
public static CSharpMatchingRule Parse(string pattern) | |||||
{ | |||||
// Try to match the regex | |||||
pattern = Regex.Replace(pattern, @"\s", string.Empty); | |||||
Match match = CSharpMatchingRule.ruleRegex.Match(pattern); | |||||
if (!match.Success || string.IsNullOrWhiteSpace(match.Groups[0].Value)) | |||||
{ | |||||
throw new SnippetExtractionException("Invalid extraction rule", pattern); | |||||
} | |||||
// Retrieve values from the regex matching | |||||
string extractionOption = match.Groups[1].Value; | |||||
string rawMember = match.Groups[2].Value.Trim(); | |||||
string rawParameters = match.Groups[3].Value.Trim(); | |||||
// Build The matching chunk with extracted data | |||||
List<string> matchingChunks = new List<string>(); | |||||
matchingChunks.AddRange(rawMember.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries)); | |||||
if (rawParameters.Length >= 1) | |||||
{ | |||||
matchingChunks.Add(rawParameters); | |||||
} | |||||
// Read extraction mode | |||||
CSharpExtractionMode extractionMode = CSharpExtractionMode.FullMember; | |||||
switch (extractionOption) | |||||
{ | |||||
case "-": | |||||
extractionMode = CSharpExtractionMode.ContentOnly; | |||||
break; | |||||
case "=": | |||||
extractionMode = CSharpExtractionMode.BlockStructureOnly; | |||||
break; | |||||
} | |||||
// Build the matching rule based on the regex matching | |||||
return new CSharpMatchingRule | |||||
{ | |||||
MatchingChunks = matchingChunks.ToArray(), | |||||
ExtractionMode = extractionMode | |||||
}; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,362 @@ | |||||
using Microsoft.CodeAnalysis; | |||||
using Microsoft.CodeAnalysis.CSharp; | |||||
using Microsoft.CodeAnalysis.CSharp.Syntax; | |||||
using Microsoft.CodeAnalysis.Text; | |||||
using Projbook.Extension.Exception; | |||||
using Projbook.Extension.Spi; | |||||
using System; | |||||
using System.Linq; | |||||
using System.Text; | |||||
namespace Projbook.Extension.CSharpExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Extractor in charge of browsing source directories. load file content and extract requested member. | |||||
/// </summary> | |||||
public class CSharpSnippetExtractor : DefaultSnippetExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Represents the matching trie used for member matching. | |||||
/// Because of the cost of building the Trie, this value is lazy loaded and kept for future usages. | |||||
/// </summary> | |||||
private CSharpSyntaxMatchingNode syntaxTrie; | |||||
/// <summary> | |||||
/// Extracts a snippet from a given rule pattern. | |||||
/// </summary> | |||||
/// <param name="fullFilename">The full filename (with path) to load and to extract the snippet from.</param> | |||||
/// <param name="memberPattern">The member pattern to extract.</param> | |||||
/// <returns>The extracted snippet.</returns> | |||||
public override string Extract(string fullFilename, string memberPattern) | |||||
{ | |||||
// Return the entire code if no member is specified | |||||
if (string.IsNullOrWhiteSpace(memberPattern)) | |||||
{ | |||||
return base.Extract(fullFilename, memberPattern); | |||||
} | |||||
// Parse the matching rule from the pattern | |||||
CSharpMatchingRule rule = CSharpMatchingRule.Parse(memberPattern); | |||||
// Load the trie for pattern matching | |||||
if (null == this.syntaxTrie) | |||||
{ | |||||
// Load file content | |||||
string sourceCode = this.LoadFile(fullFilename); | |||||
// Build a syntax tree from the source code | |||||
SyntaxTree tree = CSharpSyntaxTree.ParseText(sourceCode); | |||||
SyntaxNode root = tree.GetRoot(); | |||||
// Visit the syntax tree for generating a Trie for pattern matching | |||||
CSharpSyntaxWalkerMatchingBuilder syntaxMatchingBuilder = new CSharpSyntaxWalkerMatchingBuilder(); | |||||
syntaxMatchingBuilder.Visit(root); | |||||
// Retrieve the Trie root | |||||
this.syntaxTrie = syntaxMatchingBuilder.Root; | |||||
} | |||||
// Match the rule from the syntax matching Trie | |||||
CSharpSyntaxMatchingNode matchingTrie = syntaxTrie.Match(rule.MatchingChunks); | |||||
if (null == matchingTrie) | |||||
{ | |||||
throw new SnippetExtractionException("Cannot find member", memberPattern); | |||||
} | |||||
// Build a snippet for extracted syntax nodes | |||||
return this.BuildSnippet(matchingTrie.MatchingSyntaxNodes, rule.ExtractionMode); | |||||
} | |||||
/// <summary> | |||||
/// Builds a snippet from extracted syntax nodes. | |||||
/// </summary> | |||||
/// <param name="nodes">The exctracted nodes.</param> | |||||
/// <param name="extractionMode">The extraction mode.</param> | |||||
/// <returns>The built snippet.</returns> | |||||
private string BuildSnippet(SyntaxNode[] nodes, CSharpExtractionMode extractionMode) | |||||
{ | |||||
if(nodes == null || !nodes.Any()) | |||||
{ | |||||
throw new ArgumentException("'nodes' is null or empty"); | |||||
} | |||||
// Extract code from each snippets | |||||
StringBuilder stringBuilder = new StringBuilder(); | |||||
bool firstSnippet = true; | |||||
foreach (SyntaxNode node in nodes) | |||||
{ | |||||
// Write line return between each snippet | |||||
if (!firstSnippet) | |||||
{ | |||||
stringBuilder.AppendLine(); | |||||
stringBuilder.AppendLine(); | |||||
} | |||||
// Write each snippet line | |||||
string[] lines = node.GetText().Lines.Select(x => x.ToString()).ToArray(); | |||||
int contentPosition = this.DetermineContentPosition(node); | |||||
this.WriteAndCleanupSnippet(stringBuilder, lines, extractionMode, contentPosition); | |||||
// Flag the first snippet as false | |||||
firstSnippet = false; | |||||
} | |||||
// Create the snippet from the exctracted code | |||||
return stringBuilder.ToString(); | |||||
} | |||||
/// <summary> | |||||
/// Determines the content's block position depending on the node type. | |||||
/// </summary> | |||||
/// <param name="node">The node to extract the content position from.</param> | |||||
/// <returns>The determined content position or 0 if not found.</returns> | |||||
private int DetermineContentPosition(SyntaxNode node) | |||||
{ | |||||
if(node == null) | |||||
{ | |||||
throw new ArgumentNullException(nameof(node)); | |||||
} | |||||
// Select the content node element depending on the node type | |||||
TextSpan? contentTextSpan = null; | |||||
switch (node.Kind()) | |||||
{ | |||||
// Accessor list content | |||||
case SyntaxKind.PropertyDeclaration: | |||||
case SyntaxKind.IndexerDeclaration: | |||||
case SyntaxKind.EventDeclaration: | |||||
AccessorListSyntax accessorList = node.DescendantNodes().OfType<AccessorListSyntax>().FirstOrDefault(); | |||||
if (null != accessorList) | |||||
{ | |||||
contentTextSpan = accessorList.FullSpan; | |||||
} | |||||
break; | |||||
// Contains children | |||||
case SyntaxKind.NamespaceDeclaration: | |||||
case SyntaxKind.InterfaceDeclaration: | |||||
case SyntaxKind.ClassDeclaration: | |||||
SyntaxToken token = node.ChildTokens().FirstOrDefault(x => x.Kind() == SyntaxKind.OpenBraceToken); | |||||
if (null != token) | |||||
{ | |||||
contentTextSpan = token.FullSpan; | |||||
} | |||||
break; | |||||
// Block content | |||||
case SyntaxKind.ConstructorDeclaration: | |||||
case SyntaxKind.DestructorDeclaration: | |||||
case SyntaxKind.MethodDeclaration: | |||||
case SyntaxKind.GetAccessorDeclaration: | |||||
case SyntaxKind.SetAccessorDeclaration: | |||||
case SyntaxKind.AddAccessorDeclaration: | |||||
case SyntaxKind.RemoveAccessorDeclaration: | |||||
BlockSyntax block = node.DescendantNodes().OfType<BlockSyntax>().FirstOrDefault(); | |||||
if (null != block) | |||||
{ | |||||
contentTextSpan = block.FullSpan; | |||||
} | |||||
break; | |||||
// Not processed by projbook csharp extractor | |||||
default: | |||||
break; | |||||
} | |||||
// Compute a line break insensitive position based on the fetched content text span if any is found | |||||
if (null != contentTextSpan) | |||||
{ | |||||
int relativeTextSpanStart = contentTextSpan.Value.Start - node.FullSpan.Start; | |||||
return node | |||||
.ToFullString() | |||||
.Substring(0, relativeTextSpanStart) | |||||
.Replace("\r\n", "") | |||||
.Replace("\n", "").Length; | |||||
} | |||||
// Otherwise return 0 as default value | |||||
return 0; | |||||
} | |||||
/// <summary> | |||||
/// Writes and cleanup line snippets. | |||||
/// Snippets are moved out of their context, for this reason we need to trim lines aroung and remove a part of the indentation. | |||||
/// </summary> | |||||
/// <param name="stringBuilder">The string builder used as output.</param> | |||||
/// <param name="lines">The lines to process.</param> | |||||
/// <param name="extractionMode">The extraction mode.</param> | |||||
/// <param name="contentPosition">The content position.</param> | |||||
private void WriteAndCleanupSnippet(StringBuilder stringBuilder, string[] lines, CSharpExtractionMode extractionMode, int contentPosition) | |||||
{ | |||||
// Do not process if lines are empty | |||||
if (0 >= lines.Length) | |||||
{ | |||||
return; | |||||
} | |||||
// Compute the index of the first selected line | |||||
int startPos = 0; | |||||
int skippedCharNumber = 0; | |||||
if (CSharpExtractionMode.ContentOnly == extractionMode) | |||||
{ | |||||
// Compute the content position index in the first processed line | |||||
int contentPositionFirstLineIndex = 0; | |||||
for (int totalLinePosition = 0; startPos < lines.Length; ++startPos) | |||||
{ | |||||
// Compute the content position in the current line | |||||
string line = lines[startPos]; | |||||
int relativePosition = contentPosition - totalLinePosition; | |||||
int contentPositionInLine = relativePosition < line.Length ? relativePosition: -1; | |||||
// In expected in the current line | |||||
if (contentPositionInLine >= 0) | |||||
{ | |||||
// Look for the relative index in the current line | |||||
// Save the found index and break the iteration if any open bracket is found | |||||
int indexOf = line.IndexOf('{', contentPositionInLine); | |||||
if (0 <= indexOf) | |||||
{ | |||||
contentPositionFirstLineIndex = indexOf; | |||||
break; | |||||
} | |||||
} | |||||
// Move the total line position after the processed line | |||||
totalLinePosition += lines[startPos].Length; | |||||
} | |||||
// Extract block code if any opening bracket has been found | |||||
if (startPos < lines.Length) | |||||
{ | |||||
int openingBracketPos = lines[startPos].IndexOf('{', contentPositionFirstLineIndex); | |||||
if (openingBracketPos >= 0) | |||||
{ | |||||
// Extract the code before the curly bracket | |||||
if (lines[startPos].Length > openingBracketPos) | |||||
{ | |||||
lines[startPos] = lines[startPos].Substring(openingBracketPos + 1); | |||||
} | |||||
// Skip the current line if empty | |||||
if (string.IsNullOrWhiteSpace(lines[startPos]) && lines.Length > 1 + startPos) | |||||
{ | |||||
++startPos; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
// Skip leading whitespace lines and keep track of the amount of skipped char | |||||
for (; startPos < lines.Length; ++startPos) | |||||
{ | |||||
// Break on non whitespace line | |||||
string line = lines[startPos]; | |||||
if (line.Trim().Length > 0) | |||||
{ | |||||
break; | |||||
} | |||||
// Record skipped char number | |||||
skippedCharNumber += line.Length; | |||||
} | |||||
} | |||||
// Compute the index of the lastselected line | |||||
int endPos = -1 + lines.Length; | |||||
if (CSharpExtractionMode.ContentOnly == extractionMode) | |||||
{ | |||||
for (; 0 <= endPos && !lines[endPos].ToString().Contains('}'); --endPos); | |||||
// Extract block code if any closing bracket has been found | |||||
if (0 <= endPos) | |||||
{ | |||||
int closingBracketPos = lines[endPos].IndexOf('}'); | |||||
if (closingBracketPos >= 0) | |||||
{ | |||||
// Extract the code before the curly bracket | |||||
if (lines[endPos].Length > closingBracketPos) | |||||
lines[endPos] = lines[endPos].Substring(0, closingBracketPos).TrimEnd(); | |||||
} | |||||
// Skip the current line if empty | |||||
if (string.IsNullOrWhiteSpace(lines[endPos]) && lines.Length > -1 + endPos) | |||||
{ | |||||
--endPos; | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
for (; 0 <= endPos && lines[endPos].ToString().Trim().Length == 0; --endPos); | |||||
} | |||||
// Compute the padding to remove for removing a part of the indentation | |||||
int leftPadding = int.MaxValue; | |||||
for (int i = startPos; i <= endPos; ++i) | |||||
{ | |||||
// Ignore empty lines in the middle of the snippet | |||||
if (!string.IsNullOrWhiteSpace(lines[i])) | |||||
{ | |||||
// Adjust the left padding with the available whitespace at the beginning of the line | |||||
leftPadding = Math.Min(leftPadding, lines[i].ToString().TakeWhile(Char.IsWhiteSpace).Count()); | |||||
} | |||||
} | |||||
// Write selected lines to the string builder | |||||
bool firstLine = true; | |||||
for (int i = startPos; i <= endPos; ++i) | |||||
{ | |||||
// Write line return between each line | |||||
if (!firstLine) | |||||
{ | |||||
stringBuilder.AppendLine(); | |||||
} | |||||
// Remove a part of the indentation padding | |||||
if (lines[i].Length > leftPadding) | |||||
{ | |||||
string line = lines[i].Substring(leftPadding); | |||||
// Process the snippet depending on the extraction mode | |||||
switch (extractionMode) | |||||
{ | |||||
// Extract the block structure only | |||||
case CSharpExtractionMode.BlockStructureOnly: | |||||
// Compute the content position in the current line | |||||
int relativePosition = contentPosition - skippedCharNumber; | |||||
int contentPositionInLine = relativePosition < line.Length + leftPadding ? relativePosition : -1; | |||||
// Look for open bracket from the content position in line | |||||
int openingBracketPos = -1; | |||||
if (contentPositionInLine >= 0) | |||||
{ | |||||
openingBracketPos = line.IndexOf('{', Math.Max(0, contentPositionInLine - leftPadding)); | |||||
} | |||||
// Anonymize code content if an open bracket is found | |||||
if (openingBracketPos >= 0) | |||||
{ | |||||
// Extract the code before the curly bracket | |||||
if (line.Length > openingBracketPos) | |||||
line = line.Substring(0, 1 + openingBracketPos); | |||||
// Replace the content and close the block | |||||
line += string.Format("{0} // ...{0}}}", Environment.NewLine); | |||||
// Stop the iteration | |||||
endPos = i; | |||||
} | |||||
break; | |||||
} | |||||
// Append the line | |||||
stringBuilder.Append(line); | |||||
skippedCharNumber += lines[i].Length; | |||||
} | |||||
// Flag the first line as false | |||||
firstLine = false; | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,207 @@ | |||||
using System; | |||||
using Microsoft.CodeAnalysis; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
namespace Projbook.Extension.CSharpExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Represents a syntax matching node. | |||||
/// Thie node is used to build a Trie representing possible matching. | |||||
/// Each node contians children and matching syntax nodes. | |||||
/// </summary> | |||||
public class CSharpSyntaxMatchingNode | |||||
{ | |||||
/// <summary> | |||||
/// The public Matching SyntaxNodes. | |||||
/// </summary> | |||||
public SyntaxNode[] MatchingSyntaxNodes | |||||
{ | |||||
get | |||||
{ | |||||
// Return empty array id the nodes are empty | |||||
if (null == this.matchingSyntaxNodes) | |||||
{ | |||||
return new SyntaxNode[0]; | |||||
} | |||||
// Return the matching syntax nodes | |||||
return this.matchingSyntaxNodes.ToArray(); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// The node's children. | |||||
/// </summary> | |||||
private Dictionary<string, CSharpSyntaxMatchingNode> children; | |||||
/// <summary> | |||||
/// The node's maching syntax node. | |||||
/// </summary> | |||||
private List<SyntaxNode> matchingSyntaxNodes; | |||||
/// <summary> | |||||
/// Finds a node from syntax chunk. | |||||
/// </summary> | |||||
/// <param name="chunks">The chunks to match.</param> | |||||
/// <returns></returns> | |||||
public CSharpSyntaxMatchingNode Match(string[] chunks) | |||||
{ | |||||
if(chunks == null) | |||||
{ | |||||
throw new ArgumentNullException(nameof(chunks)); | |||||
} | |||||
// Browse the Trie until finding a matching | |||||
CSharpSyntaxMatchingNode matchingNode = this; | |||||
foreach (string fragment in chunks) | |||||
{ | |||||
// Could not find any matching | |||||
if (null == matchingNode.children || !matchingNode.children.TryGetValue(fragment, out matchingNode)) | |||||
{ | |||||
return null; | |||||
} | |||||
} | |||||
// Return the matching node | |||||
return matchingNode; | |||||
} | |||||
/// <summary> | |||||
/// Lookup a node from children and return it. if the node doesn't exist, a new one will be created and added to the children. | |||||
/// </summary> | |||||
/// <param name="name">The node name.</param> | |||||
/// <returns>The node matching the requested name.</returns> | |||||
public CSharpSyntaxMatchingNode EnsureNode(string name) | |||||
{ | |||||
if(string.IsNullOrWhiteSpace(name)) | |||||
{ | |||||
throw new ArgumentException($"'{nameof(name)}' is null or whitespace"); | |||||
} | |||||
// Fetch a node from existing children and return it if any is found | |||||
CSharpSyntaxMatchingNode firstLevelNode; | |||||
if (null != this.children && this.children.TryGetValue(name, out firstLevelNode)) | |||||
{ | |||||
return firstLevelNode; | |||||
} | |||||
// Otherwise create a new node and return it | |||||
// Lazu create the dictionary for storing children | |||||
if (null == this.children) | |||||
{ | |||||
this.children = new Dictionary<string, CSharpSyntaxMatchingNode>(); | |||||
} | |||||
// Assign and return the new node | |||||
return this.children[name] = new CSharpSyntaxMatchingNode(); | |||||
} | |||||
/// <summary> | |||||
/// Adds a syntax node as matching node. | |||||
/// </summary> | |||||
/// <param name="node"></param> | |||||
public void AddSyntaxNode(SyntaxNode node) | |||||
{ | |||||
if(node == null) | |||||
{ | |||||
throw new ArgumentNullException(nameof(node)); | |||||
} | |||||
// Lazy create the syntax node list | |||||
if (null == this.matchingSyntaxNodes) | |||||
{ | |||||
this.matchingSyntaxNodes = new List<SyntaxNode>(); | |||||
} | |||||
// Add the node to the known matching node | |||||
this.matchingSyntaxNodes.Add(node); | |||||
} | |||||
/// <summary> | |||||
/// Copies to a given node. | |||||
/// </summary> | |||||
/// <param name="targetNode">The node wherer to copy.</param> | |||||
/// <param name="name">The node name.</param> | |||||
public void CopyTo(CSharpSyntaxMatchingNode targetNode, string name) | |||||
{ | |||||
if(string.IsNullOrWhiteSpace(name)) | |||||
{ | |||||
throw new ArgumentException($"'{nameof(name)}' is null or whitespace"); | |||||
} | |||||
if(targetNode == null) | |||||
{ | |||||
throw new ArgumentNullException(nameof(targetNode)); | |||||
} | |||||
// Ensure and retrieve a node the the copy | |||||
CSharpSyntaxMatchingNode newNode = targetNode.EnsureNode(name); | |||||
// Add syntax node to the created node | |||||
if (null != this.matchingSyntaxNodes) | |||||
{ | |||||
// Lazy create the syntax nodes | |||||
if (null == newNode.matchingSyntaxNodes) | |||||
{ | |||||
newNode.matchingSyntaxNodes = new List<SyntaxNode>(); | |||||
} | |||||
// Merge syntax nodes | |||||
int[] indexes = newNode.matchingSyntaxNodes.Select(x => x.Span.Start).ToArray(); | |||||
newNode.matchingSyntaxNodes.AddRange(this.matchingSyntaxNodes.Where(x => !indexes.Contains(x.Span.Start))); | |||||
} | |||||
// Recurse for applying copy to the children | |||||
if (null != this.children && this.children.Count > 0) | |||||
{ | |||||
string[] childrenName = this.children.Keys.ToArray(); | |||||
foreach (string childName in childrenName) | |||||
{ | |||||
this.children[childName].CopyTo(newNode, childName); | |||||
} | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Overrides ToString to renger the internal Trie to a string. | |||||
/// </summary> | |||||
/// <returns>The rendered Trie as string.</returns> | |||||
public override string ToString() | |||||
{ | |||||
StringBuilder strinbBuilder = new StringBuilder(); | |||||
this.Write(strinbBuilder, null, 0); | |||||
return strinbBuilder.ToString(); | |||||
} | |||||
/// <summary> | |||||
/// Writes a node to a string builder and recurse to the children. | |||||
/// </summary> | |||||
/// <param name="stringBuilder">The string builder used as output.</param> | |||||
/// <param name="name">The node name.</param> | |||||
/// <param name="level">The node level.</param> | |||||
private void Write(StringBuilder stringBuilder, string name, int level) | |||||
{ | |||||
// Print the node only if the name is not null in order to ignore the root node for more clarity | |||||
int nextLevel = level; | |||||
if (null != name) | |||||
{ | |||||
++nextLevel; | |||||
stringBuilder.AppendLine(string.Format("{0}{1}", new string('-', level), name)); | |||||
} | |||||
// Print each matching syntax node | |||||
foreach (var matchingSyntaxNode in this.MatchingSyntaxNodes) | |||||
{ | |||||
stringBuilder.AppendLine(string.Format("{0}[{1}]", new string('-', level), matchingSyntaxNode.GetType().Name)); | |||||
} | |||||
// Recurse to children | |||||
if (null != this.children) | |||||
{ | |||||
foreach (string childName in this.children.Keys) | |||||
{ | |||||
this.children[childName].Write(stringBuilder, childName, nextLevel); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,329 @@ | |||||
using Microsoft.CodeAnalysis; | |||||
using Microsoft.CodeAnalysis.CSharp; | |||||
using Microsoft.CodeAnalysis.CSharp.Syntax; | |||||
using System; | |||||
using System.Linq; | |||||
namespace Projbook.Extension.CSharpExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Implements a syntax walker generating a Trie for pattern matching. | |||||
/// </summary> | |||||
public class CSharpSyntaxWalkerMatchingBuilder : CSharpSyntaxWalker | |||||
{ | |||||
/// <summary> | |||||
/// The current Trie root available from the outside. | |||||
/// </summary> | |||||
public CSharpSyntaxMatchingNode Root { get; private set; } | |||||
/// <summary> | |||||
/// The Trie root referencing the root without any reference change. | |||||
/// </summary> | |||||
private CSharpSyntaxMatchingNode internalInvariantRoot; | |||||
/// <summary> | |||||
/// Initializes a new instance of <see cref="CSharpSyntaxWalkerMatchingBuilder"/>. | |||||
/// </summary> | |||||
public CSharpSyntaxWalkerMatchingBuilder() | |||||
{ | |||||
this.internalInvariantRoot = new CSharpSyntaxMatchingNode(); | |||||
this.Root = this.internalInvariantRoot; | |||||
} | |||||
/// <summary> | |||||
/// Visits a namespace declaration. | |||||
/// A namespace may be composed with different segment dot separated, each segment has to be represented by a different node. | |||||
/// However the syntax node is attached to the last node only. | |||||
/// </summary> | |||||
/// <param name="node">The namespace declaration node to visit.</param> | |||||
public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) | |||||
{ | |||||
// Retrieve the namespace name and split segments | |||||
string name = node.Name.ToString(); | |||||
string[] namespaces = name.Split('.'); | |||||
// Keep track of the initial node the restore the root after the visit | |||||
CSharpSyntaxMatchingNode initialNode = this.Root; | |||||
// Browse all namespaces and generate intermediate node for each segment for the copy to the root | |||||
CSharpSyntaxMatchingNode firstNamespaceNode = null; | |||||
foreach (string currentNamespace in namespaces) | |||||
{ | |||||
// Create the node and keep track of the first one | |||||
this.Root = this.Root.EnsureNode(currentNamespace); | |||||
if (null == firstNamespaceNode) | |||||
{ | |||||
firstNamespaceNode = this.Root; | |||||
} | |||||
} | |||||
// Add the syntax node the last segment | |||||
this.Root.AddSyntaxNode(node); | |||||
// Triger member visiting | |||||
base.VisitNamespaceDeclaration(node); | |||||
// Copy the generated sub tree to the Trie root | |||||
firstNamespaceNode.CopyTo(this.internalInvariantRoot, namespaces[0]); | |||||
// Restore the initial root | |||||
this.Root = initialNode; | |||||
} | |||||
/// <summary> | |||||
/// Visits a class declaration. | |||||
/// </summary> | |||||
/// <param name="node">The class declaration to visit.</param> | |||||
public override void VisitClassDeclaration(ClassDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<ClassDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: node.TypeParameterList, | |||||
exctractName: n => node.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitClassDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits an interface declaration. | |||||
/// </summary> | |||||
/// <param name="node">The class declaration to visit.</param> | |||||
public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<InterfaceDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: node.TypeParameterList, | |||||
exctractName: n => node.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitInterfaceDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits an enum declaration. | |||||
/// </summary> | |||||
/// <param name="node">The enum declaration to visit.</param> | |||||
public override void VisitEnumDeclaration(EnumDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<EnumDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => node.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitEnumDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits an enum member declaration. | |||||
/// </summary> | |||||
/// <param name="node">The enum member declaration to visit.</param> | |||||
public override void VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<EnumMemberDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => node.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitEnumMemberDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits a property declaration. | |||||
/// </summary> | |||||
/// <param name="node">The property declaration to visit.</param> | |||||
public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<PropertyDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => n.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitPropertyDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits a field declaration. | |||||
/// </summary> | |||||
/// <param name="node">The field declaration to visit.</param> | |||||
public override void VisitFieldDeclaration(FieldDeclarationSyntax node) | |||||
{ | |||||
// Visit each variable declaration | |||||
foreach(VariableDeclaratorSyntax variableDeclarationSyntax in node.Declaration.Variables) | |||||
{ | |||||
this.Visit<FieldDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => variableDeclarationSyntax.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitFieldDeclaration); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Visits an indexter declaration. | |||||
/// </summary> | |||||
/// <param name="node">The indexter declaration to visit.</param> | |||||
public override void VisitIndexerDeclaration(IndexerDeclarationSyntax node) | |||||
{ | |||||
// Compute suffix for representing generics | |||||
string memberName = string.Empty; | |||||
if (null != node.ParameterList) | |||||
{ | |||||
memberName = string.Format( | |||||
"[{0}]", | |||||
string.Join(",", node.ParameterList.Parameters.Select(x => x.Type.ToString()))); | |||||
} | |||||
// Visit | |||||
this.Visit<IndexerDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => memberName, | |||||
targetNode: n => n, | |||||
visit: base.VisitIndexerDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits an event declaration. | |||||
/// </summary> | |||||
/// <param name="node">The event declaration to visit.</param> | |||||
public override void VisitEventDeclaration(EventDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<EventDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => n.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitEventDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits an accessor declaration. | |||||
/// </summary> | |||||
/// <param name="node">The accessor declaration to visit.</param> | |||||
public override void VisitAccessorDeclaration(AccessorDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<AccessorDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => n.Keyword.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitAccessorDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits a method declaration. | |||||
/// </summary> | |||||
/// <param name="node">The method declaration to visit.</param> | |||||
public override void VisitMethodDeclaration(MethodDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<MethodDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: node.TypeParameterList, | |||||
exctractName: n => n.Identifier.ValueText, | |||||
targetNode: n => n, | |||||
visit: base.VisitMethodDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits a constructor declaration. | |||||
/// </summary> | |||||
/// <param name="node">The constructor declaration to visit.</param> | |||||
public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<ConstructorDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => "<Constructor>", | |||||
targetNode: n => n, | |||||
visit: base.VisitConstructorDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits a destructor declaration. | |||||
/// </summary> | |||||
/// <param name="node">The destructor declaration to visit.</param> | |||||
public override void VisitDestructorDeclaration(DestructorDeclarationSyntax node) | |||||
{ | |||||
// Visit | |||||
this.Visit<DestructorDeclarationSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => "<Destructor>", | |||||
targetNode: n => n, | |||||
visit: base.VisitDestructorDeclaration); | |||||
} | |||||
/// <summary> | |||||
/// Visits parameter list. | |||||
/// </summary> | |||||
/// <param name="node">The parameter list to visit.</param> | |||||
public override void VisitParameterList(ParameterListSyntax node) | |||||
{ | |||||
// Skip parameter list when the parent is a lambda | |||||
if ( | |||||
SyntaxKind.SimpleLambdaExpression == node.Parent.Kind() || | |||||
SyntaxKind.ParenthesizedLambdaExpression == node.Parent.Kind()) | |||||
{ | |||||
return; | |||||
} | |||||
// Visit | |||||
this.Visit<ParameterListSyntax>( | |||||
node: node, | |||||
typeParameterList: null, | |||||
exctractName: n => string.Format("({0})", string.Join(",", node.Parameters.Select(x => x.Type.ToString()))), | |||||
targetNode: n => n.Parent, | |||||
visit: base.VisitParameterList); | |||||
} | |||||
/// <summary> | |||||
/// Visits a member. | |||||
/// </summary> | |||||
/// <typeparam name="T">The syntax node type to visit.</typeparam> | |||||
/// <param name="node">The node to visit.</param> | |||||
/// <param name="exctractName">Extract the node name.</param> | |||||
/// <param name="typeParameterList">The type parameter list.</param> | |||||
/// <param name="targetNode">Resolved the target node.</param> | |||||
/// <param name="visit">Visit sub nodes.</param> | |||||
private void Visit<T>(T node, Func<T, string> exctractName, TypeParameterListSyntax typeParameterList , Func<T, SyntaxNode> targetNode, Action<T> visit) where T : CSharpSyntaxNode | |||||
{ | |||||
// Retrieve the accessor name | |||||
string name = exctractName(node); | |||||
// Compute suffix for representing generics | |||||
if (null != typeParameterList) | |||||
{ | |||||
name = string.Format( | |||||
"{0}{{{1}}}", | |||||
name, | |||||
string.Join(",", typeParameterList.Parameters.Select(x => x.ToString()))); | |||||
} | |||||
// Keep track of the initial node the restore the root after the visit | |||||
CSharpSyntaxMatchingNode initialNode = this.Root; | |||||
// Create and add the node | |||||
this.Root = this.Root.EnsureNode(name); | |||||
this.Root.AddSyntaxNode(targetNode(node)); | |||||
// Trigger member visiting | |||||
visit(node); | |||||
// Copy the class sub tree to the Trie root | |||||
this.Root.CopyTo(this.internalInvariantRoot, name); | |||||
// Restore the initial root | |||||
this.Root = initialNode; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,162 @@ | |||||
using System; | |||||
using Projbook.Extension.Exception; | |||||
using System.Text; | |||||
using System.Text.RegularExpressions; | |||||
using System.Xml; | |||||
namespace Projbook.Extension.XmlExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Extractor in charge of browsing source directories. load file content and extract requested member. | |||||
/// </summary> | |||||
public class XmlSnippetExtractor : DefaultSnippetExtractor | |||||
{ | |||||
/// <summary> | |||||
/// The regex extracting the document namespaces | |||||
/// </summary> | |||||
private Regex regex = new Regex(@"xmlns:([^=]+)=""([^""]*)""", RegexOptions.Compiled); | |||||
/// <summary> | |||||
/// The lazy loaded xml document. | |||||
/// </summary> | |||||
private XmlDocument xmlDocument; | |||||
/// <summary> | |||||
/// The lazy loaded namespace manager. | |||||
/// </summary> | |||||
private XmlNamespaceManager xmlNamespaceManager; | |||||
/// <summary> | |||||
/// Extracts a snippet from a given rule pattern. | |||||
/// </summary> | |||||
/// <param name="fullFilename">The full filename (with path) to load and to extract the snippet from.</param> | |||||
/// <param name="memberPattern">The member pattern to extract.</param> | |||||
/// <returns> | |||||
/// The extracted snippet. | |||||
/// </returns> | |||||
/// <exception cref="SnippetExtractionException"> | |||||
/// Cannot parse xml file | |||||
/// or | |||||
/// Invalid extraction rule | |||||
/// or | |||||
/// Cannot find member | |||||
/// </exception> | |||||
public override string Extract(string fullFilename, string memberPattern) | |||||
{ | |||||
// Return the entire code if no member is specified | |||||
if (string.IsNullOrWhiteSpace(memberPattern)) | |||||
{ | |||||
return base.Extract(fullFilename, memberPattern); | |||||
} | |||||
// Load the xml document for xpath execution | |||||
if (null == this.xmlDocument) | |||||
{ | |||||
// Load file content | |||||
string sourceCode = this.LoadFile(fullFilename); | |||||
// Remove default avoiding to define and use a prefix for the default namespace | |||||
// This is not strictly correct in a xml point of view but it's closest to most needs | |||||
sourceCode = Regex.Replace(sourceCode, @"xmlns\s*=\s*""[^""]*""", string.Empty); | |||||
// Parse the file as xml | |||||
this.xmlDocument = new XmlDocument(); | |||||
try | |||||
{ | |||||
// Initialize the document and the namespace manager | |||||
this.xmlDocument.LoadXml(sourceCode); | |||||
this.xmlNamespaceManager = new XmlNamespaceManager(this.xmlDocument.NameTable); | |||||
// Match namespace declaration for filling the namespace manager | |||||
Match match = this.regex.Match(sourceCode); | |||||
while (match.Success) | |||||
{ | |||||
// Collect prefix and namespace value | |||||
string prefix = match.Groups[1].Value.Trim(); | |||||
string ns = match.Groups[2].Value.Trim(); | |||||
// Add namespace declaration to the namespace manager | |||||
xmlNamespaceManager.AddNamespace(prefix, ns); | |||||
// Mode to the next matching | |||||
match = match.NextMatch(); | |||||
} | |||||
} | |||||
// Throw an exception is the file is not loadable as xml document | |||||
catch (System.Exception exception) | |||||
{ | |||||
throw new SnippetExtractionException("Cannot parse xml file", exception.Message); | |||||
} | |||||
} | |||||
// Execute Xpath query | |||||
XmlNodeList xmlNodeList = null; | |||||
try | |||||
{ | |||||
xmlNodeList = this.xmlDocument.SelectNodes(memberPattern, this.xmlNamespaceManager); | |||||
} | |||||
catch | |||||
{ | |||||
throw new SnippetExtractionException("Invalid extraction rule", memberPattern); | |||||
} | |||||
// Ensure we found a result | |||||
if (xmlNodeList.Count <= 0) | |||||
{ | |||||
throw new SnippetExtractionException("Cannot find member", memberPattern); | |||||
} | |||||
// Build a snippet for extracted nodes | |||||
return this.BuildSnippet(xmlNodeList); | |||||
} | |||||
/// <summary> | |||||
/// Builds a snippet from xml node. | |||||
/// </summary> | |||||
/// <param name="xmlNodeList">The xml node list.</param> | |||||
/// <returns>The built snippet.</returns> | |||||
private string BuildSnippet(XmlNodeList xmlNodeList) | |||||
{ | |||||
// Data validation | |||||
if(xmlNodeList == null) | |||||
{ | |||||
throw new ArgumentNullException(nameof(xmlNodeList)); | |||||
} | |||||
// Extract code from each snippets | |||||
StringBuilder stringBuilder = new StringBuilder(); | |||||
bool firstSnippet = true; | |||||
for (int i = 0; i < xmlNodeList.Count; ++i) | |||||
{ | |||||
// Get the current node | |||||
XmlNode node = xmlNodeList.Item(i); | |||||
// Write line return between each snippet | |||||
if (!firstSnippet) | |||||
{ | |||||
stringBuilder.AppendLine(); | |||||
stringBuilder.AppendLine(); | |||||
} | |||||
// Write each snippet | |||||
XmlWriterSettings settings = new XmlWriterSettings(); | |||||
settings.Indent = true; | |||||
settings.OmitXmlDeclaration = true; | |||||
settings.NewLineOnAttributes = true; | |||||
using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder, settings)) | |||||
{ | |||||
node.WriteTo(xmlWriter); | |||||
} | |||||
// Flag the first snippet as false | |||||
firstSnippet = false; | |||||
} | |||||
// Remove all generate namespace declaration | |||||
// This is produce some output lacking of namespace declaration but it's what is relevant for a xml document extraction | |||||
string output = stringBuilder.ToString(); | |||||
return Regex.Replace(output, @" ?xmlns\s*(:[^=]+)?\s*=\s*""[^""]*""", string.Empty) ?? string.Empty; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,90 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> | |||||
<PropertyGroup> | |||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | |||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | |||||
<ProjectGuid>{8338B756-0519-4D20-BA04-3A8F4839237A}</ProjectGuid> | |||||
<OutputType>Library</OutputType> | |||||
<AppDesignerFolder>Properties</AppDesignerFolder> | |||||
<RootNamespace>Projbook.Extension</RootNamespace> | |||||
<AssemblyName>Projbook.Extension</AssemblyName> | |||||
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion> | |||||
<FileAlignment>512</FileAlignment> | |||||
<TargetFrameworkProfile /> | |||||
</PropertyGroup> | |||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | |||||
<DebugSymbols>true</DebugSymbols> | |||||
<DebugType>full</DebugType> | |||||
<Optimize>false</Optimize> | |||||
<OutputPath>bin\Debug\</OutputPath> | |||||
<DefineConstants>DEBUG;TRACE</DefineConstants> | |||||
<ErrorReport>prompt</ErrorReport> | |||||
<WarningLevel>4</WarningLevel> | |||||
</PropertyGroup> | |||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | |||||
<DebugType>pdbonly</DebugType> | |||||
<Optimize>true</Optimize> | |||||
<OutputPath>bin\Release\</OutputPath> | |||||
<DefineConstants>TRACE</DefineConstants> | |||||
<ErrorReport>prompt</ErrorReport> | |||||
<WarningLevel>4</WarningLevel> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<Reference Include="Microsoft.CodeAnalysis, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="Microsoft.CodeAnalysis.CSharp, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\packages\Microsoft.CodeAnalysis.CSharp.1.3.2\lib\net45\Microsoft.CodeAnalysis.CSharp.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="System" /> | |||||
<Reference Include="System.Collections.Immutable, Version=1.1.37.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\packages\System.Collections.Immutable.1.1.37\lib\dotnet\System.Collections.Immutable.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="System.Core" /> | |||||
<Reference Include="Microsoft.CSharp" /> | |||||
<Reference Include="System.Reflection.Metadata, Version=1.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\packages\System.Reflection.Metadata.1.2.0\lib\portable-net45+win8\System.Reflection.Metadata.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="System.Threading.Thread, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||||
<HintPath>..\..\packages\System.Threading.Thread.4.0.0\lib\net46\System.Threading.Thread.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="System.Xml" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<Compile Include="Extractors\CSharp\CSharpExtractionMode.cs" /> | |||||
<Compile Include="Extractors\CSharp\CSharpMatchingRule.cs" /> | |||||
<Compile Include="Extractors\CSharp\CSharpSnippetExtractor.cs" /> | |||||
<Compile Include="Extractors\CSharp\CSharpSyntaxMatchingNode.cs" /> | |||||
<Compile Include="Extractors\CSharp\CSharpSyntaxWalkerMatchingBuilder.cs" /> | |||||
<Compile Include="DefaultSnippetExtractor.cs" /> | |||||
<Compile Include="Exception\SnippetExtractionException.cs" /> | |||||
<Compile Include="Spi\ISnippetExtractor.cs" /> | |||||
<Compile Include="Spi\TargetType.cs" /> | |||||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||||
<Compile Include="Extractors\Xml\XmlSnippetExtractor.cs" /> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<None Include="app.config" /> | |||||
<None Include="packages.config"> | |||||
<SubType>Designer</SubType> | |||||
</None> | |||||
</ItemGroup> | |||||
<ItemGroup> | |||||
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" /> | |||||
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" /> | |||||
</ItemGroup> | |||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | |||||
Other similar extension points exist, see Microsoft.Common.targets. | |||||
<Target Name="BeforeBuild"> | |||||
</Target> | |||||
<Target Name="AfterBuild"> | |||||
</Target> | |||||
--> | |||||
</Project> |
@@ -0,0 +1,36 @@ | |||||
using System.Reflection; | |||||
using System.Runtime.CompilerServices; | |||||
using System.Runtime.InteropServices; | |||||
// General Information about an assembly is controlled through the following | |||||
// set of attributes. Change these attribute values to modify the information | |||||
// associated with an assembly. | |||||
[assembly: AssemblyTitle("Projbook.Extension")] | |||||
[assembly: AssemblyDescription("")] | |||||
[assembly: AssemblyConfiguration("")] | |||||
[assembly: AssemblyCompany("")] | |||||
[assembly: AssemblyProduct("Projbook.Extension")] | |||||
[assembly: AssemblyCopyright("Copyright © 2016")] | |||||
[assembly: AssemblyTrademark("")] | |||||
[assembly: AssemblyCulture("")] | |||||
// Setting ComVisible to false makes the types in this assembly not visible | |||||
// to COM components. If you need to access a type in this assembly from | |||||
// COM, set the ComVisible attribute to true on that type. | |||||
[assembly: ComVisible(false)] | |||||
// The following GUID is for the ID of the typelib if this project is exposed to COM | |||||
[assembly: Guid("8338b756-0519-4d20-ba04-3a8f4839237a")] | |||||
// Version information for an assembly consists of the following four values: | |||||
// | |||||
// Major Version | |||||
// Minor Version | |||||
// Build Number | |||||
// Revision | |||||
// | |||||
// You can specify all the values or you can default the Build and Revision Numbers | |||||
// by using the '*' as shown below: | |||||
// [assembly: AssemblyVersion("1.0.*")] | |||||
[assembly: AssemblyVersion("1.0.0.0")] | |||||
[assembly: AssemblyFileVersion("1.0.0.0")] |
@@ -0,0 +1,24 @@ | |||||
namespace Projbook.Extension.Spi | |||||
{ | |||||
/// <summary> | |||||
/// Defines interface for snippet extractor. | |||||
/// </summary> | |||||
public interface ISnippetExtractor | |||||
{ | |||||
/// <summary> | |||||
/// Defines the target type. | |||||
/// </summary> | |||||
TargetType TargetType { get; } | |||||
/// <summary> | |||||
/// Extracts a snippet. | |||||
/// </summary> | |||||
/// <param name="fullFilename">The full filename (with path) to load and to extract the snippet from.</param> | |||||
/// <param name="pattern">The extraction pattern.</param> | |||||
/// <returns> | |||||
/// The extracted snippet as string. | |||||
/// </returns> | |||||
string Extract(string fullFilename, string pattern); | |||||
} | |||||
} |
@@ -0,0 +1,23 @@ | |||||
namespace Projbook.Extension.Spi | |||||
{ | |||||
/// <summary> | |||||
/// Represents an extraction target. | |||||
/// </summary> | |||||
public enum TargetType | |||||
{ | |||||
/// <summary> | |||||
/// Free text target, used by plugins extracting from free value. | |||||
/// </summary> | |||||
FreeText, | |||||
/// <summary> | |||||
/// File target, used by plugins extracting from a file. | |||||
/// </summary> | |||||
File, | |||||
/// <summary> | |||||
/// Folder target, ised bu plugins extracting from a folder. | |||||
/// </summary> | |||||
Folder | |||||
} | |||||
} |
@@ -0,0 +1,15 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<configuration> | |||||
<runtime> | |||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> | |||||
<dependentAssembly> | |||||
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/> | |||||
<bindingRedirect oldVersion="0.0.0.0-1.3.0.0" newVersion="1.3.0.0"/> | |||||
</dependentAssembly> | |||||
<dependentAssembly> | |||||
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/> | |||||
<bindingRedirect oldVersion="0.0.0.0-1.2.0.0" newVersion="1.2.0.0"/> | |||||
</dependentAssembly> | |||||
</assemblyBinding> | |||||
</runtime> | |||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration> |
@@ -0,0 +1,16 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<packages> | |||||
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net451" /> | |||||
<package id="Microsoft.CodeAnalysis.Common" version="1.3.2" targetFramework="net451" /> | |||||
<package id="Microsoft.CodeAnalysis.CSharp" version="1.3.2" targetFramework="net451" /> | |||||
<package id="System.Collections" version="4.0.0" targetFramework="net451" /> | |||||
<package id="System.Collections.Immutable" version="1.1.37" targetFramework="net451" /> | |||||
<package id="System.Diagnostics.Debug" version="4.0.0" targetFramework="net451" /> | |||||
<package id="System.Globalization" version="4.0.0" targetFramework="net451" /> | |||||
<package id="System.Linq" version="4.0.0" targetFramework="net451" /> | |||||
<package id="System.Reflection.Metadata" version="1.2.0" targetFramework="net451" /> | |||||
<package id="System.Resources.ResourceManager" version="4.0.0" targetFramework="net451" /> | |||||
<package id="System.Runtime" version="4.0.0" targetFramework="net451" /> | |||||
<package id="System.Runtime.Extensions" version="4.0.0" targetFramework="net451" /> | |||||
<package id="System.Threading" version="4.0.0" targetFramework="net451" /> | |||||
</packages> |