* **/* (svn:ignore): Ignore generated files and directories.
[mono.git] / mcs / class / System.Data.Linq / src / DbMetal / AbstractParameters.cs
1 #region MIT license\r
2 // \r
3 // MIT license\r
4 //\r
5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne\r
6 // \r
7 // Permission is hereby granted, free of charge, to any person obtaining a copy\r
8 // of this software and associated documentation files (the "Software"), to deal\r
9 // in the Software without restriction, including without limitation the rights\r
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
11 // copies of the Software, and to permit persons to whom the Software is\r
12 // furnished to do so, subject to the following conditions:\r
13 // \r
14 // The above copyright notice and this permission notice shall be included in\r
15 // all copies or substantial portions of the Software.\r
16 // \r
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
23 // THE SOFTWARE.\r
24 // \r
25 #endregion\r
26 \r
27 using System;\r
28 using System.IO;\r
29 using System.Linq;\r
30 using System.Reflection;\r
31 using System.Collections.Generic;\r
32 using System.Text;\r
33 using DbLinq.Util;\r
34 using DbMetal.Utility;\r
35 \r
36 namespace DbMetal\r
37 {\r
38     /// <summary>\r
39     /// Parameters base class.\r
40     /// Allows to specify direct switches or place switches in a file (specified with @fileName).\r
41     /// If a file specifies several line, the parameters will allow batch processing, one per line.\r
42     /// Parameters specified before the @ file are inherited by each @ file line\r
43     /// </summary>\r
44     public abstract class AbstractParameters\r
45     {\r
46         /// <summary>\r
47         /// Describes a switch (/sprocs)\r
48         /// </summary>\r
49         public class OptionAttribute : Attribute\r
50         {\r
51             /// <summary>\r
52             /// Allows to specify a group. All options in the same group are displayed together\r
53             /// </summary>\r
54             public int Group { get; set; }\r
55 \r
56             /// <summary>\r
57             /// Description\r
58             /// </summary>\r
59             public string Text { get; set; }\r
60 \r
61             /// <summary>\r
62             /// Value name, used for help\r
63             /// </summary>\r
64             public string ValueName { get; set; }\r
65 \r
66             public OptionAttribute(string text)\r
67             {\r
68                 Text = text;\r
69             }\r
70         }\r
71 \r
72         /// <summary>\r
73         /// Describes an input file\r
74         /// </summary>\r
75         public class FileAttribute : Attribute\r
76         {\r
77             /// <summary>\r
78             /// Tells if the file is required\r
79             /// TODO: add mandatory support in parameters check\r
80             /// </summary>\r
81             public bool Mandatory { get; set; }\r
82             /// <summary>\r
83             /// The name written in help\r
84             /// </summary>\r
85             public string Name { get; set; }\r
86             /// <summary>\r
87             /// Descriptions\r
88             /// </summary>\r
89             public string Text { get; set; }\r
90 \r
91             public FileAttribute(string name, string text)\r
92             {\r
93                 Name = name;\r
94                 Text = text;\r
95             }\r
96         }\r
97 \r
98         public class AlternateAttribute : Attribute\r
99         {\r
100             public string Name { get; set; }\r
101 \r
102             public AlternateAttribute(string name)\r
103             {\r
104                 Name = name;\r
105             }\r
106         }\r
107 \r
108         public readonly IList<string> Extra = new List<string>();\r
109         private TextWriter log;\r
110         public TextWriter Log\r
111         {\r
112             get { return log ?? Console.Out; }\r
113             set { log = value; }\r
114         }\r
115 \r
116         private static bool IsParameter(string arg, string switchPrefix, out string parameterName, out string parameterValue)\r
117         {\r
118             bool isParameter;\r
119             if (arg.StartsWith(switchPrefix))\r
120             {\r
121                 isParameter = true;\r
122                 string nameValue = arg.Substring(switchPrefix.Length);\r
123                 int separator = nameValue.IndexOfAny(new[] { ':', '=' });\r
124                 if (separator >= 0)\r
125                 {\r
126                     parameterName = nameValue.Substring(0, separator);\r
127                     parameterValue = nameValue.Substring(separator + 1).Trim('\"');\r
128                 }\r
129                 else if (nameValue.EndsWith("+"))\r
130                 {\r
131                     parameterName = nameValue.Substring(0, nameValue.Length - 1);\r
132                     parameterValue = "+";\r
133                 }\r
134                 else if (nameValue.EndsWith("-"))\r
135                 {\r
136                     parameterName = nameValue.Substring(0, nameValue.Length - 1);\r
137                     parameterValue = "-";\r
138                 }\r
139                 else if (nameValue.StartsWith("no-"))\r
140                 {\r
141                     parameterName = nameValue.Substring(3);\r
142                     parameterValue = "-";\r
143                 }\r
144                 else\r
145                 {\r
146                     parameterName = nameValue;\r
147                     parameterValue = null;\r
148                 }\r
149             }\r
150             else\r
151             {\r
152                 isParameter = false;\r
153                 parameterName = null;\r
154                 parameterValue = null;\r
155             }\r
156             return isParameter;\r
157         }\r
158 \r
159         protected static bool IsParameter(string arg, out string parameterName, out string parameterValue)\r
160         {\r
161             return IsParameter(arg, "--", out parameterName, out parameterValue)\r
162                    || IsParameter(arg, "-", out parameterName, out parameterValue)\r
163                    || IsParameter(arg, "/", out parameterName, out parameterValue);\r
164         }\r
165 \r
166         protected static object GetValue(string value, Type targetType)\r
167         {\r
168             object typedValue;\r
169             if (typeof(bool).IsAssignableFrom(targetType))\r
170             {\r
171                 if (value == null || value == "+")\r
172                     typedValue = true;\r
173                 else if (value == "-")\r
174                     typedValue = false;\r
175                 else\r
176                     typedValue = Convert.ToBoolean(value);\r
177             }\r
178             else\r
179             {\r
180                 typedValue = Convert.ChangeType(value, targetType);\r
181             }\r
182             return typedValue;\r
183         }\r
184 \r
185         protected virtual MemberInfo FindParameter(string name, Type type)\r
186         {\r
187             // the easy way: find propery or field name\r
188             var flags = BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public;\r
189             var memberInfos = type.GetMember(name, flags);\r
190             if (memberInfos.Length > 0)\r
191                 return memberInfos[0];\r
192             // the hard way: look for alternate names\r
193             memberInfos = type.GetMembers();\r
194             foreach (var memberInfo in memberInfos)\r
195             {\r
196                 var alternates = (AlternateAttribute[])memberInfo.GetCustomAttributes(typeof(AlternateAttribute), true);\r
197                 if (Array.Exists(alternates, a => string.Compare(a.Name, name) == 0))\r
198                     return memberInfo;\r
199             }\r
200             return null;\r
201         }\r
202 \r
203         protected virtual MemberInfo FindParameter(string name)\r
204         {\r
205             return FindParameter(name, GetType());\r
206         }\r
207 \r
208         /// <summary>\r
209         /// Assigns a parameter by reflection\r
210         /// </summary>\r
211         /// <param name="name">parameter name (case insensitive)</param>\r
212         /// <param name="value">parameter value</param>\r
213         protected void SetParameter(string name, string value)\r
214         {\r
215             // cleanup and evaluate\r
216             name = name.Trim();\r
217             // evaluate\r
218             value = value.EvaluateEnvironment();\r
219 \r
220             var memberInfo = FindParameter(name);\r
221             if (memberInfo == null)\r
222                 throw new ArgumentException(string.Format("Parameter {0} does not exist", name));\r
223             memberInfo.SetMemberValue(this, GetValue(value, memberInfo.GetMemberType()));\r
224         }\r
225 \r
226         /// <summary>\r
227         /// Loads arguments from a given list\r
228         /// </summary>\r
229         /// <param name="args"></param>\r
230         public void Load(IList<string> args)\r
231         {\r
232             foreach (string arg in args)\r
233             {\r
234                 string key, value;\r
235                 if (IsParameter(arg, out key, out value))\r
236                     SetParameter(key, value);\r
237                 else\r
238                     Extra.Add(arg);\r
239             }\r
240         }\r
241 \r
242         protected AbstractParameters()\r
243         {\r
244         }\r
245 \r
246         protected AbstractParameters(IList<string> args)\r
247         {\r
248             Load(args);\r
249         }\r
250 \r
251         /// <summary>\r
252         /// Internal method allowing to extract arguments and specify quotes characters\r
253         /// </summary>\r
254         /// <param name="commandLine"></param>\r
255         /// <param name="quotes"></param>\r
256         /// <returns></returns>\r
257         public IList<string> ExtractArguments(string commandLine, char[] quotes)\r
258         {\r
259             var arg = new StringBuilder();\r
260             var args = new List<string>();\r
261             const char zero = '\0';\r
262             char quote = zero;\r
263             foreach (char c in commandLine)\r
264             {\r
265                 if (quote == zero)\r
266                 {\r
267                     if (quotes.Contains(c))\r
268                         quote = c;\r
269                     else if (char.IsSeparator(c) && quote == zero)\r
270                     {\r
271                         if (arg.Length > 0)\r
272                         {\r
273                             args.Add(arg.ToString());\r
274                             arg = new StringBuilder();\r
275                         }\r
276                     }\r
277                     else\r
278                         arg.Append(c);\r
279                 }\r
280                 else\r
281                 {\r
282                     if (c == quote)\r
283                         quote = zero;\r
284                     else\r
285                         arg.Append(c);\r
286                 }\r
287             }\r
288             if (arg.Length > 0)\r
289                 args.Add(arg.ToString());\r
290             return args;\r
291         }\r
292 \r
293         private static readonly char[] Quotes = new[] { '\'', '\"' };\r
294         /// <summary>\r
295         /// Extracts arguments from a full line, in a .NET compatible way\r
296         /// (includes strange quotes trimming)\r
297         /// </summary>\r
298         /// <param name="commandLine">The command line</param>\r
299         /// <returns>Arguments list</returns>\r
300         public IList<string> ExtractArguments(string commandLine)\r
301         {\r
302             return ExtractArguments(commandLine, Quotes);\r
303         }\r
304 \r
305         /// <summary>\r
306         /// Converts a list separated by a comma to a string array\r
307         /// </summary>\r
308         /// <param name="list"></param>\r
309         /// <returns></returns>\r
310         public string[] GetArray(string list)\r
311         {\r
312             if (string.IsNullOrEmpty(list))\r
313                 return new string[0];\r
314             return (from entityInterface in list.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)\r
315                     select entityInterface.Trim()).ToArray();\r
316         }\r
317 \r
318         /// <summary>\r
319         /// Processes different "lines" of parameters:\r
320         /// 1. the original input parameter must be starting with @\r
321         /// 2. all other parameters are kept as a common part\r
322         /// </summary>\r
323         /// <typeparam name="P"></typeparam>\r
324         /// <param name="args"></param>\r
325         /// <returns></returns>\r
326         protected IList<P> GetParameterBatch<P>(IList<string> args)\r
327             where P : AbstractParameters, new()\r
328         {\r
329             return GetParameterBatch<P>(args, ".");\r
330         }\r
331 \r
332         public IList<P> GetParameterBatch<P>(IList<string> args, string argsFileDirectory)\r
333             where P : AbstractParameters, new()\r
334         {\r
335             var parameters = new List<P>();\r
336             var commonArgs = new List<string>();\r
337             var argsFiles = new List<string>();\r
338             foreach (var arg in args)\r
339             {\r
340                 if (arg.StartsWith("@"))\r
341                     argsFiles.Add(arg.Substring(1));\r
342                 else\r
343                     commonArgs.Add(arg);\r
344             }\r
345             // if we specify files, we must recurse\r
346             if (argsFiles.Count > 0)\r
347             {\r
348                 foreach (var argsFile in argsFiles)\r
349                 {\r
350                     parameters.AddRange(GetParameterBatchFile<P>(commonArgs, Path.Combine(argsFileDirectory, argsFile)));\r
351                 }\r
352             }\r
353             // if we don't, just use the args\r
354             else if (commonArgs.Count > 0)\r
355             {\r
356                 var p = new P { Log = Log };\r
357                 p.Load(commonArgs);\r
358                 parameters.Add(p);\r
359             }\r
360             return parameters;\r
361         }\r
362 \r
363         private IList<P> GetParameterBatchFile<P>(IEnumerable<string> baseArgs, string argsList)\r
364             where P : AbstractParameters, new()\r
365         {\r
366             var parameters = new List<P>();\r
367             string argsFileDirectory = Path.GetDirectoryName(argsList);\r
368             using (var textReader = File.OpenText(argsList))\r
369             {\r
370                 while (!textReader.EndOfStream)\r
371                 {\r
372                     string line = textReader.ReadLine();\r
373                     if (line.StartsWith("#"))\r
374                         continue;\r
375                     var args = ExtractArguments(line);\r
376                     var allArgs = new List<string>(baseArgs);\r
377                     allArgs.AddRange(args);\r
378                     parameters.AddRange(GetParameterBatch<P>(allArgs, argsFileDirectory));\r
379                 }\r
380             }\r
381             return parameters;\r
382         }\r
383 \r
384         /// <summary>\r
385         /// Outputs a formatted string to the console.\r
386         /// We're not using the ILogger here, since we want console output.\r
387         /// </summary>\r
388         /// <param name="format"></param>\r
389         /// <param name="args"></param>\r
390         public void Write(string format, params object[] args)\r
391         {\r
392             Output.WriteLine(Log, OutputLevel.Information, format, args);\r
393         }\r
394 \r
395         /// <summary>\r
396         /// Outputs an empty line\r
397         /// </summary>\r
398         public void WriteLine()\r
399         {\r
400             Output.WriteLine(Log, OutputLevel.Information, string.Empty);\r
401         }\r
402 \r
403         // TODO: remove this\r
404         protected static int TextWidth\r
405         {\r
406             get { return Console.BufferWidth; }\r
407         }\r
408 \r
409         /// <summary>\r
410         /// Returns the application (assembly) name (without extension)\r
411         /// </summary>\r
412         protected static string ApplicationName\r
413         {\r
414             get\r
415             {\r
416                 return Assembly.GetEntryAssembly().GetName().Name;\r
417             }\r
418         }\r
419 \r
420         /// <summary>\r
421         /// Returns the application (assembly) version\r
422         /// </summary>\r
423         protected static Version ApplicationVersion\r
424         {\r
425             get\r
426             {\r
427                 return Assembly.GetEntryAssembly().GetName().Version;\r
428             }\r
429         }\r
430 \r
431         private bool headerWritten;\r
432         /// <summary>\r
433         /// Writes the application header\r
434         /// </summary>\r
435         public void WriteHeader()\r
436         {\r
437             if (!headerWritten)\r
438             {\r
439                 WriteHeaderContents();\r
440                 WriteLine();\r
441                 headerWritten = true;\r
442             }\r
443         }\r
444 \r
445         protected abstract void WriteHeaderContents();\r
446 \r
447         /// <summary>\r
448         /// Writes a small summary\r
449         /// </summary>\r
450         public abstract void WriteSummary();\r
451 \r
452         /// <summary>\r
453         /// Writes examples\r
454         /// </summary>\r
455         public virtual void WriteExamples()\r
456         {\r
457         }\r
458 \r
459         /// <summary>\r
460         /// The "syntax" is a bried containing the application name, "[options]" and eventually files.\r
461         /// For example: "DbMetal [options] [&lt;input file>]\r
462         /// </summary>\r
463         public virtual void WriteSyntax()\r
464         {\r
465             var syntax = new StringBuilder();\r
466             syntax.AppendFormat("{0} [options]", ApplicationName);\r
467             foreach (var file in GetFiles())\r
468             {\r
469                 if (file.Description.Mandatory)\r
470                     syntax.AppendFormat(" {0}", GetFileText(file));\r
471                 else\r
472                     syntax.AppendFormat(" [{0}]", GetFileText(file));\r
473             }\r
474             Write(syntax.ToString());\r
475         }\r
476 \r
477         /// <summary>\r
478         /// Describes an option\r
479         /// </summary>\r
480         protected class Option\r
481         {\r
482             /// <summary>\r
483             /// The member name (property or field)\r
484             /// </summary>\r
485             public string Name { get; set; }\r
486             /// <summary>\r
487             /// The attribute used to define the member as an option\r
488             /// </summary>\r
489             public OptionAttribute Description { get; set; }\r
490         }\r
491 \r
492         /// <summary>\r
493         /// Describes an input file\r
494         /// </summary>\r
495         protected class FileName\r
496         {\r
497             /// <summary>\r
498             /// The member name (property or field)\r
499             /// </summary>\r
500             public string Name { get; set; }\r
501             /// <summary>\r
502             /// The attribute used to define the member as an input file\r
503             /// </summary>\r
504             public FileAttribute Description { get; set; }\r
505         }\r
506 \r
507         /// <summary>\r
508         /// Internal class. I wrote it because I was thinking that the .NET framework already had such a class.\r
509         /// At second thought, I may have made a confusion with STL\r
510         /// (interesting, isn't it?)\r
511         /// </summary>\r
512         /// <typeparam name="A"></typeparam>\r
513         /// <typeparam name="B"></typeparam>\r
514         protected class Pair<A, B>\r
515         {\r
516             public A First { get; set; }\r
517             public B Second { get; set; }\r
518         }\r
519 \r
520         /// <summary>\r
521         /// Enumerates all members (fields or properties) that have been marked with the specified attribute\r
522         /// </summary>\r
523         /// <typeparam name="T">The attribute type to search for</typeparam>\r
524         /// <returns>A list of pairs with name and attribute</returns>\r
525         protected IEnumerable<Pair<string, T>> EnumerateOptions<T>()\r
526             where T : Attribute\r
527         {\r
528             Type t = GetType();\r
529             foreach (var propertyInfo in t.GetProperties())\r
530             {\r
531                 var descriptions = (T[])propertyInfo.GetCustomAttributes(typeof(T), true);\r
532                 if (descriptions.Length == 1)\r
533                     yield return new Pair<string, T> { First = propertyInfo.Name, Second = descriptions[0] };\r
534             }\r
535             foreach (var fieldInfo in t.GetFields())\r
536             {\r
537                 var descriptions = (T[])fieldInfo.GetCustomAttributes(typeof(T), true);\r
538                 if (descriptions.Length == 1)\r
539                     yield return new Pair<string, T> { First = fieldInfo.Name, Second = descriptions[0] };\r
540             }\r
541         }\r
542 \r
543         protected IEnumerable<Option> EnumerateOptions()\r
544         {\r
545             foreach (var pair in EnumerateOptions<OptionAttribute>())\r
546                 yield return new Option { Name = pair.First, Description = pair.Second };\r
547         }\r
548 \r
549         protected IEnumerable<FileName> GetFiles()\r
550         {\r
551             foreach (var pair in from p in EnumerateOptions<FileAttribute>() orderby p.Second.Mandatory select p)\r
552                 yield return new FileName { Name = pair.First, Description = pair.Second };\r
553         }\r
554 \r
555         /// <summary>\r
556         /// Returns options, grouped by group (the group number is the dictionary key)\r
557         /// </summary>\r
558         /// <returns></returns>\r
559         protected IDictionary<int, IList<Option>> GetOptions()\r
560         {\r
561             var options = new Dictionary<int, IList<Option>>();\r
562             foreach (var option in EnumerateOptions())\r
563             {\r
564                 if (!options.ContainsKey(option.Description.Group))\r
565                     options[option.Description.Group] = new List<Option>();\r
566                 options[option.Description.Group].Add(option);\r
567             }\r
568             return options;\r
569         }\r
570 \r
571         /// <summary>\r
572         /// Return a literal value based on an option\r
573         /// </summary>\r
574         /// <param name="option"></param>\r
575         /// <returns></returns>\r
576         protected virtual string GetOptionText(Option option)\r
577         {\r
578             var optionName = option.Name[0].ToString().ToLower() + option.Name.Substring(1);\r
579             if (string.IsNullOrEmpty(option.Description.ValueName))\r
580                 return optionName;\r
581             return string.Format("{0}:<{1}>",\r
582                 optionName,\r
583                 option.Description.ValueName);\r
584         }\r
585 \r
586         /// <summary>\r
587         /// Returns a literal value base on an input file\r
588         /// </summary>\r
589         /// <param name="fileName"></param>\r
590         /// <returns></returns>\r
591         protected virtual string GetFileText(FileName fileName)\r
592         {\r
593             return string.Format("<{0}>", fileName.Description.Name);\r
594         }\r
595 \r
596         /// <summary>\r
597         /// Computes the maximum options and files length, to align all descriptions\r
598         /// </summary>\r
599         /// <param name="options"></param>\r
600         /// <param name="files"></param>\r
601         /// <returns></returns>\r
602         private int GetMaximumLength(IDictionary<int, IList<Option>> options, IEnumerable<FileName> files)\r
603         {\r
604             int maxLength = 0;\r
605             foreach (var optionsList in options.Values)\r
606             {\r
607                 foreach (var option in optionsList)\r
608                 {\r
609                     var optionName = GetOptionText(option);\r
610                     int length = optionName.Length;\r
611                     if (length > maxLength)\r
612                         maxLength = length;\r
613                 }\r
614             }\r
615             foreach (var file in files)\r
616             {\r
617                 var fileName = GetFileText(file);\r
618                 int length = fileName.Length;\r
619                 if (length > maxLength)\r
620                     maxLength = length;\r
621             }\r
622             return maxLength;\r
623         }\r
624 \r
625         protected static string[] SplitText(string text, int width)\r
626         {\r
627             var lines = new List<string>(new[] { "" });\r
628             var words = text.Split(' ');\r
629             foreach (var word in words)\r
630             {\r
631                 var line = lines.Last();\r
632                 if (line.Length == 0)\r
633                     lines[lines.Count - 1] = word;\r
634                 else if (line.Length + word.Length + 1 < width)\r
635                     lines[lines.Count - 1] = line + " " + word;\r
636                 else\r
637                     lines.Add(word);\r
638             }\r
639             return lines.ToArray();\r
640         }\r
641 \r
642         protected void WriteOption(string firstLine, string text)\r
643         {\r
644             int width = TextWidth - firstLine.Length - 2;\r
645             var lines = SplitText(text, width);\r
646             var padding = string.Empty.PadRight(firstLine.Length);\r
647             for (int i = 0; i < lines.Length; i++)\r
648             {\r
649                 Write("{0} {1}", i == 0 ? firstLine : padding, lines[i]);\r
650             }\r
651         }\r
652 \r
653         /// <summary>\r
654         /// Displays all available options and files\r
655         /// </summary>\r
656         protected void WriteOptions()\r
657         {\r
658             var options = GetOptions();\r
659             var files = GetFiles();\r
660             int maxLength = GetMaximumLength(options, files);\r
661             Write("Options:");\r
662             foreach (var group in from k in options.Keys orderby k select k)\r
663             {\r
664                 var optionsList = options[group];\r
665                 foreach (var option in from o in optionsList orderby o.Name select o)\r
666                 {\r
667                     WriteOption(string.Format("  /{0}", GetOptionText(option).PadRight(maxLength)), option.Description.Text);\r
668                 }\r
669                 WriteLine();\r
670             }\r
671             foreach (var file in files)\r
672             {\r
673                 WriteOption(string.Format("  {0}", GetFileText(file).PadRight(maxLength + 1)), file.Description.Text);\r
674             }\r
675         }\r
676 \r
677         /// <summary>\r
678         /// Displays application help\r
679         /// </summary>\r
680         public void WriteHelp()\r
681         {\r
682             WriteHeader(); // includes a WriteLine()\r
683             WriteSyntax();\r
684             WriteLine();\r
685             WriteSummary();\r
686             WriteLine();\r
687             WriteOptions();\r
688             WriteLine();\r
689             WriteExamples();\r
690         }\r
691     }\r
692 }\r