#region Copyright
// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (C) 2015 Ian Horswill
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in the
// Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// --------------------------------------------------------------------------------------------------------------------
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
namespace Prolog
{
///
/// Creates documentation for the Twig system
///
[SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly"),
SuppressMessage("Microsoft.Design",
"CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
public class Manual : IDisposable
{
private Manual(string path)
{
output = File.CreateText(path);
}
///
/// Release resources associated with output stream.
///
[SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly"),
SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
public void Dispose()
{
output.Dispose();
}
private readonly TextWriter output;
private static readonly string[] ManualSections = {
"h2|Flow control|Prolog",
"h2|Meta-logical predicates|Prolog",
"h2|All solutions predicates|Prolog",
"h2|Arithmetic|Prolog",
"h2|Comparisons|Prolog",
"h2|List predicates|Prolog",
"h2|Term manipulation|Prolog",
"h2|Type predicates|Prolog",
"h2|Constraint programming|Prolog",
"h2|.NET interoperation|Prolog",
"h2|Declarations|Prolog",
"h2|Definite clause grammars|Prolog",
"h2|Loading code|Prolog",
"h2|Database manipulation|Prolog",
"h2|eremic logic|Prolog",
"h2|Other Predicates|Prolog"
};
private static readonly Type[] ClassHierarchyRoots = {
};
[SuppressMessage("Microsoft.Performance",
"CA1810:InitializeReferenceTypeStaticFieldsInline")]
static Manual()
{
foreach (var s in ManualSections)
SectionTable[s.Split('|')[1].ToLower()] = new List();
}
internal static Dictionary> SectionTable = new Dictionary>();
internal static void AddToSection(string name, Delegate d)
{
if (SectionTable.ContainsKey(name))
SectionTable[name].Add(d);
else
throw new ArgumentException("Unknown manual section: " + name);
}
internal static void AddToSections(string sections, Delegate d)
{
string[] sectionNames = sections.Split(',');
foreach (var name in sectionNames)
AddToSection(name, d);
}
///
/// Returns the text of the manual
///
///
public override string ToString()
{
return output.ToString();
}
private delegate void Thunk();
///
/// Output the manual entry for the specified procedure or macro.
///
[SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals",
MessageId = "ignore"),
SuppressMessage("Microsoft.Naming",
"CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "d")]
public void WriteEntry(Delegate d, string language)
{
Tag("dt", () => Span("dullCode", delegate
{
if (language == "Prolog")
{
Span("procedureName", Htmlify(DelegateUtils.NamedProcedureTable[d]));
var args = d.Arglist();
bool hasArg = false;
#pragma warning disable 414, 168, 219
// ReSharper disable UnusedVariable
foreach (var ignore in args)
// ReSharper restore UnusedVariable
#pragma warning restore 414, 168, 219
{
hasArg = true;
break;
}
if (hasArg)
WriteArglist(d.Arglist(), true, true);
}
else
{
OpenParen();
Span("procedureName", Htmlify(DelegateUtils.NamedProcedureTable[d]));
WriteArglist(d.Arglist(), false, false);
}
}));
Tag("dd", Htmlify(d.Documentation()));
}
[SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo",
MessageId = "System.String.ToLower"),
SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
private void WriteLanguageSection(string sectionInfo, string language)
{
string[] info = sectionInfo.Split('|');
string sectionName = info[1];
string sectionType = info[0];
string lang = info[2];
if (lang == language)
{
List entries = SectionTable[sectionName.ToLower()];
//entries.Sort((d1, d2) => Writer.namedProcedureTable[d1].CompareTo(Writer.namedProcedureTable[d2]));
Section(sectionType, sectionName);
Tag("dl", delegate
{
foreach (var d in entries)
WriteEntry(d, language);
});
}
}
///
/// Build the actual manual text
///
private void WriteBody(Thunk tocWriter, Thunk sectionWriter)
{
#if ClassDocumentation
BuildSubclassTable();
#endif
tocWriter();
sectionWriter();
//WriteLispTOC();
//WriteClassHierarchyTOC();
//WriteLispSections();
//WriteClassHierarchySections();
}
private void WriteClassHierarchySections()
{
#if ClassDocumentation
//output.Write("");
foreach (Type t in classHierarchyRoots)
WriteClassHierarchy(t);
DocumentRemainingClasses();
#endif
}
private void WriteAll(string title, Thunk tocWriter, Thunk sectionWriter)
{
output.Write(Header, title);
Section("h1", title);
WriteBody(tocWriter, sectionWriter);
output.Write(Footer);
}
private void Close()
{
output.Close();
}
///
/// Writes the Lisp manual to the specified path.
///
private static void WriteLanguageManualToFile(string language, string title, string path)
{
using (var m = new Manual(path))
{
// ReSharper disable AccessToDisposedClosure
m.WriteAll(title, () => m.WriteLanguageTOC(language), () => m.WriteLanguageSections(language));
// ReSharper restore AccessToDisposedClosure
m.Close();
}
}
///
/// Writes the Class manual to the specified path.
///
[SuppressMessage("Microsoft.Usage",
"CA2202:Do not dispose objects multiple times")]
private static void WriteClassManualToFile(string path)
{
using (var m = new Manual(path))
{
m.WriteAll("Twig Class Reference Manual", m.WriteClassHierarchyTOC, m.WriteClassHierarchySections);
m.Close();
}
}
///
/// Writes all manual files to the specified directory
///
///
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults",
MessageId = "Twig.MinimaLisp.KnowledgeBase")]
public static void WriteManual(string directory)
{
// ReSharper disable once ObjectCreationAsStatement
new KnowledgeBase("foo", null); // Force InstallPrimitives() to run.
WriteLanguageManualToFile("Prolog", "Unity Prolog Reference", directory + "\\Unity Prolog Reference.htm");
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
// ReSharper disable once InconsistentNaming
private void WriteLanguageTOC(string language)
{
int currentLevel = 1;
//Tag("h2", "Table of contents");
foreach (var sectionInfo in ManualSections)
{
string[] info = sectionInfo.Split('|');
string sectionName = info[1];
string sectionType = info[0];
string lang = info[2];
if (lang == language)
{
int sectionLevel = sectionType[1] - '0';
while (currentLevel != sectionLevel)
{
if (currentLevel < sectionLevel)
{
output.WriteLine("
");
currentLevel++;
}
else
{
output.WriteLine("
");
currentLevel--;
}
}
WriteTOCEntry(sectionName);
}
}
while (currentLevel-- > 1)
output.WriteLine("");
}
// ReSharper disable once InconsistentNaming
private void WriteClassHierarchyTOC()
{
//WriteTOCEntry("Class hierarchy");
Tag("ul", delegate
{
foreach (var t in ClassHierarchyRoots)
WriteTOCEntry(t.Name + " hierarchy");
WriteTOCEntry("Other classes");
});
output.Write("");
}
// ReSharper disable once InconsistentNaming
private void WriteTOCEntry(string sectionName)
{
Tag("li",
() => Tag("a", string.Format("href=\"#{0}\"", sectionName), () => output.Write(sectionName)));
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
private void WriteLanguageSections(string language)
{
// ReSharper disable UnusedVariable
foreach (var s in ManualSections)
// ReSharper restore UnusedVariable
{
WriteLanguageSection(s, language);
output.WriteLine();
}
}
//private readonly Dictionary> subclasses = new Dictionary>();
//private readonly Dictionary areClassMembersDocumented = new Dictionary();
//private readonly Dictionary documentationAlreadyPrinted = new Dictionary();
//[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
//private void AddSubclass(Type parent, Type subclass)
//{
// if (parent == null)
// return;
// if (!subclasses.ContainsKey(parent))
// subclasses[parent] = new List();
// subclasses[parent].Add(subclass);
//}
#if ClassDocumentation
private static readonly Assembly twigAssembly = Assembly.GetAssembly(typeof (PhysicalObject));
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
private void BuildSubclassTable()
{
foreach (Type t in twigAssembly.GetTypes())
if (t.GetDocumentation() != null)
{
if (!t.IsAbstract)
// Force generation of class documentation if it isn't abstract, since we at least want to show the constructors.
areClassMembersDocumented[t] = true;
else
{
foreach (
var member in
t.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance |
BindingFlags.Static))
{
if (member.GetDocumentation() != null)
{
areClassMembersDocumented[t] = true;
break;
}
}
}
AddSubclass(t.BaseType, t);
}
}
private bool MembersAreDocumented(Type t)
{
bool v;
if (areClassMembersDocumented.TryGetValue(t, out v))
return v;
return false;
}
private static string ClassBookmark(Type t)
{
return "Class " + t.Name;
}
private string ClassReference(Type t)
{
if (MembersAreDocumented(t))
{
return string.Format("{1}", ClassBookmark(t), TypeName(t));
}
if (t.Namespace != null && t.Namespace.StartsWith("Microsoft.Xna"))
return MSDNReference(t);
return TypeName(t);
}
private static string MSDNReference(Type t)
{
System.Diagnostics.Debug.Assert(t.Namespace != null, "t.Namespace != null");
return string.Format("{2}",
t.Namespace.ToLower(), t.Name.ToLower(), t.Name);
}
private string TypeName(Type t)
{
if (t == typeof (float))
return "float";
if (t == typeof (double))
return "double";
if (t == typeof (int))
return "int";
if (t == typeof (long))
return "long";
if (t == typeof (bool))
return "bool";
if (t == typeof (string))
return "string";
if (t == typeof (object))
return "object";
if (t == typeof (void))
return "void";
if (t.IsGenericType)
{
StringBuilder b = new StringBuilder();
string[] names = t.GetGenericTypeDefinition().Name.Split('`');
b.Append(names[0]);
b.Append("<");
bool first = true;
foreach (var a in t.GetGenericArguments())
{
if (!first)
b.Append(",");
first = false;
b.Append(TypeName(a));
}
b.Append(">");
return b.ToString();
}
return t.Name;
}
private void WriteSubhierarchy(Type t)
{
Tag("li", delegate
{
Span("dullCode", ClassReference(t));
string documentation = t.GetDocumentation();
if (documentation != null)
{
output.Write(" ");
output.Write(documentation);
}
});
if (subclasses.ContainsKey(t))
{
Tag("ul", delegate
{
foreach (Type c in subclasses[t])
WriteSubhierarchy(c);
});
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
private void WriteClassHierarchy(Type t)
{
Section("h1", t.Name + " hierarchy", t.Name + " hierarchy");
Tag("ul", () => WriteSubhierarchy(t));
DocumentSubhierarchy(t);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
private void DocumentSubhierarchy(Type t)
{
if (MembersAreDocumented(t))
DocumentClass(t);
if (subclasses.ContainsKey(t))
{
foreach (Type c in subclasses[t])
DocumentSubhierarchy(c);
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"),
System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")
]
private void DocumentClass(Type t)
{
if (documentationAlreadyPrinted.ContainsKey(t))
return;
documentationAlreadyPrinted[t] = true;
Section("h2", t.Name + " class", ClassBookmark(t));
Span("argument", delegate
{
if (t.IsEnum)
{
output.Write("Enumerated type");
}
else if (t.IsValueType)
{
output.Write("Value type");
}
else
{
output.Write("Superclasses: ");
Type p = t.BaseType;
output.Write(ClassReference(p));
System.Diagnostics.Debug.Assert(p != null, "p != null");
p = p.BaseType;
while (p != null && p != typeof (object))
{
output.Write(", ");
output.Write(ClassReference(p));
p = p.BaseType;
}
List subs;
if (subclasses.TryGetValue(t, out subs) && subs.Count > 0)
{
output.Write(" Subclasses: ");
for (int i = 0; i < subs.Count; i++)
{
if (i > 0)
output.Write(", ");
output.Write(ClassReference(subs[i]));
}
}
}
});
string doc = t.GetDocumentation();
if (doc != null)
Tag("p", doc);
if (t.IsSubclassOf(typeof (PhysicalObject)))
{
DocumentMembers(
t.GetConstructors(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance |
BindingFlags.Static),
"Constructors", !t.IsAbstract,
delegate(ConstructorInfo c)
{
ParameterInfo[] p = c.GetParameters();
if (p.Length >= 2 && p[1].ParameterType == typeof (string))
{
Span("dullCode", "(define-twig-object ");
Span("argument", "name");
Span("dullCode", " " + t.Name + ((p.Length > 2) ? " " : ""));
Span("argument",
delegate
{
for (int i = 2; i < p.Length; i++)
{
output.Write(" ");
output.Write(p[i].Name);
output.Write(":");
output.Write(ClassReference(p[i].ParameterType));
}
});
}
else
{
Span("dullCode", "(new-component ");
Span("dullCode", t.Name + ((p.Length > 1) ? " " : ""));
Span("argument",
delegate
{
for (int i = 1; i < p.Length; i++)
{
output.Write(" ");
output.Write(p[i].Name);
output.Write(":");
output.Write(ClassReference(p[i].ParameterType));
}
});
}
Span("dullCode", ")");
});
}
else if (t.IsSubclassOf(typeof (TwigGameComponent)))
{
DocumentMembers(
t.GetConstructors(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance |
BindingFlags.Static),
"Constructors", !t.IsAbstract,
delegate(ConstructorInfo c)
{
ParameterInfo[] p = c.GetParameters();
Span("dullCode", "(new-component " + t.Name + ((p.Length > 1) ? " " : ""));
Span("argument",
delegate
{
for (int i = 1; i < p.Length; i++)
{
output.Write(" ");
output.Write(p[i].Name);
output.Write(":");
output.Write(ClassReference(p[i].ParameterType));
}
});
Span("dullCode", ")");
});
}
else if (!t.IsEnum)
{
DocumentMembers(
t.GetConstructors(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance |
BindingFlags.Static),
"Constructors", !t.IsAbstract,
delegate(ConstructorInfo c)
{
Span("dullCode",
delegate
{
output.Write("(new ");
output.Write(t.Name);
});
Span("argument",
delegate
{
ParameterInfo[] p = c.GetParameters();
foreach (ParameterInfo t1 in p)
{
output.Write(" ");
output.Write(t1.Name);
output.Write(":");
output.Write(ClassReference(t1.ParameterType));
}
});
Span("dullCode", ")");
});
}
DocumentMembers(
t.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance |
BindingFlags.Static),
t.IsEnum ? "Values" : "Fields", t.IsEnum,
delegate(FieldInfo f)
{
if (f.IsStatic)
Span("dullCode", t.Name + "." + f.Name);
else
Span("dullCode", f.Name);
if (!t.IsEnum)
Span("argument", " : " + ClassReference(f.FieldType));
});
DocumentMembers(
t.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance |
BindingFlags.Static),
"Properties", t.IsEnum,
delegate(PropertyInfo p)
{
if (p.GetAccessors()[0].IsStatic)
Span("dullCode", t.Name + "." + p.Name);
else
Span("dullCode", p.Name);
Span("argument", " : " + ClassReference(p.PropertyType));
});
DocumentMembers(
t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance |
BindingFlags.Static),
"Methods", false,
delegate(MethodInfo m)
{
Span("dullCode", "(");
if (m.IsStatic)
Span("dullCode", t.Name);
else
Span("argument", t.Name.ToLower());
Span("dullCode", "." + m.Name);
Span("argument",
delegate
{
ParameterInfo[] p = m.GetParameters();
foreach (ParameterInfo t1 in p)
{
output.Write(" ");
output.Write(t1.Name);
output.Write(":");
output.Write(ClassReference(t1.ParameterType));
}
});
Span("dullCode", ")");
output.Write(" : ");
Span("argument", ClassReference(m.ReturnType));
});
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
private void DocumentRemainingClasses()
{
List remainingClasses = new List();
foreach (var pair in areClassMembersDocumented)
if (!documentationAlreadyPrinted.ContainsKey(pair.Key))
remainingClasses.Add(pair.Key);
// ReSharper disable StringCompareIsCultureSpecific.1
remainingClasses.Sort((t1, t2) => string.Compare(t1.Name, t2.Name));
// ReSharper restore StringCompareIsCultureSpecific.1
Section("h1", "Other classes");
foreach (var c in remainingClasses)
DocumentClass(c);
}
private delegate void MemberFormatter(T member);
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"),
System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1307:SpecifyStringComparison",
MessageId = "System.String.Compare(System.String,System.String)")]
private void DocumentMembers(T[] members, string heading, bool forceDocumentation, MemberFormatter format)
where T : MemberInfo
{
bool foundOne = false;
foreach (var m in members)
if (forceDocumentation || m.GetDocumentation() != null)
{
foundOne = true;
break;
}
if (foundOne)
{
Section("h3", heading, null);
List mems = new List(members);
// ReSharper disable StringCompareIsCultureSpecific.1
mems.Sort((m1, m2) => (string.Compare(m1.Name, m2.Name)));
// ReSharper restore StringCompareIsCultureSpecific.1
Tag("dl", delegate
{
foreach (var m in mems)
{
string doc = m.GetDocumentation();
if (m.Name != "value__" && (forceDocumentation || doc != null))
{
// ReSharper disable AccessToModifiedClosure
Tag("dt", () => format(m));
// ReSharper restore AccessToModifiedClosure
if (doc != null)
Tag("dd", doc);
}
}
});
}
}
#endif
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
private void Section(string sectionType, string sectionName)
{
Section(sectionType, sectionName, sectionName);
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
private void Section(string sectionType, string sectionName, string bookmarkName)
{
if (bookmarkName != null)
Tag(sectionType, () => Tag("a", string.Format("name=\"{0}\"", bookmarkName), sectionName));
else
Tag(sectionType, sectionName);
}
private void Span(string style, Thunk p)
{
Tag("span", string.Format("class=\"{0}\"", style), p);
}
private void Span(string style, string s)
{
Tag("span", string.Format("class=\"{0}\"", style), () => output.Write(s));
}
private void Tag(string name, Thunk p)
{
output.Write("<{0}>", name);
p();
output.Write("{0}>", name);
}
private void Tag(string name, string content)
{
Tag(name, () => output.Write(content));
}
private void Tag(string name, string args, string content)
{
Tag(name, args, () => output.Write(content));
}
private void Tag(string name, string args, Thunk p)
{
output.Write("<{0} {1}>", name, args);
p();
output.Write("{0}>", name);
}
private void Space(bool needComma)
{
output.Write(needComma ? ", " : " ");
}
private void WriteArglist(IEnumerable