mirror of
https://github.com/go-i2p/go-i2ptunnel-config.git
synced 2025-12-20 15:15:52 -05:00
Add support for custom output file specification in CLI
This commit is contained in:
@@ -29,6 +29,12 @@ Specify output format:
|
||||
go-i2ptunnel-config --out-format ini tunnel.config
|
||||
```
|
||||
|
||||
Specify custom output file:
|
||||
```bash
|
||||
go-i2ptunnel-config -o custom-name.yaml tunnel.config
|
||||
go-i2ptunnel-config --output /path/to/output.conf tunnel.properties
|
||||
```
|
||||
|
||||
Validate only:
|
||||
```bash
|
||||
go-i2ptunnel-config --validate tunnel.config
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
// 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
|
||||
@@ -52,10 +53,16 @@ func ConvertCommand(c *cli.Context) error {
|
||||
// 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")
|
||||
|
||||
// Handle output file priority: --output flag takes precedence over positional argument
|
||||
if outputFlag != "" {
|
||||
outputFile = outputFlag
|
||||
}
|
||||
|
||||
// Initialize converter with options
|
||||
converter := &Converter{strict: strict}
|
||||
|
||||
|
||||
@@ -270,6 +270,199 @@ func TestFormatAutoDetection(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestConvertCommandOutputFile tests custom output file specification
|
||||
func TestConvertCommandOutputFile(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
inputFile := filepath.Join(tempDir, "test.properties")
|
||||
testContent := `name=testTunnel
|
||||
type=httpclient
|
||||
interface=127.0.0.1
|
||||
listenPort=8080
|
||||
`
|
||||
|
||||
err := os.WriteFile(inputFile, []byte(testContent), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test file: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
expectedOutput string
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "positional output file argument",
|
||||
args: []string{"go-i2ptunnel-config", inputFile, "custom-positional.yaml"},
|
||||
expectedOutput: "custom-positional.yaml",
|
||||
description: "Should use positional argument for output file",
|
||||
},
|
||||
{
|
||||
name: "output flag short form",
|
||||
args: []string{"go-i2ptunnel-config", "-o", "custom-flag-short.yaml", inputFile},
|
||||
expectedOutput: "custom-flag-short.yaml",
|
||||
description: "Should use -o flag for output file",
|
||||
},
|
||||
{
|
||||
name: "output flag long form",
|
||||
args: []string{"go-i2ptunnel-config", "--output", "custom-flag-long.yaml", inputFile},
|
||||
expectedOutput: "custom-flag-long.yaml",
|
||||
description: "Should use --output flag for output file",
|
||||
},
|
||||
{
|
||||
name: "flag takes precedence over positional",
|
||||
args: []string{"go-i2ptunnel-config", "--output", "flag-wins.yaml", inputFile, "positional-loses.yaml"},
|
||||
expectedOutput: "flag-wins.yaml",
|
||||
description: "Flag should take precedence over positional argument",
|
||||
},
|
||||
{
|
||||
name: "relative path output",
|
||||
args: []string{"go-i2ptunnel-config", "-o", "subdir/output.yaml", inputFile},
|
||||
expectedOutput: "subdir/output.yaml",
|
||||
description: "Should handle relative paths in output",
|
||||
},
|
||||
{
|
||||
name: "different output format with custom name",
|
||||
args: []string{"go-i2ptunnel-config", "--out-format", "ini", "-o", "custom.conf", inputFile},
|
||||
expectedOutput: "custom.conf",
|
||||
description: "Should use custom name even with different format",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create subdir if needed for relative path test
|
||||
if strings.Contains(tt.expectedOutput, "/") {
|
||||
dir := filepath.Dir(filepath.Join(tempDir, tt.expectedOutput))
|
||||
os.MkdirAll(dir, 0755)
|
||||
}
|
||||
|
||||
// Save current directory and change to temp directory
|
||||
originalWd, _ := os.Getwd()
|
||||
defer os.Chdir(originalWd)
|
||||
os.Chdir(tempDir)
|
||||
|
||||
app := &cli.App{
|
||||
Name: "go-i2ptunnel-config",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "out-format",
|
||||
Value: "yaml",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "output, o",
|
||||
},
|
||||
},
|
||||
Action: ConvertCommand,
|
||||
}
|
||||
|
||||
err := app.Run(tt.args)
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tt.description, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the output file exists
|
||||
expectedPath := filepath.Join(tempDir, tt.expectedOutput)
|
||||
if _, err := os.Stat(expectedPath); os.IsNotExist(err) {
|
||||
t.Errorf("%s: expected output file %q was not created", tt.description, tt.expectedOutput)
|
||||
} else {
|
||||
// Clean up
|
||||
os.Remove(expectedPath)
|
||||
// Clean up subdir if it was created
|
||||
if strings.Contains(tt.expectedOutput, "/") {
|
||||
dir := filepath.Dir(expectedPath)
|
||||
os.Remove(dir) // Will only remove if empty
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConvertCommandOutputValidation tests output file validation scenarios
|
||||
func TestConvertCommandOutputValidation(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
inputFile := filepath.Join(tempDir, "test.properties")
|
||||
testContent := `name=testTunnel
|
||||
type=httpclient
|
||||
interface=127.0.0.1
|
||||
listenPort=8080
|
||||
`
|
||||
|
||||
err := os.WriteFile(inputFile, []byte(testContent), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test file: %v", err)
|
||||
}
|
||||
|
||||
// Create a read-only directory to test permission errors
|
||||
readOnlyDir := filepath.Join(tempDir, "readonly")
|
||||
err = os.Mkdir(readOnlyDir, 0444)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create read-only directory: %v", err)
|
||||
}
|
||||
defer os.Chmod(readOnlyDir, 0755) // Ensure cleanup can happen
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
expectError bool
|
||||
errorMsg string
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "invalid output directory",
|
||||
args: []string{"go-i2ptunnel-config", "-o", "/nonexistent/dir/output.yaml", inputFile},
|
||||
expectError: true,
|
||||
errorMsg: "failed to write output file",
|
||||
description: "Should fail when output directory doesn't exist",
|
||||
},
|
||||
{
|
||||
name: "read-only directory",
|
||||
args: []string{"go-i2ptunnel-config", "-o", filepath.Join(readOnlyDir, "output.yaml"), inputFile},
|
||||
expectError: true,
|
||||
errorMsg: "failed to write output file",
|
||||
description: "Should fail when output directory is read-only",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Save current directory and change to temp directory
|
||||
originalWd, _ := os.Getwd()
|
||||
defer os.Chdir(originalWd)
|
||||
os.Chdir(tempDir)
|
||||
|
||||
app := &cli.App{
|
||||
Name: "go-i2ptunnel-config",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "out-format",
|
||||
Value: "yaml",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "output, o",
|
||||
},
|
||||
},
|
||||
Action: ConvertCommand,
|
||||
}
|
||||
|
||||
err := app.Run(tt.args)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("%s: expected error but got none", tt.description)
|
||||
} else if !strings.Contains(err.Error(), tt.errorMsg) {
|
||||
t.Errorf("%s: expected error to contain %q, got: %q", tt.description, tt.errorMsg, err.Error())
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tt.description, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConvertCommandErrorHandling tests various error conditions
|
||||
func TestConvertCommandErrorHandling(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
8
main.go
8
main.go
@@ -28,6 +28,10 @@ Examples:
|
||||
# Specify output format
|
||||
go-i2ptunnel-config -out-format ini tunnel.properties
|
||||
|
||||
# Specify custom output file
|
||||
go-i2ptunnel-config -o /path/to/output.yaml tunnel.config
|
||||
go-i2ptunnel-config tunnel.config custom-name.yaml
|
||||
|
||||
# Dry run to validate without writing
|
||||
go-i2ptunnel-config -dry-run tunnel.config
|
||||
|
||||
@@ -44,6 +48,10 @@ Examples:
|
||||
Usage: "Output format (properties|ini|yaml) - defaults to yaml",
|
||||
Value: "yaml",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "output, o",
|
||||
Usage: "Output file path - auto-generated if not specified",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "validate",
|
||||
Usage: "Validate input without conversion",
|
||||
|
||||
Reference in New Issue
Block a user