Skip to content

Commit

Permalink
Doc Generation Changes - Fixes #41 (#50)
Browse files Browse the repository at this point in the history
* added docs folder to solution
* reworked doc generation to generate tables
* altered doc generation to be dynamic based upon dll passed in
* added link to toc from main readme
* Updating the repository docs folder
* modified push doc changes to only occur on master branch
* fixed issue in doc generation that was adding pipes into every char in the description
* removed old table of contents file

Co-authored-by: GitHub Action <[email protected]>
  • Loading branch information
tcartwright and actions-user authored Sep 6, 2021
1 parent a33837f commit eca61d8
Show file tree
Hide file tree
Showing 88 changed files with 229 additions and 81 deletions.
195 changes: 122 additions & 73 deletions .github/workflows/generate_docs.ps1
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
Param(
[string] $BuildDirectory,
[string] $ParentDirectory
[Parameter(Mandatory=$true)]
[string] $ParentDirectory,

[Parameter(Mandatory=$true)]
[string] $RulesDllPath
)
Clear-Host
Set-StrictMode -Version 3.0

function Remove-LeadingWhitespace {
Param(
[string] $Text
)
# speed this up. no need to split if essentially completely empty, just return empty
if ([string]::IsNullOrWhiteSpace($Text)) {
return [string]::Empty;
}

$Lines = $Text.Split([Environment]::NewLine)
$Result = ""
Expand Down Expand Up @@ -44,22 +53,23 @@ function Get-SplitPascalCase {

function Get-SqlRulesAssembly {
Param(
[string] $BuildDirectory
[System.IO.FileInfo] $AssemblyFileInfo
)
$Assembly = [System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.Modules.Name.Contains("SqlServer.Rules.dll") }

$Assembly = [System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.Modules.Name -imatch $AssemblyFileInfo.Name }

if ($null -eq $Assembly) {
Add-Type -Path "$BuildDirectory\SqlServer.Rules.dll"
Add-Type -Path $AssemblyFileInfo.FullName

$Assembly = [System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.Modules.Name.Contains("SqlServer.Rules.dll") }
$Assembly = [System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.Modules.Name -imatch $AssemblyFileInfo.Name }
}

return $Assembly
}

function Get-SqlRulesTypes {
Param(
$Assembly
[System.Reflection.Assembly]$Assembly
)

$Types = $Assembly.GetTypes() | Where-Object {
Expand All @@ -77,37 +87,36 @@ function Get-SqlRulesTypes {

function Get-XmlDocumentForRules {
Param(
$Assembly
[System.IO.FileInfo]$AssemblyFileInfo
)

$AssemblyPath = ([uri] $Assembly.CodeBase).AbsolutePath

$XmlPath = [System.IO.Path]::Combine(
[System.IO.Path]::GetDirectoryName($AssemblyPath),
"$([System.IO.Path]::GetFileNameWithoutExtension($AssemblyPath)).xml")
$XmlPath = [System.IO.Path]::Combine($AssemblyFileInfo.DirectoryName, "$($AssemblyFileInfo.BaseName).xml")

if (-not (Test-Path -LiteralPath $XmlPath)) {
throw "Unable to find XML file at '$XmlPath'"
exit 1
}

Write-Information "Loading rule xml: $XmlPath"
[xml]$Xml = Get-Content -Path $XmlPath -Raw

return $Xml
}

function Get-SqlRules {
Param(
$Xml,
$Types
[xml]$Xml,
[Type[]]$Types
)

$Rules = @()
$Rules = New-Object System.Collections.ArrayList

foreach ($Type in $Types) {
Write-Information "Getting rule: $($Type.Name)"
$RuleObject = [PSCustomObject] @{
Category = ""
ClassName = ""
NameSpace = ""
FriendlyName = ""
RuleId = ""
RuleScope = ""
Expand All @@ -123,9 +132,9 @@ function Get-SqlRules {
$TypeXml = ($Xml.doc.members.member | Where-Object { $_.name -ieq "T:$($Type.FullName)" })

if ($TypeXml) {
$RuleObject.FriendlyName = $TypeXml.FriendlyName
$RuleObject.Summary = Remove-LeadingWhitespace -Text $TypeXml.summary
$RuleObject.Example = Remove-LeadingWhitespace -Text $TypeXml.ExampleMd
$RuleObject.FriendlyName = ($TypeXml.FriendlyName).Trim()
$RuleObject.Summary = (Remove-LeadingWhitespace -Text $TypeXml.summary).Trim()
$RuleObject.Example = (Remove-LeadingWhitespace -Text $TypeXml.ExampleMd).Trim()
$RuleObject.IsIgnorable = $TypeXml.IsIgnorable
}
else {
Expand All @@ -135,7 +144,8 @@ function Get-SqlRules {
if ([String]::IsNullOrWhiteSpace($RuleObject.FriendlyName)) {
$RuleObject.FriendlyName = Get-SplitPascalCase -PascalText $Type.Name
}


$RuleObject.NameSpace = $Type.Namespace
$RuleObject.ClassName = $Type.Name
$RuleObject.RuleId = ($RuleAttr.Id).Split('.')[1]
$RuleObject.Description = $RuleAttr.Description
Expand All @@ -149,85 +159,117 @@ function Get-SqlRules {
$null)
$RuleInstance = $Constructor.Invoke($null)

$elementTypes = New-Object System.Collections.ArrayList
foreach ($ApplicableType in $RuleInstance.SupportedElementTypes) {
$RuleObject.SupportedElementTypes += Get-SplitPascalCase -PascalText $ApplicableType.Name
$elementTypes.Add((Get-SplitPascalCase -PascalText $ApplicableType.Name)) | Out-Null
}
$RuleObject.SupportedElementTypes = ($elementTypes.ToArray() | Sort-Object)
}
else {
$RuleObject.SupportedElementTypes += "Model"
}

$Rules += $RuleObject
$Rules.Add($RuleObject) | Out-Null
}

return $Rules
return $Rules.ToArray()
}

function New-TableOfContents {
Param(
$ParentDirectory,
$RuleObjects
[string]$ParentDirectory,
[PSCustomObject[]]$RuleObjects
)

$TableOfContents = @{}
$spaces = " " * 2

foreach ($Rule in $RuleObjects) {
if ($TableOfContents.ContainsKey($Rule.Category)) {
$TableOfContents[$Rule.Category] += $Rule
}
else {
$TableOfContents.Add($Rule.Category, @($Rule))
}
}

$OrderedCategories = $TableOfContents.Keys | Sort-Object
$categories = $RuleObjects | Group-Object { $_.Category } | Sort-Object -Property Key

$StringBuilder = [System.Text.StringBuilder]::new()
[void]$StringBuilder.AppendLine("# Table of Contents")

foreach ($Category in $OrderedCategories) {
[void]$StringBuilder.AppendLine("## $Category")

for ($i = 0; $i -lt $TableOfContents[$Category].Count; $i += 1) {
$Rule = $TableOfContents[$Category][$i]

[void]$StringBuilder.AppendLine("$($i + 1). [$($Rule.FriendlyName)]($Category/$($Rule.RuleId).md)")
[void]$StringBuilder.AppendLine("$spaces")
[void]$StringBuilder.AppendLine("[This document is automatically generated. All changed made to it WILL be lost]: <>$spaces")

[void]$StringBuilder.AppendLine("$spaces")
[void]$StringBuilder.AppendLine("# Table of Contents$spaces")
[void]$StringBuilder.AppendLine("$spaces")

foreach ($category in $categories) {
[void]$StringBuilder.AppendLine("$spaces")
[void]$StringBuilder.AppendLine("## $($category.Name)$spaces")
[void]$StringBuilder.AppendLine("$spaces")

[void]$StringBuilder.AppendLine("| Rule Id | Friendly Name | Ignorable | Description |")
[void]$StringBuilder.AppendLine("|----|----|----|----|")
foreach ($rule in ($category.Group | Sort-Object -Property RuleId )) {
$friendlyName = "[$($Rule.FriendlyName)]($($category.Name)/$($Rule.RuleId).md)"
[void]$StringBuilder.AppendLine("| $($Rule.RuleId) | $friendlyName | $($Rule.IsIgnorable) | $($Rule.Description -replace "\|", "&#124;") |")
}
}

$FilePath = "$ParentDirectory\docs\"
New-Item -Path $FilePath -Force -ItemType Directory | Out-Null

$StringBuilder.ToString() | Out-File "$FilePath\TableOfContents.md" -Force
$StringBuilder.ToString() | Out-File "$FilePath\table_of_contents.md" -Force
}

function New-MdFromRuleObject {
Param(
[string] $ParentDirectory,
$RuleObject
[object] $RuleObject,
[string] $AssemblyName
)

$StringBuilder = [System.Text.StringBuilder]::new()

[void]$StringBuilder.AppendLine("[This document is automatically generated. All changed made to it WILL be lost]: <>")
[void]$StringBuilder.AppendLine("# $($RuleObject.FriendlyName)")
[void]$StringBuilder.AppendLine("Namespace: SqlServer.Rules.$($RuleObject.Category) ")
[void]$StringBuilder.AppendLine("Assembly: SqlServer.Rules.dll ")
[void]$StringBuilder.AppendLine("Ignorable: $($RuleObject.IsIgnorable) ")

[void]$StringBuilder.AppendLine("Applicable Types: $($RuleObject.SupportedElementTypes -join ', ') ")
[void]$StringBuilder.AppendLine("")

[void]$StringBuilder.AppendLine($RuleObject.Description)

[void]$StringBuilder.AppendLine("## Summary")
[void]$StringBuilder.AppendLine($RuleObject.Summary)
# MD EOL - When you do want to insert a <br /> break tag using Markdown, you end a line with two or more spaces, then type return.
$spaces = " " * 2
[void]$StringBuilder.AppendLine("$spaces")
[void]$StringBuilder.AppendLine("[This document is automatically generated. All changed made to it WILL be lost]: <>$spaces")

[void]$StringBuilder.AppendLine("$spaces")
[void]$StringBuilder.AppendLine("# SQL Server Rule: $($RuleObject.RuleId)$spaces")
[void]$StringBuilder.AppendLine("$spaces")
[void]$StringBuilder.AppendLine("| | |")
[void]$StringBuilder.AppendLine("|----|----|")
[void]$StringBuilder.AppendLine("| Assembly | $AssemblyName$spaces |")
[void]$StringBuilder.AppendLine("| Namespace | $($RuleObject.Namespace) |")
[void]$StringBuilder.AppendLine("| Class | $($RuleObject.ClassName) |")

[void]$StringBuilder.AppendLine("$spaces")
[void]$StringBuilder.AppendLine("## Rule Information$spaces")
[void]$StringBuilder.AppendLine("$spaces")

[void]$StringBuilder.AppendLine("| | |")
[void]$StringBuilder.AppendLine("|----|----|")
[void]$StringBuilder.AppendLine("| Id | $($RuleObject.RuleId) |")
[void]$StringBuilder.AppendLine("| Friendly Name | $($RuleObject.FriendlyName) |")
[void]$StringBuilder.AppendLine("| Category | $($RuleObject.Category) |")
[void]$StringBuilder.AppendLine("| Ignorable | $($RuleObject.IsIgnorable) |")
[void]$StringBuilder.AppendLine("| Applicable Types | $($RuleObject.SupportedElementTypes | Select-Object -First 1) |")
$ruleElementTypes = $RuleObject.SupportedElementTypes | Select-Object -Skip 1
if ($ruleElementTypes) {
[void]$StringBuilder.AppendLine("| | $($ruleElementTypes -join " |`r`n| | ") |")
}

if (-not [String]::IsNullOrWhiteSpace($RuleObject.Example)) {
[void]$StringBuilder.AppendLine("## Examples")
[void]$StringBuilder.Append($RuleObject.Example)
[void]$StringBuilder.AppendLine("$spaces")
[void]$StringBuilder.AppendLine("## Description$spaces")
[void]$StringBuilder.AppendLine("$spaces")
[void]$StringBuilder.AppendLine("$($RuleObject.Description)$spaces")

[void]$StringBuilder.AppendLine("$spaces")
[void]$StringBuilder.AppendLine("## Summary$spaces")
[void]$StringBuilder.AppendLine("$spaces")
[void]$StringBuilder.AppendLine("$($RuleObject.Summary)$spaces")

if (-not [String]::IsNullOrWhiteSpace("$($RuleObject.Example)$spaces")) {
[void]$StringBuilder.AppendLine("$spaces")
[void]$StringBuilder.AppendLine("### Examples$spaces")
[void]$StringBuilder.AppendLine("$spaces")
[void]$StringBuilder.Append("$($RuleObject.Example)$spaces")
}

[void]$StringBuilder.AppendLine("")

$FilePath = "$ParentDirectory\docs\$($RuleObject.Category)\"
New-Item -Path $FilePath -Force -ItemType Directory | Out-Null

Expand All @@ -236,20 +278,27 @@ function New-MdFromRuleObject {

function Publish-Markdown {
Param(
[string] $BuildDirectory,
[string] $ParentDirectory
[string] $ParentDirectory,
[string] $AssemblyPath
)

$Assembly = Get-SqlRulesAssembly -BuildDirectory $BuildDirectory
$Types = Get-SqlRulesTypes -Assembly $Assembly
$Xml = Get-XmlDocumentForRules -Assembly $Assembly
$Rules = Get-SqlRules -Xml $Xml -Types $Types
$assemblyFI = New-Object System.IO.FileInfo ($AssemblyPath)

if (-not $assemblyFI.Exists) {
throw "The path to the assembly for the rules does not exist."
}

[System.Reflection.Assembly] $Assembly = Get-SqlRulesAssembly -AssemblyFileInfo $assemblyFI
[Type[]] $Types = Get-SqlRulesTypes -Assembly $Assembly
[xml] $Xml = Get-XmlDocumentForRules -AssemblyFileInfo $assemblyFI
$SqlRules = Get-SqlRules -Xml $Xml -Types $Types

foreach ($Rule in $Rules) {
New-MdFromRuleObject -ParentDirectory $ParentDirectory -RuleObject $Rule
foreach ($SqlRule in $SqlRules) {
New-MdFromRuleObject -ParentDirectory $ParentDirectory -RuleObject $SqlRule -AssemblyName $assemblyFI.Name
}

New-TableOfContents -ParentDirectory $ParentDirectory -RuleObjects $Rules
New-TableOfContents -ParentDirectory $ParentDirectory -RuleObjects $SqlRules
}

Publish-Markdown -BuildDirectory $BuildDirectory -ParentDirectory $ParentDirectory

Publish-Markdown -ParentDirectory $ParentDirectory -AssemblyPath $RulesDllPath
4 changes: 2 additions & 2 deletions .github/workflows/get_project_outputs.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ if ([string]::IsNullOrWhiteSpace($assemblyName)) {
$outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($ProjectPath), $outputPath, $framework)
$outputDll = [System.IO.Path]::Combine($outputPath, "$assemblyName.dll")

echo "GITHUB_BUILD_OUTPUT_PATH=$outputPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "GITHUB_BUILD_OUTPUT_DLL=$outputDll" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "BUILD_OUTPUT_PATH=$outputPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "BUILD_OUTPUT_DLL=$outputDll" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append

9 changes: 5 additions & 4 deletions .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ name: .NET Pipeline
# Run Unit Tests - Runs the unit tests defined in the project
# Remove Docs - Removes the docs folder in preparation for it to be regenerated. So any deleted rules have their markdown removed.
# Extract Build Info - Extracts the build location, and assembly info from the csproj file, Extracts:
# - ${{ env.GITHUB_BUILD_OUTPUT_PATH }} - The directory path where the build output is placed
# - ${{ env.GITHUB_BUILD_OUTPUT_DLL }} - The fully qualified path to the build dll
# - ${{ env.BUILD_OUTPUT_PATH }} - The directory path where the build output is placed
# - ${{ env.BUILD_OUTPUT_DLL }} - The fully qualified path to the build dll
# Generate Docs - generates the rules for the docs by extracting that information from the rule attribute, and the xml comments
# Write Docs Tree - purely a debugging step to see what doc files were written by the powershell
# Commit doc files - Commits any changes if there are any to the repo as the user Github Action
Expand Down Expand Up @@ -87,7 +87,7 @@ jobs:
run: |
.\.github\workflows\generate_docs.ps1 `
-ParentDirectory "${{ github.workspace }}" `
-BuildDirectory "${{ env.GITHUB_BUILD_OUTPUT_PATH }}\"
-RulesDllPath "${{ env.BUILD_OUTPUT_DLL }}"
# TREE DOCS - JUST TO GIVE A VISUAL OF THE FILES GENERATED FOR DEBUG PURPOSES
- name: Write Docs Tree
Expand All @@ -104,6 +104,7 @@ jobs:
# PUSH DOCS
- name: Push doc changes
uses: ad-m/github-push-action@master
if: ${{ github.ref == 'refs/heads/master' }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.HEAD_REF }}
Expand All @@ -123,7 +124,7 @@ jobs:
run: |
.\.github\workflows\create_release.ps1 `
-ParentDirectory "${{ github.workspace }}" `
-BuildDirectory "${{ env.GITHUB_BUILD_OUTPUT_PATH }}" `
-BuildDirectory "${{ env.BUILD_OUTPUT_PATH }}" `
-releasePath "${{ github.workspace }}\\$($env:REPO_NAME).${{ steps.tag_version.outputs.new_tag }}.zip"
- name: Create Release
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

Just what it says on the box: A library of SQL best practices as extended [database code analysis rules](https://docs.microsoft.com/en-us/sql/ssdt/overview-of-extensibility-for-database-code-analysis-rules?view=sql-server-ver15) checked at build.

For a complete list of the current rules we have implemented see [here](docs/table_of_contents.md)

For example code see [here](https://github.com/microsoft/DACExtensions/tree/master/RuleSamples)

## Organization
Expand Down
Loading

0 comments on commit eca61d8

Please sign in to comment.