Merge pull request #409 from Alkarex/patch-1
[mono.git] / mcs / tools / monkeydoc / Monkeydoc / RootTree.cs
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Collections.Specialized;
5 using System.Configuration;
6 using System.IO;
7 using System.Linq;
8 using System.Reflection;
9 using System.Runtime.InteropServices;
10 using System.Xml;
11
12 using MonkeyDoc.Providers;
13 using Mono.Lucene.Net.Analysis.Standard;
14 using Mono.Lucene.Net.Index;
15
16 namespace MonkeyDoc
17 {
18         public class RootTree : Tree
19         {
20                 public const int MonodocVersion = 2;
21                 const string RootNamespace = "root:/";
22                 string basedir;
23                 List<string> uncompiledHelpSourcePaths = new List<string>();
24                 HashSet<string> loadedSourceFiles = new HashSet<string>();
25                 List<HelpSource> helpSources = new List<HelpSource>();
26                 Dictionary<string, Node> nameToNode = new Dictionary<string, Node>();
27                 Dictionary<string, HelpSource> nameToHelpSource = new Dictionary<string, HelpSource>();
28
29                 public IList<HelpSource> HelpSources {
30                         get {
31                                 return this.helpSources.AsReadOnly();
32                         }
33                 }
34
35                 public DateTime LastHelpSourceTime {
36                         get;
37                         set;
38                 }
39
40                 static bool IsUnix {
41                         get {
42                                 int platform = (int)Environment.OSVersion.Platform;
43                                 return platform == 4 || platform == 128 || platform == 6;
44                         }
45                 }
46
47                 RootTree () : base (null, "Mono Documentation", "root:")
48                 {
49                         base.RootNode.EnsureNodes();
50                         this.LastHelpSourceTime = DateTime.Now;
51                 }
52
53                 public static RootTree LoadTree ()
54                 {
55                         return RootTree.LoadTree (RootTree.ProbeBaseDirectories ());
56                 }
57
58                 static string ProbeBaseDirectories ()
59                 {
60                         string result;
61                         try {
62                                 NameValueCollection appSettings = ConfigurationManager.AppSettings;
63                                 result = appSettings["docPath"];
64                         } catch {
65                                 result = ".";
66                         }
67                         return result;
68                 }
69
70                 public static RootTree LoadTree (string basedir, bool includeExternal = true)
71                 {
72                         if (string.IsNullOrEmpty (basedir))
73                                 throw new ArgumentNullException ("basedir");
74                         if (!Directory.Exists (basedir))
75                                 throw new ArgumentException ("basedir", string.Format ("Base documentation directory at '{0}' doesn't exist", basedir));
76
77                         XmlDocument xmlDocument = new XmlDocument ();
78                         string filename = Path.Combine (basedir, "monodoc.xml");
79                         xmlDocument.Load (filename);
80                         IEnumerable<string> sourceFiles = Directory.EnumerateFiles (Path.Combine (basedir, "sources"), "*.source");
81                         if (includeExternal)
82                                 sourceFiles = sourceFiles.Concat (RootTree.ProbeExternalDirectorySources ());
83                         return RootTree.LoadTree (basedir, xmlDocument, sourceFiles);
84                 }
85
86                 static IEnumerable<string> ProbeExternalDirectorySources ()
87                 {
88                         IEnumerable<string> enumerable = Enumerable.Empty<string> ();
89                         try {
90                                 string path = ConfigurationManager.AppSettings["docExternalPath"];
91                                 enumerable = enumerable.Concat (System.IO.Directory.EnumerateFiles (path, "*.source"));
92                         }
93                         catch {}
94
95                         if (Directory.Exists ("/Library/Frameworks/Mono.framework/External/monodoc"))
96                                 enumerable = enumerable.Concat (Directory.EnumerateFiles ("/Library/Frameworks/Mono.framework/External/monodoc", "*.source"));
97                         return enumerable;
98                 }
99
100                 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable<string> sourceFiles)
101                 {
102                         if (docTree == null) {
103                                 docTree = new XmlDocument ();
104                                 using  (Stream manifestResourceStream = typeof (RootTree).Assembly.GetManifestResourceStream ("monodoc.xml")) {
105                                         docTree.Load (manifestResourceStream);
106                                 }
107                         }
108
109                         sourceFiles =  (sourceFiles ?? new string[0]);
110                         RootTree rootTree = new RootTree ();
111                         rootTree.basedir = indexDir;
112                         XmlNodeList xml_node_list = docTree.SelectNodes ("/node/node");
113                         rootTree.nameToNode["root"] = rootTree.RootNode;
114                         rootTree.nameToNode["libraries"] = rootTree.RootNode;
115                         rootTree.Populate (rootTree.RootNode, xml_node_list);
116
117                         if (rootTree.LookupEntryPoint ("various") == null) {
118                                 Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!");
119                                 Node rootNode = rootTree.RootNode;
120                         }
121
122                         foreach (string current in sourceFiles)
123                                 rootTree.AddSourceFile (current);
124
125                         RootTree.PurgeNode (rootTree.RootNode);
126                         rootTree.RootNode.Sort ();
127                         return rootTree;
128                 }
129
130                 public void AddSource (string sourcesDir)
131                 {
132                         IEnumerable<string> enumerable = Directory.EnumerateFiles (sourcesDir, "*.source");
133                         foreach (string current in enumerable)
134                                 if (!this.AddSourceFile (current))
135                                         Console.Error.WriteLine ("Error: Could not load source file {0}", current);
136                 }
137
138                 public bool AddSourceFile (string sourceFile)
139                 {
140                         if (this.loadedSourceFiles.Contains (sourceFile))
141                                 return false;
142
143                         Node node = this.LookupEntryPoint ("various") ?? base.RootNode;
144                         XmlDocument xmlDocument = new XmlDocument ();
145                         try {
146                                 xmlDocument.Load (sourceFile);
147                         } catch {
148                                 bool result = false;
149                                 return result;
150                         }
151
152                         XmlNodeList extra_nodes = xmlDocument.SelectNodes ("/monodoc/node");
153                         if (extra_nodes.Count > 0)
154                                 this.Populate (node, extra_nodes);
155
156                         XmlNodeList sources = xmlDocument.SelectNodes ("/monodoc/source");
157                         if (sources == null) {
158                                 Console.Error.WriteLine ("Error: No <source> section found in the {0} file", sourceFile);
159                                 return false;
160                         }
161
162                         loadedSourceFiles.Add (sourceFile);
163                         foreach (XmlNode xmlNode in sources) {
164                                 XmlAttribute a = xmlNode.Attributes["provider"];
165                                 if (a == null) {
166                                         Console.Error.WriteLine ("Error: no provider in <source>");
167                                         continue;
168                                 }
169                                 string provider = a.InnerText;
170                                 a = xmlNode.Attributes["basefile"];
171                                 if (a == null) {
172                                         Console.Error.WriteLine ("Error: no basefile in <source>");
173                                         continue;
174                                 }
175                                 string basefile = a.InnerText;
176                                 a = xmlNode.Attributes["path"];
177                                 if (a == null) {
178                                         Console.Error.WriteLine ("Error: no path in <source>");
179                                         continue;
180                                 }
181                                 string path = a.InnerText;
182                                 string basefilepath = Path.Combine (Path.GetDirectoryName (sourceFile), basefile);
183                                 HelpSource helpSource = RootTree.GetHelpSource (provider, basefilepath);
184                                 if (helpSource != null) {
185                                         helpSource.RootTree = this;
186                                         this.helpSources.Add (helpSource);
187                                         this.nameToHelpSource[path] = helpSource;
188                                         Node node2 = this.LookupEntryPoint (path);
189                                         if (node2 == null) {
190                                                 Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path);
191                                                 node2 = node;
192                                         }
193                                         foreach (Node current in helpSource.Tree.RootNode.Nodes) {
194                                                 node2.AddNode (current);
195                                         }
196                                         node2.Sort ();
197                                 }
198                         }
199                         return true;
200                 }
201
202                 static bool PurgeNode (Node node)
203                 {
204                         bool result = false;
205                         if (!node.Documented)
206                         {
207                                 List<Node> list = new List<Node> ();
208                                 foreach (Node current in node.Nodes)
209                                 {
210                                         bool flag = RootTree.PurgeNode (current);
211                                         if (flag)
212                                         {
213                                                 list.Add (current);
214                                         }
215                                 }
216                                 result =  (node.Nodes.Count == list.Count);
217                                 foreach (Node current2 in list)
218                                 {
219                                         node.DeleteNode (current2);
220                                 }
221                         }
222                         return result;
223                 }
224
225                 public static string[] GetSupportedFormats ()
226                 {
227                         return new string[]
228                         {
229                                 "ecma",
230                                 "ecmaspec",
231                                 "error",
232                                 "man",
233                                 "xhtml"
234                         };
235                 }
236
237                 public static HelpSource GetHelpSource (string provider, string basefilepath)
238                 {
239                         HelpSource result;
240                         try {
241                                 switch (provider) {
242                                 case "xhtml":
243                                 case "hb":
244                                         result = new XhtmlHelpSource (basefilepath, false);
245                                         break;
246                                 case "man":
247                                         result = new ManHelpSource (basefilepath, false);
248                                         break;
249                                 case "error":
250                                         result = new ErrorHelpSource (basefilepath, false);
251                                         break;
252                                 case "ecmaspec":
253                                         result = new EcmaSpecHelpSource (basefilepath, false);
254                                         break;
255                                 case "ecma":
256                                         result = new EcmaHelpSource (basefilepath, false);
257                                         break;
258                                 default:
259                                         Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider);
260                                         result = null;
261                                         break;
262                                 }
263                         } catch (FileNotFoundException) {
264                                 Console.Error.WriteLine ("Error: did not find one of the files in sources/" + basefilepath);
265                                 result = null;
266                         }
267                         return result;
268                 }
269
270                 public static Provider GetProvider (string provider, params string[] basefilepaths)
271                 {
272                         switch (provider) {
273                         case "ecma":
274                                 return new EcmaProvider (basefilepaths[0]);
275                         case "ecmaspec":
276                                 return new EcmaSpecProvider (basefilepaths[0]);
277                         case "error":
278                                 return new ErrorProvider (basefilepaths[0]);
279                         case "man":
280                                 return new ManProvider (basefilepaths);
281                         case "xhml":
282                         case "hb":
283                                 return new XhtmlProvider (basefilepaths[0]);
284                         }
285
286                         throw new NotSupportedException (provider);
287                 }
288
289                 void Populate (Node parent, XmlNodeList xml_node_list)
290                 {
291                         foreach (XmlNode xmlNode in xml_node_list) {
292                                 XmlAttribute e = xmlNode.Attributes["parent"];
293                                 Node parent2 = null;
294                                 if (e != null && this.nameToNode.TryGetValue (e.InnerText, out parent2)) {
295                                         xmlNode.Attributes.Remove (e);
296                                         Populate (parent2, xmlNode.SelectNodes ("."));
297                                         continue;
298                                 }
299                                 e = xmlNode.Attributes["label"];
300                                 if (e == null) {
301                                         Console.Error.WriteLine ("`label' attribute missing in <node>");
302                                         continue;
303                                 }
304                                 string label = e.InnerText;
305                                 e = xmlNode.Attributes["name"];
306                                 if (e == null) {
307                                         Console.Error.WriteLine ("`name' attribute missing in <node>");
308                                         continue;
309                                 }
310                                 string name = e.InnerText;
311                                 Node orCreateNode = parent.GetOrCreateNode (label, "root:/" + name);
312                                 orCreateNode.EnsureNodes ();
313                                 this.nameToNode[name] = orCreateNode;
314                                 XmlNodeList xmlNodeList = xmlNode.SelectNodes ("./node");
315                                 if (xmlNodeList != null) {
316                                         this.Populate (orCreateNode, xmlNodeList);
317                                 }
318                         }
319                 }
320
321                 public Node LookupEntryPoint (string name)
322                 {
323                         Node result = null;
324                         if (!this.nameToNode.TryGetValue (name, out result))
325                         {
326                                 result = null;
327                         }
328                         return result;
329                 }
330
331                 public TOutput RenderUrl<TOutput> (string url, IDocGenerator<TOutput> generator, out Node node)
332                 {
333                         node = null;
334                         string internalId = null;
335                         HelpSource hs = GetHelpSourceAndIdForUrl (url, out internalId, out node);
336                         return generator.Generate (hs, internalId);
337                 }
338
339                 public HelpSource GetHelpSourceAndIdForUrl (string url, out string internalId, out Node node)
340                 {
341                         node = null;
342                         internalId = null;
343
344                         if (url.StartsWith ("root:/"))
345                                 return this.GetHelpSourceAndIdFromName (url.Substring ("root:/".Length), out internalId, out node);
346
347                         HelpSource helpSource = null;
348                         foreach (var hs in helpSources.Where (h => h.CanHandleUrl (url))) {
349                                 if (!string.IsNullOrEmpty (internalId = hs.GetInternalIdForUrl (url, out node))) {
350                                         helpSource = hs;
351                                         break;
352                                 }
353                         }
354
355                         return helpSource;
356                 }
357
358                 public HelpSource GetHelpSourceAndIdFromName (string name, out string internalId, out Node node)
359                 {
360                         internalId = "root:";
361                         node = this.LookupEntryPoint (name);
362
363                         return node == null ? null : node.Nodes.Select (n => n.Tree.HelpSource).Where (hs => hs != null).Distinct ().FirstOrDefault ();
364                 }
365
366                 public HelpSource GetHelpSourceFromId (int id)
367                 {
368                         return  (id < 0 || id >= this.helpSources.Count) ? null : this.helpSources[id];
369                 }
370
371                 public Stream GetImage (string url)
372                 {
373                         if (url.StartsWith ("source-id:")) {
374                                 string text = url.Substring (10);
375                                 int num = text.IndexOf (":");
376                                 string text2 = text.Substring (0, num);
377                                 int id = 0;
378                                 try {
379                                         id = int.Parse (text2);
380                                 } catch {
381                                         Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, text2);
382                                         return null;
383                                 }
384                                 HelpSource helpSourceFromId = this.GetHelpSourceFromId (id);
385                                 return helpSourceFromId.GetImage (text.Substring (num + 1));
386                         }
387                         Assembly assembly = Assembly.GetAssembly (typeof (RootTree));
388                         return assembly.GetManifestResourceStream (url);
389                 }
390
391                 public IndexReader GetIndex ()
392                 {
393                         string text = Path.Combine (this.basedir, "monodoc.index");
394                         if (File.Exists (text))
395                         {
396                                 return IndexReader.Load (text);
397                         }
398                         text = Path.Combine (ConfigurationManager.AppSettings["monodocIndexDirectory"], "monodoc.index");
399                         return IndexReader.Load (text);
400                 }
401
402                 public static void MakeIndex ()
403                 {
404                         RootTree rootTree = RootTree.LoadTree ();
405                         rootTree.GenerateIndex ();
406                 }
407
408                 public void GenerateIndex ()
409                 {
410                         IndexMaker indexMaker = new IndexMaker ();
411                         foreach (HelpSource current in this.helpSources)
412                                 current.PopulateIndex (indexMaker);
413                         string text = Path.Combine (this.basedir, "monodoc.index");
414                         try {
415                                 indexMaker.Save (text);
416                         } catch (UnauthorizedAccessException) {
417                                 text = Path.Combine (ConfigurationManager.AppSettings["docDir"], "monodoc.index");
418                                 try {
419                                         indexMaker.Save (text);
420                                 } catch (UnauthorizedAccessException) {
421                                         Console.WriteLine ("Unable to write index file in {0}", Path.Combine (ConfigurationManager.AppSettings["docDir"], "monodoc.index"));
422                                         return;
423                                 }
424                         }
425                         if (RootTree.IsUnix)
426                                 RootTree.chmod (text, 420);
427
428                         Console.WriteLine ("Documentation index at {0} updated", text);
429                 }
430
431                 public SearchableIndex GetSearchIndex ()
432                 {
433                         string text = Path.Combine (this.basedir, "search_index");
434                         if (System.IO.Directory.Exists (text)) {
435                                 return SearchableIndex.Load (text);
436                         }
437                         text = Path.Combine (ConfigurationManager.AppSettings["docDir"], "search_index");
438                         return SearchableIndex.Load (text);
439                 }
440
441                 public static void MakeSearchIndex ()
442                 {
443                         RootTree rootTree = RootTree.LoadTree ();
444                         rootTree.GenerateSearchIndex ();
445                 }
446
447                 public void GenerateSearchIndex ()
448                 {
449                         Console.WriteLine ("Loading the monodoc tree...");
450                         string text = Path.Combine (this.basedir, "search_index");
451                         IndexWriter indexWriter;
452                         try {
453                                 if (!Directory.Exists (text))
454                                         Directory.CreateDirectory (text);
455                                 indexWriter = new IndexWriter (Mono.Lucene.Net.Store.FSDirectory.GetDirectory (text, true), new StandardAnalyzer (), true);
456                         } catch (UnauthorizedAccessException) {
457                                 try {
458                                         text = Path.Combine (ConfigurationManager.AppSettings["docDir"], "search_index");
459                                         if (!Directory.Exists (text))
460                                                 Directory.CreateDirectory (text);
461                                         indexWriter = new IndexWriter (Mono.Lucene.Net.Store.FSDirectory.GetDirectory (text, true), new StandardAnalyzer (), true);
462                                 } catch (UnauthorizedAccessException) {
463                                         Console.WriteLine ("You don't have permissions to write on " + text);
464                                         return;
465                                 }
466                         }
467                         Console.WriteLine ("Collecting and adding documents...");
468                         foreach (HelpSource current in this.helpSources) {
469                                 current.PopulateSearchableIndex (indexWriter);
470                         }
471                         Console.WriteLine ("Closing...");
472                         indexWriter.Optimize ();
473                         indexWriter.Close ();
474                 }
475
476                 [DllImport ("libc")]
477                 static extern int chmod (string filename, int mode);
478         }
479 }