[Mono-list] A documentation coverage tool

Peter Williams peterw@ximian.com
29 Jan 2003 20:29:16 -0500


--=-ZxrTbsyXWuUBfPCEkBDg
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

Hi Duncan et al,

	In the hopes of getting a better understanding of how monodoc works, I
wrote a little tool that generates a report of how well an assembly is
documented, based on comparing its master XML file to the XML
documentation files on disk. It's not too bright and definitely could
use some love, but it seems to be doing the trick at the moment.

	Thanks,
		Peter


-- 
Peter Williams     peter@newton.cx / peterw@ximian.com

"[Ninjas] are cool; and by cool, I mean totally sweet."
                              -- REAL Ultimate Power

--=-ZxrTbsyXWuUBfPCEkBDg
Content-Disposition: attachment; filename=CoverageReport.cs
Content-Type: text/plain; name=CoverageReport.cs; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit

using System;
using System.Text;
using System.Collections;
using System.IO;
using System.Xml;

namespace MonoDoc {

class CoverageReport {

	string topdir;
	string[] args;
	int classes;
	Hashtable trans;
	Hashtable sizes;

	// this gets called once for each class listed in the
	// master XML file for the assembly

	void DoReport (XmlTextReader r, string ns, string name)
	{
		// otherwise the enumerator gets messed up as we
		// are manipulating the table while iterating over it.
		object[] keys_copy = new object[trans.Keys.Count];
		trans.Keys.CopyTo (keys_copy, 0);

		foreach (string lang in keys_copy) {
			string lpath = Path.Combine (topdir, lang);
			string nspath = Path.Combine (lpath, ns);
			string namepath = Path.Combine (nspath, name + ".xml");

			FileInfo fi = new FileInfo (namepath);
			if (!fi.Exists)
				Append (lang, namepath);
		}

		classes++;
	}

	// I feel like there has to be a better way to do this...

	void Append (string key, string val)
	{
		int sz = (int) sizes[key];
		string[] old = (string[]) trans[key];
		int len = old.Length;

		if (len == sz) {
			string[] new_data = new string[len * 2];
			Array.Copy (old, new_data, len);
			trans[key] = new_data;
		}

		((string[]) trans[key])[sz] = val;
		sizes[key] = sz + 1;
	}

	void Go ()
	{
		if (args.Length != 1) {
			Usage ();
			Environment.Exit (1);
		}

		string file = args[0];

		// list the languages we're going to be looking for

		Console.Write ("Looking for translations to: ");

		topdir = Path.GetDirectoryName (file);
		DirectoryInfo di = new DirectoryInfo (topdir);
		foreach (DirectoryInfo a in di.GetDirectories()) {
			if (a.Name != "CVS") {
				Console.Write ("{0} ", a.Name);
		
				// This feels really lame. Is there
				// no better way? 
				trans[a.Name] = new string[1];
				sizes[a.Name] = 0;
			}
		}
		Console.WriteLine();
		Console.WriteLine();

		// Now parse the master file to find out what classes
		// are actually there.

		Console.Write ("Parsing {0} ... ", file);	
		try {
			MasterParser mp = new MasterParser (file);
			mp.Process (new MasterParser.ClassCallback (DoReport));
		} catch (Exception e) {
			Console.WriteLine ("Error trying to process {0}: {1}",
				file, e.Message);
		}
		Console.WriteLine ("ok");

		// now print what's missing

		foreach (string lang in trans.Keys) {
			Console.WriteLine();

			string[] missing = (string[]) trans[lang];

			Console.WriteLine ("{0}: coverage is {1} / {2} = {3:F2}%", 
				lang, classes - missing.Length, classes,
				(float) (classes - missing.Length) * 100.0 / classes );

			Console.WriteLine ("{0}: missing files are:", lang);

			for (int i = 0; i < (int) sizes[lang]; i++)
				Console.WriteLine ("   {0}", missing[i]);
		}
	}

	///////////////////////////////////////////
	// Housekeeping

	void Usage ()
	{
		Console.WriteLine ("CoverageReport.exe [path to master xml file]");
	}

	CoverageReport (string[] args)
	{
		this.args = new string[args.Length];
		args.CopyTo (this.args, 0);

		trans = new Hashtable ();
		sizes = new Hashtable ();

		classes = 0;
	}

	static void Main (string[] args)
	{
		CoverageReport cr = new CoverageReport (args);
		cr.Go();
	}
}

}

--=-ZxrTbsyXWuUBfPCEkBDg
Content-Disposition: attachment; filename=MasterParser.cs
Content-Type: text/plain; name=MasterParser.cs; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit

using System;
using System.Text;
using System.IO;
using System.Xml;

namespace MonoDoc {

public class MasterParser {
	private FileStream master_stream;
	private XmlTextReader r;

	public delegate void ClassCallback (XmlTextReader r, string ns, string name);

	public MasterParser (string master_file)
	{
		master_stream = File.OpenRead (master_file);
		r = new XmlTextReader (master_stream);
	}

	public void Process (ClassCallback cb)
	{
		bool yay = false;

		while (r.Read()) {
			if (r.Name == "masterdoc") {
				yay = true;
				ProcessClasses (cb);
			}				
		}

		r.Close();
		master_stream.Close();

		if (yay == false)
			throw new Exception ("Didn't find <masterdoc> element");
	}

	private void ProcessClasses (ClassCallback cb)
	{
		while (r.Read()) {
			if (r.Name == "class") {
				string name = r["name"];
				string ns = r["namespace"];

				// We get fed closing tags too, or something...
				if (name == "" || ns == "")
					continue;

				cb (r, ns, name);
			}
		}
	}
}
}

--=-ZxrTbsyXWuUBfPCEkBDg
Content-Disposition: attachment; filename=makefile
Content-Type: text/x-makefile; name=makefile; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit

MCS=mcs
MCS_FLAGS=/debug
RUNTIME=mono

all: CoverageReport.exe

CoverageReport.exe: CoverageReport.cs MasterParser.cs
	$(MCS) /target:exe /out:$@ $(MCS_FLAGS) /r:System.Xml $^

clean:
	rm -f *.exe *.dbg

--=-ZxrTbsyXWuUBfPCEkBDg--