@prmichaelsen/acp-visualizer 0.1.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 +68 -0
- package/agent/commands/acp.clarification-address.md +417 -0
- package/agent/commands/acp.clarification-capture.md +386 -0
- package/agent/commands/acp.clarification-create.md +437 -0
- package/agent/commands/acp.clarifications-research.md +326 -0
- package/agent/commands/acp.command-create.md +432 -0
- package/agent/commands/acp.design-create.md +286 -0
- package/agent/commands/acp.design-reference.md +355 -0
- package/agent/commands/acp.handoff.md +270 -0
- package/agent/commands/acp.index.md +423 -0
- package/agent/commands/acp.init.md +546 -0
- package/agent/commands/acp.package-create.md +895 -0
- package/agent/commands/acp.package-info.md +212 -0
- package/agent/commands/acp.package-install.md +539 -0
- package/agent/commands/acp.package-list.md +280 -0
- package/agent/commands/acp.package-publish.md +541 -0
- package/agent/commands/acp.package-remove.md +293 -0
- package/agent/commands/acp.package-search.md +307 -0
- package/agent/commands/acp.package-update.md +361 -0
- package/agent/commands/acp.package-validate.md +540 -0
- package/agent/commands/acp.pattern-create.md +386 -0
- package/agent/commands/acp.plan.md +587 -0
- package/agent/commands/acp.proceed.md +882 -0
- package/agent/commands/acp.project-create.md +675 -0
- package/agent/commands/acp.project-info.md +312 -0
- package/agent/commands/acp.project-list.md +226 -0
- package/agent/commands/acp.project-remove.md +379 -0
- package/agent/commands/acp.project-set.md +227 -0
- package/agent/commands/acp.project-update.md +307 -0
- package/agent/commands/acp.projects-restore.md +228 -0
- package/agent/commands/acp.projects-sync.md +347 -0
- package/agent/commands/acp.report.md +407 -0
- package/agent/commands/acp.resume.md +239 -0
- package/agent/commands/acp.sessions.md +301 -0
- package/agent/commands/acp.status.md +293 -0
- package/agent/commands/acp.sync.md +364 -0
- package/agent/commands/acp.task-create.md +500 -0
- package/agent/commands/acp.update.md +302 -0
- package/agent/commands/acp.validate.md +466 -0
- package/agent/commands/acp.version-check-for-updates.md +276 -0
- package/agent/commands/acp.version-check.md +191 -0
- package/agent/commands/acp.version-update.md +289 -0
- package/agent/commands/command.template.md +339 -0
- package/agent/commands/git.commit.md +526 -0
- package/agent/commands/git.init.md +514 -0
- package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
- package/agent/commands/tanstack-cloudflare.tail.md +275 -0
- package/agent/design/.gitkeep +0 -0
- package/agent/design/design.template.md +154 -0
- package/agent/design/local.dashboard-layout-routing.md +288 -0
- package/agent/design/local.data-model-yaml-parsing.md +310 -0
- package/agent/design/local.search-filtering.md +331 -0
- package/agent/design/local.server-api-auto-refresh.md +235 -0
- package/agent/design/local.table-tree-views.md +299 -0
- package/agent/design/local.visualizer-requirements.md +349 -0
- package/agent/design/requirements.template.md +387 -0
- package/agent/index/.gitkeep +0 -0
- package/agent/index/acp.core.yaml +137 -0
- package/agent/index/local.main.template.yaml +37 -0
- package/agent/manifest.template.yaml +13 -0
- package/agent/manifest.yaml +302 -0
- package/agent/milestones/.gitkeep +0 -0
- package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
- package/agent/milestones/milestone-1-{title}.template.md +206 -0
- package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
- package/agent/package.template.yaml +86 -0
- package/agent/patterns/.gitkeep +0 -0
- package/agent/patterns/bootstrap.template.md +1237 -0
- package/agent/patterns/pattern.template.md +382 -0
- package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
- package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
- package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
- package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
- package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
- package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
- package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
- package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
- package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
- package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
- package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
- package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
- package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
- package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
- package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
- package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
- package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
- package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
- package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
- package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
- package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
- package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
- package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
- package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
- package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
- package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
- package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
- package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
- package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
- package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
- package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
- package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
- package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
- package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
- package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
- package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
- package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
- package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
- package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
- package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
- package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
- package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
- package/agent/progress.template.yaml +161 -0
- package/agent/progress.yaml +145 -0
- package/agent/schemas/package.schema.yaml +276 -0
- package/agent/scripts/acp.common.sh +1781 -0
- package/agent/scripts/acp.install.sh +333 -0
- package/agent/scripts/acp.package-create.sh +924 -0
- package/agent/scripts/acp.package-info.sh +288 -0
- package/agent/scripts/acp.package-install.sh +893 -0
- package/agent/scripts/acp.package-list.sh +311 -0
- package/agent/scripts/acp.package-publish.sh +420 -0
- package/agent/scripts/acp.package-remove.sh +348 -0
- package/agent/scripts/acp.package-search.sh +156 -0
- package/agent/scripts/acp.package-update.sh +517 -0
- package/agent/scripts/acp.package-validate.sh +1018 -0
- package/agent/scripts/acp.uninstall.sh +85 -0
- package/agent/scripts/acp.version-check-for-updates.sh +98 -0
- package/agent/scripts/acp.version-check.sh +47 -0
- package/agent/scripts/acp.version-update.sh +176 -0
- package/agent/scripts/acp.yaml-parser.sh +985 -0
- package/agent/scripts/acp.yaml-validate.sh +205 -0
- package/agent/tasks/.gitkeep +0 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
- package/agent/tasks/task-1-{title}.template.md +244 -0
- package/bin/visualize.mjs +84 -0
- package/package.json +48 -0
- package/src/components/ExtraFieldsBadge.tsx +15 -0
- package/src/components/FilterBar.tsx +33 -0
- package/src/components/Header.tsx +23 -0
- package/src/components/MilestoneTable.tsx +167 -0
- package/src/components/MilestoneTree.tsx +84 -0
- package/src/components/ProgressBar.tsx +20 -0
- package/src/components/SearchInput.tsx +22 -0
- package/src/components/Sidebar.tsx +54 -0
- package/src/components/StatusBadge.tsx +23 -0
- package/src/components/StatusDot.tsx +12 -0
- package/src/components/TaskList.tsx +36 -0
- package/src/components/ViewToggle.tsx +31 -0
- package/src/lib/config.ts +8 -0
- package/src/lib/file-watcher.ts +43 -0
- package/src/lib/search.ts +48 -0
- package/src/lib/types.ts +73 -0
- package/src/lib/useAutoRefresh.ts +31 -0
- package/src/lib/useCollapse.ts +31 -0
- package/src/lib/useFilteredData.ts +55 -0
- package/src/lib/yaml-loader-real.spec.ts +47 -0
- package/src/lib/yaml-loader.spec.ts +201 -0
- package/src/lib/yaml-loader.ts +265 -0
- package/src/routeTree.gen.ts +140 -0
- package/src/router.tsx +10 -0
- package/src/routes/__root.tsx +75 -0
- package/src/routes/api/watch.ts +29 -0
- package/src/routes/index.tsx +115 -0
- package/src/routes/milestones.tsx +50 -0
- package/src/routes/search.tsx +84 -0
- package/src/routes/tasks.tsx +63 -0
- package/src/services/progress-database.service.ts +46 -0
- package/src/styles.css +25 -0
- package/tsconfig.json +24 -0
- package/vite.config.ts +16 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,985 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Generic YAML Parser with AST
|
|
3
|
+
# Pure POSIX shell implementation
|
|
4
|
+
# Version: 1.0.0
|
|
5
|
+
# Created: 2026-02-21
|
|
6
|
+
|
|
7
|
+
# ============================================================================
|
|
8
|
+
# PORTABILITY HELPERS
|
|
9
|
+
# ============================================================================
|
|
10
|
+
|
|
11
|
+
# Portable in-place sed (works on both GNU and BSD/macOS sed)
|
|
12
|
+
# Usage: _yaml_sed_i "expression" "file"
|
|
13
|
+
_yaml_sed_i() {
|
|
14
|
+
if [ "$(uname)" = "Darwin" ]; then
|
|
15
|
+
sed -i '' "$@"
|
|
16
|
+
else
|
|
17
|
+
sed -i "$@"
|
|
18
|
+
fi
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# ============================================================================
|
|
22
|
+
# GLOBAL STATE
|
|
23
|
+
# ============================================================================
|
|
24
|
+
|
|
25
|
+
# Prevent variable reset on re-sourcing
|
|
26
|
+
if [ -z "${YAML_PARSER_LOADED:-}" ]; then
|
|
27
|
+
YAML_PARSER_LOADED=1
|
|
28
|
+
AST_FILE=""
|
|
29
|
+
AST_ROOT_ID=0
|
|
30
|
+
YAML_CURRENT_FILE=""
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# ============================================================================
|
|
34
|
+
# UTILITY FUNCTIONS
|
|
35
|
+
# ============================================================================
|
|
36
|
+
|
|
37
|
+
init_ast() {
|
|
38
|
+
AST_FILE=$(mktemp)
|
|
39
|
+
echo "0|map||root|-1|" > "$AST_FILE"
|
|
40
|
+
AST_ROOT_ID=0
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
cleanup_ast() {
|
|
44
|
+
if [ -n "$AST_FILE" ] && [ -f "$AST_FILE" ]; then
|
|
45
|
+
rm -f "$AST_FILE"
|
|
46
|
+
fi
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get_next_node_id() {
|
|
50
|
+
wc -l < "$AST_FILE"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
create_node() {
|
|
54
|
+
local type="$1"
|
|
55
|
+
local key="$2"
|
|
56
|
+
local value="$3"
|
|
57
|
+
local parent_id="$4"
|
|
58
|
+
|
|
59
|
+
key=$(echo "$key" | sed 's/|/\\|/g')
|
|
60
|
+
value=$(echo "$value" | sed 's/|/\\|/g')
|
|
61
|
+
|
|
62
|
+
local node_id
|
|
63
|
+
node_id=$(get_next_node_id)
|
|
64
|
+
|
|
65
|
+
echo "$node_id|$type|$key|$value|$parent_id|" >> "$AST_FILE"
|
|
66
|
+
echo "$node_id"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get_node() {
|
|
70
|
+
local node_id="$1"
|
|
71
|
+
sed -n "$((node_id + 1))p" "$AST_FILE"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get_node_field() {
|
|
75
|
+
local node_id="$1"
|
|
76
|
+
local field_num="$2"
|
|
77
|
+
get_node "$node_id" | cut -d'|' -f"$field_num"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
add_child() {
|
|
81
|
+
local parent_id="$1"
|
|
82
|
+
local child_id="$2"
|
|
83
|
+
|
|
84
|
+
local node
|
|
85
|
+
node=$(get_node "$parent_id")
|
|
86
|
+
|
|
87
|
+
local id type key value parent children
|
|
88
|
+
id=$(echo "$node" | cut -d'|' -f1)
|
|
89
|
+
type=$(echo "$node" | cut -d'|' -f2)
|
|
90
|
+
key=$(echo "$node" | cut -d'|' -f3)
|
|
91
|
+
value=$(echo "$node" | cut -d'|' -f4)
|
|
92
|
+
parent=$(echo "$node" | cut -d'|' -f5)
|
|
93
|
+
children=$(echo "$node" | cut -d'|' -f6)
|
|
94
|
+
|
|
95
|
+
if [ -z "$children" ]; then
|
|
96
|
+
children="$child_id"
|
|
97
|
+
else
|
|
98
|
+
children="$children,$child_id"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
local updated="$id|$type|$key|$value|$parent|$children"
|
|
102
|
+
_yaml_sed_i "$((parent_id + 1))s@.*@$updated@" "$AST_FILE"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
update_node_type() {
|
|
106
|
+
local node_id="$1"
|
|
107
|
+
local new_type="$2"
|
|
108
|
+
|
|
109
|
+
local node
|
|
110
|
+
node=$(get_node "$node_id")
|
|
111
|
+
|
|
112
|
+
local id type key value parent children
|
|
113
|
+
id=$(echo "$node" | cut -d'|' -f1)
|
|
114
|
+
key=$(echo "$node" | cut -d'|' -f3)
|
|
115
|
+
value=$(echo "$node" | cut -d'|' -f4)
|
|
116
|
+
parent=$(echo "$node" | cut -d'|' -f5)
|
|
117
|
+
children=$(echo "$node" | cut -d'|' -f6)
|
|
118
|
+
|
|
119
|
+
local updated="$id|$new_type|$key|$value|$parent|$children"
|
|
120
|
+
_yaml_sed_i "$((node_id + 1))s@.*@$updated@" "$AST_FILE"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# ============================================================================
|
|
124
|
+
# PARSER
|
|
125
|
+
# ============================================================================
|
|
126
|
+
|
|
127
|
+
yaml_parse() {
|
|
128
|
+
local file="$1"
|
|
129
|
+
|
|
130
|
+
if [ ! -f "$file" ]; then
|
|
131
|
+
echo "Error: File not found: $file" >&2
|
|
132
|
+
return 1
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
cleanup_ast
|
|
136
|
+
init_ast
|
|
137
|
+
YAML_CURRENT_FILE="$file"
|
|
138
|
+
|
|
139
|
+
# State tracking
|
|
140
|
+
local parent_stack="0"
|
|
141
|
+
local indent_stack="-1"
|
|
142
|
+
local current_parent=0
|
|
143
|
+
local prev_indent=-1
|
|
144
|
+
local last_key_node=-1
|
|
145
|
+
|
|
146
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
147
|
+
# Skip empty lines
|
|
148
|
+
[ -z "$line" ] && continue
|
|
149
|
+
|
|
150
|
+
# Skip comment lines
|
|
151
|
+
case "$line" in \#*) continue ;; esac
|
|
152
|
+
|
|
153
|
+
# Strip inline comments
|
|
154
|
+
line=$(echo "$line" | sed 's/#.*$//')
|
|
155
|
+
|
|
156
|
+
# Calculate indentation
|
|
157
|
+
local indent=0
|
|
158
|
+
local trimmed="$line"
|
|
159
|
+
while [ "$trimmed" != "${trimmed# }" ]; do
|
|
160
|
+
indent=$((indent + 1))
|
|
161
|
+
trimmed="${trimmed# }"
|
|
162
|
+
done
|
|
163
|
+
|
|
164
|
+
# Skip empty after trim
|
|
165
|
+
[ -z "$trimmed" ] && continue
|
|
166
|
+
|
|
167
|
+
# Handle dedent - pop stack
|
|
168
|
+
while [ "$prev_indent" -ge 0 ] && [ "$indent" -le "$prev_indent" ]; do
|
|
169
|
+
# Pop one level
|
|
170
|
+
parent_stack=$(echo "$parent_stack" | sed 's/,[^,]*$//')
|
|
171
|
+
indent_stack=$(echo "$indent_stack" | sed 's/,[^,]*$//')
|
|
172
|
+
|
|
173
|
+
# Get new current parent
|
|
174
|
+
current_parent=$(echo "$parent_stack" | awk -F',' '{print $NF}')
|
|
175
|
+
prev_indent=$(echo "$indent_stack" | awk -F',' '{print $NF}')
|
|
176
|
+
|
|
177
|
+
# Handle empty stack
|
|
178
|
+
[ -z "$current_parent" ] && current_parent=0
|
|
179
|
+
[ -z "$prev_indent" ] && prev_indent=-1
|
|
180
|
+
|
|
181
|
+
last_key_node=-1
|
|
182
|
+
done
|
|
183
|
+
|
|
184
|
+
# Parse line content
|
|
185
|
+
if echo "$trimmed" | grep -q '^-[[:space:]]'; then
|
|
186
|
+
# Array item
|
|
187
|
+
local item_content
|
|
188
|
+
item_content=$(echo "$trimmed" | sed 's/^-[[:space:]]*//')
|
|
189
|
+
|
|
190
|
+
# Convert last key node to array if needed
|
|
191
|
+
if [ "$last_key_node" -ge 0 ]; then
|
|
192
|
+
update_node_type "$last_key_node" "array"
|
|
193
|
+
current_parent="$last_key_node"
|
|
194
|
+
last_key_node=-1
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# Check if inline object (has colon on same line)
|
|
198
|
+
if echo "$item_content" | grep -q ':'; then
|
|
199
|
+
# Inline object: - name: value
|
|
200
|
+
local obj_node
|
|
201
|
+
obj_node=$(create_node "map" "" "" "$current_parent")
|
|
202
|
+
add_child "$current_parent" "$obj_node"
|
|
203
|
+
|
|
204
|
+
# Parse first field
|
|
205
|
+
local key value
|
|
206
|
+
key=$(echo "$item_content" | cut -d':' -f1 | sed 's/[[:space:]]*$//')
|
|
207
|
+
value=$(echo "$item_content" | cut -d':' -f2- | sed 's/^[[:space:]]*//')
|
|
208
|
+
|
|
209
|
+
local field_node
|
|
210
|
+
field_node=$(create_node "scalar" "$key" "$value" "$obj_node")
|
|
211
|
+
add_child "$obj_node" "$field_node"
|
|
212
|
+
|
|
213
|
+
# Push object onto stack for potential additional fields
|
|
214
|
+
parent_stack="$parent_stack,$obj_node"
|
|
215
|
+
indent_stack="$indent_stack,$indent"
|
|
216
|
+
current_parent="$obj_node"
|
|
217
|
+
prev_indent="$indent"
|
|
218
|
+
else
|
|
219
|
+
# Simple array item: - value
|
|
220
|
+
local item_node
|
|
221
|
+
item_node=$(create_node "scalar" "" "$item_content" "$current_parent")
|
|
222
|
+
add_child "$current_parent" "$item_node"
|
|
223
|
+
fi
|
|
224
|
+
elif echo "$trimmed" | grep -q ':'; then
|
|
225
|
+
# Key-value pair
|
|
226
|
+
local key value
|
|
227
|
+
key=$(echo "$trimmed" | cut -d':' -f1 | sed 's/[[:space:]]*$//')
|
|
228
|
+
value=$(echo "$trimmed" | cut -d':' -f2- | sed 's/^[[:space:]]*//')
|
|
229
|
+
|
|
230
|
+
if [ -z "$value" ]; then
|
|
231
|
+
# Key with no value - map or array follows
|
|
232
|
+
local node_id
|
|
233
|
+
node_id=$(create_node "map" "$key" "" "$current_parent")
|
|
234
|
+
add_child "$current_parent" "$node_id"
|
|
235
|
+
|
|
236
|
+
# Push onto stack
|
|
237
|
+
parent_stack="$parent_stack,$node_id"
|
|
238
|
+
indent_stack="$indent_stack,$indent"
|
|
239
|
+
current_parent="$node_id"
|
|
240
|
+
prev_indent="$indent"
|
|
241
|
+
last_key_node="$node_id"
|
|
242
|
+
else
|
|
243
|
+
# Check for empty array [] or empty map {}
|
|
244
|
+
if [ "$value" = "[]" ]; then
|
|
245
|
+
# Empty array
|
|
246
|
+
local node_id
|
|
247
|
+
node_id=$(create_node "array" "$key" "" "$current_parent")
|
|
248
|
+
add_child "$current_parent" "$node_id"
|
|
249
|
+
elif [ "$value" = "{}" ]; then
|
|
250
|
+
# Empty map
|
|
251
|
+
local node_id
|
|
252
|
+
node_id=$(create_node "map" "$key" "" "$current_parent")
|
|
253
|
+
add_child "$current_parent" "$node_id"
|
|
254
|
+
else
|
|
255
|
+
# Scalar value
|
|
256
|
+
local node_id
|
|
257
|
+
node_id=$(create_node "scalar" "$key" "$value" "$current_parent")
|
|
258
|
+
add_child "$current_parent" "$node_id"
|
|
259
|
+
fi
|
|
260
|
+
fi
|
|
261
|
+
fi
|
|
262
|
+
done < "$file"
|
|
263
|
+
|
|
264
|
+
return 0
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
# ============================================================================
|
|
268
|
+
# QUERY ENGINE
|
|
269
|
+
# ============================================================================
|
|
270
|
+
|
|
271
|
+
find_child_by_key() {
|
|
272
|
+
local parent_id="$1"
|
|
273
|
+
local key="$2"
|
|
274
|
+
|
|
275
|
+
local children
|
|
276
|
+
children=$(get_node_field "$parent_id" 6)
|
|
277
|
+
|
|
278
|
+
[ -z "$children" ] && return 1
|
|
279
|
+
|
|
280
|
+
local IFS=','
|
|
281
|
+
for child_id in $children; do
|
|
282
|
+
local child_key
|
|
283
|
+
child_key=$(get_node_field "$child_id" 3)
|
|
284
|
+
|
|
285
|
+
if [ "$child_key" = "$key" ]; then
|
|
286
|
+
echo "$child_id"
|
|
287
|
+
return 0
|
|
288
|
+
fi
|
|
289
|
+
done
|
|
290
|
+
|
|
291
|
+
return 1
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
find_child_by_index() {
|
|
295
|
+
local parent_id="$1"
|
|
296
|
+
local index="$2"
|
|
297
|
+
|
|
298
|
+
local children
|
|
299
|
+
children=$(get_node_field "$parent_id" 6)
|
|
300
|
+
|
|
301
|
+
[ -z "$children" ] && return 1
|
|
302
|
+
|
|
303
|
+
local child_id
|
|
304
|
+
child_id=$(echo "$children" | tr ',' '\n' | sed -n "$((index + 1))p")
|
|
305
|
+
|
|
306
|
+
if [ -n "$child_id" ]; then
|
|
307
|
+
echo "$child_id"
|
|
308
|
+
return 0
|
|
309
|
+
fi
|
|
310
|
+
|
|
311
|
+
return 1
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
yaml_query() {
|
|
315
|
+
local path="$1"
|
|
316
|
+
|
|
317
|
+
if [ -z "$AST_FILE" ] || [ ! -f "$AST_FILE" ]; then
|
|
318
|
+
echo "Error: No AST loaded. Call yaml_parse first." >&2
|
|
319
|
+
return 1
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
path=$(echo "$path" | sed 's/^\.//')
|
|
323
|
+
|
|
324
|
+
local current_node="$AST_ROOT_ID"
|
|
325
|
+
|
|
326
|
+
local IFS='.'
|
|
327
|
+
for segment in $path; do
|
|
328
|
+
if echo "$segment" | grep -q '\['; then
|
|
329
|
+
local key index
|
|
330
|
+
key=$(echo "$segment" | sed 's/\[.*//')
|
|
331
|
+
index=$(echo "$segment" | sed 's/.*\[\([0-9]*\)\].*/\1/')
|
|
332
|
+
|
|
333
|
+
current_node=$(find_child_by_key "$current_node" "$key")
|
|
334
|
+
[ -z "$current_node" ] && return 1
|
|
335
|
+
|
|
336
|
+
current_node=$(find_child_by_index "$current_node" "$index")
|
|
337
|
+
[ -z "$current_node" ] && return 1
|
|
338
|
+
else
|
|
339
|
+
current_node=$(find_child_by_key "$current_node" "$segment")
|
|
340
|
+
[ -z "$current_node" ] && return 1
|
|
341
|
+
fi
|
|
342
|
+
done
|
|
343
|
+
|
|
344
|
+
# Check node type
|
|
345
|
+
local node_type
|
|
346
|
+
node_type=$(get_node_field "$current_node" 2)
|
|
347
|
+
|
|
348
|
+
# For map or array nodes, return children keys in YAML format
|
|
349
|
+
if [ "$node_type" = "map" ] || [ "$node_type" = "array" ]; then
|
|
350
|
+
local children
|
|
351
|
+
children=$(get_node_field "$current_node" 6)
|
|
352
|
+
|
|
353
|
+
if [ -n "$children" ]; then
|
|
354
|
+
# Split by comma and iterate
|
|
355
|
+
local IFS=','
|
|
356
|
+
for child_id in $children; do
|
|
357
|
+
local child_key
|
|
358
|
+
child_key=$(get_node_field "$child_id" 3)
|
|
359
|
+
echo "${child_key}:"
|
|
360
|
+
done
|
|
361
|
+
fi
|
|
362
|
+
else
|
|
363
|
+
# For scalar nodes, return the value
|
|
364
|
+
get_node_field "$current_node" 4
|
|
365
|
+
fi
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
# Create a new node in the AST (used by yaml_set for auto-creation)
|
|
369
|
+
# Usage: node_id=$(create_node_and_link "type" "key" "value" "parent_id")
|
|
370
|
+
# Returns: new node ID
|
|
371
|
+
# NOTE: This version adds the node as a child of parent (for yaml_set)
|
|
372
|
+
create_node_and_link() {
|
|
373
|
+
local type="$1"
|
|
374
|
+
local key="$2"
|
|
375
|
+
local value="$3"
|
|
376
|
+
local parent_id="$4"
|
|
377
|
+
|
|
378
|
+
# Get next node ID
|
|
379
|
+
local next_id
|
|
380
|
+
next_id=$(wc -l < "$AST_FILE")
|
|
381
|
+
|
|
382
|
+
# Create node: id|type|key|value|parent|children
|
|
383
|
+
echo "${next_id}|${type}|${key}|${value}|${parent_id}|" >> "$AST_FILE"
|
|
384
|
+
|
|
385
|
+
# Add this node to parent's children list
|
|
386
|
+
if [ "$parent_id" != "-1" ]; then
|
|
387
|
+
# Read current parent node
|
|
388
|
+
local parent_line
|
|
389
|
+
parent_line=$(sed -n "$((parent_id + 1))p" "$AST_FILE")
|
|
390
|
+
|
|
391
|
+
# Extract parent fields
|
|
392
|
+
local parent_children
|
|
393
|
+
parent_children=$(echo "$parent_line" | cut -d'|' -f6)
|
|
394
|
+
|
|
395
|
+
# Append new child ID
|
|
396
|
+
if [ -z "$parent_children" ]; then
|
|
397
|
+
parent_children="$next_id"
|
|
398
|
+
else
|
|
399
|
+
parent_children="${parent_children},${next_id}"
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
# Update parent node with new children list
|
|
403
|
+
local parent_prefix
|
|
404
|
+
parent_prefix=$(echo "$parent_line" | cut -d'|' -f1-5)
|
|
405
|
+
_yaml_sed_i "$((parent_id + 1))s@.*@${parent_prefix}|${parent_children}@" "$AST_FILE"
|
|
406
|
+
fi
|
|
407
|
+
|
|
408
|
+
echo "$next_id"
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
# Original create_node for backward compatibility (does NOT link to parent)
|
|
412
|
+
create_node() {
|
|
413
|
+
local type="$1"
|
|
414
|
+
local key="$2"
|
|
415
|
+
local value="$3"
|
|
416
|
+
local parent_id="$4"
|
|
417
|
+
|
|
418
|
+
# Get next node ID
|
|
419
|
+
local next_id
|
|
420
|
+
next_id=$(wc -l < "$AST_FILE")
|
|
421
|
+
|
|
422
|
+
# Create node: id|type|key|value|parent|children
|
|
423
|
+
echo "${next_id}|${type}|${key}|${value}|${parent_id}|" >> "$AST_FILE"
|
|
424
|
+
|
|
425
|
+
echo "$next_id"
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
yaml_set() {
|
|
429
|
+
local path="$1"
|
|
430
|
+
local new_value="$2"
|
|
431
|
+
|
|
432
|
+
if [ -z "$AST_FILE" ] || [ ! -f "$AST_FILE" ]; then
|
|
433
|
+
echo "Error: No AST loaded. Call yaml_parse first." >&2
|
|
434
|
+
return 1
|
|
435
|
+
fi
|
|
436
|
+
|
|
437
|
+
path=$(echo "$path" | sed 's/^\.//')
|
|
438
|
+
|
|
439
|
+
local current_node="$AST_ROOT_ID"
|
|
440
|
+
local IFS='.'
|
|
441
|
+
local segments=($path)
|
|
442
|
+
local last_index=$((${#segments[@]} - 1))
|
|
443
|
+
|
|
444
|
+
# Traverse path, creating missing nodes
|
|
445
|
+
local i=0
|
|
446
|
+
for segment in "${segments[@]}"; do
|
|
447
|
+
local is_last=$((i == last_index))
|
|
448
|
+
|
|
449
|
+
if echo "$segment" | grep -q '\['; then
|
|
450
|
+
local key index
|
|
451
|
+
key=$(echo "$segment" | sed 's/\[.*//')
|
|
452
|
+
index=$(echo "$segment" | sed 's/.*\[\([0-9]*\)\].*/\1/')
|
|
453
|
+
|
|
454
|
+
local child_node
|
|
455
|
+
child_node=$(find_child_by_key "$current_node" "$key")
|
|
456
|
+
if [ -z "$child_node" ]; then
|
|
457
|
+
# Create missing array node
|
|
458
|
+
child_node=$(create_node_and_link "array" "$key" "" "$current_node")
|
|
459
|
+
fi
|
|
460
|
+
current_node="$child_node"
|
|
461
|
+
|
|
462
|
+
child_node=$(find_child_by_index "$current_node" "$index")
|
|
463
|
+
if [ -z "$child_node" ]; then
|
|
464
|
+
echo "Error: Cannot create array index $index (not supported yet)" >&2
|
|
465
|
+
return 1
|
|
466
|
+
fi
|
|
467
|
+
current_node="$child_node"
|
|
468
|
+
else
|
|
469
|
+
local child_node
|
|
470
|
+
child_node=$(find_child_by_key "$current_node" "$segment")
|
|
471
|
+
|
|
472
|
+
if [ -z "$child_node" ]; then
|
|
473
|
+
# Create missing node
|
|
474
|
+
if [ "$is_last" -eq 1 ]; then
|
|
475
|
+
# Last segment - check for empty array/map
|
|
476
|
+
if [ "$new_value" = "[]" ]; then
|
|
477
|
+
# Create empty array
|
|
478
|
+
child_node=$(create_node_and_link "array" "$segment" "" "$current_node")
|
|
479
|
+
elif [ "$new_value" = "{}" ]; then
|
|
480
|
+
# Create empty map
|
|
481
|
+
child_node=$(create_node_and_link "map" "$segment" "" "$current_node")
|
|
482
|
+
else
|
|
483
|
+
# Create scalar with value
|
|
484
|
+
child_node=$(create_node_and_link "scalar" "$segment" "$new_value" "$current_node")
|
|
485
|
+
fi
|
|
486
|
+
return 0
|
|
487
|
+
else
|
|
488
|
+
# Intermediate segment - create map
|
|
489
|
+
child_node=$(create_node_and_link "map" "$segment" "" "$current_node")
|
|
490
|
+
fi
|
|
491
|
+
fi
|
|
492
|
+
current_node="$child_node"
|
|
493
|
+
fi
|
|
494
|
+
|
|
495
|
+
i=$((i + 1))
|
|
496
|
+
done
|
|
497
|
+
|
|
498
|
+
# Update existing node value
|
|
499
|
+
local node
|
|
500
|
+
node=$(get_node "$current_node")
|
|
501
|
+
|
|
502
|
+
local id type key value parent children
|
|
503
|
+
id=$(echo "$node" | cut -d'|' -f1)
|
|
504
|
+
type=$(echo "$node" | cut -d'|' -f2)
|
|
505
|
+
key=$(echo "$node" | cut -d'|' -f3)
|
|
506
|
+
parent=$(echo "$node" | cut -d'|' -f5)
|
|
507
|
+
children=$(echo "$node" | cut -d'|' -f6)
|
|
508
|
+
|
|
509
|
+
# Check if converting to empty array
|
|
510
|
+
if [ "$new_value" = "[]" ]; then
|
|
511
|
+
# Convert node to array type and clear children
|
|
512
|
+
local updated="$id|array|$key||$parent|"
|
|
513
|
+
_yaml_sed_i "$((current_node + 1))s@.*@$updated@" "$AST_FILE"
|
|
514
|
+
else
|
|
515
|
+
new_value=$(echo "$new_value" | sed 's/|/\\|/g')
|
|
516
|
+
local updated="$id|$type|$key|$new_value|$parent|$children"
|
|
517
|
+
_yaml_sed_i "$((current_node + 1))s@.*@$updated@" "$AST_FILE"
|
|
518
|
+
fi
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
yaml_write() {
|
|
522
|
+
local output_file="$1"
|
|
523
|
+
|
|
524
|
+
if [ -z "$AST_FILE" ] || [ ! -f "$AST_FILE" ]; then
|
|
525
|
+
echo "Error: No AST loaded. Call yaml_parse first." >&2
|
|
526
|
+
return 1
|
|
527
|
+
fi
|
|
528
|
+
|
|
529
|
+
serialize_node "$AST_ROOT_ID" 0 > "$output_file"
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
serialize_node() {
|
|
533
|
+
local node_id="$1"
|
|
534
|
+
local indent_level="$2"
|
|
535
|
+
local parent_type="${3:-}"
|
|
536
|
+
|
|
537
|
+
local node
|
|
538
|
+
node=$(get_node "$node_id")
|
|
539
|
+
|
|
540
|
+
local type key value children parent_id
|
|
541
|
+
type=$(echo "$node" | cut -d'|' -f2)
|
|
542
|
+
key=$(echo "$node" | cut -d'|' -f3)
|
|
543
|
+
value=$(echo "$node" | cut -d'|' -f4)
|
|
544
|
+
parent_id=$(echo "$node" | cut -d'|' -f5)
|
|
545
|
+
children=$(echo "$node" | cut -d'|' -f6)
|
|
546
|
+
|
|
547
|
+
# Determine parent type if not provided
|
|
548
|
+
if [ -z "$parent_type" ] && [ "$parent_id" -ge 0 ]; then
|
|
549
|
+
parent_type=$(get_node_field "$parent_id" 2)
|
|
550
|
+
fi
|
|
551
|
+
|
|
552
|
+
local indent=""
|
|
553
|
+
local i=0
|
|
554
|
+
while [ "$i" -lt "$indent_level" ]; do
|
|
555
|
+
indent="$indent "
|
|
556
|
+
i=$((i + 1))
|
|
557
|
+
done
|
|
558
|
+
|
|
559
|
+
case "$type" in
|
|
560
|
+
scalar)
|
|
561
|
+
if [ -n "$key" ]; then
|
|
562
|
+
echo "$indent$key: $value"
|
|
563
|
+
else
|
|
564
|
+
echo "$indent- $value"
|
|
565
|
+
fi
|
|
566
|
+
;;
|
|
567
|
+
|
|
568
|
+
map)
|
|
569
|
+
# If this map is in an array, first child gets dash prefix
|
|
570
|
+
local is_first_child=true
|
|
571
|
+
|
|
572
|
+
if [ "$node_id" -ne 0 ] && [ -n "$key" ]; then
|
|
573
|
+
echo "$indent$key:"
|
|
574
|
+
fi
|
|
575
|
+
|
|
576
|
+
if [ -n "$children" ]; then
|
|
577
|
+
local IFS=','
|
|
578
|
+
local next_indent
|
|
579
|
+
# Root node (id=0) doesn't add indentation
|
|
580
|
+
if [ "$node_id" -eq 0 ]; then
|
|
581
|
+
next_indent="$indent_level"
|
|
582
|
+
else
|
|
583
|
+
next_indent="$((indent_level + 1))"
|
|
584
|
+
fi
|
|
585
|
+
|
|
586
|
+
for child_id in $children; do
|
|
587
|
+
# If parent is array and this is first child, use dash
|
|
588
|
+
if [ "$parent_type" = "array" ] && [ "$is_first_child" = true ]; then
|
|
589
|
+
# Serialize first field with dash
|
|
590
|
+
local child_node
|
|
591
|
+
child_node=$(get_node "$child_id")
|
|
592
|
+
local child_type child_key child_value
|
|
593
|
+
child_type=$(echo "$child_node" | cut -d'|' -f2)
|
|
594
|
+
child_key=$(echo "$child_node" | cut -d'|' -f3)
|
|
595
|
+
child_value=$(echo "$child_node" | cut -d'|' -f4)
|
|
596
|
+
|
|
597
|
+
if [ "$child_type" = "scalar" ] && [ -n "$child_key" ]; then
|
|
598
|
+
echo "$indent- $child_key: $child_value"
|
|
599
|
+
fi
|
|
600
|
+
is_first_child=false
|
|
601
|
+
else
|
|
602
|
+
serialize_node "$child_id" "$next_indent" "$type"
|
|
603
|
+
fi
|
|
604
|
+
done
|
|
605
|
+
fi
|
|
606
|
+
;;
|
|
607
|
+
|
|
608
|
+
array)
|
|
609
|
+
if [ -n "$key" ]; then
|
|
610
|
+
echo "$indent$key:"
|
|
611
|
+
fi
|
|
612
|
+
|
|
613
|
+
if [ -n "$children" ]; then
|
|
614
|
+
local IFS=','
|
|
615
|
+
# Array children need to be indented
|
|
616
|
+
for child_id in $children; do
|
|
617
|
+
serialize_node "$child_id" "$((indent_level + 1))" "array"
|
|
618
|
+
done
|
|
619
|
+
fi
|
|
620
|
+
;;
|
|
621
|
+
esac
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
# ============================================================================
|
|
625
|
+
# BACKWARD COMPATIBILITY
|
|
626
|
+
# ============================================================================
|
|
627
|
+
|
|
628
|
+
yaml_get() {
|
|
629
|
+
local file="$1"
|
|
630
|
+
local key="$2"
|
|
631
|
+
|
|
632
|
+
if [ "$YAML_CURRENT_FILE" != "$file" ]; then
|
|
633
|
+
yaml_parse "$file" || return 1
|
|
634
|
+
fi
|
|
635
|
+
|
|
636
|
+
yaml_query ".$key"
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
yaml_get_nested() {
|
|
640
|
+
local file="$1"
|
|
641
|
+
local path="$2"
|
|
642
|
+
|
|
643
|
+
if [ "$YAML_CURRENT_FILE" != "$file" ]; then
|
|
644
|
+
yaml_parse "$file" || return 1
|
|
645
|
+
fi
|
|
646
|
+
|
|
647
|
+
yaml_query ".$path"
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
# Check if key exists (checks if node exists, not if it has a value)
|
|
651
|
+
yaml_has_key() {
|
|
652
|
+
local file="$1"
|
|
653
|
+
local key="$2"
|
|
654
|
+
|
|
655
|
+
if [ "$YAML_CURRENT_FILE" != "$file" ]; then
|
|
656
|
+
yaml_parse "$file" || return 1
|
|
657
|
+
fi
|
|
658
|
+
|
|
659
|
+
# Try to find the node (returns empty string on failure, but exit code tells us)
|
|
660
|
+
path=$(echo "$key" | sed 's/^\.//')
|
|
661
|
+
local current_node="$AST_ROOT_ID"
|
|
662
|
+
|
|
663
|
+
local IFS='.'
|
|
664
|
+
for segment in $path; do
|
|
665
|
+
if echo "$segment" | grep -q '\['; then
|
|
666
|
+
local k index
|
|
667
|
+
k=$(echo "$segment" | sed 's/\[.*//')
|
|
668
|
+
index=$(echo "$segment" | sed 's/.*\[\([0-9]*\)\].*/\1/')
|
|
669
|
+
|
|
670
|
+
current_node=$(find_child_by_key "$current_node" "$k" 2>/dev/null)
|
|
671
|
+
[ -z "$current_node" ] && return 1
|
|
672
|
+
|
|
673
|
+
current_node=$(find_child_by_index "$current_node" "$index" 2>/dev/null)
|
|
674
|
+
[ -z "$current_node" ] && return 1
|
|
675
|
+
else
|
|
676
|
+
current_node=$(find_child_by_key "$current_node" "$segment" 2>/dev/null)
|
|
677
|
+
[ -z "$current_node" ] && return 1
|
|
678
|
+
fi
|
|
679
|
+
done
|
|
680
|
+
|
|
681
|
+
# Node exists
|
|
682
|
+
return 0
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
# Get array count (for object arrays)
|
|
686
|
+
# Usage: yaml_get_array file.yaml "contents.commands"
|
|
687
|
+
# Returns: count of array elements
|
|
688
|
+
yaml_get_array() {
|
|
689
|
+
local file="$1"
|
|
690
|
+
local path="$2"
|
|
691
|
+
|
|
692
|
+
if [ "$YAML_CURRENT_FILE" != "$file" ]; then
|
|
693
|
+
yaml_parse "$file" || return 1
|
|
694
|
+
fi
|
|
695
|
+
|
|
696
|
+
# Find the array node
|
|
697
|
+
path=$(echo "$path" | sed 's/^\.//')
|
|
698
|
+
local current_node="$AST_ROOT_ID"
|
|
699
|
+
|
|
700
|
+
local IFS='.'
|
|
701
|
+
for segment in $path; do
|
|
702
|
+
current_node=$(find_child_by_key "$current_node" "$segment")
|
|
703
|
+
[ -z "$current_node" ] && return 1
|
|
704
|
+
done
|
|
705
|
+
|
|
706
|
+
# Get children count
|
|
707
|
+
local children
|
|
708
|
+
children=$(get_node_field "$current_node" 6)
|
|
709
|
+
|
|
710
|
+
if [ -z "$children" ]; then
|
|
711
|
+
echo "0"
|
|
712
|
+
else
|
|
713
|
+
echo "$children" | tr ',' '\n' | wc -l
|
|
714
|
+
fi
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
# Append scalar item to array
|
|
718
|
+
# Usage: yaml_array_append ".path.to.array" "value"
|
|
719
|
+
# Returns: node_id of new item
|
|
720
|
+
yaml_array_append() {
|
|
721
|
+
local path="$1"
|
|
722
|
+
local value="$2"
|
|
723
|
+
|
|
724
|
+
if [ -z "$AST_FILE" ] || [ ! -f "$AST_FILE" ]; then
|
|
725
|
+
echo "Error: No AST loaded. Call yaml_parse first." >&2
|
|
726
|
+
return 1
|
|
727
|
+
fi
|
|
728
|
+
|
|
729
|
+
# Find the array node
|
|
730
|
+
path=$(echo "$path" | sed 's/^\.//')
|
|
731
|
+
local current_node="$AST_ROOT_ID"
|
|
732
|
+
|
|
733
|
+
local IFS='.'
|
|
734
|
+
for segment in $path; do
|
|
735
|
+
if echo "$segment" | grep -q '\['; then
|
|
736
|
+
local key index
|
|
737
|
+
key=$(echo "$segment" | sed 's/\[.*//')
|
|
738
|
+
index=$(echo "$segment" | sed 's/.*\[\([0-9]*\)\].*/\1/')
|
|
739
|
+
|
|
740
|
+
current_node=$(find_child_by_key "$current_node" "$key")
|
|
741
|
+
[ -z "$current_node" ] && return 1
|
|
742
|
+
|
|
743
|
+
current_node=$(find_child_by_index "$current_node" "$index")
|
|
744
|
+
[ -z "$current_node" ] && return 1
|
|
745
|
+
else
|
|
746
|
+
current_node=$(find_child_by_key "$current_node" "$segment")
|
|
747
|
+
[ -z "$current_node" ] && return 1
|
|
748
|
+
fi
|
|
749
|
+
done
|
|
750
|
+
|
|
751
|
+
# Verify it's an array
|
|
752
|
+
local node_type
|
|
753
|
+
node_type=$(get_node_field "$current_node" 2)
|
|
754
|
+
|
|
755
|
+
if [ "$node_type" != "array" ]; then
|
|
756
|
+
echo "Error: Path does not point to an array" >&2
|
|
757
|
+
return 1
|
|
758
|
+
fi
|
|
759
|
+
|
|
760
|
+
# Create new scalar node
|
|
761
|
+
local new_node
|
|
762
|
+
new_node=$(create_node "scalar" "" "$value" "$current_node")
|
|
763
|
+
|
|
764
|
+
# Add as child
|
|
765
|
+
add_child "$current_node" "$new_node"
|
|
766
|
+
|
|
767
|
+
echo "$new_node"
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
# Delete a node at the specified path
|
|
771
|
+
# Usage: yaml_delete ".path.to.node"
|
|
772
|
+
# Returns: 0 on success, 1 on failure
|
|
773
|
+
yaml_delete() {
|
|
774
|
+
local path="$1"
|
|
775
|
+
|
|
776
|
+
if [ -z "$path" ]; then
|
|
777
|
+
echo "Error: Path is required" >&2
|
|
778
|
+
return 1
|
|
779
|
+
fi
|
|
780
|
+
|
|
781
|
+
# Remove leading dot
|
|
782
|
+
path="${path#.}"
|
|
783
|
+
|
|
784
|
+
# Navigate to parent and get the key to delete
|
|
785
|
+
local parent_path=""
|
|
786
|
+
local key_to_delete=""
|
|
787
|
+
|
|
788
|
+
# Split path into parent and key
|
|
789
|
+
if echo "$path" | grep -q '\.'; then
|
|
790
|
+
parent_path=$(echo "$path" | sed 's/\.[^.]*$//')
|
|
791
|
+
key_to_delete=$(echo "$path" | sed 's/.*\.//')
|
|
792
|
+
else
|
|
793
|
+
parent_path=""
|
|
794
|
+
key_to_delete="$path"
|
|
795
|
+
fi
|
|
796
|
+
|
|
797
|
+
# Find parent node
|
|
798
|
+
local current_node=0
|
|
799
|
+
if [ -n "$parent_path" ]; then
|
|
800
|
+
local IFS='.'
|
|
801
|
+
for segment in $parent_path; do
|
|
802
|
+
if echo "$segment" | grep -q '\['; then
|
|
803
|
+
local key index
|
|
804
|
+
key=$(echo "$segment" | sed 's/\[.*//')
|
|
805
|
+
index=$(echo "$segment" | sed 's/.*\[\([0-9]*\)\].*/\1/')
|
|
806
|
+
|
|
807
|
+
current_node=$(find_child_by_key "$current_node" "$key")
|
|
808
|
+
[ -z "$current_node" ] && return 1
|
|
809
|
+
|
|
810
|
+
current_node=$(find_child_by_index "$current_node" "$index")
|
|
811
|
+
[ -z "$current_node" ] && return 1
|
|
812
|
+
else
|
|
813
|
+
current_node=$(find_child_by_key "$current_node" "$segment")
|
|
814
|
+
[ -z "$current_node" ] && return 1
|
|
815
|
+
fi
|
|
816
|
+
done
|
|
817
|
+
fi
|
|
818
|
+
|
|
819
|
+
# Find the child node to delete
|
|
820
|
+
local node_to_delete
|
|
821
|
+
node_to_delete=$(find_child_by_key "$current_node" "$key_to_delete")
|
|
822
|
+
|
|
823
|
+
if [ -z "$node_to_delete" ]; then
|
|
824
|
+
echo "Error: Node not found: $path" >&2
|
|
825
|
+
return 1
|
|
826
|
+
fi
|
|
827
|
+
|
|
828
|
+
# Remove child from parent's children list
|
|
829
|
+
local parent_node
|
|
830
|
+
parent_node=$(get_node "$current_node")
|
|
831
|
+
|
|
832
|
+
local id type key value parent children
|
|
833
|
+
id=$(echo "$parent_node" | cut -d'|' -f1)
|
|
834
|
+
type=$(echo "$parent_node" | cut -d'|' -f2)
|
|
835
|
+
key=$(echo "$parent_node" | cut -d'|' -f3)
|
|
836
|
+
value=$(echo "$parent_node" | cut -d'|' -f4)
|
|
837
|
+
parent=$(echo "$parent_node" | cut -d'|' -f5)
|
|
838
|
+
children=$(echo "$parent_node" | cut -d'|' -f6)
|
|
839
|
+
|
|
840
|
+
# Remove node_to_delete from children list
|
|
841
|
+
local new_children=""
|
|
842
|
+
local IFS=','
|
|
843
|
+
for child_id in $children; do
|
|
844
|
+
if [ "$child_id" != "$node_to_delete" ]; then
|
|
845
|
+
if [ -z "$new_children" ]; then
|
|
846
|
+
new_children="$child_id"
|
|
847
|
+
else
|
|
848
|
+
new_children="$new_children,$child_id"
|
|
849
|
+
fi
|
|
850
|
+
fi
|
|
851
|
+
done
|
|
852
|
+
|
|
853
|
+
# Update parent node
|
|
854
|
+
local updated="$id|$type|$key|$value|$parent|$new_children"
|
|
855
|
+
_yaml_sed_i "$((current_node + 1))s@.*@$updated@" "$AST_FILE"
|
|
856
|
+
|
|
857
|
+
return 0
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
# Append object to array
|
|
861
|
+
# Usage: yaml_array_append_object ".path.to.array"
|
|
862
|
+
# Returns: node_id of new object (use yaml_object_set to add fields)
|
|
863
|
+
yaml_array_append_object() {
|
|
864
|
+
local path="$1"
|
|
865
|
+
|
|
866
|
+
if [ -z "$AST_FILE" ] || [ ! -f "$AST_FILE" ]; then
|
|
867
|
+
echo "Error: No AST loaded. Call yaml_parse first." >&2
|
|
868
|
+
return 1
|
|
869
|
+
fi
|
|
870
|
+
|
|
871
|
+
# Find the node
|
|
872
|
+
path=$(echo "$path" | sed 's/^\.//')
|
|
873
|
+
local current_node="$AST_ROOT_ID"
|
|
874
|
+
|
|
875
|
+
local IFS='.'
|
|
876
|
+
for segment in $path; do
|
|
877
|
+
if echo "$segment" | grep -q '\['; then
|
|
878
|
+
local key index
|
|
879
|
+
key=$(echo "$segment" | sed 's/\[.*//')
|
|
880
|
+
index=$(echo "$segment" | sed 's/.*\[\([0-9]*\)\].*/\1/')
|
|
881
|
+
|
|
882
|
+
current_node=$(find_child_by_key "$current_node" "$key")
|
|
883
|
+
[ -z "$current_node" ] && return 1
|
|
884
|
+
|
|
885
|
+
current_node=$(find_child_by_index "$current_node" "$index")
|
|
886
|
+
[ -z "$current_node" ] && return 1
|
|
887
|
+
else
|
|
888
|
+
current_node=$(find_child_by_key "$current_node" "$segment")
|
|
889
|
+
[ -z "$current_node" ] && return 1
|
|
890
|
+
fi
|
|
891
|
+
done
|
|
892
|
+
|
|
893
|
+
# Check node type
|
|
894
|
+
local node_type
|
|
895
|
+
node_type=$(get_node_field "$current_node" 2)
|
|
896
|
+
|
|
897
|
+
# If it's a map with no children, convert to array
|
|
898
|
+
if [ "$node_type" = "map" ]; then
|
|
899
|
+
local children
|
|
900
|
+
children=$(get_node_field "$current_node" 6)
|
|
901
|
+
if [ -z "$children" ]; then
|
|
902
|
+
# Empty map - convert to array
|
|
903
|
+
update_node_type "$current_node" "array"
|
|
904
|
+
else
|
|
905
|
+
echo "Error: Path points to non-empty map, not array" >&2
|
|
906
|
+
return 1
|
|
907
|
+
fi
|
|
908
|
+
elif [ "$node_type" != "array" ]; then
|
|
909
|
+
echo "Error: Path does not point to an array" >&2
|
|
910
|
+
return 1
|
|
911
|
+
fi
|
|
912
|
+
|
|
913
|
+
# Create new map node (object)
|
|
914
|
+
local new_node
|
|
915
|
+
new_node=$(create_node "map" "" "" "$current_node")
|
|
916
|
+
|
|
917
|
+
# Add as child
|
|
918
|
+
add_child "$current_node" "$new_node"
|
|
919
|
+
|
|
920
|
+
echo "$new_node"
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
# Set field on object (for building objects in arrays)
|
|
924
|
+
# Usage: yaml_object_set node_id "field_name" "value"
|
|
925
|
+
yaml_object_set() {
|
|
926
|
+
local object_node="$1"
|
|
927
|
+
local field_name="$2"
|
|
928
|
+
local field_value="$3"
|
|
929
|
+
|
|
930
|
+
if [ -z "$AST_FILE" ] || [ ! -f "$AST_FILE" ]; then
|
|
931
|
+
echo "Error: No AST loaded. Call yaml_parse first." >&2
|
|
932
|
+
return 1
|
|
933
|
+
fi
|
|
934
|
+
|
|
935
|
+
# Create scalar field
|
|
936
|
+
local field_node
|
|
937
|
+
field_node=$(create_node "scalar" "$field_name" "$field_value" "$object_node")
|
|
938
|
+
|
|
939
|
+
# Add as child
|
|
940
|
+
add_child "$object_node" "$field_node"
|
|
941
|
+
|
|
942
|
+
echo "$field_node"
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
# ============================================================================
|
|
946
|
+
# MAIN
|
|
947
|
+
# ============================================================================
|
|
948
|
+
|
|
949
|
+
trap cleanup_ast EXIT INT TERM
|
|
950
|
+
|
|
951
|
+
# Only run main if script is executed directly (not sourced)
|
|
952
|
+
if [ -n "${1:-}" ] && [ "${1:-}" != "-" ] && [ "${BASH_SOURCE[0]}" = "${0}" ]; then
|
|
953
|
+
case "$1" in
|
|
954
|
+
parse)
|
|
955
|
+
yaml_parse "$2"
|
|
956
|
+
echo "✓ Parsed $2 ($(get_next_node_id) nodes)"
|
|
957
|
+
;;
|
|
958
|
+
query)
|
|
959
|
+
yaml_parse "$2"
|
|
960
|
+
yaml_query "$3"
|
|
961
|
+
;;
|
|
962
|
+
set)
|
|
963
|
+
yaml_parse "$2"
|
|
964
|
+
yaml_set "$3" "$4"
|
|
965
|
+
yaml_write "$2"
|
|
966
|
+
echo "✓ Updated $2"
|
|
967
|
+
;;
|
|
968
|
+
debug)
|
|
969
|
+
yaml_parse "$2"
|
|
970
|
+
echo "AST Contents:"
|
|
971
|
+
cat "$AST_FILE"
|
|
972
|
+
;;
|
|
973
|
+
*)
|
|
974
|
+
echo "Usage: $0 {parse|query|set|debug} file.yaml [path] [value]"
|
|
975
|
+
echo ""
|
|
976
|
+
echo "Examples:"
|
|
977
|
+
echo " $0 parse file.yaml"
|
|
978
|
+
echo " $0 query file.yaml .name"
|
|
979
|
+
echo " $0 query file.yaml .tags[0]"
|
|
980
|
+
echo " $0 set file.yaml .version 2.0.0"
|
|
981
|
+
echo " $0 debug file.yaml"
|
|
982
|
+
exit 1
|
|
983
|
+
;;
|
|
984
|
+
esac
|
|
985
|
+
fi
|