| @@ -1,7 +1,7 @@ | |||
| | |||
| Microsoft Visual Studio Solution File, Format Version 12.00 | |||
| # Visual Studio 14 | |||
| VisualStudioVersion = 14.0.24720.0 | |||
| VisualStudioVersion = 14.0.25420.1 | |||
| MinimumVisualStudioVersion = 10.0.40219.1 | |||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Docnet", "src\DocNet\Docnet.csproj", "{48CA9947-AF13-459E-9D59-FC451B5C19D7}" | |||
| EndProject | |||
| @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownDeep", "src\Markdow | |||
| EndProject | |||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkdownDeepTests", "src\MarkdownDeepTests\MarkdownDeepTests.csproj", "{CD1F5BFF-0118-4994-86A2-92658A36CE1B}" | |||
| EndProject | |||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Projbook.Extension", "src\Projbook.Extension\Projbook.Extension.csproj", "{8338B756-0519-4D20-BA04-3A8F4839237A}" | |||
| EndProject | |||
| Global | |||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
| 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}.Release|Any CPU.ActiveCfg = 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 | |||
| GlobalSection(SolutionProperties) = preSolution | |||
| HideSolutionNode = FALSE | |||
| @@ -1,6 +1,18 @@ | |||
| <?xml version="1.0" encoding="utf-8" ?> | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <configuration> | |||
| <startup> | |||
| <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> | |||
| </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> | |||
| @@ -80,4 +80,4 @@ | |||
| <Target Name="AfterBuild"> | |||
| </Target> | |||
| --> | |||
| </Project> | |||
| </Project> | |||
| @@ -14,8 +14,13 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using Projbook.Extension; | |||
| using Projbook.Extension.CSharpExtractor; | |||
| using Projbook.Extension.Spi; | |||
| using Projbook.Extension.XmlExtractor; | |||
| namespace MarkdownDeep | |||
| { | |||
| @@ -1281,23 +1286,26 @@ namespace MarkdownDeep | |||
| { | |||
| return HandleAlertExtension(b); | |||
| } | |||
| if(DoesMatch("@snippet")) | |||
| { | |||
| return HandleSnippetExtension(b); | |||
| } | |||
| 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' | |||
| if(!SkipString("@alert")) | |||
| @@ -1438,15 +1446,114 @@ namespace MarkdownDeep | |||
| b.Children = contentProcessor.ScanLines(Input, startContent, endContent - startContent); | |||
| 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; | |||
| int newPosition = this.Position; | |||
| @@ -105,6 +105,12 @@ | |||
| <Install>true</Install> | |||
| </BootstrapperPackage> | |||
| </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" /> | |||
| <PropertyGroup> | |||
| <PostBuildEvent> | |||
| @@ -477,6 +477,12 @@ | |||
| <ItemGroup> | |||
| <EmbeddedResource Include="testfiles\mdtest11\Images%28Titled%29.html" /> | |||
| </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" /> | |||
| <!-- 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. | |||
| @@ -132,6 +132,10 @@ namespace MarkdownDeepTests | |||
| { | |||
| md.HtmlClassTitledImages = "figure"; | |||
| } | |||
| if(md.DocNetMode) | |||
| { | |||
| md.GitHubCodeBlocks = true; | |||
| } | |||
| string actual = md.Transform(input); | |||
| 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> | |||