Wstęp
Czy pamiętacie jeszcze Visual Studio 2003 i jego bardzo przydatną funkcjonalność, dzięki której można było generować dokumentację projektu w formacie HTML. Podejrzewam, że wielu z Was pamięta i podobnie jak mi, brakuje Wam tego w nowszych wersjach Visual Studio.
Ostatnio potrzebowałem takiej funkcjonalności, aby udokumentować bibliotekę klas, nad którą pracowałem i niestety nie udało mi się znaleźć odpowiedniego narzędzia. Jestem świadom istnienia projektów NDoc oraz Sandcastle, jednakże pierwszy z nich już od dawna nie jest rozwijany i nie wspiera .NET 2.0 a drugi jest niepotrzebnie skomplikowany w użyciu i niewiarygodnie wolny. Postanowiłem więc poświęcić trochę czasu na opracowanie własnego narzędzia, licząc na to, że przy okazji nauczę się czegoś nowego oraz że projekt będzie użyteczny nie tylko dla mnie, ale również dla innych programistów.
Poniżej zamieszczam listę obszarów, w których musiałem poszerzyć swoją wiedzę, żeby ukończyć ten projekt. Dzięki temu możesz ocenić, czy analiza kodu źródłowego może nauczyć również Ciebie czegoś nowego:
- mechanizmy refleksji,
- niuanse języków C# i MSIL (Microsoft Intermediate Language),
- wyrażenia regularne,
- składnia plików z komentarzami XML generowanych przez kompilator,
- czytanie plików XML oraz ich walidacja za pomocą schematów XSD,
- osadzane zasoby.
Przykład
Zanim zagłębicie się w szczegóły techniczne projektu ImmDoc.NET, być może zechcecie wpierw odwiedzić tę stronę, aby zobaczyć przykładową dokumentację wygenerowaną przez to narzędzie. Jest to dokumentacja wygenerowana na podstawie kilku, przypadkowo wybranych zestawów pochodzących z frameworka .NET: System.Runtime.Remoting, System.Security i System.Transactions.
Użycie
Obecnie jest tylko jeden sposób uruchamiania ImmDoca — z wiersza poleceń. Jest to po części spowodowane tym, że stworzenie GUI zajęłoby trochę czasu. Jednak dzięki takiemu podejściu łatwo jest zautomatyzować generowanie dokumentacji poprzez skrypty wsadowe albo narzędzia wspierające proces budowania projektów, jak np. NAnt.
Korzystanie z ImmDoca jest całkiem proste i intuicyjne. Możesz wszystkie swoje zestawy .NET i pliki z komentarzami XML umieścić w jednym folderze razem z plikiem wykonywalnym ImmDocNet.exe. Gdy uruchomisz program, rozpocznie się przetwarzanie plików wejściowych i po chwili powinieneś zobaczyć folder doc zawierający wygenerowaną dokumentację. Jeżeli chciałbyś, żeby ImmDoc pominął jakieś pliki, możesz to zrobić, korzystając z opcji wiersza poleceń. Wszystkie tego typu opcje wymieniam poniżej:
-
-pn, -ProjectName:STRING- użyj tej opcji, jeśli chcesz nadać swojej dokumentacji jakąś nazwę -
-ex, -Exclude:FILE- jeśli jawnie nie podasz w wierszu poleceń listy plików do przetwarzania, możesz użyć tej opcji w celu pominięcia określonych plików z bieżącego folderu -
-od, -OutputDirectory:DIR- użyj tej opcji, aby określić nazwę folderu, gdzie wygenerowana dokumentacja zostanie zapisana -
-fd, -ForceDelete- ImmDoc.NET sam nie usunie folderu wyjściowego, jeśli już istnieje, chyba, że użyjesz tej opcji -
-vl, -VerboseLevel:LEVEL- opcja ta pozwala Ci zdecydować, jak wiele komunikatów program będzie wyświetlał; im wyższa wartość, tym więcej informacji na wyjściu; LEVEL może przyjmować wartości od 0 (brak informacji) do 3 (maksymalna ilość informacji)
Jest jeszcze jedna funkcjonalność, o której, tak sądzę, warto wspomnieć. Ze względu na to, że nie da się umieszczać komentarzy XML do przestrzeni nazw, a także możesz chcieć wygenerować dokumentację dla wielu zestawów jednocześnie, ImmDoc pozwala Ci dostarczyć takie dodatkowe komentarze. W tym celu musisz utworzyć plik XML z rozszerzeniem .docs. Składnia tego pliku jest bardzo prosta, ale zwróć uwagę, że będzie on walidowany za pomocą schematu XSD — AdditionalDocumentation.xsd — który znajduje się w archiwum ZIP z kodem źródłowym projektu. Jak już stworzysz taki plik, wystarczy, że umieścisz go w tym samym folderze co zestawy i pliki XML z komentarzami, które chcesz przetworzyć albo jawnie podasz jego nazwę w wierszu poleceń. Przykład:
<?xml version="1.0" encoding="utf-8"?>
<summaries>
<assembly name="SomeAssembly">
<summary>
Tutaj jest opis zestawu.
</summary>
<namespace name="Some.Namespace">
<summary>
Tutaj jest opis przestrzeni nazw.
</summary>
</namespace>
...
</assembly>
...
</summaries>
Projekt
ImmDoc.NET składa się z dwóch zestawów, które na końcu procesu budowania, łączone są za pomocą narzędzia ILMerge. Dzięki temu cały program zamknięty jest w jednym pliku EXE. Pierwszym ze wspomnianych zestawów jest ImmDocNetLib, który zajmuje się wszystkim tym, co związane jest z analizą i przetwarzaniem przekazanych zestawów i plików XML z komentarzami. Drugi zestaw to ImmDocNet, który jest aplikacją konsolową korzystającą z ImmDocNetLib i stanowiącą interfejs użytkownika.
Jak już wspomniałem, ImmDocNetLib posiada dwie główne odpowiedzialności: analizuje przekazane pliki wejściowe oraz generuje dokumentację. Przyjrzyjmy się bliżej pierwszemu z tych zadań, który w zasadzie sprowadza się do skorzystania z refleksji w celu zebrania szczegółowych informacji na temat zestawów i modułów, które te zestawy zawierają.
Najważniejsze klasy znajdują się w przestrzeni nazw Imm.ImmDocNetLib.MyReflection.MetaClasses. W uproszczeniu można powiedzieć, że klasy te są lżejszymi odpowiednikami klas z przestrzeni nazw System.Reflection. Generalnie, praktycznie wszystkie z nich dziedziczą po klasie MetaClas, która, między innymi, zawiera takie właściwości jak Name, Summary and Remarks. Inne klasy dodatkowo zawierają właściwości specyficzne dla encji, którą opisują. Przykładowo, klasa MyMethodInfo przechowuje informacje na temat typu zwracanego przez określoną metodę a także jej parametry. W ImmDoc.NET tego typu informacje są często trzymane w innych klasach dziedziczących z MetaClass, jak np.: MyParameterInfo, MyFieldInfo itp.
W pierwszym kroku przetwarzania informacje zebrane za pomocą standardowych mechanizmów refleksji łączone są z komentarzami wyciągniętymi z plików XML, aby utworzyć wewnętrzną reprezentację analizowanych zestawów. Poniżej zamieszczam diagram UML, który powinien dać Ci ogólny pogląd na tę reprezentację. Zwróć uwagę, że na diagramie nie ma wszystkich klas, żeby nie zaciemniać samej idei:
Po zebraniu wszystkich potrzebnych informacji, nadchodzi w końcu czas na rozpoczęcie generowania dokumentacji. Używane do tego celu klasy znajdują się w przestrzeni nazw Imm.ImmDocNetLib.Documenters. Znajdziemy tam prostą klasę abstrakcyjną Documenter, z której możemy wyprowadzić klasy potomne implementujące generowanie dokumentacji w określonym formacie. Ja zaimplementowałem klasę HTMLDocumenter, która tworzy zbiór plików HTML, CSS i JavaScript, które wspólnie składają się na dokumentację zestawów przypominającą tę znaną nam wszystkim z MSDNa.
HTMLDocumenter jest raczej sporą klasą, więc gdyby ktoś chciał ponownie wykorzystać część jej funkcjonalności przy implementacji własnego generatora dokumentacji w innym formacie, wskazana by była jakaś wysokopoziomowa refaktoryzacja. Ogólne spojrzenie na odpowiedzialności klasy HTMLDocumenter powinien dać Ci poniższy diagram, na którym umieściłem sygnatury kilku metod tejże klasy.
Poniżej krótkie opisy powyższych metod:
bool GenerateDocumentation( string outputDirectory, DocumentationGenerationOptions options)
Inicjuje proces generowania dokumentacji. Wywołuje takie metody jak PrepareOutputDirectory(), GenerateMainIndex(), ExtractStyleSheets(), ProcessAssemblies(), GenerateTableOfContents() itp.
void CreateInvokableMembersOverloadsIndex(
MyInvokableMembersOverloadsInfo myInvokableMembersOverloadsInfo,
MyClassInfo declaringType,
string dirName)
Ta metoda tworzy stronę HTML, która będzie zawierała indeks wszystkich przeciążeń określonej metody bądź konstruktora.
string CreateNamespaceMemberSyntaxString(MyClassInfo namespaceMember)
Ta metoda odpowiedzialna jest za tworzenie napisu z deklaracją jakiegoś typu. Taki napis, przykładowo, składa się z modyfikatora widoczności, klasy bazowej, implementowanych interfejsów, więzów nałożonych na parametry generyczne itp.
void ExtractBinaryResourceToFile(string resourceName, string fileName)
Generowane strony HTML korzystają z kilku plików graficznych. Ta metoda zajmuje się wyciąganiem osadzonych zasobów i zapisywaniem ich do plików.
string ProcessComment(string contents)
Ta metoda przetwarza każdy komentarz znaleziony w przekazanych plikach XML w celu zastąpienia znaczników takich jak <code>, <c>, <see> itd. odpowiednimi znacznikami HTML.
void WriteIndexHeader( StreamWriter sw, string pageTitle, string[] sectionsNamesAndIndices)
Ta metoda wypisuje standardowy nagłówek HTML wykorzystywany przez prawie wszystkie wygenerowane strony.
string ResolveLink(MetaClass metaClass)
Użyteczność dokumentacji elektronicznych w dużej mierze zależy od możliwości nawigacji pomiędzy poszczególnymi stronami. Ta metoda pomaga podczas generowania odnośników, na przykład do określonej składowej klasy.
Podsumowanie
Mam nadzieję, że ImmDoc.NET komuś się przyda. Ja sam korzystam z niego często, aby bezboleśnie i szybko generować dokumentację dla projektów, które tworzę. Chętnie dowiem się, co o nim sądzicie i czy jest z Waszej strony jakieś zainteresowanie, żeby zdecydować, czy warto go rozwijać ;) Wciąż brakuje mu kilku pomniejszych funkcjonalności; również bardziej przyjazny dla użytkownika interfejs fajnie by było mieć. Tak więc, jeśli macie jakieś sugestie, pytania albo być może znaleźliście jakiś błąd, będę wdzięczny za kontakt. Mój adres e-mail to: marek.stoj@gmail.com. Strona domowa projektu ImmDoc.NET utrzymywana jest w serwisie CodePlex — to właśnie stamtąd możecie pobrać zarówno sam program, jak i jego pełny kod źródłowy.