Merge branch 'master' of https://github.com/XTZGZoReX/mono into XTZGZoReX-master
[mono.git] / mcs / tools / mdoc / Mono.Documentation / webdoc.cs
1 //
2 // webdoc.cs
3 //
4 // Author:
5 //   Jonathan Pryor  <jpryor@novell.com>
6 //
7 // Copyright (c) 2009 Novell, Inc. (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.Collections.Generic;
31 using System.Diagnostics;
32 using System.IO;
33 using System.Linq;
34 using System.Text;
35 using System.Web;
36 using System.Xml.Linq;
37
38 using Monodoc;
39 using Mono.Documentation;
40
41 using Mono.Options;
42 using Mono.Rocks;
43
44 using ICSharpCode.SharpZipLib.Zip;
45
46 namespace Mono.Documentation
47 {
48         public class MDocExportWebdocHtml : MDocCommand
49         {
50                 class Options {
51                         public Dictionary<string, List<string>> Formats = new Dictionary<string, List<string>>();
52                         public List<string> Sources = new List<string>();
53                         public bool UseSystemSources = true;
54                         public bool ForceUpdate = false;
55                         public string OutputDirectory = null;
56                 }
57
58                 public override void Run (IEnumerable<string> args)
59                 {
60                         var opts = new Options ();
61                         var formatOptions = MDocAssembler.CreateFormatOptions (this, opts.Formats);
62                         var options = new OptionSet () {
63                                 { "force-update",
64                                         "Always generate new files.  If not specified, will only generate " +
65                                         "files if the write time of the output directory is older than the " +
66                                         "write time of the source .tree/.zip files.",
67                                         v => opts.ForceUpdate = v != null },
68                                 formatOptions [0],
69                                 formatOptions [1],
70                                 { "o|out=",
71                                         "The {PREFIX} to place the generated files and directories.  " + 
72                                         "Default: \"`dirname FILE`/cache/\".\n" +
73                                         "Underneath {PREFIX}, `basename FILE .tree` directories will be " + 
74                                         "created which will contain the pre-generated HTML content.",
75                                         v => opts.OutputDirectory = v },
76                                 { "r=",
77                                         "A {SOURCE} file to use for reference purposes.\n" +
78                                         "Extension methods are searched for among all {SOURCE}s which are referenced.\n" +
79                                         "This option may be specified multiple times.",
80                                         v => opts.Sources.Add (v) },
81                                 { "use-system-sources",
82                                         "Use the system-wide .source files for reference purposes. " +
83                                         "Default is " + (opts.UseSystemSources ? "enabled" : "disabled") + ".",
84                                         v => opts.UseSystemSources = v != null },
85                         };
86                         Parse (options, args, "export-html-webdoc", 
87                                         "[OPTIONS]+ FILES",
88                                         "Export mdoc documentation within FILES to HTML for use by ASP.NET webdoc.\n\n" +
89                                         "FILES are .tree or .zip files as produced by 'mdoc assemble', or .source files\n" +
90                                         "which reference .tree and .zip files produced by 'mdoc assemble'.\n\n" + 
91                                         "See mdoc(5) or mdoc-assemble(1) for information about the .source file format.");
92                         if (opts.Formats.Values.All (files => files.Count == 0))
93                                 Error ("No files specified.");
94                         ProcessSources (opts);
95                         HelpSource.use_css = true;
96                         HelpSource.FullHtml = false;
97                         SettingsHandler.Settings.EnableEditing = false;
98                         foreach (var p in opts.Formats)
99                                 ProcessFiles (opts, p.Key, p.Value);
100                 }
101
102                 void ProcessSources (Options opts)
103                 {
104                         foreach (var p in opts.Formats) {
105                                 var files = p.Value;
106                                 foreach (var f in files.Where (f => f.EndsWith (".source")).ToList ()) {
107                                         files.Remove (f);
108                                         foreach (var tfi in GetTreeFilesFromSource (f)) {
109                                                 List<string> treeFiles;
110                                                 if (!opts.Formats.TryGetValue (tfi.Key, out treeFiles))
111                                                         opts.Formats.Add (tfi.Key, treeFiles = new List<string> ());
112                                                 treeFiles.Add (tfi.Value);
113                                         }
114                                 }
115                         }
116                 }
117
118                 IEnumerable<KeyValuePair<string, string>> GetTreeFilesFromSource (string sourceFile)
119                 {
120                         try {
121                                 var source = XElement.Load (sourceFile);
122                                 return source.Descendants ("source")
123                                         .Select (e => new KeyValuePair<string, string>(e.Attribute ("provider").Value, 
124                                                                 Path.Combine (Path.GetDirectoryName (sourceFile), e.Attribute ("basefile").Value + ".tree")));
125                         }
126                         catch (Exception e) {
127                                 Message (TraceLevel.Error, "mdoc: error parsing file {0}: {1}", sourceFile, e.Message);
128                                 return new KeyValuePair<string, string>[0];
129                         }
130                 }
131
132                 void ProcessFiles (Options opts, string format, List<string> files)
133                 {
134                         foreach (var basePath in 
135                                         files.Select (f => 
136                                                         Path.Combine (Path.GetDirectoryName (f), Path.GetFileNameWithoutExtension (f)))
137                                         .Distinct ()) {
138                                 string treeFile = basePath + ".tree";
139                                 string zipFile  = basePath + ".zip";
140                                 if (!Exists (treeFile) || !Exists (zipFile))
141                                         continue;
142                                 string outDir = opts.OutputDirectory != null 
143                                         ? Path.Combine (opts.OutputDirectory, Path.GetFileName (basePath))
144                                         : XmlDocUtils.GetCacheDirectory (basePath);
145                                 if (!opts.ForceUpdate && Directory.Exists (outDir) &&
146                                                         MaxWriteTime (treeFile, zipFile) < Directory.GetLastWriteTime (outDir))
147                                         continue;
148                                 Message (TraceLevel.Warning, "Processing files: {0}, {1}", treeFile, zipFile);
149                                 Directory.CreateDirectory (outDir);
150                                 ExtractZipFile (zipFile, outDir);
151                                 GenerateCache (opts, basePath, format, outDir);
152                         }
153                 }
154
155                 bool Exists (string file)
156                 {
157                         if (!File.Exists (file)) {
158                                         Message (TraceLevel.Error,
159                                                         "mdoc: Could not find file: {0}", file);
160                                         return false;
161                         }
162                         return true;
163                 }
164
165                 DateTime MaxWriteTime (params string[] files)
166                 {
167                         return files.Select (f => File.GetLastWriteTime (f)).Max ();
168                 }
169
170                 void ExtractZipFile (string zipFile, string outDir)
171                 {
172                         ZipInputStream zip = new ZipInputStream (File.OpenRead (zipFile));
173
174                         ZipEntry entry;
175                         while ((entry = zip.GetNextEntry ()) != null) {
176                                 string file = Path.Combine (outDir, entry.Name);
177                                 Directory.CreateDirectory (Path.GetDirectoryName (file));
178                                 using (var output = File.OpenWrite (file))
179                                         zip.WriteTo (output);
180                         }
181                 }
182
183                 void GenerateCache (Options opts, string basePath, string format, string outDir)
184                 {
185                         var hs = RootTree.GetHelpSource (format, basePath);
186                         if (hs == null) {
187                                 Error ("Unable to find a HelpSource for provider '{0}' and file '{1}.tree'.", format, basePath);
188                         }
189                         var tree = hs.Tree;
190                         RootTree docRoot = null;
191                         if (!opts.UseSystemSources)
192                                 docRoot = RootTree.LoadTree (null, null, opts.Sources);
193                         else {
194                                 docRoot = RootTree.LoadTree ();
195                                 foreach (var source in opts.Sources)
196                                         docRoot.AddSourceFile (source);
197                         }
198                         hs.RootTree = docRoot;
199                         foreach (Node node in tree.TraverseDepthFirst<Node, Node> (t => t, t => t.Nodes.Cast<Node> ())) {
200                                 var url = node.URL;
201                                 Message (TraceLevel.Info, "\tProcessing URL: {0}", url);
202                                 if (string.IsNullOrEmpty (url))
203                                         continue;
204                                 var file = XmlDocUtils.GetCachedFileName (outDir, url);
205                                 using (var o = File.AppendText (file)) {
206                                         Node _;
207                                         string contents = hs.GetText (url, out _) ?? hs.RenderNamespaceLookup (url, out _);
208                                         o.Write (contents);
209                                 }
210                         }
211                 }
212         }
213 }