mirror of
https://github.com/go-i2p/go-i2ptunnel-config.git
synced 2025-06-08 02:29:09 -04:00
Check in config converter
This commit is contained in:
14
go.mod
Normal file
14
go.mod
Normal file
@ -0,0 +1,14 @@
|
||||
module github.com/go-i2p/go-i2ptunnel-config
|
||||
|
||||
go 1.23.5
|
||||
|
||||
require (
|
||||
github.com/magiconair/properties v1.8.9
|
||||
github.com/urfave/cli v1.22.16
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
)
|
30
go.sum
Normal file
30
go.sum
Normal file
@ -0,0 +1,30 @@
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
|
||||
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ=
|
||||
github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
48
lib/convert.go
Normal file
48
lib/convert.go
Normal file
@ -0,0 +1,48 @@
|
||||
package i2pconv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// ConvertCommand handles the CLI command for converting tunnel configurations
|
||||
func ConvertCommand(c *cli.Context) error {
|
||||
inputFormat := c.StringSlice("input-format")[0]
|
||||
outputFormat := c.StringSlice("output-format")[0]
|
||||
strict := c.Bool("strict")
|
||||
dryRun := c.Bool("dry-run")
|
||||
|
||||
converter := &Converter{strict: strict}
|
||||
|
||||
inputData, err := os.ReadFile(c.Args().Get(0))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read input file: %w", err)
|
||||
}
|
||||
|
||||
config, err := converter.parseInput(inputData, inputFormat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse input: %w", err)
|
||||
}
|
||||
|
||||
if err := converter.validate(config); err != nil {
|
||||
return fmt.Errorf("validation error: %w", err)
|
||||
}
|
||||
|
||||
outputData, err := converter.generateOutput(config, outputFormat)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate output: %w", err)
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
fmt.Println(string(outputData))
|
||||
} else {
|
||||
outputFile := c.Args().Get(1)
|
||||
if err := os.WriteFile(outputFile, outputData, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write output file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
29
lib/errors.go
Normal file
29
lib/errors.go
Normal file
@ -0,0 +1,29 @@
|
||||
package i2pconv
|
||||
|
||||
// ConversionError represents an error during conversion
|
||||
type ConversionError struct {
|
||||
Op string
|
||||
Err error
|
||||
}
|
||||
|
||||
// ValidationError represents an error during validation
|
||||
type ValidationError struct {
|
||||
Config *TunnelConfig
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return "validation: " + e.Err.Error()
|
||||
}
|
||||
|
||||
func (e *ValidationError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func (e *ConversionError) Error() string {
|
||||
return e.Op + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
func (e *ConversionError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
78
lib/i2pconv.go
Normal file
78
lib/i2pconv.go
Normal file
@ -0,0 +1,78 @@
|
||||
package i2pconv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TunnelConfig represents a normalized tunnel configuration
|
||||
type TunnelConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Type string `yaml:"type"`
|
||||
Interface string `yaml:"interface,omitempty"`
|
||||
Port int `yaml:"port,omitempty"`
|
||||
PersistentKey bool `yaml:"persistentKey,omitempty"`
|
||||
Description string `yaml:"description,omitempty"`
|
||||
I2CP map[string]interface{} `yaml:"i2cp,omitempty"`
|
||||
Tunnel map[string]interface{} `yaml:"options,omitempty"`
|
||||
Inbound map[string]interface{} `yaml:"inbound,omitempty"`
|
||||
Outbound map[string]interface{} `yaml:"outbound,omitempty"`
|
||||
}
|
||||
|
||||
// generateOutput generates the output based on the format
|
||||
func (c *Converter) generateOutput(config *TunnelConfig, format string) ([]byte, error) {
|
||||
switch format {
|
||||
case "properties":
|
||||
return c.generateJavaProperties(config)
|
||||
case "yaml":
|
||||
return c.generateYAML(config)
|
||||
case "ini":
|
||||
return c.generateINI(config)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported output format: %s", format)
|
||||
}
|
||||
}
|
||||
|
||||
// validate checks the configuration for any errors
|
||||
func (c *Converter) validate(config *TunnelConfig) error {
|
||||
if config.Name == "" {
|
||||
return fmt.Errorf("name is required")
|
||||
}
|
||||
if config.Type == "" {
|
||||
return fmt.Errorf("type is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Converter handles configuration format conversions
|
||||
type Converter struct {
|
||||
strict bool
|
||||
//logger Logger
|
||||
}
|
||||
|
||||
// Convert transforms between configuration formats
|
||||
func (c *Converter) Convert(input []byte, inFormat, outFormat string) ([]byte, error) {
|
||||
config, err := c.parseInput(input, inFormat)
|
||||
if err != nil {
|
||||
return nil, &ConversionError{Op: "parse", Err: err}
|
||||
}
|
||||
|
||||
if err := c.validate(config); err != nil {
|
||||
return nil, &ValidationError{Config: config, Err: err}
|
||||
}
|
||||
|
||||
return c.generateOutput(config, outFormat)
|
||||
}
|
||||
|
||||
// parseInput parses the input based on the format
|
||||
func (c *Converter) parseInput(input []byte, format string) (*TunnelConfig, error) {
|
||||
switch format {
|
||||
case "properties":
|
||||
return c.parseJavaProperties(input)
|
||||
case "yaml":
|
||||
return c.parseYAML(input)
|
||||
case "ini":
|
||||
return c.parseINI(input)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported input format: %s", format)
|
||||
}
|
||||
}
|
100
lib/ini.go
Normal file
100
lib/ini.go
Normal file
@ -0,0 +1,100 @@
|
||||
package i2pconv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *Converter) parseINI(input []byte) (*TunnelConfig, error) {
|
||||
config := &TunnelConfig{
|
||||
I2CP: make(map[string]interface{}),
|
||||
Tunnel: make(map[string]interface{}),
|
||||
Inbound: make(map[string]interface{}),
|
||||
Outbound: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
lines := strings.Split(string(input), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
|
||||
switch key {
|
||||
case "keys":
|
||||
config.PersistentKey = true
|
||||
case "type":
|
||||
config.Type = value
|
||||
case "host":
|
||||
config.Interface = value
|
||||
case "port":
|
||||
port, err := strconv.Atoi(value)
|
||||
if err == nil {
|
||||
config.Port = port
|
||||
}
|
||||
default:
|
||||
if strings.HasPrefix(key, "i2cp.") {
|
||||
config.I2CP[strings.TrimPrefix(key, "i2cp.")] = value
|
||||
} else if strings.HasPrefix(key, "inbound") {
|
||||
config.Inbound[strings.TrimPrefix(key, "inbound.")] = value
|
||||
} else if strings.HasPrefix(key, "outbound") {
|
||||
config.Outbound[strings.TrimPrefix(key, "outbound.")] = value
|
||||
} else {
|
||||
config.Tunnel[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c *Converter) generateINI(config *TunnelConfig) ([]byte, error) {
|
||||
var sb strings.Builder
|
||||
|
||||
if config.Name != "" {
|
||||
sb.WriteString(fmt.Sprintf("name = %s\n", config.Name))
|
||||
}
|
||||
if config.Type != "" {
|
||||
sb.WriteString(fmt.Sprintf("type = %s\n", config.Type))
|
||||
}
|
||||
if config.Interface != "" {
|
||||
sb.WriteString(fmt.Sprintf("interface = %s\n", config.Interface))
|
||||
}
|
||||
if config.Port != 0 {
|
||||
sb.WriteString(fmt.Sprintf("port = %d\n", config.Port))
|
||||
}
|
||||
|
||||
if config.PersistentKey {
|
||||
sb.WriteString(fmt.Sprintf("keys = %s\n", strings.TrimSpace(strings.ReplaceAll(config.Name, " ", "_"))))
|
||||
}
|
||||
if config.Description != "" {
|
||||
sb.WriteString(fmt.Sprintf("description = %s\n", config.Description))
|
||||
}
|
||||
|
||||
for k, v := range config.I2CP {
|
||||
sb.WriteString(fmt.Sprintf("i2cp.%s = %v\n", k, v))
|
||||
}
|
||||
|
||||
for k, v := range config.Tunnel {
|
||||
sb.WriteString(fmt.Sprintf("%s = %v\n", k, v))
|
||||
}
|
||||
|
||||
for k, v := range config.Inbound {
|
||||
sb.WriteString(fmt.Sprintf("inbound.%s = %v\n", k, v))
|
||||
}
|
||||
|
||||
for k, v := range config.Outbound {
|
||||
sb.WriteString(fmt.Sprintf("outbound.%s = %v\n", k, v))
|
||||
}
|
||||
|
||||
return []byte(sb.String()), nil
|
||||
}
|
108
lib/properties.go
Normal file
108
lib/properties.go
Normal file
@ -0,0 +1,108 @@
|
||||
package i2pconv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/magiconair/properties"
|
||||
)
|
||||
|
||||
// Example Java properties parser
|
||||
func (c *Converter) parseJavaProperties(input []byte) (*TunnelConfig, error) {
|
||||
p := properties.MustLoadString(string(input))
|
||||
|
||||
config := &TunnelConfig{
|
||||
I2CP: make(map[string]interface{}),
|
||||
Tunnel: make(map[string]interface{}),
|
||||
Inbound: make(map[string]interface{}),
|
||||
Outbound: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
// Parse tunnel.*.name pattern
|
||||
for _, k := range p.Keys() {
|
||||
c.parsePropertyKey(k, p.GetString(k, ""), config)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c *Converter) parsePropertyKey(k string, s string, config *TunnelConfig) {
|
||||
if strings.HasPrefix(k, "#") {
|
||||
return
|
||||
}
|
||||
kv := strings.Split(k, "=")
|
||||
if len(kv) != 2 {
|
||||
return
|
||||
}
|
||||
parts := strings.Split(kv[0], ".")
|
||||
|
||||
switch parts[1] {
|
||||
case "name":
|
||||
config.Name = s
|
||||
case "type":
|
||||
config.Type = s
|
||||
case "interface":
|
||||
config.Interface = s
|
||||
case "listenPort":
|
||||
port, err := strconv.Atoi(s)
|
||||
if err == nil {
|
||||
config.Port = port
|
||||
}
|
||||
case "option.persistentClientKey":
|
||||
config.PersistentKey = true
|
||||
case "description":
|
||||
config.Description = s
|
||||
default:
|
||||
if strings.HasPrefix(parts[1], "option.i2cp") {
|
||||
config.I2CP[parts[2]] = s
|
||||
} else if strings.HasPrefix(parts[1], "option.i2ptunnel") {
|
||||
config.Tunnel[parts[2]] = s
|
||||
} else if strings.HasPrefix(parts[1], "option.inbound") {
|
||||
config.Inbound[parts[2]] = s
|
||||
} else if strings.HasPrefix(parts[1], "option.outbound") {
|
||||
config.Outbound[parts[2]] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Converter) generateJavaProperties(config *TunnelConfig) ([]byte, error) {
|
||||
var sb strings.Builder
|
||||
|
||||
if config.Name != "" {
|
||||
sb.WriteString(fmt.Sprintf("name=%s\n", config.Name))
|
||||
}
|
||||
if config.Type != "" {
|
||||
sb.WriteString(fmt.Sprintf("type=%s\n", config.Type))
|
||||
}
|
||||
if config.Interface != "" {
|
||||
sb.WriteString(fmt.Sprintf("interface=%s\n", config.Interface))
|
||||
}
|
||||
if config.Port != 0 {
|
||||
sb.WriteString(fmt.Sprintf("listenPort=%d\n", config.Port))
|
||||
}
|
||||
if config.PersistentKey {
|
||||
sb.WriteString("option.persistentClientKey=true\n")
|
||||
}
|
||||
if config.Description != "" {
|
||||
sb.WriteString(fmt.Sprintf("description=%s\n", config.Description))
|
||||
}
|
||||
|
||||
for k, v := range config.I2CP {
|
||||
sb.WriteString(fmt.Sprintf("option.i2cp.%s=%v\n", k, v))
|
||||
}
|
||||
|
||||
for k, v := range config.Tunnel {
|
||||
sb.WriteString(fmt.Sprintf("option.i2ptunnel.%s=%v\n", k, v))
|
||||
}
|
||||
|
||||
for k, v := range config.Inbound {
|
||||
sb.WriteString(fmt.Sprintf("option.inbound.%s=%v\n", k, v))
|
||||
}
|
||||
|
||||
for k, v := range config.Outbound {
|
||||
sb.WriteString(fmt.Sprintf("option.outbound.%s=%v\n", k, v))
|
||||
}
|
||||
|
||||
return []byte(sb.String()), nil
|
||||
}
|
27
lib/yaml.go
Normal file
27
lib/yaml.go
Normal file
@ -0,0 +1,27 @@
|
||||
package i2pconv
|
||||
|
||||
import "gopkg.in/yaml.v2"
|
||||
|
||||
// Example YAML parser
|
||||
func (c *Converter) parseYAML(input []byte) (*TunnelConfig, error) {
|
||||
config := &TunnelConfig{}
|
||||
err := yaml.Unmarshal(input, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c *Converter) generateYAML(config *TunnelConfig) ([]byte, error) {
|
||||
type wrapper struct {
|
||||
Tunnels map[string]*TunnelConfig `yaml:"tunnels"`
|
||||
}
|
||||
|
||||
out := wrapper{
|
||||
Tunnels: map[string]*TunnelConfig{
|
||||
config.Name: config,
|
||||
},
|
||||
}
|
||||
|
||||
return yaml.Marshal(out)
|
||||
}
|
42
main.go
Normal file
42
main.go
Normal file
@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
i2pconv "github.com/go-i2p/go-i2ptunnel-config/lib"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// CLI implementation
|
||||
func main() {
|
||||
cmd := &cli.App{
|
||||
Name: "i2pconv",
|
||||
Usage: "Convert I2P tunnel configurations between formats",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "input-format, if",
|
||||
Usage: "Input format (properties|ini|yaml)",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "output-format, of",
|
||||
Usage: "Output format (properties|ini|yaml)",
|
||||
Required: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "strict",
|
||||
Usage: "Enable strict validation",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "dry-run",
|
||||
Usage: "Validate without writing output",
|
||||
},
|
||||
},
|
||||
Action: i2pconv.ConvertCommand,
|
||||
}
|
||||
|
||||
if err := cmd.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user