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.
Files changed (4) hide show
  1. package/README.md +17 -16
  2. package/cli.mjs +110 -1
  3. package/package.json +5 -3
  4. package/index.ts +0 -120
package/README.md CHANGED
@@ -5,33 +5,29 @@ JSON 转扁平 TOML,最多两层 key。
5
5
  ## 安装
6
6
 
7
7
  ```bash
8
- git clone https://github.com/nano-properties/json-pretty-toml.git
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
- bun index.ts < in.json > out.toml
14
+ npx json-pretty-toml < input.json
15
+ ```
16
+
17
+ ## 使用
18
18
 
19
- # 或直接输入
20
- echo '{"a":{"b":1}}' | bun index.ts
19
+ ```bash
20
+ json-pretty-toml < input.json > output.toml
21
21
  ```
22
22
 
23
23
  ## 规则
24
24
 
25
- | JSON 层级 | TOML 输出 |
26
- | ------------ | ------------------- |
27
- | 第一层 | `[Table]` |
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
- import { TOMLWriter } from './index.ts'
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.0",
3
+ "version": "1.0.2",
4
4
  "description": "Convert JSON to flat TOML with max 2-level keys",
5
- "main": "index.ts",
5
+ "main": "cli.mjs",
6
6
  "type": "module",
7
7
  "engines": {
8
8
  "node": ">=23"
9
9
  },
10
- "bin": "./cli.mjs",
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
- }