2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Collections.Specialized;
5 using System.Configuration;
8 using System.Reflection;
9 using System.Runtime.InteropServices;
12 using MonkeyDoc.Providers;
13 using Mono.Lucene.Net.Analysis.Standard;
14 using Mono.Lucene.Net.Index;
18 public class RootTree : Tree
20 public const int MonodocVersion = 2;
21 const string RootNamespace = "root:/";
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>();
29 public IList<HelpSource> HelpSources {
31 return this.helpSources.AsReadOnly();
35 public DateTime LastHelpSourceTime {
42 int platform = (int)Environment.OSVersion.Platform;
43 return platform == 4 || platform == 128 || platform == 6;
47 RootTree () : base (null, "Mono Documentation", "root:")
49 base.RootNode.EnsureNodes();
50 this.LastHelpSourceTime = DateTime.Now;
53 public static RootTree LoadTree ()
55 return RootTree.LoadTree (RootTree.ProbeBaseDirectories ());
58 static string ProbeBaseDirectories ()
62 NameValueCollection appSettings = ConfigurationManager.AppSettings;
63 result = appSettings["docPath"];
70 public static RootTree LoadTree (string basedir, bool includeExternal = true)
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));
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");
82 sourceFiles = sourceFiles.Concat (RootTree.ProbeExternalDirectorySources ());
83 return RootTree.LoadTree (basedir, xmlDocument, sourceFiles);
86 static IEnumerable<string> ProbeExternalDirectorySources ()
88 IEnumerable<string> enumerable = Enumerable.Empty<string> ();
90 string path = ConfigurationManager.AppSettings["docExternalPath"];
91 enumerable = enumerable.Concat (System.IO.Directory.EnumerateFiles (path, "*.source"));
95 if (Directory.Exists ("/Library/Frameworks/Mono.framework/External/monodoc"))
96 enumerable = enumerable.Concat (Directory.EnumerateFiles ("/Library/Frameworks/Mono.framework/External/monodoc", "*.source"));
100 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable<string> sourceFiles)
102 if (docTree == null) {
103 docTree = new XmlDocument ();
104 using (Stream manifestResourceStream = typeof (RootTree).Assembly.GetManifestResourceStream ("monodoc.xml")) {
105 docTree.Load (manifestResourceStream);
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);
117 if (rootTree.LookupEntryPoint ("various") == null) {
118 Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!");
119 Node rootNode = rootTree.RootNode;
122 foreach (string current in sourceFiles)
123 rootTree.AddSourceFile (current);
125 RootTree.PurgeNode (rootTree.RootNode);
126 rootTree.RootNode.Sort ();
130 public void AddSource (string sourcesDir)
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);
138 public bool AddSourceFile (string sourceFile)
140 if (this.loadedSourceFiles.Contains (sourceFile))
143 Node node = this.LookupEntryPoint ("various") ?? base.RootNode;
144 XmlDocument xmlDocument = new XmlDocument ();
146 xmlDocument.Load (sourceFile);
152 XmlNodeList extra_nodes = xmlDocument.SelectNodes ("/monodoc/node");
153 if (extra_nodes.Count > 0)
154 this.Populate (node, extra_nodes);
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);
162 loadedSourceFiles.Add (sourceFile);
163 foreach (XmlNode xmlNode in sources) {
164 XmlAttribute a = xmlNode.Attributes["provider"];
166 Console.Error.WriteLine ("Error: no provider in <source>");
169 string provider = a.InnerText;
170 a = xmlNode.Attributes["basefile"];
172 Console.Error.WriteLine ("Error: no basefile in <source>");
175 string basefile = a.InnerText;
176 a = xmlNode.Attributes["path"];
178 Console.Error.WriteLine ("Error: no path in <source>");
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);
190 Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path);
193 foreach (Node current in helpSource.Tree.RootNode.Nodes) {
194 node2.AddNode (current);
202 static bool PurgeNode (Node node)
205 if (!node.Documented)
207 List<Node> list = new List<Node> ();
208 foreach (Node current in node.Nodes)
210 bool flag = RootTree.PurgeNode (current);
216 result = (node.Nodes.Count == list.Count);
217 foreach (Node current2 in list)
219 node.DeleteNode (current2);
225 public static string[] GetSupportedFormats ()
237 public static HelpSource GetHelpSource (string provider, string basefilepath)
244 result = new XhtmlHelpSource (basefilepath, false);
247 result = new ManHelpSource (basefilepath, false);
250 result = new ErrorHelpSource (basefilepath, false);
253 result = new EcmaSpecHelpSource (basefilepath, false);
256 result = new EcmaHelpSource (basefilepath, false);
259 Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider);
263 } catch (FileNotFoundException) {
264 Console.Error.WriteLine ("Error: did not find one of the files in sources/" + basefilepath);
270 public static Provider GetProvider (string provider, params string[] basefilepaths)
274 return new EcmaProvider (basefilepaths[0]);
276 return new EcmaSpecProvider (basefilepaths[0]);
278 return new ErrorProvider (basefilepaths[0]);
280 return new ManProvider (basefilepaths);
283 return new XhtmlProvider (basefilepaths[0]);
286 throw new NotSupportedException (provider);
289 void Populate (Node parent, XmlNodeList xml_node_list)
291 foreach (XmlNode xmlNode in xml_node_list) {
292 XmlAttribute e = xmlNode.Attributes["parent"];
294 if (e != null && this.nameToNode.TryGetValue (e.InnerText, out parent2)) {
295 xmlNode.Attributes.Remove (e);
296 Populate (parent2, xmlNode.SelectNodes ("."));
299 e = xmlNode.Attributes["label"];
301 Console.Error.WriteLine ("`label' attribute missing in <node>");
304 string label = e.InnerText;
305 e = xmlNode.Attributes["name"];
307 Console.Error.WriteLine ("`name' attribute missing in <node>");
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);
321 public Node LookupEntryPoint (string name)
324 if (!this.nameToNode.TryGetValue (name, out result))
331 public TOutput RenderUrl<TOutput> (string url, IDocGenerator<TOutput> generator, out Node node)
334 string internalId = null;
335 HelpSource hs = GetHelpSourceAndIdForUrl (url, out internalId, out node);
336 return generator.Generate (hs, internalId);
339 public HelpSource GetHelpSourceAndIdForUrl (string url, out string internalId, out Node node)
344 if (url.StartsWith ("root:/"))
345 return this.GetHelpSourceAndIdFromName (url.Substring ("root:/".Length), out internalId, out node);
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))) {
358 public HelpSource GetHelpSourceAndIdFromName (string name, out string internalId, out Node node)
360 internalId = "root:";
361 node = this.LookupEntryPoint (name);
363 return node == null ? null : node.Nodes.Select (n => n.Tree.HelpSource).Where (hs => hs != null).Distinct ().FirstOrDefault ();
366 public HelpSource GetHelpSourceFromId (int id)
368 return (id < 0 || id >= this.helpSources.Count) ? null : this.helpSources[id];
371 public Stream GetImage (string url)
373 if (url.StartsWith ("source-id:")) {
374 string text = url.Substring (10);
375 int num = text.IndexOf (":");
376 string text2 = text.Substring (0, num);
379 id = int.Parse (text2);
381 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, text2);
384 HelpSource helpSourceFromId = this.GetHelpSourceFromId (id);
385 return helpSourceFromId.GetImage (text.Substring (num + 1));
387 Assembly assembly = Assembly.GetAssembly (typeof (RootTree));
388 return assembly.GetManifestResourceStream (url);
391 public IndexReader GetIndex ()
393 string text = Path.Combine (this.basedir, "monodoc.index");
394 if (File.Exists (text))
396 return IndexReader.Load (text);
398 text = Path.Combine (ConfigurationManager.AppSettings["monodocIndexDirectory"], "monodoc.index");
399 return IndexReader.Load (text);
402 public static void MakeIndex ()
404 RootTree rootTree = RootTree.LoadTree ();
405 rootTree.GenerateIndex ();
408 public void GenerateIndex ()
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");
415 indexMaker.Save (text);
416 } catch (UnauthorizedAccessException) {
417 text = Path.Combine (ConfigurationManager.AppSettings["docDir"], "monodoc.index");
419 indexMaker.Save (text);
420 } catch (UnauthorizedAccessException) {
421 Console.WriteLine ("Unable to write index file in {0}", Path.Combine (ConfigurationManager.AppSettings["docDir"], "monodoc.index"));
426 RootTree.chmod (text, 420);
428 Console.WriteLine ("Documentation index at {0} updated", text);
431 public SearchableIndex GetSearchIndex ()
433 string text = Path.Combine (this.basedir, "search_index");
434 if (System.IO.Directory.Exists (text)) {
435 return SearchableIndex.Load (text);
437 text = Path.Combine (ConfigurationManager.AppSettings["docDir"], "search_index");
438 return SearchableIndex.Load (text);
441 public static void MakeSearchIndex ()
443 RootTree rootTree = RootTree.LoadTree ();
444 rootTree.GenerateSearchIndex ();
447 public void GenerateSearchIndex ()
449 Console.WriteLine ("Loading the monodoc tree...");
450 string text = Path.Combine (this.basedir, "search_index");
451 IndexWriter indexWriter;
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) {
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);
467 Console.WriteLine ("Collecting and adding documents...");
468 foreach (HelpSource current in this.helpSources) {
469 current.PopulateSearchableIndex (indexWriter);
471 Console.WriteLine ("Closing...");
472 indexWriter.Optimize ();
473 indexWriter.Close ();
477 static extern int chmod (string filename, int mode);