#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