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 Monodoc.Providers;
13 using Lucene.Net.Analysis.Standard;
14 using Lucene.Net.Index;
24 public const int MonodocVersion = 2;
25 const string RootNamespace = "root:/";
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>();
33 public IList<HelpSource> HelpSources {
35 return this.helpSources.AsReadOnly();
39 public DateTime LastHelpSourceTime {
46 int platform = (int)Environment.OSVersion.Platform;
47 return platform == 4 || platform == 128 || platform == 6;
51 RootTree () : base (null, "Mono Documentation", "root:")
53 base.RootNode.EnsureNodes();
54 this.LastHelpSourceTime = DateTime.Now;
57 public static void AddUncompiledSource (string path)
59 uncompiledHelpSourcePaths.Add (path);
62 public static RootTree LoadTree ()
64 return RootTree.LoadTree (RootTree.ProbeBaseDirectories ());
67 static string ProbeBaseDirectories ()
71 result = Config.Get ("docPath") ?? ".";
77 public static RootTree LoadTree (string basedir, bool includeExternal = true)
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));
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");
89 sourceFiles = sourceFiles.Concat (RootTree.ProbeExternalDirectorySources ());
90 return RootTree.LoadTree (basedir, xmlDocument, sourceFiles);
93 static IEnumerable<string> ProbeExternalDirectorySources ()
95 IEnumerable<string> enumerable = Enumerable.Empty<string> ();
97 string path = Config.Get ("docExternalPath");
98 enumerable = enumerable.Concat (System.IO.Directory.EnumerateFiles (path, "*.source"));
102 if (Directory.Exists ("/Library/Frameworks/Mono.framework/External/monodoc"))
103 enumerable = enumerable.Concat (Directory.EnumerateFiles ("/Library/Frameworks/Mono.framework/External/monodoc", "*.source"));
105 var windowsPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "monodoc");
106 if (Directory.Exists (windowsPath))
107 enumerable = enumerable.Concat (Directory.EnumerateFiles (windowsPath, "*.source"));
112 public static RootTree LoadTree (string indexDir, XmlDocument docTree, IEnumerable<string> sourceFiles)
114 if (docTree == null) {
115 docTree = new XmlDocument ();
116 using (Stream manifestResourceStream = typeof (RootTree).Assembly.GetManifestResourceStream ("monodoc.xml")) {
117 docTree.Load (manifestResourceStream);
121 sourceFiles = (sourceFiles ?? new string[0]);
122 RootTree rootTree = new RootTree ();
123 rootTree.basedir = indexDir;
124 XmlNodeList xml_node_list = docTree.SelectNodes ("/node/node");
125 rootTree.nameToNode["root"] = rootTree.RootNode;
126 rootTree.nameToNode["libraries"] = rootTree.RootNode;
127 rootTree.Populate (rootTree.RootNode, xml_node_list);
129 if (rootTree.LookupEntryPoint ("various") == null) {
130 Console.Error.WriteLine ("No 'various' doc node! Check monodoc.xml!");
131 Node rootNode = rootTree.RootNode;
134 foreach (string current in sourceFiles)
135 rootTree.AddSourceFile (current);
137 foreach (string path in uncompiledHelpSourcePaths) {
138 var hs = new Providers.EcmaUncompiledHelpSource (path);
139 hs.RootTree = rootTree;
140 rootTree.helpSources.Add (hs);
141 string epath = "extra-help-source-" + hs.Name;
142 Node hsn = rootTree.RootNode.CreateNode (hs.Name, RootNamespace + epath);
143 rootTree.nameToHelpSource [epath] = hs;
145 foreach (Node n in hs.Tree.RootNode.ChildNodes)
149 RootTree.PurgeNode (rootTree.RootNode);
150 rootTree.RootNode.Sort ();
154 public void AddSource (string sourcesDir)
156 IEnumerable<string> enumerable = Directory.EnumerateFiles (sourcesDir, "*.source");
157 foreach (string current in enumerable)
158 if (!this.AddSourceFile (current))
159 Console.Error.WriteLine ("Error: Could not load source file {0}", current);
162 public bool AddSourceFile (string sourceFile)
164 if (this.loadedSourceFiles.Contains (sourceFile))
167 Node node = this.LookupEntryPoint ("various") ?? base.RootNode;
168 XmlDocument xmlDocument = new XmlDocument ();
170 xmlDocument.Load (sourceFile);
176 XmlNodeList extra_nodes = xmlDocument.SelectNodes ("/monodoc/node");
177 if (extra_nodes.Count > 0)
178 this.Populate (node, extra_nodes);
180 XmlNodeList sources = xmlDocument.SelectNodes ("/monodoc/source");
181 if (sources == null) {
182 Console.Error.WriteLine ("Error: No <source> section found in the {0} file", sourceFile);
186 loadedSourceFiles.Add (sourceFile);
187 foreach (XmlNode xmlNode in sources) {
188 XmlAttribute a = xmlNode.Attributes["provider"];
190 Console.Error.WriteLine ("Error: no provider in <source>");
193 string provider = a.InnerText;
194 a = xmlNode.Attributes["basefile"];
196 Console.Error.WriteLine ("Error: no basefile in <source>");
199 string basefile = a.InnerText;
200 a = xmlNode.Attributes["path"];
202 Console.Error.WriteLine ("Error: no path in <source>");
205 string path = a.InnerText;
206 string basefilepath = Path.Combine (Path.GetDirectoryName (sourceFile), basefile);
207 HelpSource helpSource = RootTree.GetHelpSource (provider, basefilepath);
208 if (helpSource != null) {
209 helpSource.RootTree = this;
210 this.helpSources.Add (helpSource);
211 this.nameToHelpSource[path] = helpSource;
212 Node node2 = this.LookupEntryPoint (path);
214 Console.Error.WriteLine ("node `{0}' is not defined on the documentation map", path);
217 foreach (Node current in helpSource.Tree.RootNode.ChildNodes) {
218 node2.AddNode (current);
226 static bool PurgeNode (Node node)
229 if (!node.Documented)
231 List<Node> list = new List<Node> ();
232 foreach (Node current in node.ChildNodes)
234 bool flag = RootTree.PurgeNode (current);
240 result = (node.ChildNodes.Count == list.Count);
241 foreach (Node current2 in list)
243 node.DeleteNode (current2);
249 public static string[] GetSupportedFormats ()
261 public static HelpSource GetHelpSource (string provider, string basefilepath)
268 result = new XhtmlHelpSource (basefilepath, false);
271 result = new ManHelpSource (basefilepath, false);
274 result = new ErrorHelpSource (basefilepath, false);
277 result = new EcmaSpecHelpSource (basefilepath, false);
280 result = new EcmaHelpSource (basefilepath, false);
283 Console.Error.WriteLine ("Error: Unknown provider specified: {0}", provider);
287 } catch (FileNotFoundException) {
288 Console.Error.WriteLine ("Error: did not find one of the files in sources/" + basefilepath);
294 public static Provider GetProvider (string provider, params string[] basefilepaths)
298 return new EcmaProvider (basefilepaths[0]);
300 return new EcmaSpecProvider (basefilepaths[0]);
302 return new ErrorProvider (basefilepaths[0]);
304 return new ManProvider (basefilepaths);
307 return new XhtmlProvider (basefilepaths[0]);
310 throw new NotSupportedException (provider);
313 void Populate (Node parent, XmlNodeList xml_node_list)
315 foreach (XmlNode xmlNode in xml_node_list) {
316 XmlAttribute e = xmlNode.Attributes["parent"];
318 if (e != null && this.nameToNode.TryGetValue (e.InnerText, out parent2)) {
319 xmlNode.Attributes.Remove (e);
320 Populate (parent2, xmlNode.SelectNodes ("."));
323 e = xmlNode.Attributes["label"];
325 Console.Error.WriteLine ("`label' attribute missing in <node>");
328 string label = e.InnerText;
329 e = xmlNode.Attributes["name"];
331 Console.Error.WriteLine ("`name' attribute missing in <node>");
334 string name = e.InnerText;
335 Node orCreateNode = parent.GetOrCreateNode (label, RootNamespace + name);
336 orCreateNode.EnsureNodes ();
337 this.nameToNode[name] = orCreateNode;
338 XmlNodeList xmlNodeList = xmlNode.SelectNodes ("./node");
339 if (xmlNodeList != null) {
340 this.Populate (orCreateNode, xmlNodeList);
345 public Node LookupEntryPoint (string name)
348 if (!this.nameToNode.TryGetValue (name, out result))
353 public TOutput RenderUrl<TOutput> (string url, IDocGenerator<TOutput> generator, HelpSource hintSource = null)
356 return RenderUrl<TOutput> (url, generator, out dummy, hintSource);
359 public TOutput RenderUrl<TOutput> (string url, IDocGenerator<TOutput> generator, out Node node, HelpSource hintSource = null)
362 string internalId = null;
363 Dictionary<string, string> context = null;
364 HelpSource hs = GetHelpSourceAndIdForUrl (url, hintSource, out internalId, out context, out node);
365 return generator.Generate (hs, internalId, context);
368 public HelpSource GetHelpSourceAndIdForUrl (string url, out string internalId, out Dictionary<string, string> context)
371 return GetHelpSourceAndIdForUrl (url, out internalId, out context, out dummy);
374 public HelpSource GetHelpSourceAndIdForUrl (string url, out string internalId, out Dictionary<string, string> context, out Node node)
376 return GetHelpSourceAndIdForUrl (url, null, out internalId, out context, out node);
379 public HelpSource GetHelpSourceAndIdForUrl (string url, HelpSource hintSource, out string internalId, out Dictionary<string, string> context, out Node node)
385 if (url == "root:") {
386 context = new Dictionary<string, string> { {"specialpage", "master-root"} };
389 // We return the first help source available since the generator will simply fetch this RootTree instance through it
390 return helpSources.FirstOrDefault ();
392 if (url.StartsWith (RootNamespace, StringComparison.OrdinalIgnoreCase)) {
393 context = new Dictionary<string, string> { {"specialpage", "root"} };
394 return GetHelpSourceAndIdFromName (url.Substring (RootNamespace.Length), out internalId, out node);
397 HelpSource helpSource = hintSource;
398 if (helpSource == null || string.IsNullOrEmpty (internalId = helpSource.GetInternalIdForUrl (url, out node, out context))) {
400 foreach (var hs in helpSources.Where (h => h.CanHandleUrl (url))) {
401 if (!string.IsNullOrEmpty (internalId = hs.GetInternalIdForUrl (url, out node, out context))) {
411 public HelpSource GetHelpSourceAndIdFromName (string name, out string internalId, out Node node)
413 internalId = "root:";
414 node = LookupEntryPoint (name);
416 return node == null ? null : node.ChildNodes.Select (n => n.Tree.HelpSource).FirstOrDefault (hs => hs != null);
419 public HelpSource GetHelpSourceFromId (int id)
421 return (id < 0 || id >= this.helpSources.Count) ? null : this.helpSources[id];
424 public Stream GetImage (string url)
426 if (url.StartsWith ("source-id:", StringComparison.OrdinalIgnoreCase)) {
427 string text = url.Substring (10);
428 int num = text.IndexOf (":");
429 string text2 = text.Substring (0, num);
431 if (!int.TryParse (text2, out id)) {
432 Console.Error.WriteLine ("Failed to parse source-id url: {0} `{1}'", url, text2);
435 HelpSource helpSourceFromId = this.GetHelpSourceFromId (id);
436 return helpSourceFromId.GetImage (text.Substring (num + 1));
438 Assembly assembly = Assembly.GetAssembly (typeof (RootTree));
439 return assembly.GetManifestResourceStream (url);
442 public IndexReader GetIndex ()
444 var paths = GetIndexesPathPrefixes ().Select (bp => Path.Combine (bp, "monodoc.index"));
445 var p = paths.FirstOrDefault (File.Exists);
446 return p == null ? (IndexReader)null : IndexReader.Load (p);
449 public static void MakeIndex ()
451 RootTree rootTree = RootTree.LoadTree ();
452 rootTree.GenerateIndex ();
455 public bool GenerateIndex ()
457 IndexMaker indexMaker = new IndexMaker ();
458 foreach (HelpSource current in this.helpSources)
459 current.PopulateIndex (indexMaker);
461 var paths = GetIndexesPathPrefixes ().Select (bp => Path.Combine (bp, "monodoc.index"));
462 bool successful = false;
464 foreach (var path in paths) {
466 indexMaker.Save (path);
469 RootTree.chmod (path, 420);
470 } catch (UnauthorizedAccessException) {
474 Console.WriteLine ("You don't have permissions to write on any of [" + string.Join (", ", paths) + "]");
478 Console.WriteLine ("Documentation index updated");
482 public SearchableIndex GetSearchIndex ()
484 var paths = GetIndexesPathPrefixes ().Select (bp => Path.Combine (bp, "search_index"));
485 var p = paths.FirstOrDefault (Directory.Exists);
486 return p == null ? (SearchableIndex)null : SearchableIndex.Load (p);
489 public static void MakeSearchIndex ()
491 RootTree rootTree = RootTree.LoadTree ();
492 rootTree.GenerateSearchIndex ();
495 public bool GenerateSearchIndex ()
497 Console.WriteLine ("Loading the monodoc tree...");
498 IndexWriter indexWriter = null;
499 var analyzer = new StandardAnalyzer (Lucene.Net.Util.Version.LUCENE_CURRENT);
500 var paths = GetIndexesPathPrefixes ().Select (bp => Path.Combine (bp, "search_index"));
501 bool successful = false;
503 foreach (var path in paths) {
505 if (!Directory.Exists (path))
506 Directory.CreateDirectory (path);
507 var directory = Lucene.Net.Store.FSDirectory.Open (path);
508 indexWriter = new IndexWriter (directory, analyzer, true, IndexWriter.MaxFieldLength.LIMITED);
510 } catch (UnauthorizedAccessException) {}
513 Console.WriteLine ("You don't have permissions to write on any of [" + string.Join (", ", paths) + "]");
516 Console.WriteLine ("Collecting and adding documents...");
517 foreach (HelpSource current in this.helpSources) {
518 current.PopulateSearchableIndex (indexWriter);
520 Console.WriteLine ("Closing...");
521 indexWriter.Optimize ();
522 indexWriter.Close ();
527 static extern int chmod (string filename, int mode);
529 IEnumerable<string> GetIndexesPathPrefixes ()
531 yield return basedir;
532 yield return Config.Get ("docPath");
533 var indexDirectory = Config.Get ("monodocIndexDirectory");
534 if (!string.IsNullOrEmpty (indexDirectory))
535 yield return indexDirectory;
536 yield return Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData), "monodoc");
540 public string GetTitle (string url)
542 return "Mono Documentation";