json-pretty-toml 1.0.0
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 +60 -0
- package/cli.mjs +23 -0
- package/index.ts +120 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# json-pretty-toml
|
|
2
|
+
|
|
3
|
+
JSON 转扁平 TOML,最多两层 key。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/nano-properties/json-pretty-toml.git
|
|
9
|
+
cd json-pretty-toml
|
|
10
|
+
bun install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 使用
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 从文件转换
|
|
17
|
+
bun index.ts < in.json > out.toml
|
|
18
|
+
|
|
19
|
+
# 或直接输入
|
|
20
|
+
echo '{"a":{"b":1}}' | bun index.ts
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 规则
|
|
24
|
+
|
|
25
|
+
| JSON 层级 | TOML 输出 |
|
|
26
|
+
| ------------ | ------------------- |
|
|
27
|
+
| 第一层 | `[Table]` |
|
|
28
|
+
| 第二层简单值 | `key = value` |
|
|
29
|
+
| 第二层对象 | `key.sub = { ... }` |
|
|
30
|
+
|
|
31
|
+
## 示例
|
|
32
|
+
|
|
33
|
+
输入:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"gateway": {
|
|
38
|
+
"port": 18789,
|
|
39
|
+
"auth": { "mode": "token" }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
输出:
|
|
45
|
+
|
|
46
|
+
```toml
|
|
47
|
+
[gateway]
|
|
48
|
+
port = 18789
|
|
49
|
+
auth.mode = "token"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 测试
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bun test
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { TOMLWriter } from './index.ts'
|
|
3
|
+
|
|
4
|
+
async function readStdin() {
|
|
5
|
+
const chunks = []
|
|
6
|
+
for await (const chunk of process.stdin) {
|
|
7
|
+
chunks.push(chunk)
|
|
8
|
+
}
|
|
9
|
+
return Buffer.concat(chunks).toString('utf8')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const input = await readStdin()
|
|
13
|
+
|
|
14
|
+
let json
|
|
15
|
+
try {
|
|
16
|
+
json = JSON.parse(input)
|
|
17
|
+
} catch {
|
|
18
|
+
console.error('Error: Invalid JSON input')
|
|
19
|
+
process.exit(1)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const writer = new TOMLWriter()
|
|
23
|
+
console.log(writer.convert(json))
|
package/index.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
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
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "json-pretty-toml",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Convert JSON to flat TOML with max 2-level keys",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=23"
|
|
9
|
+
},
|
|
10
|
+
"bin": "./cli.mjs",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "bun test",
|
|
13
|
+
"format": "prettier --write ."
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"json",
|
|
17
|
+
"toml",
|
|
18
|
+
"converter",
|
|
19
|
+
"cli"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"prettier": "^3.8.1"
|
|
24
|
+
}
|
|
25
|
+
}
|