feat: 添加批量和目录转换功能
批量转换服务: - BatchConversionService: 批量转换服务实现 - ConvertDirectoryAsync: 目录转换(递归所有子目录) - ConvertFilesAsync: 多文件批量转换 - 保持原始目录结构 - 自动生成批量报告 CLI 工具增强: - --batch/-b: 启用批量转换模式 - --recursive/-r: 递归处理子目录 - --verbose: 显示详细信息 - convert 命令自动检测目录/文件模式 批量转换结果: - BatchConversionResult: 批量转换结果 - ConvertedFileInfo: 成功文件详情 - FailedFileInfo: 失败文件详情 - 统计:总数/成功/失败/耗时 测试覆盖: - ConvertDirectoryAsync_ValidDirectory: 目录转换测试 - ConvertFilesAsync_MultipleFiles: 多文件测试 总计:40 个测试全部通过 ✅ 使用示例: # 转换整个目录 dotnet run --project CodePlay.CLI -- convert -s CSharp -t Java -i ./src -o ./output-java -b # 递归转换(默认) dotnet run --project CodePlay.CLI -- convert -s CSharp -t Java -i ./src -b -r true # 详细输出 dotnet run --project CodePlay.CLI -- convert -s CSharp -t Java -i ./src -b --verbose Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
using CodePlay.Core.Models;
|
||||
using CodePlay.Core.Common;
|
||||
|
||||
namespace CodePlay.Core.Services;
|
||||
|
||||
public interface IBatchConversionService
|
||||
{
|
||||
Task<BatchConversionResult> ConvertDirectoryAsync(string sourceDirectory, string targetDirectory,
|
||||
LanguageType sourceLanguage, LanguageType targetLanguage, ConversionOptions? options = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<BatchConversionResult> ConvertFilesAsync(IEnumerable<string> sourceFiles, string targetDirectory,
|
||||
LanguageType sourceLanguage, LanguageType targetLanguage, ConversionOptions? options = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public class BatchConversionService : IBatchConversionService
|
||||
{
|
||||
private readonly ConversionService _conversionService;
|
||||
private readonly IReportStorageService _reportStorageService;
|
||||
|
||||
public BatchConversionService(ConversionService conversionService, IReportStorageService reportStorageService)
|
||||
{
|
||||
_conversionService = conversionService;
|
||||
_reportStorageService = reportStorageService;
|
||||
}
|
||||
|
||||
public async Task<BatchConversionResult> ConvertDirectoryAsync(string sourceDirectory, string targetDirectory,
|
||||
LanguageType sourceLanguage, LanguageType targetLanguage, ConversionOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = new BatchConversionResult
|
||||
{
|
||||
SourceDirectory = sourceDirectory,
|
||||
TargetDirectory = targetDirectory,
|
||||
SourceLanguage = sourceLanguage,
|
||||
TargetLanguage = targetLanguage,
|
||||
StartedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
if (!Directory.Exists(sourceDirectory))
|
||||
{
|
||||
result.Success = false;
|
||||
result.ErrorMessage = $"Source directory not found: {sourceDirectory}";
|
||||
return result;
|
||||
}
|
||||
|
||||
var fileExtension = GetFileExtension(sourceLanguage);
|
||||
var sourceFiles = Directory.GetFiles(sourceDirectory, $"*{fileExtension}", SearchOption.AllDirectories);
|
||||
|
||||
return await ConvertFilesAsync(sourceFiles, targetDirectory, sourceLanguage, targetLanguage, options, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<BatchConversionResult> ConvertFilesAsync(IEnumerable<string> sourceFiles, string targetDirectory,
|
||||
LanguageType sourceLanguage, LanguageType targetLanguage, ConversionOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = new BatchConversionResult
|
||||
{
|
||||
TargetDirectory = targetDirectory,
|
||||
SourceLanguage = sourceLanguage,
|
||||
TargetLanguage = targetLanguage,
|
||||
StartedAt = DateTime.UtcNow,
|
||||
TotalFiles = sourceFiles.Count()
|
||||
};
|
||||
|
||||
if (!Directory.Exists(targetDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
}
|
||||
|
||||
var targetExtension = GetFileExtension(targetLanguage);
|
||||
|
||||
foreach (var sourceFile in sourceFiles)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
result.CancelledAt = DateTime.UtcNow;
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var relativePath = Path.GetRelativePath(Path.GetDirectoryName(sourceFile)!, sourceFile);
|
||||
var targetFileName = Path.ChangeExtension(relativePath, targetExtension);
|
||||
var targetFilePath = Path.Combine(targetDirectory, targetFileName);
|
||||
|
||||
var targetFileDir = Path.GetDirectoryName(targetFilePath)!;
|
||||
if (!Directory.Exists(targetFileDir))
|
||||
{
|
||||
Directory.CreateDirectory(targetFileDir);
|
||||
}
|
||||
|
||||
var sourceCode = await File.ReadAllTextAsync(sourceFile, cancellationToken);
|
||||
var conversionResult = await _conversionService.ConvertAsync(
|
||||
sourceCode, sourceLanguage, targetLanguage, options);
|
||||
|
||||
if (conversionResult.Success)
|
||||
{
|
||||
await File.WriteAllTextAsync(targetFilePath, conversionResult.TransformedCode, cancellationToken);
|
||||
|
||||
result.SuccessfulFiles++;
|
||||
result.ConvertedFiles.Add(new ConvertedFileInfo
|
||||
{
|
||||
SourceFile = sourceFile,
|
||||
TargetFile = targetFilePath,
|
||||
LinesConverted = conversionResult.Report?.LinesConverted ?? 0,
|
||||
ClassesConverted = conversionResult.Report?.ClassesConverted ?? 0,
|
||||
MethodsConverted = conversionResult.Report?.MethodsConverted ?? 0,
|
||||
Warnings = conversionResult.Warnings.Count,
|
||||
Issues = conversionResult.Report?.Issues.Count ?? 0
|
||||
});
|
||||
|
||||
if (conversionResult.Report != null)
|
||||
{
|
||||
conversionResult.Report.ProjectId = "batch-" + DateTime.UtcNow.ToString("yyyyMMdd");
|
||||
await _reportStorageService.SaveReportAsync(conversionResult.Report);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.FailedFiles++;
|
||||
result.FailedFileList.Add(new FailedFileInfo
|
||||
{
|
||||
SourceFile = sourceFile,
|
||||
ErrorMessage = conversionResult.ErrorMessage ?? "Unknown error"
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.FailedFiles++;
|
||||
result.FailedFileList.Add(new FailedFileInfo
|
||||
{
|
||||
SourceFile = sourceFile,
|
||||
ErrorMessage = ex.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result.CompletedAt = DateTime.UtcNow;
|
||||
result.Duration = result.CompletedAt - result.StartedAt;
|
||||
result.Success = result.FailedFiles == 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private string GetFileExtension(LanguageType language)
|
||||
{
|
||||
return language switch
|
||||
{
|
||||
LanguageType.CSharp => ".cs",
|
||||
LanguageType.Java => ".java",
|
||||
LanguageType.CPlusPlus => ".cpp",
|
||||
_ => throw new ArgumentException($"Unsupported language: {language}")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class BatchConversionResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
public string SourceDirectory { get; set; } = string.Empty;
|
||||
public string TargetDirectory { get; set; } = string.Empty;
|
||||
public LanguageType SourceLanguage { get; set; }
|
||||
public LanguageType TargetLanguage { get; set; }
|
||||
public int TotalFiles { get; set; }
|
||||
public int SuccessfulFiles { get; set; }
|
||||
public int FailedFiles { get; set; }
|
||||
public DateTime StartedAt { get; set; }
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
public DateTime? CancelledAt { get; set; }
|
||||
public TimeSpan? Duration { get; set; }
|
||||
public List<ConvertedFileInfo> ConvertedFiles { get; set; } = new();
|
||||
public List<FailedFileInfo> FailedFileList { get; set; } = new();
|
||||
}
|
||||
|
||||
public class ConvertedFileInfo
|
||||
{
|
||||
public string SourceFile { get; set; } = string.Empty;
|
||||
public string TargetFile { get; set; } = string.Empty;
|
||||
public int LinesConverted { get; set; }
|
||||
public int ClassesConverted { get; set; }
|
||||
public int MethodsConverted { get; set; }
|
||||
public int Warnings { get; set; }
|
||||
public int Issues { get; set; }
|
||||
}
|
||||
|
||||
public class FailedFileInfo
|
||||
{
|
||||
public string SourceFile { get; set; } = string.Empty;
|
||||
public string? TargetFile { get; set; }
|
||||
public string ErrorMessage { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -30,6 +30,26 @@ public class ConversionService
|
||||
_converters[(source, target)] = converter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换代码 (简化版)
|
||||
/// </summary>
|
||||
public async Task<ConversionResult> ConvertAsync(
|
||||
string sourceCode,
|
||||
LanguageType sourceLanguage,
|
||||
LanguageType targetLanguage,
|
||||
ConversionOptions? options = null)
|
||||
{
|
||||
var request = new ConversionRequest
|
||||
{
|
||||
SourceCode = sourceCode,
|
||||
SourceLanguage = sourceLanguage,
|
||||
TargetLanguage = targetLanguage,
|
||||
Options = options
|
||||
};
|
||||
|
||||
return await ConvertAsync(request, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换代码
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user