2 // The main differences with mono-api-diff are:
3 // * this tool directly produce HTML similar to gdiff.sh used for Xamarin.iOS
4 // * this tool reports changes in an "evolutionary" way, not in a breaking way,
5 // i.e. it does not assume the source assembly is right (but simply older)
6 // * the diff .xml output was not easy to convert back into the HTML format
7 // that gdiff.sh produced
10 // Sebastien Pouliot <sebastien@xamarin.com>
12 // Copyright 2013-2014 Xamarin Inc. http://www.xamarin.com
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using System.Collections.Generic;
37 using System.Text.RegularExpressions;
41 namespace Xamarin.ApiDiff {
43 public static class State {
44 static TextWriter output;
46 public static TextWriter Output {
52 set { output = value; }
55 public static string Assembly { get; set; }
56 public static string Namespace { get; set; }
57 public static string Type { get; set; }
58 public static string BaseType { get; set; }
60 public static int Indent { get; set; }
62 static List<Regex> ignoreAdded = new List<Regex> ();
63 public static List<Regex> IgnoreAdded {
64 get { return ignoreAdded; }
67 static List<Regex> ignoreNew = new List<Regex> ();
68 public static List<Regex> IgnoreNew {
69 get { return ignoreNew; }
72 static List<Regex> ignoreRemoved = new List<Regex> ();
73 public static List<Regex> IgnoreRemoved {
74 get { return ignoreRemoved; }
77 public static bool IgnoreParameterNameChanges { get; set; }
78 public static bool IgnoreVirtualChanges { get; set; }
79 public static bool IgnoreAddedPropertySetters { get; set; }
81 public static bool IgnoreNonbreaking { get; set; }
83 public static bool Lax;
84 public static bool Colorize = true;
88 public static int Main (string[] args)
92 List<string> extra = null;
94 var options = new OptionSet {
95 { "h|help", "Show this help", v => showHelp = true },
96 { "d|diff=", "HTML diff file out output (omit for stdout)", v => diff = v },
97 { "i|ignore=", "Ignore new, added, and removed members whose description matches a given C# regular expression (see below).",
99 var r = new Regex (v);
100 State.IgnoreAdded.Add (r);
101 State.IgnoreRemoved.Add (r);
102 State.IgnoreNew.Add (r);
105 { "a|ignore-added=", "Ignore added members whose description matches a given C# regular expression (see below).",
106 v => State.IgnoreAdded.Add (new Regex (v))
108 { "r|ignore-removed=", "Ignore removed members whose description matches a given C# regular expression (see below).",
109 v => State.IgnoreRemoved.Add (new Regex (v))
111 { "n|ignore-new=", "Ignore new namespaces and types whose description matches a given C# regular expression (see below).",
112 v => State.IgnoreNew.Add (new Regex (v))
114 { "ignore-changes-parameter-names", "Ignore changes to parameter names for identically prototyped methods.",
115 v => State.IgnoreParameterNameChanges = v != null
117 { "ignore-changes-property-setters", "Ignore adding setters to properties.",
118 v => State.IgnoreAddedPropertySetters = v != null
120 { "ignore-changes-virtual", "Ignore changing non-`virtual` to `virtual` or adding `override`.",
121 v => State.IgnoreVirtualChanges = v != null
123 { "c|colorize:", "Colorize HTML output", v => State.Colorize = string.IsNullOrEmpty (v) ? true : bool.Parse (v) },
124 { "x|lax", "Ignore duplicate XML entries", v => State.Lax = true },
125 { "ignore-nonbreaking", "Ignore all nonbreaking changes", v => State.IgnoreNonbreaking = true }
129 extra = options.Parse (args);
130 } catch (OptionException e) {
131 Console.WriteLine ("Option error: {0}", e.Message);
135 if (State.IgnoreNonbreaking) {
136 State.IgnoreAddedPropertySetters = true;
137 State.IgnoreVirtualChanges = true;
138 State.IgnoreNew.Add (new Regex (".*"));
139 State.IgnoreAdded.Add (new Regex (".*"));
142 if (showHelp || extra == null || extra.Count < 2 || extra.Count > 3) {
143 Console.WriteLine (@"Usage: mono-api-html [options] <reference.xml> <assembly.xml> [diff.html]");
144 Console.WriteLine ();
145 Console.WriteLine ("Available options:");
146 options.WriteOptionDescriptions (Console.Out);
147 Console.WriteLine ();
148 Console.WriteLine ("Ignoring Members:");
149 Console.WriteLine ();
150 Console.WriteLine (" Members that were added can be filtered out of the diff by using the");
151 Console.WriteLine (" -i, --ignore-added option. The option takes a C# regular expression");
152 Console.WriteLine (" to match against member descriptions. For example, to ignore the");
153 Console.WriteLine (" introduction of the interfaces 'INSCopying' and 'INSCoding' on types");
154 Console.WriteLine (" pass the following to mono-api-html:");
155 Console.WriteLine ();
156 Console.WriteLine (" mono-api-html ... -i 'INSCopying$' -i 'INSCoding$'");
157 Console.WriteLine ();
158 Console.WriteLine (" The regular expressions will match any member description ending with");
159 Console.WriteLine (" 'INSCopying' or 'INSCoding'.");
160 Console.WriteLine ();
164 var input = extra [0];
165 var output = extra [1];
166 if (extra.Count == 3 && diff == null)
170 var ac = new AssemblyComparer (input, output);
172 string diffHtml = String.Empty;
173 using (var writer = new StringWriter ()) {
174 State.Output = writer;
176 diffHtml = State.Output.ToString ();
178 if (diffHtml.Length > 0) {
179 using (var file = new StreamWriter (diff)) {
180 file.WriteLine ("<div>");
181 if (State.Colorize) {
182 file.WriteLine ("<style scoped>");
183 file.WriteLine ("\t.obsolete { color: gray; }");
184 file.WriteLine ("\t.added { color: green; }");
185 file.WriteLine ("\t.removed-inline { text-decoration: line-through; }");
186 file.WriteLine ("\t.removed-breaking-inline { color: red;}");
187 file.WriteLine ("\t.added-breaking-inline { text-decoration: underline; }");
188 file.WriteLine ("\t.nonbreaking { color: black; }");
189 file.WriteLine ("\t.breaking { color: red; }");
190 file.WriteLine ("</style>");
193 @"<script type=""text/javascript"">
194 // Only some elements have 'data-is-[non-]breaking' attributes. Here we
195 // iterate over all descendents elements, and set 'data-is-[non-]breaking'
196 // depending on whether there are any descendents with that attribute.
197 function propagateDataAttribute (element)
199 if (element.hasAttribute ('data-is-propagated'))
203 var any_breaking = element.hasAttribute ('data-is-breaking');
204 var any_non_breaking = element.hasAttribute ('data-is-non-breaking');
205 for (i = 0; i < element.children.length; i++) {
206 var el = element.children [i];
207 propagateDataAttribute (el);
208 any_breaking |= el.hasAttribute ('data-is-breaking');
209 any_non_breaking |= el.hasAttribute ('data-is-non-breaking');
213 element.setAttribute ('data-is-breaking', null);
214 else if (any_non_breaking)
215 element.setAttribute ('data-is-non-breaking', null);
216 element.setAttribute ('data-is-propagated', null);
219 function hideNonBreakingChanges ()
221 var topNodes = document.querySelectorAll ('[data-is-topmost]');
224 for (n = 0; n < topNodes.length; n++) {
225 propagateDataAttribute (topNodes [n]);
226 var elements = topNodes [n].querySelectorAll ('[data-is-non-breaking]');
227 for (i = 0; i < elements.length; i++) {
228 var el = elements [i];
229 if (!el.hasAttribute ('data-original-display'))
230 el.setAttribute ('data-original-display', el.style.display);
231 el.style.display = 'none';
235 var links = document.getElementsByClassName ('hide-nonbreaking');
236 for (i = 0; i < links.length; i++)
237 links [i].style.display = 'none';
238 links = document.getElementsByClassName ('restore-nonbreaking');
239 for (i = 0; i < links.length; i++)
240 links [i].style.display = '';
243 function showNonBreakingChanges ()
245 var elements = document.querySelectorAll ('[data-original-display]');
247 for (i = 0; i < elements.length; i++) {
248 var el = elements [i];
249 el.style.display = el.getAttribute ('data-original-display');
252 var links = document.getElementsByClassName ('hide-nonbreaking');
253 for (i = 0; i < links.length; i++)
254 links [i].style.display = '';
255 links = document.getElementsByClassName ('restore-nonbreaking');
256 for (i = 0; i < links.length; i++)
257 links [i].style.display = 'none';
260 if (ac.SourceAssembly == ac.TargetAssembly) {
261 file.WriteLine ("<h1>{0}.dll</h1>", ac.SourceAssembly);
263 file.WriteLine ("<h1>{0}.dll vs {1}.dll</h1>", ac.SourceAssembly, ac.TargetAssembly);
265 if (!State.IgnoreNonbreaking) {
266 file.WriteLine ("<a href='javascript: hideNonBreakingChanges (); ' class='hide-nonbreaking'>Hide non-breaking changes</a>");
267 file.WriteLine ("<a href='javascript: showNonBreakingChanges (); ' class='restore-nonbreaking' style='display: none;'>Show non-breaking changes</a>");
268 file.WriteLine ("<br/>");
270 file.WriteLine ("<div data-is-topmost>");
271 file.Write (diffHtml);
272 file.WriteLine ("</div> <!-- end topmost div -->");
273 file.WriteLine ("</div>");
277 State.Output = Console.Out;
281 catch (Exception e) {
282 Console.WriteLine (e);