[netcore] Remove local copy of static alc resolve methods
[mono-project.git] / mcs / tools / resx2sr / resx2sr.cs
blobc75dfe9f4d4cdb139b5329bbae5027b8077a56de
1 //
2 // resx2sr.cs
3 //
4 // Authors:
5 // Marek Safar <marek.safar@gmail.com>
6 //
7 // Copyright (C) 2016 Xamarin Inc (http://www.xamarin.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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
30 * When making any changes to this tool, please be aware that these are not
31 * automatically tested by a Jenkins job, so you need to do that manually.
33 * You need to do the following:
35 * 1.) Build the tool with the `build` profile
36 * $ make PROFILE=build -C mcs/tools/resx2sr
38 * 2.) Update the SR in corlib:
39 * $ make -C mcs/class/corlib update-corefx-sr
41 * 3.) Look at the output by doing a `git diff` and also building the code.
43 * 4.) Search for 'RESX_RESOURCE_STRING' and `RESX_EXTRA_ARGUMENTS` in all BCL Makefiles:
44 * $ git grep RESX_RESOURCE_STRING mcs/class
45 * $ git grep 'RESX_EXTRA_ARGUMENTS' mcs/class
47 * 5.) As a minimum, repeat steps 2.) and 3.) for each directory containing `RESX_EXTRA_ARGUMENTS`.
48 * (at the moment, this is only corlib, but please check with `git grep` to make sure).
50 * 6.) To test the tool in `--existing` mode, check out `System.Web.Services`, you can do something
51 * like this:
53 * RESX_EXTRA_ARGUMENTS = --in=corefx/SR.template.cs --name=System.Web.Services.Res --existing
54 * RESX_RESOURCE_STRING = ../referencesource/System.Web.Services/System.Web.Services.txt
56 * If you have any questions about the process, please ask me and I'll be glad to help.
58 * December 7th, 2018
59 * Martin Baulig (mabaul@microsoft.com)
63 using System;
64 using System.IO;
65 using System.Collections.Generic;
66 using System.Resources;
67 using System.ComponentModel.Design;
68 using System.Text.RegularExpressions;
69 using Mono.Options;
71 public class Program
73 class CmdOptions
75 public bool ShowHelp { get; set; }
76 public string OutputFile { get; set; }
77 public bool ExistingOnly { get; set; }
78 public bool WarnConstantMismatch { get; set; }
81 internal static List<string> InputFiles;
82 internal static Dictionary<string, object> ExistingKeys;
84 public static int Main (string[] args)
86 var options = new CmdOptions ();
88 InputFiles = new List<string> ();
89 ExistingKeys = new Dictionary<string, object> ();
91 string className = "SR";
93 var p = new OptionSet () {
94 { "o|out=", "Specifies output file name",
95 v => options.OutputFile = v },
96 { "i|in=", "Specifies input file name",
97 v => InputFiles.Add (v) },
98 { "n|name=", "Name for generated class, default is 'SR'",
99 v => className = v },
100 { "h|help", "Display available options",
101 v => options.ShowHelp = v != null },
102 { "e|existing", "Only update existing values, do not add keys",
103 v => options.ExistingOnly = true
104 }, { "warn-mismatch", "Warn about constant mismatches",
105 v => options.WarnConstantMismatch = true
109 List<string> extra;
110 try {
111 extra = p.Parse (args);
112 } catch (OptionException e) {
113 Console.WriteLine (e.Message);
114 Console.WriteLine ("Try 'resx2sr -help' for more information.");
115 return 1;
118 if (options.ShowHelp) {
119 ShowHelp (p);
120 return 0;
123 if (extra.Count < 1) {
124 ShowHelp (p);
125 return 2;
128 if (!LoadInputFiles ())
129 return 4;
131 var resxStrings = new List<Tuple<string, string, string>> ();
132 if (!LoadStrings (resxStrings, extra))
133 return 3;
135 GenerateFile (className, resxStrings, options);
137 return 0;
140 static void ShowHelp (OptionSet p)
142 Console.WriteLine ("Usage: resx2sr [options] input-files");
143 Console.WriteLine ("Generates C# file with string constants from resource file");
144 Console.WriteLine ();
145 Console.WriteLine ("Options:");
146 p.WriteOptionDescriptions (Console.Out);
149 static void GenerateFile (string className, List<Tuple<string, string, string>> txtStrings, CmdOptions options)
151 // var outputFile = options.OutputFile ?? "SR.cs";
153 using (var str = options.OutputFile == null ? Console.Out : new StreamWriter (options.OutputFile)) {
154 str.WriteLine ("//");
155 str.WriteLine ("// This file was generated by resx2sr tool");
156 str.WriteLine ("//");
157 str.WriteLine ();
159 int nsIdx = className.LastIndexOf ('.');
160 if (nsIdx > 0) {
161 str.WriteLine ($"namespace {className.Substring (0, nsIdx)}");
162 str.WriteLine ("{");
163 className = className.Substring (nsIdx+1);
166 str.WriteLine ($"partial class {className}");
167 str.WriteLine ("{");
169 var dict = new Dictionary<string, string> ();
171 foreach (var entry in txtStrings) {
173 var value = ToCSharpString (entry.Item2);
174 string found;
175 if (dict.TryGetValue (entry.Item1, out found)) {
176 if (found == value || !options.WarnConstantMismatch)
177 continue;
179 // The same entry was found with different values in multiple input files.
180 Console.Error.WriteLine ($"Constant value mismatch for {entry.Item1}:\n\tOld: {found}\n\tNew: {value}");
181 continue;
184 // Always add to list of seen keys.
185 dict.Add (entry.Item1, value);
187 // The following conditional could be simplified to one line, but I belive
188 // it is easier to understand what it does by writing it verbosely like this.
189 if (options.ExistingOnly) {
190 // We read in all entries, then update the strings of the existing ones,
191 // so skip the non-existing ones.
192 if (!ExistingKeys.ContainsKey (entry.Item1))
193 continue;
194 } else {
195 // In this mode of operation, we pass some existing files via `--in=`,
196 // so we skip the existing ones.
197 if (ExistingKeys.ContainsKey (entry.Item1))
198 continue;
201 str.Write ($"\tpublic const string {entry.Item1} = \"{value}\";");
203 if (!string.IsNullOrEmpty (entry.Item3))
204 str.Write (" // {entry.Item3}");
206 str.WriteLine ();
209 if (options.ExistingOnly) {
210 // Add any keys that we missed from the input files.
211 foreach (var v in ExistingKeys.Keys) {
212 if (!dict.ContainsKey (v)) {
213 str.WriteLine ($"\tinternal const string {v} = \"{v}\";");
218 str.WriteLine ("}");
220 if (nsIdx > 0) {
221 str.WriteLine ("}");
226 static string ToCSharpString (string str)
228 str = str.Replace ("\n", "\\n");
230 return str.Replace ("\\", "\\\\").Replace ("\"", "\\\"");
233 static bool LoadStrings (List<Tuple<string, string, string>> resourcesStrings, List<string> files)
235 var keys = new Dictionary<string, string> ();
236 foreach (var fileName in files) {
237 if (!File.Exists (fileName)) {
238 Console.Error.WriteLine ($"Error reading resource file '{fileName}'");
239 return false;
242 if (string.Equals (Path.GetExtension (fileName), ".txt", StringComparison.OrdinalIgnoreCase)) {
243 resourcesStrings.AddRange (ReadTextResources (fileName));
244 } else {
245 resourcesStrings.AddRange (ReadResxFile (fileName));
249 return true;
252 static IEnumerable<Tuple<string,string,string>> ReadResxFile (string fileName)
254 var rr = new ResXResourceReader (fileName);
255 rr.UseResXDataNodes = true;
256 var dict = rr.GetEnumerator ();
257 while (dict.MoveNext ()) {
258 var node = (ResXDataNode)dict.Value;
259 yield return Tuple.Create (node.Name, (string) node.GetValue ((ITypeResolutionService)null), node.Comment);
263 static IEnumerable<Tuple<string,string,string>> ReadTextResources (string fileName)
265 foreach (var line in File.ReadAllLines (fileName)) {
266 if (line.Length == 0 || line[0] == ';') {
267 continue;
269 var idx = line.IndexOf ('=');
270 if (idx < 1) {
271 Console.Error.WriteLine ($"Error reading resource file '{fileName}'");
272 continue;
274 yield return Tuple.Create (line.Substring (0, idx), line.Substring (idx+1), (string)null);
278 static bool LoadInputFiles ()
280 var reg = new Regex (@"\s*public const string (\w+)\s+=\s+");
281 var keys = new Dictionary<string, string> ();
282 foreach (var fileName in InputFiles) {
283 if (!File.Exists (fileName)) {
284 Console.Error.WriteLine ($"Error reading input file '{fileName}'");
285 return false;
288 using (var reader = new StreamReader (fileName)) {
289 string line;
290 while ((line = reader.ReadLine ()) != null) {
291 var match = reg.Match (line);
292 if (!match.Success)
293 continue;
295 var key = match.Groups[1].Value;
296 ExistingKeys[key] = null;
301 return true;