Click here to Skip to main content
15,896,290 members
Articles / Programming Languages / XML

Adaptive Console Framework - Build Your Console Application on the Fly

Rate me:
Please Sign up or sign in to vote.
3.00/5 (1 vote)
18 Sep 2008CDDL9 min read 31K   286   15  
Introduces the goal and use of the Adaptive Console Framework.
/* ----------------------------------------------------------------------------
 * AdaptiveConsole - A flexable console application framework.
 * Copyright (C) 2007-2008 SunnyChen.ORG, all rights reserved.
 * 
 * Author        : Sunny Chen
 * Company       : SunnyChen.ORG, http://www.sunnychen.org
 * Version       : 1.0
 * Date          : 9/16/2008
 * 
 * 
 * UPDATE HISTORY
 * ----------------------------------------------------------------------------
 * DATE          DESCRIPTION                            VERSION         UPDATED_BY
 * 9/16/2008     Created                                3.5.3182.35766  Sunny Chen
 * 9/20/2008     Added Default field on the option.     3.5.3182.35766  Sunny Chen
 * 9/23/2008     BUG FIX #1                             3.5.3189.17673  Sunny Chen
 * 11/27/2008    Modified                               3.5.3253.15384  Sunny Chen
 * ---------------------------------------------------------------------------- */

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using AdaptiveConsole.Config;

namespace AdaptiveConsole
{
    /// <summary>
    /// The base class for all console applications which use AdaptiveConsole framework.
    /// </summary>
    public abstract class ConsoleApplicationBase
    {
        #region Internal Constants
        /// <summary>
        /// The separator character which is used for separating the arguments that are defined on the option contracts.
        /// </summary>
        internal const char OPTION_CONTRACT_ARGUMENT_SEP = ';';
        /// <summary>
        /// The separater character which is used for separating the arguments that are defined on the option names.
        /// </summary>
        internal const char OPTION_ATTRIBUTE_NAME_SEP = ';';
        /// <summary>
        /// The template for assembly version.
        /// </summary>
        internal const string TEMPLATE_ASSEMBLY_VERSION = "<%app_asm_ver%>";
        /// <summary>
        /// The template for assembly title.
        /// </summary>
        internal const string TEMPLATE_ASSEMBLY_TITLE = "<%app_asm_title%>";
        /// <summary>
        /// The template for assembly description.
        /// </summary>
        internal const string TEMPLATE_ASSEMBLY_DESCRIPTION = "<%app_asm_desc%>";
        /// <summary>
        /// The template for assembly product information.
        /// </summary>
        internal const string TEMPLATE_ASSEMBLY_PRODUCT = "<%app_asm_product%>";
        /// <summary>
        /// The template for assembly copyright information.
        /// </summary>
        internal const string TEMPLATE_ASSEMBLY_COPYRIGHT = "<%app_asm_copyright%>";
        /// <summary>
        /// The template for assembly company information.
        /// </summary>
        internal const string TEMPLATE_ASSEMBLY_COMPANY = "<%app_asm_company%>";
        /// <summary>
        /// The template for the replacement of application logo.
        /// </summary>
        internal const string TEMPLATE_APPLICATION_LOGO = "<%app_logo%>";
        /// <summary>
        /// The template for the replacement of application description.
        /// </summary>
        internal const string TEMPLATE_APPLICATION_DESCRIPTION = "<%app_desc%>";
        /// <summary>
        /// The template for the replacement of application syntax lines.
        /// </summary>
        internal const string TEMPLATE_APPLICATION_SYNTAX_LINES = "<%app_syn_lines%>";
        /// <summary>
        /// The template for the replacement of application syntax description.
        /// </summary>
        internal const string TEMPLATE_APPLICATION_SYNTAX_DESCRIPTIONS = "<%app_syn_descs%>";

        #endregion

        #region Private Constants
        /// <summary>
        /// The separater charater which is used for separating the values within the ValueList argument.
        /// </summary>
        private const char OPTION_NAME_VALUE_SEP = ':';
        /// <summary>
        /// Represents the name of the configuration section.
        /// </summary>
        private const string CONFIG_SECTION_NAME = @"AdaptiveConsole";
        #endregion

        #region Private Fields
        /// <summary>
        /// The System.Configuration instance that holds the configuration information.
        /// </summary>
        private Configuration config;

        /// <summary>
        /// Refers to the methods that takes the option contract, contract property information and
        /// option information as parameters, and has no return values.
        /// </summary>
        /// <param name="target">The option contract which holds the property.</param>
        /// <param name="property">The reflection information of the property.</param>
        /// <param name="attribute">The option attribute which describes the property.</param>
        private delegate void OptionTraversalEventHandler(OptionContractBase target, PropertyInfo property, OptionAttribute attribute);

        /// <summary>
        /// The entry assembly of the console application.
        /// </summary>
        private Assembly entryAssembly = Assembly.GetEntryAssembly();
        #endregion

        #region Private Properties
        /// <summary>
        /// Gets or sets the option contract repository. Each contract information contains a instance of
        /// the option contract and its attribute information.
        /// </summary>
        private IList<OptionContractInfo> OptionContractRepository { get; set; }

        /// <summary>
        /// Gets the version of the assembly.
        /// </summary>
        private string AssemblyVersion
        {
            get
            {
                return entryAssembly.GetName().Version.ToString();
            }
        }

        /// <summary>
        /// Gets the title of the assembly.
        /// </summary>
        private string AssemblyTitle
        {
            get
            {
                object[] attributes = entryAssembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
                if (attributes.Length > 0)
                {
                    AssemblyTitleAttribute titleAttribute = (AssemblyTitleAttribute)attributes[0];
                    if (titleAttribute.Title != "")
                    {
                        return titleAttribute.Title;
                    }
                }
                return Path.GetFileNameWithoutExtension(entryAssembly.CodeBase);
            }
        }

        /// <summary>
        /// Gets the description of the assembly.
        /// </summary>
        private string AssemblyDescription
        {
            get
            {
                object[] attributes = entryAssembly.GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false);
                if (attributes.Length == 0)
                {
                    return "";
                }
                return ((AssemblyDescriptionAttribute)attributes[0]).Description;
            }
        }

        /// <summary>
        /// Gets the product information of the assembly.
        /// </summary>
        private string AssemblyProduct
        {
            get
            {
                object[] attributes = entryAssembly.GetCustomAttributes(typeof(AssemblyProductAttribute), false);
                if (attributes.Length == 0)
                {
                    return "";
                }
                return ((AssemblyProductAttribute)attributes[0]).Product;
            }
        }

        /// <summary>
        /// Gets the copyright information of the assembly.
        /// </summary>
        private string AssemblyCopyright
        {
            get
            {
                object[] attributes = entryAssembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
                if (attributes.Length == 0)
                {
                    return "";
                }
                return ((AssemblyCopyrightAttribute)attributes[0]).Copyright;
            }
        }

        /// <summary>
        /// Gets the company information of the assembly.
        /// </summary>
        private string AssemblyCompany
        {
            get
            {
                object[] attributes = entryAssembly.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
                if (attributes.Length == 0)
                {
                    return "";
                }
                return ((AssemblyCompanyAttribute)attributes[0]).Company;
            }
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Checks if the name of an option contains the specified argument.
        /// </summary>
        /// <param name="name">Name of the option.</param>
        /// <param name="argument">The argument to be checked.</param>
        /// <param name="caseSensitive">Indicates whether should perform a case-sensitive check.</param>
        /// <param name="sep">The separator for the elements in the name.</param>
        /// <returns>True if the specified argument is within the option name, otherwise false.</returns>
        private bool ContainsOption(string name, string argument, bool caseSensitive, char sep)
        {
            string[] options = name.Split(sep);
            foreach (string opt in options)
            {
                if (caseSensitive)
                {
                    if (opt.Trim().Equals(argument))
                        return true;
                }
                else
                {
                    if (opt.Trim().ToUpper().Equals(argument.ToUpper()))
                        return true;
                }
            }
            return false;
        }
 
        /// <summary>
        /// Gets the option name and value from a SingleValue option.
        /// </summary>
        /// <param name="argument">The command line argument from which the option name and value should be retrieved.</param>
        /// <param name="optionName">Name of the option.</param>
        /// <param name="optionValue">Value of the option</param>
        /// <param name="sep">The separator for the command line argument.</param>
        /// <returns>True if successfully get the name and value, otherwise false.</returns>
        /// <example>
        /// For example, when dealing with the argument "/output:a.xml", the option name might be "/output" and
        /// the option value might be "a.xml".
        /// </example>
        private bool GetSingleValue(string argument, ref string optionName, ref string optionValue, char sep)
        {
            int pos = argument.IndexOf(sep);
            if (pos == -1)
            {
                optionName = string.Empty;
                optionValue = string.Empty;
                return false;
            }
            optionName = argument.Substring(0, pos);
            optionValue = argument.Substring(pos + 1);
            return true;
        }

        /// <summary>
        /// Gets the option name and a list of values from a ValueList option.
        /// </summary>
        /// <param name="argument">The command line argument from which the option name and values should be retrieved.</param>
        /// <param name="optionName">Name of the option.</param>
        /// <param name="optionValues">Values of the option</param>
        /// <param name="sep">The separator for the command line argument.</param>
        /// <param name="valueSep">The separator for the values.</param>
        /// <returns>True if successfully get the name and values, otherwise false.</returns>
        /// <example>
        /// For example, when dealing with the argument "/input:a.xsd,b.xsd,c.xsd", the option name might be "/input" and
        /// the option values are "a.xsd", "b.xsd", "c.xsd".
        /// </example>
        private bool GetMultipleValues(string argument, ref string optionName, ref string[] optionValues, char sep, char valueSep)
        {
            int pos = argument.IndexOf(sep);
            if (pos == -1)
            {
                optionName = string.Empty;
                optionValues = null;
                return false;
            }
            optionName = argument.Substring(0, pos);
            optionValues = argument.Substring(pos + 1).Split(valueSep);
            return true;
        }

        /// <summary>
        /// Traverses through each option within an option contract and performs specific operation.
        /// </summary>
        /// <param name="optionContract">The option contract to be traversed.</param>
        /// <param name="handler">The operation which should be taken on the option.</param>
        private void TraverseOptions(OptionContractBase optionContract, OptionTraversalEventHandler handler)
        {
            foreach (PropertyInfo property in optionContract.GetType().GetProperties())
            {
                //foreach (object customAttribute in property.GetCustomAttributes(false))
                //{
                //    if (customAttribute is OptionAttribute)
                //    {
                //        handler(optionContract, property, customAttribute as OptionAttribute);
                //        break;
                //    }
                //}
                object[] customAttributes = property.GetCustomAttributes(typeof(OptionAttribute), false);
                if (customAttributes != null && customAttributes.Length > 0)
                {
                    OptionAttribute optionAttribute = customAttributes.First() as OptionAttribute;
                    handler(optionContract, property, optionAttribute);
                }
            }
        }

        /// <summary>
        /// Populates the property of a patternized contract with the values which come from the 
        /// command line argument.
        /// </summary>
        /// <param name="target">The instance of an option contract to be populated.</param>
        /// <param name="property">The reflection information of the property to which the value should be populated.</param>
        /// <param name="attribute">The option attribute on the property.</param>
        /// <exception cref="AdaptiveConsole.ArgumentMissingException">Throws when an option is a required option but it is not specified in the command line arguments.</exception>
        /// <exception cref="AdaptiveConsole.InvalidOptionException">Throws when an invalid option type is provided.</exception>
        private void PopulateContractOption(OptionContractBase target, PropertyInfo property, OptionAttribute attribute)
        {
            bool containsOption = false;
            string optionName = string.Empty;

            switch (attribute.Type)
            {
                // If the option is a swith, and there is an occurrence in the command line argument,
                // that means the swith is turn on and we set the property to True. If there is no
                // occurrence of the option in the command line argument, it means that the switch
                // is turn off.
                case OptionType.Switch:
                    foreach (ArgumentInfo arg in this.Arguments)
                    {
                        if (this.ContainsOption(attribute.Name, arg.Argument, attribute.CaseSensitive, OPTION_ATTRIBUTE_NAME_SEP))
                        {
                            arg.Category = ArgumentCategory.Option;
                            containsOption = true;
                            break;
                        }
                    }
                    if (containsOption)
                        property.SetValue(target, true, null);
                    else
                        property.SetValue(target, false, null);
                    break;
                // If the option is a single value option, for each command line argument, we firstly
                // try getting the option name and value from the argument. If failed in getting the name
                // and value, that means current argument is not a single value option, we iterates to
                // the next command line argument. If we successfully get the name and value from the
                // single value option, we then check if the option name contains the name of the argument.
                // If it does, that means this is the command line argument that we want and we should populate
                // the option value to the property of the option.
                case OptionType.SingleValue:
                    optionName = string.Empty; 
                    string optionValue = string.Empty;
                    foreach (ArgumentInfo arg in this.Arguments)
                    {
                        if (!this.GetSingleValue(arg.Argument, ref optionName, ref optionValue, OPTION_NAME_VALUE_SEP))
                            continue;
                        if (this.ContainsOption(attribute.Name, optionName, attribute.CaseSensitive, OPTION_ATTRIBUTE_NAME_SEP))
                        {
                            arg.Category = ArgumentCategory.Option;
                            containsOption = true;
                            break;
                        }
                    }
                    if (containsOption)
                        property.SetValue(target, optionValue, null);
                    else
                    {
                        if (!attribute.Required)
                        {
                            // Sunny Chen Modified Begin - 9/20/2008 - Added for handling the Default field of the option.
                            if (!attribute.Default.Equals(string.Empty))
                                property.SetValue(target, attribute.Default, null);
                            else
                                property.SetValue(target, string.Empty, null);
                            // Sunny Chen Modified End
                        }
                        else
                            throw new ArgumentMissingException("Argument {0} is required.", attribute.Name);
                    }
                    break;
                // If the option is a multiple value option, for each command line argument, we firstly
                // try getting the option name and values from the argument. If failed in getting the name
                // and values, that means current argument is not a multiple value option, we iterates to
                // the next command line argument. If we successfully get the name and values from the
                // multiple value option, we then check if the option name contains the name of the argument.
                // If it does, that means this is the command line argument that we want and we should populate
                // the option values to the property of the option.
                case OptionType.ValueList:
                    optionName = string.Empty;
                    string[] optionValues = null;
                    foreach (ArgumentInfo arg in this.Arguments)
                    {
                        if (!this.GetMultipleValues(arg.Argument, ref optionName, ref optionValues, OPTION_NAME_VALUE_SEP, attribute.ValueSeparator))
                            continue;
                        if (this.ContainsOption(attribute.Name, optionName, attribute.CaseSensitive, OPTION_ATTRIBUTE_NAME_SEP))
                        {
                            arg.Category = ArgumentCategory.Option;
                            containsOption = true;
                            break;
                        }
                    }
                    if (containsOption)
                        property.SetValue(target, optionValues, null);
                    else
                    {
                        if (!attribute.Required)
                            property.SetValue(target, null, null);
                        else
                            throw new ArgumentMissingException("Argument {0} is required.", attribute.Name);
                    }
                    break;
                default:
                    throw new InvalidOptionException("The option is of the invalid type.");
            }
        }

        /// <summary>
        /// Checks if the command line argument matches the given contract.
        /// </summary>
        /// <param name="optionContractInfo">The contract information to be matched.</param>
        /// <returns>True if matches, otherwise false.</returns>
        private bool MatchContract(OptionContractInfo optionContractInfo)
        {
            switch (optionContractInfo.ContractAttribute.Type)
            {
                // If the contract is an Exact contract, checks if the command line argument exactly matches
                // the argument provided by the contract.
                case ContractType.Exact:
                    if (this.ContainsOption(optionContractInfo.ContractAttribute.Argument,
                        this.Arguments.ToArgumentString(),
                        optionContractInfo.ContractAttribute.CaseSensitive,
                        OPTION_CONTRACT_ARGUMENT_SEP))
                        return true;
                    return false;

                // If the contract is a None contract, that means no argument is required to run the console
                // application. So simply returns true when no argument is provided.
                case ContractType.None:
                    if (this.Arguments == null ||
                        this.Arguments.Count == 0)
                        return true;
                    return false;

                // If the contract is a Free contract, that means all the arguments will be considered as
                // parameters other than options. In such case, there must be at least one argument in the
                // argument list. This is for distinguishing the Free contract from None contract.
                case ContractType.Free:
                    if (this.Arguments == null ||
                        this.Arguments.Count == 0)
                        return false;
                    return true;

                // This is a patternized contract. For the patternized contract, the option properties will
                // be populated by command line arguments and the type of the argument will be set to Option
                // properly.
                default:
                    try
                    {
                        TraverseOptions(optionContractInfo.OptionContract,
                            PopulateContractOption);
                        return true;
                    }
                    catch
                    {
                        return false;
                    }
            }
        }

        /// <summary>
        /// Validates the option contracts.
        /// </summary>
        /// <exception cref="AdaptiveConsole.InvalidContractException">
        /// Throws when:
        /// 1. The option contract is an Exact contract but with no argument specified.
        /// 2. The Exact contract with the given argument already exists.
        /// 3. The contract repository has registered more than one None contract.
        /// 4. The contract repository has registered more than one Free contract.
        /// 5. The contract repository has registered both Free and Patternized contracts.
        /// </exception>
        /// <exception cref="AdaptiveConsole.InvalidOptionException">
        /// Throws when:
        /// 1. The option has not the name been specified.
        /// 2. The option with specific name already exists.
        /// 3. The option is a Swith option but the corresponding property is not a System.Boolean property.
        /// 4. The option is a SingleValue option but the corresponding property is not a System.String property.
        /// 5. The option is a ValueList option but the corresponding property is not a System.Array property.
        /// </exception>
        private void ValidateOptionContracts()
        {
            int noneContracts = 0;
            int freeContracts = 0;
            int exactContracts = 0;
            int patternizedContracts = 0;
            HashSet<string> exactArgumentSet = new HashSet<string>();
            foreach (OptionContractInfo oci in this.OptionContractRepository)
            {
                switch (oci.ContractAttribute.Type)
                {
                    case ContractType.None:
                        noneContracts++;
                        break;
                    case ContractType.Free:
                        freeContracts++;
                        break;
                    case ContractType.Exact:
                        exactContracts++;
                        if (oci.ContractAttribute.Argument == null ||
                            oci.ContractAttribute.Argument.Trim().Equals(string.Empty))
                            throw new InvalidContractException("The option contract is an Exact contract but the contract argument is missing.");

                        /* Sunny Chen Modified: 12/19/2008 --> */
                        /* 
                        if (exactArgumentSet.Contains(oci.ContractAttribute.Argument))
                            throw new InvalidContractException("The exact contract with the argument of {0} already exists.", oci.ContractAttribute.Argument);
                        else
                            exactArgumentSet.Add(oci.ContractAttribute.Argument);
                         * */
                        string argument = oci.ContractAttribute.Argument;
                        if (oci.ContractAttribute.CaseSensitive)
                        {
                            if (exactArgumentSet.Contains(argument))
                                throw new InvalidContractException("The exact contract with the argument of {0} already exists.", argument);
                            else
                                exactArgumentSet.Add(argument);
                        }
                        else
                        {
                            var query = from exactArgument in exactArgumentSet
                                        where exactArgument.ToUpper().Equals(argument.ToUpper())
                                        select exactArgument;
                            if (query.Count() > 0)
                                throw new InvalidContractException("The exact contract with the argument of {0} already exists.", argument);
                            else
                                exactArgumentSet.Add(argument);
                        }
                        /* <-- Sunny Chen Modified */
                        break;
                    default:
                        patternizedContracts++;
                        TraverseOptions(
                            oci.OptionContract,
                            delegate(OptionContractBase target, PropertyInfo property, OptionAttribute attribute)
                            {
                                HashSet<string> optionNameSet = new HashSet<string>();
                                if (attribute.Name == null ||
                                    attribute.Name.Trim().Equals(string.Empty))
                                    throw new InvalidOptionException("Option contract {0} contains options that have no name specified.", target);
                                
                                if (optionNameSet.Contains(attribute.Name))
                                    throw new InvalidOptionException("The option {0} has already been defined.", attribute);
                                else
                                    optionNameSet.Add(attribute.Name);

                                switch (attribute.Type)
                                {
                                    case OptionType.Switch:
                                        if (!property.PropertyType.FullName.Equals("System.Boolean"))
                                            throw new InvalidOptionException("Property {0} refers to a Switch option but it is not a System.Boolean property.", property);
                                        // Sunny Chen Added Begin - 9/20/2008 - Added for handling the Default field of option.
                                        if (!attribute.Default.Equals(string.Empty))
                                            throw new InvalidOptionException("Cannot specify the Default field on the Switch option.");
                                        // Sunny Chen Added End
                                        break;
                                    case OptionType.SingleValue:
                                        if (!property.PropertyType.FullName.Equals("System.String"))
                                            throw new InvalidOptionException("Property {0} refers to a SingleValue option but it is not a System.String property.", property);
                                        break;
                                    default:
                                        if (!property.PropertyType.FullName.Equals("System.Array"))
                                            throw new InvalidOptionException("Property {0} refers to a ValueList option but it is not a System.Array property.", property);
                                        // Sunny Chen Added Begin - 9/20/2008 - Added for handling the Default field of option.
                                        if (!attribute.Default.Equals(string.Empty))
                                            throw new InvalidOptionException("Cannot specify the Default field on the ValueList option.");
                                        // Sunny Chen Added End
                                        break;
                                }
                            });
                        break;
                }
            }
            if (noneContracts > 1)
                throw new InvalidContractException("A contract repository can only have one contract which is the type of None.");
            if (freeContracts > 1)
                throw new InvalidContractException("A contract repository can only have one contract which is the type of Free.");
            if (freeContracts == 1 && (patternizedContracts > 0))
                throw new InvalidContractException("A contract repository cannot have any Patternized contracts while there is a Free contract in it.");
            /* Sunny Chen Added 12/12/2008: BUG FIX #3 --> */
            if (freeContracts == 1 && (exactContracts > 0))
                throw new InvalidContractException("A contract repository cannot have any Exact contract while there is a Free contract in it.");
            /* <-- Sunny Chen Added 12/12/2008 */
        }

        /// <summary>
        /// Generates the syntax lines from each contract defined.
        /// </summary>
        /// <returns>The syntax lines represented by the returned string.</returns>
        private string GenerateSyntaxLines()
        {
            StringBuilder sb = new StringBuilder();
            var query = from p in this.OptionContractRepository
                        orderby p.ContractAttribute.Type
                        select p;

            foreach (OptionContractInfo optionContractInfo in query)
            {
                string syntax = optionContractInfo.OptionContract.Syntax;
                if (syntax != null)
                {
                    sb.Append(string.Format("{0} {1}", this.ApplicationName, syntax));
                    sb.Append(Environment.NewLine);
                }
            }

            return sb.ToString();
        }

        /// <summary>
        /// Generates the syntax descriptions from each contract defined.
        /// </summary>
        /// <returns>The syntax descriptions represented by the returned string.</returns>
        private string GenerateSyntaxDescriptions()
        {
            StringBuilder sb = new StringBuilder();
            var query = from p in this.OptionContractRepository
                        orderby p.ContractAttribute.Type
                        select p;

            foreach (OptionContractInfo optionContractInfo in query)
            {
                string helpText = optionContractInfo.OptionContract.HelpText;
                if (helpText != null && !helpText.Equals(string.Empty))
                {
                    sb.Append(helpText);
                    sb.Append(Environment.NewLine);
                    sb.Append(Environment.NewLine);
                }
            }
            return sb.ToString();
        }
        
        /// <summary>
        /// Gets a replaced string according to the given template.
        /// </summary>
        /// <param name="template">The template in which the macros need to be replaced.</param>
        /// <returns>The replaced string.</returns>
        private string GetReplacedString(string template)
        {
            return template
                .Replace("<%<%", "<~%")
                .Replace("%>%>", "%~>")
                .Replace(TEMPLATE_APPLICATION_LOGO, this.Logo)
                .Replace(TEMPLATE_APPLICATION_DESCRIPTION, this.Description)
                .Replace(TEMPLATE_APPLICATION_SYNTAX_LINES, this.GenerateSyntaxLines())
                .Replace(TEMPLATE_ASSEMBLY_COMPANY, this.AssemblyCompany)
                .Replace(TEMPLATE_ASSEMBLY_COPYRIGHT, this.AssemblyCopyright)
                .Replace(TEMPLATE_ASSEMBLY_DESCRIPTION, this.AssemblyDescription)
                .Replace(TEMPLATE_ASSEMBLY_PRODUCT, this.AssemblyProduct)
                .Replace(TEMPLATE_ASSEMBLY_TITLE, this.AssemblyTitle)
                .Replace(TEMPLATE_ASSEMBLY_VERSION, this.AssemblyVersion)
                .Replace(TEMPLATE_APPLICATION_SYNTAX_DESCRIPTIONS, this.GenerateSyntaxDescriptions())
                .Replace("<~%", "<%")
                .Replace("%~>", "%>");
        }
        #endregion

        #region Protected Properties
        /// <summary>
        /// Gets or sets the list of command line argument information. Each element
        /// contains the information about the command line argument and its argument type.
        /// </summary>
        protected IList<ArgumentInfo> Arguments { get; set; }

        /// <summary>
        /// Gets or sets the configuration handler for the AdaptiveConsole framework.
        /// </summary>
        protected AdaptiveConsoleConfigHandler AdaptiveConsoleConfig { get; set; }

        /// <summary>
        /// Gets the template string which defines the banner format of the help screen.
        /// </summary>
        protected virtual string BannerTemplate
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("<%app_logo%>");
                sb.Append(Environment.NewLine);
                sb.Append("<%app_asm_copyright%>");
                sb.Append(Environment.NewLine);
                sb.Append(Environment.NewLine);
                sb.Append("<%app_desc%>");
                sb.Append(Environment.NewLine);
                sb.Append(Environment.NewLine);
                return sb.ToString();
            }
        }

        /// <summary>
        /// Gets the template string which defines the body format of the help screen.
        /// </summary>
        protected virtual string HelpBodyTemplate
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("<%app_syn_lines%>");
                sb.Append(Environment.NewLine);
                sb.Append("<%app_syn_descs%>");
                return sb.ToString();
            }
        }


        /// <summary>
        /// Gets or sets the Logo of the console application.
        /// </summary>
        protected abstract string Logo { get; }

        /// <summary>
        /// Gets or sets the description text of the console application.
        /// </summary>
        protected abstract string Description { get; }

        #endregion

        #region Constructors
        /// <summary>
        /// Initializes the console application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public ConsoleApplicationBase(string[] args)
        {
            // Creates the argument information list.
            this.Arguments = new List<ArgumentInfo>();
            // Populates the argument information list. Initially each argument is the type of Parameter.
            foreach (string arg in args)
                this.Arguments.Add(new ArgumentInfo(arg, ArgumentCategory.Parameter));

            // Gets the application configuration.
            config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            this.AdaptiveConsoleConfig = (AdaptiveConsoleConfigHandler)config.GetSection(CONFIG_SECTION_NAME);

            // Creates the option contract repository.
            this.OptionContractRepository = new List<OptionContractInfo>();
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the full file name of the console application.
        /// </summary>
        public string ApplicationFullName
        {
            get { return Process.GetCurrentProcess().MainModule.FileName; }
        }

        /// <summary>
        /// Gets the file name of the console application.
        /// </summary>
        public string ApplicationName
        {
            get { return Path.GetFileName(ApplicationFullName); }
        }

        #endregion

        #region Public Methods
        /// <summary>
        /// Initializes the console application.
        /// </summary>
        /// <exception cref="AdaptiveConsole.AdaptiveConsoleException">Throws when failed to initialize the application.</exception>
        public virtual void Init()
        {
            try
            {
                // Populates the contract repository.
                // Assembly assembly = Assembly.Load(this.AdaptiveConsoleConfig.ContractRepository);
                //foreach (Type type in assembly.GetTypes())
                //{
                //    object[] customAttributes = type.GetCustomAttributes(false);
                //    bool hasAttribute = false;
                //    OptionContractAttribute contractAttribute = null;

                //    foreach (object customAttribute in customAttributes)
                //    {
                //        if (customAttribute is OptionContractAttribute)
                //        {
                //            hasAttribute = true;
                //            contractAttribute = (customAttribute as OptionContractAttribute);
                //            break;
                //        }
                //    }

                //    if (!hasAttribute) continue;

                //    this.OptionContractRepository.Add(new OptionContractInfo((OptionContractBase)Activator.CreateInstance(type), contractAttribute));
                //}

                IList<Assembly> assemblies = new List<Assembly>();
                foreach (RepositoryConfigElement repositoryConfig in this.AdaptiveConsoleConfig.Repositories)
                {
                    try
                    {
                        assemblies.Add(Assembly.Load(repositoryConfig.Assembly));
                    }
                    catch
                    {
                        continue;
                    }
                }
                foreach (Assembly assembly in assemblies)
                {
                    foreach (Type type in assembly.GetExportedTypes())
                    {
                        object[] customAttributes = type.GetCustomAttributes(typeof(OptionContractAttribute), false);
                        if (customAttributes != null && customAttributes.Length > 0)
                        {
                            OptionContractAttribute optionContractAttribute = (customAttributes.First() as OptionContractAttribute);
                            this.OptionContractRepository.Add(new OptionContractInfo((OptionContractBase)Activator.CreateInstance(type), optionContractAttribute));
                        }
                        else
                            continue;
                    }
                }
                // Validates the contract repository, if failed to validate, exceptions will be thrown
                // and the console application will output the exception message.
                this.ValidateOptionContracts();
            }
            catch (AdaptiveConsoleException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new AdaptiveConsoleException(AdaptiveConsoleException.GENERAL_EXCEPTION_MESSAGE, ex);
            }
        }

        /// <summary>
        /// Runs the console application.
        /// </summary>
        /// <exception cref="AdaptiveConsole.AdaptiveConsoleException">Throws when failed to run the application.</exception>
        public virtual void Run()
        {
            try
            {
                // Checks each option contract with the given command line argument.
                // If successfully matched the contract, execute the contract with the given arguments.
                foreach (OptionContractInfo optionContractInfo in this.OptionContractRepository)
                {
                    if (this.MatchContract(optionContractInfo))
                    {
                        optionContractInfo.OptionContract.DoExecute(this, this.Arguments);
                        return;
                    }
                }
                this.PrintHelpMessage();
            }
            catch (AdaptiveConsoleException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new AdaptiveConsoleException(AdaptiveConsoleException.GENERAL_EXCEPTION_MESSAGE, ex);
            }
        }

        /// <summary>
        /// Finalizes the console application.
        /// </summary>
        public virtual void Done() { }

        /// <summary>
        /// Prints the Logo and description to the screen.
        /// </summary>
        public void PrintBanner()
        {
            Console.Write(GetReplacedString(this.BannerTemplate));
        }

        /// <summary>
        /// Prints the body of the help screen.
        /// </summary>
        public void PrintHelpBody()
        {
            Console.Write(GetReplacedString(this.HelpBodyTemplate));
        }

        /// <summary>
        /// Prints the help message.
        /// </summary>
        public void PrintHelpMessage()
        {
            // bool syntaxPrinted = false;
            // Prints the logo
            this.PrintBanner();
            //// Dumps the syntax text from each option contract and prints the syntax definitions
            //// to the screen.
            //foreach (OptionContractInfo optionContractInfo in this.OptionContractRepository)
            //{
            //    string syntax = optionContractInfo.OptionContract.Syntax;
            //    if (syntax != null)
            //    {
            //        syntaxPrinted = true;
            //        Console.WriteLine("{0} {1}", this.ApplicationName, syntax);
            //    }
            //}

            //if (syntaxPrinted)
            //    Console.WriteLine();
            
            //// Displays the detailed information about the contract.
            //foreach (OptionContractInfo optionContractInfo in this.OptionContractRepository)
            //{
            //    string helpText = optionContractInfo.OptionContract.HelpText;
            //    if (helpText != null && !helpText.Equals(string.Empty))
            //    {
            //        Console.WriteLine(optionContractInfo.OptionContract.HelpText);
            //        Console.WriteLine();
            //    }
            //}
            this.PrintHelpBody();
        }

        
        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Architect Prosource Development
China China
I have more than 13 years' experience in software development and more than 5 years' working experience in software industry. I'm the National Certified System Analyst and now I'm the consultant of China System Analyst Institution. I also got the MCP/MCAD certificate on .NET technology in the year 2004.
I'm very interested in system architect and analysis, and also interested in .NET technologies. For my blog please refer to http://www.sunnychen.org.

Comments and Discussions