diff options
author | Mike Norman <mike.norman@xamarin.com> | 2017-05-31 21:26:44 +0300 |
---|---|---|
committer | Joel Martinez <joelmartinez@gmail.com> | 2017-06-02 18:42:11 +0300 |
commit | 239cf594ccb55e47128e02bfc3c3753ad1f0ca9c (patch) | |
tree | ed97ce7efeb17f902c54b6ac731935ce9a619425 /tools | |
parent | fb070470b334d2830556405694c9bb02fd8f4a1c (diff) |
Tool to report on and bulk update ECMAXML files
Diffstat (limited to 'tools')
-rw-r--r-- | tools/DocStat/DocStat.sln | 17 | ||||
-rw-r--r-- | tools/DocStat/DocStat/CommandUtils.cs | 203 | ||||
-rw-r--r-- | tools/DocStat/DocStat/DocStat.csproj | 47 | ||||
-rw-r--r-- | tools/DocStat/DocStat/ParallelXmlHelper.cs | 136 | ||||
-rw-r--r-- | tools/DocStat/DocStat/apistat.cs | 80 | ||||
-rw-r--r-- | tools/DocStat/DocStat/comparefix.cs | 137 | ||||
-rw-r--r-- | tools/DocStat/DocStat/internalize.cs | 97 | ||||
-rw-r--r-- | tools/DocStat/DocStat/obsolete.cs | 73 | ||||
-rw-r--r-- | tools/DocStat/DocStat/remaining.cs | 133 |
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(); + } + } +} |