mirror of
https://github.com/go-i2p/go-i2ptunnel-config.git
synced 2025-12-20 15:15:52 -05:00
Add batch processing support for multiple configuration files
This commit is contained in:
@@ -45,6 +45,12 @@ Test conversion (dry-run):
|
||||
go-i2ptunnel-config --dry-run tunnel.config
|
||||
```
|
||||
|
||||
Batch process multiple files:
|
||||
```bash
|
||||
go-i2ptunnel-config --batch "*.config"
|
||||
go-i2ptunnel-config --batch --out-format ini "tunnels/*.properties"
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork repository
|
||||
|
||||
304
lib/convert.go
304
lib/convert.go
@@ -3,69 +3,155 @@ package i2pconv
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// ConvertCommand converts the input configuration file to the specified output format.
|
||||
// It supports automatic format detection based on file extensions and provides comprehensive
|
||||
// argument validation. The command can validate-only, perform dry-run conversions, or
|
||||
// write output to specified files.
|
||||
//
|
||||
// Parameters:
|
||||
// - c (*cli.Context): The CLI context containing the command-line arguments and flags.
|
||||
//
|
||||
// Arguments:
|
||||
// - input-file: Path to the input configuration file (required)
|
||||
// - output-file: Path for output file (optional, defaults based on input name and format)
|
||||
//
|
||||
// Flags:
|
||||
// - in-format: Input format (properties|ini|yaml) - auto-detected if not specified
|
||||
// - out-format: Output format (properties|ini|yaml) - defaults to yaml
|
||||
// - output: Output file path - takes precedence over positional output-file argument
|
||||
// - validate: Validate input without performing conversion
|
||||
// - strict: Enable strict validation of the configuration
|
||||
// - dry-run: Print output to console instead of writing to file
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if any step fails, including argument validation, file I/O,
|
||||
// format detection, parsing, validation, or output generation.
|
||||
//
|
||||
// Format Detection:
|
||||
//
|
||||
// Auto-detects input format based on file extension:
|
||||
// - .config, .properties, .prop -> properties format
|
||||
// - .conf, .ini -> ini format
|
||||
// - .yaml, .yml -> yaml format
|
||||
//
|
||||
// Related:
|
||||
// - Converter.DetectFormat, Converter.ParseInput, Converter.validate, Converter.generateOutput
|
||||
func ConvertCommand(c *cli.Context) error {
|
||||
// Validate required arguments
|
||||
if c.NArg() < 1 {
|
||||
return fmt.Errorf("input file is required\nUsage: %s <input-file> [output-file]", c.App.Name)
|
||||
// BatchResult represents the result of processing a single file in batch mode
|
||||
type BatchResult struct {
|
||||
InputFile string
|
||||
OutputFile string
|
||||
InputFormat string
|
||||
OutputFormat string
|
||||
Success bool
|
||||
Error error
|
||||
}
|
||||
|
||||
inputFile := c.Args().Get(0)
|
||||
outputFile := c.Args().Get(1)
|
||||
// ProcessBatch processes multiple files using glob patterns and returns results for each file.
|
||||
// It continues processing even if some files fail, collecting all results for reporting.
|
||||
//
|
||||
// Parameters:
|
||||
// - pattern: Glob pattern to match input files
|
||||
// - c: CLI context containing flags and options
|
||||
//
|
||||
// Returns:
|
||||
// - []BatchResult: Results for each processed file
|
||||
// - error: Fatal error that prevented batch processing from starting
|
||||
func ProcessBatch(pattern string, c *cli.Context) ([]BatchResult, error) {
|
||||
// Expand glob pattern to get list of files
|
||||
files, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid glob pattern '%s': %w", pattern, err)
|
||||
}
|
||||
|
||||
// Get flags with proper defaults
|
||||
if len(files) == 0 {
|
||||
return nil, fmt.Errorf("no files match pattern '%s'", pattern)
|
||||
}
|
||||
|
||||
// Get flags
|
||||
inputFormat := c.String("in-format")
|
||||
outputFormat := c.String("out-format")
|
||||
outputFlag := c.String("output")
|
||||
validateOnly := c.Bool("validate")
|
||||
strict := c.Bool("strict")
|
||||
dryRun := c.Bool("dry-run")
|
||||
|
||||
// Handle output file priority: --output flag takes precedence over positional argument
|
||||
if outputFlag != "" {
|
||||
outputFile = outputFlag
|
||||
}
|
||||
|
||||
// Initialize converter with options
|
||||
// Process each file individually
|
||||
results := make([]BatchResult, 0, len(files))
|
||||
converter := &Converter{strict: strict}
|
||||
|
||||
for _, inputFile := range files {
|
||||
result := BatchResult{
|
||||
InputFile: inputFile,
|
||||
OutputFormat: outputFormat,
|
||||
}
|
||||
|
||||
// Process single file using existing logic
|
||||
err := processSingleFile(inputFile, "", inputFormat, outputFormat, validateOnly, dryRun, converter)
|
||||
if err != nil {
|
||||
result.Success = false
|
||||
result.Error = err
|
||||
} else {
|
||||
result.Success = true
|
||||
result.OutputFile = generateOutputFilename(inputFile, outputFormat)
|
||||
|
||||
// Detect input format for reporting
|
||||
if inputFormat == "" {
|
||||
detectedFormat, err := converter.DetectFormat(inputFile)
|
||||
if err == nil {
|
||||
result.InputFormat = detectedFormat
|
||||
}
|
||||
} else {
|
||||
result.InputFormat = inputFormat
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// reportBatchResults prints a summary of batch processing results and returns appropriate error.
|
||||
// It reports both successful and failed file processing, providing clear feedback to users.
|
||||
//
|
||||
// Parameters:
|
||||
// - results: Slice of BatchResult containing processing results for each file
|
||||
// - validateOnly: Whether the operation was validation-only
|
||||
// - dryRun: Whether the operation was a dry-run
|
||||
//
|
||||
// Returns:
|
||||
// - error: Non-nil if any files failed processing, nil if all succeeded
|
||||
func reportBatchResults(results []BatchResult, validateOnly, dryRun bool) error {
|
||||
successCount := 0
|
||||
failureCount := 0
|
||||
|
||||
// Report individual results
|
||||
for _, result := range results {
|
||||
if result.Success {
|
||||
successCount++
|
||||
if validateOnly {
|
||||
fmt.Printf("✓ Configuration in '%s' is valid (%s format)\n",
|
||||
result.InputFile, result.InputFormat)
|
||||
} else if dryRun {
|
||||
// Individual dry-run output already printed during processing
|
||||
fmt.Printf("✓ Dry-run conversion '%s' (%s -> %s)\n",
|
||||
result.InputFile, result.InputFormat, result.OutputFormat)
|
||||
} else {
|
||||
fmt.Printf("✓ Converted '%s' (%s) -> '%s' (%s)\n",
|
||||
result.InputFile, result.InputFormat, result.OutputFile, result.OutputFormat)
|
||||
}
|
||||
} else {
|
||||
failureCount++
|
||||
fmt.Printf("✗ Failed to process '%s': %v\n", result.InputFile, result.Error)
|
||||
}
|
||||
}
|
||||
|
||||
// Print summary
|
||||
total := len(results)
|
||||
if validateOnly {
|
||||
fmt.Printf("\nValidation summary: %d/%d files valid, %d failed\n", successCount, total, failureCount)
|
||||
} else if dryRun {
|
||||
fmt.Printf("\nDry-run summary: %d/%d files processed, %d failed\n", successCount, total, failureCount)
|
||||
} else {
|
||||
fmt.Printf("\nBatch conversion summary: %d/%d files converted, %d failed\n", successCount, total, failureCount)
|
||||
}
|
||||
|
||||
// Return error if any files failed
|
||||
if failureCount > 0 {
|
||||
return fmt.Errorf("%d of %d files failed processing", failureCount, total)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processSingleFile handles the conversion of a single file with the given parameters.
|
||||
// This function contains the core conversion logic extracted from ConvertCommand to enable reuse
|
||||
// in both single-file and batch processing modes.
|
||||
//
|
||||
// Parameters:
|
||||
// - inputFile: Path to input configuration file
|
||||
// - outputFile: Path for output file (empty string for auto-generation)
|
||||
// - inputFormat: Input format (empty string for auto-detection)
|
||||
// - outputFormat: Output format
|
||||
// - validateOnly: Whether to only validate without conversion
|
||||
// - dryRun: Whether to print output instead of writing to file
|
||||
// - converter: Converter instance with configuration
|
||||
//
|
||||
// Returns:
|
||||
// - error: Any error that occurred during processing
|
||||
func processSingleFile(inputFile, outputFile, inputFormat, outputFormat string, validateOnly, dryRun bool, converter *Converter) error {
|
||||
// Read input file
|
||||
inputData, err := os.ReadFile(inputFile)
|
||||
if err != nil {
|
||||
@@ -93,7 +179,6 @@ func ConvertCommand(c *cli.Context) error {
|
||||
|
||||
// If validate-only mode, we're done
|
||||
if validateOnly {
|
||||
fmt.Printf("✓ Configuration in '%s' is valid (%s format)\n", inputFile, inputFormat)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -105,7 +190,7 @@ func ConvertCommand(c *cli.Context) error {
|
||||
|
||||
// Handle output - either print to console or write to file
|
||||
if dryRun {
|
||||
fmt.Printf("# Converted from %s to %s format:\n", inputFormat, outputFormat)
|
||||
fmt.Printf("# Converted '%s' from %s to %s format:\n", inputFile, inputFormat, outputFormat)
|
||||
fmt.Println(string(outputData))
|
||||
return nil
|
||||
}
|
||||
@@ -120,7 +205,126 @@ func ConvertCommand(c *cli.Context) error {
|
||||
return fmt.Errorf("failed to write output file '%s': %w", outputFile, err)
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Converted '%s' (%s) -> '%s' (%s)\n", inputFile, inputFormat, outputFile, outputFormat)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertCommand converts the input configuration file to the specified output format.
|
||||
// It supports automatic format detection based on file extensions and provides comprehensive
|
||||
// argument validation. The command can validate-only, perform dry-run conversions, write
|
||||
// output to specified files, or process multiple files in batch mode.
|
||||
//
|
||||
// Parameters:
|
||||
// - c (*cli.Context): The CLI context containing the command-line arguments and flags.
|
||||
//
|
||||
// Arguments:
|
||||
// - input-file: Path to the input configuration file or glob pattern (required)
|
||||
// - output-file: Path for output file (optional, defaults based on input name and format)
|
||||
//
|
||||
// Flags:
|
||||
// - in-format: Input format (properties|ini|yaml) - auto-detected if not specified
|
||||
// - out-format: Output format (properties|ini|yaml) - defaults to yaml
|
||||
// - output: Output file path - takes precedence over positional output-file argument
|
||||
// - validate: Validate input without performing conversion
|
||||
// - strict: Enable strict validation of the configuration
|
||||
// - dry-run: Print output to console instead of writing to file
|
||||
// - batch: Process multiple files using glob patterns
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if any step fails, including argument validation, file I/O,
|
||||
// format detection, parsing, validation, or output generation.
|
||||
//
|
||||
// Format Detection:
|
||||
//
|
||||
// Auto-detects input format based on file extension:
|
||||
// - .config, .properties, .prop -> properties format
|
||||
// - .conf, .ini -> ini format
|
||||
// - .yaml, .yml -> yaml format
|
||||
//
|
||||
// Batch Processing:
|
||||
// - When --batch flag is used, the input argument is treated as a glob pattern
|
||||
// - Multiple files are processed independently with individual success/failure reporting
|
||||
// - Processing continues even if some files fail
|
||||
//
|
||||
// Related:
|
||||
// - Converter.DetectFormat, Converter.ParseInput, Converter.validate, Converter.generateOutput
|
||||
// - ProcessBatch, processSingleFile
|
||||
func ConvertCommand(c *cli.Context) error {
|
||||
// Validate required arguments
|
||||
if c.NArg() < 1 {
|
||||
return fmt.Errorf("input file is required\nUsage: %s <input-file> [output-file]", c.App.Name)
|
||||
}
|
||||
|
||||
inputArg := c.Args().Get(0)
|
||||
outputFile := c.Args().Get(1)
|
||||
|
||||
// Get flags with proper defaults
|
||||
inputFormat := c.String("in-format")
|
||||
outputFormat := c.String("out-format")
|
||||
outputFlag := c.String("output")
|
||||
validateOnly := c.Bool("validate")
|
||||
strict := c.Bool("strict")
|
||||
dryRun := c.Bool("dry-run")
|
||||
batchMode := c.Bool("batch")
|
||||
|
||||
// Handle output file priority: --output flag takes precedence over positional argument
|
||||
if outputFlag != "" {
|
||||
outputFile = outputFlag
|
||||
}
|
||||
|
||||
// Check for incompatible options in batch mode
|
||||
if batchMode {
|
||||
if outputFile != "" {
|
||||
return fmt.Errorf("cannot specify output file in batch mode - files are auto-generated")
|
||||
}
|
||||
|
||||
// Process batch
|
||||
results, err := ProcessBatch(inputArg, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("batch processing failed: %w", err)
|
||||
}
|
||||
|
||||
// Report results
|
||||
return reportBatchResults(results, validateOnly, dryRun)
|
||||
}
|
||||
|
||||
// Single file processing (original behavior)
|
||||
inputFile := inputArg
|
||||
converter := &Converter{strict: strict}
|
||||
|
||||
// Use extracted single file processing logic
|
||||
err := processSingleFile(inputFile, outputFile, inputFormat, outputFormat, validateOnly, dryRun, converter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Report success for single file (only if not validate-only or dry-run, as those print their own messages)
|
||||
if !validateOnly && !dryRun {
|
||||
// Auto-detect format for reporting if not specified
|
||||
reportInputFormat := inputFormat
|
||||
if reportInputFormat == "" {
|
||||
if detected, err := converter.DetectFormat(inputFile); err == nil {
|
||||
reportInputFormat = detected
|
||||
}
|
||||
}
|
||||
|
||||
// Generate output filename for reporting if not specified
|
||||
reportOutputFile := outputFile
|
||||
if reportOutputFile == "" {
|
||||
reportOutputFile = generateOutputFilename(inputFile, outputFormat)
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Converted '%s' (%s) -> '%s' (%s)\n", inputFile, reportInputFormat, reportOutputFile, outputFormat)
|
||||
} else if validateOnly {
|
||||
// Auto-detect format for validation reporting if not specified
|
||||
reportInputFormat := inputFormat
|
||||
if reportInputFormat == "" {
|
||||
if detected, err := converter.DetectFormat(inputFile); err == nil {
|
||||
reportInputFormat = detected
|
||||
}
|
||||
}
|
||||
fmt.Printf("✓ Configuration in '%s' is valid (%s format)\n", inputFile, reportInputFormat)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package i2pconv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -520,3 +521,282 @@ func TestConvertCommandErrorHandling(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestProcessBatchWithGlob tests the glob pattern functionality using filepath.Glob directly
|
||||
func TestProcessBatchWithGlob(t *testing.T) {
|
||||
// Create temporary directory for test files
|
||||
tempDir := t.TempDir()
|
||||
|
||||
// Create test properties files
|
||||
validProperties := `name=test-tunnel
|
||||
type=httpclient
|
||||
interface=127.0.0.1
|
||||
listenPort=8080
|
||||
`
|
||||
|
||||
// Write test files
|
||||
testFiles := map[string]string{
|
||||
"tunnel1.properties": validProperties,
|
||||
"tunnel2.config": validProperties,
|
||||
"tunnel3.properties": validProperties,
|
||||
}
|
||||
|
||||
for filename, content := range testFiles {
|
||||
err := os.WriteFile(filepath.Join(tempDir, filename), []byte(content), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test file %s: %v", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pattern string
|
||||
expectFiles int
|
||||
expectError bool
|
||||
errorMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid glob pattern for properties",
|
||||
pattern: filepath.Join(tempDir, "*.properties"),
|
||||
expectFiles: 2,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid glob pattern for config files",
|
||||
pattern: filepath.Join(tempDir, "*.config"),
|
||||
expectFiles: 1,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "no matching files",
|
||||
pattern: filepath.Join(tempDir, "*.nonexistent"),
|
||||
expectFiles: 0,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "all files",
|
||||
pattern: filepath.Join(tempDir, "*"),
|
||||
expectFiles: 3,
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
files, err := filepath.Glob(tt.pattern)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("expected error but got none")
|
||||
} else if tt.errorMsg != "" && !strings.Contains(err.Error(), tt.errorMsg) {
|
||||
t.Errorf("expected error to contain %q, got: %q", tt.errorMsg, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(files) != tt.expectFiles {
|
||||
t.Errorf("expected %d files, got %d", tt.expectFiles, len(files))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestProcessSingleFile tests the extracted single file processing logic
|
||||
func TestProcessSingleFile(t *testing.T) {
|
||||
// Create temporary directory for test files
|
||||
tempDir := t.TempDir()
|
||||
|
||||
validProperties := `name=test-tunnel
|
||||
type=httpclient
|
||||
interface=127.0.0.1
|
||||
listenPort=8080
|
||||
`
|
||||
|
||||
invalidProperties := `name=
|
||||
type=invalid-type
|
||||
`
|
||||
|
||||
// Write test files
|
||||
validFile := filepath.Join(tempDir, "valid.properties")
|
||||
invalidFile := filepath.Join(tempDir, "invalid.properties")
|
||||
|
||||
err := os.WriteFile(validFile, []byte(validProperties), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create valid test file: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(invalidFile, []byte(invalidProperties), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create invalid test file: %v", err)
|
||||
}
|
||||
|
||||
converter := &Converter{strict: false}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
inputFile string
|
||||
outputFile string
|
||||
inputFormat string
|
||||
outputFormat string
|
||||
validateOnly bool
|
||||
dryRun bool
|
||||
expectError bool
|
||||
errorMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid file conversion",
|
||||
inputFile: validFile,
|
||||
inputFormat: "properties",
|
||||
outputFormat: "yaml",
|
||||
dryRun: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid file validation only",
|
||||
inputFile: validFile,
|
||||
inputFormat: "properties",
|
||||
outputFormat: "yaml",
|
||||
validateOnly: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid file validation",
|
||||
inputFile: invalidFile,
|
||||
inputFormat: "properties",
|
||||
outputFormat: "yaml",
|
||||
validateOnly: true,
|
||||
expectError: true,
|
||||
errorMsg: "validation error",
|
||||
},
|
||||
{
|
||||
name: "auto-detect format",
|
||||
inputFile: validFile,
|
||||
inputFormat: "", // Auto-detect
|
||||
outputFormat: "yaml",
|
||||
dryRun: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "nonexistent file",
|
||||
inputFile: filepath.Join(tempDir, "nonexistent.properties"),
|
||||
inputFormat: "properties",
|
||||
expectError: true,
|
||||
errorMsg: "failed to read input file",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := processSingleFile(tt.inputFile, tt.outputFile, tt.inputFormat,
|
||||
tt.outputFormat, tt.validateOnly, tt.dryRun, converter)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("expected error but got none")
|
||||
} else if tt.errorMsg != "" && !strings.Contains(err.Error(), tt.errorMsg) {
|
||||
t.Errorf("expected error to contain %q, got: %q", tt.errorMsg, err.Error())
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestReportBatchResults tests the batch results reporting functionality
|
||||
func TestReportBatchResults(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
results []BatchResult
|
||||
validateOnly bool
|
||||
dryRun bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "all successful conversions",
|
||||
results: []BatchResult{
|
||||
{
|
||||
InputFile: "file1.properties",
|
||||
OutputFile: "file1.yaml",
|
||||
InputFormat: "properties",
|
||||
OutputFormat: "yaml",
|
||||
Success: true,
|
||||
},
|
||||
{
|
||||
InputFile: "file2.properties",
|
||||
OutputFile: "file2.yaml",
|
||||
InputFormat: "properties",
|
||||
OutputFormat: "yaml",
|
||||
Success: true,
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "mixed success and failure",
|
||||
results: []BatchResult{
|
||||
{
|
||||
InputFile: "file1.properties",
|
||||
OutputFile: "file1.yaml",
|
||||
InputFormat: "properties",
|
||||
OutputFormat: "yaml",
|
||||
Success: true,
|
||||
},
|
||||
{
|
||||
InputFile: "file2.properties",
|
||||
Success: false,
|
||||
Error: fmt.Errorf("validation failed"),
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "validation only mode",
|
||||
results: []BatchResult{
|
||||
{
|
||||
InputFile: "file1.properties",
|
||||
InputFormat: "properties",
|
||||
Success: true,
|
||||
},
|
||||
},
|
||||
validateOnly: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "dry run mode",
|
||||
results: []BatchResult{
|
||||
{
|
||||
InputFile: "file1.properties",
|
||||
InputFormat: "properties",
|
||||
OutputFormat: "yaml",
|
||||
Success: true,
|
||||
},
|
||||
},
|
||||
dryRun: true,
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := reportBatchResults(tt.results, tt.validateOnly, tt.dryRun)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("expected error but got none")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
8
main.go
8
main.go
@@ -35,6 +35,10 @@ Examples:
|
||||
# Dry run to validate without writing
|
||||
go-i2ptunnel-config -dry-run tunnel.config
|
||||
|
||||
# Batch process multiple files using glob patterns
|
||||
go-i2ptunnel-config -batch "*.config"
|
||||
go-i2ptunnel-config -batch -out-format ini "tunnels/*.properties"
|
||||
|
||||
# Specify both input and output formats explicitly
|
||||
go-i2ptunnel-config -in-format properties -out-format yaml tunnel.txt`,
|
||||
ArgsUsage: "<input-file> [output-file]",
|
||||
@@ -64,6 +68,10 @@ Examples:
|
||||
Name: "dry-run",
|
||||
Usage: "Print output to console instead of writing to file",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "batch",
|
||||
Usage: "Process multiple files using glob patterns",
|
||||
},
|
||||
},
|
||||
Action: i2pconv.ConvertCommand,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user