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