#region MIT license // // MIT license // // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #endregion using System; using System.IO; using System.Linq; using System.Reflection; using System.Collections.Generic; using System.Text; using DbLinq.Util; using DbMetal.Utility; namespace DbMetal { /// /// Parameters base class. /// Allows to specify direct switches or place switches in a file (specified with @fileName). /// If a file specifies several line, the parameters will allow batch processing, one per line. /// Parameters specified before the @ file are inherited by each @ file line /// public abstract class AbstractParameters { /// /// Describes a switch (/sprocs) /// public class OptionAttribute : Attribute { /// /// Allows to specify a group. All options in the same group are displayed together /// public int Group { get; set; } /// /// Description /// public string Text { get; set; } /// /// Value name, used for help /// public string ValueName { get; set; } public OptionAttribute(string text) { Text = text; } } /// /// Describes an input file /// public class FileAttribute : Attribute { /// /// Tells if the file is required /// TODO: add mandatory support in parameters check /// public bool Mandatory { get; set; } /// /// The name written in help /// public string Name { get; set; } /// /// Descriptions /// public string Text { get; set; } public FileAttribute(string name, string text) { Name = name; Text = text; } } public class AlternateAttribute : Attribute { public string Name { get; set; } public AlternateAttribute(string name) { Name = name; } } public readonly IList Extra = new List(); private TextWriter log; public TextWriter Log { get { return log ?? Console.Out; } set { log = value; } } private static bool IsParameter(string arg, string switchPrefix, out string parameterName, out string parameterValue) { bool isParameter; if (arg.StartsWith(switchPrefix)) { isParameter = true; string nameValue = arg.Substring(switchPrefix.Length); int separator = nameValue.IndexOfAny(new[] { ':', '=' }); if (separator >= 0) { parameterName = nameValue.Substring(0, separator); parameterValue = nameValue.Substring(separator + 1).Trim('\"'); } else if (nameValue.EndsWith("+")) { parameterName = nameValue.Substring(0, nameValue.Length - 1); parameterValue = "+"; } else if (nameValue.EndsWith("-")) { parameterName = nameValue.Substring(0, nameValue.Length - 1); parameterValue = "-"; } else if (nameValue.StartsWith("no-")) { parameterName = nameValue.Substring(3); parameterValue = "-"; } else { parameterName = nameValue; parameterValue = null; } } else { isParameter = false; parameterName = null; parameterValue = null; } return isParameter; } protected static bool IsParameter(string arg, out string parameterName, out string parameterValue) { return IsParameter(arg, "--", out parameterName, out parameterValue) || IsParameter(arg, "-", out parameterName, out parameterValue) || IsParameter(arg, "/", out parameterName, out parameterValue); } protected static object GetValue(string value, Type targetType) { object typedValue; if (typeof(bool).IsAssignableFrom(targetType)) { if (value == null || value == "+") typedValue = true; else if (value == "-") typedValue = false; else typedValue = Convert.ToBoolean(value); } else { typedValue = Convert.ChangeType(value, targetType); } return typedValue; } protected virtual MemberInfo FindParameter(string name, Type type) { // the easy way: find propery or field name var flags = BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public; var memberInfos = type.GetMember(name, flags); if (memberInfos.Length > 0) return memberInfos[0]; // the hard way: look for alternate names memberInfos = type.GetMembers(); foreach (var memberInfo in memberInfos) { var alternates = (AlternateAttribute[])memberInfo.GetCustomAttributes(typeof(AlternateAttribute), true); if (Array.Exists(alternates, a => string.Compare(a.Name, name) == 0)) return memberInfo; } return null; } protected virtual MemberInfo FindParameter(string name) { return FindParameter(name, GetType()); } /// /// Assigns a parameter by reflection /// /// parameter name (case insensitive) /// parameter value protected void SetParameter(string name, string value) { // cleanup and evaluate name = name.Trim(); // evaluate value = value.EvaluateEnvironment(); var memberInfo = FindParameter(name); if (memberInfo == null) throw new ArgumentException(string.Format("Parameter {0} does not exist", name)); memberInfo.SetMemberValue(this, GetValue(value, memberInfo.GetMemberType())); } /// /// Loads arguments from a given list /// /// public void Load(IList args) { foreach (string arg in args) { string key, value; if (IsParameter(arg, out key, out value)) SetParameter(key, value); else Extra.Add(arg); } } protected AbstractParameters() { } protected AbstractParameters(IList args) { Load(args); } /// /// Internal method allowing to extract arguments and specify quotes characters /// /// /// /// public IList ExtractArguments(string commandLine, char[] quotes) { var arg = new StringBuilder(); var args = new List(); const char zero = '\0'; char quote = zero; foreach (char c in commandLine) { if (quote == zero) { if (quotes.Contains(c)) quote = c; else if (char.IsSeparator(c) && quote == zero) { if (arg.Length > 0) { args.Add(arg.ToString()); arg = new StringBuilder(); } } else arg.Append(c); } else { if (c == quote) quote = zero; else arg.Append(c); } } if (arg.Length > 0) args.Add(arg.ToString()); return args; } private static readonly char[] Quotes = new[] { '\'', '\"' }; /// /// Extracts arguments from a full line, in a .NET compatible way /// (includes strange quotes trimming) /// /// The command line /// Arguments list public IList ExtractArguments(string commandLine) { return ExtractArguments(commandLine, Quotes); } /// /// Converts a list separated by a comma to a string array /// /// /// public string[] GetArray(string list) { if (string.IsNullOrEmpty(list)) return new string[0]; return (from entityInterface in list.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) select entityInterface.Trim()).ToArray(); } /// /// Processes different "lines" of parameters: /// 1. the original input parameter must be starting with @ /// 2. all other parameters are kept as a common part /// /// /// /// protected IList

GetParameterBatch

(IList args) where P : AbstractParameters, new() { return GetParameterBatch

(args, "."); } public IList

GetParameterBatch

(IList args, string argsFileDirectory) where P : AbstractParameters, new() { var parameters = new List

(); var commonArgs = new List(); var argsFiles = new List(); foreach (var arg in args) { if (arg.StartsWith("@")) argsFiles.Add(arg.Substring(1)); else commonArgs.Add(arg); } // if we specify files, we must recurse if (argsFiles.Count > 0) { foreach (var argsFile in argsFiles) { parameters.AddRange(GetParameterBatchFile

(commonArgs, Path.Combine(argsFileDirectory, argsFile))); } } // if we don't, just use the args else if (commonArgs.Count > 0) { var p = new P { Log = Log }; p.Load(commonArgs); parameters.Add(p); } return parameters; } private IList

GetParameterBatchFile

(IEnumerable baseArgs, string argsList) where P : AbstractParameters, new() { var parameters = new List

(); string argsFileDirectory = Path.GetDirectoryName(argsList); using (var textReader = File.OpenText(argsList)) { while (!textReader.EndOfStream) { string line = textReader.ReadLine(); if (line.StartsWith("#")) continue; var args = ExtractArguments(line); var allArgs = new List(baseArgs); allArgs.AddRange(args); parameters.AddRange(GetParameterBatch

(allArgs, argsFileDirectory)); } } return parameters; } ///

/// Outputs a formatted string to the console. /// We're not using the ILogger here, since we want console output. /// /// /// public void Write(string format, params object[] args) { Output.WriteLine(Log, OutputLevel.Information, format, args); } /// /// Outputs an empty line /// public void WriteLine() { Output.WriteLine(Log, OutputLevel.Information, string.Empty); } // TODO: remove this protected static int TextWidth { get { return Console.BufferWidth; } } /// /// Returns the application (assembly) name (without extension) /// protected static string ApplicationName { get { return Assembly.GetEntryAssembly().GetName().Name; } } /// /// Returns the application (assembly) version /// protected static Version ApplicationVersion { get { // Assembly.GetEntryAssembly() is null when loading from the // non-default AppDomain. var a = Assembly.GetEntryAssembly(); return a != null ? a.GetName().Version : new Version(); } } private bool headerWritten; /// /// Writes the application header /// public void WriteHeader() { if (!headerWritten) { WriteHeaderContents(); WriteLine(); headerWritten = true; } } protected abstract void WriteHeaderContents(); /// /// Writes a small summary /// public abstract void WriteSummary(); /// /// Writes examples /// public virtual void WriteExamples() { } /// /// The "syntax" is a bried containing the application name, "[options]" and eventually files. /// For example: "DbMetal [options] [<input file>] /// public virtual void WriteSyntax() { var syntax = new StringBuilder(); syntax.AppendFormat("{0} [options]", ApplicationName); foreach (var file in GetFiles()) { if (file.Description.Mandatory) syntax.AppendFormat(" {0}", GetFileText(file)); else syntax.AppendFormat(" [{0}]", GetFileText(file)); } Write(syntax.ToString()); } /// /// Describes an option /// protected class Option { /// /// The member name (property or field) /// public string Name { get; set; } /// /// The attribute used to define the member as an option /// public OptionAttribute Description { get; set; } } /// /// Describes an input file /// protected class FileName { /// /// The member name (property or field) /// public string Name { get; set; } /// /// The attribute used to define the member as an input file /// public FileAttribute Description { get; set; } } /// /// Internal class. I wrote it because I was thinking that the .NET framework already had such a class. /// At second thought, I may have made a confusion with STL /// (interesting, isn't it?) /// /// /// protected class Pair { public A First { get; set; } public B Second { get; set; } } /// /// Enumerates all members (fields or properties) that have been marked with the specified attribute /// /// The attribute type to search for /// A list of pairs with name and attribute protected IEnumerable> EnumerateOptions() where T : Attribute { Type t = GetType(); foreach (var propertyInfo in t.GetProperties()) { var descriptions = (T[])propertyInfo.GetCustomAttributes(typeof(T), true); if (descriptions.Length == 1) yield return new Pair { First = propertyInfo.Name, Second = descriptions[0] }; } foreach (var fieldInfo in t.GetFields()) { var descriptions = (T[])fieldInfo.GetCustomAttributes(typeof(T), true); if (descriptions.Length == 1) yield return new Pair { First = fieldInfo.Name, Second = descriptions[0] }; } } protected IEnumerable