mirror of https://github.com/abpframework/abp.git
committed by
GitHub
63 changed files with 2014 additions and 20 deletions
@ -0,0 +1,27 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Ionic.Zip" Version="1.9.1.8" /> |
|||
<PackageReference Include="HtmlAgilityPack.NetCore" Version="1.5.0.1" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" /> |
|||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" /> |
|||
<PackageReference Include="System.Security.Permissions" Version="4.5.0" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Ddd.Domain\Volo.Abp.Ddd.Domain.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.Json\Volo.Abp.Json.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,19 @@ |
|||
using System.Text; |
|||
using Volo.Abp.Domain; |
|||
using Volo.Abp.Json; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Cli |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpDddDomainModule), |
|||
typeof(AbpJsonModule) |
|||
)] |
|||
public class AbpCliCoreModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
using System.Linq; |
|||
using System.Text; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Cli.Args |
|||
{ |
|||
public class CommandLineArgs |
|||
{ |
|||
[CanBeNull] |
|||
public string Command { get; } |
|||
|
|||
[CanBeNull] |
|||
public string Target { get; } |
|||
|
|||
[NotNull] |
|||
public CommandLineOptions Options { get; } |
|||
|
|||
public CommandLineArgs([CanBeNull] string command = null, [CanBeNull] string target = null) |
|||
{ |
|||
Command = command; |
|||
Target = target; |
|||
Options = new CommandLineOptions(); |
|||
} |
|||
|
|||
public static CommandLineArgs Empty() |
|||
{ |
|||
return new CommandLineArgs(); |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
var sb = new StringBuilder(); |
|||
|
|||
if (Command != null) |
|||
{ |
|||
sb.AppendLine($"Command: {Command}"); |
|||
} |
|||
|
|||
if (Target != null) |
|||
{ |
|||
sb.AppendLine($"Target: {Target}"); |
|||
} |
|||
|
|||
if (Options.Any()) |
|||
{ |
|||
sb.AppendLine("Options:"); |
|||
foreach (var option in Options) |
|||
{ |
|||
sb.AppendLine($" - {option.Key} = {option.Value}"); |
|||
} |
|||
} |
|||
|
|||
if (sb.Length <= 0) |
|||
{ |
|||
sb.Append("<EMPTY>"); |
|||
} |
|||
|
|||
return sb.ToString(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Args |
|||
{ |
|||
public class CommandLineArgumentParser : ICommandLineArgumentParser, ITransientDependency |
|||
{ |
|||
public CommandLineArgs Parse(string[] args) |
|||
{ |
|||
if (args.IsNullOrEmpty()) |
|||
{ |
|||
return CommandLineArgs.Empty(); |
|||
} |
|||
|
|||
var argumentList = args.ToList(); |
|||
|
|||
//Get command
|
|||
|
|||
var command = argumentList[0]; |
|||
argumentList.RemoveAt(0); |
|||
|
|||
if (!argumentList.Any()) |
|||
{ |
|||
return new CommandLineArgs(command); |
|||
} |
|||
|
|||
//Get target
|
|||
|
|||
var target = argumentList[0]; |
|||
if (target.StartsWith("-")) |
|||
{ |
|||
target = null; |
|||
} |
|||
else |
|||
{ |
|||
argumentList.RemoveAt(0); |
|||
} |
|||
|
|||
if (!argumentList.Any()) |
|||
{ |
|||
return new CommandLineArgs(command, target); |
|||
} |
|||
|
|||
//Get options
|
|||
|
|||
var commandLineArgs = new CommandLineArgs(command, target); |
|||
|
|||
while (argumentList.Any()) |
|||
{ |
|||
var optionName = ParseOptionName(argumentList[0]); |
|||
argumentList.RemoveAt(0); |
|||
|
|||
if (!argumentList.Any()) |
|||
{ |
|||
commandLineArgs.Options[optionName] = null; |
|||
break; |
|||
} |
|||
|
|||
if (IsOptionName(argumentList[0])) |
|||
{ |
|||
commandLineArgs.Options[optionName] = null; |
|||
continue; |
|||
} |
|||
|
|||
commandLineArgs.Options[optionName] = argumentList[0]; |
|||
argumentList.RemoveAt(0); |
|||
} |
|||
|
|||
return commandLineArgs; |
|||
} |
|||
|
|||
private static bool IsOptionName(string argument) |
|||
{ |
|||
return argument.StartsWith("-") || argument.StartsWith("--"); |
|||
} |
|||
|
|||
private static string ParseOptionName(string argument) |
|||
{ |
|||
if (argument.StartsWith("--")) |
|||
{ |
|||
if (argument.Length <= 2) |
|||
{ |
|||
throw new ArgumentException("Should specify an option name after '--' prefix!"); |
|||
} |
|||
|
|||
return argument.RemovePreFix("--"); |
|||
} |
|||
|
|||
if (argument.StartsWith("-")) |
|||
{ |
|||
if (argument.Length <= 1) |
|||
{ |
|||
throw new ArgumentException("Should specify an option name after '--' prefix!"); |
|||
} |
|||
|
|||
return argument.RemovePreFix("-"); |
|||
} |
|||
|
|||
throw new ArgumentException("Option names should start with '-' or '--'."); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.Cli.Args |
|||
{ |
|||
public class CommandLineOptions : Dictionary<string, string> |
|||
{ |
|||
[CanBeNull] |
|||
public string GetOrNull([NotNull] string name, params string[] alternativeNames) |
|||
{ |
|||
Check.NotNullOrWhiteSpace(name, nameof(name)); |
|||
|
|||
var value = this.GetOrDefault(name); |
|||
if (!value.IsNullOrWhiteSpace()) |
|||
{ |
|||
return value; |
|||
} |
|||
|
|||
if (!alternativeNames.IsNullOrEmpty()) |
|||
{ |
|||
foreach (var alternativeName in alternativeNames) |
|||
{ |
|||
value = this.GetOrDefault(alternativeName); |
|||
if (!value.IsNullOrWhiteSpace()) |
|||
{ |
|||
return value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.Cli.Args |
|||
{ |
|||
public interface ICommandLineArgumentParser |
|||
{ |
|||
CommandLineArgs Parse(string[] args); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace Volo.Abp.Cli |
|||
{ |
|||
public static class CliPaths |
|||
{ |
|||
public static string TemplateCachePath => Path.Combine(AbpRootPath, "templates"); |
|||
public static string CliLogPath => Path.Combine(AbpRootPath, "cli", "logs"); |
|||
|
|||
private static readonly string AbpRootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".abp"); |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
using System; |
|||
using System.Reflection; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Volo.Abp.Cli.Args; |
|||
using Volo.Abp.Cli.Commands; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.ProjectBuilding; |
|||
|
|||
namespace Volo.Abp.Cli |
|||
{ |
|||
public class CliService : ITransientDependency |
|||
{ |
|||
public static string Version => typeof(AbpCliCoreModule).Assembly.GetFileVersion(); |
|||
|
|||
public ILogger<CliService> Logger { get; set; } |
|||
|
|||
protected ICommandLineArgumentParser CommandLineArgumentParser { get; } |
|||
protected ICommandSelector CommandSelector { get; } |
|||
public IHybridServiceScopeFactory ServiceScopeFactory { get; } |
|||
|
|||
public CliService( |
|||
ICommandLineArgumentParser commandLineArgumentParser, |
|||
ICommandSelector commandSelector, |
|||
IHybridServiceScopeFactory serviceScopeFactory) |
|||
{ |
|||
CommandLineArgumentParser = commandLineArgumentParser; |
|||
CommandSelector = commandSelector; |
|||
ServiceScopeFactory = serviceScopeFactory; |
|||
|
|||
Logger = NullLogger<CliService>.Instance; |
|||
} |
|||
|
|||
public async Task RunAsync(string[] args) |
|||
{ |
|||
Logger.LogInformation("ABP CLI (abp.io)"); |
|||
Logger.LogInformation("Version: " + Version); |
|||
|
|||
var commandLineArgs = CommandLineArgumentParser.Parse(args); |
|||
var commandType = CommandSelector.Select(commandLineArgs); |
|||
|
|||
using (var scope = ServiceScopeFactory.CreateScope()) |
|||
{ |
|||
var command = (IConsoleCommand)scope.ServiceProvider.GetRequiredService(commandType); |
|||
await command.ExecuteAsync(commandLineArgs); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
using Volo.Abp.Cli.Args; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Commands |
|||
{ |
|||
public class CommandSelector : ICommandSelector, ITransientDependency |
|||
{ |
|||
public Type Select(CommandLineArgs commandLineArgs) |
|||
{ |
|||
//TODO: Create options to define commands
|
|||
//TODO: Get from dependency injection instead of new?
|
|||
|
|||
if (commandLineArgs.Command == "new") |
|||
{ |
|||
return typeof(NewProjectCommand); |
|||
} |
|||
|
|||
return typeof(MainHelpCommand); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
using Volo.Abp.Cli.Args; |
|||
|
|||
namespace Volo.Abp.Cli.Commands |
|||
{ |
|||
public interface ICommandSelector |
|||
{ |
|||
Type Select(CommandLineArgs commandLineArgs); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Cli.Args; |
|||
|
|||
namespace Volo.Abp.Cli.Commands |
|||
{ |
|||
public interface IConsoleCommand |
|||
{ |
|||
Task ExecuteAsync(CommandLineArgs commandLineArgs); |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Cli.Args; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Cli.Commands |
|||
{ |
|||
public class MainHelpCommand : IConsoleCommand, ITransientDependency |
|||
{ |
|||
public Task ExecuteAsync(CommandLineArgs commandLineArgs) |
|||
{ |
|||
Console.WriteLine("*********** ABP CLI ****************"); |
|||
Console.WriteLine(); |
|||
Console.WriteLine("Usage:"); |
|||
Console.WriteLine(); |
|||
Console.WriteLine(" abp <command> <target> [options]"); |
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Ionic.Zip; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Volo.Abp.Cli.Args; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.ProjectBuilding; |
|||
using Volo.Abp.ProjectBuilding.Building; |
|||
|
|||
namespace Volo.Abp.Cli.Commands |
|||
{ |
|||
public class NewProjectCommand : IConsoleCommand, ITransientDependency |
|||
{ |
|||
public ILogger<NewProjectCommand> Logger { get; set; } |
|||
|
|||
protected ProjectBuilder ProjectBuilder { get; } |
|||
|
|||
public NewProjectCommand(ProjectBuilder projectBuilder) |
|||
{ |
|||
ProjectBuilder = projectBuilder; |
|||
|
|||
Logger = NullLogger<NewProjectCommand>.Instance; |
|||
} |
|||
|
|||
public async Task ExecuteAsync(CommandLineArgs commandLineArgs) |
|||
{ |
|||
if (commandLineArgs.Target == null) |
|||
{ |
|||
Logger.LogWarning("Project name is missing."); |
|||
Logger.LogWarning("Usage:"); |
|||
Logger.LogWarning(" abp new <project-name> [-t <template-name>]"); |
|||
Logger.LogWarning("Examples:"); |
|||
Logger.LogWarning(" abp new Acme.BookStore"); |
|||
Logger.LogWarning(" abp new Acme.BookStore mvc"); |
|||
return; |
|||
} |
|||
|
|||
Logger.LogInformation("Creating a new project..."); |
|||
Logger.LogInformation("Project name: " + commandLineArgs.Target); |
|||
|
|||
var result = await ProjectBuilder.BuildAsync( |
|||
new ProjectBuildArgs( |
|||
SolutionName.Parse(commandLineArgs.Target), |
|||
GetDatabaseProviderOrNull(commandLineArgs), |
|||
commandLineArgs.Options.GetOrNull(Options.Template.Short, Options.Template.Long) |
|||
) |
|||
); |
|||
|
|||
using (var templateFileStream = new MemoryStream(result.ZipContent)) |
|||
{ |
|||
using (var templateZipFile = ZipFile.Read(templateFileStream)) |
|||
{ |
|||
templateZipFile.ExtractAll(Directory.GetCurrentDirectory(), ExtractExistingFileAction.Throw); |
|||
} |
|||
} |
|||
|
|||
Logger.LogInformation($"Successfully created the project '{commandLineArgs.Target}'"); |
|||
} |
|||
|
|||
protected virtual DatabaseProvider GetDatabaseProviderOrNull(CommandLineArgs commandLineArgs) |
|||
{ |
|||
var optionValue = commandLineArgs.Options.GetOrNull(Options.DatabaseProvider.Short, Options.DatabaseProvider.Long); |
|||
switch (optionValue) |
|||
{ |
|||
case "ef": |
|||
return DatabaseProvider.EntityFrameworkCore; |
|||
case "mongodb": |
|||
return DatabaseProvider.MongoDb; |
|||
default: |
|||
return DatabaseProvider.NotSpecified; |
|||
} |
|||
} |
|||
|
|||
public static class Options |
|||
{ |
|||
public static class Template |
|||
{ |
|||
public const string Short = "t"; |
|||
public const string Long = "template"; |
|||
} |
|||
|
|||
public static class DatabaseProvider |
|||
{ |
|||
public const string Short = "d"; |
|||
public const string Long = "databaseProvider"; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Volo.Abp.Cli; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.IO; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding |
|||
{ |
|||
public class AbpIoTemplateStore : ITemplateStore, ITransientDependency |
|||
{ |
|||
public ILogger<AbpIoTemplateStore> Logger { get; set; } |
|||
|
|||
public AbpIoTemplateStore() |
|||
{ |
|||
Logger = NullLogger<AbpIoTemplateStore>.Instance; |
|||
} |
|||
|
|||
public async Task<TemplateFile> GetAsync(string templateName, string version) |
|||
{ |
|||
var localCacheFolder = Path.Combine(CliPaths.TemplateCachePath, version); |
|||
DirectoryHelper.CreateIfNotExists(localCacheFolder); |
|||
|
|||
var localCacheFile = Path.Combine(localCacheFolder, templateName + ".zip"); |
|||
if (File.Exists(localCacheFile)) |
|||
{ |
|||
Logger.LogInformation("Using cached template: " + templateName + ", version: " + version); |
|||
return new TemplateFile(File.ReadAllBytes(localCacheFile)); |
|||
} |
|||
|
|||
Logger.LogInformation("Downloading template: " + templateName + ", version: " + version); |
|||
|
|||
using (var client = new System.Net.Http.HttpClient()) |
|||
{ |
|||
client.Timeout = TimeSpan.FromMinutes(5); |
|||
var downloadUrl = "https://abp.io/downloads/templates/" + version + "/" + templateName + ".zip"; |
|||
var fileContents = await client.GetByteArrayAsync(downloadUrl); |
|||
File.WriteAllBytes(localCacheFile, fileContents); |
|||
return new TemplateFile(fileContents); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Volo.Abp.ProjectBuilding.Building |
|||
{ |
|||
public enum DatabaseProvider |
|||
{ |
|||
NotSpecified = 0, |
|||
EntityFrameworkCore = 1, |
|||
MongoDb = 2 |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Building |
|||
{ |
|||
public class GithubRepositoryInfo |
|||
{ |
|||
public string RepositoryNameWithOrganization { get; } |
|||
|
|||
public string RepositoryName { get; } |
|||
|
|||
public string AccessToken { get; } |
|||
|
|||
public GithubRepositoryInfo(string repositoryNameWithOrganization, string accessToken) |
|||
{ |
|||
if (!repositoryNameWithOrganization.Contains("/")) |
|||
{ |
|||
throw new ApplicationException($"{nameof(repositoryNameWithOrganization)} '{repositoryNameWithOrganization}' is not valid! It should be formatted as 'organization-name/repository-name'." ); |
|||
} |
|||
|
|||
RepositoryNameWithOrganization = repositoryNameWithOrganization; |
|||
RepositoryName = repositoryNameWithOrganization.Split('/')[1]; |
|||
AccessToken = accessToken; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.ProjectBuilding.Files; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Building |
|||
{ |
|||
public class ProjectBuildContext |
|||
{ |
|||
[NotNull] |
|||
public TemplateFile TemplateFile { get; } |
|||
|
|||
[NotNull] |
|||
public string Version { get; } |
|||
|
|||
[NotNull] |
|||
public ProjectBuildArgs BuildArgs { get; } |
|||
|
|||
[NotNull] |
|||
public TemplateInfo Template { get; } |
|||
|
|||
public FileEntryList Files { get; set; } |
|||
|
|||
public ProjectResult Result { get; set; } |
|||
|
|||
public ProjectBuildContext([NotNull] TemplateInfo template, |
|||
[NotNull] TemplateFile templateFile, |
|||
[NotNull] ProjectBuildArgs buildArgs, |
|||
[NotNull] string version) |
|||
{ |
|||
Template = Check.NotNull(template, nameof(template)); |
|||
TemplateFile = Check.NotNull(templateFile, nameof(templateFile)); |
|||
BuildArgs = Check.NotNull(buildArgs, nameof(buildArgs)); |
|||
Version = Check.NotNullOrWhiteSpace(version, nameof(version)); |
|||
|
|||
Result = new ProjectResult(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using Volo.Abp.ProjectBuilding.Files; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Building |
|||
{ |
|||
public static class ProjectBuildContextExtensions |
|||
{ |
|||
public static FileEntry GetFile(this ProjectBuildContext context, string filePath) |
|||
{ |
|||
var file = context.Files.FirstOrDefault(f => f.Name == filePath); |
|||
if (file == null) |
|||
{ |
|||
throw new ApplicationException("Could not find file: " + filePath); |
|||
} |
|||
|
|||
return file; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Building |
|||
{ |
|||
public class ProjectBuildPipeline |
|||
{ |
|||
public List<ProjectBuildPipelineStep> Steps { get; } |
|||
|
|||
public ProjectBuildPipeline() |
|||
{ |
|||
Steps = new List<ProjectBuildPipelineStep>(); |
|||
} |
|||
|
|||
public void Execute(ProjectBuildContext context) |
|||
{ |
|||
foreach (var step in Steps) |
|||
{ |
|||
step.Execute(context); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using Volo.Abp.ProjectBuilding.Building.Steps; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Building |
|||
{ |
|||
public static class ProjectBuildPipelineBuilder |
|||
{ |
|||
public static ProjectBuildPipeline Build(ProjectBuildContext context) |
|||
{ |
|||
var pipeline = new ProjectBuildPipeline(); |
|||
|
|||
pipeline.Steps.Add(new FileEntryListReadStep()); |
|||
pipeline.Steps.AddRange(context.Template.GetCustomSteps(context)); |
|||
pipeline.Steps.Add(new NugetReferenceReplaceStep()); |
|||
pipeline.Steps.Add(new TemplateCodeDeleteStep()); |
|||
pipeline.Steps.Add(new SolutionRenameStep()); |
|||
pipeline.Steps.Add(new CreateProjectResultZipStep()); |
|||
|
|||
return pipeline; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.ProjectBuilding.Building |
|||
{ |
|||
public abstract class ProjectBuildPipelineStep |
|||
{ |
|||
public abstract void Execute(ProjectBuildContext context); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.ProjectBuilding.Building |
|||
{ |
|||
public class ProjectResult |
|||
{ |
|||
public byte[] ZipContent { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using Ionic.Zip; |
|||
using Volo.Abp.ProjectBuilding.Files; |
|||
using Volo.Abp.ProjectBuilding.Zipping; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Building.Steps |
|||
{ |
|||
public class CreateProjectResultZipStep : ProjectBuildPipelineStep |
|||
{ |
|||
public override void Execute(ProjectBuildContext context) |
|||
{ |
|||
context.Result.ZipContent = CreateZipFileFromEntries(context.Files); |
|||
} |
|||
|
|||
private static byte[] CreateZipFileFromEntries(FileEntryList entries) |
|||
{ |
|||
using (var resultZipFile = new ZipFile()) |
|||
{ |
|||
entries.CopyToZipFile(resultZipFile); |
|||
return resultZipFile.GetBytes(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using System.IO; |
|||
using Ionic.Zip; |
|||
using Volo.Abp.ProjectBuilding.Files; |
|||
using Volo.Abp.ProjectBuilding.Zipping; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Building.Steps |
|||
{ |
|||
public class FileEntryListReadStep : ProjectBuildPipelineStep |
|||
{ |
|||
public override void Execute(ProjectBuildContext context) |
|||
{ |
|||
context.Files = GetEntriesFromZipFile(context.TemplateFile.FileBytes); |
|||
} |
|||
|
|||
private static FileEntryList GetEntriesFromZipFile(byte[] fileBytes) |
|||
{ |
|||
using (var memoryStream = new MemoryStream(fileBytes)) |
|||
{ |
|||
using (var templateZipFile = ZipFile.Read(memoryStream)) |
|||
{ |
|||
return templateZipFile.ToFileEntryList(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using HtmlAgilityPack; |
|||
using Volo.Abp.ProjectBuilding.Files; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Building.Steps |
|||
{ |
|||
public class NugetReferenceReplaceStep : ProjectBuildPipelineStep |
|||
{ |
|||
public override void Execute(ProjectBuildContext context) |
|||
{ |
|||
new NugetReferenceReplacer( |
|||
context.Files, |
|||
"MyCompanyName", |
|||
"MyProjectName", |
|||
context.Version |
|||
).Run(); |
|||
} |
|||
|
|||
private class NugetReferenceReplacer |
|||
{ |
|||
private readonly List<FileEntry> _entries; |
|||
private readonly string _companyNamePlaceHolder; |
|||
private readonly string _projectNamePlaceHolder; |
|||
private readonly string _latestNugetPackageVersion; |
|||
|
|||
public NugetReferenceReplacer(List<FileEntry> entries, string companyNamePlaceHolder, string projectNamePlaceHolder, string latestNugetPackageVersion) |
|||
{ |
|||
_entries = entries; |
|||
_companyNamePlaceHolder = companyNamePlaceHolder; |
|||
_projectNamePlaceHolder = projectNamePlaceHolder; |
|||
_latestNugetPackageVersion = latestNugetPackageVersion; |
|||
} |
|||
|
|||
public void Run() |
|||
{ |
|||
foreach (var fileEntry in _entries) |
|||
{ |
|||
if (fileEntry.Name.EndsWith(".csproj")) |
|||
{ |
|||
fileEntry.SetContent(ProcessFileContent(fileEntry.Content)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private string ProcessFileContent(string content) |
|||
{ |
|||
Check.NotNull(content, nameof(content)); |
|||
|
|||
var doc = new HtmlDocument(); |
|||
|
|||
doc.Load(GenerateStreamFromString(content)); |
|||
|
|||
var nodes = doc.DocumentNode.SelectNodes("//projectreference[@include]"); |
|||
|
|||
if (nodes == null) |
|||
{ |
|||
return content; |
|||
} |
|||
|
|||
return ProcessReferenceNodes(nodes, content); |
|||
} |
|||
|
|||
private string ProcessReferenceNodes(HtmlNodeCollection nodes, string content) |
|||
{ |
|||
Check.NotNull(nodes, nameof(nodes)); |
|||
Check.NotNull(content, nameof(content)); |
|||
|
|||
foreach (var node in nodes) |
|||
{ |
|||
var valueAttr = node.Attributes.FirstOrDefault(a => a.Name.ToLower() == "include"); |
|||
|
|||
// ReSharper disable once PossibleNullReferenceException : Can not be null because nodes are selected with include attribute filter in previous method
|
|||
if (valueAttr.Value.Contains($"{_companyNamePlaceHolder}.{_projectNamePlaceHolder}")) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var newValue = ConvertToNugetReference(valueAttr.Value); |
|||
|
|||
var oldLine = $"<ProjectReference Include=\"{valueAttr.Value}\""; |
|||
var oldLineAlt = $"<ProjectReference Include=\"{valueAttr.Value}\""; |
|||
var newLine = $"<PackageReference Include=\"{newValue}\" Version=\"{_latestNugetPackageVersion}\""; |
|||
|
|||
content = content.Replace(oldLine, newLine); |
|||
content = content.Replace(oldLineAlt, newLine); |
|||
} |
|||
|
|||
return content; |
|||
} |
|||
|
|||
private string ConvertToNugetReference(string oldValue) |
|||
{ |
|||
var directory = new DirectoryInfo(oldValue); |
|||
|
|||
var newValue = directory.Name.Replace(".csproj", ""); |
|||
|
|||
return newValue; |
|||
} |
|||
|
|||
private static Stream GenerateStreamFromString(string s) |
|||
{ |
|||
var stream = new MemoryStream(); |
|||
var writer = new StreamWriter(stream); |
|||
writer.Write(s); |
|||
writer.Flush(); |
|||
stream.Position = 0; |
|||
return stream; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Building.Steps |
|||
{ |
|||
public class RemoveProjectFromSolutionStep : ProjectBuildPipelineStep |
|||
{ |
|||
private readonly string _projectName; |
|||
private readonly string _solutionFilePath; |
|||
private readonly string _projectFolderPath; |
|||
|
|||
public RemoveProjectFromSolutionStep( |
|||
string projectName, |
|||
string solutionFilePath = null, |
|||
string projectFolderPath = null) |
|||
{ |
|||
_projectName = projectName; |
|||
_solutionFilePath = solutionFilePath ?? "/MyCompanyName.MyProjectName.sln"; |
|||
_projectFolderPath = projectFolderPath ?? ("/src/" + projectName); |
|||
} |
|||
|
|||
public override void Execute(ProjectBuildContext context) |
|||
{ |
|||
context.Files.RemoveAll(file => file.Name.StartsWith(_projectFolderPath)); |
|||
var solutionFile = context.GetFile(_solutionFilePath); |
|||
solutionFile.NormalizeLineEndings(); |
|||
solutionFile.SetLines(RemoveProject(solutionFile.GetLines().ToList())); |
|||
} |
|||
|
|||
private List<string> RemoveProject(List<string> solutionFileLines) |
|||
{ |
|||
var projectKey = FindProjectKey(solutionFileLines); |
|||
var newSolutionFileLines = new List<string>(); |
|||
var firstOccurence = true; |
|||
|
|||
for (var i = 0; i < solutionFileLines.Count; ++i) |
|||
{ |
|||
if (solutionFileLines[i].Contains(projectKey)) |
|||
{ |
|||
if (firstOccurence) |
|||
{ |
|||
firstOccurence = false; |
|||
++i; //Skip "EndProject" line too.
|
|||
} |
|||
|
|||
continue; |
|||
} |
|||
|
|||
newSolutionFileLines.Add(solutionFileLines[i]); |
|||
} |
|||
|
|||
return newSolutionFileLines; |
|||
} |
|||
|
|||
private string FindProjectKey(List<string> solutionFileLines) |
|||
{ |
|||
foreach (var solutionFileLine in solutionFileLines) |
|||
{ |
|||
if (solutionFileLine.Contains(_projectName)) |
|||
{ |
|||
var curlyBracketStartIndex = solutionFileLine.LastIndexOf("{", StringComparison.OrdinalIgnoreCase); |
|||
var curlyBracketEndIndex = solutionFileLine.LastIndexOf("}", StringComparison.OrdinalIgnoreCase); |
|||
return solutionFileLine.Substring(curlyBracketStartIndex + 1, curlyBracketEndIndex - curlyBracketStartIndex - 1); |
|||
} |
|||
} |
|||
|
|||
throw new ApplicationException($"The solution file '{_solutionFilePath}' does not contain a project '{_projectName}'"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,116 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Volo.Abp.ProjectBuilding.Files; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Building.Steps |
|||
{ |
|||
public class SolutionRenameStep : ProjectBuildPipelineStep |
|||
{ |
|||
public override void Execute(ProjectBuildContext context) |
|||
{ |
|||
new SolutionRenamer( |
|||
context.Files, |
|||
"MyCompanyName", |
|||
"MyProjectName", |
|||
context.BuildArgs.SolutionName.CompanyName, |
|||
context.BuildArgs.SolutionName.ProjectName |
|||
).Run(); |
|||
} |
|||
|
|||
private class SolutionRenamer |
|||
{ |
|||
private readonly List<FileEntry> _entries; |
|||
private readonly string _companyNamePlaceHolder; |
|||
private readonly string _projectNamePlaceHolder; |
|||
|
|||
private readonly string _companyName; |
|||
private readonly string _projectName; |
|||
|
|||
public SolutionRenamer(List<FileEntry> entries, string companyNamePlaceHolder, string projectNamePlaceHolder, string companyName, string projectName) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(companyName)) |
|||
{ |
|||
companyName = null; |
|||
} |
|||
|
|||
if (companyNamePlaceHolder == null && companyName != null) |
|||
{ |
|||
throw new UserFriendlyException($"Can not set {nameof(companyName)} if {nameof(companyNamePlaceHolder)} is null."); |
|||
} |
|||
|
|||
_entries = entries; |
|||
|
|||
_companyNamePlaceHolder = companyNamePlaceHolder; |
|||
_projectNamePlaceHolder = projectNamePlaceHolder ?? throw new ArgumentNullException(nameof(projectNamePlaceHolder)); |
|||
|
|||
_companyName = companyName; |
|||
_projectName = projectName ?? throw new ArgumentNullException(nameof(projectName)); |
|||
} |
|||
|
|||
public void Run() |
|||
{ |
|||
if (_companyNamePlaceHolder != null && _companyName != null) |
|||
{ |
|||
RenameDirectoryRecursively(_companyNamePlaceHolder, _companyName); |
|||
RenameAllFiles(_companyNamePlaceHolder, _companyName); |
|||
ReplaceContent(_companyNamePlaceHolder, _companyName); |
|||
} |
|||
else if (_companyNamePlaceHolder != null) |
|||
{ |
|||
RenameDirectoryRecursively(_companyNamePlaceHolder + "." + _projectNamePlaceHolder, _projectNamePlaceHolder); |
|||
RenameAllFiles(_companyNamePlaceHolder + "." + _projectNamePlaceHolder, _projectNamePlaceHolder); |
|||
ReplaceContent(_companyNamePlaceHolder + "." + _projectNamePlaceHolder, _projectNamePlaceHolder); |
|||
} |
|||
|
|||
RenameDirectoryRecursively(_projectNamePlaceHolder, _projectName); |
|||
RenameAllFiles(_projectNamePlaceHolder, _projectName); |
|||
ReplaceContent(_projectNamePlaceHolder, _projectName); |
|||
} |
|||
|
|||
private void RenameDirectoryRecursively(string placeHolder, string name) |
|||
{ |
|||
foreach (var entry in _entries.Where(e => e.IsDirectory)) |
|||
{ |
|||
if (entry.Name.Contains(placeHolder)) |
|||
{ |
|||
entry.SetName(entry.Name.Replace(placeHolder, name)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void RenameAllFiles(string placeHolder, string name) |
|||
{ |
|||
foreach (var entry in _entries.Where(e => !e.IsDirectory)) |
|||
{ |
|||
if (entry.Name.Contains(placeHolder)) |
|||
{ |
|||
entry.SetName(entry.Name.Replace(placeHolder, name)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void ReplaceContent(string placeHolder, string name) |
|||
{ |
|||
foreach (var entry in _entries.Where(e => !e.IsDirectory)) |
|||
{ |
|||
if (entry.Content.Length < placeHolder.Length) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (entry.IsBinaryFile) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var newContent = entry.Content.Replace(placeHolder, name); |
|||
if (newContent != entry.Content) |
|||
{ |
|||
entry.SetContent(newContent); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
using System; |
|||
using Volo.Abp.ProjectBuilding.Files; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Building.Steps |
|||
{ |
|||
public class SwitchEntityFrameworkCoreToMongoDbStep : ProjectBuildPipelineStep |
|||
{ |
|||
public override void Execute(ProjectBuildContext context) |
|||
{ |
|||
ChangeProjectReference(context); |
|||
ChangeWebModuleUsage(context); |
|||
ChangeConnectionString(context); |
|||
} |
|||
|
|||
private void ChangeProjectReference(ProjectBuildContext context) |
|||
{ |
|||
var file = context.GetFile("/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj"); |
|||
|
|||
file.NormalizeLineEndings(); |
|||
|
|||
var lines = file.GetLines(); |
|||
for (var i = 0; i < lines.Length; i++) |
|||
{ |
|||
if (lines[i].Contains("ProjectReference") && lines[i].Contains("MyCompanyName.MyProjectName.EntityFrameworkCore")) |
|||
{ |
|||
lines[i] = lines[i].Replace("EntityFrameworkCore", "MongoDB"); |
|||
file.SetLines(lines); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
throw new ApplicationException("Could not find the EntityFrameworkCore reference in the MyCompanyName.MyProjectName.Web.csproj!"); |
|||
} |
|||
|
|||
private void ChangeWebModuleUsage(ProjectBuildContext context) |
|||
{ |
|||
var file = context.GetFile("/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs"); |
|||
|
|||
file.NormalizeLineEndings(); |
|||
|
|||
file.RemoveTemplateCodeIfNot("EntityFrameworkCore"); |
|||
|
|||
var lines = file.GetLines(); |
|||
|
|||
for (var i = 0; i < lines.Length; i++) |
|||
{ |
|||
if (lines[i].Contains("using MyCompanyName.MyProjectName.EntityFrameworkCore")) |
|||
{ |
|||
lines[i] = "using MyCompanyName.MyProjectName.MongoDb;"; |
|||
} |
|||
else if (lines[i].Contains("MyProjectNameEntityFrameworkCoreModule")) |
|||
{ |
|||
lines[i] = lines[i].Replace("MyProjectNameEntityFrameworkCoreModule", "MyProjectNameMongoDbModule"); |
|||
} |
|||
} |
|||
|
|||
file.SetLines(lines); |
|||
} |
|||
|
|||
private void ChangeConnectionString(ProjectBuildContext context) |
|||
{ |
|||
var file = context.GetFile("/src/MyCompanyName.MyProjectName.Web/appsettings.json"); |
|||
|
|||
file.NormalizeLineEndings(); |
|||
|
|||
var lines = file.GetLines(); |
|||
for (var i = 0; i < lines.Length; i++) |
|||
{ |
|||
if (lines[i].Contains("Default") && lines[i].Contains("Database")) |
|||
{ |
|||
lines[i] = " \"Default\": \"mongodb://localhost:27017|MyProjectName\""; |
|||
file.SetLines(lines); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
throw new ApplicationException("Could not find the 'Default' connection string in appsettings.json file!"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using Volo.Abp.ProjectBuilding.Files; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Building.Steps |
|||
{ |
|||
public class TemplateCodeDeleteStep : ProjectBuildPipelineStep |
|||
{ |
|||
public override void Execute(ProjectBuildContext context) |
|||
{ |
|||
foreach (var file in context.Files) |
|||
{ |
|||
if (file.Name.EndsWith(".cs")) //TODO: Why only cs!
|
|||
{ |
|||
file.RemoveTemplateCode(); |
|||
file.RemoveTemplateCodeMarkers(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Building |
|||
{ |
|||
public abstract class TemplateInfo |
|||
{ |
|||
public string Name { get; } |
|||
|
|||
public DatabaseProvider DefaultDatabaseProvider { get; } |
|||
|
|||
protected TemplateInfo( |
|||
[NotNull] string name, |
|||
DatabaseProvider defaultDatabaseProvider = DatabaseProvider.NotSpecified) |
|||
{ |
|||
Name = Check.NotNullOrWhiteSpace(name, nameof(name)); |
|||
DefaultDatabaseProvider = Check.NotNull(defaultDatabaseProvider, nameof(defaultDatabaseProvider)); |
|||
} |
|||
|
|||
public virtual IEnumerable<ProjectBuildPipelineStep> GetCustomSteps(ProjectBuildContext context) |
|||
{ |
|||
return Array.Empty<ProjectBuildPipelineStep>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.ComponentModel.DataAnnotations.Schema; |
|||
using Volo.Abp.Domain.Entities.Auditing; |
|||
using Volo.Abp.ProjectBuilding.Building; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding |
|||
{ |
|||
[Table("Downloads")] |
|||
public class DownloadInfo : CreationAuditedAggregateRoot<int> |
|||
{ |
|||
[Required] |
|||
[StringLength(128)] |
|||
public string ProjectName { get; set; } |
|||
|
|||
[Required] |
|||
[StringLength(42)] |
|||
public string TemplateName { get; set; } |
|||
|
|||
[Required] |
|||
public DatabaseProvider DatabaseProvider { get; set; } |
|||
|
|||
[Required] |
|||
[StringLength(20)] |
|||
public string Version { get; set; } |
|||
|
|||
public int CreationDuration { get; set; } |
|||
|
|||
public DownloadInfo(string projectName, string templateName, DatabaseProvider databaseProvider, string version, int creationDuration) |
|||
{ |
|||
ProjectName = projectName; |
|||
TemplateName = templateName; |
|||
DatabaseProvider = databaseProvider; |
|||
Version = version; |
|||
CreationDuration = creationDuration; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,126 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Files |
|||
{ |
|||
public class FileEntry |
|||
{ |
|||
private static string[] BinaryFileExtensions = { |
|||
".exe", ".dll", ".bin", |
|||
".suo", ".obj", ".pdb", |
|||
".png", "jpg", "jpeg", ".ico" |
|||
,".woff", ".woff2", ".eot", ".svg", ".ttf" |
|||
}; |
|||
|
|||
public string Name { get; private set; } |
|||
|
|||
public bool IsDirectory { get; } |
|||
|
|||
public Encoding Encoding { get; } |
|||
|
|||
public byte[] Bytes { get; private set; } |
|||
|
|||
public string Content { get; private set; } |
|||
|
|||
public bool IsBinaryFile { get; private set; } |
|||
|
|||
public FileEntry(string name, byte[] bytes, bool isDirectory) |
|||
{ |
|||
Name = name; |
|||
Bytes = bytes; |
|||
IsDirectory = isDirectory; |
|||
|
|||
Encoding = CalculateEncoding(); |
|||
Content = Encoding.GetString(bytes); |
|||
IsBinaryFile = CalculateIsBinaryFile(); |
|||
} |
|||
|
|||
public void SetContent(string fileContent) |
|||
{ |
|||
Content = fileContent; |
|||
Bytes = Encoding.GetBytes(fileContent); |
|||
} |
|||
|
|||
public string[] GetLines() |
|||
{ |
|||
return Content.Split(new[] { Environment.NewLine, "\n" }, StringSplitOptions.None); |
|||
} |
|||
|
|||
public void SetLines(IEnumerable<string> lines) |
|||
{ |
|||
SetContent(lines.JoinAsString(Environment.NewLine)); |
|||
} |
|||
|
|||
public void SetName(string fileName) |
|||
{ |
|||
Name = fileName; |
|||
IsBinaryFile = CalculateIsBinaryFile(); |
|||
} |
|||
|
|||
public void NormalizeLineEndings() |
|||
{ |
|||
if (Content.IsNullOrEmpty()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
SetContent(Content.NormalizeLineEndings()); |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
var str = new StringBuilder(Name); |
|||
|
|||
if (IsDirectory) |
|||
{ |
|||
str.Append(" [DIR]"); |
|||
} |
|||
|
|||
str.Append($" [{Bytes.Length}]"); |
|||
|
|||
return str.ToString(); |
|||
} |
|||
|
|||
private Encoding CalculateEncoding() |
|||
{ |
|||
if (Bytes.IsNullOrEmpty()) |
|||
{ |
|||
return Encoding.ASCII; |
|||
} |
|||
|
|||
if (Bytes[0] == 0x2b && Bytes[1] == 0x2f && Bytes[2] == 0x76) |
|||
{ |
|||
return Encoding.UTF7; |
|||
} |
|||
|
|||
if (Bytes[0] == 0xef && Bytes[1] == 0xbb && Bytes[2] == 0xbf) |
|||
{ |
|||
return Encoding.UTF8; |
|||
} |
|||
|
|||
if (Bytes[0] == 0xff && Bytes[1] == 0xfe) |
|||
{ |
|||
return Encoding.Unicode; //UTF-16LE
|
|||
} |
|||
|
|||
if (Bytes[0] == 0xfe && Bytes[1] == 0xff) |
|||
{ |
|||
return Encoding.BigEndianUnicode; //UTF-16BE
|
|||
} |
|||
|
|||
if (Bytes[0] == 0 && Bytes[1] == 0 && Bytes[2] == 0xfe && Bytes[3] == 0xff) |
|||
{ |
|||
return Encoding.UTF32; |
|||
} |
|||
|
|||
return Encoding.UTF8; |
|||
} |
|||
|
|||
private bool CalculateIsBinaryFile() |
|||
{ |
|||
return BinaryFileExtensions.Any(ext => Name.EndsWith(ext)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,81 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Files |
|||
{ |
|||
public static class FileEntryExtensions |
|||
{ |
|||
public static void RemoveTemplateCode(this FileEntry file) |
|||
{ |
|||
RemoveMarkedTemplateCode(file, "<TEMPLATE-REMOVE>"); |
|||
} |
|||
|
|||
public static void RemoveTemplateCodeIf(this FileEntry file, string condition) |
|||
{ |
|||
RemoveByCondition(file, "IF", condition); |
|||
} |
|||
|
|||
public static void RemoveTemplateCodeIfNot(this FileEntry file, string condition) |
|||
{ |
|||
RemoveByCondition(file, "IF-NOT", condition); |
|||
} |
|||
|
|||
public static void RemoveTemplateCodeMarkers(this FileEntry file) |
|||
{ |
|||
file.NormalizeLineEndings(); |
|||
|
|||
var lines = file.GetLines(); |
|||
var newLines = new List<string>(); |
|||
|
|||
for (int i = 0; i < lines.Length; i++) |
|||
{ |
|||
if (lines[i].Contains("<TEMPLATE-REMOVE") || |
|||
lines[i].Contains("</TEMPLATE-REMOVE>")) |
|||
{ |
|||
//TODO: What if we use inline like: <TEMPLATE-REMOVE IF-NOT="..."> some-code </TEMPLATE-REMOVE>
|
|||
//TODO: This logic skips the code in that case. Should handle it
|
|||
++i; |
|||
} |
|||
|
|||
if (i < lines.Length) |
|||
{ |
|||
newLines.Add(lines[i]); |
|||
} |
|||
} |
|||
|
|||
file.SetLines(newLines); |
|||
} |
|||
|
|||
private static void RemoveByCondition(this FileEntry file, string conditionName, string condition) |
|||
{ |
|||
RemoveMarkedTemplateCode(file, $"<TEMPLATE-REMOVE {conditionName}='{condition}'>"); |
|||
} |
|||
|
|||
private static void RemoveMarkedTemplateCode(this FileEntry file, string beginMark) |
|||
{ |
|||
file.NormalizeLineEndings(); |
|||
|
|||
var lines = file.GetLines(); |
|||
var newLines = new List<string>(); |
|||
|
|||
for (int i = 0; i < lines.Length; i++) |
|||
{ |
|||
if (lines[i].Contains(beginMark)) |
|||
{ |
|||
while (!lines[i].Contains("</TEMPLATE-REMOVE>") && i < lines.Length) |
|||
{ |
|||
++i; |
|||
} |
|||
|
|||
++i; |
|||
} |
|||
|
|||
if (i < lines.Length) |
|||
{ |
|||
newLines.Add(lines[i]); |
|||
} |
|||
} |
|||
|
|||
file.SetLines(newLines); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Files |
|||
{ |
|||
public class FileEntryList : List<FileEntry> |
|||
{ |
|||
public FileEntryList(IEnumerable<FileEntry> entries) |
|||
: base(entries) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
using Ionic.Zip; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Files |
|||
{ |
|||
public static class FileEntryListExtensions |
|||
{ |
|||
public static void CopyToZipFile(this FileEntryList fileEntryList, ZipFile zipFile) |
|||
{ |
|||
foreach (var entry in fileEntryList) |
|||
{ |
|||
try |
|||
{ |
|||
zipFile.AddEntry(entry.Name, entry.Bytes); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.WriteLine(e); |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using System; |
|||
using Newtonsoft.Json; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Github |
|||
{ |
|||
[JsonObject] |
|||
public class GithubRelease |
|||
{ |
|||
[JsonProperty("id")] |
|||
public int Id { get; set; } |
|||
|
|||
[JsonProperty("name")] |
|||
public string Name { get; set; } |
|||
|
|||
[JsonProperty("prerelease")] |
|||
public bool IsPrerelease { get; set; } |
|||
|
|||
[JsonProperty("published_at")] |
|||
public DateTime PublishTime { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding |
|||
{ |
|||
public interface IProjectBuilder |
|||
{ |
|||
Task<ProjectBuildResult> BuildAsync(ProjectBuildArgs args); |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using Volo.Abp.ProjectBuilding.Building; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding |
|||
{ |
|||
public interface ITemplateInfoProvider |
|||
{ |
|||
TemplateInfo GetDefault(); |
|||
|
|||
TemplateInfo Get(string name); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding |
|||
{ |
|||
public interface ITemplateStore |
|||
{ |
|||
Task<TemplateFile> GetAsync(string templateInfoName, string version); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using JetBrains.Annotations; |
|||
using Volo.Abp.ProjectBuilding.Building; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding |
|||
{ |
|||
public class ProjectBuildArgs |
|||
{ |
|||
[NotNull] |
|||
public SolutionName SolutionName { get; } |
|||
|
|||
[CanBeNull] |
|||
public string TemplateName { get; set; } |
|||
|
|||
public DatabaseProvider DatabaseProvider { get; set; } |
|||
|
|||
public ProjectBuildArgs( |
|||
[NotNull] SolutionName solutionName, |
|||
DatabaseProvider databaseProvider = DatabaseProvider.NotSpecified, |
|||
[CanBeNull] string templateName = null) |
|||
{ |
|||
DatabaseProvider = databaseProvider; |
|||
TemplateName = templateName; |
|||
SolutionName = Check.NotNull(solutionName, nameof(solutionName)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
namespace Volo.Abp.ProjectBuilding |
|||
{ |
|||
public class ProjectBuildResult |
|||
{ |
|||
public byte[] ZipContent { get; } |
|||
|
|||
public string ProjectName { get; } |
|||
|
|||
public ProjectBuildResult(byte[] zipContent, string projectName) |
|||
{ |
|||
ZipContent = zipContent; |
|||
ProjectName = projectName; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Cli; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.ProjectBuilding.Building; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding |
|||
{ |
|||
public class ProjectBuilder : IProjectBuilder, ITransientDependency |
|||
{ |
|||
protected ITemplateStore TemplateStore { get; } |
|||
protected ITemplateInfoProvider TemplateInfoProvider { get; } |
|||
|
|||
public ProjectBuilder(ITemplateStore templateStore, ITemplateInfoProvider templateInfoProvider) |
|||
{ |
|||
TemplateStore = templateStore; |
|||
TemplateInfoProvider = templateInfoProvider; |
|||
} |
|||
|
|||
public async Task<ProjectBuildResult> BuildAsync(ProjectBuildArgs args) |
|||
{ |
|||
var templateInfo = GetTemplateInfo(args); |
|||
|
|||
args.TemplateName = templateInfo.Name; |
|||
|
|||
if (args.DatabaseProvider == DatabaseProvider.NotSpecified) |
|||
{ |
|||
if (templateInfo.DefaultDatabaseProvider != DatabaseProvider.NotSpecified) |
|||
{ |
|||
args.DatabaseProvider = templateInfo.DefaultDatabaseProvider; |
|||
} |
|||
} |
|||
|
|||
var version = CliService.Version; |
|||
|
|||
var templateFile = await TemplateStore.GetAsync(args.TemplateName, version); |
|||
|
|||
var context = new ProjectBuildContext( |
|||
templateInfo, |
|||
templateFile, |
|||
args, |
|||
version |
|||
); |
|||
|
|||
ProjectBuildPipelineBuilder.Build(context).Execute(context); |
|||
|
|||
return new ProjectBuildResult(context.Result.ZipContent, args.SolutionName.ProjectName); |
|||
} |
|||
|
|||
private TemplateInfo GetTemplateInfo(ProjectBuildArgs args) |
|||
{ |
|||
if (args.TemplateName.IsNullOrWhiteSpace()) |
|||
{ |
|||
return TemplateInfoProvider.GetDefault(); |
|||
} |
|||
else |
|||
{ |
|||
return TemplateInfoProvider.Get(args.TemplateName); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding |
|||
{ |
|||
public class SolutionName |
|||
{ |
|||
public string FullName { get; } |
|||
|
|||
public string CompanyName { get; } |
|||
|
|||
public string ProjectName { get; } |
|||
|
|||
private SolutionName(string fullName, string companyName, string projectName) |
|||
{ |
|||
FullName = fullName; |
|||
CompanyName = companyName; |
|||
ProjectName = projectName; |
|||
} |
|||
|
|||
public static SolutionName Parse(string fullName) |
|||
{ |
|||
if (fullName.Length < 1) |
|||
{ |
|||
throw new UserFriendlyException("Solution name is not valid. It should be 1 character length at minimum."); |
|||
} |
|||
|
|||
string companyName = null; |
|||
var projectName = fullName; |
|||
|
|||
if (fullName.Contains(".")) |
|||
{ |
|||
var lastDotIndex = fullName.LastIndexOf(".", StringComparison.OrdinalIgnoreCase); |
|||
companyName = fullName.Substring(0, lastDotIndex); |
|||
projectName = fullName.Substring(lastDotIndex + 1); |
|||
|
|||
if (companyName.Length < 1) |
|||
{ |
|||
throw new UserFriendlyException("Solution name is not valid. Company name should be 1 character length at minimum."); |
|||
} |
|||
|
|||
if (projectName.Length < 1) |
|||
{ |
|||
throw new UserFriendlyException("Solution name is not valid. Project name should be 1 character length at minimum."); |
|||
} |
|||
} |
|||
|
|||
return new SolutionName(fullName, companyName, projectName); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
namespace Volo.Abp.ProjectBuilding |
|||
{ |
|||
public class TemplateFile |
|||
{ |
|||
public byte[] FileBytes { get; } |
|||
|
|||
public TemplateFile(byte[] fileBytes) |
|||
{ |
|||
FileBytes = fileBytes; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.ProjectBuilding.Building; |
|||
using Volo.Abp.ProjectBuilding.Templates; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding |
|||
{ |
|||
public class TemplateInfoProvider : ITemplateInfoProvider, ITransientDependency |
|||
{ |
|||
public TemplateInfo GetDefault() |
|||
{ |
|||
return Get(MvcApplicationTemplate.TemplateName); |
|||
} |
|||
|
|||
public TemplateInfo Get(string name) |
|||
{ |
|||
switch (name) |
|||
{ |
|||
case MvcApplicationTemplate.TemplateName: |
|||
return new MvcApplicationTemplate(); |
|||
case MvcModuleTemplate.TemplateName: |
|||
return new MvcModuleTemplate(); |
|||
case ServiceTemplate.TemplateName: |
|||
return new ServiceTemplate(); |
|||
default: |
|||
throw new Exception("There is no template found with given name: " + name); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.ProjectBuilding.Building; |
|||
using Volo.Abp.ProjectBuilding.Building.Steps; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Templates |
|||
{ |
|||
public class MvcApplicationTemplate : TemplateInfo |
|||
{ |
|||
/// <summary>
|
|||
/// "mvc".
|
|||
/// </summary>
|
|||
public const string TemplateName = "mvc"; |
|||
|
|||
public MvcApplicationTemplate() |
|||
: base(TemplateName, DatabaseProvider.EntityFrameworkCore) |
|||
{ |
|||
} |
|||
|
|||
public override IEnumerable<ProjectBuildPipelineStep> GetCustomSteps(ProjectBuildContext context) |
|||
{ |
|||
var steps = new List<ProjectBuildPipelineStep>(); |
|||
SwitchDatabaseProvider(context, steps); |
|||
RemoveOtherDatabaseProviders(context, steps); |
|||
return steps; |
|||
} |
|||
|
|||
private static void SwitchDatabaseProvider(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps) |
|||
{ |
|||
if (context.BuildArgs.DatabaseProvider == DatabaseProvider.MongoDb) |
|||
{ |
|||
steps.Add(new SwitchEntityFrameworkCoreToMongoDbStep()); |
|||
} |
|||
} |
|||
|
|||
private static void RemoveOtherDatabaseProviders(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps) |
|||
{ |
|||
if (context.BuildArgs.DatabaseProvider != DatabaseProvider.EntityFrameworkCore) |
|||
{ |
|||
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore")); |
|||
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations")); |
|||
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Application.Tests", projectFolderPath: "test/MyCompanyName.MyProjectName.Application.Tests")); |
|||
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.Web.Tests", projectFolderPath: "test/MyCompanyName.MyProjectName.Web.Tests")); |
|||
} |
|||
|
|||
if (context.BuildArgs.DatabaseProvider != DatabaseProvider.MongoDb) |
|||
{ |
|||
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.MongoDB")); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using Volo.Abp.ProjectBuilding.Building; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Templates |
|||
{ |
|||
public class MvcModuleTemplate : TemplateInfo |
|||
{ |
|||
/// <summary>
|
|||
/// "mvcmodule".
|
|||
/// </summary>
|
|||
public const string TemplateName = "mvcmodule"; |
|||
|
|||
public MvcModuleTemplate() |
|||
: base(TemplateName) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using Volo.Abp.ProjectBuilding.Building; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Templates |
|||
{ |
|||
public class ServiceTemplate : TemplateInfo |
|||
{ |
|||
/// <summary>
|
|||
/// "service".
|
|||
/// </summary>
|
|||
public const string TemplateName = "service"; |
|||
|
|||
public ServiceTemplate() |
|||
: base(TemplateName) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using Ionic.Zip; |
|||
using Volo.Abp.ProjectBuilding.Files; |
|||
|
|||
namespace Volo.Abp.ProjectBuilding.Zipping |
|||
{ |
|||
public static class ZipFileExtensions |
|||
{ |
|||
public static byte[] GetBytes(this ZipFile zipFile) |
|||
{ |
|||
using (var ms = new MemoryStream()) |
|||
{ |
|||
zipFile.Save(ms); |
|||
return ms.ToArray(); |
|||
} |
|||
} |
|||
|
|||
public static FileEntryList ToFileEntryList(this ZipFile zipFile, string rootFolder = null) |
|||
{ |
|||
var zipEntries = zipFile.Entries; |
|||
|
|||
if (rootFolder != null) |
|||
{ |
|||
zipEntries = zipEntries.Where(entry => entry.FileName.StartsWith(rootFolder)).ToList(); |
|||
} |
|||
|
|||
var fileEntries = new List<FileEntry>(); |
|||
|
|||
foreach (var zipEntry in zipEntries) |
|||
{ |
|||
using (var entryStream = zipEntry.OpenReader()) |
|||
{ |
|||
var fileName = zipEntry.FileName; |
|||
|
|||
if (rootFolder != null) |
|||
{ |
|||
fileName = fileName.RemovePreFix(rootFolder); |
|||
} |
|||
|
|||
if (fileName.IsNullOrEmpty()) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
fileName = fileName.EnsureStartsWith('/'); |
|||
|
|||
fileEntries.Add(new FileEntry(fileName, entryStream.GetAllBytes(), zipEntry.IsDirectory)); |
|||
} |
|||
} |
|||
|
|||
return new FileEntryList(fileEntries); |
|||
} |
|||
} |
|||
} |
|||
@ -1,12 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Cli |
|||
{ |
|||
public class Program |
|||
{ |
|||
private static void Main(string[] args) |
|||
{ |
|||
Console.WriteLine("Welcome to ABP CLI..! This tool is in development. There is no functionality yet. So, bye bye... ;)"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Cli |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpCliCoreModule), |
|||
typeof(AbpAutofacModule) |
|||
)] |
|||
public class AbpCliModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Serilog; |
|||
using Serilog.Events; |
|||
using System.IO; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.Cli |
|||
{ |
|||
public class Program |
|||
{ |
|||
private static void Main(string[] args) |
|||
{ |
|||
Log.Logger = new LoggerConfiguration() |
|||
.MinimumLevel.Information() |
|||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning) |
|||
.Enrich.FromLogContext() |
|||
.WriteTo.File(Path.Combine(CliPaths.CliLogPath, "abp-cli-logs.txt")) |
|||
.WriteTo.Console() |
|||
.CreateLogger(); |
|||
|
|||
using (var application = AbpApplicationFactory.Create<AbpCliModule>( |
|||
options => |
|||
{ |
|||
options.UseAutofac(); |
|||
options.Services.AddLogging(c => c.AddSerilog()); |
|||
})) |
|||
{ |
|||
application.Initialize(); |
|||
|
|||
AsyncHelper.RunSync( |
|||
() => application.ServiceProvider |
|||
.GetRequiredService<CliService>() |
|||
.RunAsync(args) |
|||
); |
|||
|
|||
application.Shutdown(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using System.Diagnostics; |
|||
|
|||
namespace System.Reflection |
|||
{ |
|||
public static class AbpAssemblyExtensions |
|||
{ |
|||
public static string GetFileVersion(this Assembly assembly) |
|||
{ |
|||
return FileVersionInfo.GetVersionInfo(assembly.Location).FileVersion; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netcoreapp2.2</TargetFramework> |
|||
<LangVersion>latest</LangVersion> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Cli\Volo.Abp.Cli.csproj" /> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.Cli |
|||
{ |
|||
public class AbpCliTestBase : AbpIntegratedTest<AbpCliTestModule> |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Cli |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpTestBaseModule), |
|||
typeof(AbpCliModule) |
|||
)] |
|||
public class AbpCliTestModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
using Shouldly; |
|||
using System.Linq; |
|||
using Volo.Abp.Cli.Args; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.Cli |
|||
{ |
|||
public class CommandLineArgumentParser_Tests : AbpCliTestBase |
|||
{ |
|||
private readonly ICommandLineArgumentParser _commandLineArgumentParser; |
|||
|
|||
public CommandLineArgumentParser_Tests() |
|||
{ |
|||
_commandLineArgumentParser = GetRequiredService<ICommandLineArgumentParser>(); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(null)] |
|||
[InlineData(new object[] { new string[0] })] |
|||
public void Should_Parse_Empty_Arguments(string[] args) |
|||
{ |
|||
var commandLineArgs = _commandLineArgumentParser.Parse(args); |
|||
commandLineArgs.Command.ShouldBeNull(); |
|||
commandLineArgs.Target.ShouldBeNull(); |
|||
commandLineArgs.Options.Any().ShouldBeFalse(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Work_With_Only_Command() |
|||
{ |
|||
var commandLineArgs = _commandLineArgumentParser.Parse(new[] { "mycommand" }); |
|||
commandLineArgs.Command.ShouldBe("mycommand"); |
|||
commandLineArgs.Target.ShouldBeNull(); |
|||
commandLineArgs.Options.Any().ShouldBeFalse(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Work_With_Command_And_Target() |
|||
{ |
|||
var commandLineArgs = _commandLineArgumentParser.Parse(new[] { "mycommand", "mytarget" }); |
|||
commandLineArgs.Command.ShouldBe("mycommand"); |
|||
commandLineArgs.Target.ShouldBe("mytarget"); |
|||
commandLineArgs.Options.Any().ShouldBeFalse(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_Work_With_Command_And_Target_And_Options() |
|||
{ |
|||
var commandLineArgs = _commandLineArgumentParser.Parse(new[] |
|||
{ |
|||
"mycommand", "mytarget", "-a", "value1", "-b", "--optionC", "value2" |
|||
}); |
|||
|
|||
commandLineArgs.Command.ShouldBe("mycommand"); |
|||
commandLineArgs.Target.ShouldBe("mytarget"); |
|||
commandLineArgs.Options.Any().ShouldBeTrue(); |
|||
|
|||
commandLineArgs.Options.ShouldContainKey("a"); |
|||
commandLineArgs.Options["a"].ShouldBe("value1"); |
|||
|
|||
commandLineArgs.Options.ShouldContainKey("b"); |
|||
commandLineArgs.Options["b"].ShouldBeNull(); |
|||
|
|||
commandLineArgs.Options.ShouldContainKey("optionC"); |
|||
commandLineArgs.Options["optionC"].ShouldBe("value2"); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue