Merge branch 'IngestTemplateEngine' of https://github.com/mlorbetske/cli into rel/1.0.0

This commit is contained in:
seancpeters 2016-12-16 17:27:20 -08:00
commit b40fd2f290
7 changed files with 1594 additions and 0 deletions

View file

@ -16,6 +16,7 @@ using Microsoft.DotNet.Tools.List;
using Microsoft.DotNet.Tools.Migrate; using Microsoft.DotNet.Tools.Migrate;
using Microsoft.DotNet.Tools.MSBuild; using Microsoft.DotNet.Tools.MSBuild;
using Microsoft.DotNet.Tools.New; using Microsoft.DotNet.Tools.New;
using Microsoft.DotNet.Tools.New3;
using Microsoft.DotNet.Tools.NuGet; using Microsoft.DotNet.Tools.NuGet;
using Microsoft.DotNet.Tools.Pack; using Microsoft.DotNet.Tools.Pack;
using Microsoft.DotNet.Tools.Publish; using Microsoft.DotNet.Tools.Publish;
@ -41,6 +42,7 @@ namespace Microsoft.DotNet.Cli
["migrate"] = MigrateCommand.Run, ["migrate"] = MigrateCommand.Run,
["msbuild"] = MSBuildCommand.Run, ["msbuild"] = MSBuildCommand.Run,
["new"] = NewCommand.Run, ["new"] = NewCommand.Run,
["new3"] = New3Command.Run,
["nuget"] = NuGetCommand.Run, ["nuget"] = NuGetCommand.Run,
["pack"] = PackCommand.Run, ["pack"] = PackCommand.Run,
["publish"] = PublishCommand.Run, ["publish"] = PublishCommand.Run,

View file

@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.DotNet.Cli.CommandLine;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.DotNet.Tools.New3
{
internal static class AppExtensions
{
public static CommandOption Help(this CommandLineApplication app)
{
return app.Option("-h|--help", LocalizableStrings.DisplaysHelp, CommandOptionType.NoValue);
}
public static IReadOnlyDictionary<string, IList<string>> ParseExtraArgs(this CommandLineApplication app, IList<string> extraArgFileNames)
{
Dictionary<string, IList<string>> parameters = new Dictionary<string, IList<string>>();
// Note: If the same param is specified multiple times across the files, last-in-wins
// TODO: consider another course of action.
if (extraArgFileNames.Count > 0)
{
foreach (string argFile in extraArgFileNames)
{
using (Stream s = File.OpenRead(argFile))
using (TextReader r = new StreamReader(s, Encoding.UTF8, true, 4096, true))
using (JsonTextReader reader = new JsonTextReader(r))
{
JObject obj = JObject.Load(reader);
foreach (JProperty property in obj.Properties())
{
if(property.Value.Type == JTokenType.String)
{
IList<string> values = new List<string>
{
property.Value.ToString()
};
// adding 2 dashes to the file-based params
// won't work right if there's a param that should have 1 dash
//
// TOOD: come up with a better way to deal with this
parameters["--" + property.Name] = values;
}
}
}
}
}
for (int i = 0; i < app.RemainingArguments.Count; ++i)
{
string key = app.RemainingArguments[i];
CommandOption arg = app.Options.FirstOrDefault(x => x.Template.Split('|').Any(y => string.Equals(y, key, StringComparison.OrdinalIgnoreCase)));
bool handled = false;
if (arg != null)
{
if (arg.OptionType != CommandOptionType.NoValue)
{
handled = arg.TryParse(app.RemainingArguments[i + 1]);
++i;
}
else
{
handled = arg.TryParse(null);
}
}
if (handled)
{
continue;
}
if (!key.StartsWith("-", StringComparison.Ordinal))
{
throw new Exception(LocalizableStrings.ParameterNamePrefixError);
}
// Check the next value. If it doesn't start with a '-' then it's a value for the current param.
// Otherwise it's its own param.
string value = null;
if (app.RemainingArguments.Count > i + 1)
{
value = app.RemainingArguments[i + 1];
if (value.StartsWith("-", StringComparison.Ordinal))
{
value = null;
}
else
{
++i;
}
}
if (!parameters.TryGetValue(key, out var valueList))
{
valueList = new List<string>();
parameters.Add(key, valueList);
}
valueList.Add(value);
}
return parameters;
}
}
}

View file

@ -0,0 +1,409 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Edge.Settings;
namespace Microsoft.DotNet.Tools.New3
{
internal class ExtendedCommandParser
{
private CommandLineApplication _app;
// Hidden & default options. Listed here to avoid clashes with on-the-fly params from individual templates.
private HashSet<string> _defaultCommandOptions;
// key is the variant, value is the canonical version
private IDictionary<string, string> _hiddenCommandCanonicalMapping;
// key is the canonical version
IDictionary<string, CommandOptionType> _hiddenCommandOptions;
// maps the template param variants to the canonical forms
private IDictionary<string, string> _templateParamCanonicalMapping;
// Canonical form -> data type
private IDictionary<string, string> _templateParamDataTypeMapping;
// stores the parsed values
private IDictionary<string, string> _parsedTemplateParams;
private IDictionary<string, IList<string>> _parsedInternalParams;
private IDictionary<string, IList<string>> _parsedRemainingParams;
// stores the options & arguments that are NOT hidden
// this is used exclusively to show the help.
// it's a bit of a hack.
CommandLineApplication _helpDisplayer;
public ExtendedCommandParser()
{
_app = new CommandLineApplication(false);
_defaultCommandOptions = new HashSet<string>();
_hiddenCommandOptions = new Dictionary<string, CommandOptionType>();
_hiddenCommandCanonicalMapping = new Dictionary<string, string>();
_templateParamCanonicalMapping = new Dictionary<string, string>();
_templateParamDataTypeMapping = new Dictionary<string, string>();
_parsedTemplateParams = new Dictionary<string, string>();
_parsedInternalParams = new Dictionary<string, IList<string>>();
_parsedRemainingParams = new Dictionary<string, IList<string>>();
_helpDisplayer = new CommandLineApplication(false);
}
// TODO: consider optionally showing help for things not handled by the CommandLineApplication instance
public void ShowHelp()
{
_helpDisplayer.ShowHelp();
}
public void RemoveOption(CommandOption option)
{
_app.Options.Remove(option);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsParameterNameTaken(string testName)
{
return _defaultCommandOptions.Contains(testName)
|| _hiddenCommandCanonicalMapping.ContainsKey(testName)
|| _templateParamCanonicalMapping.ContainsKey(testName);
}
public int Execute(params string[] args)
{
return _app.Execute(args);
}
public void OnExecute(Func<Task<int>> invoke)
{
_app.OnExecute(invoke);
}
// Returns the "standard" args that were input - the ones handled by the CommandLineApplication
public List<string> RemainingArguments
{
get { return _app.RemainingArguments; }
}
public CommandArgument Argument(string parameter, string description)
{
if (IsParameterNameTaken(parameter))
{
throw new Exception(string.Format(LocalizableStrings.ParameterReuseError, parameter));
}
_defaultCommandOptions.Add(parameter);
// its not hidden, add it to the help
_helpDisplayer.Argument(parameter, description);
return _app.Argument(parameter, description);
}
public void InternalOption(string parameterVariants, string canonical, string description, CommandOptionType optionType)
{
_helpDisplayer.Option(parameterVariants, description, optionType);
HiddenInternalOption(parameterVariants, canonical, optionType);
}
// NOTE: the exceptions here should never happen, this is strictly called by the program
// Once testing is done, we can probably remove them.
public void HiddenInternalOption(string parameterVariants, string canonical, CommandOptionType optionType)
{
string[] parameters = parameterVariants.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < parameters.Length; i++)
{
if (IsParameterNameTaken(parameters[i]))
{
throw new Exception(string.Format(LocalizableStrings.ParameterReuseError, parameters[i]));
}
_hiddenCommandCanonicalMapping.Add(parameters[i], canonical);
}
_hiddenCommandOptions.Add(canonical, optionType);
}
public bool TemplateParamHasValue(string paramName)
{
return _parsedTemplateParams.ContainsKey(paramName);
}
public string TemplateParamValue(string paramName)
{
_parsedTemplateParams.TryGetValue(paramName, out string value);
return value;
}
// returns a copy of the template params
public IReadOnlyDictionary<string, string> AllTemplateParams
{
get
{
return new Dictionary<string, string>(_parsedTemplateParams);
}
}
public bool InternalParamHasValue(string paramName)
{
return _parsedInternalParams.ContainsKey(paramName);
}
public string InternalParamValue(string paramName)
{
if (_parsedInternalParams.TryGetValue(paramName, out IList<string> values))
{
return values.FirstOrDefault();
}
else
{
return null;
}
}
public IList<string> InternalParamValueList(string paramName)
{
_parsedInternalParams.TryGetValue(paramName, out IList<string> values);
return values;
}
public IDictionary<string, IList<string>> RemainingParameters
{
get
{
return _parsedRemainingParams;
}
}
// Parses all command line args, and any input arg files.
// NOTE: any previously parsed values are lost - this resets the parsed values.
public void ParseArgs(IList<string> extraArgFileNames = null)
{
_parsedTemplateParams = new Dictionary<string, string>();
_parsedInternalParams = new Dictionary<string, IList<string>>();
_parsedRemainingParams = new Dictionary<string, IList<string>>();
if (extraArgFileNames == null)
{
extraArgFileNames = new List<string>();
}
IReadOnlyDictionary<string, IList<string>> allParameters = _app.ParseExtraArgs(extraArgFileNames);
foreach (KeyValuePair<string, IList<string>> param in allParameters)
{
if (_hiddenCommandCanonicalMapping.TryGetValue(param.Key, out string canonicalName))
{
CommandOptionType optionType = _hiddenCommandOptions[canonicalName];
if (optionType == CommandOptionType.MultipleValue)
{
; // nothing to check
}
else if (optionType == CommandOptionType.SingleValue)
{
if (param.Value.Count != 1)
{
throw new Exception(string.Format(LocalizableStrings.MultipleValuesSpecifiedForSingleValuedParameter, canonicalName));
}
}
else // NoValue
{
if (param.Value.Count != 1 || param.Value[0] != null)
{
throw new Exception(string.Format(LocalizableStrings.ValueSpecifiedForValuelessParameter, canonicalName));
}
}
_parsedInternalParams.Add(canonicalName, param.Value);
}
else if (_templateParamCanonicalMapping.TryGetValue(param.Key, out canonicalName))
{
if (_parsedTemplateParams.ContainsKey(canonicalName))
{
// error, the same param was specified twice
throw new Exception(string.Format(LocalizableStrings.ParameterSpecifiedMultipleTimes, canonicalName, param.Key));
}
else
{
if ((param.Value[0] == null) && (_templateParamDataTypeMapping[canonicalName] != "bool"))
{
throw new Exception(string.Format(LocalizableStrings.ParameterMissingValue, param.Key, canonicalName));
}
// TODO: allow for multi-valued params
_parsedTemplateParams[canonicalName] = param.Value[0];
}
}
else
{
// not a known internal or template param.
_parsedRemainingParams[param.Key] = param.Value;
}
}
}
// Canonical is the template param name without any dashes. The things mapped to it all have dashes, including the param name itself.
public void SetupTemplateParameters(IParameterSet allParams, IReadOnlyDictionary<string, string> parameterNameMap)
{
HashSet<string> invalidParams = new HashSet<string>();
foreach (ITemplateParameter parameter in allParams.ParameterDefinitions.Where(x => x.Priority != TemplateParameterPriority.Implicit).OrderBy(x => x.Name))
{
if (parameter.Name.IndexOf(':') >= 0)
{ // Colon is reserved, template param names cannot have any.
invalidParams.Add(parameter.Name);
continue;
}
if (parameterNameMap == null || !parameterNameMap.TryGetValue(parameter.Name, out string flagFullText))
{
flagFullText = parameter.Name;
}
bool longNameFound = false;
bool shortNameFound = false;
// always unless taken
string nameAsParameter = "--" + flagFullText;
if (!IsParameterNameTaken(nameAsParameter))
{
MapTemplateParamToCanonical(nameAsParameter, parameter.Name);
longNameFound = true;
}
// only as fallback
string qualifiedName = "--param:" + flagFullText;
if (!longNameFound && !IsParameterNameTaken(qualifiedName))
{
MapTemplateParamToCanonical(qualifiedName, parameter.Name);
longNameFound = true;
}
// always unless taken
string shortName = "-" + PosixNameToShortName(flagFullText);
if (!IsParameterNameTaken(shortName))
{
MapTemplateParamToCanonical(shortName, parameter.Name);
shortNameFound = true;
}
// only as fallback
string singleLetterName = "-" + flagFullText.Substring(0, 1);
if (!shortNameFound && !IsParameterNameTaken(singleLetterName))
{
MapTemplateParamToCanonical(singleLetterName, parameter.Name);
shortNameFound = true;
}
// only as fallback
string qualifiedShortName = "-p:" + PosixNameToShortName(flagFullText);
if (!shortNameFound && !IsParameterNameTaken(qualifiedShortName))
{
MapTemplateParamToCanonical(qualifiedShortName, parameter.Name);
shortNameFound = true;
}
// only as fallback
string qualifiedSingleLetterName = "-p:" + flagFullText.Substring(0, 1);
if (!shortNameFound && !IsParameterNameTaken(qualifiedSingleLetterName))
{
MapTemplateParamToCanonical(qualifiedSingleLetterName, parameter.Name);
shortNameFound = true;
}
if (!shortNameFound && !longNameFound)
{
invalidParams.Add(flagFullText);
}
else
{
_templateParamDataTypeMapping[parameter.Name] = parameter.DataType;
}
}
if (invalidParams.Count > 0)
{
string unusableDisplayList = string.Join(", ", invalidParams);
throw new Exception(string.Format(LocalizableStrings.TemplateMalformedDueToBadParameters, unusableDisplayList));
}
}
private void MapTemplateParamToCanonical(string variant, string canonical)
{
if (_templateParamCanonicalMapping.TryGetValue(variant, out string existingCanonical))
{
throw new Exception(string.Format(LocalizableStrings.OptionVariantAlreadyDefined, variant, canonical, existingCanonical));
}
_templateParamCanonicalMapping[variant] = canonical;
}
// Concats the first letter of dash separated word.
private static string PosixNameToShortName(string name)
{
IList<string> wordsInName = name.Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries).ToList();
IList<string> firstLetters = new List<string>();
foreach (string word in wordsInName)
{
firstLetters.Add(word.Substring(0, 1));
}
return string.Join("", firstLetters);
}
private IDictionary<string, IList<string>> _canonicalToVariantsTemplateParamMap;
public IDictionary<string, IList<string>> CanonicalToVariantsTemplateParamMap
{
get
{
if (_canonicalToVariantsTemplateParamMap == null)
{
_canonicalToVariantsTemplateParamMap = new Dictionary<string, IList<string>>();
foreach (KeyValuePair<string, string> variantToCanonical in _templateParamCanonicalMapping)
{
string variant = variantToCanonical.Key;
string canonical = variantToCanonical.Value;
if (!_canonicalToVariantsTemplateParamMap.TryGetValue(canonical, out var variantList))
{
variantList = new List<string>();
_canonicalToVariantsTemplateParamMap.Add(canonical, variantList);
}
variantList.Add(variant);
}
}
return _canonicalToVariantsTemplateParamMap;
}
}
public string Name
{
get
{
return _app.Name;
}
set
{
_app.Name = value;
}
}
public string FullName
{
get
{
return _app.FullName;
}
set
{
_app.FullName = value;
}
}
}
}

View file

@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.DotNet.Tools.New3
{
public class HelpFormatter
{
public static HelpFormatter<T> For<T>(IEnumerable<T> items, int columnPadding, char? headerSeparator = null, bool blankLineBetweenRows = false)
{
return new HelpFormatter<T>(items, columnPadding, headerSeparator, blankLineBetweenRows);
}
}
public class HelpFormatter<T>
{
private readonly bool _blankLineBetweenRows;
private readonly int _columnPadding;
private readonly List<ColumnDefinition> _columns = new List<ColumnDefinition>();
private readonly char? _headerSeparator;
private readonly IEnumerable<T> _items;
public HelpFormatter(IEnumerable<T> items, int columnPadding, char? headerSeparator, bool blankLineBetweenRows)
{
_items = items;
_columnPadding = columnPadding;
_headerSeparator = headerSeparator;
_blankLineBetweenRows = blankLineBetweenRows;
}
public HelpFormatter<T> DefineColumn(Func<T, string> binder, string header = null, int maxWidth = 0, bool alwaysMaximizeWidth = false)
{
_columns.Add(new ColumnDefinition(header, binder, maxWidth, alwaysMaximizeWidth));
return this;
}
public string Layout()
{
Dictionary<int, int> widthLookup = new Dictionary<int, int>();
Dictionary<int, int> lineCountLookup = new Dictionary<int, int>();
List<TextWrapper[]> textByRow = new List<TextWrapper[]>();
TextWrapper[] header = new TextWrapper[_columns.Count];
int headerLines = 0;
for (int i = 0; i < _columns.Count; ++i)
{
header[i] = new TextWrapper(_columns[i].Header, _columns[i].MaxWidth, _columns[i].AlwaysMaximizeWidth);
headerLines = Math.Max(headerLines, header[i].LineCount);
widthLookup[i] = header[i].MaxWidth;
}
int lineNumber = 0;
foreach (T item in _items)
{
TextWrapper[] line = new TextWrapper[_columns.Count];
int maxLineCount = 0;
for (int i = 0; i < _columns.Count; ++i)
{
line[i] = _columns[i].GetCell(item);
widthLookup[i] = Math.Max(widthLookup[i], line[i].MaxWidth);
maxLineCount = Math.Max(maxLineCount, line[i].LineCount);
}
lineCountLookup[lineNumber++] = maxLineCount;
textByRow.Add(line);
}
StringBuilder b = new StringBuilder();
if (_columns.Any(x => !string.IsNullOrEmpty(x.Header)))
{
for (int j = 0; j < headerLines; ++j)
{
for (int i = 0; i < _columns.Count - 1; ++i)
{
b.Append(header[i][j, widthLookup[i]]);
b.Append("".PadRight(_columnPadding));
}
b.AppendLine(header[_columns.Count - 1][j, widthLookup[_columns.Count - 1]]);
}
}
if (_headerSeparator.HasValue)
{
int totalWidth = _columnPadding * (_columns.Count - 1);
for (int i = 0; i < _columns.Count; ++i)
{
totalWidth += Math.Max(header[i].MaxWidth, widthLookup[i]);
}
b.AppendLine("".PadRight(totalWidth, _headerSeparator.Value));
}
int currentLine = 0;
foreach (TextWrapper[] line in textByRow)
{
for (int j = 0; j < lineCountLookup[currentLine]; ++j)
{
for (int i = 0; i < _columns.Count - 1; ++i)
{
b.Append(line[i][j, widthLookup[i]]);
b.Append("".PadRight(_columnPadding));
}
b.AppendLine(line[_columns.Count - 1][j, widthLookup[_columns.Count - 1]]);
}
if (_blankLineBetweenRows)
{
b.AppendLine();
}
++currentLine;
}
return b.ToString();
}
private class ColumnDefinition
{
private readonly int _maxWidth;
private readonly string _header;
private readonly Func<T, string> _binder;
private readonly bool _alwaysMaximizeWidth;
public ColumnDefinition(string header, Func<T, string> binder, int maxWidth = -1, bool alwaysMaximizeWidth = false)
{
_header = header;
_maxWidth = maxWidth > 0 ? maxWidth : int.MaxValue;
_binder = binder;
_alwaysMaximizeWidth = alwaysMaximizeWidth && maxWidth > 0;
}
public string Header => _header;
public bool AlwaysMaximizeWidth => _alwaysMaximizeWidth;
public int MaxWidth => _maxWidth;
public TextWrapper GetCell(T value)
{
return new TextWrapper(_binder(value), _maxWidth, _alwaysMaximizeWidth);
}
}
private class TextWrapper
{
private readonly IReadOnlyList<string> _lines;
public TextWrapper(string text, int maxWidth, bool alwaysMax)
{
List<string> lines = new List<string>();
int position = 0;
int realMaxWidth = alwaysMax ? maxWidth : 0;
while (position < text.Length)
{
int newline = text.IndexOf(Environment.NewLine, position, StringComparison.Ordinal);
if (newline > -1)
{
if (newline - position <= maxWidth)
{
lines.Add(text.Substring(position, newline - position).TrimEnd());
position = newline + Environment.NewLine.Length;
}
else
{
GetLineText(text, lines, maxWidth, newline, ref position);
}
}
else
{
GetLineText(text, lines, maxWidth, text.Length - 1, ref position);
}
realMaxWidth = Math.Max(realMaxWidth, lines[lines.Count - 1].Length);
}
_lines = lines;
MaxWidth = realMaxWidth;
}
public int LineCount => _lines.Count;
public int MaxWidth { get; }
public string this[int index, int padTo = 0]
{
get { return (_lines.Count > index ? _lines[index] : string.Empty).PadRight(MaxWidth).PadRight(padTo > MaxWidth ? padTo : MaxWidth); }
}
private static void GetLineText(string text, List<string> lines, int maxLength, int end, ref int position)
{
if (text.Length - position < maxLength)
{
lines.Add(text.Substring(position));
position = text.Length;
return;
}
int lastBreak = text.LastIndexOfAny(new[] { ' ', '-' }, end, end - position);
while (lastBreak > 0 && lastBreak - position > maxLength)
{
--lastBreak;
lastBreak = text.LastIndexOfAny(new[] { ' ', '-' }, lastBreak, lastBreak - position);
}
if (lastBreak > 0)
{
lines.Add(text.Substring(position, lastBreak - position + 1).TrimEnd());
position = lastBreak + 1;
}
else
{
int properMax = Math.Min(maxLength - 1, text.Length - position);
lines.Add(text.Substring(position, properMax) + '-');
position += maxLength - 1;
}
}
}
}
}

View file

@ -0,0 +1,115 @@
using System;
namespace Microsoft.DotNet.Tools.New3
{
internal class LocalizableStrings
{
public const string DisplaysHelp = "Displays help for this command.";
public const string ParameterNamePrefixError = "Parameter names must start with -- or -";
public const string ParameterReuseError = "Parameter name {0} cannot be used for multiple purposes";
public const string MultipleValuesSpecifiedForSingleValuedParameter = "Multiple values specified for single value parameter: {0}";
public const string ValueSpecifiedForValuelessParameter = "Value specified for valueless parameter: {0}";
public const string ParameterSpecifiedMultipleTimes = "Parameter [{0}] was specified multiple times, including with the flag [{1}]";
public const string ParameterMissingValue = "Parameter [{0}] ({1}) must be given a value";
public const string TemplateMalformedDueToBadParameters = "Template is malformed. The following parameter names are invalid: {0}";
public const string OptionVariantAlreadyDefined = "Option variant {0} for canonical {1} was already defined for canonical {2}";
public const string ListsTemplates = "List templates containing the specified name.";
public const string NameOfOutput = "The name for the output being created. If no name is specified, the name of the current directory is used.";
public const string CreateDirectoryHelp = "Indicates whether to create a directory for the generated content.";
public const string CreateAliasHelp = "Creates an alias for the specified template.";
public const string ExtraArgsFileHelp = "Specifies a file containing additional parameters.";
public const string LocaleHelp = "The locale to use";
public const string QuietModeHelp = "Doesn't output any status information.";
public const string InstallHelp = "Installs a source or a template pack.";
public const string UpdateHelp = "Update matching templates.";
public const string CommandDescription = "Template Instantiation Commands for .NET Core CLI.";
public const string TemplateArgumentHelp = "The template to instantiate.";
public const string BadLocaleError = "Invalid format for input locale: [{0}]. Example valid formats: [en] [en-US]";
public const string AliasCreated = "Alias creation successful";
public const string AliasAlreadyExists = "Specified alias {0} already exists. Please specify a different alias.";
public const string CreateSuccessful = "The template {0} created successfully.";
public const string CreateFailed = "Template {0} could not be created. Error returned was: {1}";
public const string InstallSuccessful = "{0} was installed successfully.";
public const string InstallFailed = "{0} could not be installed. Error returned was: {1}.";
public const string MissingRequiredParameter = "Mandatory parameter {0} missing for template {1}.";
public const string GettingReady = "Getting ready...";
public const string InvalidInputSwitch = "Invalid input switch:";
public const string CheckingForUpdates = "Checking for updates for {0}...";
public const string UpdateAvailable = "An update for {0} is available...";
public const string NoUpdates = "No updates were found.";
public const string InstallingUpdates = "Installing updates...";
public const string BadPackageSpec = "Package [{0}] is not a valid package specification";
public const string Templates = "Templates";
public const string ShortName = "Short Name";
public const string Alias = "Alias";
public const string CurrentConfiguration = "Current configuration:";
public const string NoItems = "(No Items)";
public const string MountPoints = "Mount Points";
public const string MountPointFactories = "Mount Point Factories";
public const string Generators = "Generators";
public const string Id = "Id";
public const string Parent = "Parent";
public const string Assembly = "Assembly";
public const string Type = "Type";
public const string Factory = "Factory";
public const string Author = "Author: {0}";
public const string Description = "Description: {0}";
public const string Options = "Options:";
public const string ConfiguredValue = "Configured Value: {0}";
public const string DefaultValue = "Default: {0}";
public const string NoParameters = " (No Parameters)";
}
}

View file

@ -0,0 +1,714 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.TemplateEngine.Abstractions;
using Microsoft.TemplateEngine.Abstractions.Mount;
using Microsoft.TemplateEngine.Edge;
using Microsoft.TemplateEngine.Edge.Settings;
using Microsoft.TemplateEngine.Edge.Template;
using Microsoft.TemplateEngine.Utils;
namespace Microsoft.DotNet.Tools.New3
{
public class New3Command
{
private static readonly string HostIdentifier = "dotnetcli";
private static readonly Version HostVersion = typeof(Program).GetTypeInfo().Assembly.GetName().Version;
private static DefaultTemplateEngineHost Host;
private static void SetupInternalCommands(ExtendedCommandParser appExt)
{
// visible
appExt.InternalOption("-l|--list", "--list", LocalizableStrings.ListsTemplates, CommandOptionType.NoValue);
appExt.InternalOption("-n|--name", "--name", LocalizableStrings.NameOfOutput, CommandOptionType.SingleValue);
appExt.InternalOption("-h|--help", "--help", LocalizableStrings.DisplaysHelp, CommandOptionType.NoValue);
// hidden
appExt.HiddenInternalOption("-d|--dir", "--dir", CommandOptionType.NoValue);
appExt.HiddenInternalOption("-a|--alias", "--alias", CommandOptionType.SingleValue);
appExt.HiddenInternalOption("-x|--extra-args", "--extra-args", CommandOptionType.MultipleValue);
appExt.HiddenInternalOption("--locale", "--locale", CommandOptionType.SingleValue);
appExt.HiddenInternalOption("--quiet", "--quiet", CommandOptionType.NoValue);
appExt.HiddenInternalOption("-i|--install", "--install", CommandOptionType.MultipleValue);
// reserved but not currently used
appExt.HiddenInternalOption("-up|--update", "--update", CommandOptionType.MultipleValue);
appExt.HiddenInternalOption("-u|--uninstall", "--uninstall", CommandOptionType.MultipleValue);
appExt.HiddenInternalOption("--skip-update-check", "--skip-update-check", CommandOptionType.NoValue);
// Preserve these for now - they've got the help text, in case we want it back.
// (they'll need to get converted to extended option calls)
//
//CommandOption dirOption = app.Option("-d|--dir", LocalizableStrings.CreateDirectoryHelp, CommandOptionType.NoValue);
//CommandOption aliasOption = app.Option("-a|--alias", LocalizableStrings.CreateAliasHelp, CommandOptionType.SingleValue);
//CommandOption parametersFilesOption = app.Option("-x|--extra-args", LocalizableString.ExtraArgsFileHelp, CommandOptionType.MultipleValue);
//CommandOption localeOption = app.Option("--locale", LocalizableStrings.LocaleHelp, CommandOptionType.SingleValue);
//CommandOption quietOption = app.Option("--quiet", LocalizableStrings.QuietModeHelp, CommandOptionType.NoValue);
//CommandOption installOption = app.Option("-i|--install", LocalizableStrings.InstallHelp, CommandOptionType.MultipleValue);
//CommandOption update = app.Option("--update", LocalizableStrings.UpdateHelp, CommandOptionType.NoValue);
}
public static int Run(string[] args)
{
// Initial host setup has the current locale. May need to be changed based on inputs.
Host = new DefaultTemplateEngineHost(HostIdentifier, HostVersion, CultureInfo.CurrentCulture.Name);
EngineEnvironmentSettings.Host = Host;
ExtendedCommandParser app = new ExtendedCommandParser()
{
Name = "dotnet new",
FullName = LocalizableStrings.CommandDescription
};
SetupInternalCommands(app);
CommandArgument templateNames = app.Argument("template", LocalizableStrings.TemplateArgumentHelp);
app.OnExecute(async () =>
{
app.ParseArgs();
if (app.InternalParamHasValue("--extra-args"))
{
app.ParseArgs(app.InternalParamValueList("--extra-args"));
}
if (app.RemainingParameters.ContainsKey("--debug:attach"))
{
Console.ReadLine();
}
if (app.InternalParamHasValue("--locale"))
{
string newLocale = app.InternalParamValue("--locale");
if (!ValidateLocaleFormat(newLocale))
{
EngineEnvironmentSettings.Host.LogMessage(string.Format(LocalizableStrings.BadLocaleError, newLocale));
return -1;
}
Host.UpdateLocale(newLocale);
}
int resultCode = InitializationAndDebugging(app, out bool shouldExit);
if (shouldExit)
{
return resultCode;
}
resultCode = ParseTemplateArgs(app, templateNames.Value, out shouldExit);
if (shouldExit)
{
return resultCode;
}
resultCode = MaintenanceAndInfo(app, templateNames.Value, out shouldExit);
if (shouldExit)
{
return resultCode;
}
return await CreateTemplateAsync(app, templateNames.Value);
});
int result;
try
{
using (Timing.Over("Execute"))
{
result = app.Execute(args);
}
}
catch (Exception ex)
{
AggregateException ax = ex as AggregateException;
while (ax != null && ax.InnerExceptions.Count == 1)
{
ex = ax.InnerException;
ax = ex as AggregateException;
}
Reporter.Error.WriteLine(ex.Message.Bold().Red());
while (ex.InnerException != null)
{
ex = ex.InnerException;
ax = ex as AggregateException;
while (ax != null && ax.InnerExceptions.Count == 1)
{
ex = ax.InnerException;
ax = ex as AggregateException;
}
Reporter.Error.WriteLine(ex.Message.Bold().Red());
}
Reporter.Error.WriteLine(ex.StackTrace.Bold().Red());
result = 1;
}
return result;
}
private static async Task<int> CreateTemplateAsync(ExtendedCommandParser app, string templateName)
{
string nameValue = app.InternalParamValue("--name");
string fallbackName = new DirectoryInfo(Directory.GetCurrentDirectory()).Name;
bool dirValue = app.InternalParamHasValue("--dir");
string aliasName = app.InternalParamValue("--alias");
bool skipUpdateCheckValue = app.InternalParamHasValue("--skip-update-check");
// TODO: refactor alias creation out of InstantiateAsync()
TemplateCreationResult instantiateResult = await TemplateCreator.InstantiateAsync(templateName ?? "", nameValue, fallbackName, dirValue, aliasName, app.AllTemplateParams, skipUpdateCheckValue);
string resultTemplateName = string.IsNullOrEmpty(instantiateResult.TemplateFullName) ? templateName : instantiateResult.TemplateFullName;
switch (instantiateResult.Status)
{
case CreationResultStatus.AliasSucceeded:
// TODO: get this localized - in the mean time just list the templates, showing the alias
//EngineEnvironmentSettings.Host.LogMessage(LocalizableStrings.AliasCreated);
ListTemplates(templateName);
break;
case CreationResultStatus.AliasFailed:
EngineEnvironmentSettings.Host.LogMessage(string.Format(LocalizableStrings.AliasAlreadyExists, aliasName));
ListTemplates(templateName);
break;
case CreationResultStatus.CreateSucceeded:
EngineEnvironmentSettings.Host.LogMessage(string.Format(LocalizableStrings.CreateSuccessful, resultTemplateName));
break;
case CreationResultStatus.CreateFailed:
case CreationResultStatus.TemplateNotFound:
EngineEnvironmentSettings.Host.LogMessage(string.Format(LocalizableStrings.CreateFailed, resultTemplateName, instantiateResult.Message));
ListTemplates(templateName);
break;
case CreationResultStatus.InstallSucceeded:
EngineEnvironmentSettings.Host.LogMessage(string.Format(LocalizableStrings.InstallSuccessful, resultTemplateName));
break;
case CreationResultStatus.InstallFailed:
EngineEnvironmentSettings.Host.LogMessage(string.Format(LocalizableStrings.InstallFailed, resultTemplateName, instantiateResult.Message));
break;
case CreationResultStatus.MissingMandatoryParam:
EngineEnvironmentSettings.Host.LogMessage(string.Format(LocalizableStrings.MissingRequiredParameter, instantiateResult.Message, resultTemplateName));
break;
default:
break;
}
return instantiateResult.ResultCode;
}
private static int InitializationAndDebugging(ExtendedCommandParser app, out bool shouldExit)
{
bool reinitFlag = app.RemainingArguments.Any(x => x == "--debug:reinit");
if (reinitFlag)
{
Paths.User.FirstRunCookie.Delete();
}
// Note: this leaves things in a weird state. Might be related to the localized caches.
// not sure, need to look into it.
if (reinitFlag || app.RemainingArguments.Any(x => x == "--debug:reset-config"))
{
Paths.User.AliasesFile.Delete();
Paths.User.SettingsFile.Delete();
TemplateCache.DeleteAllLocaleCacheFiles();
shouldExit = true;
return 0;
}
if (!Paths.User.BaseDir.Exists() || !Paths.User.FirstRunCookie.Exists())
{
if (!app.InternalParamHasValue("--quiet"))
{
Reporter.Output.WriteLine(LocalizableStrings.GettingReady);
}
ConfigureEnvironment();
Paths.User.FirstRunCookie.WriteAllText("");
}
if (app.RemainingArguments.Any(x => x == "--debug:showconfig"))
{
ShowConfig();
shouldExit = true;
return 0;
}
shouldExit = false;
return 0;
}
private static int ParseTemplateArgs(ExtendedCommandParser app, string templateName, out bool shouldExit)
{
try
{
IReadOnlyCollection<ITemplateInfo> templates = TemplateCreator.List(templateName);
if (templates.Count == 1)
{
ITemplateInfo templateInfo = templates.First();
ITemplate template = SettingsLoader.LoadTemplate(templateInfo);
IParameterSet allParams = template.Generator.GetParametersForTemplate(template);
IReadOnlyDictionary<string, string> parameterNameMap = template.Generator.ParameterMapForTemplate(template);
app.SetupTemplateParameters(allParams, parameterNameMap);
}
// re-parse after setting up the template params
app.ParseArgs(app.InternalParamValueList("--extra-args"));
}
catch (Exception ex)
{
Reporter.Error.WriteLine(ex.Message.Red().Bold());
app.ShowHelp();
shouldExit = true;
return -1;
}
if (app.RemainingParameters.Any(x => !x.Key.StartsWith("--debug:")))
{
EngineEnvironmentSettings.Host.LogMessage(LocalizableStrings.InvalidInputSwitch);
foreach (string flag in app.RemainingParameters.Keys)
{
EngineEnvironmentSettings.Host.LogMessage($"\t{flag}");
}
shouldExit = true;
return DisplayHelp(templateName, app, app.AllTemplateParams);
}
shouldExit = false;
return 0;
}
private static int MaintenanceAndInfo(ExtendedCommandParser app, string templateName, out bool shouldExit)
{
if (app.InternalParamHasValue("--list"))
{
ListTemplates(templateName);
shouldExit = true;
return -1;
}
if (app.InternalParamHasValue("--help"))
{
shouldExit = true;
return DisplayHelp(templateName, app, app.AllTemplateParams);
}
if (app.InternalParamHasValue("--install"))
{
InstallPackages(app.InternalParamValueList("--install").ToList(), app.InternalParamHasValue("--quiet"));
shouldExit = true;
return 0;
}
//if (update.HasValue())
//{
// return PerformUpdateAsync(templateName, quiet, source);
//}
if (string.IsNullOrEmpty(templateName))
{
ListTemplates(string.Empty);
shouldExit = true;
return -1;
}
shouldExit = false;
return 0;
}
private static Regex _localeFormatRegex = new Regex(@"
^
[a-z]{2}
(?:-[A-Z]{2})?
$"
, RegexOptions.IgnorePatternWhitespace);
private static bool ValidateLocaleFormat(string localeToCheck)
{
return _localeFormatRegex.IsMatch(localeToCheck);
}
//private static async Task<int> PerformUpdateAsync(string name, bool quiet, CommandOption source)
//{
// HashSet<IConfiguredTemplateSource> allSources = new HashSet<IConfiguredTemplateSource>();
// HashSet<string> toInstall = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// foreach (ITemplate template in TemplateCreator.List(name, source))
// {
// allSources.Add(template.Source);
// }
// foreach (IConfiguredTemplateSource src in allSources)
// {
// if (!quiet)
// {
// Reporter.Output.WriteLine(string.Format(LocalizableStrings.CheckingForUpdates, src.Alias));
// }
// bool updatesReady;
// if (src.ParentSource != null)
// {
// updatesReady = await src.Source.CheckForUpdatesAsync(src.ParentSource, src.Location);
// }
// else
// {
// updatesReady = await src.Source.CheckForUpdatesAsync(src.Location);
// }
// if (updatesReady)
// {
// if (!quiet)
// {
// Reporter.Output.WriteLine(string.Format(LocalizableStrings.UpdateAvailable, src.Alias));
// }
// string packageId = src.ParentSource != null
// ? src.Source.GetInstallPackageId(src.ParentSource, src.Location)
// : src.Source.GetInstallPackageId(src.Location);
// toInstall.Add(packageId);
// }
// }
// if(toInstall.Count == 0)
// {
// if (!quiet)
// {
// Reporter.Output.WriteLine(LocalizableStrings.NoUpdates);
// }
// return 0;
// }
// if (!quiet)
// {
// Reporter.Output.WriteLine(LocalizableString.InstallingUpdates);
// }
// List<string> installCommands = new List<string>();
// List<string> uninstallCommands = new List<string>();
// foreach (string packageId in toInstall)
// {
// installCommands.Add("-i");
// installCommands.Add(packageId);
// uninstallCommands.Add("-i");
// uninstallCommands.Add(packageId);
// }
// installCommands.Add("--quiet");
// uninstallCommands.Add("--quiet");
// Command.CreateDotNet("new", uninstallCommands).ForwardStdOut().ForwardStdErr().Execute();
// Command.CreateDotNet("new", installCommands).ForwardStdOut().ForwardStdErr().Execute();
// Broker.ComponentRegistry.ForceReinitialize();
// if (!quiet)
// {
// Reporter.Output.WriteLine("Done.");
// }
// return 0;
//}
private static void ConfigureEnvironment()
{
string[] packageList;
if (Paths.Global.DefaultInstallPackageList.FileExists())
{
packageList = Paths.Global.DefaultInstallPackageList.ReadAllText().Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
if (packageList.Length > 0)
{
InstallPackages(packageList, true);
}
}
if (Paths.Global.DefaultInstallTemplateList.FileExists())
{
packageList = Paths.Global.DefaultInstallTemplateList.ReadAllText().Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
if (packageList.Length > 0)
{
InstallPackages(packageList, true);
}
}
}
private static void InstallPackages(IReadOnlyList<string> packageNames, bool quiet = false)
{
List<string> toInstall = new List<string>();
foreach (string package in packageNames)
{
string pkg = package.Trim();
pkg = Environment.ExpandEnvironmentVariables(pkg);
try
{
if (Directory.Exists(pkg) || File.Exists(pkg))
{
string packageLocation = new DirectoryInfo(pkg).FullName;
TemplateCache.Scan(packageLocation);
}
else
{
string directory = Path.GetDirectoryName(pkg);
string fileGlob = Path.GetFileName(pkg);
string fullDirectory = new DirectoryInfo(directory).FullName;
string fullPathGlob = Path.Combine(fullDirectory, fileGlob);
TemplateCache.Scan(fullPathGlob);
}
}
catch
{
EngineEnvironmentSettings.Host.OnNonCriticalError("InvalidPackageSpecification", string.Format(LocalizableStrings.BadPackageSpec, pkg), null, 0);
}
}
TemplateCache.WriteTemplateCaches();
if (!quiet)
{
ListTemplates(string.Empty);
}
}
private static void ListTemplates(string templateNames)
{
IEnumerable<ITemplateInfo> results = TemplateCreator.List(templateNames);
HelpFormatter<ITemplateInfo> formatter = new HelpFormatter<ITemplateInfo>(results, 6, '-', false);
formatter.DefineColumn(delegate (ITemplateInfo t) { return t.Name; }, LocalizableStrings.Templates);
formatter.DefineColumn(delegate (ITemplateInfo t) { return $"[{t.ShortName}]"; }, LocalizableStrings.ShortName);
formatter.DefineColumn(delegate (ITemplateInfo t) { return AliasRegistry.GetAliasForTemplate(t) ?? ""; }, LocalizableStrings.Alias);
Reporter.Output.WriteLine(formatter.Layout());
}
private static void ShowConfig()
{
Reporter.Output.WriteLine(LocalizableStrings.CurrentConfiguration);
Reporter.Output.WriteLine(" ");
TableFormatter.Print(SettingsLoader.MountPoints, LocalizableStrings.NoItems, " ", '-', new Dictionary<string, Func<MountPointInfo, object>>
{
{LocalizableStrings.MountPoints, x => x.Place},
{LocalizableStrings.Id, x => x.MountPointId},
{LocalizableStrings.Parent, x => x.ParentMountPointId},
{LocalizableStrings.Factory, x => x.MountPointFactoryId}
});
TableFormatter.Print(SettingsLoader.Components.OfType<IMountPointFactory>(), LocalizableStrings.NoItems, " ", '-', new Dictionary<string, Func<IMountPointFactory, object>>
{
{LocalizableStrings.MountPointFactories, x => x.Id},
{LocalizableStrings.Type, x => x.GetType().FullName},
{LocalizableStrings.Assembly, x => x.GetType().GetTypeInfo().Assembly.FullName}
});
TableFormatter.Print(SettingsLoader.Components.OfType<IGenerator>(), LocalizableStrings.NoItems, " ", '-', new Dictionary<string, Func<IGenerator, object>>
{
{LocalizableStrings.Generators, x => x.Id},
{LocalizableStrings.Type, x => x.GetType().FullName},
{LocalizableStrings.Assembly, x => x.GetType().GetTypeInfo().Assembly.FullName}
});
}
private static int DisplayHelp(string templateNames, ExtendedCommandParser app, IReadOnlyDictionary<string, string> userParameters)
{
if (string.IsNullOrWhiteSpace(templateNames))
{ // no template specified
app.ShowHelp();
return 0;
}
IReadOnlyCollection<ITemplateInfo> templates = TemplateCreator.List(templateNames);
if (templates.Count > 1)
{
ListTemplates(templateNames);
return -1;
}
else if (templates.Count == 1)
{
ITemplateInfo templateInfo = templates.First();
return TemplateHelp(templateInfo, app, userParameters);
}
else
{
// TODO: add a message indicating no templates matched the pattern. Requires LOC coordination
ListTemplates(string.Empty);
return -1;
}
}
private static int TemplateHelp(ITemplateInfo templateInfo, ExtendedCommandParser app, IReadOnlyDictionary<string, string> userParameters)
{
Reporter.Output.WriteLine(templateInfo.Name);
if (!string.IsNullOrWhiteSpace(templateInfo.Author))
{
Reporter.Output.WriteLine(string.Format(LocalizableStrings.Author, templateInfo.Author));
}
if (!string.IsNullOrWhiteSpace(templateInfo.Description))
{
Reporter.Output.WriteLine(string.Format(LocalizableStrings.Description, templateInfo.Description));
}
ITemplate template = SettingsLoader.LoadTemplate(templateInfo);
IParameterSet allParams = TemplateCreator.SetupDefaultParamValuesFromTemplateAndHost(template, template.DefaultName);
TemplateCreator.ResolveUserParameters(template, allParams, userParameters);
ParameterHelp(allParams, app);
return 0;
}
private static void ParameterHelp(IParameterSet allParams, ExtendedCommandParser app)
{
IEnumerable<ITemplateParameter> filteredParams = allParams.ParameterDefinitions.Where(x => x.Priority != TemplateParameterPriority.Implicit);
if (filteredParams.Any())
{
HelpFormatter<ITemplateParameter> formatter = new HelpFormatter<ITemplateParameter>(filteredParams, 2, null, true);
formatter.DefineColumn(
param =>
{
// the key is guaranteed to exist
IList<string> variants = app.CanonicalToVariantsTemplateParamMap[param.Name];
string options = string.Join("|", variants.Reverse());
return " " + options;
},
LocalizableStrings.Options
);
formatter.DefineColumn(delegate (ITemplateParameter param)
{
StringBuilder displayValue = new StringBuilder(255);
displayValue.AppendLine(param.Documentation);
if (string.Equals(param.DataType, "choice", StringComparison.OrdinalIgnoreCase))
{
displayValue.AppendLine(string.Join(", ", param.Choices));
}
else
{
displayValue.Append(param.DataType ?? "string");
displayValue.AppendLine(" - " + param.Priority.ToString());
}
if (allParams.ResolvedValues.TryGetValue(param, out object resolvedValueObject))
{
string resolvedValue = resolvedValueObject as string;
if (!string.IsNullOrEmpty(resolvedValue)
&& !string.IsNullOrEmpty(param.DefaultValue)
&& !string.Equals(param.DefaultValue, resolvedValue))
{
displayValue.AppendLine(string.Format(LocalizableStrings.ConfiguredValue, resolvedValue));
}
}
if (!string.IsNullOrEmpty(param.DefaultValue))
{
displayValue.AppendLine(string.Format(LocalizableStrings.DefaultValue, param.DefaultValue));
}
return displayValue.ToString();
},
string.Empty
);
Reporter.Output.WriteLine(formatter.Layout());
}
else
{
Reporter.Output.WriteLine(LocalizableStrings.NoParameters);
}
}
}
internal class TableFormatter
{
public static void Print<T>(IEnumerable<T> items, string noItemsMessage, string columnPad, char header, Dictionary<string, Func<T, object>> dictionary)
{
List<string>[] columns = new List<string>[dictionary.Count];
for (int i = 0; i < dictionary.Count; ++i)
{
columns[i] = new List<string>();
}
string[] headers = new string[dictionary.Count];
int[] columnWidths = new int[dictionary.Count];
int valueCount = 0;
foreach (T item in items)
{
int index = 0;
foreach (KeyValuePair<string, Func<T, object>> act in dictionary)
{
headers[index] = act.Key;
columns[index++].Add(act.Value(item)?.ToString() ?? "(null)");
}
++valueCount;
}
if (valueCount > 0)
{
for (int i = 0; i < columns.Length; ++i)
{
columnWidths[i] = Math.Max(columns[i].Max(x => x.Length), headers[i].Length);
}
}
else
{
int index = 0;
foreach (KeyValuePair<string, Func<T, object>> act in dictionary)
{
headers[index] = act.Key;
columnWidths[index++] = act.Key.Length;
}
}
int headerWidth = columnWidths.Sum() + columnPad.Length * (dictionary.Count - 1);
for (int i = 0; i < headers.Length - 1; ++i)
{
Reporter.Output.Write(headers[i].PadRight(columnWidths[i]));
Reporter.Output.Write(columnPad);
}
Reporter.Output.WriteLine(headers[headers.Length - 1]);
Reporter.Output.WriteLine("".PadRight(headerWidth, header));
for (int i = 0; i < valueCount; ++i)
{
for (int j = 0; j < columns.Length - 1; ++j)
{
Reporter.Output.Write(columns[j][i].PadRight(columnWidths[j]));
Reporter.Output.Write(columnPad);
}
Reporter.Output.WriteLine(columns[headers.Length - 1][i]);
}
if (valueCount == 0)
{
Reporter.Output.WriteLine(noItemsMessage);
}
Reporter.Output.WriteLine(" ");
}
}
}

View file

@ -80,6 +80,19 @@
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions"> <PackageReference Include="Microsoft.DotNet.PlatformAbstractions">
<Version>1.0.1-beta-000933</Version> <Version>1.0.1-beta-000933</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.TemplateEngine.Utils">
<Version>1.0.0-beta1-20161216-12</Version>
</PackageReference>
<PackageReference Include="Microsoft.TemplateEngine.Abstractions">
<Version>1.0.0-beta1-20161216-12</Version>
</PackageReference>
<PackageReference Include="Microsoft.TemplateEngine.Edge">
<Version>1.0.0-beta1-20161216-12</Version>
</PackageReference>
<PackageReference Include="Microsoft.TemplateEngine.Orchestrator.RunnableProjects">
<Version>1.0.0-beta1-20161216-12</Version>
</PackageReference>
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp1.0' "> <PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">