Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/api-doc-tools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorMike Norman <mike.norman@xamarin.com>2017-05-31 21:26:44 +0300
committerJoel Martinez <joelmartinez@gmail.com>2017-06-02 18:42:11 +0300
commit239cf594ccb55e47128e02bfc3c3753ad1f0ca9c (patch)
treeed97ce7efeb17f902c54b6ac731935ce9a619425 /tools
parentfb070470b334d2830556405694c9bb02fd8f4a1c (diff)
Tool to report on and bulk update ECMAXML files
Diffstat (limited to 'tools')
-rw-r--r--tools/DocStat/DocStat.sln17
-rw-r--r--tools/DocStat/DocStat/CommandUtils.cs203
-rw-r--r--tools/DocStat/DocStat/DocStat.csproj47
-rw-r--r--tools/DocStat/DocStat/ParallelXmlHelper.cs136
-rw-r--r--tools/DocStat/DocStat/apistat.cs80
-rw-r--r--tools/DocStat/DocStat/comparefix.cs137
-rw-r--r--tools/DocStat/DocStat/internalize.cs97
-rw-r--r--tools/DocStat/DocStat/obsolete.cs73
-rw-r--r--tools/DocStat/DocStat/remaining.cs133
9 files changed, 923 insertions, 0 deletions
diff --git a/tools/DocStat/DocStat.sln b/tools/DocStat/DocStat.sln
new file mode 100644
index 00000000..3b2f6025
--- /dev/null
+++ b/tools/DocStat/DocStat.sln
@@ -0,0 +1,17 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocStat", "DocStat\DocStat.csproj", "{EF899D5E-28F7-4CEE-A47A-80C4B4995B81}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x86 = Debug|x86
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {EF899D5E-28F7-4CEE-A47A-80C4B4995B81}.Debug|x86.ActiveCfg = Debug|x86
+ {EF899D5E-28F7-4CEE-A47A-80C4B4995B81}.Debug|x86.Build.0 = Debug|x86
+ {EF899D5E-28F7-4CEE-A47A-80C4B4995B81}.Release|x86.ActiveCfg = Release|x86
+ {EF899D5E-28F7-4CEE-A47A-80C4B4995B81}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+EndGlobal
diff --git a/tools/DocStat/DocStat/CommandUtils.cs b/tools/DocStat/DocStat/CommandUtils.cs
new file mode 100644
index 00000000..83aeeeb8
--- /dev/null
+++ b/tools/DocStat/DocStat/CommandUtils.cs
@@ -0,0 +1,203 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Xml;
+using System.Xml.Linq;
+using Mono.Options;
+
+namespace DocStat
+{
+ public static class CommandUtils
+ {
+ public static List<string> ProcessFileArgs(IEnumerable<string> args,
+ ref string rootdir,
+ ref string omitlist,
+ ref string processlist,
+ ref string pattern)
+ {
+ // Take that, compiler!
+ string _rd = "";
+ string _ol = "";
+ string _pl = "";
+ string _pa = "";
+
+ List<string> extras;
+
+ var opt = new OptionSet {
+
+ { "d|dir|directory=",
+ (string d) => _rd = d },
+ // Provide a file that contains a list of files to omit. User may use more than one -o
+ { "e|exceptlist=",
+ (m) => _ol = m },
+ // List a file that contains a list of files to process
+ { "p|processlist=",
+ (f) => _pl = f},
+ { "n|namematches=",
+ (n) => _pa = n }
+ };
+ extras = opt.Parse(args);
+
+ // And that!
+ rootdir = String.IsNullOrEmpty(_rd) ? rootdir : _rd;
+ omitlist = String.IsNullOrEmpty(_ol) ? omitlist : _ol;
+ processlist =String.IsNullOrEmpty(_pl) ? processlist : _pl;
+ pattern = String.IsNullOrEmpty(_pa) ? pattern : _pa;
+
+ return extras;
+ }
+
+ public static IEnumerable<string> GetFileList(string processListFileName,
+ string omitListFileName,
+ string rootDir,
+ string pattern,
+ bool recurse = true,
+ bool skipNsAndIndex = true)
+ {
+ IEnumerable<string> toProcess = Enumerable.Empty<string>();
+
+ // Build search predicates
+ Func<string, bool> fileMatches;
+ Func<string, bool> omitFile;
+
+ if (!String.IsNullOrEmpty(pattern))
+ {
+ Regex fm = new Regex(pattern);
+ fileMatches = fm.IsMatch;
+ }
+ else
+ {
+ fileMatches = (string s) => true;
+ }
+
+ if (String.IsNullOrEmpty(omitListFileName))
+ {
+ omitFile = (string s) => false;
+ }
+ else
+ {
+ if (File.Exists(omitListFileName))
+ {
+ IEnumerable<string> toOmit = FileNamesIn(omitListFileName);
+ omitFile = toOmit.Contains;
+ }
+ else
+ throw new ArgumentException("Omission file does not exist: " + omitListFileName);
+ }
+
+ // Process any user-supplied file lists
+ if (!String.IsNullOrEmpty(processListFileName))
+ {
+ if (File.Exists(processListFileName))
+ {
+ toProcess = toProcess.Union(
+ FileNamesIn(processListFileName)
+ .Where((p) => fileMatches(Path.GetFileName(p)) && !omitFile(p)));
+ }
+ else
+ {
+ throw new FileNotFoundException("Process list file does not exist: " + processListFileName);
+ }
+ }
+
+ if (String.IsNullOrEmpty(rootDir) && !String.IsNullOrEmpty(processListFileName))
+ return toProcess; // they gave us a list only, so they're happy
+
+ if (String.IsNullOrEmpty(rootDir))
+ rootDir = Directory.GetCurrentDirectory(); // no list or rootdir, so they want the default
+
+ if (!Directory.Exists(rootDir)) //They gave us something, but it was a boo-boo
+ throw new ArgumentException("The provided root directory was required and does not exist: " + rootDir);
+
+ // We have a good root directory, and we want to use it.
+
+ Func<string, bool> isNsOrIndex;
+
+ if (skipNsAndIndex)
+ {
+ isNsOrIndex = (string fName) => {
+ string barename = Path.GetFileName(fName);
+ return barename.StartsWith("ns-") || barename.StartsWith("index");
+ };
+ }
+ else
+ {
+ isNsOrIndex = (string arg) => false;
+ }
+ return toProcess.Union(Directory.GetFiles(rootDir,
+ "*.xml",
+ recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
+ .Where(p => fileMatches(Path.GetFileName(p)) && !omitFile(p) && !isNsOrIndex(p));
+
+ }
+
+ public static void ThrowOnFiniteExtras(List<string> extras)
+ {
+ if (extras.Count > 1)
+ {
+ StringBuilder s = new StringBuilder("The following options were not recoginzed:\n");
+ List<string> sl = new List<string>();
+ for (int i = 1; i < extras.Count; i++)
+ {
+ sl.Add(extras[i]);
+ }
+ s.Append(String.Join("\n", sl));
+ throw new Exception(s.ToString());
+ }
+
+ }
+
+ public static XmlDocument ToXmlDocument(XDocument xDocument)
+ {
+ var xmlDocument = new XmlDocument();
+ using (var reader = xDocument.CreateReader())
+ {
+ xmlDocument.Load(reader);
+ }
+
+ var xDeclaration = xDocument.Declaration;
+ if (xDeclaration != null)
+ {
+ var xmlDeclaration = xmlDocument.CreateXmlDeclaration(
+ xDeclaration.Version,
+ xDeclaration.Encoding,
+ xDeclaration.Standalone);
+ xmlDocument.InsertBefore(xmlDeclaration, xmlDocument.FirstChild);
+ }
+
+ return xmlDocument;
+ }
+
+ public static void WriteXDocument(XDocument xdoc, string file)
+ {
+ if (!File.Exists(file))
+ throw new FileNotFoundException("File not found: " + file);
+
+ XmlDocument xmldoc = CommandUtils.ToXmlDocument(xdoc);
+ TextWriter xdout = new StreamWriter(file);
+ // Write back
+ XmlTextWriter writer = new XmlTextWriter(xdout)
+ {
+ Formatting = Formatting.Indented,
+ IndentChar = ' ',
+ Indentation = 2
+ };
+ xmldoc.WriteTo(writer);
+ xdout.WriteLine();
+ xdout.Flush();
+ }
+
+ private static IEnumerable<string> FileNamesIn(string fileListPath)
+ {
+ return File.ReadLines(fileListPath)
+ .Where(s =>
+ !String.IsNullOrEmpty(s) &&
+ Uri.IsWellFormedUriString(s, UriKind.Absolute) &&
+ File.Exists(s)
+ );
+ }
+ }
+}
diff --git a/tools/DocStat/DocStat/DocStat.csproj b/tools/DocStat/DocStat/DocStat.csproj
new file mode 100644
index 00000000..d06c6276
--- /dev/null
+++ b/tools/DocStat/DocStat/DocStat.csproj
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+ <ProjectGuid>{EF899D5E-28F7-4CEE-A47A-80C4B4995B81}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <RootNamespace>DocStat</RootNamespace>
+ <AssemblyName>DocStat</AssemblyName>
+ <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG;</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <PlatformTarget>x86</PlatformTarget>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <PlatformTarget>x86</PlatformTarget>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="apistat.cs" />
+ <Compile Include="CommandUtils.cs" />
+ <Compile Include="comparefix.cs" />
+ <Compile Include="internalize.cs" />
+ <Compile Include="obsolete.cs" />
+ <Compile Include="ParallelXmlHelper.cs" />
+ <Compile Include="remaining.cs" />
+ <Compile Include="..\..\..\mdoc\Options.cs">
+ <Link>Options.cs</Link>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Xml" />
+ <Reference Include="System" />
+ </ItemGroup>
+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/tools/DocStat/DocStat/ParallelXmlHelper.cs b/tools/DocStat/DocStat/ParallelXmlHelper.cs
new file mode 100644
index 00000000..69f05ea0
--- /dev/null
+++ b/tools/DocStat/DocStat/ParallelXmlHelper.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+
+namespace DocStat
+{
+ public static class ParallelXmlHelper
+ {
+ public static void Fix(XElement toFix, XElement forReference)
+ {
+ if (null == forReference)
+ {
+ return;
+ }
+
+ if (XNode.DeepEquals(toFix, forReference))
+ {
+ return;
+ }
+
+ toFix.ReplaceWith(forReference);
+ }
+
+ // Sometimes, the identifying attribute lives in a child. This means that
+ // we need to return a predicate that finds the piece to match.
+ // So:
+ // Return a function that takes an XElement with a known unique identifier
+ // and return a predicate that gets the XAttribute that identifies it
+ public static Func<XElement, XAttribute> IdentifyingAttributePredicateFor(XElement el)
+ {
+ switch (el.Name.ToString())
+ {
+ case "typeparam":
+ case "param":
+ return (XElement e) => e.Attribute("name");
+ case "Member":
+ // The ILAsm signature is unique, and always present
+ return (XElement e) => e.Elements("MemberSignature")
+ .First((a) => a.Attribute("Language").Value == "ILAsm")
+ .Attribute("Value");
+ case "related":
+ return (XElement e) => e.Attribute("href"); ;
+ default:
+ throw new Exception("Encountered plural node of type " + el.Name);
+ }
+ }
+
+ // Walk the hierarchy to get a selector that can be used to find the current element
+ // This selector can then be used on the parallel XDocument to retrieve the equivalent
+ // XElement. The selector returns null early if any selector returns null
+ public static Func<XDocument, XElement> GetSelectorFor(XElement toSelect)
+ {
+ List<Func<XElement, XElement>> toRun = new List<Func<XElement, XElement>>();
+
+ XElement current = toSelect;
+ Func<XElement, XElement> selector;
+
+ while (current.Parent != null)
+ {
+ XName currentName = current.Name;
+ if (current.Parent.Elements(currentName).Count() > 1)
+ {
+ // Function that finds the element-specific unique attribute.
+ Func<XElement, XAttribute> uniquePredicate
+ = IdentifyingAttributePredicateFor(current);
+
+ // Get the unique attribute value in *this* xml tree.
+ // (We know this exists.)
+ string uniqueAttr = uniquePredicate(current).Value;
+
+ selector = (XElement arg) => arg.Elements(currentName)
+ .FirstOrDefault((XElement a) =>
+ uniquePredicate(a).Value == uniqueAttr);
+ }
+ else
+ {
+ selector = (XElement arg) => arg.Element(currentName);
+ }
+
+ toRun.Add(selector);
+ current = current.Parent;
+ }
+
+ return (XDocument doc) =>
+ {
+ XElement _current = doc.Root;
+
+ foreach (var _selector in ((IEnumerable<Func<XElement, XElement>>)toRun).Reverse())
+ {
+
+ _current = _selector.Invoke(_current);
+
+ if (_current == null)
+ return null;
+ }
+ return _current;
+ };
+ }
+
+ public static XElement ParallelElement(XElement sourceElement,
+ string sourcePath,
+ string sourceRoot,
+ string refRoot,
+ HashSet<string> refPaths)
+ {
+ string parallelPath = GetParallelFilePathFor(sourcePath, refRoot, sourceRoot);
+
+ // bail early if we can
+
+ if (!File.Exists(parallelPath) || !refPaths.Contains(parallelPath))
+ return null;
+
+ FileAttributes attr = File.GetAttributes(parallelPath);
+ if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
+ return null;
+
+ XDocument refToSearch = XDocument.Load(parallelPath);
+ Console.WriteLine("Found parallel document");
+ var toReturn = GetSelectorFor(sourceElement).Invoke(refToSearch);
+ Console.WriteLine("Got the parallel element");
+ return toReturn;
+ }
+
+ public static string GetParallelFilePathFor(string pathToTypeToFix,
+ string rootOfFilesToUse,
+ string rootOfFilesToFix)
+ {
+ string fullFixPath = Path.GetFullPath(pathToTypeToFix);
+ string fullFixRoot = Path.GetFullPath(rootOfFilesToFix);
+ string fullRefRoot = Path.GetFullPath(rootOfFilesToUse);
+ return fullFixPath.Replace(fullFixRoot, fullRefRoot);
+ }
+ }
+}
diff --git a/tools/DocStat/DocStat/apistat.cs b/tools/DocStat/DocStat/apistat.cs
new file mode 100644
index 00000000..860579ad
--- /dev/null
+++ b/tools/DocStat/DocStat/apistat.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Mono.Options;
+
+
+namespace DocStat
+{
+ public class apistat
+ {
+ private static void Main(string[] args)
+ {
+ apistat a = new apistat();
+ try
+ {
+ a.Run(args);
+ }
+ catch (Exception e)
+ {
+
+ Console.Error.WriteLine("apistat: {0}", e.Message);
+ Environment.ExitCode = 1;
+ }
+ }
+
+ internal Dictionary<string, ApiCommand> subcommands;
+
+ private void Run (string[] args) {
+ subcommands = new Dictionary<string, ApiCommand>() {
+ {"internalize", new InternalizeCommand() },
+ {"remaining", new RemainingCommand() },
+ {"obsolete", new ObsoleteCommand() },
+ {"comparefix", new CompareFixCommand() }
+ };
+
+ GetCommand(args.First()).Run(args);
+ }
+ internal ApiCommand GetCommand(string command)
+ {
+ ApiCommand a;
+ if (!subcommands.TryGetValue(command, out a))
+ {
+ throw new Exception(String.Format("Unknown command: {0}.", command));
+ }
+ return a;
+ }
+ }
+
+ public abstract class ApiCommand {
+
+ public abstract void Run(IEnumerable<string> args);
+
+ protected List<string> Parse(OptionSet p, IEnumerable<string> args,
+ string command, string prototype, string description)
+ {
+ bool showHelp = false;
+ p.Add("h|?|help",
+ "Show this message and exit.",
+ v => showHelp = v != null);
+
+ List<string> extra = null;
+ if (args != null)
+ {
+ extra = p.Parse(args.Skip(1));
+ }
+ if (args == null || showHelp)
+ {
+ Console.WriteLine("usage: mdoc {0} {1}",
+ args == null ? command : args.First(), prototype);
+ Console.WriteLine();
+ Console.WriteLine(description);
+ Console.WriteLine();
+ Console.WriteLine("Available Options:");
+ p.WriteOptionDescriptions(Console.Out);
+ return null;
+ }
+ return extra;
+ }
+ }
+}
diff --git a/tools/DocStat/DocStat/comparefix.cs b/tools/DocStat/DocStat/comparefix.cs
new file mode 100644
index 00000000..5bfd8c58
--- /dev/null
+++ b/tools/DocStat/DocStat/comparefix.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using Mono.Options;
+
+namespace DocStat
+{
+ public class CompareFixCommand : ApiCommand
+ {
+
+ public override void Run(IEnumerable<string> args)
+ {
+ string filesToFixDir = "";
+ string omitlist = "";
+ string processlist = "";
+ string pattern = "";
+
+ List<string> extras = CommandUtils.ProcessFileArgs(args,
+ ref filesToFixDir,
+ ref omitlist,
+ ref processlist,
+ ref pattern);
+ // must have
+ string filesToUseDir = "";
+ // should have
+ bool doSummaries = true;
+ bool doParameters = true;
+ bool doReturns = true;
+ bool doRemarks = true;
+ bool doTypes = true;
+ // nice to have
+ bool dryRun = false;
+ bool reportChanges = false;
+
+ var opts = new OptionSet {
+ {"f|fix=", (f) => filesToFixDir = f},
+ {"u|using=", (u) => filesToUseDir = u},
+ {"s|summaries", (s) => doSummaries = s != null},
+ {"a|params", (p) => doParameters = p != null },
+ {"r|retvals", (r) => doReturns = r != null },
+ {"m|remarks", (r) => doRemarks = r != null },
+ {"t|typesummaries", (t) => doTypes = t != null },
+ {"y|dryrun", (d) => dryRun = d != null },
+ {"c|reportchanges", (c) => reportChanges = c != null },
+ };
+
+ extras = opts.Parse(extras);
+ CommandUtils.ThrowOnFiniteExtras(extras);
+ if (String.IsNullOrEmpty(filesToUseDir))
+ throw new ArgumentException("You must supply a parallel directory from which to source new content with '[u|using]'=.");
+
+
+ IEnumerable<string> toFix = CommandUtils.GetFileList(processlist, omitlist, filesToFixDir, pattern);
+ HashSet<string> toUse = new HashSet<string>(CommandUtils.GetFileList("", "", filesToUseDir, ""));
+
+ toFix = toFix.Where((f) => toUse.Contains(ParallelXmlHelper.GetParallelFilePathFor(f, filesToUseDir, filesToFixDir)));
+
+ // closure for lexical brevity in loop below
+ Action<XElement, string> Fix = (XElement e, string f) =>
+ {
+ Console.WriteLine(e.Name);
+ ParallelXmlHelper.Fix(e, ParallelXmlHelper.ParallelElement(e,
+ f,
+ filesToFixDir,
+ filesToUseDir,
+ toUse));
+ };
+
+ foreach (var f in toFix)
+ {
+ bool changed = false;
+ XDocument fixie = XDocument.Load(f);
+
+ EventHandler<XObjectChangeEventArgs> SetTrueIfChanged = null;
+ SetTrueIfChanged =
+ new EventHandler<XObjectChangeEventArgs>((sender, e) => { fixie.Changed -= SetTrueIfChanged; changed = true; });
+ fixie.Changed += SetTrueIfChanged;
+
+ // (1) Fix ype-level summary and remarks:
+ XElement typeSummaryToFix = fixie.Element("Type").Element("Docs").Element("summary");
+ Fix(typeSummaryToFix, f);
+
+ XElement typeRemarksToFix = fixie.Element("Type").Element("Docs").Element("remarks");
+ Fix(typeRemarksToFix, f);
+
+ var members = fixie.Element("Type").Element("Members");
+ if (null != members)
+ {
+ foreach (XElement m in members.Elements().
+ Where((XElement e) => ParallelXmlHelper.ParallelElement(e,
+ f,
+ filesToFixDir,
+ filesToUseDir,
+ toUse) != null))
+ {
+ // (2) Fix summary, remarks, return values, parameters, and typeparams
+ XElement summary = m.Element("Docs").Element("summary");
+ Fix(summary, f);
+
+ XElement remarks = m.Element("Docs").Element("remarks");
+ if (null != remarks)
+ Fix(remarks, f);
+
+ XElement returns = m.Element("Docs").Element("returns");
+ if (null != returns)
+ Fix(returns, f);
+
+ if (m.Element("Docs").Elements("param").Any())
+ {
+ IEnumerable<XElement> _params = m.Element("Docs").Elements("param");
+ foreach (XElement p in _params)
+ {
+ Fix(p, f);
+ }
+ }
+
+ if (m.Element("Docs").Elements("typeparam").Any())
+ {
+ IEnumerable<XElement> typeparams = m.Element("Docs").Elements("typeparam");
+ foreach (XElement p in typeparams)
+ {
+ Fix(p, f);
+ }
+ }
+ }
+ }
+
+ if (changed)
+ {
+ CommandUtils.WriteXDocument(fixie, f);
+ }
+ }
+
+ }
+ }
+}
diff --git a/tools/DocStat/DocStat/internalize.cs b/tools/DocStat/DocStat/internalize.cs
new file mode 100644
index 00000000..d02e443f
--- /dev/null
+++ b/tools/DocStat/DocStat/internalize.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using Mono.Options;
+
+namespace DocStat
+{
+ public class InternalizeCommand : ApiCommand
+ {
+
+ public override void Run(IEnumerable<string> args)
+ {
+ string rootdir = "";
+ string omitlist = "";
+ string processlist = "";
+ string pattern = "";
+ List<string> extras = CommandUtils.ProcessFileArgs(args,
+ ref rootdir,
+ ref omitlist,
+ ref processlist,
+ ref pattern
+ );
+
+ string message = "For internal use only.";
+ string sigil = "To be added.";
+ bool nocheck = false;
+ bool nosigil = false;
+
+ var opt = new OptionSet {
+ { "m|message=", (m) => message = m },
+ { "s|sigil=", (s) => sigil = s },
+ { "no-check-browsable", (n) => nocheck = n != null},
+ { "no-check-TBA", (t) => nosigil = t != null }
+ };
+
+ extras = opt.Parse(extras);
+
+ CommandUtils.ThrowOnFiniteExtras(extras);
+
+ Func<XElement, bool> hassigil;
+ Func<XDocument, bool> typehassigil;
+ Func<XElement, bool> qualifies;
+
+ if (nosigil)
+ {
+ // Mark types and members internal, regardless of whether the summaries are filled out
+ hassigil = (x) => true;
+ typehassigil = (x) => true;
+ }
+ else
+ {
+ hassigil = (e) => e.Element("Docs").Element("summary").Value == sigil;
+ typehassigil = (t) => t.Element("Type").Element("Docs").Element("summary").Value == sigil;
+ }
+
+ if (!nocheck)
+ {
+ qualifies = (e) =>
+ {
+ return e.Elements("Attributes")
+ .Any((XElement child) => child.Elements("Attribute")
+ .Any((XElement name) => name.Value.Contains("EditorBrowsableState.Never")))
+ && hassigil(e);
+
+ };
+ }
+ else
+ {
+ qualifies = hassigil;
+ }
+
+ foreach (string file in CommandUtils.GetFileList(processlist, omitlist, rootdir, pattern))
+ {
+ XDocument xdoc = new XDocument(XElement.Load(file));
+ // Find every member that has the internal marker and summary="To be added." (or the provided sigil)
+
+ XElement memberRoot = xdoc.Element("Type").Element("Members");
+ if (memberRoot == null || !memberRoot.Descendants().Any())
+ continue;
+
+ IEnumerable<XElement> hits = memberRoot.Elements("Member").Where(s => qualifies(s));
+
+ foreach (XElement x in hits)
+ {
+ x.Element("Docs").Element("summary").Value = message;
+ }
+
+ if (typehassigil(xdoc))
+ xdoc.Element("Type").Element("Docs").Element("summary").Value = message;
+
+
+ CommandUtils.WriteXDocument(xdoc, file);
+ }
+ }
+ }
+}
diff --git a/tools/DocStat/DocStat/obsolete.cs b/tools/DocStat/DocStat/obsolete.cs
new file mode 100644
index 00000000..86cbd6be
--- /dev/null
+++ b/tools/DocStat/DocStat/obsolete.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using Mono.Options;
+
+namespace DocStat
+{
+ public class ObsoleteCommand : ApiCommand
+ {
+
+ public override void Run(IEnumerable<string> args)
+ {
+ string rootdir = "";
+ string omitlist = "";
+ string processlist = "";
+ string pattern = "";
+
+ List<string> extras = CommandUtils.ProcessFileArgs(args,
+ ref rootdir,
+ ref omitlist,
+ ref processlist,
+ ref pattern);
+ string obsoleteMarker = "System.Obsolete";
+ string sigil = "To be added.";
+ bool skipSigil = false;
+ string message = "Deprecated. Do not use.";
+ var opt = new OptionSet {
+ {"a|attribute",
+ (x) => obsoleteMarker = x },
+ { "s|sigil=", (s) => sigil = s },
+ { "no-check-TBA", (s) => skipSigil = s != null},
+ { "m|message=", (m) => message = m}
+ };
+
+ extras = opt.Parse(extras);
+ CommandUtils.ThrowOnFiniteExtras(extras);
+
+ Func<XElement, bool> sigilCheck;
+ Func<XElement, bool> obsoleteCheck;
+
+ if (skipSigil)
+ {
+ sigilCheck = (e) => true;
+ }
+ else
+ {
+ sigilCheck = (e) => e.Element("Docs").Element("summary").Value == sigil;
+ }
+
+ obsoleteCheck = (e) => e.Elements("Attribute").Any((arg) =>
+ arg.Elements("Attribute").Any((arg2) =>
+ arg2.Value.StartsWith(obsoleteMarker))); ; ;
+
+ foreach (string file in CommandUtils.GetFileList(processlist, omitlist, rootdir, pattern))
+ {
+ // find all the ones that have attributes that start with the provided attribute
+ XDocument xdoc = XDocument.Load(file);
+
+ XElement memberRoot = xdoc.Element("Type").Element("Members");
+ if (memberRoot == null || !memberRoot.Descendants().Any())
+ continue;
+
+ foreach (XElement toMark in memberRoot.Elements("Member")
+ .Where((e) => obsoleteCheck(e) && sigilCheck(e)))
+ {
+ toMark.Element("Docs").Element("summary").Value = message;
+ }
+ CommandUtils.WriteXDocument(xdoc, file);
+ }
+ }
+ }
+}
diff --git a/tools/DocStat/DocStat/remaining.cs b/tools/DocStat/DocStat/remaining.cs
new file mode 100644
index 00000000..5bf9d6b9
--- /dev/null
+++ b/tools/DocStat/DocStat/remaining.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using Mono.Options;
+
+namespace DocStat
+{
+ public class RemainingCommand : ApiCommand
+ {
+
+ public override void Run(IEnumerable<string> args)
+ {
+
+ string rootdir = "";
+ string omitlist = "";
+ string processlist = "";
+ string pattern = "";
+ List<string> extras = CommandUtils.ProcessFileArgs(args,
+ ref rootdir,
+ ref omitlist,
+ ref processlist,
+ ref pattern);
+ string sigil = "To be added.";
+ string outputfile = "";
+
+ var opt = new OptionSet
+ {
+ {"s|sigil=", (s) => sigil = s},
+ {"o|output|ofile=", (o) => outputfile = o.EndsWith(@"csv") ? o : outputfile}
+ };
+
+ extras = opt.Parse(extras);
+
+ if (String.IsNullOrEmpty(outputfile))
+ throw new ArgumentException("You must supply an output file, and it must end with '.csv'");
+
+ CommandUtils.ThrowOnFiniteExtras(extras);
+
+ List<XElement> results = new List<XElement>();
+
+ IEnumerable<string> files = CommandUtils.GetFileList(processlist, omitlist, rootdir, pattern);
+ List<string> fileList = files.ToList();
+ foreach (string file in fileList)
+ {
+ AddResults(file, results, sigil);
+ }
+
+ WriteResults(results, outputfile);
+
+ }
+
+ internal void AddResults(string file, List<XElement> results, string sigil)
+ {
+ // Add results
+ // <QueryResults>
+ // <Type name="..." filename="....">
+ // <Member name="...">
+ // ....
+ // </Type ... >
+ // <Type>
+ // ....
+ // <QueryResults>
+
+ XElement top = XElement.Load(file);
+ if (top.Name == "Type")
+ {
+ // We got a live one!
+ IEnumerable<XElement> qres =
+ from member in top.Descendants("Member")
+ where (string)member.Element("Docs").Element("summary") == sigil
+ select member;
+ List<XElement> le = new List<XElement>(qres);
+ if (le.Any())
+ {
+ string typeName = top.Attribute("FullName").Value;
+ XElement t = new XElement("Type");
+ t.Add(new XAttribute("name", typeName));
+ t.Add(new XAttribute("filename", file));
+ // add member name node for each node
+
+ foreach (XElement m in le)
+ {
+ XElement mres = new XElement("Member");
+ mres.Add(new XAttribute("name", m.Attribute("MemberName").Value));
+ t.Add(mres);
+ }
+
+ results.Add(t);
+ }
+ }
+
+ }
+
+ internal void WriteResults(List<XElement> results, string outputFileName)
+ {
+ if (null == results || results.Count == 0)
+ {
+ return;
+ }
+
+ StreamWriter ofile = new StreamWriter(outputFileName);
+ int typeCount = 0;
+ int memberCount = 0;
+ ofile.WriteLine("Type,Count,File Name,Member");
+
+ string typeFormat = "\"{0}\",\"{1}\",\"{2}\",";
+ string memberFormat = ",,,\"{0}\"";
+ string rollupFormat = "Types:,\"{0}\",Members:,\"{1}\"";
+ foreach (XElement e in results)
+ {
+ //List<XElement> countable = new List<XElement>(e.Elements());
+ ofile.WriteLine(typeFormat,
+ e.Attribute("name").Value,
+ e.Elements().Count(),
+ e.Attribute("filename").Value.Replace("`", "\\`"));
+ typeCount++;
+ foreach (XElement x in e.Elements())
+ {
+ ofile.WriteLine(memberFormat, x.Attribute("name").Value);
+ memberCount++;
+ }
+
+
+ }
+
+ ofile.WriteLine(rollupFormat, typeCount, memberCount);
+ ofile.Flush();
+ ofile.Close();
+ }
+ }
+}