Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Artees committed Aug 11, 2018
1 parent b65cea2 commit 22a6c96
Show file tree
Hide file tree
Showing 31 changed files with 1,303 additions and 1 deletion.
35 changes: 35 additions & 0 deletions DocCover.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocCover", "DocCover\DocCover.csproj", "{DC97B8FE-D074-4DB4-9572-9B470902709E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocCoverTest", "DocCoverTest\DocCoverTest.csproj", "{6F532FE3-D4B6-4347-9330-B4042870EE6F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocCoverTestAssembly", "DocCoverTestAssembly\DocCoverTestAssembly.csproj", "{A5A8E2D0-9FD0-47BF-86F1-FD0D6ADA1466}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
Pack|Any CPU = Pack|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DC97B8FE-D074-4DB4-9572-9B470902709E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC97B8FE-D074-4DB4-9572-9B470902709E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC97B8FE-D074-4DB4-9572-9B470902709E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC97B8FE-D074-4DB4-9572-9B470902709E}.Release|Any CPU.Build.0 = Release|Any CPU
{DC97B8FE-D074-4DB4-9572-9B470902709E}.Pack|Any CPU.ActiveCfg = Pack|Any CPU
{DC97B8FE-D074-4DB4-9572-9B470902709E}.Pack|Any CPU.Build.0 = Pack|Any CPU
{6F532FE3-D4B6-4347-9330-B4042870EE6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6F532FE3-D4B6-4347-9330-B4042870EE6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F532FE3-D4B6-4347-9330-B4042870EE6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6F532FE3-D4B6-4347-9330-B4042870EE6F}.Release|Any CPU.Build.0 = Release|Any CPU
{6F532FE3-D4B6-4347-9330-B4042870EE6F}.Pack|Any CPU.ActiveCfg = Pack|Any CPU
{6F532FE3-D4B6-4347-9330-B4042870EE6F}.Pack|Any CPU.Build.0 = Pack|Any CPU
{A5A8E2D0-9FD0-47BF-86F1-FD0D6ADA1466}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5A8E2D0-9FD0-47BF-86F1-FD0D6ADA1466}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5A8E2D0-9FD0-47BF-86F1-FD0D6ADA1466}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5A8E2D0-9FD0-47BF-86F1-FD0D6ADA1466}.Release|Any CPU.Build.0 = Release|Any CPU
{A5A8E2D0-9FD0-47BF-86F1-FD0D6ADA1466}.Pack|Any CPU.ActiveCfg = Pack|Any CPU
{A5A8E2D0-9FD0-47BF-86F1-FD0D6ADA1466}.Pack|Any CPU.Build.0 = Pack|Any CPU
EndGlobalSection
EndGlobal
25 changes: 25 additions & 0 deletions DocCover/Artees/Tools/DocCover/BadgeStyle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Collections.Generic;

namespace Artees.Tools.DocCover
{
public class BadgeStyle : TypesafeEnum
{
public static readonly Dictionary<string, BadgeStyle> All =
new Dictionary<string, BadgeStyle>();

// ReSharper disable UnusedMember.Global
public static readonly BadgeStyle Plastic = new BadgeStyle("plastic"),
Flat = new BadgeStyle("flat"),
FlatSquare = new BadgeStyle("flat-square"),
ForTheBadge = new BadgeStyle("for-the-badge"),
Popout = new BadgeStyle("popout"),
PopoutSquare = new BadgeStyle("popout-square"),
Social = new BadgeStyle("social");
// ReSharper restore UnusedMember.Global

private BadgeStyle(string name) : base(name)
{
All[name] = this;
}
}
}
65 changes: 65 additions & 0 deletions DocCover/Artees/Tools/DocCover/DocCover.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml;
using Artees.Diagnostics.BDD;
using Artees.Tools.XmlDocumentationNameGetter;

namespace Artees.Tools.DocCover
{
public static class DocCover
{
public static DocCoverReport GetReport(string xmlPath, string dllPath)
{
xmlPath.Aka("XML").Should().Not().BeNull();
var report = new DocCoverReport();
if (xmlPath == null) return report;
var xml = new XmlDocument();
xml.Load(xmlPath);
var dll = Assembly.LoadFile(dllPath);
report.AssemblyName = dll.GetName();
var xmlMembers = new List<string>();
var xmlMembersnodeList = xml.GetElementsByTagName("member");
for (var i = 0; i < xmlMembersnodeList.Count; i++)
{
var xmlMember = xmlMembersnodeList.Item(i);
xmlMembers.Add(xmlMember?.Attributes?["name"].Value);
}

CheckIfTypesAreDocumented(dll.ExportedTypes, xmlMembers, true, report);
CheckIfTypesAreDocumented(dll.DefinedTypes, xmlMembers, false, report);
xmlMembers.Count.Aka("Final").Should().BeEqual(0);
return report;
}

private static void CheckIfTypesAreDocumented(IEnumerable<Type> types,
List<string> xmlMembers, bool isPublic, DocCoverReport report)
{
foreach (var type in types)
{
var typeName = type.GetXmlDocsName();
var success = xmlMembers.RemoveAll(s => s == typeName);
report.Add(new DocCoverMember(type, isPublic, success > 0));
const BindingFlags pub = BindingFlags.DeclaredOnly | BindingFlags.Public |
BindingFlags.Instance | BindingFlags.Static;
var publicMembers = type.GetMembers(pub);
CheckIfMembersAreDocumented(publicMembers, xmlMembers, isPublic, report);
const BindingFlags nonPublic = BindingFlags.DeclaredOnly | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.Static;
var nonPublicMembers = type.GetMembers(nonPublic);
CheckIfMembersAreDocumented(nonPublicMembers, xmlMembers, false, report);
}
}

private static void CheckIfMembersAreDocumented(IEnumerable<MemberInfo> members,
List<string> xmlMembers, bool isPublic, DocCoverReport report)
{
foreach (var member in members)
{
var mName = member.GetXmlDocsName();
var success = xmlMembers.RemoveAll(s => s.StartsWith(mName));
report.Add(new DocCoverMember(member, isPublic, success > 0));
}
}
}
}
30 changes: 30 additions & 0 deletions DocCover/Artees/Tools/DocCover/DocCoverMember.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Reflection;
using Artees.Tools.XmlDocumentationNameGetter;

namespace Artees.Tools.DocCover
{
public class DocCoverMember : IComparable<DocCoverMember>
{
private readonly MemberInfo _memberInfo;

public readonly bool IsPublic,
IsDocumented;

public DocCoverMember(MemberInfo member, bool isPublic, bool isDocumented)
{
_memberInfo = member;
IsPublic = isPublic;
IsDocumented = isDocumented;
}

public string Name => _memberInfo.GetXmlDocsName();

public MemberTypes Type => _memberInfo.MemberType;

public int CompareTo(DocCoverMember other)
{
return string.Compare(Name, other.Name, StringComparison.InvariantCulture);
}
}
}
95 changes: 95 additions & 0 deletions DocCover/Artees/Tools/DocCover/DocCoverReport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;

namespace Artees.Tools.DocCover
{
public class DocCoverReport
{
private readonly List<DocCoverMember> _members = new List<DocCoverMember>();

public IReadOnlyList<DocCoverMember> Members => _members;

internal AssemblyName AssemblyName { private get; set; } =
Assembly.GetExecutingAssembly().GetName();

internal void Add(DocCoverMember member)
{
_members.Add(member);
}

public double GetCoverage()
{
var documented = Members.Count(m => m.IsPublic && m.Type != MemberTypes.Constructor &&
m.IsDocumented);
var total = Members.Count(m => m.IsPublic && m.Type != MemberTypes.Constructor);
return (double) documented / total;
}

public string GetHtml()
{
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
var path = Path.Combine(baseDirectory, "ReportTemplate.html");
var template = File.ReadAllText(path);
var assemblyName = GetAssemblyNameAndVersion(AssemblyName);
var documented = Members.Count(m => m.IsPublic && m.IsDocumented);
var undocumented = Members.Where(m => m.IsPublic && !m.IsDocumented &&
m.Type != MemberTypes.Constructor).ToList();
undocumented.Sort();
var undocumentedCount = undocumented.Count;
var coverage = (double) documented / (documented + undocumentedCount);
var memberPath = Path.Combine(baseDirectory, "MemberTemplate.html");
var memberTemplate = File.ReadAllText(memberPath);
var membersList = undocumented.Count > 0
? undocumented.Select(m => string.Format(memberTemplate, m.Name))
: new List<string> {"N/A"};
var membersHtml = string.Join(string.Empty, membersList);
var generatorAssemblyName = Assembly.GetExecutingAssembly().GetName();
var generator = GetAssemblyNameAndVersion(generatorAssemblyName);
var html = string.Format(template, assemblyName, DateTime.Now, documented,
undocumentedCount, coverage, membersHtml, generator);
return html;
}

private static string GetAssemblyNameAndVersion(AssemblyName assemblyName)
{
return $"{assemblyName.Name} {assemblyName.Version}";
}

public string GetBadge()
{
return GetBadge(BadgeStyle.Flat);
}

public string GetBadge(BadgeStyle style)
{
using (var client = new WebClient())
{
var coverage = Math.Floor(GetCoverage() * 100);
var color = GetBadgeColor(coverage);
var url = "https://img.shields.io/badge/" +
$"documented-{coverage}%-{color}.svg?style={style}";
return client.DownloadString(url);
}
}

private static string GetBadgeColor(double coverage)
{
var colorIndex = (int) Math.Floor(coverage / (100.0 / 6.0));
switch (colorIndex)
{
case 0: return "red";
case 1: return "orange";
case 2: return "yellow";
case 3: return "yellowgreen";
case 4: return "green";
case 5: return "brightgreen";
case 6: return "brightgreen";
default: return "lightgrey";
}
}
}
}
101 changes: 101 additions & 0 deletions DocCover/Artees/Tools/DocCover/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using Artees.Diagnostics.BDD;
using CommandLine;

namespace Artees.Tools.DocCover
{
internal static class Program
{
public static void Main(string[] args)
{
using (var shouldListener = new WarningShouldListener())
{
ShouldAssertions.Listeners.Add(shouldListener);
using (var traceListener = new ConsoleTraceListener())
{
Trace.Listeners.Add(traceListener);
Execute(args);
}
}
}

private static void Execute(IEnumerable<string> args)
{
Parser.Default.ParseArguments<Options>(args).WithParsed(Execute).WithNotParsed(Fail);
}

private static void Execute(Options options)
{
var xmlPath = options.Xml;
xmlPath.Aka("XML").Should().Not().BeNull();
if (xmlPath == null) return;
var dllPath = Path.GetFullPath(options.Dll);
var report = DocCover.GetReport(xmlPath, dllPath);
var html = report.GetHtml();
var outputPath = Path.GetFullPath(options.Output);
if (Directory.Exists(outputPath)) Directory.Delete(outputPath, true);
Directory.CreateDirectory(outputPath);
File.WriteAllText(Path.Combine(outputPath, "index.html"), html);
var reportTemplatePath =
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "report.css");
File.Copy(reportTemplatePath, Path.Combine(outputPath, "report.css"));
var badge = report.GetBadge(BadgeStyle.All[options.Style]);
File.WriteAllText(Path.Combine(outputPath, "badge.svg"), badge);
}

private static void Fail(IEnumerable<Error> errors)
{
foreach (var error in errors)
{
if (error.Tag == ErrorType.HelpRequestedError) continue;
ShouldAssertions.Fail(error.ToString());
}
}

// ReSharper disable once ClassNeverInstantiated.Local
private class Options
{
// ReSharper disable UnusedAutoPropertyAccessor.Local, MemberCanBePrivate.Local
[Option('x', "xml", Hidden = true)] public string XmlOption { private get; set; }

[Value(0, MetaName = "-x, --xml", HelpText = "The XML document to be analyzed.")]
public string XmlValue { private get; set; }

public string Xml => XmlOption ?? XmlValue ?? Dll.Remove(Dll.Length - 4) + ".xml";

[Option('d', "dll", Hidden = true)] public string DllOption { private get; set; }

[Value(1, MetaName = "-d, --dll",
HelpText = "The assembly file to be analyzed. " +
"If not specified, the path of the XML document will be used.")]
public string DllValue { private get; set; }

public string Dll => DllOption ?? DllValue ?? Xml.Remove(Xml.Length - 4) + ".dll";

[Option('o', "outputdir", Hidden = true)]
public string OutputOption { private get; set; }

[Value(2, MetaName = "-o, --outputdir",
HelpText = "The directory where the generated report should be saved.")]
public string OutputValue { private get; set; }

public string Output => OutputOption ?? OutputValue ?? "doc_cover";

[Option('s', "badgestyle", Hidden = true)]
public string StyleOption { private get; set; }

[Value(3, MetaName = "-s, --badgestyle",
HelpText =
"The style of the generated badge. The following styles are available: " +
"plastic, flat, flat-square, for-the-badge, popout, popout-square, " +
"social.")]
public string StyleValue { private get; set; }

public string Style => StyleOption ?? StyleValue ?? "flat";
// ReSharper restore UnusedAutoPropertyAccessor.Local, MemberCanBePrivate.Local
}
}
}
26 changes: 26 additions & 0 deletions DocCover/Artees/Tools/DocCover/TypesafeEnum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Artees.Tools.DocCover
{
/// <summary>
/// <see href="http://www.javacamp.org/designPattern/enum.html"/>
/// </summary>
public abstract class TypesafeEnum
{
private static int _nextId;

// ReSharper disable once UnusedMember.Global
public readonly int Id = _nextId++;

// ReSharper disable once MemberCanBePrivate.Global
public readonly string Name;

protected TypesafeEnum(string name)
{
Name = name;
}

public override string ToString()
{
return Name;
}
}
}
Loading

0 comments on commit 22a6c96

Please sign in to comment.