@fuzionx/framework 0.1.42 → 0.1.43

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 (2) hide show
  1. package/lib/core/Config.js +112 -9
  2. package/package.json +2 -2
@@ -62,21 +62,94 @@ export default class Config {
62
62
 
63
63
  /**
64
64
  * 간이 YAML 파서 — 외부 의존 없이 fuzionx.yaml 구조 파싱.
65
- * 지원: 중첩 객체, 문자열, 숫자, boolean, 배열(인라인), 인용 문자열.
65
+ * 지원: 중첩 객체, 문자열, 숫자, boolean, 배열(인라인/블록), 인용 문자열.
66
+ * Windows(\r\n) / Unix(\n) 줄바꿈 모두 지원.
66
67
  * @param {string} content
67
68
  * @returns {object}
68
69
  */
69
70
  static parseYaml(content) {
70
71
  const result = {};
71
- const lines = content.split('\n');
72
+ // Windows \r\n \n 정규화 (정규식 $, . 호환)
73
+ const lines = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
74
+ // stack: { indent, obj, lastKey? }
72
75
  const stack = [{ indent: -1, obj: result }];
73
76
 
74
77
  for (const rawLine of lines) {
75
- // 주석/빈 무시
76
- const line = rawLine.replace(/#.*$/, '');
78
+ // 인용부호 바깥의 # 주석만 제거 (문자열 안의 # 보존)
79
+ const line = Config._stripInlineComment(rawLine);
77
80
  if (!line.trim()) continue;
78
81
 
79
82
  const indent = line.search(/\S/);
83
+
84
+ // ── YAML 블록 배열 항목: "- value" 또는 "- key: value" ──
85
+ const listMatch = line.match(/^(\s*)-\s+(.+)$/);
86
+ if (listMatch) {
87
+ const listIndent = listMatch[1].length;
88
+
89
+ // 스택에서 현재 indent보다 깊은 레벨 제거 (배열은 유지)
90
+ while (stack.length > 1) {
91
+ const top = stack[stack.length - 1];
92
+ if (Array.isArray(top.obj) && top.indent === listIndent) break;
93
+ if (top.indent >= listIndent) {
94
+ stack.pop();
95
+ } else {
96
+ break;
97
+ }
98
+ }
99
+
100
+ // 부모 찾기 — 배열이면 직접 사용, 아니면 lastKey를 배열로 변환
101
+ const top = stack[stack.length - 1];
102
+ let arr;
103
+ if (Array.isArray(top.obj)) {
104
+ arr = top.obj;
105
+ } else {
106
+ // lastKey 탐색: 현재 top → 상위 스택 순서로 찾기
107
+ // (빈 값 키 origins: → {} 가 스택에 있으면 lastKey=undefined,
108
+ // 이 경우 부모의 lastKey가 "origins")
109
+ let foundIdx = -1;
110
+ let foundKey = null;
111
+ let foundObj = null;
112
+ for (let si = stack.length - 1; si >= 0; si--) {
113
+ if (stack[si].lastKey !== undefined) {
114
+ foundIdx = si;
115
+ foundKey = stack[si].lastKey;
116
+ foundObj = stack[si].obj;
117
+ // 배열이 아닌 빈 객체가 이 키 아래 있으면 그게 배열로 바뀌어야 함
118
+ const target = Array.isArray(foundObj)
119
+ ? foundObj[foundObj.length - 1]
120
+ : foundObj;
121
+ if (target && typeof target[foundKey] !== undefined) {
122
+ foundObj = target;
123
+ }
124
+ break;
125
+ }
126
+ }
127
+ if (foundKey === null) continue;
128
+ if (!Array.isArray(foundObj[foundKey])) {
129
+ foundObj[foundKey] = [];
130
+ }
131
+ arr = foundObj[foundKey];
132
+ // 기존 빈 객체 스택 제거 후 배열로 교체
133
+ while (stack.length > foundIdx + 1) stack.pop();
134
+ stack.push({ indent: listIndent, obj: arr });
135
+ }
136
+
137
+ const itemContent = listMatch[2].trim();
138
+ // "- key: value" 형태인지 확인
139
+ const kvMatch = itemContent.match(/^(?:(["'])([^"']+)\1|([-\w.]+))\s*:\s*(.+)$/);
140
+ if (kvMatch) {
141
+ const k = kvMatch[2] || kvMatch[3];
142
+ const v = Config._parseYamlValue(kvMatch[4].trim());
143
+ const obj = { [k]: v };
144
+ arr.push(obj);
145
+ stack.push({ indent: listIndent + 2, obj, lastKey: k });
146
+ } else {
147
+ arr.push(Config._parseYamlValue(itemContent));
148
+ }
149
+ continue;
150
+ }
151
+
152
+ // ── 키: 값 파싱 ──
80
153
  // 키: unquoted ([-\w.]+) 또는 quoted ("..." / '...')
81
154
  const match = line.match(/^(\s*)(?:(["'])([^"']+)\2|([-\w.]+))\s*:\s*(.*)$/);
82
155
  if (!match) continue;
@@ -89,22 +162,50 @@ export default class Config {
89
162
  stack.pop();
90
163
  }
91
164
 
92
- const parent = stack[stack.length - 1].obj;
165
+ const top = stack[stack.length - 1];
166
+ // 배열 안의 마지막 객체에 키 추가 (- key: value 후속 키)
167
+ const target = Array.isArray(top.obj) ? top.obj[top.obj.length - 1] : top.obj;
93
168
  const value = rawValue.trim();
94
169
 
95
170
  if (!value) {
96
171
  // 하위 객체 시작
97
- parent[key] = {};
98
- stack.push({ indent, obj: parent[key] });
172
+ target[key] = {};
173
+ stack.push({ indent, obj: target[key], lastKey: undefined });
174
+ // lastKey를 부모에 설정 (배열 전환 시 필요)
175
+ const parentIdx = stack.length - 2;
176
+ if (parentIdx >= 0) {
177
+ stack[parentIdx].lastKey = key;
178
+ }
99
179
  } else {
100
180
  // 값 파싱
101
- parent[key] = Config._parseYamlValue(value);
181
+ target[key] = Config._parseYamlValue(value);
182
+ top.lastKey = key;
102
183
  }
103
184
  }
104
185
 
105
186
  return result;
106
187
  }
107
188
 
189
+ /**
190
+ * 인라인 주석 제거 — 인용부호 안의 # 보존
191
+ * @param {string} line
192
+ * @returns {string}
193
+ * @private
194
+ */
195
+ static _stripInlineComment(line) {
196
+ let inSingle = false;
197
+ let inDouble = false;
198
+ for (let i = 0; i < line.length; i++) {
199
+ const ch = line[i];
200
+ if (ch === "'" && !inDouble) inSingle = !inSingle;
201
+ else if (ch === '"' && !inSingle) inDouble = !inDouble;
202
+ else if (ch === '#' && !inSingle && !inDouble) {
203
+ return line.slice(0, i);
204
+ }
205
+ }
206
+ return line;
207
+ }
208
+
108
209
  /**
109
210
  * YAML 값 파싱 (문자열, 숫자, boolean, 배열)
110
211
  * @private
@@ -121,7 +222,9 @@ export default class Config {
121
222
 
122
223
  // 인라인 배열 [a, b, c]
123
224
  if (raw.startsWith('[') && raw.endsWith(']')) {
124
- return raw.slice(1, -1).split(',').map(s => Config._parseYamlValue(s.trim()));
225
+ const inner = raw.slice(1, -1).trim();
226
+ if (!inner) return [];
227
+ return inner.split(',').map(s => Config._parseYamlValue(s.trim()));
125
228
  }
126
229
 
127
230
  // 숫자
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzionx/framework",
3
- "version": "0.1.42",
3
+ "version": "0.1.43",
4
4
  "type": "module",
5
5
  "description": "Full-stack MVC framework built on @fuzionx/core — Controller, Service, Model, Middleware, DI, EventBus",
6
6
  "main": "index.js",
@@ -34,7 +34,7 @@
34
34
  "url": "https://github.com/saytohenry/fuzionx"
35
35
  },
36
36
  "dependencies": {
37
- "@fuzionx/core": "^0.1.42",
37
+ "@fuzionx/core": "^0.1.43",
38
38
  "better-sqlite3": "^12.8.0",
39
39
  "knex": "^3.2.5",
40
40
  "mongoose": "^9.3.2",