Browse Source

V5: Add TOML Support (rebased from 36ba06837f68db1e04fd090f36bf10d85cd5ced7)

秋のかえで 4 years ago
parent
commit
dde9463275

+ 1 - 0
go.mod

@@ -10,6 +10,7 @@ require (
 	github.com/jhump/protoreflect v1.8.2
 	github.com/lucas-clemente/quic-go v0.21.1
 	github.com/miekg/dns v1.1.42
+	github.com/pelletier/go-toml v1.8.1
 	github.com/pires/go-proxyproto v0.5.0
 	github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c
 	github.com/stretchr/testify v1.7.0

+ 20 - 0
infra/conf/json/toml.go

@@ -0,0 +1,20 @@
+package json
+
+import (
+	"encoding/json"
+
+	"github.com/pelletier/go-toml"
+)
+
+// FromTOML convert toml to json
+func FromTOML(v []byte) ([]byte, error) {
+	m := make(map[string]interface{})
+	if err := toml.Unmarshal(v, &m); err != nil {
+		return nil, err
+	}
+	j, err := json.Marshal(m)
+	if err != nil {
+		return nil, err
+	}
+	return j, nil
+}

+ 113 - 0
infra/conf/json/toml_test.go

@@ -0,0 +1,113 @@
+package json
+
+import (
+	"encoding/json"
+	"testing"
+)
+
+func TestTOMLToJSON_V2Style(t *testing.T) {
+	input := `
+[log]
+loglevel = 'debug'
+
+[[inbounds]]
+port = 10800
+listen = '127.0.0.1'
+protocol = 'socks'
+
+[inbounds.settings]
+udp = true
+
+[[outbounds]]
+protocol = 'vmess'
+[[outbounds.settings.vnext]]
+port = 443
+address = 'example.com'
+
+[[outbounds.settings.vnext.users]]
+id = '98a15fa6-2eb1-edd5-50ea-cfc428aaab78'
+
+[outbounds.streamSettings]
+network = 'tcp'
+security = 'tls'
+`
+	expected := `
+{
+    "log": {
+        "loglevel": "debug"
+    },
+    "inbounds": [{
+        "port": 10800,
+        "listen": "127.0.0.1",
+        "protocol": "socks",
+        "settings": {
+            "udp": true
+        }
+    }],
+    "outbounds": [{
+        "protocol": "vmess",
+        "settings": {
+            "vnext": [{
+                "port": 443,
+                "address": "example.com",
+                "users": [{
+                    "id": "98a15fa6-2eb1-edd5-50ea-cfc428aaab78"
+                }]
+            }]
+        },
+        "streamSettings": {
+            "network": "tcp",
+            "security": "tls"
+        }
+    }]
+}
+`
+	bs, err := FromTOML([]byte(input))
+	if err != nil {
+		t.Error(err)
+	}
+	m := make(map[string]interface{})
+	json.Unmarshal(bs, &m)
+	assertResult(t, m, expected)
+}
+func TestTOMLToJSON_ValueTypes(t *testing.T) {
+	input := `
+boolean = [ true, false, true, false ]
+float = [ 3.14, 685_230.15 ]
+int = [ 123, 685_230 ]
+string = [ "哈哈", "Hello world", "newline newline2" ]
+date = [ "2018-02-17" ]
+datetime = [ "2018-02-17T15:02:31+08:00" ]
+mixed = [ true, false, 1, 0, "hello" ]
+1 = 0
+true = true
+str = "hello"
+
+[null]
+nodeName = "node"
+`
+	expected := `
+{
+    "boolean": [true, false, true, false],
+    "float": [3.14, 685230.15],
+    "int": [123, 685230],
+    "null": {
+        "nodeName": "node"
+    },
+    "string": ["哈哈", "Hello world",  "newline newline2"],
+    "date": ["2018-02-17"],
+    "datetime": ["2018-02-17T15:02:31+08:00"],
+    "mixed": [true,false,1,0,"hello"],
+    "1": 0,
+    "true": true,
+    "str": "hello"
+}
+`
+	bs, err := FromTOML([]byte(input))
+	if err != nil {
+		t.Error(err)
+	}
+	m := make(map[string]interface{})
+	json.Unmarshal(bs, &m)
+	assertResult(t, m, expected)
+}

+ 1 - 1
main/commands/all/api/shared_test.go

@@ -3,7 +3,7 @@ package api
 import (
 	"testing"
 
-	statsService "v2ray.com/core/app/stats/command"
+	statsService "github.com/v2fly/v2ray-core/v4/app/stats/command"
 )
 
 func TestEmptyResponese_0(t *testing.T) {

+ 15 - 10
main/commands/all/convert.go

@@ -3,6 +3,7 @@ package all
 import (
 	"bytes"
 	"encoding/json"
+	"github.com/pelletier/go-toml"
 	"google.golang.org/protobuf/proto"
 	"os"
 	"strings"
@@ -24,12 +25,12 @@ Arguments:
 
 	-i, -input
 		Specify the input format.
-		Available values: "json", "yaml"
+		Available values: "json", "toml", "yaml"
 		Default: "json"
 
 	-o, -output
 		Specify the output format
-		Available values: "json", "yaml", "protobuf" / "pb"
+		Available values: "json", "toml", "yaml", "protobuf" / "pb"
 		Default: "json"
 
 	-r
@@ -38,16 +39,14 @@ Arguments:
 Examples:
 
 	{{.Exec}} {{.LongName}} -output=protobuf config.json           (1)
-	{{.Exec}} {{.LongName}} -output=yaml config.json               (2)
-	{{.Exec}} {{.LongName}} -input=yaml config.yaml                (3)
-	{{.Exec}} {{.LongName}} "path/to/dir"                          (4)
-	{{.Exec}} {{.LongName}} -i yaml -o protobuf c1.yaml <url>.yaml (5)
+	{{.Exec}} {{.LongName}} -input=toml config.toml                (2)
+	{{.Exec}} {{.LongName}} "path/to/dir"                          (3)
+	{{.Exec}} {{.LongName}} -i yaml -o protobuf c1.yaml <url>.yaml (4)
 
 (1) Convert json to protobuf
-(2) Convert json to yaml
-(3) Convert yaml to json
-(4) Merge json files in dir
-(5) Merge yaml files and convert to protobuf
+(2) Convert toml to json
+(3) Merge json files in dir
+(4) Merge yaml files and convert to protobuf
 
 Use "{{.Exec}} help config-merge" for more information about merge.
 `,
@@ -64,6 +63,7 @@ var (
 )
 var formatExtensions = map[string][]string{
 	"json": {".json", ".jsonc"},
+	"toml": {".toml"},
 	"yaml": {".yaml", ".yml"},
 }
 
@@ -97,6 +97,11 @@ func executeConvert(cmd *base.Command, args []string) {
 		if err != nil {
 			base.Fatalf("failed to marshal json: %s", err)
 		}
+	case "toml":
+		out, err = toml.Marshal(m)
+		if err != nil {
+			base.Fatalf("failed to marshal json: %s", err)
+		}
 	case "yaml":
 		out, err = yaml.Marshal(m)
 		if err != nil {

+ 25 - 0
main/commands/all/convert_confs.go

@@ -23,6 +23,15 @@ func mergeConvertToMap(files []string, format string) map[string]interface{} {
 		if err != nil {
 			base.Fatalf("failed to load json: %s", err)
 		}
+	case "toml":
+		bs, err := tomlsToJSONs(files)
+		if err != nil {
+			base.Fatalf("failed to convert toml to json: %s", err)
+		}
+		m, err = merge.BytesToMap(bs)
+		if err != nil {
+			base.Fatalf("failed to merge converted json: %s", err)
+		}
 	case "yaml":
 		bs, err := yamlsToJSONs(files)
 		if err != nil {
@@ -111,3 +120,19 @@ func yamlsToJSONs(files []string) ([][]byte, error) {
 	}
 	return jsons, nil
 }
+
+func tomlsToJSONs(files []string) ([][]byte, error) {
+	jsons := make([][]byte, 0)
+	for _, file := range files {
+		bs, err := cmdarg.LoadArgToBytes(file)
+		if err != nil {
+			return nil, err
+		}
+		j, err := json.FromTOML(bs)
+		if err != nil {
+			return nil, err
+		}
+		jsons = append(jsons, j)
+	}
+	return jsons, nil
+}

+ 4 - 1
main/commands/all/format_doc.go

@@ -11,7 +11,10 @@ var docFormat = &base.Command{
 {{.Exec}} supports different config formats:
 
 	* json (.json, .jsonc)
-	  The default loader, multiple config files support.
+	  The default loader, multiple config files support.	
+
+	* toml (.toml)
+	  The toml loader, multiple config files support.
 
 	* yaml (.yml)
 	  The yaml loader, multiple config files support.

+ 1 - 1
main/commands/all/merge_doc.go

@@ -14,7 +14,7 @@ Merging of config files is applied in following commands:
 	{{.Exec}} test -c c1.yaml -c c2.yaml ...
 	{{.Exec}} convert c1.json dir1 ...
 
-Support of yaml is implemented by converting yaml to json, 
+Support of toml and yaml is implemented by converting them to json, 
 both merge and load. So we take json as example here.
 
 Suppose we have 2 JSON files,

+ 3 - 0
main/distro/all/all.go

@@ -76,6 +76,9 @@ import (
 	// The following line loads JSON internally
 	_ "github.com/v2fly/v2ray-core/v4/main/json"
 
+	// TOML config support.
+	_ "github.com/v2fly/v2ray-core/v4/main/toml"
+
 	// YAML config support.
 	_ "github.com/v2fly/v2ray-core/v4/main/yaml"
 

+ 69 - 0
main/toml/toml.go

@@ -0,0 +1,69 @@
+package toml
+
+import (
+	"bytes"
+	"errors"
+	"io"
+	"io/ioutil"
+
+	"github.com/v2fly/v2ray-core/v4"
+	"github.com/v2fly/v2ray-core/v4/common"
+	"github.com/v2fly/v2ray-core/v4/common/cmdarg"
+	"github.com/v2fly/v2ray-core/v4/infra/conf/json"
+	"github.com/v2fly/v2ray-core/v4/infra/conf/merge"
+	"github.com/v2fly/v2ray-core/v4/infra/conf/serial"
+)
+
+func init() {
+	common.Must(core.RegisterConfigLoader(&core.ConfigFormat{
+		Name:      []string{"TOML"},
+		Extension: []string{".toml"},
+		Loader: func(input interface{}) (*core.Config, error) {
+			switch v := input.(type) {
+			case cmdarg.Arg:
+				bs, err := tomlsToJSONs(v)
+				if err != nil {
+					return nil, err
+				}
+				data, err := merge.BytesToJSON(bs)
+				if err != nil {
+					return nil, err
+				}
+				r := bytes.NewReader(data)
+				cf, err := serial.DecodeJSONConfig(r)
+				if err != nil {
+					return nil, err
+				}
+				return cf.Build()
+			case io.Reader:
+				bs, err := ioutil.ReadAll(v)
+				if err != nil {
+					return nil, err
+				}
+				bs, err = json.FromTOML(bs)
+				if err != nil {
+					return nil, err
+				}
+				return serial.LoadJSONConfig(bytes.NewBuffer(bs))
+			default:
+				return nil, errors.New("unknow type")
+			}
+		},
+	}))
+}
+
+func tomlsToJSONs(files []string) ([][]byte, error) {
+	jsons := make([][]byte, 0)
+	for _, file := range files {
+		bs, err := cmdarg.LoadArgToBytes(file)
+		if err != nil {
+			return nil, err
+		}
+		j, err := json.FromTOML(bs)
+		if err != nil {
+			return nil, err
+		}
+		jsons = append(jsons, j)
+	}
+	return jsons, nil
+}