json-pretty-toml 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -16
- package/cli.mjs +110 -1
- package/package.json +5 -3
- package/index.ts +0 -120
package/README.md
CHANGED
|
@@ -5,33 +5,29 @@ JSON 转扁平 TOML,最多两层 key。
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
9
|
-
cd json-pretty-toml
|
|
10
|
-
bun install
|
|
8
|
+
npm install -g json-pretty-toml
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
或直接用 npx:
|
|
14
12
|
|
|
15
13
|
```bash
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
npx json-pretty-toml < input.json
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 使用
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
```bash
|
|
20
|
+
json-pretty-toml < input.json > output.toml
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## 规则
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
| 第二层简单值 | `key = value` |
|
|
29
|
-
| 第二层对象 | `key.sub = { ... }` |
|
|
25
|
+
- 第一层 → `[Table]`
|
|
26
|
+
- 第二层简单值 → `key = value`
|
|
27
|
+
- 第二层对象 → `key.sub = { ... }`
|
|
30
28
|
|
|
31
29
|
## 示例
|
|
32
30
|
|
|
33
|
-
输入:
|
|
34
|
-
|
|
35
31
|
```json
|
|
36
32
|
{
|
|
37
33
|
"gateway": {
|
|
@@ -49,9 +45,14 @@ port = 18789
|
|
|
49
45
|
auth.mode = "token"
|
|
50
46
|
```
|
|
51
47
|
|
|
52
|
-
##
|
|
48
|
+
## 要求
|
|
49
|
+
|
|
50
|
+
Node.js >= 23
|
|
51
|
+
|
|
52
|
+
## 开发
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
+
bun install
|
|
55
56
|
bun test
|
|
56
57
|
```
|
|
57
58
|
|
package/cli.mjs
CHANGED
|
@@ -1,5 +1,114 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
class TOMLWriter {
|
|
4
|
+
indent = ' '
|
|
5
|
+
|
|
6
|
+
convert(json) {
|
|
7
|
+
const lines = []
|
|
8
|
+
|
|
9
|
+
for (const [tableName, tableValue] of Object.entries(json)) {
|
|
10
|
+
lines.push(`[${this.formatKey(tableName)}]`)
|
|
11
|
+
|
|
12
|
+
if (this.isObject(tableValue)) {
|
|
13
|
+
for (const [secondKey, secondValue] of Object.entries(tableValue)) {
|
|
14
|
+
if (this.isObject(secondValue)) {
|
|
15
|
+
for (const [thirdKey, thirdValue] of Object.entries(secondValue)) {
|
|
16
|
+
const dottedKey = `${this.formatKey(secondKey)}.${this.formatKey(thirdKey)}`
|
|
17
|
+
lines.push(`${dottedKey} = ${this.formatValue(thirdValue, '')}`)
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
lines.push(`${this.formatKey(secondKey)} = ${this.formatValue(secondValue, '')}`)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
lines.push(`value = ${this.formatValue(tableValue, '')}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
lines.push('')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return lines.join('\n').trim()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
formatValue(value, currentIndent) {
|
|
34
|
+
if (value === null) return '""'
|
|
35
|
+
if (typeof value === 'string') return JSON.stringify(value)
|
|
36
|
+
if (typeof value === 'number') return String(value)
|
|
37
|
+
if (typeof value === 'boolean') return String(value)
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
return this.formatArray(value, currentIndent)
|
|
40
|
+
}
|
|
41
|
+
if (this.isObject(value)) {
|
|
42
|
+
return this.formatInlineTable(value, currentIndent)
|
|
43
|
+
}
|
|
44
|
+
return String(value)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
formatArray(arr, currentIndent) {
|
|
48
|
+
if (arr.length === 0) return '[]'
|
|
49
|
+
|
|
50
|
+
const hasObject = arr.some((v) => this.isObject(v))
|
|
51
|
+
if (!hasObject) {
|
|
52
|
+
const items = arr.map((v) => this.formatValue(v, currentIndent)).join(', ')
|
|
53
|
+
return `[${items}]`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const nextIndent = currentIndent + this.indent
|
|
57
|
+
const items = arr.map((v) => {
|
|
58
|
+
const formatted = this.isObject(v) ? this.formatInlineTable(v, nextIndent) : this.formatValue(v, nextIndent)
|
|
59
|
+
return `${nextIndent}${formatted},`
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return `[\n${items.join('\n')}\n${currentIndent}]`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
formatInlineTable(obj, currentIndent) {
|
|
66
|
+
const nextIndent = currentIndent + this.indent
|
|
67
|
+
const entries = Object.entries(obj)
|
|
68
|
+
|
|
69
|
+
const isSimple = entries.length <= 2 && entries.every(([, v]) => !this.isObject(v) && !Array.isArray(v))
|
|
70
|
+
|
|
71
|
+
if (isSimple) {
|
|
72
|
+
const inline = entries.map(([k, v]) => `${this.formatKey(k)} = ${this.formatValue(v, currentIndent)}`).join(', ')
|
|
73
|
+
return `{ ${inline} }`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const lines = entries.map(([k, v]) => {
|
|
77
|
+
const value = this.formatValue(v, nextIndent)
|
|
78
|
+
return `${nextIndent}${this.formatKey(k)} = ${value},`
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return `{\n${lines.join('\n')}\n${currentIndent}}`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
formatKey(key) {
|
|
85
|
+
return /^[A-Za-z0-9_-]+$/.test(key) ? key : JSON.stringify(key)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
isObject(value) {
|
|
89
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// CLI
|
|
94
|
+
if (process.argv.includes('-h') || process.argv.includes('--help')) {
|
|
95
|
+
console.log(`json-pretty-toml - Convert JSON to flat TOML
|
|
96
|
+
|
|
97
|
+
Usage:
|
|
98
|
+
json-pretty-toml < input.json > output.toml
|
|
99
|
+
|
|
100
|
+
Options:
|
|
101
|
+
-h, --help Show this help message
|
|
102
|
+
|
|
103
|
+
Sample:
|
|
104
|
+
echo '{"server":{"port":8080}}' | json-pretty-toml
|
|
105
|
+
|
|
106
|
+
Output:
|
|
107
|
+
[server]
|
|
108
|
+
port = 8080
|
|
109
|
+
`)
|
|
110
|
+
process.exit(0)
|
|
111
|
+
}
|
|
3
112
|
|
|
4
113
|
async function readStdin() {
|
|
5
114
|
const chunks = []
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-pretty-toml",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Convert JSON to flat TOML with max 2-level keys",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "cli.mjs",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=23"
|
|
9
9
|
},
|
|
10
|
-
"bin":
|
|
10
|
+
"bin": {
|
|
11
|
+
"json-pretty-toml": "./cli.mjs"
|
|
12
|
+
},
|
|
11
13
|
"scripts": {
|
|
12
14
|
"test": "bun test",
|
|
13
15
|
"format": "prettier --write ."
|
package/index.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
export class TOMLWriter {
|
|
2
|
-
private indent = ' '
|
|
3
|
-
|
|
4
|
-
convert(json: Record<string, unknown>): string {
|
|
5
|
-
const lines: string[] = []
|
|
6
|
-
|
|
7
|
-
for (const [tableName, tableValue] of Object.entries(json)) {
|
|
8
|
-
lines.push(`[${this.formatKey(tableName)}]`)
|
|
9
|
-
|
|
10
|
-
if (this.isObject(tableValue)) {
|
|
11
|
-
for (const [secondKey, secondValue] of Object.entries(tableValue)) {
|
|
12
|
-
if (this.isObject(secondValue)) {
|
|
13
|
-
for (const [thirdKey, thirdValue] of Object.entries(secondValue)) {
|
|
14
|
-
const dottedKey = `${this.formatKey(secondKey)}.${this.formatKey(thirdKey)}`
|
|
15
|
-
lines.push(`${dottedKey} = ${this.formatValue(thirdValue, '')}`)
|
|
16
|
-
}
|
|
17
|
-
} else {
|
|
18
|
-
lines.push(`${this.formatKey(secondKey)} = ${this.formatValue(secondValue, '')}`)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
} else {
|
|
22
|
-
lines.push(`value = ${this.formatValue(tableValue, '')}`)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
lines.push('')
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return lines.join('\n').trim()
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
private formatValue(value: unknown, currentIndent: string): string {
|
|
32
|
-
if (value === null) return '""'
|
|
33
|
-
if (typeof value === 'string') return JSON.stringify(value)
|
|
34
|
-
if (typeof value === 'number') return String(value)
|
|
35
|
-
if (typeof value === 'boolean') return String(value)
|
|
36
|
-
if (Array.isArray(value)) {
|
|
37
|
-
return this.formatArray(value, currentIndent)
|
|
38
|
-
}
|
|
39
|
-
if (this.isObject(value)) {
|
|
40
|
-
return this.formatInlineTable(value, currentIndent)
|
|
41
|
-
}
|
|
42
|
-
return String(value)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private formatArray(arr: unknown[], currentIndent: string): string {
|
|
46
|
-
if (arr.length === 0) return '[]'
|
|
47
|
-
|
|
48
|
-
const hasObject = arr.some((v) => this.isObject(v))
|
|
49
|
-
if (!hasObject) {
|
|
50
|
-
const items = arr.map((v) => this.formatValue(v, currentIndent)).join(', ')
|
|
51
|
-
return `[${items}]`
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const nextIndent = currentIndent + this.indent
|
|
55
|
-
const items = arr.map((v) => {
|
|
56
|
-
const formatted = this.isObject(v) ? this.formatInlineTable(v, nextIndent) : this.formatValue(v, nextIndent)
|
|
57
|
-
return `${nextIndent}${formatted},`
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
return `[
|
|
61
|
-
${items.join('\n')}
|
|
62
|
-
${currentIndent}]`
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private formatInlineTable(obj: Record<string, unknown>, currentIndent: string): string {
|
|
66
|
-
const nextIndent = currentIndent + this.indent
|
|
67
|
-
const entries = Object.entries(obj)
|
|
68
|
-
|
|
69
|
-
const isSimple = entries.length <= 2 && entries.every(([, v]) => !this.isObject(v) && !Array.isArray(v))
|
|
70
|
-
|
|
71
|
-
if (isSimple) {
|
|
72
|
-
const inline = entries.map(([k, v]) => `${this.formatKey(k)} = ${this.formatValue(v, currentIndent)}`).join(', ')
|
|
73
|
-
return `{ ${inline} }`
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const lines = entries.map(([k, v]) => {
|
|
77
|
-
const value = this.formatValue(v, nextIndent)
|
|
78
|
-
return `${nextIndent}${this.formatKey(k)} = ${value},`
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
return `{
|
|
82
|
-
${lines.join('\n')}
|
|
83
|
-
${currentIndent}}`
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
private formatKey(key: string): string {
|
|
87
|
-
return /^[A-Za-z0-9_-]+$/.test(key) ? key : JSON.stringify(key)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
private isObject(value: unknown): value is Record<string, unknown> {
|
|
91
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async function readStdin(): Promise<string> {
|
|
96
|
-
const chunks: Buffer[] = []
|
|
97
|
-
for await (const chunk of process.stdin) {
|
|
98
|
-
chunks.push(chunk)
|
|
99
|
-
}
|
|
100
|
-
return Buffer.concat(chunks).toString('utf8')
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function main() {
|
|
104
|
-
const input = await readStdin()
|
|
105
|
-
|
|
106
|
-
let json: Record<string, unknown>
|
|
107
|
-
try {
|
|
108
|
-
json = JSON.parse(input)
|
|
109
|
-
} catch {
|
|
110
|
-
console.error('Error: Invalid JSON input')
|
|
111
|
-
process.exit(1)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const writer = new TOMLWriter()
|
|
115
|
-
console.log(writer.convert(json))
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (process.argv[1] === import.meta.filename) {
|
|
119
|
-
main()
|
|
120
|
-
}
|