this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

more

+919 -11789
+1
.devcontainer/init-firewall.sh
··· 72 72 "opam.ocaml.org" \ 73 73 "deb.debian.org" \ 74 74 "dl.geotessera.org" \ 75 + "tangled.org" \ 75 76 "api.fastmail.com" \ 76 77 "packages.apache.org" \ 77 78 "statsig.com"; do
+5 -1
.devcontainer/setup-ocaml.sh
··· 20 20 opam init --disable-sandboxing -y 21 21 eval $(opam env) 22 22 23 + opam repo add aoah https://tangled.org/anil.recoil.org/aoah-opam-repo.git 24 + 23 25 echo "Installing OCaml LSP server and common tools..." 24 26 opam install -y \ 25 27 ocaml-lsp-server \ ··· 50 52 bytesrw \ 51 53 toml \ 52 54 crockford \ 53 - jsonfeed 55 + jsonfeed \ 56 + yamlt \ 57 + xdge 54 58 55 59 echo "Setting up shell environment..." 56 60 echo 'eval $(opam env)' >> ~/.bashrc
+1
term/sortal-mosaic/.gitignore
··· 1 + _build
+11
term/sortal-mosaic/bin/dune
··· 1 + (executable 2 + (name main) 3 + (public_name sortal-browser) 4 + (libraries 5 + sortal_mosaic 6 + sortal 7 + eio 8 + eio_main 9 + logs 10 + logs.fmt 11 + fmt.tty))
+23
term/sortal-mosaic/bin/main.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Sortal Browser - Terminal UI for browsing contacts *) 7 + 8 + let () = 9 + (* Set up logging *) 10 + Fmt_tty.setup_std_outputs (); 11 + Logs.set_reporter (Logs_fmt.reporter ()); 12 + Logs.set_level (Some Logs.Warning); 13 + 14 + Eio_main.run @@ fun env -> 15 + let fs = Eio.Stdenv.fs env in 16 + let store = Sortal.create fs "sortal" in 17 + 18 + (* Run the TEA app *) 19 + Mosaic_tea.run 20 + ~exit_on_ctrl_c:false 21 + ~mouse:(Some `Sgr_any) 22 + ~bracketed_paste:true 23 + (Sortal_mosaic.app store)
+27
term/sortal-mosaic/dune-project
··· 1 + (lang dune 3.20) 2 + 3 + (name sortal_mosaic) 4 + 5 + (generate_opam_files true) 6 + 7 + (license ISC) 8 + (authors "Anil Madhavapeddy") 9 + (maintainers "Anil Madhavapeddy <anil@recoil.org>") 10 + 11 + (package 12 + (name sortal_mosaic) 13 + (synopsis "Terminal UI for browsing Sortal contacts") 14 + (description 15 + "A terminal-based contact browser combining Sortal (contact management) 16 + and Mosaic (TEA-based terminal UI framework). Features master-detail 17 + layout, search, and temporal data display.") 18 + (depends 19 + (ocaml (>= 5.1.0)) 20 + eio 21 + eio_main 22 + sortal 23 + mosaic 24 + matrix 25 + toffee 26 + fmt 27 + logs))
+5
term/sortal-mosaic/dune-workspace
··· 1 + (lang dune 3.20) 2 + 3 + (env 4 + (dev 5 + (flags (:standard -w -69))))
+12
term/sortal-mosaic/lib/dune
··· 1 + (library 2 + (name sortal_mosaic) 3 + (public_name sortal_mosaic) 4 + (libraries 5 + sortal 6 + mosaic.tea 7 + mosaic.ui 8 + matrix 9 + matrix.ansi 10 + matrix.input 11 + toffee 12 + unix))
+796
term/sortal-mosaic/lib/sortal_mosaic.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Sortal-Mosaic: Terminal UI for browsing contacts *) 7 + 8 + open Mosaic_tea 9 + module Contact = Sortal.Contact 10 + module Temporal = Sortal.Temporal 11 + 12 + (* ========================================================================= *) 13 + (* Model Types *) 14 + (* ========================================================================= *) 15 + 16 + type focus_area = 17 + | Search_input 18 + | Contact_list 19 + | Detail_view 20 + | Log_panel 21 + 22 + type log_level = Info | Warn | Error 23 + 24 + type log_entry = { 25 + timestamp : float; 26 + level : log_level; 27 + message : string; 28 + } 29 + 30 + type model = { 31 + (* Data *) 32 + store : Sortal.Store.t; 33 + all_contacts : Contact.t list; 34 + filtered_contacts : Contact.t list; 35 + (* Selection *) 36 + selected_index : int; 37 + selected_contact : Contact.t option; 38 + (* Search *) 39 + search_query : string; 40 + (* UI State *) 41 + focus : focus_area; 42 + detail_scroll_y : int; 43 + log_scroll_y : int; 44 + (* Logs *) 45 + logs : log_entry list; 46 + max_logs : int; 47 + (* Layout *) 48 + terminal_width : int; 49 + terminal_height : int; 50 + list_width_pct : int; 51 + log_height : int; 52 + log_expanded : bool; 53 + } 54 + 55 + (* ========================================================================= *) 56 + (* Messages *) 57 + (* ========================================================================= *) 58 + 59 + type msg = 60 + (* Navigation *) 61 + | Select_next 62 + | Select_prev 63 + | Select_contact of int 64 + | Page_up 65 + | Page_down 66 + | Go_to_top 67 + | Go_to_bottom 68 + (* Focus *) 69 + | Focus_search 70 + | Focus_list 71 + | Focus_detail 72 + | Focus_log 73 + | Cycle_focus 74 + | Cycle_focus_back 75 + (* Search *) 76 + | Update_search of string 77 + | Clear_search 78 + | Submit_search 79 + (* Scrolling *) 80 + | Scroll_detail of int 81 + | Scroll_log of int 82 + (* Data *) 83 + | Refresh_contacts 84 + (* Log panel *) 85 + | Toggle_log_expand 86 + (* System *) 87 + | Resize of int * int 88 + | Quit 89 + 90 + (* ========================================================================= *) 91 + (* Helpers *) 92 + (* ========================================================================= *) 93 + 94 + let log_entry level message = 95 + { timestamp = Unix.gettimeofday (); level; message } 96 + 97 + let add_log model entry = 98 + let logs = entry :: model.logs in 99 + let logs = 100 + if List.length logs > model.max_logs then 101 + List.filteri (fun i _ -> i < model.max_logs) logs 102 + else logs 103 + in 104 + { model with logs } 105 + 106 + let format_temporal_range range = 107 + match range with 108 + | None -> "" 109 + | Some r -> ( 110 + match (r.Temporal.from, r.until) with 111 + | Some f, Some u -> 112 + Printf.sprintf " [%s-%s]" 113 + (Temporal.format_date f) 114 + (Temporal.format_date u) 115 + | Some f, None -> 116 + Printf.sprintf " [%s-]" (Temporal.format_date f) 117 + | None, Some u -> 118 + Printf.sprintf " [-%s]" (Temporal.format_date u) 119 + | None, None -> "") 120 + 121 + (* ========================================================================= *) 122 + (* Init *) 123 + (* ========================================================================= *) 124 + 125 + let init store () = 126 + let all_contacts = 127 + Sortal.list store |> List.sort (fun a b -> Contact.compare a b) 128 + in 129 + let log_msg = 130 + log_entry Info 131 + (Printf.sprintf "Loaded %d contacts" (List.length all_contacts)) 132 + in 133 + let model = 134 + { 135 + store; 136 + all_contacts; 137 + filtered_contacts = all_contacts; 138 + selected_index = 0; 139 + selected_contact = List.nth_opt all_contacts 0; 140 + search_query = ""; 141 + focus = Contact_list; 142 + detail_scroll_y = 0; 143 + log_scroll_y = 0; 144 + logs = [ log_msg ]; 145 + max_logs = 100; 146 + terminal_width = 80; 147 + terminal_height = 24; 148 + list_width_pct = 30; 149 + log_height = 4; 150 + log_expanded = false; 151 + } 152 + in 153 + (model, Cmd.none) 154 + 155 + (* ========================================================================= *) 156 + (* Update *) 157 + (* ========================================================================= *) 158 + 159 + let rec update msg model = 160 + match msg with 161 + | Update_search query -> 162 + let filtered = 163 + if query = "" then model.all_contacts 164 + else Sortal.search_all model.store query 165 + in 166 + let log_msg = 167 + log_entry Info 168 + (Printf.sprintf "Search '%s' matched %d contacts" query 169 + (List.length filtered)) 170 + in 171 + let selected = List.nth_opt filtered 0 in 172 + let model = 173 + { 174 + model with 175 + search_query = query; 176 + filtered_contacts = filtered; 177 + selected_index = 0; 178 + selected_contact = selected; 179 + detail_scroll_y = 0; 180 + } 181 + in 182 + (add_log model log_msg, Cmd.none) 183 + | Clear_search -> 184 + let model = 185 + { 186 + model with 187 + search_query = ""; 188 + filtered_contacts = model.all_contacts; 189 + selected_index = 0; 190 + selected_contact = List.nth_opt model.all_contacts 0; 191 + focus = Contact_list; 192 + } 193 + in 194 + (model, Cmd.focus "contact-select") 195 + | Submit_search -> ({ model with focus = Contact_list }, Cmd.focus "contact-select") 196 + | Select_contact idx -> 197 + let contact = List.nth_opt model.filtered_contacts idx in 198 + ( { 199 + model with 200 + selected_index = idx; 201 + selected_contact = contact; 202 + detail_scroll_y = 0; 203 + }, 204 + Cmd.none ) 205 + | Select_next -> 206 + let max_idx = max 0 (List.length model.filtered_contacts - 1) in 207 + let new_idx = min (model.selected_index + 1) max_idx in 208 + update (Select_contact new_idx) model 209 + | Select_prev -> 210 + let new_idx = max (model.selected_index - 1) 0 in 211 + update (Select_contact new_idx) model 212 + | Page_down -> 213 + let page_size = 10 in 214 + let max_idx = max 0 (List.length model.filtered_contacts - 1) in 215 + let new_idx = min (model.selected_index + page_size) max_idx in 216 + update (Select_contact new_idx) model 217 + | Page_up -> 218 + let page_size = 10 in 219 + let new_idx = max (model.selected_index - page_size) 0 in 220 + update (Select_contact new_idx) model 221 + | Go_to_top -> update (Select_contact 0) model 222 + | Go_to_bottom -> 223 + let max_idx = max 0 (List.length model.filtered_contacts - 1) in 224 + update (Select_contact max_idx) model 225 + | Focus_search -> ({ model with focus = Search_input }, Cmd.focus "search-input") 226 + | Focus_list -> ({ model with focus = Contact_list }, Cmd.focus "contact-select") 227 + | Focus_detail -> ({ model with focus = Detail_view }, Cmd.focus "detail-scroll") 228 + | Focus_log -> ({ model with focus = Log_panel }, Cmd.focus "log-scroll") 229 + | Cycle_focus -> 230 + let next_focus = 231 + match model.focus with 232 + | Search_input -> Contact_list 233 + | Contact_list -> Detail_view 234 + | Detail_view -> Log_panel 235 + | Log_panel -> Search_input 236 + in 237 + let focus_id = 238 + match next_focus with 239 + | Search_input -> "search-input" 240 + | Contact_list -> "contact-select" 241 + | Detail_view -> "detail-scroll" 242 + | Log_panel -> "log-scroll" 243 + in 244 + ({ model with focus = next_focus }, Cmd.focus focus_id) 245 + | Cycle_focus_back -> 246 + let prev_focus = 247 + match model.focus with 248 + | Search_input -> Log_panel 249 + | Contact_list -> Search_input 250 + | Detail_view -> Contact_list 251 + | Log_panel -> Detail_view 252 + in 253 + let focus_id = 254 + match prev_focus with 255 + | Search_input -> "search-input" 256 + | Contact_list -> "contact-select" 257 + | Detail_view -> "detail-scroll" 258 + | Log_panel -> "log-scroll" 259 + in 260 + ({ model with focus = prev_focus }, Cmd.focus focus_id) 261 + | Scroll_detail delta -> 262 + let new_scroll = max 0 (model.detail_scroll_y + delta) in 263 + ({ model with detail_scroll_y = new_scroll }, Cmd.none) 264 + | Scroll_log delta -> 265 + let new_scroll = max 0 (model.log_scroll_y + delta) in 266 + ({ model with log_scroll_y = new_scroll }, Cmd.none) 267 + | Toggle_log_expand -> 268 + ({ model with log_expanded = not model.log_expanded }, Cmd.none) 269 + | Refresh_contacts -> 270 + let all_contacts = 271 + Sortal.list model.store |> List.sort (fun a b -> Contact.compare a b) 272 + in 273 + let filtered = 274 + if model.search_query = "" then all_contacts 275 + else Sortal.search_all model.store model.search_query 276 + in 277 + let log_msg = 278 + log_entry Info 279 + (Printf.sprintf "Refreshed: %d contacts" (List.length all_contacts)) 280 + in 281 + let model = 282 + { 283 + model with 284 + all_contacts; 285 + filtered_contacts = filtered; 286 + selected_index = 0; 287 + selected_contact = List.nth_opt filtered 0; 288 + } 289 + in 290 + (add_log model log_msg, Cmd.none) 291 + | Resize (w, h) -> 292 + ({ model with terminal_width = w; terminal_height = h }, Cmd.none) 293 + | Quit -> (model, Cmd.quit) 294 + 295 + (* ========================================================================= *) 296 + (* View Helpers *) 297 + (* ========================================================================= *) 298 + 299 + let section_header title = 300 + box ~flex_direction:Column ~margin:(margin 0) ~gap:(gap 0) 301 + [ 302 + text ~content:title 303 + ~text_style:(Ansi.Style.make ~bold:true ~fg:Ansi.Color.cyan ()) 304 + (); 305 + text ~content:(String.make (String.length title) '-') 306 + ~text_style:(Ansi.Style.make ~fg:Ansi.Color.cyan ()) 307 + (); 308 + ] 309 + 310 + let labeled_value ?badge ?badge_color ?suffix ?suffix_color ?value_color label 311 + value = 312 + let value_style = 313 + match value_color with 314 + | Some c -> Ansi.Style.make ~fg:c () 315 + | None -> Ansi.Style.default 316 + in 317 + box ~flex_direction:Row ~gap:(gap 1) 318 + [ 319 + text ~content:label (); 320 + text ~content:value ~text_style:value_style (); 321 + (match badge with 322 + | Some b -> 323 + let style = 324 + Ansi.Style.make 325 + ~fg:(Option.value badge_color ~default:Ansi.Color.green) 326 + () 327 + in 328 + text ~content:b ~text_style:style () 329 + | None -> null); 330 + (match suffix with 331 + | Some s -> 332 + let style = 333 + Ansi.Style.make 334 + ~fg:(Option.value suffix_color ~default:(Ansi.Color.grayscale ~level:12)) 335 + () 336 + in 337 + text ~content:s ~text_style:style () 338 + | None -> null); 339 + ] 340 + 341 + let indented_text content = 342 + box ~padding:(padding 0) ~margin:(margin 0) 343 + [ 344 + text ~content:(" " ^ content) 345 + ~text_style:(Ansi.Style.make ~fg:Ansi.Color.white ()) 346 + (); 347 + ] 348 + 349 + (* ========================================================================= *) 350 + (* View Components *) 351 + (* ========================================================================= *) 352 + 353 + let search_bar model = 354 + let count_text = 355 + Printf.sprintf "%d contacts" (List.length model.filtered_contacts) 356 + in 357 + let focused = model.focus = Search_input in 358 + box ~flex_direction:Row 359 + ~padding: 360 + (Toffee.Geometry.Rect.make 361 + ~top:(Toffee.Style.Length_percentage.length 0.) 362 + ~bottom:(Toffee.Style.Length_percentage.length 0.) 363 + ~left:(Toffee.Style.Length_percentage.length 1.) 364 + ~right:(Toffee.Style.Length_percentage.length 1.)) 365 + ~gap:(gap 2) ~align_items:Center ~border:true ~border_sides:[ `Bottom ] 366 + ~size:{ width = pct 100; height = px 3 } 367 + [ 368 + text ~content:"Search:" (); 369 + text_input ~id:"search-input" ~autofocus:focused 370 + ~placeholder:"Type to filter..." ~value:model.search_query 371 + ~on_input:(fun s -> Some (Update_search s)) 372 + ~on_submit:(fun _ -> Some Submit_search) 373 + ~size:{ width = px 30; height = px 1 } 374 + ~focused_background:Ansi.Color.black 375 + ~focused_text_color:Ansi.Color.white (); 376 + box ~flex_grow:1. []; 377 + text ~content:count_text 378 + ~text_style:(Ansi.Style.make ~fg:Ansi.Color.cyan ()) 379 + (); 380 + ] 381 + 382 + let contact_list model = 383 + let options = 384 + model.filtered_contacts 385 + |> List.map (fun contact -> 386 + let name = Contact.primary_name contact in 387 + let kind = Contact.kind contact in 388 + let kind_badge = 389 + match kind with 390 + | Contact.Person -> "" 391 + | Organization -> " [Org]" 392 + | Group -> " [Grp]" 393 + | Role -> " [Role]" 394 + in 395 + let has_current = 396 + Option.is_some (Contact.current_email contact) 397 + || Option.is_some (Contact.current_organization contact) 398 + in 399 + let indicator = if has_current then " *" else "" in 400 + { 401 + Select.name = name ^ kind_badge ^ indicator; 402 + description = Some ("@" ^ Contact.handle contact); 403 + }) 404 + in 405 + let list_width = model.list_width_pct in 406 + box ~border:true ~title:"Contacts" 407 + ~size:{ width = pct list_width; height = pct 100 } 408 + [ 409 + select ~id:"contact-select" ~options ~selected_index:model.selected_index 410 + ~autofocus:(model.focus = Contact_list) 411 + ~show_description:true ~show_scroll_indicator:true ~wrap_selection:false 412 + ~selected_background:Ansi.Color.blue 413 + ~selected_text_color:Ansi.Color.white 414 + ~focused_background:(Ansi.Color.grayscale ~level:4) 415 + ~on_change:(fun idx -> Some (Select_contact idx)) 416 + ~size:{ width = pct 100; height = pct 100 } 417 + (); 418 + ] 419 + 420 + let current_info_section contact = 421 + let current_email = Contact.current_email contact in 422 + let current_org = Contact.current_organization contact in 423 + if Option.is_none current_email && Option.is_none current_org then null 424 + else 425 + box ~flex_direction:Column ~gap:(gap 1) 426 + [ 427 + section_header "CURRENT"; 428 + (match current_email with 429 + | None -> null 430 + | Some email -> 431 + labeled_value "Email:" email ~badge:"[current]" 432 + ~badge_color:Ansi.Color.green); 433 + (match current_org with 434 + | None -> null 435 + | Some org -> 436 + box ~flex_direction:Column 437 + [ 438 + labeled_value "Org:" org.name ~badge:"[current]" 439 + ~badge_color:Ansi.Color.green; 440 + (match org.title with 441 + | Some t -> indented_text ("Title: " ^ t) 442 + | None -> null); 443 + (match org.department with 444 + | Some d -> indented_text ("Dept: " ^ d) 445 + | None -> null); 446 + ]); 447 + ] 448 + 449 + let services_section contact = 450 + let services = Contact.current_services contact in 451 + if services = [] then null 452 + else 453 + box ~flex_direction:Column ~gap:(gap 1) 454 + [ 455 + section_header "SERVICES"; 456 + fragment 457 + (List.map 458 + (fun (svc : Contact.service) -> 459 + let kind_str = 460 + match svc.kind with 461 + | Some k -> Contact.service_kind_to_string k 462 + | None -> "link" 463 + in 464 + let handle_str = 465 + match svc.handle with 466 + | Some h -> Printf.sprintf "@%s" h 467 + | None -> svc.url 468 + in 469 + let primary_suffix = if svc.primary then " (primary)" else "" in 470 + labeled_value (kind_str ^ ":") handle_str ~suffix:primary_suffix 471 + ~suffix_color:Ansi.Color.yellow) 472 + services); 473 + ] 474 + 475 + let historical_section contact = 476 + let all_emails = Contact.emails contact in 477 + let historical_emails = 478 + List.filter 479 + (fun (e : Contact.email) -> 480 + not (Temporal.is_current e.range)) 481 + all_emails 482 + in 483 + let all_orgs = Contact.organizations contact in 484 + let historical_orgs = 485 + List.filter 486 + (fun (o : Contact.organization) -> 487 + not (Temporal.is_current o.range)) 488 + all_orgs 489 + in 490 + if historical_emails = [] && historical_orgs = [] then null 491 + else 492 + box ~flex_direction:Column ~gap:(gap 1) 493 + [ 494 + section_header "HISTORICAL"; 495 + fragment 496 + (List.map 497 + (fun (e : Contact.email) -> 498 + let range_str = format_temporal_range e.range in 499 + labeled_value "Email:" e.address ~suffix:range_str 500 + ~value_color:(Ansi.Color.grayscale ~level:12)) 501 + historical_emails); 502 + fragment 503 + (List.map 504 + (fun (o : Contact.organization) -> 505 + let title_str = 506 + match o.title with Some t -> " - " ^ t | None -> "" 507 + in 508 + let range_str = format_temporal_range o.range in 509 + labeled_value "Org:" (o.name ^ title_str) ~suffix:range_str 510 + ~value_color:(Ansi.Color.grayscale ~level:12)) 511 + historical_orgs); 512 + ] 513 + 514 + let links_section contact = 515 + let urls = Contact.urls contact in 516 + if urls = [] then null 517 + else 518 + box ~flex_direction:Column ~gap:(gap 1) 519 + [ 520 + section_header "LINKS"; 521 + fragment 522 + (List.map 523 + (fun (u : Contact.url_entry) -> 524 + let label_str = 525 + match u.label with Some l -> " (" ^ l ^ ")" | None -> "" 526 + in 527 + labeled_value "URL:" (u.url ^ label_str) 528 + ~value_color:Ansi.Color.blue) 529 + urls); 530 + ] 531 + 532 + let all_emails_section contact = 533 + let emails = Contact.emails contact in 534 + if emails = [] then null 535 + else 536 + box ~flex_direction:Column ~gap:(gap 1) 537 + [ 538 + section_header "ALL EMAILS"; 539 + fragment 540 + (List.map 541 + (fun (e : Contact.email) -> 542 + let type_str = 543 + match e.type_ with 544 + | Some Contact.Work -> " (work)" 545 + | Some Personal -> " (personal)" 546 + | Some Other -> " (other)" 547 + | None -> "" 548 + in 549 + let is_current = Temporal.is_current e.range in 550 + let range_str = format_temporal_range e.range in 551 + if is_current then 552 + labeled_value "Email:" (e.address ^ type_str) 553 + ~badge:"[current]" ~badge_color:Ansi.Color.green 554 + else 555 + labeled_value "Email:" (e.address ^ type_str) ~suffix:range_str 556 + ~value_color:(Ansi.Color.grayscale ~level:12)) 557 + emails); 558 + ] 559 + 560 + let contact_detail model = 561 + let detail_width = 100 - model.list_width_pct in 562 + match model.selected_contact with 563 + | None -> 564 + box ~border:true ~title:"Details" 565 + ~size:{ width = pct detail_width; height = pct 100 } 566 + ~align_items:Center ~justify_content:Center 567 + [ 568 + text ~content:"Select a contact" 569 + ~text_style:(Ansi.Style.make ~fg:(Ansi.Color.grayscale ~level:12) ()) 570 + (); 571 + ] 572 + | Some contact -> 573 + let handle = Contact.handle contact in 574 + let kind = 575 + Contact.kind contact |> Contact.contact_kind_to_string 576 + |> String.capitalize_ascii 577 + in 578 + let name = Contact.primary_name contact in 579 + let aliases = Contact.names contact |> List.tl in 580 + let title = Printf.sprintf "@%s - %s" handle kind in 581 + box ~border:true ~title 582 + ~size:{ width = pct detail_width; height = pct 100 } 583 + [ 584 + scroll_box ~id:"detail-scroll" 585 + ~autofocus:(model.focus = Detail_view) 586 + ~scroll_y:true 587 + ~size:{ width = pct 100; height = pct 100 } 588 + [ 589 + box ~flex_direction:Column ~padding:(padding 1) ~gap:(gap 1) 590 + [ 591 + (* Header *) 592 + text ~content:name 593 + ~text_style: 594 + (Ansi.Style.make ~bold:true ~fg:Ansi.Color.white ()) 595 + (); 596 + (if aliases <> [] then 597 + text 598 + ~content: 599 + (Printf.sprintf "aka: %s" (String.concat ", " aliases)) 600 + ~text_style: 601 + (Ansi.Style.make ~fg:(Ansi.Color.grayscale ~level:12) ~italic:true ()) 602 + () 603 + else null); 604 + text 605 + ~content:(String.make 50 '-') 606 + ~text_style:(Ansi.Style.make ~fg:Ansi.Color.cyan ()) 607 + (); 608 + (* Sections *) 609 + current_info_section contact; 610 + services_section contact; 611 + all_emails_section contact; 612 + historical_section contact; 613 + links_section contact; 614 + (* ORCID if present *) 615 + (match Contact.orcid contact with 616 + | Some orcid -> 617 + labeled_value "ORCID:" ("https://orcid.org/" ^ orcid) 618 + ~value_color:Ansi.Color.blue 619 + | None -> null); 620 + ]; 621 + ]; 622 + ] 623 + 624 + let log_panel model = 625 + let log_style level = 626 + match level with 627 + | Info -> Ansi.Style.make ~fg:Ansi.Color.cyan () 628 + | Warn -> Ansi.Style.make ~fg:Ansi.Color.yellow () 629 + | Error -> Ansi.Style.make ~fg:Ansi.Color.red ~bold:true () 630 + in 631 + let level_str = function Info -> "INFO " | Warn -> "WARN " | Error -> "ERROR" in 632 + let height = if model.log_expanded then 12 else model.log_height in 633 + box ~border:true ~title:"Logs" ~size:{ width = pct 100; height = px height } 634 + [ 635 + scroll_box ~id:"log-scroll" 636 + ~autofocus:(model.focus = Log_panel) 637 + ~scroll_y:true 638 + ~size:{ width = pct 100; height = pct 100 } 639 + [ 640 + box ~flex_direction:Column 641 + [ 642 + fragment 643 + (List.rev_map 644 + (fun entry -> 645 + let time = Unix.localtime entry.timestamp in 646 + let time_str = 647 + Printf.sprintf "[%02d:%02d:%02d]" time.Unix.tm_hour 648 + time.Unix.tm_min time.Unix.tm_sec 649 + in 650 + box ~flex_direction:Row ~gap:(gap 1) 651 + [ 652 + text ~content:time_str 653 + ~text_style:(Ansi.Style.make ~fg:(Ansi.Color.grayscale ~level:12) ()) 654 + (); 655 + text ~content:(level_str entry.level) 656 + ~text_style:(log_style entry.level) 657 + (); 658 + text ~content:entry.message (); 659 + ]) 660 + model.logs); 661 + ]; 662 + ]; 663 + ] 664 + 665 + let help_bar _model = 666 + let key_style = Ansi.Style.make ~fg:Ansi.Color.yellow () in 667 + let sep = " | " in 668 + box 669 + ~size:{ width = pct 100; height = px 1 } 670 + ~flex_direction:Row ~gap:(gap 0) 671 + ~background:Ansi.Color.black 672 + [ 673 + text ~content:" j/k" ~text_style:key_style (); 674 + text ~content:":nav" (); 675 + text ~content:sep (); 676 + text ~content:"/" ~text_style:key_style (); 677 + text ~content:":search" (); 678 + text ~content:sep (); 679 + text ~content:"Tab" ~text_style:key_style (); 680 + text ~content:":focus" (); 681 + text ~content:sep (); 682 + text ~content:"l" ~text_style:key_style (); 683 + text ~content:":log" (); 684 + text ~content:sep (); 685 + text ~content:"r" ~text_style:key_style (); 686 + text ~content:":refresh" (); 687 + text ~content:sep (); 688 + text ~content:"q" ~text_style:key_style (); 689 + text ~content:":quit" (); 690 + ] 691 + 692 + (* ========================================================================= *) 693 + (* Main View *) 694 + (* ========================================================================= *) 695 + 696 + let view model = 697 + box ~flex_direction:Column ~size:{ width = pct 100; height = pct 100 } 698 + [ 699 + (* Search bar *) 700 + search_bar model; 701 + (* Main content: list + detail *) 702 + box ~flex_direction:Row ~flex_grow:1. 703 + ~size:{ width = pct 100; height = auto } 704 + [ contact_list model; contact_detail model ]; 705 + (* Log panel *) 706 + log_panel model; 707 + (* Help bar *) 708 + help_bar model; 709 + ] 710 + 711 + (* ========================================================================= *) 712 + (* Subscriptions *) 713 + (* ========================================================================= *) 714 + 715 + let subscriptions model = 716 + Sub.batch 717 + [ 718 + Sub.on_key (fun ev -> 719 + let data = Mosaic_ui.Event.Key.data ev in 720 + let key = data.key in 721 + let ctrl = data.modifier.ctrl in 722 + let shift = data.modifier.shift in 723 + match (model.focus, key, ctrl, shift) with 724 + (* Global quit *) 725 + | _, Char c, false, false when Uchar.equal c (Uchar.of_char 'q') -> 726 + Some Quit 727 + | _, Char c, true, false when Uchar.equal c (Uchar.of_char 'c') -> 728 + Some Quit 729 + (* Global search *) 730 + | focus, Char c, false, false 731 + when Uchar.equal c (Uchar.of_char '/') && focus <> Search_input -> 732 + Some Focus_search 733 + (* Global refresh *) 734 + | _, Char c, false, false when Uchar.equal c (Uchar.of_char 'r') -> 735 + Some Refresh_contacts 736 + (* Tab cycling *) 737 + | _, Tab, false, false -> Some Cycle_focus 738 + | _, Tab, false, true -> Some Cycle_focus_back 739 + (* Log expand toggle *) 740 + | _, Char c, false, false when Uchar.equal c (Uchar.of_char 'l') -> 741 + Some Toggle_log_expand 742 + (* Search mode *) 743 + | Search_input, Escape, false, false -> Some Clear_search 744 + (* List navigation - only when list is focused *) 745 + | Contact_list, Up, false, false -> Some Select_prev 746 + | Contact_list, Down, false, false -> Some Select_next 747 + | Contact_list, Char c, false, false 748 + when Uchar.equal c (Uchar.of_char 'k') -> 749 + Some Select_prev 750 + | Contact_list, Char c, false, false 751 + when Uchar.equal c (Uchar.of_char 'j') -> 752 + Some Select_next 753 + | Contact_list, Char c, false, false 754 + when Uchar.equal c (Uchar.of_char 'g') -> 755 + Some Go_to_top 756 + | Contact_list, Char c, false, true 757 + when Uchar.equal c (Uchar.of_char 'G') -> 758 + Some Go_to_bottom 759 + | Contact_list, Char c, true, false 760 + when Uchar.equal c (Uchar.of_char 'u') -> 761 + Some Page_up 762 + | Contact_list, Char c, true, false 763 + when Uchar.equal c (Uchar.of_char 'd') -> 764 + Some Page_down 765 + | Contact_list, Page_up, false, false -> Some Page_up 766 + | Contact_list, Page_down, false, false -> Some Page_down 767 + | Contact_list, Enter, false, false -> Some Focus_detail 768 + (* Detail scrolling *) 769 + | Detail_view, Up, false, false -> Some (Scroll_detail (-1)) 770 + | Detail_view, Down, false, false -> Some (Scroll_detail 1) 771 + | Detail_view, Char c, false, false 772 + when Uchar.equal c (Uchar.of_char 'k') -> 773 + Some (Scroll_detail (-1)) 774 + | Detail_view, Char c, false, false 775 + when Uchar.equal c (Uchar.of_char 'j') -> 776 + Some (Scroll_detail 1) 777 + | Detail_view, Escape, false, false -> Some Focus_list 778 + (* Log scrolling *) 779 + | Log_panel, Up, false, false -> Some (Scroll_log (-1)) 780 + | Log_panel, Down, false, false -> Some (Scroll_log 1) 781 + | Log_panel, Char c, false, false 782 + when Uchar.equal c (Uchar.of_char 'k') -> 783 + Some (Scroll_log (-1)) 784 + | Log_panel, Char c, false, false 785 + when Uchar.equal c (Uchar.of_char 'j') -> 786 + Some (Scroll_log 1) 787 + | Log_panel, Escape, false, false -> Some Focus_list 788 + | _ -> None); 789 + Sub.on_resize (fun ~width ~height -> Resize (width, height)); 790 + ] 791 + 792 + (* ========================================================================= *) 793 + (* App *) 794 + (* ========================================================================= *) 795 + 796 + let app store = { init = init store; update; view; subscriptions }
+38
term/sortal-mosaic/sortal_mosaic.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "Terminal UI for browsing Sortal contacts" 4 + description: """ 5 + A terminal-based contact browser combining Sortal (contact management) 6 + and Mosaic (TEA-based terminal UI framework). Features master-detail 7 + layout, search, and temporal data display.""" 8 + maintainer: ["Anil Madhavapeddy <anil@recoil.org>"] 9 + authors: ["Anil Madhavapeddy"] 10 + license: "ISC" 11 + depends: [ 12 + "dune" {>= "3.20"} 13 + "ocaml" {>= "5.1.0"} 14 + "eio" 15 + "eio_main" 16 + "sortal" 17 + "mosaic" 18 + "matrix" 19 + "toffee" 20 + "fmt" 21 + "logs" 22 + "odoc" {with-doc} 23 + ] 24 + build: [ 25 + ["dune" "subst"] {dev} 26 + [ 27 + "dune" 28 + "build" 29 + "-p" 30 + name 31 + "-j" 32 + jobs 33 + "@install" 34 + "@runtest" {with-test} 35 + "@doc" {with-doc} 36 + ] 37 + ] 38 + x-maintenance-intent: ["(latest)"]
-3
yaml/ocaml-yamle/.gitignore
··· 1 - _build 2 - *.cmi 3 - *.cmo
-43
yaml/ocaml-yamle/TODO.md
··· 1 - # Yamle Implementation Progress 2 - 3 - ## Phase 1: Foundation 4 - - [x] Project structure and dune files 5 - - [ ] Position module - location tracking 6 - - [ ] Span module - source ranges 7 - - [ ] Error module - exception with position info 8 - - [ ] Encoding module - UTF-8/16 detection 9 - 10 - ## Phase 2: Styles and Input 11 - - [ ] Scalar_style module 12 - - [ ] Layout_style module 13 - - [ ] Chomping module 14 - - [ ] Input module - character source abstraction 15 - 16 - ## Phase 3: Scanner (Lexer) 17 - - [ ] Token module - token types 18 - - [ ] Scanner module - tokenizer with lookahead 19 - 20 - ## Phase 4: Parser 21 - - [ ] Event module - parser events 22 - - [ ] Parser module - state machine 23 - 24 - ## Phase 5: Data Structures 25 - - [ ] Value module - JSON-compatible representation 26 - - [ ] Tag module - YAML tags 27 - - [ ] Scalar module - scalar with metadata 28 - - [ ] Sequence module - sequence with metadata 29 - - [ ] Mapping module - mapping with metadata 30 - - [ ] Yaml module - full YAML representation 31 - - [ ] Document module - document wrapper 32 - 33 - ## Phase 6: Loader and Emitter 34 - - [ ] Loader module - events to data structures 35 - - [ ] Emitter module - data structures to YAML string 36 - 37 - ## Phase 7: Top-Level API 38 - - [ ] Yamle module - main API 39 - - [ ] Stream submodule - streaming interface 40 - 41 - ## Phase 8: Testing 42 - - [ ] Unit tests for each module 43 - - [ ] Integration tests with YAML test suite
-64
yaml/ocaml-yamle/bin/dune
··· 1 - (executable 2 - (name yamlcat) 3 - (public_name yamlcat) 4 - (libraries yamle cmdliner)) 5 - 6 - (executable 7 - (name test_emit) 8 - (libraries yamle)) 9 - 10 - (executable 11 - (name test_remaining) 12 - (libraries yamle)) 13 - 14 - (executable 15 - (name test_scan) 16 - (libraries yamle)) 17 - 18 - (executable 19 - (name test_empty) 20 - (libraries yamle)) 21 - 22 - (executable 23 - (name test_specific) 24 - (libraries yamle)) 25 - 26 - (executable 27 - (name test_detailed) 28 - (libraries yamle test_suite_lib)) 29 - 30 - (executable 31 - (name test_empty_final) 32 - (libraries yamle)) 33 - 34 - (executable 35 - (name test_block_debug) 36 - (libraries yamle)) 37 - 38 - (executable 39 - (name test_failing) 40 - (libraries yamle)) 41 - 42 - (executable 43 - (name test_debug_cases) 44 - (libraries yamle)) 45 - 46 - (executable 47 - (name test_seq_comment) 48 - (libraries yamle)) 49 - 50 - (executable 51 - (name test_rzp5_exact) 52 - (libraries yamle)) 53 - 54 - (executable 55 - (name test_indent_comment) 56 - (libraries yamle)) 57 - 58 - (executable 59 - (name test_tokens_debug) 60 - (libraries yamle)) 61 - 62 - (executable 63 - (name test_error_detail) 64 - (libraries yamle))
-14
yaml/ocaml-yamle/bin/test_emit.ml
··· 1 - let () = 2 - let yaml = {| 3 - name: Alice 4 - age: 30 5 - hobbies: 6 - - reading 7 - - coding 8 - |} in 9 - let v = Yamle.of_string yaml in 10 - print_endline "=== Using to_string (YAML output) ==="; 11 - print_endline (Yamle.to_string v); 12 - print_endline ""; 13 - print_endline "=== Using pp (JSON-like) ==="; 14 - Format.printf "%a@." Yamle.pp v
-184
yaml/ocaml-yamle/bin/yamlcat.ml
··· 1 - (** yamlcat - parse and reprint YAML files *) 2 - 3 - open Cmdliner 4 - 5 - type output_format = Yaml | Json | Flow | Debug 6 - 7 - let rec json_to_string buf (v : Yamle.value) = 8 - match v with 9 - | `Null -> Buffer.add_string buf "null" 10 - | `Bool b -> Buffer.add_string buf (if b then "true" else "false") 11 - | `Float f -> 12 - if Float.is_integer f && Float.abs f < 1e15 then 13 - Buffer.add_string buf (Printf.sprintf "%.0f" f) 14 - else 15 - Buffer.add_string buf (Printf.sprintf "%g" f) 16 - | `String s -> Buffer.add_string buf (Printf.sprintf "%S" s) 17 - | `A items -> 18 - Buffer.add_char buf '['; 19 - List.iteri (fun i item -> 20 - if i > 0 then Buffer.add_string buf ", "; 21 - json_to_string buf item 22 - ) items; 23 - Buffer.add_char buf ']' 24 - | `O pairs -> 25 - Buffer.add_char buf '{'; 26 - List.iteri (fun i (k, v) -> 27 - if i > 0 then Buffer.add_string buf ", "; 28 - Buffer.add_string buf (Printf.sprintf "%S: " k); 29 - json_to_string buf v 30 - ) pairs; 31 - Buffer.add_char buf '}' 32 - 33 - let value_to_json v = 34 - let buf = Buffer.create 256 in 35 - json_to_string buf v; 36 - Buffer.contents buf 37 - 38 - let process_string ~format ~resolve_aliases ~max_nodes ~max_depth content = 39 - try 40 - (* Always parse as multi-document stream *) 41 - let documents = Yamle.documents_of_string content in 42 - 43 - match format with 44 - | Yaml -> 45 - (* Convert through Value to apply tag-based type coercion *) 46 - let first = ref true in 47 - List.iter (fun doc -> 48 - if not !first then print_string "---\n"; 49 - first := false; 50 - match Yamle.Document.root doc with 51 - | None -> print_endline "" 52 - | Some yaml -> 53 - let value = Yamle.to_json ~resolve_aliases ~max_nodes ~max_depth yaml in 54 - print_string (Yamle.to_string value) 55 - ) documents 56 - | Flow -> 57 - (* Convert through Value to apply tag-based type coercion *) 58 - let first = ref true in 59 - List.iter (fun doc -> 60 - if not !first then print_string "---\n"; 61 - first := false; 62 - match Yamle.Document.root doc with 63 - | None -> print_endline "" 64 - | Some yaml -> 65 - let value = Yamle.to_json ~resolve_aliases ~max_nodes ~max_depth yaml in 66 - print_string (Yamle.to_string ~layout_style:Yamle.Layout_style.Flow value) 67 - ) documents 68 - | Json -> 69 - let first = ref true in 70 - List.iter (fun doc -> 71 - match Yamle.Document.root doc with 72 - | None -> () 73 - | Some yaml -> 74 - if not !first then print_endline "---"; 75 - first := false; 76 - let value = Yamle.to_json ~resolve_aliases ~max_nodes ~max_depth yaml in 77 - print_endline (value_to_json value) 78 - ) documents 79 - | Debug -> 80 - List.iteri (fun i doc -> 81 - Format.printf "Document %d:@." (i + 1); 82 - Format.printf "%a@." Yamle.Document.pp doc 83 - ) documents 84 - with 85 - | Yamle.Yamle_error e -> 86 - Printf.eprintf "Error: %s\n" (Yamle.Error.to_string e); 87 - exit 1 88 - 89 - let process_file ~format ~resolve_aliases ~max_nodes ~max_depth filename = 90 - let content = 91 - if filename = "-" then 92 - In_channel.input_all In_channel.stdin 93 - else 94 - In_channel.with_open_text filename In_channel.input_all 95 - in 96 - process_string ~format ~resolve_aliases ~max_nodes ~max_depth content 97 - 98 - let run format _all resolve_aliases max_nodes max_depth files = 99 - let files = if files = [] then ["-"] else files in 100 - List.iter (process_file ~format ~resolve_aliases ~max_nodes ~max_depth) files; 101 - `Ok () 102 - 103 - (* Command-line arguments *) 104 - 105 - let format_arg = 106 - let doc = "Output format: yaml (default), json, flow, or debug." in 107 - let formats = [ 108 - ("yaml", Yaml); 109 - ("json", Json); 110 - ("flow", Flow); 111 - ("debug", Debug); 112 - ] in 113 - Arg.(value & opt (enum formats) Yaml & info ["format"; "f"] ~docv:"FORMAT" ~doc) 114 - 115 - let json_arg = 116 - let doc = "Output as JSON (shorthand for --format=json)." in 117 - Arg.(value & flag & info ["json"] ~doc) 118 - 119 - let flow_arg = 120 - let doc = "Output in flow style (shorthand for --format=flow)." in 121 - Arg.(value & flag & info ["flow"] ~doc) 122 - 123 - let debug_arg = 124 - let doc = "Output internal representation (shorthand for --format=debug)." in 125 - Arg.(value & flag & info ["debug"] ~doc) 126 - 127 - let all_arg = 128 - let doc = "Output all documents (for multi-document YAML)." in 129 - Arg.(value & flag & info ["all"; "a"] ~doc) 130 - 131 - let no_resolve_aliases_arg = 132 - let doc = "Don't resolve aliases (keep them as references)." in 133 - Arg.(value & flag & info ["no-resolve-aliases"] ~doc) 134 - 135 - let max_nodes_arg = 136 - let doc = "Maximum number of nodes during alias expansion (default: 10000000). \ 137 - Protection against billion laughs attack." in 138 - Arg.(value & opt int Yamle.default_max_alias_nodes & info ["max-nodes"] ~docv:"N" ~doc) 139 - 140 - let max_depth_arg = 141 - let doc = "Maximum alias nesting depth (default: 100). \ 142 - Protection against deeply nested alias chains." in 143 - Arg.(value & opt int Yamle.default_max_alias_depth & info ["max-depth"] ~docv:"N" ~doc) 144 - 145 - let files_arg = 146 - let doc = "YAML file(s) to process. Use '-' for stdin." in 147 - Arg.(value & pos_all file [] & info [] ~docv:"FILE" ~doc) 148 - 149 - let combined_format format json flow debug = 150 - if json then Json 151 - else if flow then Flow 152 - else if debug then Debug 153 - else format 154 - 155 - let term = 156 - let combine format json flow debug all no_resolve max_nodes max_depth files = 157 - let format = combined_format format json flow debug in 158 - let resolve_aliases = not no_resolve in 159 - run format all resolve_aliases max_nodes max_depth files 160 - in 161 - Term.(ret (const combine $ format_arg $ json_arg $ flow_arg $ debug_arg $ 162 - all_arg $ no_resolve_aliases_arg $ max_nodes_arg $ max_depth_arg $ files_arg)) 163 - 164 - let info = 165 - let doc = "Parse and reprint YAML files" in 166 - let man = [ 167 - `S Manpage.s_description; 168 - `P "$(tname) parses YAML files and reprints them in various formats. \ 169 - It can be used to validate YAML, convert between styles, or convert to JSON."; 170 - `S Manpage.s_examples; 171 - `P "Parse and reprint a YAML file:"; 172 - `Pre " $(tname) config.yaml"; 173 - `P "Convert YAML to JSON:"; 174 - `Pre " $(tname) --json config.yaml"; 175 - `P "Process multi-document YAML:"; 176 - `Pre " $(tname) --all multi.yaml"; 177 - `P "Limit alias expansion (protection against malicious YAML):"; 178 - `Pre " $(tname) --max-nodes 1000 --max-depth 10 untrusted.yaml"; 179 - `S Manpage.s_bugs; 180 - `P "Report bugs at https://github.com/avsm/ocaml-yaml/issues"; 181 - ] in 182 - Cmd.info "yamlcat" ~version:"0.1.0" ~doc ~man 183 - 184 - let () = exit (Cmd.eval (Cmd.v info term))
-19
yaml/ocaml-yamle/dune-project
··· 1 - (lang dune 3.0) 2 - (name yamle) 3 - (version 0.1.0) 4 - 5 - (generate_opam_files true) 6 - 7 - (source (github ocaml/yamle)) 8 - (license ISC) 9 - (authors "Yamle Authors") 10 - (maintainers "yamle@example.com") 11 - 12 - (package 13 - (name yamle) 14 - (synopsis "Pure OCaml YAML 1.2 parser and emitter") 15 - (description "A pure OCaml implementation of YAML 1.2 parsing and emission, with no C dependencies.") 16 - (depends 17 - (ocaml (>= 4.14.0)) 18 - (dune (>= 3.0)) 19 - (alcotest :with-test)))
-26
yaml/ocaml-yamle/lib/chomping.ml
··· 1 - (** Block scalar chomping indicators *) 2 - 3 - type t = 4 - | Strip (** Remove final line break and trailing empty lines *) 5 - | Clip (** Keep final line break, remove trailing empty lines (default) *) 6 - | Keep (** Keep final line break and trailing empty lines *) 7 - 8 - let to_string = function 9 - | Strip -> "strip" 10 - | Clip -> "clip" 11 - | Keep -> "keep" 12 - 13 - let pp fmt t = 14 - Format.pp_print_string fmt (to_string t) 15 - 16 - let of_char = function 17 - | '-' -> Some Strip 18 - | '+' -> Some Keep 19 - | _ -> None 20 - 21 - let to_char = function 22 - | Strip -> Some '-' 23 - | Clip -> None 24 - | Keep -> Some '+' 25 - 26 - let equal a b = a = b
-54
yaml/ocaml-yamle/lib/document.ml
··· 1 - (** YAML document with directives and content *) 2 - 3 - type t = { 4 - version : (int * int) option; 5 - tags : (string * string) list; 6 - root : Yaml.t option; 7 - implicit_start : bool; 8 - implicit_end : bool; 9 - } 10 - 11 - let make 12 - ?(version : (int * int) option) 13 - ?(tags : (string * string) list = []) 14 - ?(implicit_start = true) 15 - ?(implicit_end = true) 16 - root = 17 - { version; tags; root; implicit_start; implicit_end } 18 - 19 - let version t = t.version 20 - let tags t = t.tags 21 - let root t = t.root 22 - let implicit_start t = t.implicit_start 23 - let implicit_end t = t.implicit_end 24 - 25 - let with_version version t = { t with version = Some version } 26 - let with_tags tags t = { t with tags } 27 - let with_root root t = { t with root = Some root } 28 - 29 - let pp fmt t = 30 - Format.fprintf fmt "@[<v 2>document(@,"; 31 - (match t.version with 32 - | Some (maj, min) -> Format.fprintf fmt "version=%d.%d,@ " maj min 33 - | None -> ()); 34 - if t.tags <> [] then begin 35 - Format.fprintf fmt "tags=["; 36 - List.iteri (fun i (h, p) -> 37 - if i > 0 then Format.fprintf fmt ", "; 38 - Format.fprintf fmt "%s -> %s" h p 39 - ) t.tags; 40 - Format.fprintf fmt "],@ " 41 - end; 42 - Format.fprintf fmt "implicit_start=%b,@ " t.implicit_start; 43 - Format.fprintf fmt "implicit_end=%b,@ " t.implicit_end; 44 - (match t.root with 45 - | Some root -> Format.fprintf fmt "root=%a" Yaml.pp root 46 - | None -> Format.fprintf fmt "root=<empty>"); 47 - Format.fprintf fmt "@]@,)" 48 - 49 - let equal a b = 50 - Option.equal (fun (a1, a2) (b1, b2) -> a1 = b1 && a2 = b2) a.version b.version && 51 - List.equal (fun (h1, p1) (h2, p2) -> h1 = h2 && p1 = p2) a.tags b.tags && 52 - Option.equal Yaml.equal a.root b.root && 53 - a.implicit_start = b.implicit_start && 54 - a.implicit_end = b.implicit_end
-26
yaml/ocaml-yamle/lib/dune
··· 1 - (library 2 - (name yamle) 3 - (public_name yamle) 4 - (modules 5 - position 6 - span 7 - error 8 - encoding 9 - scalar_style 10 - layout_style 11 - chomping 12 - input 13 - token 14 - scanner 15 - event 16 - parser 17 - value 18 - tag 19 - scalar 20 - sequence 21 - mapping 22 - yaml 23 - document 24 - loader 25 - emitter 26 - yamle))
-764
yaml/ocaml-yamle/lib/emitter.ml
··· 1 - (** Emitter - converts YAML data structures to string output *) 2 - 3 - type config = { 4 - encoding : Encoding.t; 5 - scalar_style : Scalar_style.t; 6 - layout_style : Layout_style.t; 7 - indent : int; 8 - width : int; 9 - canonical : bool; 10 - } 11 - 12 - let default_config = { 13 - encoding = Encoding.Utf8; 14 - scalar_style = Scalar_style.Any; 15 - layout_style = Layout_style.Any; 16 - indent = 2; 17 - width = 80; 18 - canonical = false; 19 - } 20 - 21 - type state = 22 - | Initial 23 - | Stream_started 24 - | Document_started 25 - | In_block_sequence of int (* indent level *) 26 - | In_block_mapping_key of int 27 - | In_block_mapping_value of int 28 - | In_block_mapping_first_key of int (* first key after "- ", no indent needed *) 29 - | In_flow_sequence 30 - | In_flow_mapping_key 31 - | In_flow_mapping_value 32 - | Document_ended 33 - | Stream_ended 34 - 35 - type t = { 36 - config : config; 37 - buffer : Buffer.t; 38 - mutable state : state; 39 - mutable states : state list; 40 - mutable indent : int; 41 - mutable flow_level : int; 42 - mutable need_separator : bool; 43 - } 44 - 45 - let create ?(config = default_config) () = { 46 - config; 47 - buffer = Buffer.create 1024; 48 - state = Initial; 49 - states = []; 50 - indent = 0; 51 - flow_level = 0; 52 - need_separator = false; 53 - } 54 - 55 - let contents t = Buffer.contents t.buffer 56 - 57 - let reset t = 58 - Buffer.clear t.buffer; 59 - t.state <- Initial; 60 - t.states <- []; 61 - t.indent <- 0; 62 - t.flow_level <- 0; 63 - t.need_separator <- false 64 - 65 - (** Output helpers *) 66 - 67 - let write t s = Buffer.add_string t.buffer s 68 - let write_char t c = Buffer.add_char t.buffer c 69 - 70 - let write_indent t = 71 - for _ = 1 to t.indent do 72 - write_char t ' ' 73 - done 74 - 75 - let write_newline t = 76 - write_char t '\n' 77 - 78 - let push_state t s = 79 - t.states <- t.state :: t.states; 80 - t.state <- s 81 - 82 - let pop_state t = 83 - match t.states with 84 - | s :: rest -> 85 - t.state <- s; 86 - t.states <- rest 87 - | [] -> 88 - t.state <- Stream_ended 89 - 90 - (** Check if string needs quoting *) 91 - let needs_quoting s = 92 - if String.length s = 0 then true 93 - else 94 - let first = s.[0] in 95 - (* Check first character *) 96 - if first = '-' || first = '?' || first = ':' || first = ',' || 97 - first = '[' || first = ']' || first = '{' || first = '}' || 98 - first = '#' || first = '&' || first = '*' || first = '!' || 99 - first = '|' || first = '>' || first = '\'' || first = '"' || 100 - first = '%' || first = '@' || first = '`' || first = ' ' then 101 - true 102 - else 103 - (* Check for special values *) 104 - let lower = String.lowercase_ascii s in 105 - if lower = "null" || lower = "true" || lower = "false" || 106 - lower = "yes" || lower = "no" || lower = "on" || lower = "off" || 107 - lower = "~" || lower = ".inf" || lower = "-.inf" || lower = ".nan" then 108 - true 109 - else 110 - (* Check for characters that need quoting *) 111 - try 112 - String.iter (fun c -> 113 - if c = ':' || c = '#' || c = '\n' || c = '\r' then 114 - raise Exit 115 - ) s; 116 - (* Check if it looks like a number *) 117 - (try ignore (Float.of_string s); true with _ -> false) 118 - with Exit -> true 119 - 120 - (** Check if string contains characters requiring double quotes *) 121 - let needs_double_quotes s = 122 - try 123 - String.iter (fun c -> 124 - if c = '\n' || c = '\r' || c = '\t' || c = '\\' || 125 - c < ' ' || c = '"' then 126 - raise Exit 127 - ) s; 128 - false 129 - with Exit -> true 130 - 131 - (** Write scalar with appropriate quoting *) 132 - let write_scalar t ?(style = Scalar_style.Any) value = 133 - let effective_style = 134 - if style = Scalar_style.Any then 135 - if needs_double_quotes value then Scalar_style.Double_quoted 136 - else if needs_quoting value then Scalar_style.Single_quoted 137 - else Scalar_style.Plain 138 - else style 139 - in 140 - match effective_style with 141 - | Scalar_style.Plain | Scalar_style.Any -> 142 - write t value 143 - 144 - | Scalar_style.Single_quoted -> 145 - write_char t '\''; 146 - String.iter (fun c -> 147 - if c = '\'' then write t "''" 148 - else write_char t c 149 - ) value; 150 - write_char t '\'' 151 - 152 - | Scalar_style.Double_quoted -> 153 - write_char t '"'; 154 - String.iter (fun c -> 155 - match c with 156 - | '"' -> write t "\\\"" 157 - | '\\' -> write t "\\\\" 158 - | '\n' -> write t "\\n" 159 - | '\r' -> write t "\\r" 160 - | '\t' -> write t "\\t" 161 - | c when c < ' ' -> write t (Printf.sprintf "\\x%02x" (Char.code c)) 162 - | c -> write_char t c 163 - ) value; 164 - write_char t '"' 165 - 166 - | Scalar_style.Literal -> 167 - write t "|"; 168 - write_newline t; 169 - let lines = String.split_on_char '\n' value in 170 - List.iter (fun line -> 171 - write_indent t; 172 - write t line; 173 - write_newline t 174 - ) lines 175 - 176 - | Scalar_style.Folded -> 177 - write t ">"; 178 - write_newline t; 179 - let lines = String.split_on_char '\n' value in 180 - List.iter (fun line -> 181 - write_indent t; 182 - write t line; 183 - write_newline t 184 - ) lines 185 - 186 - (** Write anchor if present *) 187 - let write_anchor t anchor = 188 - match anchor with 189 - | Some name -> 190 - write_char t '&'; 191 - write t name; 192 - write_char t ' ' 193 - | None -> () 194 - 195 - (** Write tag if present and not implicit *) 196 - let write_tag t ~implicit tag = 197 - if not implicit then 198 - match tag with 199 - | Some tag_str -> 200 - write_char t '!'; 201 - write t tag_str; 202 - write_char t ' ' 203 - | None -> () 204 - 205 - (** Emit events *) 206 - 207 - let emit t (ev : Event.t) = 208 - match ev with 209 - | Event.Stream_start _ -> 210 - t.state <- Stream_started 211 - 212 - | Event.Stream_end -> 213 - t.state <- Stream_ended 214 - 215 - | Event.Document_start { version; implicit } -> 216 - if not implicit then begin 217 - (match version with 218 - | Some (maj, min) -> 219 - write t (Printf.sprintf "%%YAML %d.%d\n" maj min) 220 - | None -> ()); 221 - write t "---"; 222 - write_newline t 223 - end; 224 - t.state <- Document_started 225 - 226 - | Event.Document_end { implicit } -> 227 - if not implicit then begin 228 - write t "..."; 229 - write_newline t 230 - end; 231 - t.state <- Document_ended 232 - 233 - | Event.Alias { anchor } -> 234 - if t.flow_level > 0 then begin 235 - if t.need_separator then write t ", "; 236 - t.need_separator <- true; 237 - write_char t '*'; 238 - write t anchor 239 - end else begin 240 - (match t.state with 241 - | In_block_sequence _ -> 242 - write_indent t; 243 - write t "- *"; 244 - write t anchor; 245 - write_newline t 246 - | In_block_mapping_key _ -> 247 - write_indent t; 248 - write_char t '*'; 249 - write t anchor; 250 - write t ": "; 251 - t.state <- In_block_mapping_value t.indent 252 - | In_block_mapping_value indent -> 253 - write_char t '*'; 254 - write t anchor; 255 - write_newline t; 256 - t.state <- In_block_mapping_key indent 257 - | _ -> 258 - write_char t '*'; 259 - write t anchor; 260 - write_newline t) 261 - end 262 - 263 - | Event.Scalar { anchor; tag; value; plain_implicit; style; _ } -> 264 - if t.flow_level > 0 then begin 265 - (match t.state with 266 - | In_flow_mapping_key -> 267 - if t.need_separator then write t ", "; 268 - write_anchor t anchor; 269 - write_tag t ~implicit:plain_implicit tag; 270 - write_scalar t ~style value; 271 - write t ": "; 272 - t.need_separator <- false; 273 - t.state <- In_flow_mapping_value 274 - | In_flow_mapping_value -> 275 - write_anchor t anchor; 276 - write_tag t ~implicit:plain_implicit tag; 277 - write_scalar t ~style value; 278 - t.need_separator <- true; 279 - t.state <- In_flow_mapping_key 280 - | _ -> 281 - if t.need_separator then write t ", "; 282 - t.need_separator <- true; 283 - write_anchor t anchor; 284 - write_tag t ~implicit:plain_implicit tag; 285 - write_scalar t ~style value) 286 - end else begin 287 - match t.state with 288 - | In_block_sequence _ -> 289 - write_indent t; 290 - write t "- "; 291 - write_anchor t anchor; 292 - write_tag t ~implicit:plain_implicit tag; 293 - write_scalar t ~style value; 294 - write_newline t 295 - | In_block_mapping_key indent -> 296 - write_indent t; 297 - write_anchor t anchor; 298 - write_tag t ~implicit:plain_implicit tag; 299 - write_scalar t ~style value; 300 - write_char t ':'; 301 - t.state <- In_block_mapping_value indent 302 - | In_block_mapping_first_key indent -> 303 - (* First key after "- ", no indent needed *) 304 - write_anchor t anchor; 305 - write_tag t ~implicit:plain_implicit tag; 306 - write_scalar t ~style value; 307 - write_char t ':'; 308 - t.state <- In_block_mapping_value indent 309 - | In_block_mapping_value indent -> 310 - write_char t ' '; 311 - write_anchor t anchor; 312 - write_tag t ~implicit:plain_implicit tag; 313 - write_scalar t ~style value; 314 - write_newline t; 315 - t.state <- In_block_mapping_key indent 316 - | _ -> 317 - write_anchor t anchor; 318 - write_tag t ~implicit:plain_implicit tag; 319 - write_scalar t ~style value; 320 - write_newline t 321 - end 322 - 323 - | Event.Sequence_start { anchor; tag; implicit; style } -> 324 - let use_flow = style = Layout_style.Flow || t.flow_level > 0 in 325 - if t.flow_level > 0 then begin 326 - (match t.state with 327 - | In_flow_mapping_key -> 328 - if t.need_separator then write t ", "; 329 - write_anchor t anchor; 330 - write_tag t ~implicit tag; 331 - write_char t '['; 332 - t.flow_level <- t.flow_level + 1; 333 - t.need_separator <- false; 334 - push_state t In_flow_mapping_value; (* After ] we'll be in value position but sequence handles it *) 335 - t.state <- In_flow_sequence 336 - | In_flow_mapping_value -> 337 - write_anchor t anchor; 338 - write_tag t ~implicit tag; 339 - write_char t '['; 340 - t.flow_level <- t.flow_level + 1; 341 - t.need_separator <- false; 342 - push_state t In_flow_mapping_key; 343 - t.state <- In_flow_sequence 344 - | _ -> 345 - if t.need_separator then write t ", "; 346 - write_anchor t anchor; 347 - write_tag t ~implicit tag; 348 - write_char t '['; 349 - t.flow_level <- t.flow_level + 1; 350 - t.need_separator <- false; 351 - push_state t In_flow_sequence) 352 - end else begin 353 - match t.state with 354 - | In_block_sequence _ -> 355 - write_indent t; 356 - write t "- "; 357 - write_anchor t anchor; 358 - write_tag t ~implicit tag; 359 - if use_flow then begin 360 - write_char t '['; 361 - t.flow_level <- t.flow_level + 1; 362 - t.need_separator <- false; 363 - push_state t In_flow_sequence 364 - end else begin 365 - write_newline t; 366 - push_state t (In_block_sequence t.indent); 367 - t.indent <- t.indent + t.config.indent 368 - end 369 - | In_block_mapping_key indent -> 370 - write_indent t; 371 - write_anchor t anchor; 372 - write_tag t ~implicit tag; 373 - write t ":"; 374 - write_newline t; 375 - push_state t (In_block_mapping_key indent); 376 - t.indent <- t.indent + t.config.indent; 377 - t.state <- In_block_sequence t.indent 378 - | In_block_mapping_first_key indent -> 379 - (* First key after "- " with sequence value - no indent *) 380 - write_anchor t anchor; 381 - write_tag t ~implicit tag; 382 - write t ":"; 383 - write_newline t; 384 - push_state t (In_block_mapping_key indent); 385 - t.indent <- t.indent + t.config.indent; 386 - t.state <- In_block_sequence t.indent 387 - | In_block_mapping_value indent -> 388 - write_anchor t anchor; 389 - write_tag t ~implicit tag; 390 - if use_flow then begin 391 - write_char t ' '; 392 - write_char t '['; 393 - t.flow_level <- t.flow_level + 1; 394 - t.need_separator <- false; 395 - (* Save key state to return to after flow sequence *) 396 - t.state <- In_block_mapping_key indent; 397 - push_state t In_flow_sequence 398 - end else begin 399 - write_newline t; 400 - (* Save key state to return to after nested sequence *) 401 - t.state <- In_block_mapping_key indent; 402 - push_state t (In_block_sequence (t.indent + t.config.indent)); 403 - t.indent <- t.indent + t.config.indent 404 - end 405 - | _ -> 406 - write_anchor t anchor; 407 - write_tag t ~implicit tag; 408 - if use_flow then begin 409 - write_char t '['; 410 - t.flow_level <- t.flow_level + 1; 411 - t.need_separator <- false; 412 - push_state t In_flow_sequence 413 - end else begin 414 - push_state t (In_block_sequence t.indent); 415 - t.state <- In_block_sequence t.indent 416 - end 417 - end 418 - 419 - | Event.Sequence_end -> 420 - if t.flow_level > 0 then begin 421 - write_char t ']'; 422 - t.flow_level <- t.flow_level - 1; 423 - t.need_separator <- true; 424 - pop_state t; 425 - (* Write newline if returning to block context *) 426 - (match t.state with 427 - | In_block_mapping_key _ | In_block_sequence _ -> write_newline t 428 - | _ -> ()) 429 - end else begin 430 - t.indent <- t.indent - t.config.indent; 431 - pop_state t 432 - end 433 - 434 - | Event.Mapping_start { anchor; tag; implicit; style } -> 435 - let use_flow = style = Layout_style.Flow || t.flow_level > 0 in 436 - if t.flow_level > 0 then begin 437 - (match t.state with 438 - | In_flow_mapping_key -> 439 - if t.need_separator then write t ", "; 440 - write_anchor t anchor; 441 - write_tag t ~implicit tag; 442 - write_char t '{'; 443 - t.flow_level <- t.flow_level + 1; 444 - t.need_separator <- false; 445 - push_state t In_flow_mapping_value; 446 - t.state <- In_flow_mapping_key 447 - | In_flow_mapping_value -> 448 - write_anchor t anchor; 449 - write_tag t ~implicit tag; 450 - write_char t '{'; 451 - t.flow_level <- t.flow_level + 1; 452 - t.need_separator <- false; 453 - push_state t In_flow_mapping_key; 454 - t.state <- In_flow_mapping_key 455 - | _ -> 456 - if t.need_separator then write t ", "; 457 - write_anchor t anchor; 458 - write_tag t ~implicit tag; 459 - write_char t '{'; 460 - t.flow_level <- t.flow_level + 1; 461 - t.need_separator <- false; 462 - push_state t In_flow_mapping_key) 463 - end else begin 464 - match t.state with 465 - | In_block_sequence _ -> 466 - write_indent t; 467 - write t "- "; 468 - write_anchor t anchor; 469 - write_tag t ~implicit tag; 470 - if use_flow then begin 471 - write_char t '{'; 472 - t.flow_level <- t.flow_level + 1; 473 - t.need_separator <- false; 474 - push_state t In_flow_mapping_key 475 - end else begin 476 - (* Don't write newline - first key goes on same line as "- " *) 477 - push_state t (In_block_sequence t.indent); 478 - t.indent <- t.indent + t.config.indent; 479 - t.state <- In_block_mapping_first_key t.indent 480 - end 481 - | In_block_mapping_key indent -> 482 - write_indent t; 483 - write_anchor t anchor; 484 - write_tag t ~implicit tag; 485 - write t ":"; 486 - write_newline t; 487 - push_state t (In_block_mapping_key indent); 488 - t.indent <- t.indent + t.config.indent; 489 - t.state <- In_block_mapping_key t.indent 490 - | In_block_mapping_first_key indent -> 491 - (* First key after "- " with mapping value - no indent *) 492 - write_anchor t anchor; 493 - write_tag t ~implicit tag; 494 - write t ":"; 495 - write_newline t; 496 - push_state t (In_block_mapping_key indent); 497 - t.indent <- t.indent + t.config.indent; 498 - t.state <- In_block_mapping_key t.indent 499 - | In_block_mapping_value indent -> 500 - write_anchor t anchor; 501 - write_tag t ~implicit tag; 502 - if use_flow then begin 503 - write_char t ' '; 504 - write_char t '{'; 505 - t.flow_level <- t.flow_level + 1; 506 - t.need_separator <- false; 507 - (* Save key state to return to after flow mapping *) 508 - t.state <- In_block_mapping_key indent; 509 - push_state t In_flow_mapping_key 510 - end else begin 511 - write_newline t; 512 - (* Save key state to return to after nested mapping *) 513 - t.state <- In_block_mapping_key indent; 514 - push_state t (In_block_mapping_key (t.indent + t.config.indent)); 515 - t.indent <- t.indent + t.config.indent 516 - end 517 - | _ -> 518 - write_anchor t anchor; 519 - write_tag t ~implicit tag; 520 - if use_flow then begin 521 - write_char t '{'; 522 - t.flow_level <- t.flow_level + 1; 523 - t.need_separator <- false; 524 - push_state t In_flow_mapping_key 525 - end else begin 526 - push_state t (In_block_mapping_key t.indent); 527 - t.state <- In_block_mapping_key t.indent 528 - end 529 - end 530 - 531 - | Event.Mapping_end -> 532 - if t.flow_level > 0 then begin 533 - write_char t '}'; 534 - t.flow_level <- t.flow_level - 1; 535 - t.need_separator <- true; 536 - pop_state t; 537 - (* Write newline if returning to block context *) 538 - (match t.state with 539 - | In_block_mapping_key _ | In_block_sequence _ -> write_newline t 540 - | _ -> ()) 541 - end else begin 542 - t.indent <- t.indent - t.config.indent; 543 - pop_state t 544 - end 545 - 546 - (** High-level emission *) 547 - 548 - let rec emit_yaml_node t (yaml : Yaml.t) = 549 - match yaml with 550 - | `Scalar s -> 551 - emit t (Event.Scalar { 552 - anchor = Scalar.anchor s; 553 - tag = Scalar.tag s; 554 - value = Scalar.value s; 555 - plain_implicit = Scalar.plain_implicit s; 556 - quoted_implicit = Scalar.quoted_implicit s; 557 - style = Scalar.style s; 558 - }) 559 - 560 - | `Alias name -> 561 - emit t (Event.Alias { anchor = name }) 562 - 563 - | `A seq -> 564 - let members = Sequence.members seq in 565 - let style = 566 - (* Force flow style for empty sequences *) 567 - if members = [] then Layout_style.Flow 568 - else Sequence.style seq 569 - in 570 - emit t (Event.Sequence_start { 571 - anchor = Sequence.anchor seq; 572 - tag = Sequence.tag seq; 573 - implicit = Sequence.implicit seq; 574 - style; 575 - }); 576 - List.iter (emit_yaml_node t) members; 577 - emit t Event.Sequence_end 578 - 579 - | `O map -> 580 - let members = Mapping.members map in 581 - let style = 582 - (* Force flow style for empty mappings *) 583 - if members = [] then Layout_style.Flow 584 - else Mapping.style map 585 - in 586 - emit t (Event.Mapping_start { 587 - anchor = Mapping.anchor map; 588 - tag = Mapping.tag map; 589 - implicit = Mapping.implicit map; 590 - style; 591 - }); 592 - List.iter (fun (k, v) -> 593 - emit_yaml_node t k; 594 - emit_yaml_node t v 595 - ) members; 596 - emit t Event.Mapping_end 597 - 598 - let emit_yaml t yaml = 599 - emit t (Event.Stream_start { encoding = t.config.encoding }); 600 - emit t (Event.Document_start { version = None; implicit = true }); 601 - emit_yaml_node t yaml; 602 - emit t (Event.Document_end { implicit = true }); 603 - emit t Event.Stream_end 604 - 605 - let rec emit_value_node t (value : Value.t) = 606 - match value with 607 - | `Null -> 608 - emit t (Event.Scalar { 609 - anchor = None; tag = None; 610 - value = "null"; 611 - plain_implicit = true; quoted_implicit = false; 612 - style = Scalar_style.Plain; 613 - }) 614 - 615 - | `Bool b -> 616 - emit t (Event.Scalar { 617 - anchor = None; tag = None; 618 - value = if b then "true" else "false"; 619 - plain_implicit = true; quoted_implicit = false; 620 - style = Scalar_style.Plain; 621 - }) 622 - 623 - | `Float f -> 624 - let value = 625 - match Float.classify_float f with 626 - | FP_nan -> ".nan" 627 - | FP_infinite -> if f > 0.0 then ".inf" else "-.inf" 628 - | _ -> 629 - if Float.is_integer f && Float.abs f < 1e15 then 630 - Printf.sprintf "%.0f" f 631 - else 632 - Printf.sprintf "%g" f 633 - in 634 - emit t (Event.Scalar { 635 - anchor = None; tag = None; 636 - value; 637 - plain_implicit = true; quoted_implicit = false; 638 - style = Scalar_style.Plain; 639 - }) 640 - 641 - | `String s -> 642 - let style = 643 - if needs_double_quotes s then Scalar_style.Double_quoted 644 - else if needs_quoting s then Scalar_style.Single_quoted 645 - else Scalar_style.Plain 646 - in 647 - emit t (Event.Scalar { 648 - anchor = None; tag = None; 649 - value = s; 650 - plain_implicit = style = Scalar_style.Plain; 651 - quoted_implicit = style <> Scalar_style.Plain; 652 - style; 653 - }) 654 - 655 - | `A items -> 656 - let style = 657 - (* Force flow style for empty sequences *) 658 - if items = [] then Layout_style.Flow 659 - else if t.config.layout_style = Layout_style.Flow then Layout_style.Flow 660 - else Layout_style.Block 661 - in 662 - emit t (Event.Sequence_start { 663 - anchor = None; tag = None; 664 - implicit = true; 665 - style; 666 - }); 667 - List.iter (emit_value_node t) items; 668 - emit t Event.Sequence_end 669 - 670 - | `O pairs -> 671 - let style = 672 - (* Force flow style for empty mappings *) 673 - if pairs = [] then Layout_style.Flow 674 - else if t.config.layout_style = Layout_style.Flow then Layout_style.Flow 675 - else Layout_style.Block 676 - in 677 - emit t (Event.Mapping_start { 678 - anchor = None; tag = None; 679 - implicit = true; 680 - style; 681 - }); 682 - List.iter (fun (k, v) -> 683 - emit t (Event.Scalar { 684 - anchor = None; tag = None; 685 - value = k; 686 - plain_implicit = not (needs_quoting k); 687 - quoted_implicit = needs_quoting k; 688 - style = if needs_quoting k then Scalar_style.Double_quoted else Scalar_style.Plain; 689 - }); 690 - emit_value_node t v 691 - ) pairs; 692 - emit t Event.Mapping_end 693 - 694 - let emit_value t value = 695 - emit t (Event.Stream_start { encoding = t.config.encoding }); 696 - emit t (Event.Document_start { version = None; implicit = true }); 697 - emit_value_node t value; 698 - emit t (Event.Document_end { implicit = true }); 699 - emit t Event.Stream_end 700 - 701 - (** Strip anchors from a YAML tree *) 702 - let rec strip_anchors (yaml : Yaml.t) : Yaml.t = 703 - match yaml with 704 - | `Scalar s -> 705 - if Scalar.anchor s = None then yaml 706 - else 707 - `Scalar (Scalar.make 708 - ?tag:(Scalar.tag s) 709 - ~plain_implicit:(Scalar.plain_implicit s) 710 - ~quoted_implicit:(Scalar.quoted_implicit s) 711 - ~style:(Scalar.style s) 712 - (Scalar.value s)) 713 - | `Alias _ -> yaml 714 - | `A seq -> 715 - `A (Sequence.make 716 - ?tag:(Sequence.tag seq) 717 - ~implicit:(Sequence.implicit seq) 718 - ~style:(Sequence.style seq) 719 - (List.map strip_anchors (Sequence.members seq))) 720 - | `O map -> 721 - `O (Mapping.make 722 - ?tag:(Mapping.tag map) 723 - ~implicit:(Mapping.implicit map) 724 - ~style:(Mapping.style map) 725 - (List.map (fun (k, v) -> (strip_anchors k, strip_anchors v)) (Mapping.members map))) 726 - 727 - let emit_document ?(resolve_aliases = true) t doc = 728 - emit t (Event.Document_start { 729 - version = Document.version doc; 730 - implicit = Document.implicit_start doc; 731 - }); 732 - (match Document.root doc with 733 - | Some yaml -> 734 - let yaml = if resolve_aliases then 735 - yaml |> Yaml.resolve_aliases |> strip_anchors 736 - else yaml in 737 - emit_yaml_node t yaml 738 - | None -> 739 - emit t (Event.Scalar { 740 - anchor = None; tag = None; 741 - value = ""; 742 - plain_implicit = true; quoted_implicit = false; 743 - style = Scalar_style.Plain; 744 - })); 745 - emit t (Event.Document_end { implicit = Document.implicit_end doc }) 746 - 747 - (** Convenience functions *) 748 - 749 - let value_to_string ?(config = default_config) value = 750 - let t = create ~config () in 751 - emit_value t value; 752 - contents t 753 - 754 - let yaml_to_string ?(config = default_config) yaml = 755 - let t = create ~config () in 756 - emit_yaml t yaml; 757 - contents t 758 - 759 - let documents_to_string ?(config = default_config) ?(resolve_aliases = true) documents = 760 - let t = create ~config () in 761 - emit t (Event.Stream_start { encoding = config.encoding }); 762 - List.iter (emit_document ~resolve_aliases t) documents; 763 - emit t Event.Stream_end; 764 - contents t
-54
yaml/ocaml-yamle/lib/encoding.ml
··· 1 - (** Character encoding detection and handling *) 2 - 3 - type t = 4 - | Utf8 5 - | Utf16be 6 - | Utf16le 7 - | Utf32be 8 - | Utf32le 9 - 10 - let to_string = function 11 - | Utf8 -> "UTF-8" 12 - | Utf16be -> "UTF-16BE" 13 - | Utf16le -> "UTF-16LE" 14 - | Utf32be -> "UTF-32BE" 15 - | Utf32le -> "UTF-32LE" 16 - 17 - let pp fmt t = 18 - Format.pp_print_string fmt (to_string t) 19 - 20 - (** Detect encoding from BOM or first bytes. 21 - Returns (encoding, bom_length) *) 22 - let detect s = 23 - let len = String.length s in 24 - if len = 0 then (Utf8, 0) 25 - else 26 - let b0 = Char.code s.[0] in 27 - let b1 = if len > 1 then Char.code s.[1] else 0 in 28 - let b2 = if len > 2 then Char.code s.[2] else 0 in 29 - let b3 = if len > 3 then Char.code s.[3] else 0 in 30 - (* Check for BOM first *) 31 - if b0 = 0xEF && b1 = 0xBB && b2 = 0xBF then 32 - (Utf8, 3) 33 - else if b0 = 0xFE && b1 = 0xFF then 34 - (Utf16be, 2) 35 - else if b0 = 0xFF && b1 = 0xFE then 36 - if b2 = 0x00 && b3 = 0x00 then 37 - (Utf32le, 4) 38 - else 39 - (Utf16le, 2) 40 - else if b0 = 0x00 && b1 = 0x00 && b2 = 0xFE && b3 = 0xFF then 41 - (Utf32be, 4) 42 - (* No BOM - detect from content pattern *) 43 - else if b0 = 0x00 && b1 = 0x00 && b2 = 0x00 && b3 <> 0x00 then 44 - (Utf32be, 0) 45 - else if b0 <> 0x00 && b1 = 0x00 && b2 = 0x00 && b3 = 0x00 then 46 - (Utf32le, 0) 47 - else if b0 = 0x00 && b1 <> 0x00 then 48 - (Utf16be, 0) 49 - else if b0 <> 0x00 && b1 = 0x00 then 50 - (Utf16le, 0) 51 - else 52 - (Utf8, 0) 53 - 54 - let equal a b = a = b
-196
yaml/ocaml-yamle/lib/error.ml
··· 1 - (** Error handling with position information *) 2 - 3 - (** Error classification *) 4 - type kind = 5 - (* Scanner errors *) 6 - | Unexpected_character of char 7 - | Unexpected_eof 8 - | Invalid_escape_sequence of string 9 - | Invalid_unicode_escape of string 10 - | Invalid_hex_escape of string 11 - | Invalid_tag of string 12 - | Invalid_anchor of string 13 - | Invalid_alias of string 14 - | Invalid_comment 15 - | Unclosed_single_quote 16 - | Unclosed_double_quote 17 - | Unclosed_flow_sequence 18 - | Unclosed_flow_mapping 19 - | Invalid_indentation of int * int (** expected, got *) 20 - | Invalid_flow_indentation (** Content in flow collection must be indented *) 21 - | Tab_in_indentation 22 - | Invalid_block_scalar_header of string 23 - | Invalid_quoted_scalar_indentation of string 24 - | Invalid_directive of string 25 - | Invalid_yaml_version of string 26 - | Invalid_tag_directive of string 27 - | Reserved_directive of string 28 - | Illegal_flow_key_line (** Key and : must be on same line in flow context *) 29 - | Block_sequence_disallowed (** Block sequence entries not allowed in this context *) 30 - 31 - (* Parser errors *) 32 - | Unexpected_token of string 33 - | Expected_document_start 34 - | Expected_document_end 35 - | Expected_block_entry 36 - | Expected_key 37 - | Expected_value 38 - | Expected_node 39 - | Expected_scalar 40 - | Expected_sequence_end 41 - | Expected_mapping_end 42 - | Duplicate_anchor of string 43 - | Undefined_alias of string 44 - | Alias_cycle of string 45 - | Multiple_documents 46 - | Mapping_key_too_long 47 - 48 - (* Loader errors *) 49 - | Invalid_scalar_conversion of string * string (** value, target type *) 50 - | Type_mismatch of string * string (** expected, got *) 51 - | Unresolved_alias of string 52 - | Key_not_found of string 53 - | Alias_expansion_node_limit of int (** max nodes exceeded *) 54 - | Alias_expansion_depth_limit of int (** max depth exceeded *) 55 - 56 - (* Emitter errors *) 57 - | Invalid_encoding of string 58 - | Scalar_contains_invalid_chars of string 59 - | Anchor_not_set 60 - | Invalid_state of string 61 - 62 - (* Generic *) 63 - | Custom of string 64 - 65 - (** Full error with location *) 66 - type t = { 67 - kind : kind; 68 - span : Span.t option; 69 - context : string list; 70 - source : string option; 71 - } 72 - 73 - (** The exception raised by yamle *) 74 - exception Yamle_error of t 75 - 76 - let () = 77 - Printexc.register_printer (function 78 - | Yamle_error e -> 79 - let loc = match e.span with 80 - | None -> "" 81 - | Some span -> " at " ^ Span.to_string span 82 - in 83 - Some (Printf.sprintf "Yamle_error: %s%s" 84 - (match e.kind with Custom s -> s | _ -> "error") loc) 85 - | _ -> None) 86 - 87 - let make ?span ?(context=[]) ?source kind = 88 - { kind; span; context; source } 89 - 90 - let raise ?span ?context ?source kind = 91 - Stdlib.raise (Yamle_error (make ?span ?context ?source kind)) 92 - 93 - let raise_at pos kind = 94 - let span = Span.point pos in 95 - raise ~span kind 96 - 97 - let raise_span span kind = 98 - raise ~span kind 99 - 100 - let with_context ctx f = 101 - try f () with 102 - | Yamle_error e -> 103 - Stdlib.raise (Yamle_error { e with context = ctx :: e.context }) 104 - 105 - let kind_to_string = function 106 - | Unexpected_character c -> Printf.sprintf "unexpected character %C" c 107 - | Unexpected_eof -> "unexpected end of input" 108 - | Invalid_escape_sequence s -> Printf.sprintf "invalid escape sequence: %s" s 109 - | Invalid_unicode_escape s -> Printf.sprintf "invalid unicode escape: %s" s 110 - | Invalid_hex_escape s -> Printf.sprintf "invalid hex escape: %s" s 111 - | Invalid_tag s -> Printf.sprintf "invalid tag: %s" s 112 - | Invalid_anchor s -> Printf.sprintf "invalid anchor: %s" s 113 - | Invalid_alias s -> Printf.sprintf "invalid alias: %s" s 114 - | Invalid_comment -> "comments must be separated from other tokens by whitespace" 115 - | Unclosed_single_quote -> "unclosed single quote" 116 - | Unclosed_double_quote -> "unclosed double quote" 117 - | Unclosed_flow_sequence -> "unclosed flow sequence '['" 118 - | Unclosed_flow_mapping -> "unclosed flow mapping '{'" 119 - | Invalid_indentation (expected, got) -> 120 - Printf.sprintf "invalid indentation: expected %d, got %d" expected got 121 - | Invalid_flow_indentation -> "invalid indentation in flow construct" 122 - | Tab_in_indentation -> "tab character in indentation" 123 - | Invalid_block_scalar_header s -> 124 - Printf.sprintf "invalid block scalar header: %s" s 125 - | Invalid_quoted_scalar_indentation s -> 126 - Printf.sprintf "%s" s 127 - | Invalid_directive s -> Printf.sprintf "invalid directive: %s" s 128 - | Invalid_yaml_version s -> Printf.sprintf "invalid YAML version: %s" s 129 - | Invalid_tag_directive s -> Printf.sprintf "invalid TAG directive: %s" s 130 - | Reserved_directive s -> Printf.sprintf "reserved directive: %s" s 131 - | Illegal_flow_key_line -> "key and ':' must be on the same line in flow context" 132 - | Block_sequence_disallowed -> "block sequence entries are not allowed in this context" 133 - | Unexpected_token s -> Printf.sprintf "unexpected token: %s" s 134 - | Expected_document_start -> "expected document start '---'" 135 - | Expected_document_end -> "expected document end '...'" 136 - | Expected_block_entry -> "expected block entry '-'" 137 - | Expected_key -> "expected mapping key" 138 - | Expected_value -> "expected mapping value" 139 - | Expected_node -> "expected node" 140 - | Expected_scalar -> "expected scalar" 141 - | Expected_sequence_end -> "expected sequence end ']'" 142 - | Expected_mapping_end -> "expected mapping end '}'" 143 - | Duplicate_anchor s -> Printf.sprintf "duplicate anchor: &%s" s 144 - | Undefined_alias s -> Printf.sprintf "undefined alias: *%s" s 145 - | Alias_cycle s -> Printf.sprintf "alias cycle detected: *%s" s 146 - | Multiple_documents -> "multiple documents found when single expected" 147 - | Mapping_key_too_long -> "mapping key too long (max 1024 characters)" 148 - | Invalid_scalar_conversion (value, typ) -> 149 - Printf.sprintf "cannot convert %S to %s" value typ 150 - | Type_mismatch (expected, got) -> 151 - Printf.sprintf "type mismatch: expected %s, got %s" expected got 152 - | Unresolved_alias s -> Printf.sprintf "unresolved alias: *%s" s 153 - | Key_not_found s -> Printf.sprintf "key not found: %s" s 154 - | Alias_expansion_node_limit n -> 155 - Printf.sprintf "alias expansion exceeded node limit (%d nodes)" n 156 - | Alias_expansion_depth_limit n -> 157 - Printf.sprintf "alias expansion exceeded depth limit (%d levels)" n 158 - | Invalid_encoding s -> Printf.sprintf "invalid encoding: %s" s 159 - | Scalar_contains_invalid_chars s -> 160 - Printf.sprintf "scalar contains invalid characters: %s" s 161 - | Anchor_not_set -> "anchor not set" 162 - | Invalid_state s -> Printf.sprintf "invalid state: %s" s 163 - | Custom s -> s 164 - 165 - let to_string t = 166 - let loc = match t.span with 167 - | None -> "" 168 - | Some span -> " at " ^ Span.to_string span 169 - in 170 - let ctx = match t.context with 171 - | [] -> "" 172 - | ctxs -> " (in " ^ String.concat " > " (List.rev ctxs) ^ ")" 173 - in 174 - kind_to_string t.kind ^ loc ^ ctx 175 - 176 - let pp fmt t = 177 - Format.fprintf fmt "Yamle error: %s" (to_string t) 178 - 179 - let extract_line source line_num = 180 - let lines = String.split_on_char '\n' source in 181 - if line_num >= 1 && line_num <= List.length lines then 182 - Some (List.nth lines (line_num - 1)) 183 - else 184 - None 185 - 186 - let pp_with_source ~source fmt t = 187 - pp fmt t; 188 - match t.span with 189 - | None -> () 190 - | Some span -> 191 - match extract_line source span.start.line with 192 - | None -> () 193 - | Some line -> 194 - Format.fprintf fmt "\n %d | %s\n" span.start.line line; 195 - let padding = String.make (span.start.column - 1) ' ' in 196 - Format.fprintf fmt " | %s^" padding
-77
yaml/ocaml-yamle/lib/event.ml
··· 1 - (** YAML parser events *) 2 - 3 - type t = 4 - | Stream_start of { encoding : Encoding.t } 5 - | Stream_end 6 - | Document_start of { 7 - version : (int * int) option; 8 - implicit : bool; 9 - } 10 - | Document_end of { implicit : bool } 11 - | Alias of { anchor : string } 12 - | Scalar of { 13 - anchor : string option; 14 - tag : string option; 15 - value : string; 16 - plain_implicit : bool; 17 - quoted_implicit : bool; 18 - style : Scalar_style.t; 19 - } 20 - | Sequence_start of { 21 - anchor : string option; 22 - tag : string option; 23 - implicit : bool; 24 - style : Layout_style.t; 25 - } 26 - | Sequence_end 27 - | Mapping_start of { 28 - anchor : string option; 29 - tag : string option; 30 - implicit : bool; 31 - style : Layout_style.t; 32 - } 33 - | Mapping_end 34 - 35 - type spanned = { 36 - event : t; 37 - span : Span.t; 38 - } 39 - 40 - let pp fmt = function 41 - | Stream_start { encoding } -> 42 - Format.fprintf fmt "stream-start(%a)" Encoding.pp encoding 43 - | Stream_end -> 44 - Format.fprintf fmt "stream-end" 45 - | Document_start { version; implicit } -> 46 - Format.fprintf fmt "document-start(version=%s, implicit=%b)" 47 - (match version with None -> "none" | Some (maj, min) -> Printf.sprintf "%d.%d" maj min) 48 - implicit 49 - | Document_end { implicit } -> 50 - Format.fprintf fmt "document-end(implicit=%b)" implicit 51 - | Alias { anchor } -> 52 - Format.fprintf fmt "alias(%s)" anchor 53 - | Scalar { anchor; tag; value; style; _ } -> 54 - Format.fprintf fmt "scalar(anchor=%s, tag=%s, style=%a, value=%S)" 55 - (Option.value anchor ~default:"none") 56 - (Option.value tag ~default:"none") 57 - Scalar_style.pp style 58 - value 59 - | Sequence_start { anchor; tag; implicit; style } -> 60 - Format.fprintf fmt "sequence-start(anchor=%s, tag=%s, implicit=%b, style=%a)" 61 - (Option.value anchor ~default:"none") 62 - (Option.value tag ~default:"none") 63 - implicit 64 - Layout_style.pp style 65 - | Sequence_end -> 66 - Format.fprintf fmt "sequence-end" 67 - | Mapping_start { anchor; tag; implicit; style } -> 68 - Format.fprintf fmt "mapping-start(anchor=%s, tag=%s, implicit=%b, style=%a)" 69 - (Option.value anchor ~default:"none") 70 - (Option.value tag ~default:"none") 71 - implicit 72 - Layout_style.pp style 73 - | Mapping_end -> 74 - Format.fprintf fmt "mapping-end" 75 - 76 - let pp_spanned fmt { event; span } = 77 - Format.fprintf fmt "%a at %a" pp event Span.pp span
-151
yaml/ocaml-yamle/lib/input.ml
··· 1 - (** Character input source with lookahead *) 2 - 3 - type t = { 4 - source : string; 5 - mutable pos : int; (** Current byte position *) 6 - mutable position : Position.t; (** Line/column tracking *) 7 - length : int; 8 - } 9 - 10 - let of_string source = 11 - let encoding, bom_len = Encoding.detect source in 12 - (* For now, we only support UTF-8. Skip BOM if present. *) 13 - ignore encoding; 14 - { 15 - source; 16 - pos = bom_len; 17 - position = Position.initial; 18 - length = String.length source; 19 - } 20 - 21 - let position t = t.position 22 - 23 - let is_eof t = t.pos >= t.length 24 - 25 - let peek t = 26 - if t.pos >= t.length then None 27 - else Some t.source.[t.pos] 28 - 29 - let peek_exn t = 30 - if t.pos >= t.length then 31 - Error.raise_at t.position Unexpected_eof 32 - else 33 - t.source.[t.pos] 34 - 35 - let peek_nth t n = 36 - let idx = t.pos + n in 37 - if idx >= t.length then None 38 - else Some t.source.[idx] 39 - 40 - let peek_string t n = 41 - if t.pos + n > t.length then 42 - String.sub t.source t.pos (t.length - t.pos) 43 - else 44 - String.sub t.source t.pos n 45 - 46 - let next t = 47 - if t.pos >= t.length then None 48 - else begin 49 - let c = t.source.[t.pos] in 50 - t.pos <- t.pos + 1; 51 - t.position <- Position.advance_char c t.position; 52 - Some c 53 - end 54 - 55 - let next_exn t = 56 - match next t with 57 - | Some c -> c 58 - | None -> Error.raise_at t.position Unexpected_eof 59 - 60 - let skip t n = 61 - for _ = 1 to n do 62 - ignore (next t) 63 - done 64 - 65 - let skip_while t pred = 66 - while not (is_eof t) && pred (Option.get (peek t)) do 67 - ignore (next t) 68 - done 69 - 70 - (** Character classification *) 71 - 72 - let is_break c = c = '\n' || c = '\r' 73 - 74 - let is_blank c = c = ' ' || c = '\t' 75 - 76 - let is_whitespace c = is_break c || is_blank c 77 - 78 - let is_digit c = c >= '0' && c <= '9' 79 - 80 - let is_hex c = 81 - (c >= '0' && c <= '9') || 82 - (c >= 'a' && c <= 'f') || 83 - (c >= 'A' && c <= 'F') 84 - 85 - let is_alpha c = 86 - (c >= 'a' && c <= 'z') || 87 - (c >= 'A' && c <= 'Z') 88 - 89 - let is_alnum c = is_alpha c || is_digit c 90 - 91 - (** YAML indicator characters *) 92 - let is_indicator c = 93 - match c with 94 - | '-' | '?' | ':' | ',' | '[' | ']' | '{' | '}' 95 - | '#' | '&' | '*' | '!' | '|' | '>' | '\'' | '"' 96 - | '%' | '@' | '`' -> true 97 - | _ -> false 98 - 99 - (** Characters that cannot start a plain scalar *) 100 - let is_flow_indicator c = 101 - match c with 102 - | ',' | '[' | ']' | '{' | '}' -> true 103 - | _ -> false 104 - 105 - (** Check if next char satisfies predicate *) 106 - let next_is pred t = 107 - match peek t with 108 - | None -> false 109 - | Some c -> pred c 110 - 111 - let next_is_break t = next_is is_break t 112 - let next_is_blank t = next_is is_blank t 113 - let next_is_whitespace t = next_is is_whitespace t 114 - let next_is_digit t = next_is is_digit t 115 - let next_is_hex t = next_is is_hex t 116 - let next_is_alpha t = next_is is_alpha t 117 - let next_is_indicator t = next_is is_indicator t 118 - 119 - (** Check if at document boundary (--- or ...) *) 120 - let at_document_boundary t = 121 - if t.position.column <> 1 then false 122 - else 123 - let s = peek_string t 4 in 124 - let prefix = String.sub s 0 (min 3 (String.length s)) in 125 - (prefix = "---" || prefix = "...") && 126 - (String.length s < 4 || is_whitespace s.[3] || String.length s = 3) 127 - 128 - (** Consume line break, handling \r\n as single break *) 129 - let consume_break t = 130 - match peek t with 131 - | Some '\r' -> 132 - ignore (next t); 133 - (match peek t with 134 - | Some '\n' -> ignore (next t) 135 - | _ -> ()) 136 - | Some '\n' -> 137 - ignore (next t) 138 - | _ -> () 139 - 140 - (** Get remaining content from current position *) 141 - let remaining t = 142 - if t.pos >= t.length then "" 143 - else String.sub t.source t.pos (t.length - t.pos) 144 - 145 - (** Mark current position for span creation *) 146 - let mark t = t.position 147 - 148 - (** Get the character before the current position *) 149 - let peek_back t = 150 - if t.pos <= 0 then None 151 - else Some t.source.[t.pos - 1]
-24
yaml/ocaml-yamle/lib/layout_style.ml
··· 1 - (** Collection layout styles *) 2 - 3 - type t = 4 - | Any (** Let emitter choose *) 5 - | Block (** Indentation-based *) 6 - | Flow (** Inline with brackets *) 7 - 8 - let to_string = function 9 - | Any -> "any" 10 - | Block -> "block" 11 - | Flow -> "flow" 12 - 13 - let pp fmt t = 14 - Format.pp_print_string fmt (to_string t) 15 - 16 - let equal a b = a = b 17 - 18 - let compare a b = 19 - let to_int = function 20 - | Any -> 0 21 - | Block -> 1 22 - | Flow -> 2 23 - in 24 - Int.compare (to_int a) (to_int b)
-276
yaml/ocaml-yamle/lib/loader.ml
··· 1 - (** Loader - converts parser events to YAML data structures *) 2 - 3 - (** Stack frame for building nested structures *) 4 - type frame = 5 - | Sequence_frame of { 6 - anchor : string option; 7 - tag : string option; 8 - implicit : bool; 9 - style : Layout_style.t; 10 - items : Yaml.t list; 11 - } 12 - | Mapping_frame of { 13 - anchor : string option; 14 - tag : string option; 15 - implicit : bool; 16 - style : Layout_style.t; 17 - pairs : (Yaml.t * Yaml.t) list; 18 - pending_key : Yaml.t option; 19 - } 20 - 21 - type state = { 22 - mutable stack : frame list; 23 - mutable current : Yaml.t option; 24 - mutable documents : Document.t list; 25 - mutable doc_version : (int * int) option; 26 - mutable doc_implicit_start : bool; 27 - } 28 - 29 - let create_state () = { 30 - stack = []; 31 - current = None; 32 - documents = []; 33 - doc_version = None; 34 - doc_implicit_start = true; 35 - } 36 - 37 - (** Process a single event *) 38 - let rec process_event state (ev : Event.spanned) = 39 - match ev.event with 40 - | Event.Stream_start _ -> () 41 - 42 - | Event.Stream_end -> () 43 - 44 - | Event.Document_start { version; implicit } -> 45 - state.doc_version <- version; 46 - state.doc_implicit_start <- implicit 47 - 48 - | Event.Document_end { implicit } -> 49 - let doc = Document.make 50 - ?version:state.doc_version 51 - ~implicit_start:state.doc_implicit_start 52 - ~implicit_end:implicit 53 - state.current 54 - in 55 - state.documents <- doc :: state.documents; 56 - state.current <- None; 57 - state.doc_version <- None; 58 - state.doc_implicit_start <- true 59 - 60 - | Event.Alias { anchor } -> 61 - let node : Yaml.t = `Alias anchor in 62 - add_node state node 63 - 64 - | Event.Scalar { anchor; tag; value; plain_implicit; quoted_implicit; style } -> 65 - let scalar = Scalar.make 66 - ?anchor ?tag 67 - ~plain_implicit ~quoted_implicit 68 - ~style value 69 - in 70 - let node : Yaml.t = `Scalar scalar in 71 - add_node state node 72 - 73 - | Event.Sequence_start { anchor; tag; implicit; style } -> 74 - let frame = Sequence_frame { 75 - anchor; tag; implicit; style; 76 - items = []; 77 - } in 78 - state.stack <- frame :: state.stack 79 - 80 - | Event.Sequence_end -> 81 - (match state.stack with 82 - | Sequence_frame { anchor; tag; implicit; style; items } :: rest -> 83 - let seq = Sequence.make ?anchor ?tag ~implicit ~style (List.rev items) in 84 - let node : Yaml.t = `A seq in 85 - state.stack <- rest; 86 - add_node state node 87 - | _ -> Error.raise (Invalid_state "unexpected sequence end")) 88 - 89 - | Event.Mapping_start { anchor; tag; implicit; style } -> 90 - let frame = Mapping_frame { 91 - anchor; tag; implicit; style; 92 - pairs = []; 93 - pending_key = None; 94 - } in 95 - state.stack <- frame :: state.stack 96 - 97 - | Event.Mapping_end -> 98 - (match state.stack with 99 - | Mapping_frame { anchor; tag; implicit; style; pairs; pending_key = None } :: rest -> 100 - let map = Mapping.make ?anchor ?tag ~implicit ~style (List.rev pairs) in 101 - let node : Yaml.t = `O map in 102 - state.stack <- rest; 103 - add_node state node 104 - | Mapping_frame { pending_key = Some _; _ } :: _ -> 105 - Error.raise (Invalid_state "mapping ended with pending key") 106 - | _ -> Error.raise (Invalid_state "unexpected mapping end")) 107 - 108 - (** Add a node to current context *) 109 - and add_node state node = 110 - match state.stack with 111 - | [] -> 112 - state.current <- Some node 113 - 114 - | Sequence_frame f :: rest -> 115 - state.stack <- Sequence_frame { f with items = node :: f.items } :: rest 116 - 117 - | Mapping_frame f :: rest -> 118 - (match f.pending_key with 119 - | None -> 120 - (* This is a key *) 121 - state.stack <- Mapping_frame { f with pending_key = Some node } :: rest 122 - | Some key -> 123 - (* This is a value *) 124 - state.stack <- Mapping_frame { 125 - f with 126 - pairs = (key, node) :: f.pairs; 127 - pending_key = None; 128 - } :: rest) 129 - 130 - (** Load single document as Value. 131 - 132 - @param resolve_aliases Whether to resolve aliases (default true) 133 - @param max_nodes Maximum nodes during alias expansion (default 10M) 134 - @param max_depth Maximum alias nesting depth (default 100) 135 - *) 136 - let value_of_string 137 - ?(resolve_aliases = true) 138 - ?(max_nodes = Yaml.default_max_alias_nodes) 139 - ?(max_depth = Yaml.default_max_alias_depth) 140 - s = 141 - let parser = Parser.of_string s in 142 - let state = create_state () in 143 - Parser.iter (process_event state) parser; 144 - match state.documents with 145 - | [] -> `Null 146 - | [doc] -> 147 - (match Document.root doc with 148 - | None -> `Null 149 - | Some yaml -> 150 - Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth yaml) 151 - | _ -> Error.raise Multiple_documents 152 - 153 - (** Load single document as Yaml. 154 - 155 - @param resolve_aliases Whether to resolve aliases (default false for Yaml.t) 156 - @param max_nodes Maximum nodes during alias expansion (default 10M) 157 - @param max_depth Maximum alias nesting depth (default 100) 158 - *) 159 - let yaml_of_string 160 - ?(resolve_aliases = false) 161 - ?(max_nodes = Yaml.default_max_alias_nodes) 162 - ?(max_depth = Yaml.default_max_alias_depth) 163 - s = 164 - let parser = Parser.of_string s in 165 - let state = create_state () in 166 - Parser.iter (process_event state) parser; 167 - match state.documents with 168 - | [] -> `Scalar (Scalar.make "") 169 - | [doc] -> 170 - (match Document.root doc with 171 - | None -> `Scalar (Scalar.make "") 172 - | Some yaml -> 173 - if resolve_aliases then 174 - Yaml.resolve_aliases ~max_nodes ~max_depth yaml 175 - else 176 - yaml) 177 - | _ -> Error.raise Multiple_documents 178 - 179 - (** Load all documents *) 180 - let documents_of_string s = 181 - let parser = Parser.of_string s in 182 - let state = create_state () in 183 - Parser.iter (process_event state) parser; 184 - List.rev state.documents 185 - 186 - (** Load single Value from parser. 187 - 188 - @param resolve_aliases Whether to resolve aliases (default true) 189 - @param max_nodes Maximum nodes during alias expansion (default 10M) 190 - @param max_depth Maximum alias nesting depth (default 100) 191 - *) 192 - let load_value 193 - ?(resolve_aliases = true) 194 - ?(max_nodes = Yaml.default_max_alias_nodes) 195 - ?(max_depth = Yaml.default_max_alias_depth) 196 - parser = 197 - let state = create_state () in 198 - let rec loop () = 199 - match Parser.next parser with 200 - | None -> None 201 - | Some ev -> 202 - process_event state ev; 203 - match ev.event with 204 - | Event.Document_end _ -> 205 - (match state.documents with 206 - | doc :: _ -> 207 - state.documents <- []; 208 - Some (match Document.root doc with 209 - | None -> `Null 210 - | Some yaml -> 211 - Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth yaml) 212 - | [] -> None) 213 - | Event.Stream_end -> None 214 - | _ -> loop () 215 - in 216 - loop () 217 - 218 - (** Load single Yaml from parser *) 219 - let load_yaml parser = 220 - let state = create_state () in 221 - let rec loop () = 222 - match Parser.next parser with 223 - | None -> None 224 - | Some ev -> 225 - process_event state ev; 226 - match ev.event with 227 - | Event.Document_end _ -> 228 - (match state.documents with 229 - | doc :: _ -> 230 - state.documents <- []; 231 - Some (match Document.root doc with 232 - | None -> `Scalar (Scalar.make "") 233 - | Some yaml -> yaml) 234 - | [] -> None) 235 - | Event.Stream_end -> None 236 - | _ -> loop () 237 - in 238 - loop () 239 - 240 - (** Load single Document from parser *) 241 - let load_document parser = 242 - let state = create_state () in 243 - let rec loop () = 244 - match Parser.next parser with 245 - | None -> None 246 - | Some ev -> 247 - process_event state ev; 248 - match ev.event with 249 - | Event.Document_end _ -> 250 - (match state.documents with 251 - | doc :: _ -> 252 - state.documents <- []; 253 - Some doc 254 - | [] -> None) 255 - | Event.Stream_end -> None 256 - | _ -> loop () 257 - in 258 - loop () 259 - 260 - (** Iterate over documents *) 261 - let iter_documents f parser = 262 - let rec loop () = 263 - match load_document parser with 264 - | None -> () 265 - | Some doc -> f doc; loop () 266 - in 267 - loop () 268 - 269 - (** Fold over documents *) 270 - let fold_documents f init parser = 271 - let rec loop acc = 272 - match load_document parser with 273 - | None -> acc 274 - | Some doc -> loop (f acc doc) 275 - in 276 - loop init
-92
yaml/ocaml-yamle/lib/mapping.ml
··· 1 - (** YAML mapping (object) values with metadata *) 2 - 3 - type ('k, 'v) t = { 4 - anchor : string option; 5 - tag : string option; 6 - implicit : bool; 7 - style : Layout_style.t; 8 - members : ('k * 'v) list; 9 - } 10 - 11 - let make 12 - ?(anchor : string option) 13 - ?(tag : string option) 14 - ?(implicit = true) 15 - ?(style = Layout_style.Any) 16 - members = 17 - { anchor; tag; implicit; style; members } 18 - 19 - let members t = t.members 20 - let anchor t = t.anchor 21 - let tag t = t.tag 22 - let implicit t = t.implicit 23 - let style t = t.style 24 - 25 - let with_anchor anchor t = { t with anchor = Some anchor } 26 - let with_tag tag t = { t with tag = Some tag } 27 - let with_style style t = { t with style } 28 - 29 - let map_keys f t = { t with members = List.map (fun (k, v) -> (f k, v)) t.members } 30 - let map_values f t = { t with members = List.map (fun (k, v) -> (k, f v)) t.members } 31 - let map f t = { t with members = List.map (fun (k, v) -> f k v) t.members } 32 - 33 - let length t = List.length t.members 34 - 35 - let is_empty t = t.members = [] 36 - 37 - let find pred t = 38 - match List.find_opt (fun (k, _) -> pred k) t.members with 39 - | Some (_, v) -> Some v 40 - | None -> None 41 - 42 - let find_key pred t = 43 - List.find_opt (fun (k, _) -> pred k) t.members 44 - 45 - let mem pred t = 46 - List.exists (fun (k, _) -> pred k) t.members 47 - 48 - let keys t = List.map fst t.members 49 - 50 - let values t = List.map snd t.members 51 - 52 - let iter f t = List.iter (fun (k, v) -> f k v) t.members 53 - 54 - let fold f init t = List.fold_left (fun acc (k, v) -> f acc k v) init t.members 55 - 56 - let pp pp_key pp_val fmt t = 57 - Format.fprintf fmt "@[<hv 2>mapping(@,"; 58 - (match t.anchor with 59 - | Some a -> Format.fprintf fmt "anchor=%s,@ " a 60 - | None -> ()); 61 - (match t.tag with 62 - | Some tag -> Format.fprintf fmt "tag=%s,@ " tag 63 - | None -> ()); 64 - Format.fprintf fmt "style=%a,@ " Layout_style.pp t.style; 65 - Format.fprintf fmt "members={@,"; 66 - List.iteri (fun i (k, v) -> 67 - if i > 0 then Format.fprintf fmt ",@ "; 68 - Format.fprintf fmt "@[<hv 2>%a:@ %a@]" pp_key k pp_val v 69 - ) t.members; 70 - Format.fprintf fmt "@]@,})" 71 - 72 - let equal eq_k eq_v a b = 73 - Option.equal String.equal a.anchor b.anchor && 74 - Option.equal String.equal a.tag b.tag && 75 - a.implicit = b.implicit && 76 - Layout_style.equal a.style b.style && 77 - List.equal (fun (k1, v1) (k2, v2) -> eq_k k1 k2 && eq_v v1 v2) a.members b.members 78 - 79 - let compare cmp_k cmp_v a b = 80 - let c = Option.compare String.compare a.anchor b.anchor in 81 - if c <> 0 then c else 82 - let c = Option.compare String.compare a.tag b.tag in 83 - if c <> 0 then c else 84 - let c = Bool.compare a.implicit b.implicit in 85 - if c <> 0 then c else 86 - let c = Layout_style.compare a.style b.style in 87 - if c <> 0 then c else 88 - let cmp_pair (k1, v1) (k2, v2) = 89 - let c = cmp_k k1 k2 in 90 - if c <> 0 then c else cmp_v v1 v2 91 - in 92 - List.compare cmp_pair a.members b.members
-781
yaml/ocaml-yamle/lib/parser.ml
··· 1 - (** YAML parser - converts tokens to semantic events via state machine *) 2 - 3 - (** Parser states *) 4 - type state = 5 - | Stream_start 6 - | Implicit_document_start 7 - | Document_start 8 - | Document_content 9 - | Document_content_done (* After parsing a node, check for unexpected content *) 10 - | Document_end 11 - | Block_node 12 - | Block_node_or_indentless_sequence 13 - | Flow_node 14 - | Block_sequence_first_entry 15 - | Block_sequence_entry 16 - | Indentless_sequence_entry 17 - | Block_mapping_first_key 18 - | Block_mapping_key 19 - | Block_mapping_value 20 - | Flow_sequence_first_entry 21 - | Flow_sequence_entry 22 - | Flow_sequence_entry_mapping_key 23 - | Flow_sequence_entry_mapping_value 24 - | Flow_sequence_entry_mapping_end 25 - | Flow_mapping_first_key 26 - | Flow_mapping_key 27 - | Flow_mapping_value 28 - | Flow_mapping_empty_value 29 - | End 30 - 31 - type t = { 32 - scanner : Scanner.t; 33 - mutable state : state; 34 - mutable states : state list; (** State stack *) 35 - mutable marks : Span.t list; (** Mark stack for span tracking *) 36 - mutable version : (int * int) option; 37 - mutable tag_directives : (string * string) list; 38 - mutable current_token : Token.spanned option; 39 - mutable finished : bool; 40 - mutable explicit_doc_end : bool; (** True if last doc ended with explicit ... *) 41 - mutable stream_start : bool; (** True if we haven't emitted any documents yet *) 42 - } 43 - 44 - let create scanner = { 45 - scanner; 46 - state = Stream_start; 47 - states = []; 48 - marks = []; 49 - version = None; 50 - tag_directives = [ 51 - ("!", "!"); 52 - ("!!", "tag:yaml.org,2002:"); 53 - ]; 54 - current_token = None; 55 - finished = false; 56 - explicit_doc_end = false; 57 - stream_start = true; 58 - } 59 - 60 - let of_string s = create (Scanner.of_string s) 61 - 62 - (** Get current token, fetching if needed *) 63 - let current_token t = 64 - match t.current_token with 65 - | Some tok -> tok 66 - | None -> 67 - let tok = Scanner.next t.scanner in 68 - t.current_token <- tok; 69 - match tok with 70 - | Some tok -> tok 71 - | None -> Error.raise Unexpected_eof 72 - 73 - (** Peek at current token *) 74 - let peek_token t = 75 - match t.current_token with 76 - | Some _ -> t.current_token 77 - | None -> 78 - t.current_token <- Scanner.next t.scanner; 79 - t.current_token 80 - 81 - (** Skip current token *) 82 - let skip_token t = 83 - t.current_token <- None 84 - 85 - (** Check if current token matches *) 86 - let check t pred = 87 - match peek_token t with 88 - | Some tok -> pred tok.token 89 - | None -> false 90 - 91 - (** Check for specific token *) 92 - let check_token t token_match = 93 - check t token_match 94 - 95 - (** Push state onto stack *) 96 - let push_state t s = 97 - t.states <- s :: t.states 98 - 99 - (** Pop state from stack *) 100 - let pop_state t = 101 - match t.states with 102 - | s :: rest -> 103 - t.states <- rest; 104 - s 105 - | [] -> End 106 - 107 - (** Resolve a tag *) 108 - let resolve_tag t ~handle ~suffix = 109 - if handle = "" then 110 - (* Verbatim tag - suffix is already the full URI *) 111 - suffix 112 - else 113 - match List.assoc_opt handle t.tag_directives with 114 - | Some prefix -> prefix ^ suffix 115 - | None when handle = "!" -> "!" ^ suffix 116 - | None -> Error.raise (Invalid_tag (handle ^ suffix)) 117 - 118 - (** Process directives at document start *) 119 - let process_directives t = 120 - t.version <- None; 121 - t.tag_directives <- [("!", "!"); ("!!", "tag:yaml.org,2002:")]; 122 - 123 - while check t (function 124 - | Token.Version_directive _ | Token.Tag_directive _ -> true 125 - | _ -> false) 126 - do 127 - let tok = current_token t in 128 - skip_token t; 129 - match tok.token with 130 - | Token.Version_directive { major; minor } -> 131 - if t.version <> None then 132 - Error.raise_span tok.span (Invalid_yaml_version "duplicate YAML directive"); 133 - t.version <- Some (major, minor) 134 - | Token.Tag_directive { handle; prefix } -> 135 - (* Skip empty tag directives (these are reserved/unknown directives that were ignored) *) 136 - if handle = "" && prefix = "" then 137 - () (* Ignore reserved directives *) 138 - else begin 139 - if List.mem_assoc handle t.tag_directives && 140 - handle <> "!" && handle <> "!!" then 141 - Error.raise_span tok.span (Invalid_tag_directive ("duplicate tag handle: " ^ handle)); 142 - t.tag_directives <- (handle, prefix) :: t.tag_directives 143 - end 144 - | _ -> () 145 - done 146 - 147 - (** Parse anchor and/or tag properties *) 148 - let parse_properties t = 149 - let anchor = ref None in 150 - let tag = ref None in 151 - 152 - while check t (function 153 - | Token.Anchor _ | Token.Tag _ -> true 154 - | _ -> false) 155 - do 156 - let tok = current_token t in 157 - skip_token t; 158 - match tok.token with 159 - | Token.Anchor name -> 160 - if !anchor <> None then 161 - Error.raise_span tok.span (Duplicate_anchor name); 162 - anchor := Some name 163 - | Token.Tag { handle; suffix } -> 164 - if !tag <> None then 165 - Error.raise_span tok.span (Invalid_tag "duplicate tag"); 166 - let resolved = 167 - if handle = "" && suffix = "" then None 168 - else if handle = "!" && suffix = "" then Some "!" 169 - else Some (resolve_tag t ~handle ~suffix) 170 - in 171 - tag := resolved 172 - | _ -> () 173 - done; 174 - (!anchor, !tag) 175 - 176 - (** Empty scalar event *) 177 - let empty_scalar_event ~anchor ~tag span = 178 - Event.Scalar { 179 - anchor; 180 - tag; 181 - value = ""; 182 - plain_implicit = tag = None; 183 - quoted_implicit = false; 184 - style = Scalar_style.Plain; 185 - }, span 186 - 187 - (** Parse stream start *) 188 - let parse_stream_start t = 189 - let tok = current_token t in 190 - skip_token t; 191 - match tok.token with 192 - | Token.Stream_start encoding -> 193 - t.state <- Implicit_document_start; 194 - Event.Stream_start { encoding }, tok.span 195 - | _ -> 196 - Error.raise_span tok.span (Unexpected_token "expected stream start") 197 - 198 - (** Parse document start (implicit or explicit) *) 199 - let parse_document_start t ~implicit = 200 - process_directives t; 201 - 202 - if not implicit then begin 203 - let tok = current_token t in 204 - match tok.token with 205 - | Token.Document_start -> 206 - skip_token t 207 - | _ -> 208 - Error.raise_span tok.span Expected_document_start 209 - end; 210 - 211 - let span = match peek_token t with 212 - | Some tok -> tok.span 213 - | None -> Span.point Position.initial 214 - in 215 - 216 - (* After first document, stream_start is false *) 217 - t.stream_start <- false; 218 - push_state t Document_end; 219 - t.state <- Document_content; 220 - Event.Document_start { version = t.version; implicit }, span 221 - 222 - (** Parse document end *) 223 - let parse_document_end t = 224 - let implicit = not (check t (function Token.Document_end -> true | _ -> false)) in 225 - let span = match peek_token t with 226 - | Some tok -> tok.span 227 - | None -> Span.point Position.initial 228 - in 229 - 230 - if not implicit then skip_token t; 231 - 232 - (* Track if this document ended explicitly with ... *) 233 - t.explicit_doc_end <- not implicit; 234 - t.state <- Implicit_document_start; 235 - Event.Document_end { implicit }, span 236 - 237 - (** Parse node in various contexts *) 238 - let parse_node t ~block ~indentless = 239 - let tok = current_token t in 240 - match tok.token with 241 - | Token.Alias name -> 242 - skip_token t; 243 - t.state <- pop_state t; 244 - Event.Alias { anchor = name }, tok.span 245 - 246 - | Token.Anchor _ | Token.Tag _ -> 247 - let anchor, tag = parse_properties t in 248 - let tok = current_token t in 249 - (match tok.token with 250 - | Token.Block_entry when indentless -> 251 - t.state <- Indentless_sequence_entry; 252 - Event.Sequence_start { 253 - anchor; tag; 254 - implicit = tag = None; 255 - style = Layout_style.Block; 256 - }, tok.span 257 - 258 - | Token.Block_sequence_start when block -> 259 - t.state <- Block_sequence_first_entry; 260 - skip_token t; 261 - Event.Sequence_start { 262 - anchor; tag; 263 - implicit = tag = None; 264 - style = Layout_style.Block; 265 - }, tok.span 266 - 267 - | Token.Block_mapping_start when block -> 268 - t.state <- Block_mapping_first_key; 269 - skip_token t; 270 - Event.Mapping_start { 271 - anchor; tag; 272 - implicit = tag = None; 273 - style = Layout_style.Block; 274 - }, tok.span 275 - 276 - | Token.Flow_sequence_start -> 277 - t.state <- Flow_sequence_first_entry; 278 - skip_token t; 279 - Event.Sequence_start { 280 - anchor; tag; 281 - implicit = tag = None; 282 - style = Layout_style.Flow; 283 - }, tok.span 284 - 285 - | Token.Flow_mapping_start -> 286 - t.state <- Flow_mapping_first_key; 287 - skip_token t; 288 - Event.Mapping_start { 289 - anchor; tag; 290 - implicit = tag = None; 291 - style = Layout_style.Flow; 292 - }, tok.span 293 - 294 - | Token.Scalar { style; value } -> 295 - skip_token t; 296 - t.state <- pop_state t; 297 - let plain_implicit = tag = None && style = Scalar_style.Plain in 298 - let quoted_implicit = tag = None && style <> Scalar_style.Plain in 299 - Event.Scalar { 300 - anchor; tag; value; 301 - plain_implicit; quoted_implicit; style; 302 - }, tok.span 303 - 304 - | _ -> 305 - (* Empty node *) 306 - t.state <- pop_state t; 307 - empty_scalar_event ~anchor ~tag tok.span) 308 - 309 - | Token.Block_sequence_start when block -> 310 - t.state <- Block_sequence_first_entry; 311 - skip_token t; 312 - Event.Sequence_start { 313 - anchor = None; tag = None; 314 - implicit = true; 315 - style = Layout_style.Block; 316 - }, tok.span 317 - 318 - | Token.Block_mapping_start when block -> 319 - t.state <- Block_mapping_first_key; 320 - skip_token t; 321 - Event.Mapping_start { 322 - anchor = None; tag = None; 323 - implicit = true; 324 - style = Layout_style.Block; 325 - }, tok.span 326 - 327 - | Token.Flow_sequence_start -> 328 - t.state <- Flow_sequence_first_entry; 329 - skip_token t; 330 - Event.Sequence_start { 331 - anchor = None; tag = None; 332 - implicit = true; 333 - style = Layout_style.Flow; 334 - }, tok.span 335 - 336 - | Token.Flow_mapping_start -> 337 - t.state <- Flow_mapping_first_key; 338 - skip_token t; 339 - Event.Mapping_start { 340 - anchor = None; tag = None; 341 - implicit = true; 342 - style = Layout_style.Flow; 343 - }, tok.span 344 - 345 - | Token.Block_entry when indentless -> 346 - t.state <- Indentless_sequence_entry; 347 - Event.Sequence_start { 348 - anchor = None; tag = None; 349 - implicit = true; 350 - style = Layout_style.Block; 351 - }, tok.span 352 - 353 - | Token.Scalar { style; value } -> 354 - skip_token t; 355 - t.state <- pop_state t; 356 - let plain_implicit = style = Scalar_style.Plain in 357 - let quoted_implicit = style <> Scalar_style.Plain in 358 - Event.Scalar { 359 - anchor = None; tag = None; value; 360 - plain_implicit; quoted_implicit; style; 361 - }, tok.span 362 - 363 - | _ -> 364 - (* Empty node *) 365 - t.state <- pop_state t; 366 - empty_scalar_event ~anchor:None ~tag:None tok.span 367 - 368 - (** Parse block sequence entry *) 369 - let parse_block_sequence_entry t = 370 - let tok = current_token t in 371 - match tok.token with 372 - | Token.Block_entry -> 373 - skip_token t; 374 - if check t (function 375 - | Token.Block_entry | Token.Block_end -> true 376 - | _ -> false) 377 - then begin 378 - t.state <- Block_sequence_entry; 379 - empty_scalar_event ~anchor:None ~tag:None tok.span 380 - end else begin 381 - push_state t Block_sequence_entry; 382 - parse_node t ~block:true ~indentless:false 383 - end 384 - | Token.Block_end -> 385 - skip_token t; 386 - t.state <- pop_state t; 387 - Event.Sequence_end, tok.span 388 - | _ -> 389 - Error.raise_span tok.span Expected_block_entry 390 - 391 - (** Parse block mapping key *) 392 - let parse_block_mapping_key t = 393 - let tok = current_token t in 394 - match tok.token with 395 - | Token.Key -> 396 - skip_token t; 397 - if check t (function 398 - | Token.Key | Token.Value | Token.Block_end -> true 399 - | _ -> false) 400 - then begin 401 - t.state <- Block_mapping_value; 402 - empty_scalar_event ~anchor:None ~tag:None tok.span 403 - end else begin 404 - push_state t Block_mapping_value; 405 - parse_node t ~block:true ~indentless:true 406 - end 407 - (* Handle value without explicit key - key is empty/null *) 408 - | Token.Value -> 409 - t.state <- Block_mapping_value; 410 - empty_scalar_event ~anchor:None ~tag:None tok.span 411 - | Token.Block_end -> 412 - skip_token t; 413 - t.state <- pop_state t; 414 - Event.Mapping_end, tok.span 415 - | _ -> 416 - Error.raise_span tok.span Expected_key 417 - 418 - (** Parse block mapping value *) 419 - let parse_block_mapping_value t = 420 - let tok = current_token t in 421 - match tok.token with 422 - | Token.Value -> 423 - skip_token t; 424 - if check t (function 425 - | Token.Key | Token.Value | Token.Block_end -> true 426 - | _ -> false) 427 - then begin 428 - t.state <- Block_mapping_key; 429 - empty_scalar_event ~anchor:None ~tag:None tok.span 430 - end else begin 431 - push_state t Block_mapping_key; 432 - parse_node t ~block:true ~indentless:true 433 - end 434 - | _ -> 435 - (* Implicit empty value *) 436 - t.state <- Block_mapping_key; 437 - empty_scalar_event ~anchor:None ~tag:None tok.span 438 - 439 - (** Parse indentless sequence entry *) 440 - let parse_indentless_sequence_entry t = 441 - let tok = current_token t in 442 - match tok.token with 443 - | Token.Block_entry -> 444 - skip_token t; 445 - if check t (function 446 - | Token.Block_entry | Token.Key | Token.Value | Token.Block_end -> true 447 - | _ -> false) 448 - then begin 449 - t.state <- Indentless_sequence_entry; 450 - empty_scalar_event ~anchor:None ~tag:None tok.span 451 - end else begin 452 - push_state t Indentless_sequence_entry; 453 - parse_node t ~block:true ~indentless:false 454 - end 455 - | _ -> 456 - t.state <- pop_state t; 457 - Event.Sequence_end, tok.span 458 - 459 - (** Parse flow sequence *) 460 - let rec parse_flow_sequence_entry t ~first = 461 - let tok = current_token t in 462 - match tok.token with 463 - | Token.Flow_sequence_end -> 464 - skip_token t; 465 - t.state <- pop_state t; 466 - Event.Sequence_end, tok.span 467 - | Token.Flow_entry when not first -> 468 - skip_token t; 469 - parse_flow_sequence_entry_internal t 470 - | _ when first -> 471 - parse_flow_sequence_entry_internal t 472 - | _ -> 473 - Error.raise_span tok.span Expected_sequence_end 474 - 475 - and parse_flow_sequence_entry_internal t = 476 - let tok = current_token t in 477 - match tok.token with 478 - | Token.Flow_sequence_end -> 479 - (* Trailing comma case - don't emit empty scalar, just go back to sequence entry state *) 480 - skip_token t; 481 - t.state <- pop_state t; 482 - Event.Sequence_end, tok.span 483 - | Token.Flow_entry -> 484 - (* Double comma or comma after comma - invalid *) 485 - Error.raise_span tok.span (Unexpected_token "unexpected ',' in flow sequence") 486 - | Token.Key -> 487 - skip_token t; 488 - t.state <- Flow_sequence_entry_mapping_key; 489 - Event.Mapping_start { 490 - anchor = None; tag = None; 491 - implicit = true; 492 - style = Layout_style.Flow; 493 - }, tok.span 494 - | Token.Value -> 495 - (* Implicit empty key mapping: [ : value ] *) 496 - t.state <- Flow_sequence_entry_mapping_key; 497 - Event.Mapping_start { 498 - anchor = None; tag = None; 499 - implicit = true; 500 - style = Layout_style.Flow; 501 - }, tok.span 502 - | _ -> 503 - push_state t Flow_sequence_entry; 504 - parse_node t ~block:false ~indentless:false 505 - 506 - (** Parse flow sequence entry mapping *) 507 - let parse_flow_sequence_entry_mapping_key t = 508 - let tok = current_token t in 509 - if check t (function 510 - | Token.Value | Token.Flow_entry | Token.Flow_sequence_end -> true 511 - | _ -> false) 512 - then begin 513 - t.state <- Flow_sequence_entry_mapping_value; 514 - empty_scalar_event ~anchor:None ~tag:None tok.span 515 - end else begin 516 - push_state t Flow_sequence_entry_mapping_value; 517 - parse_node t ~block:false ~indentless:false 518 - end 519 - 520 - let parse_flow_sequence_entry_mapping_value t = 521 - let tok = current_token t in 522 - match tok.token with 523 - | Token.Value -> 524 - skip_token t; 525 - if check t (function 526 - | Token.Flow_entry | Token.Flow_sequence_end -> true 527 - | _ -> false) 528 - then begin 529 - t.state <- Flow_sequence_entry_mapping_end; 530 - empty_scalar_event ~anchor:None ~tag:None tok.span 531 - end else begin 532 - push_state t Flow_sequence_entry_mapping_end; 533 - parse_node t ~block:false ~indentless:false 534 - end 535 - | _ -> 536 - t.state <- Flow_sequence_entry_mapping_end; 537 - empty_scalar_event ~anchor:None ~tag:None tok.span 538 - 539 - let parse_flow_sequence_entry_mapping_end t = 540 - let tok = current_token t in 541 - t.state <- Flow_sequence_entry; 542 - Event.Mapping_end, tok.span 543 - 544 - (** Parse flow mapping *) 545 - let rec parse_flow_mapping_key t ~first = 546 - let tok = current_token t in 547 - match tok.token with 548 - | Token.Flow_mapping_end -> 549 - skip_token t; 550 - t.state <- pop_state t; 551 - Event.Mapping_end, tok.span 552 - | Token.Flow_entry when not first -> 553 - skip_token t; 554 - parse_flow_mapping_key_internal t 555 - | _ when first -> 556 - parse_flow_mapping_key_internal t 557 - | _ -> 558 - Error.raise_span tok.span Expected_mapping_end 559 - 560 - and parse_flow_mapping_key_internal t = 561 - let tok = current_token t in 562 - match tok.token with 563 - | Token.Flow_mapping_end -> 564 - (* Trailing comma case - don't emit empty scalar, just return to key state *) 565 - skip_token t; 566 - t.state <- pop_state t; 567 - Event.Mapping_end, tok.span 568 - | Token.Flow_entry -> 569 - (* Double comma or comma after comma - invalid *) 570 - Error.raise_span tok.span (Unexpected_token "unexpected ',' in flow mapping") 571 - | Token.Key -> 572 - skip_token t; 573 - if check t (function 574 - | Token.Value | Token.Flow_entry | Token.Flow_mapping_end -> true 575 - | _ -> false) 576 - then begin 577 - t.state <- Flow_mapping_value; 578 - empty_scalar_event ~anchor:None ~tag:None tok.span 579 - end else begin 580 - push_state t Flow_mapping_value; 581 - parse_node t ~block:false ~indentless:false 582 - end 583 - | _ -> 584 - push_state t Flow_mapping_value; 585 - parse_node t ~block:false ~indentless:false 586 - 587 - let parse_flow_mapping_value t ~empty = 588 - let tok = current_token t in 589 - if empty then begin 590 - t.state <- Flow_mapping_key; 591 - empty_scalar_event ~anchor:None ~tag:None tok.span 592 - end else 593 - match tok.token with 594 - | Token.Value -> 595 - skip_token t; 596 - if check t (function 597 - | Token.Flow_entry | Token.Flow_mapping_end -> true 598 - | _ -> false) 599 - then begin 600 - t.state <- Flow_mapping_key; 601 - empty_scalar_event ~anchor:None ~tag:None tok.span 602 - end else begin 603 - push_state t Flow_mapping_key; 604 - parse_node t ~block:false ~indentless:false 605 - end 606 - | _ -> 607 - t.state <- Flow_mapping_key; 608 - empty_scalar_event ~anchor:None ~tag:None tok.span 609 - 610 - (** Main state machine dispatcher *) 611 - let rec parse t = 612 - match t.state with 613 - | Stream_start -> 614 - parse_stream_start t 615 - 616 - | Implicit_document_start -> 617 - (* Skip any document end markers before checking what's next *) 618 - while check t (function Token.Document_end -> true | _ -> false) do 619 - t.explicit_doc_end <- true; (* Seeing ... counts as explicit end *) 620 - skip_token t 621 - done; 622 - 623 - let tok = current_token t in 624 - (match tok.token with 625 - | Token.Stream_end -> 626 - skip_token t; 627 - t.state <- End; 628 - t.finished <- true; 629 - Event.Stream_end, tok.span 630 - | Token.Version_directive _ | Token.Tag_directive _ -> 631 - (* Directives are only allowed at stream start or after explicit ... (MUS6/01) *) 632 - if not t.stream_start && not t.explicit_doc_end then 633 - Error.raise_span tok.span (Invalid_directive "directives require explicit document end '...' before them"); 634 - parse_document_start t ~implicit:false 635 - | Token.Document_start -> 636 - parse_document_start t ~implicit:false 637 - (* These tokens are invalid at document start - they indicate leftover junk *) 638 - | Token.Flow_sequence_end | Token.Flow_mapping_end | Token.Flow_entry 639 - | Token.Block_end | Token.Value -> 640 - Error.raise_span tok.span (Unexpected_token "unexpected token at document start") 641 - | _ -> 642 - parse_document_start t ~implicit:true) 643 - 644 - | Document_start -> 645 - parse_document_start t ~implicit:false 646 - 647 - | Document_content -> 648 - if check t (function 649 - | Token.Version_directive _ | Token.Tag_directive _ 650 - | Token.Document_start | Token.Document_end | Token.Stream_end -> true 651 - | _ -> false) 652 - then begin 653 - let tok = current_token t in 654 - t.state <- pop_state t; 655 - empty_scalar_event ~anchor:None ~tag:None tok.span 656 - end else begin 657 - (* Push Document_content_done so we return there after parsing the node. 658 - This allows us to check for unexpected content after the node. *) 659 - push_state t Document_content_done; 660 - parse_node t ~block:true ~indentless:false 661 - end 662 - 663 - | Document_content_done -> 664 - (* After parsing a node in document content, check for unexpected content *) 665 - if check t (function 666 - | Token.Version_directive _ | Token.Tag_directive _ 667 - | Token.Document_start | Token.Document_end | Token.Stream_end -> true 668 - | _ -> false) 669 - then begin 670 - (* Valid document boundary - continue to Document_end *) 671 - t.state <- pop_state t; 672 - parse t (* Continue to emit the next event *) 673 - end else begin 674 - (* Unexpected content after document value - this is an error (KS4U, BS4K) *) 675 - let tok = current_token t in 676 - Error.raise_span tok.span 677 - (Unexpected_token "content not allowed after document value") 678 - end 679 - 680 - | Document_end -> 681 - parse_document_end t 682 - 683 - | Block_node -> 684 - parse_node t ~block:true ~indentless:false 685 - 686 - | Block_node_or_indentless_sequence -> 687 - parse_node t ~block:true ~indentless:true 688 - 689 - | Flow_node -> 690 - parse_node t ~block:false ~indentless:false 691 - 692 - | Block_sequence_first_entry -> 693 - t.state <- Block_sequence_entry; 694 - parse_block_sequence_entry t 695 - 696 - | Block_sequence_entry -> 697 - parse_block_sequence_entry t 698 - 699 - | Indentless_sequence_entry -> 700 - parse_indentless_sequence_entry t 701 - 702 - | Block_mapping_first_key -> 703 - t.state <- Block_mapping_key; 704 - parse_block_mapping_key t 705 - 706 - | Block_mapping_key -> 707 - parse_block_mapping_key t 708 - 709 - | Block_mapping_value -> 710 - parse_block_mapping_value t 711 - 712 - | Flow_sequence_first_entry -> 713 - parse_flow_sequence_entry t ~first:true 714 - 715 - | Flow_sequence_entry -> 716 - parse_flow_sequence_entry t ~first:false 717 - 718 - | Flow_sequence_entry_mapping_key -> 719 - parse_flow_sequence_entry_mapping_key t 720 - 721 - | Flow_sequence_entry_mapping_value -> 722 - parse_flow_sequence_entry_mapping_value t 723 - 724 - | Flow_sequence_entry_mapping_end -> 725 - parse_flow_sequence_entry_mapping_end t 726 - 727 - | Flow_mapping_first_key -> 728 - parse_flow_mapping_key t ~first:true 729 - 730 - | Flow_mapping_key -> 731 - parse_flow_mapping_key t ~first:false 732 - 733 - | Flow_mapping_value -> 734 - parse_flow_mapping_value t ~empty:false 735 - 736 - | Flow_mapping_empty_value -> 737 - parse_flow_mapping_value t ~empty:true 738 - 739 - | End -> 740 - let span = Span.point Position.initial in 741 - t.finished <- true; 742 - Event.Stream_end, span 743 - 744 - (** Get next event *) 745 - let next t = 746 - if t.finished then None 747 - else begin 748 - let event, span = parse t in 749 - Some { Event.event; span } 750 - end 751 - 752 - (** Peek at next event *) 753 - let peek t = 754 - (* Parser is not easily peekable without full state save/restore *) 755 - (* For now, we don't support peek - could add caching if needed *) 756 - if t.finished then None 757 - else 758 - (* Just call next and the caller will have to deal with it *) 759 - next t 760 - 761 - (** Iterate over all events *) 762 - let iter f t = 763 - let rec loop () = 764 - match next t with 765 - | None -> () 766 - | Some ev -> f ev; loop () 767 - in 768 - loop () 769 - 770 - (** Fold over all events *) 771 - let fold f init t = 772 - let rec loop acc = 773 - match next t with 774 - | None -> acc 775 - | Some ev -> loop (f acc ev) 776 - in 777 - loop init 778 - 779 - (** Convert to list *) 780 - let to_list t = 781 - fold (fun acc ev -> ev :: acc) [] t |> List.rev
-42
yaml/ocaml-yamle/lib/position.ml
··· 1 - (** Position tracking for source locations *) 2 - 3 - type t = { 4 - index : int; (** Byte offset from start *) 5 - line : int; (** 1-indexed line number *) 6 - column : int; (** 1-indexed column number *) 7 - } 8 - 9 - let initial = { index = 0; line = 1; column = 1 } 10 - 11 - let advance_byte t = 12 - { t with index = t.index + 1; column = t.column + 1 } 13 - 14 - let advance_line t = 15 - { index = t.index + 1; line = t.line + 1; column = 1 } 16 - 17 - let advance_char c t = 18 - if c = '\n' then advance_line t 19 - else advance_byte t 20 - 21 - let advance_utf8 uchar t = 22 - let len = Uchar.utf_8_byte_length uchar in 23 - let code = Uchar.to_int uchar in 24 - if code = 0x0A (* LF *) then 25 - { index = t.index + len; line = t.line + 1; column = 1 } 26 - else 27 - { t with index = t.index + len; column = t.column + 1 } 28 - 29 - let advance_bytes n t = 30 - { t with index = t.index + n; column = t.column + n } 31 - 32 - let pp fmt t = 33 - Format.fprintf fmt "line %d, column %d" t.line t.column 34 - 35 - let to_string t = 36 - Format.asprintf "%a" pp t 37 - 38 - let compare a b = 39 - Int.compare a.index b.index 40 - 41 - let equal a b = 42 - a.index = b.index
-61
yaml/ocaml-yamle/lib/scalar.ml
··· 1 - (** YAML scalar values with metadata *) 2 - 3 - type t = { 4 - anchor : string option; 5 - tag : string option; 6 - value : string; 7 - plain_implicit : bool; 8 - quoted_implicit : bool; 9 - style : Scalar_style.t; 10 - } 11 - 12 - let make 13 - ?(anchor : string option) 14 - ?(tag : string option) 15 - ?(plain_implicit = true) 16 - ?(quoted_implicit = false) 17 - ?(style = Scalar_style.Plain) 18 - value = 19 - { anchor; tag; value; plain_implicit; quoted_implicit; style } 20 - 21 - let value t = t.value 22 - let anchor t = t.anchor 23 - let tag t = t.tag 24 - let style t = t.style 25 - let plain_implicit t = t.plain_implicit 26 - let quoted_implicit t = t.quoted_implicit 27 - 28 - let with_anchor anchor t = { t with anchor = Some anchor } 29 - let with_tag tag t = { t with tag = Some tag } 30 - let with_style style t = { t with style } 31 - 32 - let pp fmt t = 33 - Format.fprintf fmt "scalar(%S" t.value; 34 - (match t.anchor with 35 - | Some a -> Format.fprintf fmt ", anchor=%s" a 36 - | None -> ()); 37 - (match t.tag with 38 - | Some tag -> Format.fprintf fmt ", tag=%s" tag 39 - | None -> ()); 40 - Format.fprintf fmt ", style=%a)" Scalar_style.pp t.style 41 - 42 - let equal a b = 43 - Option.equal String.equal a.anchor b.anchor && 44 - Option.equal String.equal a.tag b.tag && 45 - String.equal a.value b.value && 46 - a.plain_implicit = b.plain_implicit && 47 - a.quoted_implicit = b.quoted_implicit && 48 - Scalar_style.equal a.style b.style 49 - 50 - let compare a b = 51 - let c = Option.compare String.compare a.anchor b.anchor in 52 - if c <> 0 then c else 53 - let c = Option.compare String.compare a.tag b.tag in 54 - if c <> 0 then c else 55 - let c = String.compare a.value b.value in 56 - if c <> 0 then c else 57 - let c = Bool.compare a.plain_implicit b.plain_implicit in 58 - if c <> 0 then c else 59 - let c = Bool.compare a.quoted_implicit b.quoted_implicit in 60 - if c <> 0 then c else 61 - Scalar_style.compare a.style b.style
-33
yaml/ocaml-yamle/lib/scalar_style.ml
··· 1 - (** Scalar formatting styles *) 2 - 3 - type t = 4 - | Any (** Let emitter choose *) 5 - | Plain (** Unquoted: foo *) 6 - | Single_quoted (** 'foo' *) 7 - | Double_quoted (** "foo" *) 8 - | Literal (** | block *) 9 - | Folded (** > block *) 10 - 11 - let to_string = function 12 - | Any -> "any" 13 - | Plain -> "plain" 14 - | Single_quoted -> "single-quoted" 15 - | Double_quoted -> "double-quoted" 16 - | Literal -> "literal" 17 - | Folded -> "folded" 18 - 19 - let pp fmt t = 20 - Format.pp_print_string fmt (to_string t) 21 - 22 - let equal a b = a = b 23 - 24 - let compare a b = 25 - let to_int = function 26 - | Any -> 0 27 - | Plain -> 1 28 - | Single_quoted -> 2 29 - | Double_quoted -> 3 30 - | Literal -> 4 31 - | Folded -> 5 32 - in 33 - Int.compare (to_int a) (to_int b)
-1568
yaml/ocaml-yamle/lib/scanner.ml
··· 1 - (** YAML tokenizer/scanner with lookahead for ambiguity resolution *) 2 - 3 - (** Simple key tracking for mapping key disambiguation *) 4 - type simple_key = { 5 - sk_possible : bool; 6 - sk_required : bool; 7 - sk_token_number : int; 8 - sk_position : Position.t; 9 - } 10 - 11 - (** Indent level tracking *) 12 - type indent = { 13 - indent : int; 14 - needs_block_end : bool; 15 - sequence : bool; (** true if this is a sequence indent *) 16 - } 17 - 18 - type t = { 19 - input : Input.t; 20 - mutable tokens : Token.spanned Queue.t; 21 - mutable token_number : int; 22 - mutable tokens_taken : int; 23 - mutable stream_started : bool; 24 - mutable stream_ended : bool; 25 - mutable indent_stack : indent list; (** Stack of indentation levels *) 26 - mutable flow_level : int; (** Nesting depth in [] or {} *) 27 - mutable flow_indent : int; (** Column where outermost flow collection started *) 28 - mutable simple_keys : simple_key option list; (** Per flow-level simple key tracking *) 29 - mutable allow_simple_key : bool; 30 - mutable leading_whitespace : bool; (** True when at start of line (only whitespace seen) *) 31 - mutable document_has_content : bool; (** True if we've emitted content tokens in current document *) 32 - mutable adjacent_value_allowed_at : Position.t option; (** Position where adjacent : is allowed *) 33 - mutable pending_value : bool; (** True if we've emitted a KEY and are waiting for VALUE *) 34 - mutable flow_mapping_stack : bool list; (** Stack of whether each flow level is a mapping *) 35 - } 36 - 37 - let create input = 38 - { 39 - input; 40 - tokens = Queue.create (); 41 - token_number = 0; 42 - tokens_taken = 0; 43 - stream_started = false; 44 - stream_ended = false; 45 - indent_stack = []; 46 - flow_level = 0; 47 - flow_indent = 0; 48 - simple_keys = [None]; (* One entry for the base level *) 49 - allow_simple_key = true; 50 - leading_whitespace = true; (* Start at beginning of stream *) 51 - document_has_content = false; 52 - adjacent_value_allowed_at = None; 53 - pending_value = false; 54 - flow_mapping_stack = []; 55 - } 56 - 57 - let of_string s = create (Input.of_string s) 58 - 59 - let position t = Input.position t.input 60 - 61 - (** Add a token to the queue *) 62 - let emit t span token = 63 - Queue.add { Token.token; span } t.tokens; 64 - t.token_number <- t.token_number + 1 65 - 66 - (** Get current column (1-indexed) *) 67 - let column t = (Input.position t.input).column 68 - 69 - (** Get current indent level *) 70 - let current_indent t = 71 - match t.indent_stack with 72 - | [] -> -1 73 - | { indent; _ } :: _ -> indent 74 - 75 - (** Skip whitespace to end of line, checking for valid comments. 76 - Returns true if any whitespace (including tabs) was found before a comment. *) 77 - let skip_whitespace_and_comment t = 78 - let has_whitespace = ref false in 79 - (* Skip blanks (spaces and tabs) *) 80 - while Input.next_is_blank t.input do 81 - has_whitespace := true; 82 - ignore (Input.next t.input) 83 - done; 84 - (* Check for comment *) 85 - if Input.next_is (( = ) '#') t.input then begin 86 - (* Validate: comment must be preceded by whitespace or be at start of line *) 87 - if not !has_whitespace then begin 88 - (* Check if we're at the start of input or after whitespace (blank or line break) *) 89 - match Input.peek_back t.input with 90 - | None -> () (* Start of input - OK *) 91 - | Some c when Input.is_whitespace c -> () (* After whitespace - OK *) 92 - | _ -> 93 - (* Comment not preceded by whitespace - ERROR *) 94 - Error.raise_at (Input.mark t.input) Invalid_comment 95 - end; 96 - (* Skip to end of line *) 97 - while not (Input.is_eof t.input) && not (Input.next_is_break t.input) do 98 - ignore (Input.next t.input) 99 - done 100 - end 101 - 102 - (** Skip blanks (spaces/tabs) and return (found_tabs, found_spaces) *) 103 - let skip_blanks_check_tabs t = 104 - let found_tab = ref false in 105 - let found_space = ref false in 106 - while Input.next_is_blank t.input do 107 - (match Input.peek t.input with 108 - | Some '\t' -> found_tab := true 109 - | Some ' ' -> found_space := true 110 - | _ -> ()); 111 - ignore (Input.next t.input) 112 - done; 113 - (!found_tab, !found_space) 114 - 115 - (** Skip whitespace and comments, return true if at newline *) 116 - let rec skip_to_next_token t = 117 - (* Check for tabs used as indentation in block context *) 118 - (match Input.peek t.input with 119 - | Some '\t' when t.flow_level = 0 && t.leading_whitespace && 120 - (column t - 1) < current_indent t -> 121 - (* Tab found in indentation zone - this is invalid *) 122 - (* Skip to end of line to check if line has content *) 123 - let start_pos = Input.mark t.input in 124 - while Input.next_is_blank t.input do 125 - ignore (Input.next t.input) 126 - done; 127 - (* If we have content on this line with a tab, raise error *) 128 - if not (Input.next_is_break t.input) && not (Input.is_eof t.input) then 129 - Error.raise_at start_pos Tab_in_indentation 130 - | _ -> ()); 131 - 132 - (* Skip blanks and validate comments *) 133 - skip_whitespace_and_comment t; 134 - (* Skip line break in block context *) 135 - if t.flow_level = 0 && Input.next_is_break t.input then begin 136 - Input.consume_break t.input; 137 - t.allow_simple_key <- true; 138 - t.leading_whitespace <- true; 139 - skip_to_next_token t 140 - end 141 - else if t.flow_level > 0 && Input.next_is_whitespace t.input then begin 142 - (* In flow context, skip all whitespace including line breaks *) 143 - if Input.next_is_break t.input then begin 144 - Input.consume_break t.input; 145 - (* Allow simple keys after line breaks in flow context *) 146 - t.allow_simple_key <- true; 147 - (* After line break in flow, check for tabs at start of line (Y79Y/03) 148 - Tabs are not allowed as indentation - if tab is first char and results 149 - in a column less than flow_indent, it's an error *) 150 - if Input.next_is (( = ) '\t') t.input then begin 151 - (* Tab at start of line in flow context - skip tabs and check position *) 152 - let start_mark = Input.mark t.input in 153 - while Input.next_is (( = ) '\t') t.input do 154 - ignore (Input.next t.input) 155 - done; 156 - (* If only tabs were used (no spaces) and column < flow_indent, error *) 157 - if not (Input.next_is_break t.input) && not (Input.is_eof t.input) && 158 - column t < t.flow_indent then 159 - Error.raise_at start_mark Invalid_flow_indentation 160 - end; 161 - skip_to_next_token t 162 - end else begin 163 - ignore (Input.next t.input); 164 - skip_to_next_token t 165 - end 166 - end 167 - 168 - (** Roll the indentation level *) 169 - let roll_indent t col ~sequence = 170 - if t.flow_level = 0 && col > current_indent t then begin 171 - t.indent_stack <- { indent = col; needs_block_end = true; sequence } :: t.indent_stack; 172 - true 173 - end else 174 - false 175 - 176 - (** Unroll indentation to given column *) 177 - let unroll_indent t col = 178 - while t.flow_level = 0 && 179 - match t.indent_stack with 180 - | { indent; needs_block_end = true; _ } :: _ when indent > col -> true 181 - | _ -> false 182 - do 183 - match t.indent_stack with 184 - | { indent = _; needs_block_end = true; _ } :: rest -> 185 - let pos = Input.position t.input in 186 - let span = Span.point pos in 187 - emit t span Token.Block_end; 188 - t.indent_stack <- rest 189 - | _ -> () 190 - done 191 - 192 - (** Save a potential simple key *) 193 - let save_simple_key t = 194 - if t.allow_simple_key then begin 195 - (* A simple key is required only if we're in a block context, 196 - at the current indentation level, AND the current indent needs a block end. 197 - This matches saphyr's logic and prevents false positives for values. *) 198 - let required = t.flow_level = 0 && 199 - match t.indent_stack with 200 - | { indent; needs_block_end = true; _ } :: _ -> 201 - indent = column t 202 - | _ -> false 203 - in 204 - let sk = { 205 - sk_possible = true; 206 - sk_required = required; 207 - sk_token_number = t.token_number; 208 - sk_position = Input.position t.input; 209 - } in 210 - (* Remove any existing simple key at current level *) 211 - t.simple_keys <- ( 212 - match t.simple_keys with 213 - | _ :: rest -> Some sk :: rest 214 - | [] -> [Some sk] 215 - ) 216 - end 217 - 218 - (** Remove simple key at current level *) 219 - let remove_simple_key t = 220 - match t.simple_keys with 221 - | Some sk :: _rest when sk.sk_required -> 222 - Error.raise_at sk.sk_position Expected_key 223 - | _ :: rest -> t.simple_keys <- None :: rest 224 - | [] -> () 225 - 226 - (** Stale simple keys that span too many tokens *) 227 - let stale_simple_keys t = 228 - t.simple_keys <- List.map (fun sk_opt -> 229 - match sk_opt with 230 - | Some sk when sk.sk_possible && 231 - (Input.position t.input).line > sk.sk_position.line && 232 - t.flow_level = 0 -> 233 - if sk.sk_required then 234 - Error.raise_at sk.sk_position Expected_key; 235 - None 236 - | _ -> sk_opt 237 - ) t.simple_keys 238 - 239 - (** Read anchor or alias name *) 240 - let scan_anchor_alias t = 241 - let start = Input.mark t.input in 242 - let buf = Buffer.create 16 in 243 - (* Per YAML 1.2 spec: anchor names can contain any character that is NOT: 244 - - Whitespace (space, tab, line breaks) 245 - - Flow indicators: []{} 246 - - Comma (,) 247 - This matches the saphyr implementation: is_yaml_non_space && !is_flow *) 248 - while 249 - match Input.peek t.input with 250 - | Some c when not (Input.is_whitespace c) && 251 - not (Input.is_flow_indicator c) && 252 - c <> '\x00' -> 253 - Buffer.add_char buf c; 254 - ignore (Input.next t.input); 255 - true 256 - | _ -> false 257 - do () done; 258 - let name = Buffer.contents buf in 259 - if String.length name = 0 then 260 - Error.raise_at start (Invalid_anchor "empty anchor name"); 261 - (name, Span.make ~start ~stop:(Input.mark t.input)) 262 - 263 - (** Scan tag handle *) 264 - let scan_tag_handle t = 265 - let start = Input.mark t.input in 266 - let buf = Buffer.create 16 in 267 - (* Expect ! *) 268 - (match Input.peek t.input with 269 - | Some '!' -> 270 - Buffer.add_char buf '!'; 271 - ignore (Input.next t.input) 272 - | _ -> Error.raise_at start (Invalid_tag "expected '!'")); 273 - (* Read word chars *) 274 - while 275 - match Input.peek t.input with 276 - | Some c when Input.is_alnum c || c = '-' -> 277 - Buffer.add_char buf c; 278 - ignore (Input.next t.input); 279 - true 280 - | _ -> false 281 - do () done; 282 - (* Check for secondary ! *) 283 - (match Input.peek t.input with 284 - | Some '!' -> 285 - Buffer.add_char buf '!'; 286 - ignore (Input.next t.input) 287 - | _ -> ()); 288 - Buffer.contents buf 289 - 290 - (** Scan tag suffix (after handle) *) 291 - let scan_tag_suffix t = 292 - let is_hex_digit c = 293 - (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') 294 - in 295 - let hex_val c = 296 - match c with 297 - | '0'..'9' -> Char.code c - Char.code '0' 298 - | 'A'..'F' -> Char.code c - Char.code 'A' + 10 299 - | 'a'..'f' -> Char.code c - Char.code 'a' + 10 300 - | _ -> 0 301 - in 302 - let buf = Buffer.create 32 in 303 - while 304 - match Input.peek t.input with 305 - | Some '%' -> 306 - (* Percent-encoded character *) 307 - ignore (Input.next t.input); 308 - (match Input.peek t.input, Input.peek_nth t.input 1 with 309 - | Some c1, Some c2 when is_hex_digit c1 && is_hex_digit c2 -> 310 - ignore (Input.next t.input); 311 - ignore (Input.next t.input); 312 - let code = (hex_val c1) * 16 + (hex_val c2) in 313 - Buffer.add_char buf (Char.chr code); 314 - true 315 - | _ -> 316 - (* Invalid percent encoding - keep the % *) 317 - Buffer.add_char buf '%'; 318 - true) 319 - | Some c when not (Input.is_whitespace c) && 320 - not (Input.is_flow_indicator c) -> 321 - Buffer.add_char buf c; 322 - ignore (Input.next t.input); 323 - true 324 - | _ -> false 325 - do () done; 326 - Buffer.contents buf 327 - 328 - (** Scan a tag *) 329 - let scan_tag t = 330 - let start = Input.mark t.input in 331 - ignore (Input.next t.input); (* consume ! *) 332 - let handle, suffix = 333 - match Input.peek t.input with 334 - | Some '<' -> 335 - (* Verbatim tag: !<...> - handle is empty, suffix is full URI *) 336 - ignore (Input.next t.input); 337 - let buf = Buffer.create 32 in 338 - while 339 - match Input.peek t.input with 340 - | Some '>' -> false 341 - | Some c -> 342 - Buffer.add_char buf c; 343 - ignore (Input.next t.input); 344 - true 345 - | None -> Error.raise_at (Input.mark t.input) (Invalid_tag "unclosed verbatim tag") 346 - do () done; 347 - ignore (Input.next t.input); (* consume > *) 348 - ("", Buffer.contents buf) 349 - | Some c when Input.is_whitespace c || Input.is_flow_indicator c -> 350 - (* Non-specific tag: ! *) 351 - ("!", "") 352 - | Some '!' -> 353 - (* Secondary handle: !! *) 354 - ignore (Input.next t.input); (* consume second ! *) 355 - let suffix = scan_tag_suffix t in 356 - ("!!", suffix) 357 - | _ -> 358 - (* Primary handle or just suffix: !foo or !e!foo *) 359 - (* Read alphanumeric characters *) 360 - let buf = Buffer.create 16 in 361 - while 362 - match Input.peek t.input with 363 - | Some c when Input.is_alnum c || c = '-' -> 364 - Buffer.add_char buf c; 365 - ignore (Input.next t.input); 366 - true 367 - | _ -> false 368 - do () done; 369 - (* Check if next character is ! - if so, this is a named handle *) 370 - (match Input.peek t.input with 371 - | Some '!' -> 372 - (* Named handle like !e! *) 373 - ignore (Input.next t.input); 374 - let handle_name = Buffer.contents buf in 375 - let suffix = scan_tag_suffix t in 376 - ("!" ^ handle_name ^ "!", suffix) 377 - | _ -> 378 - (* Just ! followed by suffix *) 379 - ("!", Buffer.contents buf ^ scan_tag_suffix t)) 380 - in 381 - (* Validate that tag is followed by whitespace, break, or (in flow) flow indicator *) 382 - (match Input.peek t.input with 383 - | None -> () (* EOF is ok *) 384 - | Some c when Input.is_whitespace c || Input.is_break c -> () 385 - | Some c when t.flow_level > 0 && Input.is_flow_indicator c -> () 386 - | _ -> Error.raise_at start (Invalid_tag "expected whitespace or line break after tag")); 387 - let span = Span.make ~start ~stop:(Input.mark t.input) in 388 - (handle, suffix, span) 389 - 390 - (** Scan single-quoted scalar *) 391 - let scan_single_quoted t = 392 - let start = Input.mark t.input in 393 - ignore (Input.next t.input); (* consume opening single-quote *) 394 - let buf = Buffer.create 64 in 395 - let whitespace = Buffer.create 16 in (* Track trailing whitespace *) 396 - 397 - let flush_whitespace () = 398 - if Buffer.length whitespace > 0 then begin 399 - Buffer.add_buffer buf whitespace; 400 - Buffer.clear whitespace 401 - end 402 - in 403 - 404 - let rec loop () = 405 - match Input.peek t.input with 406 - | None -> Error.raise_at start Unclosed_single_quote 407 - | Some '\'' -> 408 - ignore (Input.next t.input); 409 - (* Check for escaped quote ('') *) 410 - (match Input.peek t.input with 411 - | Some '\'' -> 412 - flush_whitespace (); 413 - Buffer.add_char buf '\''; 414 - ignore (Input.next t.input); 415 - loop () 416 - | _ -> 417 - (* End of string - flush any trailing whitespace *) 418 - flush_whitespace ()) 419 - | Some ' ' | Some '\t' -> 420 - (* Track whitespace - don't add to buf yet *) 421 - Buffer.add_char whitespace (Option.get (Input.peek t.input)); 422 - ignore (Input.next t.input); 423 - loop () 424 - | Some '\n' | Some '\r' -> 425 - (* Discard trailing whitespace before line break *) 426 - Buffer.clear whitespace; 427 - Input.consume_break t.input; 428 - (* Skip leading whitespace on next line *) 429 - while Input.next_is_blank t.input do 430 - ignore (Input.next t.input) 431 - done; 432 - (* Check for document boundary *) 433 - if Input.at_document_boundary t.input then 434 - Error.raise_at start Unclosed_single_quote; 435 - (* Check indentation: continuation must be > block indent (QB6E, DK95) *) 436 - let col = column t in 437 - let indent = current_indent t in 438 - if not (Input.is_eof t.input) && not (Input.next_is_break t.input) && col <= indent && indent >= 0 then 439 - Error.raise_at (Input.mark t.input) (Invalid_quoted_scalar_indentation "invalid indentation in quoted scalar"); 440 - (* Count empty lines (consecutive line breaks) *) 441 - let empty_lines = ref 0 in 442 - while Input.next_is_break t.input do 443 - incr empty_lines; 444 - Input.consume_break t.input; 445 - while Input.next_is_blank t.input do 446 - ignore (Input.next t.input) 447 - done; 448 - if Input.at_document_boundary t.input then 449 - Error.raise_at start Unclosed_single_quote; 450 - (* Check indentation after each empty line too *) 451 - let col = column t in 452 - let indent = current_indent t in 453 - if not (Input.is_eof t.input) && not (Input.next_is_break t.input) && col <= indent && indent >= 0 then 454 - Error.raise_at (Input.mark t.input) (Invalid_quoted_scalar_indentation "invalid indentation in quoted scalar") 455 - done; 456 - (* Apply folding rules *) 457 - if !empty_lines > 0 then begin 458 - (* Empty lines: preserve as newlines *) 459 - for _ = 1 to !empty_lines do 460 - Buffer.add_char buf '\n' 461 - done 462 - end else 463 - (* Single break: fold to space (even at start of string) *) 464 - Buffer.add_char buf ' '; 465 - loop () 466 - | Some c -> 467 - flush_whitespace (); 468 - Buffer.add_char buf c; 469 - ignore (Input.next t.input); 470 - loop () 471 - in 472 - loop (); 473 - let span = Span.make ~start ~stop:(Input.mark t.input) in 474 - (Buffer.contents buf, span) 475 - 476 - (** Decode hex escape of given length *) 477 - let decode_hex t len = 478 - let start = Input.mark t.input in 479 - let buf = Buffer.create len in 480 - for _ = 1 to len do 481 - match Input.peek t.input with 482 - | Some c when Input.is_hex c -> 483 - Buffer.add_char buf c; 484 - ignore (Input.next t.input) 485 - | _ -> 486 - Error.raise_at start (Invalid_hex_escape (Buffer.contents buf)) 487 - done; 488 - let code = int_of_string ("0x" ^ Buffer.contents buf) in 489 - if code <= 0x7F then 490 - String.make 1 (Char.chr code) 491 - else if code <= 0x7FF then 492 - let b1 = 0xC0 lor (code lsr 6) in 493 - let b2 = 0x80 lor (code land 0x3F) in 494 - String.init 2 (fun i -> Char.chr (if i = 0 then b1 else b2)) 495 - else if code <= 0xFFFF then 496 - let b1 = 0xE0 lor (code lsr 12) in 497 - let b2 = 0x80 lor ((code lsr 6) land 0x3F) in 498 - let b3 = 0x80 lor (code land 0x3F) in 499 - String.init 3 (fun i -> Char.chr (match i with 0 -> b1 | 1 -> b2 | _ -> b3)) 500 - else 501 - let b1 = 0xF0 lor (code lsr 18) in 502 - let b2 = 0x80 lor ((code lsr 12) land 0x3F) in 503 - let b3 = 0x80 lor ((code lsr 6) land 0x3F) in 504 - let b4 = 0x80 lor (code land 0x3F) in 505 - String.init 4 (fun i -> Char.chr (match i with 0 -> b1 | 1 -> b2 | 2 -> b3 | _ -> b4)) 506 - 507 - (** Scan double-quoted scalar *) 508 - let scan_double_quoted t = 509 - let start = Input.mark t.input in 510 - ignore (Input.next t.input); (* consume opening double-quote *) 511 - let buf = Buffer.create 64 in 512 - let whitespace = Buffer.create 16 in (* Track pending whitespace *) 513 - 514 - let flush_whitespace () = 515 - if Buffer.length whitespace > 0 then begin 516 - Buffer.add_buffer buf whitespace; 517 - Buffer.clear whitespace 518 - end 519 - in 520 - 521 - let rec loop () = 522 - match Input.peek t.input with 523 - | None -> Error.raise_at start Unclosed_double_quote 524 - | Some '"' -> 525 - (* Flush trailing whitespace before closing quote to preserve it *) 526 - flush_whitespace (); 527 - ignore (Input.next t.input) 528 - | Some ' ' | Some '\t' as c_opt -> 529 - (* Track whitespace - don't add to buf yet *) 530 - let c = match c_opt with Some c -> c | None -> assert false in 531 - Buffer.add_char whitespace c; 532 - ignore (Input.next t.input); 533 - loop () 534 - | Some '\\' -> 535 - (* Escape sequence - this is non-whitespace content *) 536 - flush_whitespace (); (* Commit any pending whitespace *) 537 - ignore (Input.next t.input); 538 - (match Input.peek t.input with 539 - | None -> Error.raise_at start (Invalid_escape_sequence "\\<EOF>") 540 - | Some '0' -> Buffer.add_char buf '\x00'; ignore (Input.next t.input) 541 - | Some 'a' -> Buffer.add_char buf '\x07'; ignore (Input.next t.input) 542 - | Some 'b' -> Buffer.add_char buf '\x08'; ignore (Input.next t.input) 543 - | Some 't' | Some '\t' -> Buffer.add_char buf '\t'; ignore (Input.next t.input) 544 - | Some 'n' -> Buffer.add_char buf '\n'; ignore (Input.next t.input) 545 - | Some 'v' -> Buffer.add_char buf '\x0B'; ignore (Input.next t.input) 546 - | Some 'f' -> Buffer.add_char buf '\x0C'; ignore (Input.next t.input) 547 - | Some 'r' -> Buffer.add_char buf '\r'; ignore (Input.next t.input) 548 - | Some 'e' -> Buffer.add_char buf '\x1B'; ignore (Input.next t.input) 549 - | Some ' ' -> Buffer.add_char buf ' '; ignore (Input.next t.input) 550 - | Some '"' -> Buffer.add_char buf '"'; ignore (Input.next t.input) 551 - | Some '/' -> Buffer.add_char buf '/'; ignore (Input.next t.input) 552 - | Some '\\' -> Buffer.add_char buf '\\'; ignore (Input.next t.input) 553 - | Some 'N' -> Buffer.add_string buf "\xC2\x85"; ignore (Input.next t.input) (* NEL *) 554 - | Some '_' -> Buffer.add_string buf "\xC2\xA0"; ignore (Input.next t.input) (* NBSP *) 555 - | Some 'L' -> Buffer.add_string buf "\xE2\x80\xA8"; ignore (Input.next t.input) (* LS *) 556 - | Some 'P' -> Buffer.add_string buf "\xE2\x80\xA9"; ignore (Input.next t.input) (* PS *) 557 - | Some 'x' -> 558 - ignore (Input.next t.input); 559 - Buffer.add_string buf (decode_hex t 2) 560 - | Some 'u' -> 561 - ignore (Input.next t.input); 562 - Buffer.add_string buf (decode_hex t 4) 563 - | Some 'U' -> 564 - ignore (Input.next t.input); 565 - Buffer.add_string buf (decode_hex t 8) 566 - | Some '\n' | Some '\r' -> 567 - (* Line continuation escape *) 568 - Input.consume_break t.input; 569 - while Input.next_is_blank t.input do 570 - ignore (Input.next t.input) 571 - done 572 - | Some c -> 573 - Error.raise_at (Input.mark t.input) 574 - (Invalid_escape_sequence (Printf.sprintf "\\%c" c))); 575 - loop () 576 - | Some '\n' | Some '\r' -> 577 - (* Line break: discard any pending trailing whitespace *) 578 - Buffer.clear whitespace; 579 - Input.consume_break t.input; 580 - (* Count consecutive line breaks (empty lines) *) 581 - let empty_lines = ref 0 in 582 - let continue = ref true in 583 - let started_with_tab = ref false in 584 - while !continue do 585 - (* Track if we start with a tab (for DK95/01 check) *) 586 - if Input.next_is (( = ) '\t') t.input then started_with_tab := true; 587 - (* Skip blanks (spaces/tabs) on the line *) 588 - while Input.next_is_blank t.input do 589 - ignore (Input.next t.input) 590 - done; 591 - (* Check if we hit another line break (empty line) *) 592 - if Input.next_is_break t.input then begin 593 - Input.consume_break t.input; 594 - incr empty_lines; 595 - started_with_tab := false (* Reset for next line *) 596 - end else 597 - continue := false 598 - done; 599 - (* Check for document boundary - this terminates the quoted string *) 600 - if Input.at_document_boundary t.input then 601 - Error.raise_at start Unclosed_double_quote; 602 - (* Check indentation: continuation must be > block indent (QB6E, DK95) 603 - Note: must be strictly greater than block indent, not just equal *) 604 - let col = column t in 605 - let indent = current_indent t in 606 - let start_col = start.column in 607 - (* DK95/01: if continuation started with tabs and column < start column, error *) 608 - if not (Input.is_eof t.input) && !started_with_tab && col < start_col then 609 - Error.raise_at (Input.mark t.input) (Invalid_quoted_scalar_indentation "invalid indentation in quoted scalar"); 610 - if not (Input.is_eof t.input) && col <= indent && indent >= 0 then 611 - Error.raise_at (Input.mark t.input) (Invalid_quoted_scalar_indentation "invalid indentation in quoted scalar"); 612 - (* Per YAML spec: single break = space, break + empty lines = newlines *) 613 - if !empty_lines > 0 then begin 614 - (* Empty lines: output N newlines where N = number of empty lines *) 615 - for _ = 1 to !empty_lines do 616 - Buffer.add_char buf '\n' 617 - done 618 - end else 619 - (* Single break folds to space *) 620 - Buffer.add_char buf ' '; 621 - loop () 622 - | Some c -> 623 - (* Non-whitespace character *) 624 - flush_whitespace (); (* Commit any pending whitespace *) 625 - Buffer.add_char buf c; 626 - ignore (Input.next t.input); 627 - loop () 628 - in 629 - loop (); 630 - let span = Span.make ~start ~stop:(Input.mark t.input) in 631 - (Buffer.contents buf, span) 632 - 633 - (** Check if character can appear in plain scalar at this position *) 634 - let can_continue_plain t c ~in_flow = 635 - match c with 636 - | ':' -> 637 - (* : is OK if not followed by whitespace or flow indicator *) 638 - (match Input.peek_nth t.input 1 with 639 - | None -> true 640 - | Some c2 when Input.is_whitespace c2 -> false 641 - | Some c2 when in_flow && Input.is_flow_indicator c2 -> false 642 - | _ -> true) 643 - | '#' -> 644 - (* # is a comment indicator only if preceded by whitespace *) 645 - (* Check the previous character to determine if this is a comment *) 646 - (match Input.peek_back t.input with 647 - | None -> true (* At start - can't be comment indicator, allow it *) 648 - | Some c when Input.is_whitespace c -> false (* Preceded by whitespace - comment *) 649 - | Some c when Input.is_break c -> false (* At start of line - comment *) 650 - | _ -> true) (* Not preceded by whitespace - part of scalar *) 651 - | c when in_flow && Input.is_flow_indicator c -> false 652 - | _ when Input.is_break c -> false 653 - | _ -> true 654 - 655 - (** Scan plain scalar *) 656 - let scan_plain_scalar t = 657 - let start = Input.mark t.input in 658 - let in_flow = t.flow_level > 0 in 659 - let indent = current_indent t in 660 - (* In flow context, scalars must be indented more than the current block indent. 661 - This ensures that content at block indent or less ends the flow context. *) 662 - if in_flow && (column t - 1) < indent then 663 - Error.raise_at start Invalid_flow_indentation; 664 - let buf = Buffer.create 64 in 665 - let spaces = Buffer.create 16 in 666 - let whitespace = Buffer.create 16 in (* Track whitespace within a line *) 667 - let leading_blanks = ref false in 668 - 669 - let rec scan_line () = 670 - match Input.peek t.input with 671 - | None -> () 672 - | Some c when Input.is_blank c && can_continue_plain t c ~in_flow -> 673 - (* Blank character within a line - save to whitespace buffer *) 674 - Buffer.add_char whitespace c; 675 - ignore (Input.next t.input); 676 - scan_line () 677 - | Some c when can_continue_plain t c ~in_flow -> 678 - (* Non-blank character - process any pending breaks/whitespace first *) 679 - begin 680 - if Buffer.length spaces > 0 then begin 681 - if !leading_blanks then begin 682 - (* Fold line break *) 683 - if Buffer.contents spaces = "\n" then 684 - Buffer.add_char buf ' ' 685 - else begin 686 - (* Multiple breaks - preserve all but first *) 687 - let s = Buffer.contents spaces in 688 - Buffer.add_substring buf s 1 (String.length s - 1) 689 - end 690 - end else 691 - Buffer.add_buffer buf spaces; 692 - Buffer.clear spaces 693 - end; 694 - (* Add any pending whitespace from within the line *) 695 - if Buffer.length whitespace > 0 then begin 696 - Buffer.add_buffer buf whitespace; 697 - Buffer.clear whitespace 698 - end; 699 - (* Add the character *) 700 - Buffer.add_char buf c; 701 - ignore (Input.next t.input); 702 - leading_blanks := false; 703 - scan_line () 704 - end 705 - | _ -> () 706 - in 707 - 708 - let rec scan_lines () = 709 - scan_line (); 710 - (* Check for line continuation *) 711 - if Input.next_is_break t.input then begin 712 - (* Discard any trailing whitespace from the current line *) 713 - Buffer.clear whitespace; 714 - (* Save the line break *) 715 - if !leading_blanks then begin 716 - (* We already had a break - this is an additional break (empty line) *) 717 - Buffer.add_char spaces '\n' 718 - end else begin 719 - (* First line break *) 720 - Buffer.clear spaces; 721 - Buffer.add_char spaces '\n'; 722 - leading_blanks := true 723 - end; 724 - Input.consume_break t.input; 725 - (* Note: We do NOT set allow_simple_key here during plain scalar scanning. 726 - Setting it here would incorrectly allow ':' that appears on a continuation 727 - line to become a mapping indicator. The flag will be set properly after 728 - the scalar ends and skip_to_next_token processes line breaks. *) 729 - (* Skip leading blanks on the next line *) 730 - while Input.next_is_blank t.input do 731 - ignore (Input.next t.input) 732 - done; 733 - let col = (Input.position t.input).column in 734 - (* Check indentation - stop if we're at or before the containing block's indent *) 735 - (* However, allow empty lines (line breaks) to continue even if dedented *) 736 - if Input.next_is_break t.input then 737 - scan_lines () (* Empty line - continue *) 738 - else if not in_flow && col <= indent then 739 - () (* Stop - dedented or at parent level in block context *) 740 - else if Input.at_document_boundary t.input then 741 - () (* Stop - document boundary *) 742 - else 743 - scan_lines () 744 - end 745 - in 746 - 747 - scan_lines (); 748 - let value = Buffer.contents buf in 749 - (* Trim trailing whitespace (spaces and tabs) *) 750 - let value = 751 - let len = String.length value in 752 - let rec find_end i = 753 - if i < 0 then 0 754 - else match value.[i] with 755 - | ' ' | '\t' -> find_end (i - 1) 756 - | _ -> i + 1 757 - in 758 - let end_pos = find_end (len - 1) in 759 - String.sub value 0 end_pos 760 - in 761 - let span = Span.make ~start ~stop:(Input.mark t.input) in 762 - (* Return value, span, and whether we ended with leading blanks (crossed a line break) *) 763 - (value, span, !leading_blanks) 764 - 765 - (** Scan block scalar (literal | or folded >) *) 766 - let scan_block_scalar t literal = 767 - let start = Input.mark t.input in 768 - ignore (Input.next t.input); (* consume | or > *) 769 - 770 - (* Parse header: optional indentation indicator and chomping *) 771 - let explicit_indent = ref None in 772 - let chomping = ref Chomping.Clip in 773 - 774 - (* First character of header *) 775 - (match Input.peek t.input with 776 - | Some c when Input.is_digit c && c <> '0' -> 777 - explicit_indent := Some (Char.code c - Char.code '0'); 778 - ignore (Input.next t.input) 779 - | Some '-' -> chomping := Chomping.Strip; ignore (Input.next t.input) 780 - | Some '+' -> chomping := Chomping.Keep; ignore (Input.next t.input) 781 - | _ -> ()); 782 - 783 - (* Second character of header *) 784 - (match Input.peek t.input with 785 - | Some c when Input.is_digit c && c <> '0' && !explicit_indent = None -> 786 - explicit_indent := Some (Char.code c - Char.code '0'); 787 - ignore (Input.next t.input) 788 - | Some '-' when !chomping = Chomping.Clip -> 789 - chomping := Chomping.Strip; ignore (Input.next t.input) 790 - | Some '+' when !chomping = Chomping.Clip -> 791 - chomping := Chomping.Keep; ignore (Input.next t.input) 792 - | _ -> ()); 793 - 794 - (* Skip whitespace and optional comment *) 795 - skip_whitespace_and_comment t; 796 - 797 - (* Consume line break *) 798 - if Input.next_is_break t.input then 799 - Input.consume_break t.input 800 - else if not (Input.is_eof t.input) then 801 - Error.raise_at (Input.mark t.input) 802 - (Invalid_block_scalar_header "expected newline after header"); 803 - 804 - let base_indent = current_indent t in 805 - (* base_indent is the indent level from the stack, -1 if empty. 806 - It's used directly for comparisons in implicit indent case. *) 807 - let content_indent = ref ( 808 - match !explicit_indent with 809 - | Some n -> 810 - (* Explicit indent: base_indent is 1-indexed column, convert to 0-indexed. 811 - content_indent = (base_indent - 1) + n, but at least n for document level. *) 812 - let base_level = max 0 (base_indent - 1) in 813 - base_level + n 814 - | None -> 0 (* Will be determined by first non-empty line *) 815 - ) in 816 - 817 - let buf = Buffer.create 256 in 818 - let trailing_breaks = Buffer.create 16 in 819 - let leading_blank = ref false in (* Was the previous line "more indented"? *) 820 - let max_empty_line_indent = ref 0 in (* Track max indent of empty lines before first content *) 821 - 822 - (* Skip to content indentation, skipping empty lines. 823 - Returns the number of spaces actually skipped (important for detecting dedentation). *) 824 - let rec skip_to_content_indent () = 825 - if !content_indent > 0 then begin 826 - (* Explicit indent - skip up to content_indent spaces *) 827 - let spaces_skipped = ref 0 in 828 - while !spaces_skipped < !content_indent && Input.next_is (( = ) ' ') t.input do 829 - incr spaces_skipped; 830 - ignore (Input.next t.input) 831 - done; 832 - 833 - (* Check if this line is empty (only spaces/tabs until break/eof) *) 834 - if Input.next_is_break t.input then begin 835 - (* Empty line - record the break and continue *) 836 - Buffer.add_char trailing_breaks '\n'; 837 - Input.consume_break t.input; 838 - skip_to_content_indent () 839 - end else if !spaces_skipped < !content_indent then begin 840 - (* Line starts with fewer spaces than content_indent - dedented *) 841 - !spaces_skipped 842 - end else if Input.next_is_blank t.input then begin 843 - (* Line has spaces/tabs beyond content_indent - could be whitespace content or empty line. 844 - For literal scalars, whitespace-only lines ARE content (not empty). 845 - For folded scalars, whitespace-only lines that are "more indented" are preserved. *) 846 - if literal then 847 - (* Literal: whitespace beyond content_indent is content, let read_lines handle it *) 848 - !content_indent 849 - else begin 850 - (* Folded: check if rest is only blanks *) 851 - let idx = ref 0 in 852 - while match Input.peek_nth t.input !idx with 853 - | Some c when Input.is_blank c -> incr idx; true 854 - | _ -> false 855 - do () done; 856 - match Input.peek_nth t.input (!idx) with 857 - | None | Some '\n' | Some '\r' -> 858 - (* Empty/whitespace-only line in folded - skip spaces *) 859 - while Input.next_is_blank t.input do 860 - ignore (Input.next t.input) 861 - done; 862 - Buffer.add_char trailing_breaks '\n'; 863 - Input.consume_break t.input; 864 - skip_to_content_indent () 865 - | _ -> 866 - (* Has non-whitespace content *) 867 - !content_indent 868 - end 869 - end else 870 - !content_indent 871 - end else begin 872 - (* Implicit indent - skip empty lines without consuming spaces. 873 - Note: Only SPACES count as indentation. Tabs are content, not indentation. 874 - So we only check for spaces when determining if a line is "empty". *) 875 - if Input.next_is_break t.input then begin 876 - Buffer.add_char trailing_breaks '\n'; 877 - Input.consume_break t.input; 878 - skip_to_content_indent () 879 - end else if Input.next_is (( = ) ' ') t.input then begin 880 - (* Check if line is empty (only spaces before break) *) 881 - let idx = ref 0 in 882 - while match Input.peek_nth t.input !idx with 883 - | Some ' ' -> incr idx; true 884 - | _ -> false 885 - do () done; 886 - match Input.peek_nth t.input (!idx) with 887 - | None | Some '\n' | Some '\r' -> 888 - (* Line has only spaces - empty line *) 889 - (* Track max indent of empty lines for later validation *) 890 - if !idx > !max_empty_line_indent then 891 - max_empty_line_indent := !idx; 892 - while Input.next_is (( = ) ' ') t.input do 893 - ignore (Input.next t.input) 894 - done; 895 - Buffer.add_char trailing_breaks '\n'; 896 - Input.consume_break t.input; 897 - skip_to_content_indent () 898 - | _ -> 899 - (* Has content (including tabs which are content, not indentation) *) 900 - 0 901 - end else if Input.next_is (( = ) '\t') t.input then begin 902 - (* Tab at start of line in implicit indent mode - this is an error (Y79Y) 903 - because tabs cannot be used as indentation in YAML *) 904 - Error.raise_at (Input.mark t.input) Tab_in_indentation 905 - end else 906 - (* Not at break or space - other content character *) 907 - 0 908 - end 909 - in 910 - 911 - (* Read content *) 912 - let rec read_lines () = 913 - let spaces_skipped = skip_to_content_indent () in 914 - 915 - (* Check if we're at content *) 916 - if Input.is_eof t.input then () 917 - else if Input.at_document_boundary t.input then () 918 - else begin 919 - (* Count additional leading spaces beyond what was skipped *) 920 - let extra_spaces = ref 0 in 921 - while Input.next_is (( = ) ' ') t.input do 922 - incr extra_spaces; 923 - ignore (Input.next t.input) 924 - done; 925 - 926 - (* Calculate actual line indentation *) 927 - let line_indent = spaces_skipped + !extra_spaces in 928 - 929 - (* Determine content indent from first content line (implicit case) *) 930 - let first_line = !content_indent = 0 in 931 - (* base_indent is 1-indexed column, convert to 0-indexed for comparison with line_indent. 932 - If base_indent = -1 (empty stack), then base_level = -1 means col 0 is valid. *) 933 - let base_level = base_indent - 1 in 934 - let should_process = 935 - if !content_indent = 0 then begin 936 - (* For implicit indent, content must be more indented than base_level. *) 937 - if line_indent <= base_level then 938 - false (* No content - first line not indented enough *) 939 - else begin 940 - (* Validate: first content line must be indented at least as much as 941 - the maximum indent seen on empty lines before it (5LLU, S98Z, W9L4) *) 942 - if line_indent < !max_empty_line_indent && line_indent > base_level then 943 - Error.raise_at (Input.mark t.input) 944 - (Invalid_block_scalar_header "wrongly indented line in block scalar"); 945 - content_indent := line_indent; 946 - true 947 - end 948 - end else if line_indent < !content_indent then 949 - false (* Dedented - done with content *) 950 - else 951 - true 952 - in 953 - 954 - if should_process then begin 955 - (* Check if current line is "more indented" (has extra indent or starts with whitespace). 956 - For folded scalars, lines that start with any whitespace (space or tab) after the 957 - content indentation are "more indented" and preserve breaks. 958 - Note: we check Input.next_is_blank BEFORE reading content to see if content starts with whitespace. *) 959 - let trailing_blank = line_indent > !content_indent || Input.next_is_blank t.input in 960 - 961 - (* Add trailing breaks to buffer *) 962 - if Buffer.length buf > 0 then begin 963 - if Buffer.length trailing_breaks > 0 then begin 964 - if literal then 965 - Buffer.add_buffer buf trailing_breaks 966 - else begin 967 - (* Folded scalar: fold only if both previous and current lines are not more-indented *) 968 - if not !leading_blank && not trailing_blank then begin 969 - let breaks = Buffer.contents trailing_breaks in 970 - if String.length breaks = 1 then 971 - Buffer.add_char buf ' ' 972 - else 973 - Buffer.add_substring buf breaks 1 (String.length breaks - 1) 974 - end else begin 975 - (* Preserve breaks for more-indented lines *) 976 - Buffer.add_buffer buf trailing_breaks 977 - end 978 - end 979 - end else if not literal then 980 - Buffer.add_char buf ' ' 981 - end else 982 - Buffer.add_buffer buf trailing_breaks; 983 - Buffer.clear trailing_breaks; 984 - 985 - (* Add extra indentation for literal or more-indented folded lines *) 986 - (* On the first line (when determining content_indent), we've already consumed all spaces, 987 - so we should NOT add any back. On subsequent lines, we add only the spaces beyond content_indent. *) 988 - if not first_line && (literal || (!extra_spaces > 0 && not literal)) then begin 989 - for _ = 1 to !extra_spaces do 990 - Buffer.add_char buf ' ' 991 - done 992 - end; 993 - 994 - (* Read line content *) 995 - while not (Input.is_eof t.input) && not (Input.next_is_break t.input) do 996 - Buffer.add_char buf (Input.next_exn t.input) 997 - done; 998 - 999 - (* Record trailing break *) 1000 - if Input.next_is_break t.input then begin 1001 - Buffer.add_char trailing_breaks '\n'; 1002 - Input.consume_break t.input 1003 - end; 1004 - 1005 - (* Update leading_blank for next iteration *) 1006 - leading_blank := trailing_blank; 1007 - 1008 - read_lines () 1009 - end 1010 - end 1011 - in 1012 - 1013 - read_lines (); 1014 - 1015 - (* Apply chomping *) 1016 - let value = 1017 - let content = Buffer.contents buf in 1018 - match !chomping with 1019 - | Chomping.Strip -> content 1020 - | Chomping.Clip -> 1021 - if String.length content > 0 then content ^ "\n" else content 1022 - | Chomping.Keep -> 1023 - content ^ Buffer.contents trailing_breaks 1024 - in 1025 - 1026 - let span = Span.make ~start ~stop:(Input.mark t.input) in 1027 - let style = if literal then Scalar_style.Literal else Scalar_style.Folded in 1028 - (value, style, span) 1029 - 1030 - (** Scan directive (after %) *) 1031 - let scan_directive t = 1032 - let start = Input.mark t.input in 1033 - ignore (Input.next t.input); (* consume % *) 1034 - 1035 - (* Read directive name *) 1036 - let name_buf = Buffer.create 16 in 1037 - while 1038 - match Input.peek t.input with 1039 - | Some c when Input.is_alnum c || c = '-' -> 1040 - Buffer.add_char name_buf c; 1041 - ignore (Input.next t.input); 1042 - true 1043 - | _ -> false 1044 - do () done; 1045 - let name = Buffer.contents name_buf in 1046 - 1047 - (* Skip blanks *) 1048 - while Input.next_is_blank t.input do 1049 - ignore (Input.next t.input) 1050 - done; 1051 - 1052 - match name with 1053 - | "YAML" -> 1054 - (* Version directive: %YAML 1.2 *) 1055 - let major = ref 0 in 1056 - let minor = ref 0 in 1057 - (* Read major version *) 1058 - while Input.next_is_digit t.input do 1059 - major := !major * 10 + (Char.code (Input.next_exn t.input) - Char.code '0') 1060 - done; 1061 - (* Expect . *) 1062 - (match Input.peek t.input with 1063 - | Some '.' -> ignore (Input.next t.input) 1064 - | _ -> Error.raise_at (Input.mark t.input) (Invalid_yaml_version "expected '.'")); 1065 - (* Read minor version *) 1066 - while Input.next_is_digit t.input do 1067 - minor := !minor * 10 + (Char.code (Input.next_exn t.input) - Char.code '0') 1068 - done; 1069 - (* Validate: only whitespace and comments allowed before line break (MUS6) *) 1070 - skip_whitespace_and_comment t; 1071 - if not (Input.next_is_break t.input) && not (Input.is_eof t.input) then 1072 - Error.raise_at (Input.mark t.input) (Invalid_directive "expected comment or line break after version"); 1073 - let span = Span.make ~start ~stop:(Input.mark t.input) in 1074 - Token.Version_directive { major = !major; minor = !minor }, span 1075 - 1076 - | "TAG" -> 1077 - (* Tag directive: %TAG !foo! tag:example.com,2000: *) 1078 - let handle = scan_tag_handle t in 1079 - (* Skip blanks *) 1080 - while Input.next_is_blank t.input do 1081 - ignore (Input.next t.input) 1082 - done; 1083 - (* Read prefix *) 1084 - let prefix_buf = Buffer.create 32 in 1085 - while 1086 - match Input.peek t.input with 1087 - | Some c when not (Input.is_whitespace c) -> 1088 - Buffer.add_char prefix_buf c; 1089 - ignore (Input.next t.input); 1090 - true 1091 - | _ -> false 1092 - do () done; 1093 - let prefix = Buffer.contents prefix_buf in 1094 - let span = Span.make ~start ~stop:(Input.mark t.input) in 1095 - Token.Tag_directive { handle; prefix }, span 1096 - 1097 - | _ -> 1098 - (* Reserved/Unknown directive - skip to end of line and ignore *) 1099 - (* Per YAML spec, reserved directives should be ignored with a warning *) 1100 - while not (Input.is_eof t.input) && not (Input.next_is_break t.input) do 1101 - ignore (Input.next t.input) 1102 - done; 1103 - let span = Span.make ~start ~stop:(Input.mark t.input) in 1104 - (* Return an empty tag directive token to indicate directive was processed but ignored *) 1105 - Token.Tag_directive { handle = ""; prefix = "" }, span 1106 - 1107 - (** Fetch the next token(s) into the queue *) 1108 - let rec fetch_next_token t = 1109 - skip_to_next_token t; 1110 - stale_simple_keys t; 1111 - let col = column t in 1112 - (* Unroll indents that are deeper than current column. 1113 - Note: we use col, not col-1, to allow entries at the same level. *) 1114 - unroll_indent t col; 1115 - 1116 - (* We're about to process actual content, not leading whitespace *) 1117 - t.leading_whitespace <- false; 1118 - 1119 - if Input.is_eof t.input then 1120 - fetch_stream_end t 1121 - else if Input.at_document_boundary t.input then 1122 - fetch_document_indicator t 1123 - else begin 1124 - match Input.peek t.input with 1125 - | None -> fetch_stream_end t 1126 - | Some '%' when (Input.position t.input).column = 1 -> 1127 - fetch_directive t 1128 - | Some '[' -> fetch_flow_collection_start t Token.Flow_sequence_start 1129 - | Some '{' -> fetch_flow_collection_start t Token.Flow_mapping_start 1130 - | Some ']' -> fetch_flow_collection_end t Token.Flow_sequence_end 1131 - | Some '}' -> fetch_flow_collection_end t Token.Flow_mapping_end 1132 - | Some ',' -> fetch_flow_entry t 1133 - | Some '-' when t.flow_level = 0 && check_block_entry t -> 1134 - fetch_block_entry t 1135 - | Some '?' when check_key t -> 1136 - fetch_key t 1137 - | Some ':' when check_value t -> 1138 - fetch_value t 1139 - | Some '*' -> fetch_alias t 1140 - | Some '&' -> fetch_anchor t 1141 - | Some '!' -> fetch_tag t 1142 - | Some '|' when t.flow_level = 0 -> fetch_block_scalar t true 1143 - | Some '>' when t.flow_level = 0 -> fetch_block_scalar t false 1144 - | Some '\'' -> fetch_single_quoted t 1145 - | Some '"' -> fetch_double_quoted t 1146 - | Some '-' when can_start_plain t -> 1147 - fetch_plain_scalar t 1148 - | Some '?' when can_start_plain t -> 1149 - fetch_plain_scalar t 1150 - | Some ':' when can_start_plain t -> 1151 - fetch_plain_scalar t 1152 - | Some c when can_start_plain_char c t -> 1153 - fetch_plain_scalar t 1154 - | Some c -> 1155 - Error.raise_at (Input.mark t.input) (Unexpected_character c) 1156 - end 1157 - 1158 - and fetch_stream_end t = 1159 - if not t.stream_ended then begin 1160 - unroll_indent t (-1); 1161 - remove_simple_key t; 1162 - t.allow_simple_key <- false; 1163 - t.stream_ended <- true; 1164 - let span = Span.point (Input.mark t.input) in 1165 - emit t span Token.Stream_end 1166 - end 1167 - 1168 - and fetch_document_indicator t = 1169 - unroll_indent t (-1); 1170 - remove_simple_key t; 1171 - t.allow_simple_key <- false; 1172 - let start = Input.mark t.input in 1173 - let indicator = Input.peek_string t.input 3 in 1174 - Input.skip t.input 3; 1175 - let span = Span.make ~start ~stop:(Input.mark t.input) in 1176 - let token = if indicator = "---" then Token.Document_start else Token.Document_end in 1177 - (* Reset document content flag after document end marker *) 1178 - if indicator = "..." then begin 1179 - t.document_has_content <- false; 1180 - (* After document end marker, skip whitespace and check for end of line or comment *) 1181 - while Input.next_is_blank t.input do ignore (Input.next t.input) done; 1182 - (match Input.peek t.input with 1183 - | None -> () (* EOF is ok *) 1184 - | Some c when Input.is_break c -> () 1185 - | Some '#' -> () (* Comment is ok *) 1186 - | _ -> Error.raise_at start (Invalid_directive "content not allowed after document end marker on same line")) 1187 - end; 1188 - emit t span token 1189 - 1190 - and fetch_directive t = 1191 - (* Directives can only appear: 1192 - 1. At stream start (before any document content) 1193 - 2. After a document end marker (...) 1194 - If we've emitted content in the current document, we need a document end marker first *) 1195 - if t.document_has_content then 1196 - Error.raise_at (Input.mark t.input) 1197 - (Unexpected_token "directives must be separated from document content by document end marker (...)"); 1198 - unroll_indent t (-1); 1199 - remove_simple_key t; 1200 - t.allow_simple_key <- false; 1201 - let token, span = scan_directive t in 1202 - emit t span token 1203 - 1204 - and fetch_flow_collection_start t token_type = 1205 - save_simple_key t; 1206 - (* Record indent of outermost flow collection *) 1207 - if t.flow_level = 0 then 1208 - t.flow_indent <- column t; 1209 - t.flow_level <- t.flow_level + 1; 1210 - (* Track whether this is a mapping or sequence *) 1211 - let is_mapping = (token_type = Token.Flow_mapping_start) in 1212 - t.flow_mapping_stack <- is_mapping :: t.flow_mapping_stack; 1213 - t.allow_simple_key <- true; 1214 - t.simple_keys <- None :: t.simple_keys; 1215 - t.document_has_content <- true; 1216 - let start = Input.mark t.input in 1217 - ignore (Input.next t.input); 1218 - let span = Span.make ~start ~stop:(Input.mark t.input) in 1219 - emit t span token_type 1220 - 1221 - and fetch_flow_collection_end t token_type = 1222 - remove_simple_key t; 1223 - t.flow_level <- t.flow_level - 1; 1224 - t.flow_mapping_stack <- (match t.flow_mapping_stack with _ :: rest -> rest | [] -> []); 1225 - t.simple_keys <- (match t.simple_keys with _ :: rest -> rest | [] -> []); 1226 - t.allow_simple_key <- false; 1227 - let start = Input.mark t.input in 1228 - ignore (Input.next t.input); 1229 - (* Allow adjacent values after flow collection ends *) 1230 - if t.flow_level > 0 then 1231 - t.adjacent_value_allowed_at <- Some (Input.position t.input); 1232 - let span = Span.make ~start ~stop:(Input.mark t.input) in 1233 - emit t span token_type 1234 - 1235 - and fetch_flow_entry t = 1236 - remove_simple_key t; 1237 - t.allow_simple_key <- true; 1238 - let start = Input.mark t.input in 1239 - ignore (Input.next t.input); 1240 - let span = Span.make ~start ~stop:(Input.mark t.input) in 1241 - emit t span Token.Flow_entry 1242 - 1243 - and check_block_entry t = 1244 - (* - followed by whitespace or EOF *) 1245 - match Input.peek_nth t.input 1 with 1246 - | None -> true 1247 - | Some c -> Input.is_whitespace c 1248 - 1249 - and fetch_block_entry t = 1250 - if t.flow_level = 0 then begin 1251 - (* Block entries require allow_simple_key to be true. 1252 - This prevents block sequences on the same line as a mapping value, 1253 - e.g., "key: - a" is invalid. *) 1254 - if not t.allow_simple_key then 1255 - Error.raise_at (Input.mark t.input) Block_sequence_disallowed; 1256 - let col = column t in 1257 - if roll_indent t col ~sequence:true then begin 1258 - let span = Span.point (Input.mark t.input) in 1259 - emit t span Token.Block_sequence_start 1260 - end 1261 - end; 1262 - remove_simple_key t; 1263 - t.allow_simple_key <- true; 1264 - t.document_has_content <- true; 1265 - let start = Input.mark t.input in 1266 - ignore (Input.next t.input); 1267 - 1268 - (* Check for tabs after - : pattern like -\t- is invalid *) 1269 - let (found_tabs, _found_spaces) = skip_blanks_check_tabs t in 1270 - if found_tabs then begin 1271 - (* If we found tabs and next char is - followed by whitespace, error *) 1272 - match Input.peek t.input with 1273 - | Some '-' -> 1274 - (match Input.peek_nth t.input 1 with 1275 - | None -> Error.raise_at start Tab_in_indentation 1276 - | Some c when Input.is_whitespace c -> 1277 - Error.raise_at start Tab_in_indentation 1278 - | Some _ -> ()) 1279 - | _ -> () 1280 - end; 1281 - 1282 - let span = Span.make ~start ~stop:(Input.mark t.input) in 1283 - emit t span Token.Block_entry 1284 - 1285 - and check_key t = 1286 - (* ? followed by whitespace or flow indicator in both block and flow *) 1287 - match Input.peek_nth t.input 1 with 1288 - | None -> true 1289 - | Some c -> 1290 - Input.is_whitespace c || 1291 - (t.flow_level > 0 && Input.is_flow_indicator c) 1292 - 1293 - and fetch_key t = 1294 - if t.flow_level = 0 then begin 1295 - if not t.allow_simple_key then 1296 - Error.raise_at (Input.mark t.input) Expected_key; 1297 - let col = column t in 1298 - if roll_indent t col ~sequence:false then begin 1299 - let span = Span.point (Input.mark t.input) in 1300 - emit t span Token.Block_mapping_start 1301 - end 1302 - end; 1303 - remove_simple_key t; 1304 - t.allow_simple_key <- t.flow_level = 0; 1305 - t.document_has_content <- true; 1306 - let start = Input.mark t.input in 1307 - ignore (Input.next t.input); 1308 - 1309 - (* Check for tabs after ? : pattern like ?\t- or ?\tkey is invalid *) 1310 - let (found_tabs, _found_spaces) = skip_blanks_check_tabs t in 1311 - if found_tabs && t.flow_level = 0 then begin 1312 - (* In block context, tabs after ? are not allowed *) 1313 - Error.raise_at start Tab_in_indentation 1314 - end; 1315 - 1316 - let span = Span.make ~start ~stop:(Input.mark t.input) in 1317 - emit t span Token.Key; 1318 - t.pending_value <- true (* We've emitted a KEY, now waiting for VALUE *) 1319 - 1320 - and check_value t = 1321 - (* : followed by whitespace in block, or whitespace/flow indicator in flow, or adjacent value *) 1322 - match Input.peek_nth t.input 1 with 1323 - | None -> true 1324 - | Some c -> 1325 - Input.is_whitespace c || 1326 - (t.flow_level > 0 && Input.is_flow_indicator c) || 1327 - (* Allow adjacent values in flow context at designated positions *) 1328 - (t.flow_level > 0 && 1329 - match t.adjacent_value_allowed_at with 1330 - | Some pos -> pos.Position.line = (Input.position t.input).Position.line && 1331 - pos.Position.column = (Input.position t.input).Position.column 1332 - | None -> false) 1333 - 1334 - and fetch_value t = 1335 - let start = Input.mark t.input in 1336 - (* Check for simple key *) 1337 - let used_simple_key = 1338 - match t.simple_keys with 1339 - | Some sk :: _ when sk.sk_possible -> 1340 - (* In implicit flow mapping (inside a flow sequence), key and : must be on the same line. 1341 - In explicit flow mapping { }, key and : can span lines. *) 1342 - let is_implicit_flow_mapping = match t.flow_mapping_stack with 1343 - | false :: _ -> true (* false = we're in a sequence, so any mapping is implicit *) 1344 - | _ -> false 1345 - in 1346 - if is_implicit_flow_mapping && sk.sk_position.line < (Input.position t.input).line then 1347 - Error.raise_at start Illegal_flow_key_line; 1348 - (* Insert KEY token before the simple key value *) 1349 - let key_span = Span.point sk.sk_position in 1350 - let key_token = { Token.token = Token.Key; span = key_span } in 1351 - (* We need to insert at the right position *) 1352 - let tokens = Queue.to_seq t.tokens |> Array.of_seq in 1353 - Queue.clear t.tokens; 1354 - let insert_pos = sk.sk_token_number - t.tokens_taken in 1355 - Array.iteri (fun i tok -> 1356 - if i = insert_pos then Queue.add key_token t.tokens; 1357 - Queue.add tok t.tokens 1358 - ) tokens; 1359 - if insert_pos >= Array.length tokens then 1360 - Queue.add key_token t.tokens; 1361 - t.token_number <- t.token_number + 1; 1362 - t.pending_value <- true; (* We've inserted a KEY token, now waiting for VALUE *) 1363 - (* Roll indent for implicit block mapping *) 1364 - if t.flow_level = 0 then begin 1365 - let col = sk.sk_position.column in 1366 - if roll_indent t col ~sequence:false then begin 1367 - let span = Span.point sk.sk_position in 1368 - (* Insert block mapping start before key *) 1369 - let bm_token = { Token.token = Token.Block_mapping_start; span } in 1370 - let tokens = Queue.to_seq t.tokens |> Array.of_seq in 1371 - Queue.clear t.tokens; 1372 - Array.iteri (fun i tok -> 1373 - if i = insert_pos then Queue.add bm_token t.tokens; 1374 - Queue.add tok t.tokens 1375 - ) tokens; 1376 - if insert_pos >= Array.length tokens then 1377 - Queue.add bm_token t.tokens; 1378 - t.token_number <- t.token_number + 1 1379 - end 1380 - end; 1381 - t.simple_keys <- None :: (List.tl t.simple_keys); 1382 - true 1383 - | _ -> 1384 - (* No simple key - this is a complex value (or empty key) *) 1385 - if t.flow_level = 0 then begin 1386 - if not t.allow_simple_key then 1387 - Error.raise_at (Input.mark t.input) Expected_key; 1388 - let col = column t in 1389 - if roll_indent t col ~sequence:false then begin 1390 - let span = Span.point (Input.mark t.input) in 1391 - emit t span Token.Block_mapping_start 1392 - end 1393 - (* Note: We don't emit KEY here. Empty key handling is done by the parser, 1394 - which emits empty scalar when it sees VALUE without preceding KEY. *) 1395 - end; 1396 - false 1397 - in 1398 - remove_simple_key t; 1399 - (* In block context without simple key, allow simple keys for compact mappings like ": moon: white" 1400 - In flow context or after using a simple key, disallow simple keys *) 1401 - t.allow_simple_key <- (not used_simple_key) && (t.flow_level = 0); 1402 - t.document_has_content <- true; 1403 - let start = Input.mark t.input in 1404 - ignore (Input.next t.input); 1405 - 1406 - (* Check for tabs after : : patterns like :\t- or :\tkey: are invalid in block context (Y79Y/09) 1407 - However, :\t bar (tab followed by space then content) is valid (6BCT) *) 1408 - let (found_tabs, found_spaces) = skip_blanks_check_tabs t in 1409 - if found_tabs && not found_spaces && t.flow_level = 0 then begin 1410 - (* In block context, tabs-only after : followed by indicator or alphanumeric are not allowed *) 1411 - match Input.peek t.input with 1412 - | Some ('-' | '?') -> 1413 - Error.raise_at start Tab_in_indentation 1414 - | Some c when (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') -> 1415 - (* Tab-only followed by alphanumeric - likely a key, which is invalid *) 1416 - Error.raise_at start Tab_in_indentation 1417 - | _ -> () 1418 - end; 1419 - 1420 - (* Skip any comment that may follow the colon and whitespace *) 1421 - skip_whitespace_and_comment t; 1422 - 1423 - let span = Span.make ~start ~stop:(Input.mark t.input) in 1424 - emit t span Token.Value; 1425 - t.pending_value <- false (* We've emitted a VALUE, no longer pending *) 1426 - 1427 - and fetch_alias t = 1428 - save_simple_key t; 1429 - t.allow_simple_key <- false; 1430 - t.document_has_content <- true; 1431 - let start = Input.mark t.input in 1432 - ignore (Input.next t.input); (* consume * *) 1433 - let name, span = scan_anchor_alias t in 1434 - let span = Span.make ~start ~stop:span.stop in 1435 - emit t span (Token.Alias name) 1436 - 1437 - and fetch_anchor t = 1438 - save_simple_key t; 1439 - t.allow_simple_key <- false; 1440 - t.document_has_content <- true; 1441 - let start = Input.mark t.input in 1442 - ignore (Input.next t.input); (* consume & *) 1443 - let name, span = scan_anchor_alias t in 1444 - let span = Span.make ~start ~stop:span.stop in 1445 - emit t span (Token.Anchor name) 1446 - 1447 - and fetch_tag t = 1448 - save_simple_key t; 1449 - t.allow_simple_key <- false; 1450 - t.document_has_content <- true; 1451 - let handle, suffix, span = scan_tag t in 1452 - emit t span (Token.Tag { handle; suffix }) 1453 - 1454 - and fetch_block_scalar t literal = 1455 - remove_simple_key t; 1456 - t.allow_simple_key <- true; 1457 - t.document_has_content <- true; 1458 - let value, style, span = scan_block_scalar t literal in 1459 - emit t span (Token.Scalar { style; value }) 1460 - 1461 - and fetch_single_quoted t = 1462 - save_simple_key t; 1463 - t.allow_simple_key <- false; 1464 - t.document_has_content <- true; 1465 - let value, span = scan_single_quoted t in 1466 - (* Allow adjacent values after quoted scalars in flow context (for JSON compatibility) *) 1467 - skip_to_next_token t; 1468 - if t.flow_level > 0 then 1469 - t.adjacent_value_allowed_at <- Some (Input.position t.input); 1470 - emit t span (Token.Scalar { style = Scalar_style.Single_quoted; value }) 1471 - 1472 - and fetch_double_quoted t = 1473 - save_simple_key t; 1474 - t.allow_simple_key <- false; 1475 - t.document_has_content <- true; 1476 - let value, span = scan_double_quoted t in 1477 - (* Allow adjacent values after quoted scalars in flow context (for JSON compatibility) *) 1478 - skip_to_next_token t; 1479 - if t.flow_level > 0 then 1480 - t.adjacent_value_allowed_at <- Some (Input.position t.input); 1481 - emit t span (Token.Scalar { style = Scalar_style.Double_quoted; value }) 1482 - 1483 - and can_start_plain t = 1484 - (* Check if - ? : can start a plain scalar *) 1485 - match Input.peek_nth t.input 1 with 1486 - | None -> false 1487 - | Some c -> 1488 - not (Input.is_whitespace c) && 1489 - (t.flow_level = 0 || not (Input.is_flow_indicator c)) 1490 - 1491 - and can_start_plain_char c _t = 1492 - (* Characters that can start a plain scalar *) 1493 - if Input.is_whitespace c then false 1494 - else if Input.is_indicator c then false 1495 - else true 1496 - 1497 - and fetch_plain_scalar t = 1498 - save_simple_key t; 1499 - t.allow_simple_key <- false; 1500 - t.document_has_content <- true; 1501 - let value, span, ended_with_linebreak = scan_plain_scalar t in 1502 - (* If the plain scalar ended after crossing a line break (leading_blanks = true), 1503 - allow simple keys. This is important because the scanner already consumed the 1504 - line break and leading whitespace when checking for continuation. *) 1505 - if ended_with_linebreak then 1506 - t.allow_simple_key <- true; 1507 - emit t span (Token.Scalar { style = Scalar_style.Plain; value }) 1508 - 1509 - (** Check if we need more tokens to resolve simple keys *) 1510 - let need_more_tokens t = 1511 - if t.stream_ended then false 1512 - else if Queue.is_empty t.tokens then true 1513 - else 1514 - (* Check if any simple key could affect the first queued token *) 1515 - List.exists (function 1516 - | Some sk when sk.sk_possible -> 1517 - sk.sk_token_number >= t.tokens_taken 1518 - | _ -> false 1519 - ) t.simple_keys 1520 - 1521 - (** Ensure we have enough tokens to return one safely *) 1522 - let ensure_tokens t = 1523 - if not t.stream_started then begin 1524 - t.stream_started <- true; 1525 - let span = Span.point (Input.position t.input) in 1526 - let encoding, _ = Encoding.detect t.input.source in 1527 - emit t span (Token.Stream_start encoding) 1528 - end; 1529 - while need_more_tokens t do 1530 - fetch_next_token t 1531 - done 1532 - 1533 - (** Get next token *) 1534 - let next t = 1535 - ensure_tokens t; 1536 - if Queue.is_empty t.tokens then 1537 - None 1538 - else begin 1539 - t.tokens_taken <- t.tokens_taken + 1; 1540 - Some (Queue.pop t.tokens) 1541 - end 1542 - 1543 - (** Peek at next token *) 1544 - let peek t = 1545 - ensure_tokens t; 1546 - Queue.peek_opt t.tokens 1547 - 1548 - (** Iterate over all tokens *) 1549 - let iter f t = 1550 - let rec loop () = 1551 - match next t with 1552 - | None -> () 1553 - | Some tok -> f tok; loop () 1554 - in 1555 - loop () 1556 - 1557 - (** Fold over all tokens *) 1558 - let fold f init t = 1559 - let rec loop acc = 1560 - match next t with 1561 - | None -> acc 1562 - | Some tok -> loop (f acc tok) 1563 - in 1564 - loop init 1565 - 1566 - (** Convert to list *) 1567 - let to_list t = 1568 - fold (fun acc tok -> tok :: acc) [] t |> List.rev
-72
yaml/ocaml-yamle/lib/sequence.ml
··· 1 - (** YAML sequence (array) values with metadata *) 2 - 3 - type 'a t = { 4 - anchor : string option; 5 - tag : string option; 6 - implicit : bool; 7 - style : Layout_style.t; 8 - members : 'a list; 9 - } 10 - 11 - let make 12 - ?(anchor : string option) 13 - ?(tag : string option) 14 - ?(implicit = true) 15 - ?(style = Layout_style.Any) 16 - members = 17 - { anchor; tag; implicit; style; members } 18 - 19 - let members t = t.members 20 - let anchor t = t.anchor 21 - let tag t = t.tag 22 - let implicit t = t.implicit 23 - let style t = t.style 24 - 25 - let with_anchor anchor t = { t with anchor = Some anchor } 26 - let with_tag tag t = { t with tag = Some tag } 27 - let with_style style t = { t with style } 28 - 29 - let map f t = { t with members = List.map f t.members } 30 - 31 - let length t = List.length t.members 32 - 33 - let is_empty t = t.members = [] 34 - 35 - let nth t n = List.nth t.members n 36 - 37 - let nth_opt t n = List.nth_opt t.members n 38 - 39 - let iter f t = List.iter f t.members 40 - 41 - let fold f init t = List.fold_left f init t.members 42 - 43 - let pp pp_elem fmt t = 44 - Format.fprintf fmt "@[<hv 2>sequence(@,"; 45 - (match t.anchor with 46 - | Some a -> Format.fprintf fmt "anchor=%s,@ " a 47 - | None -> ()); 48 - (match t.tag with 49 - | Some tag -> Format.fprintf fmt "tag=%s,@ " tag 50 - | None -> ()); 51 - Format.fprintf fmt "style=%a,@ " Layout_style.pp t.style; 52 - Format.fprintf fmt "members=[@,%a@]@,)" 53 - (Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ") pp_elem) 54 - t.members 55 - 56 - let equal eq a b = 57 - Option.equal String.equal a.anchor b.anchor && 58 - Option.equal String.equal a.tag b.tag && 59 - a.implicit = b.implicit && 60 - Layout_style.equal a.style b.style && 61 - List.equal eq a.members b.members 62 - 63 - let compare cmp a b = 64 - let c = Option.compare String.compare a.anchor b.anchor in 65 - if c <> 0 then c else 66 - let c = Option.compare String.compare a.tag b.tag in 67 - if c <> 0 then c else 68 - let c = Bool.compare a.implicit b.implicit in 69 - if c <> 0 then c else 70 - let c = Layout_style.compare a.style b.style in 71 - if c <> 0 then c else 72 - List.compare cmp a.members b.members
-35
yaml/ocaml-yamle/lib/span.ml
··· 1 - (** Source spans representing ranges in input *) 2 - 3 - type t = { 4 - start : Position.t; 5 - stop : Position.t; 6 - } 7 - 8 - let make ~start ~stop = { start; stop } 9 - 10 - let point pos = { start = pos; stop = pos } 11 - 12 - let merge a b = 13 - let start = if Position.compare a.start b.start <= 0 then a.start else b.start in 14 - let stop = if Position.compare a.stop b.stop >= 0 then a.stop else b.stop in 15 - { start; stop } 16 - 17 - let extend span pos = 18 - { span with stop = pos } 19 - 20 - let pp fmt t = 21 - if t.start.line = t.stop.line then 22 - Format.fprintf fmt "line %d, columns %d-%d" 23 - t.start.line t.start.column t.stop.column 24 - else 25 - Format.fprintf fmt "lines %d-%d" t.start.line t.stop.line 26 - 27 - let to_string t = 28 - Format.asprintf "%a" pp t 29 - 30 - let compare a b = 31 - let c = Position.compare a.start b.start in 32 - if c <> 0 then c else Position.compare a.stop b.stop 33 - 34 - let equal a b = 35 - Position.equal a.start b.start && Position.equal a.stop b.stop
-70
yaml/ocaml-yamle/lib/tag.ml
··· 1 - (** YAML tags for type information *) 2 - 3 - type t = { 4 - handle : string; (** e.g., "!" or "!!" or "!foo!" *) 5 - suffix : string; (** e.g., "str", "int", "custom/type" *) 6 - } 7 - 8 - let make ~handle ~suffix = { handle; suffix } 9 - 10 - let of_string s = 11 - if String.length s = 0 then None 12 - else if s.[0] <> '!' then None 13 - else 14 - (* Find the suffix after the handle *) 15 - let len = String.length s in 16 - if len = 1 then Some { handle = "!"; suffix = "" } 17 - else if s.[1] = '!' then 18 - (* !! handle *) 19 - Some { handle = "!!"; suffix = String.sub s 2 (len - 2) } 20 - else if s.[1] = '<' then 21 - (* Verbatim tag !<...> *) 22 - if len > 2 && s.[len - 1] = '>' then 23 - Some { handle = "!"; suffix = String.sub s 2 (len - 3) } 24 - else 25 - None 26 - else 27 - (* Primary handle or local tag *) 28 - Some { handle = "!"; suffix = String.sub s 1 (len - 1) } 29 - 30 - let to_string t = 31 - if t.handle = "!" && t.suffix = "" then "!" 32 - else t.handle ^ t.suffix 33 - 34 - let to_uri t = 35 - match t.handle with 36 - | "!!" -> "tag:yaml.org,2002:" ^ t.suffix 37 - | "!" -> "!" ^ t.suffix 38 - | h -> h ^ t.suffix 39 - 40 - let pp fmt t = 41 - Format.pp_print_string fmt (to_string t) 42 - 43 - let equal a b = 44 - String.equal a.handle b.handle && String.equal a.suffix b.suffix 45 - 46 - let compare a b = 47 - let c = String.compare a.handle b.handle in 48 - if c <> 0 then c else String.compare a.suffix b.suffix 49 - 50 - (** Standard tags *) 51 - 52 - let null = { handle = "!!"; suffix = "null" } 53 - let bool = { handle = "!!"; suffix = "bool" } 54 - let int = { handle = "!!"; suffix = "int" } 55 - let float = { handle = "!!"; suffix = "float" } 56 - let str = { handle = "!!"; suffix = "str" } 57 - let seq = { handle = "!!"; suffix = "seq" } 58 - let map = { handle = "!!"; suffix = "map" } 59 - let binary = { handle = "!!"; suffix = "binary" } 60 - let timestamp = { handle = "!!"; suffix = "timestamp" } 61 - 62 - (** Check if tag matches a standard type *) 63 - 64 - let is_null t = equal t null || (t.handle = "!" && t.suffix = "") 65 - let is_bool t = equal t bool 66 - let is_int t = equal t int 67 - let is_float t = equal t float 68 - let is_str t = equal t str 69 - let is_seq t = equal t seq 70 - let is_map t = equal t map
-78
yaml/ocaml-yamle/lib/token.ml
··· 1 - (** YAML token types produced by the scanner *) 2 - 3 - type t = 4 - | Stream_start of Encoding.t 5 - | Stream_end 6 - | Version_directive of { major : int; minor : int } 7 - | Tag_directive of { handle : string; prefix : string } 8 - | Document_start (** --- *) 9 - | Document_end (** ... *) 10 - | Block_sequence_start 11 - | Block_mapping_start 12 - | Block_entry (** - *) 13 - | Block_end (** implicit, from dedent *) 14 - | Flow_sequence_start (** [ *) 15 - | Flow_sequence_end (** ] *) 16 - | Flow_mapping_start (** { *) 17 - | Flow_mapping_end (** } *) 18 - | Flow_entry (** , *) 19 - | Key (** ? or implicit key *) 20 - | Value (** : *) 21 - | Anchor of string (** &name *) 22 - | Alias of string (** *name *) 23 - | Tag of { handle : string; suffix : string } 24 - | Scalar of { style : Scalar_style.t; value : string } 25 - 26 - type spanned = { 27 - token : t; 28 - span : Span.t; 29 - } 30 - 31 - let pp_token fmt = function 32 - | Stream_start enc -> 33 - Format.fprintf fmt "STREAM-START(%a)" Encoding.pp enc 34 - | Stream_end -> 35 - Format.fprintf fmt "STREAM-END" 36 - | Version_directive { major; minor } -> 37 - Format.fprintf fmt "VERSION-DIRECTIVE(%d.%d)" major minor 38 - | Tag_directive { handle; prefix } -> 39 - Format.fprintf fmt "TAG-DIRECTIVE(%s, %s)" handle prefix 40 - | Document_start -> 41 - Format.fprintf fmt "DOCUMENT-START" 42 - | Document_end -> 43 - Format.fprintf fmt "DOCUMENT-END" 44 - | Block_sequence_start -> 45 - Format.fprintf fmt "BLOCK-SEQUENCE-START" 46 - | Block_mapping_start -> 47 - Format.fprintf fmt "BLOCK-MAPPING-START" 48 - | Block_entry -> 49 - Format.fprintf fmt "BLOCK-ENTRY" 50 - | Block_end -> 51 - Format.fprintf fmt "BLOCK-END" 52 - | Flow_sequence_start -> 53 - Format.fprintf fmt "FLOW-SEQUENCE-START" 54 - | Flow_sequence_end -> 55 - Format.fprintf fmt "FLOW-SEQUENCE-END" 56 - | Flow_mapping_start -> 57 - Format.fprintf fmt "FLOW-MAPPING-START" 58 - | Flow_mapping_end -> 59 - Format.fprintf fmt "FLOW-MAPPING-END" 60 - | Flow_entry -> 61 - Format.fprintf fmt "FLOW-ENTRY" 62 - | Key -> 63 - Format.fprintf fmt "KEY" 64 - | Value -> 65 - Format.fprintf fmt "VALUE" 66 - | Anchor name -> 67 - Format.fprintf fmt "ANCHOR(%s)" name 68 - | Alias name -> 69 - Format.fprintf fmt "ALIAS(%s)" name 70 - | Tag { handle; suffix } -> 71 - Format.fprintf fmt "TAG(%s, %s)" handle suffix 72 - | Scalar { style; value } -> 73 - Format.fprintf fmt "SCALAR(%a, %S)" Scalar_style.pp style value 74 - 75 - let pp fmt t = pp_token fmt t 76 - 77 - let pp_spanned fmt { token; span } = 78 - Format.fprintf fmt "%a at %a" pp_token token Span.pp span
-182
yaml/ocaml-yamle/lib/value.ml
··· 1 - (** JSON-compatible YAML value representation *) 2 - 3 - type t = [ 4 - | `Null 5 - | `Bool of bool 6 - | `Float of float 7 - | `String of string 8 - | `A of t list 9 - | `O of (string * t) list 10 - ] 11 - 12 - (** Constructors *) 13 - 14 - let null : t = `Null 15 - let bool b : t = `Bool b 16 - let int n : t = `Float (Float.of_int n) 17 - let float f : t = `Float f 18 - let string s : t = `String s 19 - 20 - let list f xs : t = `A (List.map f xs) 21 - let obj pairs : t = `O pairs 22 - 23 - (** Type name for error messages *) 24 - let type_name : t -> string = function 25 - | `Null -> "null" 26 - | `Bool _ -> "bool" 27 - | `Float _ -> "float" 28 - | `String _ -> "string" 29 - | `A _ -> "array" 30 - | `O _ -> "object" 31 - 32 - (** Safe accessors (return option) *) 33 - 34 - let as_null = function `Null -> Some () | _ -> None 35 - let as_bool = function `Bool b -> Some b | _ -> None 36 - let as_float = function `Float f -> Some f | _ -> None 37 - let as_string = function `String s -> Some s | _ -> None 38 - let as_list = function `A l -> Some l | _ -> None 39 - let as_assoc = function `O o -> Some o | _ -> None 40 - 41 - let as_int = function 42 - | `Float f -> 43 - let i = Float.to_int f in 44 - if Float.equal (Float.of_int i) f then Some i else None 45 - | _ -> None 46 - 47 - (** Unsafe accessors (raise on type mismatch) *) 48 - 49 - let to_null v = 50 - match as_null v with 51 - | Some () -> () 52 - | None -> Error.raise (Type_mismatch ("null", type_name v)) 53 - 54 - let to_bool v = 55 - match as_bool v with 56 - | Some b -> b 57 - | None -> Error.raise (Type_mismatch ("bool", type_name v)) 58 - 59 - let to_float v = 60 - match as_float v with 61 - | Some f -> f 62 - | None -> Error.raise (Type_mismatch ("float", type_name v)) 63 - 64 - let to_string v = 65 - match as_string v with 66 - | Some s -> s 67 - | None -> Error.raise (Type_mismatch ("string", type_name v)) 68 - 69 - let to_list v = 70 - match as_list v with 71 - | Some l -> l 72 - | None -> Error.raise (Type_mismatch ("array", type_name v)) 73 - 74 - let to_assoc v = 75 - match as_assoc v with 76 - | Some o -> o 77 - | None -> Error.raise (Type_mismatch ("object", type_name v)) 78 - 79 - let to_int v = 80 - match as_int v with 81 - | Some i -> i 82 - | None -> Error.raise (Type_mismatch ("int", type_name v)) 83 - 84 - (** Object access *) 85 - 86 - let mem key = function 87 - | `O pairs -> List.exists (fun (k, _) -> k = key) pairs 88 - | _ -> false 89 - 90 - let find key = function 91 - | `O pairs -> List.assoc_opt key pairs 92 - | _ -> None 93 - 94 - let get key v = 95 - match find key v with 96 - | Some v -> v 97 - | None -> Error.raise (Key_not_found key) 98 - 99 - let keys = function 100 - | `O pairs -> List.map fst pairs 101 - | v -> Error.raise (Type_mismatch ("object", type_name v)) 102 - 103 - let values = function 104 - | `O pairs -> List.map snd pairs 105 - | v -> Error.raise (Type_mismatch ("object", type_name v)) 106 - 107 - (** Combinators *) 108 - 109 - let combine v1 v2 = 110 - match v1, v2 with 111 - | `O o1, `O o2 -> `O (o1 @ o2) 112 - | v1, _ -> Error.raise (Type_mismatch ("object", type_name v1)) 113 - 114 - let map f = function 115 - | `A l -> `A (List.map f l) 116 - | v -> Error.raise (Type_mismatch ("array", type_name v)) 117 - 118 - let filter pred = function 119 - | `A l -> `A (List.filter pred l) 120 - | v -> Error.raise (Type_mismatch ("array", type_name v)) 121 - 122 - (** Pretty printing *) 123 - 124 - let rec pp fmt (v : t) = 125 - match v with 126 - | `Null -> Format.pp_print_string fmt "null" 127 - | `Bool b -> Format.pp_print_bool fmt b 128 - | `Float f -> 129 - if Float.is_integer f && Float.abs f < 1e15 then 130 - Format.fprintf fmt "%.0f" f 131 - else 132 - Format.fprintf fmt "%g" f 133 - | `String s -> Format.fprintf fmt "%S" s 134 - | `A [] -> Format.pp_print_string fmt "[]" 135 - | `A items -> 136 - Format.fprintf fmt "@[<hv 2>[@,%a@]@,]" 137 - (Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ") pp) 138 - items 139 - | `O [] -> Format.pp_print_string fmt "{}" 140 - | `O pairs -> 141 - Format.fprintf fmt "@[<hv 2>{@,%a@]@,}" 142 - (Format.pp_print_list ~pp_sep:(fun fmt () -> Format.fprintf fmt ",@ ") 143 - (fun fmt (k, v) -> Format.fprintf fmt "@[<hv 2>%S:@ %a@]" k pp v)) 144 - pairs 145 - 146 - (** Equality and comparison *) 147 - 148 - let rec equal (a : t) (b : t) = 149 - match a, b with 150 - | `Null, `Null -> true 151 - | `Bool a, `Bool b -> a = b 152 - | `Float a, `Float b -> Float.equal a b 153 - | `String a, `String b -> String.equal a b 154 - | `A a, `A b -> List.equal equal a b 155 - | `O a, `O b -> 156 - List.length a = List.length b && 157 - List.for_all2 (fun (k1, v1) (k2, v2) -> k1 = k2 && equal v1 v2) a b 158 - | _ -> false 159 - 160 - let rec compare (a : t) (b : t) = 161 - match a, b with 162 - | `Null, `Null -> 0 163 - | `Null, _ -> -1 164 - | _, `Null -> 1 165 - | `Bool a, `Bool b -> Bool.compare a b 166 - | `Bool _, _ -> -1 167 - | _, `Bool _ -> 1 168 - | `Float a, `Float b -> Float.compare a b 169 - | `Float _, _ -> -1 170 - | _, `Float _ -> 1 171 - | `String a, `String b -> String.compare a b 172 - | `String _, _ -> -1 173 - | _, `String _ -> 1 174 - | `A a, `A b -> List.compare compare a b 175 - | `A _, _ -> -1 176 - | _, `A _ -> 1 177 - | `O a, `O b -> 178 - let cmp_pair (k1, v1) (k2, v2) = 179 - let c = String.compare k1 k2 in 180 - if c <> 0 then c else compare v1 v2 181 - in 182 - List.compare cmp_pair a b
-257
yaml/ocaml-yamle/lib/yaml.ml
··· 1 - (** Full YAML representation with anchors, tags, and aliases *) 2 - 3 - type t = [ 4 - | `Scalar of Scalar.t 5 - | `Alias of string 6 - | `A of t Sequence.t 7 - | `O of (t, t) Mapping.t 8 - ] 9 - 10 - (** Pretty printing *) 11 - 12 - let rec pp fmt (v : t) = 13 - match v with 14 - | `Scalar s -> Scalar.pp fmt s 15 - | `Alias name -> Format.fprintf fmt "*%s" name 16 - | `A seq -> Sequence.pp pp fmt seq 17 - | `O map -> Mapping.pp pp pp fmt map 18 - 19 - (** Equality *) 20 - 21 - let rec equal (a : t) (b : t) = 22 - match a, b with 23 - | `Scalar a, `Scalar b -> Scalar.equal a b 24 - | `Alias a, `Alias b -> String.equal a b 25 - | `A a, `A b -> Sequence.equal equal a b 26 - | `O a, `O b -> Mapping.equal equal equal a b 27 - | _ -> false 28 - 29 - (** Construct from JSON-compatible Value *) 30 - 31 - let rec of_value (v : Value.t) : t = 32 - match v with 33 - | `Null -> `Scalar (Scalar.make "null") 34 - | `Bool true -> `Scalar (Scalar.make "true") 35 - | `Bool false -> `Scalar (Scalar.make "false") 36 - | `Float f -> 37 - let s = 38 - if Float.is_integer f && Float.abs f < 1e15 then 39 - Printf.sprintf "%.0f" f 40 - else 41 - Printf.sprintf "%g" f 42 - in 43 - `Scalar (Scalar.make s) 44 - | `String s -> 45 - `Scalar (Scalar.make s ~style:Scalar_style.Double_quoted) 46 - | `A items -> 47 - `A (Sequence.make (List.map of_value items)) 48 - | `O pairs -> 49 - `O (Mapping.make (List.map (fun (k, v) -> 50 - (`Scalar (Scalar.make k), of_value v) 51 - ) pairs)) 52 - 53 - (** Default limits for alias expansion (protection against billion laughs attack) *) 54 - let default_max_alias_nodes = 10_000_000 55 - let default_max_alias_depth = 100 56 - 57 - (** Resolve aliases by replacing them with referenced nodes. 58 - 59 - @param max_nodes Maximum number of nodes to create during expansion (default 10M) 60 - @param max_depth Maximum depth of alias-within-alias resolution (default 100) 61 - @raise Alias_expansion_node_limit if max_nodes is exceeded 62 - @raise Alias_expansion_depth_limit if max_depth is exceeded 63 - *) 64 - let resolve_aliases ?(max_nodes = default_max_alias_nodes) ?(max_depth = default_max_alias_depth) (root : t) : t = 65 - let anchors = Hashtbl.create 16 in 66 - let node_count = ref 0 in 67 - 68 - (* Check node limit *) 69 - let check_node_limit () = 70 - incr node_count; 71 - if !node_count > max_nodes then 72 - Error.raise (Alias_expansion_node_limit max_nodes) 73 - in 74 - 75 - (* First pass: collect all anchors *) 76 - let rec collect (v : t) = 77 - match v with 78 - | `Scalar s -> 79 - (match Scalar.anchor s with 80 - | Some name -> Hashtbl.replace anchors name v 81 - | None -> ()) 82 - | `Alias _ -> () 83 - | `A seq -> 84 - (match Sequence.anchor seq with 85 - | Some name -> Hashtbl.replace anchors name v 86 - | None -> ()); 87 - List.iter collect (Sequence.members seq) 88 - | `O map -> 89 - (match Mapping.anchor map with 90 - | Some name -> Hashtbl.replace anchors name v 91 - | None -> ()); 92 - List.iter (fun (k, v) -> collect k; collect v) (Mapping.members map) 93 - in 94 - collect root; 95 - 96 - (* Second pass: resolve aliases with depth tracking *) 97 - let rec resolve ~depth (v : t) : t = 98 - check_node_limit (); 99 - match v with 100 - | `Scalar _ -> v 101 - | `Alias name -> 102 - if depth >= max_depth then 103 - Error.raise (Alias_expansion_depth_limit max_depth); 104 - (match Hashtbl.find_opt anchors name with 105 - | Some target -> resolve ~depth:(depth + 1) target 106 - | None -> Error.raise (Undefined_alias name)) 107 - | `A seq -> 108 - `A (Sequence.map (resolve ~depth) seq) 109 - | `O map -> 110 - `O (Mapping.make 111 - ?anchor:(Mapping.anchor map) 112 - ?tag:(Mapping.tag map) 113 - ~implicit:(Mapping.implicit map) 114 - ~style:(Mapping.style map) 115 - (List.map (fun (k, v) -> (resolve ~depth k, resolve ~depth v)) (Mapping.members map))) 116 - in 117 - resolve ~depth:0 root 118 - 119 - (** Convert scalar to JSON value based on content *) 120 - let rec scalar_to_value s = 121 - let value = Scalar.value s in 122 - let tag = Scalar.tag s in 123 - let style = Scalar.style s in 124 - 125 - (* If explicitly tagged, respect the tag *) 126 - match tag with 127 - | Some "tag:yaml.org,2002:null" | Some "!!null" -> 128 - `Null 129 - | Some "tag:yaml.org,2002:bool" | Some "!!bool" -> 130 - (match String.lowercase_ascii value with 131 - | "true" | "yes" | "on" -> `Bool true 132 - | "false" | "no" | "off" -> `Bool false 133 - | _ -> Error.raise (Invalid_scalar_conversion (value, "bool"))) 134 - | Some "tag:yaml.org,2002:int" | Some "!!int" -> 135 - (try `Float (Float.of_string value) 136 - with _ -> Error.raise (Invalid_scalar_conversion (value, "int"))) 137 - | Some "tag:yaml.org,2002:float" | Some "!!float" -> 138 - (try `Float (Float.of_string value) 139 - with _ -> Error.raise (Invalid_scalar_conversion (value, "float"))) 140 - | Some "tag:yaml.org,2002:str" | Some "!!str" -> 141 - `String value 142 - | Some _ -> 143 - (* Unknown tag - treat as string *) 144 - `String value 145 - | None -> 146 - (* Implicit type resolution for plain scalars *) 147 - if style <> Scalar_style.Plain then 148 - `String value 149 - else 150 - infer_scalar_type value 151 - 152 - (** Infer type from plain scalar value *) 153 - and infer_scalar_type value = 154 - let lower = String.lowercase_ascii value in 155 - (* Null *) 156 - if value = "" || lower = "null" || lower = "~" then 157 - `Null 158 - (* Boolean *) 159 - else if lower = "true" || lower = "yes" || lower = "on" then 160 - `Bool true 161 - else if lower = "false" || lower = "no" || lower = "off" then 162 - `Bool false 163 - (* Special floats *) 164 - else if lower = ".inf" || lower = "+.inf" then 165 - `Float Float.infinity 166 - else if lower = "-.inf" then 167 - `Float Float.neg_infinity 168 - else if lower = ".nan" then 169 - `Float Float.nan 170 - (* Try numeric *) 171 - else 172 - try_parse_number value 173 - 174 - (** Try to parse as number *) 175 - and try_parse_number value = 176 - (* Try integer first *) 177 - let try_int () = 178 - if String.length value > 0 then 179 - let first = value.[0] in 180 - if first = '-' || first = '+' || (first >= '0' && first <= '9') then 181 - try 182 - (* Handle octal: 0o prefix or leading 0 *) 183 - if String.length value > 2 && value.[0] = '0' then 184 - match value.[1] with 185 - | 'x' | 'X' -> 186 - (* Hex *) 187 - Some (`Float (Float.of_int (int_of_string value))) 188 - | 'o' | 'O' -> 189 - (* Octal *) 190 - Some (`Float (Float.of_int (int_of_string value))) 191 - | 'b' | 'B' -> 192 - (* Binary *) 193 - Some (`Float (Float.of_int (int_of_string value))) 194 - | _ -> 195 - (* Decimal with leading zero or octal in YAML 1.1 *) 196 - Some (`Float (Float.of_string value)) 197 - else 198 - Some (`Float (Float.of_string value)) 199 - with _ -> None 200 - else None 201 - else None 202 - in 203 - match try_int () with 204 - | Some v -> v 205 - | None -> 206 - (* Try float *) 207 - try 208 - let f = Float.of_string value in 209 - `Float f 210 - with _ -> 211 - (* Not a number - it's a string *) 212 - `String value 213 - 214 - (** Convert to JSON-compatible Value. 215 - 216 - @param resolve_aliases_first Whether to resolve aliases before conversion (default true) 217 - @param max_nodes Maximum nodes during alias expansion (default 10M) 218 - @param max_depth Maximum alias nesting depth (default 100) 219 - @raise Unresolved_alias if resolve_aliases_first is false and an alias is encountered 220 - *) 221 - let to_value 222 - ?(resolve_aliases_first = true) 223 - ?(max_nodes = default_max_alias_nodes) 224 - ?(max_depth = default_max_alias_depth) 225 - (v : t) : Value.t = 226 - let v = if resolve_aliases_first then resolve_aliases ~max_nodes ~max_depth v else v in 227 - let rec convert (v : t) : Value.t = 228 - match v with 229 - | `Scalar s -> scalar_to_value s 230 - | `Alias name -> Error.raise (Unresolved_alias name) 231 - | `A seq -> `A (List.map convert (Sequence.members seq)) 232 - | `O map -> 233 - `O (List.map (fun (k, v) -> 234 - let key = match k with 235 - | `Scalar s -> Scalar.value s 236 - | _ -> Error.raise (Type_mismatch ("string key", "complex key")) 237 - in 238 - (key, convert v) 239 - ) (Mapping.members map)) 240 - in 241 - convert v 242 - 243 - (** Get anchor from any node *) 244 - let anchor (v : t) = 245 - match v with 246 - | `Scalar s -> Scalar.anchor s 247 - | `Alias _ -> None 248 - | `A seq -> Sequence.anchor seq 249 - | `O map -> Mapping.anchor map 250 - 251 - (** Get tag from any node *) 252 - let tag (v : t) = 253 - match v with 254 - | `Scalar s -> Scalar.tag s 255 - | `Alias _ -> None 256 - | `A seq -> Sequence.tag seq 257 - | `O map -> Mapping.tag map
-185
yaml/ocaml-yamle/lib/yamle.ml
··· 1 - type value = Value.t 2 - type yaml = Yaml.t 3 - 4 - type version = [ `V1_1 | `V1_2 ] 5 - 6 - type encoding = Encoding.t 7 - type scalar_style = Scalar_style.t 8 - type layout_style = Layout_style.t 9 - 10 - (** {1 Error handling} *) 11 - 12 - type error = Error.t 13 - exception Yamle_error = Error.Yamle_error 14 - 15 - (** {1 Alias expansion limits (protection against billion laughs attack)} *) 16 - 17 - let default_max_alias_nodes = Yaml.default_max_alias_nodes 18 - let default_max_alias_depth = Yaml.default_max_alias_depth 19 - 20 - (** {1 JSON-compatible parsing} *) 21 - 22 - let of_string 23 - ?(resolve_aliases = true) 24 - ?(max_nodes = default_max_alias_nodes) 25 - ?(max_depth = default_max_alias_depth) 26 - s = 27 - Loader.value_of_string ~resolve_aliases ~max_nodes ~max_depth s 28 - 29 - let documents_of_string s = Loader.documents_of_string s 30 - 31 - (** {1 JSON-compatible emission} *) 32 - 33 - let to_string 34 - ?(encoding = Encoding.Utf8) 35 - ?(scalar_style = Scalar_style.Any) 36 - ?(layout_style = Layout_style.Any) 37 - value = 38 - let config = { 39 - Emitter.default_config with 40 - encoding; 41 - scalar_style; 42 - layout_style; 43 - } in 44 - Emitter.value_to_string ~config value 45 - 46 - (** {1 YAML-specific parsing} *) 47 - 48 - let yaml_of_string 49 - ?(resolve_aliases = false) 50 - ?(max_nodes = default_max_alias_nodes) 51 - ?(max_depth = default_max_alias_depth) 52 - s = 53 - Loader.yaml_of_string ~resolve_aliases ~max_nodes ~max_depth s 54 - 55 - (** {1 YAML-specific emission} *) 56 - 57 - let yaml_to_string 58 - ?(encoding = Encoding.Utf8) 59 - ?(scalar_style = Scalar_style.Any) 60 - ?(layout_style = Layout_style.Any) 61 - yaml = 62 - let config = { 63 - Emitter.default_config with 64 - encoding; 65 - scalar_style; 66 - layout_style; 67 - } in 68 - Emitter.yaml_to_string ~config yaml 69 - 70 - let documents_to_string 71 - ?(encoding = Encoding.Utf8) 72 - ?(scalar_style = Scalar_style.Any) 73 - ?(layout_style = Layout_style.Any) 74 - ?(resolve_aliases = true) 75 - documents = 76 - let config = { 77 - Emitter.default_config with 78 - encoding; 79 - scalar_style; 80 - layout_style; 81 - } in 82 - Emitter.documents_to_string ~config ~resolve_aliases documents 83 - 84 - (** {1 Conversion} *) 85 - 86 - let to_json 87 - ?(resolve_aliases = true) 88 - ?(max_nodes = default_max_alias_nodes) 89 - ?(max_depth = default_max_alias_depth) 90 - yaml = 91 - Yaml.to_value ~resolve_aliases_first:resolve_aliases ~max_nodes ~max_depth yaml 92 - 93 - let of_json value = Yaml.of_value value 94 - 95 - (** {1 Pretty printing} *) 96 - 97 - let pp = Value.pp 98 - let pp_yaml = Yaml.pp 99 - let equal = Value.equal 100 - let equal_yaml = Yaml.equal 101 - 102 - (** {1 Nested modules} *) 103 - 104 - module Error = Error 105 - module Position = Position 106 - module Span = Span 107 - module Encoding = Encoding 108 - module Input = Input 109 - module Scalar_style = Scalar_style 110 - module Layout_style = Layout_style 111 - module Chomping = Chomping 112 - module Token = Token 113 - module Scanner = Scanner 114 - module Event = Event 115 - module Parser = Parser 116 - module Tag = Tag 117 - module Value = Value 118 - module Scalar = Scalar 119 - module Sequence = Sequence 120 - module Mapping = Mapping 121 - module Yaml = Yaml 122 - module Document = Document 123 - module Loader = Loader 124 - module Emitter = Emitter 125 - 126 - (** {1 Streaming interface} *) 127 - 128 - module Stream = struct 129 - type parser = Parser.t 130 - type emitter = Emitter.t 131 - 132 - let parser s = Parser.of_string s 133 - 134 - let do_parse p = Parser.next p 135 - 136 - let emitter ?len:_ () = Emitter.create () 137 - 138 - let emit e ev = Emitter.emit e ev 139 - 140 - let emitter_buf e = Emitter.contents e 141 - 142 - (** Convenience event emitters *) 143 - 144 - let stream_start e enc = 145 - Emitter.emit e (Event.Stream_start { encoding = enc }) 146 - 147 - let stream_end e = 148 - Emitter.emit e Event.Stream_end 149 - 150 - let document_start ?version ?(implicit = true) e = 151 - let version = match version with 152 - | Some `V1_1 -> Some (1, 1) 153 - | Some `V1_2 -> Some (1, 2) 154 - | None -> None 155 - in 156 - Emitter.emit e (Event.Document_start { version; implicit }) 157 - 158 - let document_end ?(implicit = true) e = 159 - Emitter.emit e (Event.Document_end { implicit }) 160 - 161 - let scalar s e = 162 - Emitter.emit e (Event.Scalar { 163 - anchor = Scalar.anchor s; 164 - tag = Scalar.tag s; 165 - value = Scalar.value s; 166 - plain_implicit = Scalar.plain_implicit s; 167 - quoted_implicit = Scalar.quoted_implicit s; 168 - style = Scalar.style s; 169 - }) 170 - 171 - let alias e name = 172 - Emitter.emit e (Event.Alias { anchor = name }) 173 - 174 - let sequence_start ?anchor ?tag ?(implicit = true) ?(style = Layout_style.Any) e = 175 - Emitter.emit e (Event.Sequence_start { anchor; tag; implicit; style }) 176 - 177 - let sequence_end e = 178 - Emitter.emit e Event.Sequence_end 179 - 180 - let mapping_start ?anchor ?tag ?(implicit = true) ?(style = Layout_style.Any) e = 181 - Emitter.emit e (Event.Mapping_start { anchor; tag; implicit; style }) 182 - 183 - let mapping_end e = 184 - Emitter.emit e Event.Mapping_end 185 - end
-375
yaml/ocaml-yamle/tests/cram/anchors.t
··· 1 - Anchor and Alias Support (currently not supported) 2 - 3 - These tests document anchor (&) and alias (*) support that is not yet 4 - implemented. Currently, aliases fail with "unresolved alias" error. 5 - 6 - Test: Simple scalar anchor and alias 7 - 8 - $ echo 'anchor: &anc value 9 - > alias: *anc' | yamlcat 2>&1 10 - anchor: value 11 - alias: value 12 - 13 - Test: Numeric anchor and alias 14 - 15 - $ echo 'original: &num 42 16 - > copy: *num' | yamlcat 2>&1 17 - original: 42 18 - copy: 42 19 - 20 - Test: Sequence anchor and alias 21 - 22 - $ echo 'list: &items 23 - > - one 24 - > - two 25 - > copy: *items' | yamlcat 2>&1 26 - list: 27 - - one 28 - - two 29 - copy: 30 - - one 31 - - two 32 - 33 - Test: Mapping anchor and alias 34 - 35 - $ echo 'person: &p 36 - > name: Alice 37 - > age: 30 38 - > copy: *p' | yamlcat 2>&1 39 - person: 40 - name: Alice 41 - age: 30 42 - copy: 43 - name: Alice 44 - age: 30 45 - 46 - Test: Multiple aliases to same anchor 47 - 48 - $ echo 'value: &v 100 49 - > first: *v 50 - > second: *v 51 - > third: *v' | yamlcat 2>&1 52 - value: 100 53 - first: 100 54 - second: 100 55 - third: 100 56 - 57 - Test: Anchor in flow context 58 - 59 - $ echo '[&item apple, *item]' | yamlcat 2>&1 60 - - apple 61 - - apple 62 - 63 - Test: Anchor with mapping in flow 64 - 65 - $ echo '{original: &cfg {a: 1}, copy: *cfg}' | yamlcat 2>&1 66 - original: 67 - a: 1 68 - copy: 69 - a: 1 70 - 71 - Test: Anchors file from test suite 72 - 73 - $ yamlcat ../yaml/anchors_basic.yml 2>&1 74 - scalar_anchor: Hello, World! 75 - scalar_alias: Hello, World! 76 - --- 77 - original: 42 78 - copy: 42 79 - another_copy: 42 80 - --- 81 - original_list: 82 - - apple 83 - - banana 84 - - cherry 85 - copied_list: 86 - - apple 87 - - banana 88 - - cherry 89 - --- 90 - original_map: 91 - name: Alice 92 - age: 30 93 - city: London 94 - copied_map: 95 - name: Alice 96 - age: 30 97 - city: London 98 - --- 99 - defaults: 100 - timeout: 30 101 - retries: 3 102 - colors: 103 - - red 104 - - green 105 - - blue 106 - config: 107 - settings: 108 - timeout: 30 109 - retries: 3 110 - palette: 111 - - red 112 - - green 113 - - blue 114 - --- 115 - template: 116 - metadata: 117 - version: 1 118 - author: John Doe 119 - settings: 120 - enabled: true 121 - debug: false 122 - instance1: 123 - metadata: 124 - version: 1 125 - author: John Doe 126 - settings: 127 - enabled: true 128 - debug: false 129 - instance2: 130 - metadata: 131 - version: 1 132 - author: John Doe 133 - settings: 134 - enabled: true 135 - debug: false 136 - --- 137 - items: 138 - - id: 1 139 - name: First 140 - - id: 2 141 - name: Second 142 - - id: 1 143 - name: First 144 - --- 145 - shared_value: 100 146 - calculations: 147 - base: 100 148 - doubled: 200 149 - reference: 100 150 - another_ref: 100 151 - --- 152 - feature_flag: true 153 - features: 154 - login: true 155 - signup: true 156 - export: true 157 - --- 158 - empty: null 159 - values: 160 - first: null 161 - second: null 162 - --- 163 - message: "This is a multi-line\nmessage with some\nspecial content!\n" 164 - output1: "This is a multi-line\nmessage with some\nspecial content!\n" 165 - output2: "This is a multi-line\nmessage with some\nspecial content!\n" 166 - --- 167 - database: 168 - primary: 169 - host: localhost 170 - port: 5432 171 - ssl: true 172 - replica: 173 - host: localhost 174 - port: 5432 175 - ssl: true 176 - backup: 177 - host: localhost 178 - port: 5432 179 - ssl: true 180 - 181 - $ yamlcat ../yaml/anchors_merge.yml 2>&1 182 - defaults: 183 - timeout: 30 184 - retries: 3 185 - verbose: false 186 - production: 187 - <<: 188 - timeout: 30 189 - retries: 3 190 - verbose: false 191 - environment: production 192 - --- 193 - base: 194 - color: red 195 - size: medium 196 - weight: 100 197 - custom: 198 - <<: 199 - color: red 200 - size: medium 201 - weight: 100 202 - color: blue 203 - shape: circle 204 - --- 205 - connection: 206 - host: localhost 207 - port: 8080 208 - authentication: 209 - username: admin 210 - password: secret 211 - server: 212 - <<: 213 - - host: localhost 214 - port: 8080 215 - - username: admin 216 - password: secret 217 - ssl: true 218 - --- 219 - defaults: 220 - timeout: 30 221 - retries: 3 222 - advanced: 223 - cache: true 224 - pool_size: 10 225 - config: 226 - <<: 227 - - timeout: 30 228 - retries: 3 229 - - cache: true 230 - pool_size: 10 231 - timeout: 60 232 - custom: value 233 - --- 234 - base_style: 235 - font: Arial 236 - size: 12 237 - heading_defaults: 238 - <<: 239 - font: Arial 240 - size: 12 241 - weight: bold 242 - main_heading: 243 - <<: 244 - <<: 245 - font: Arial 246 - size: 12 247 - weight: bold 248 - size: 18 249 - color: navy 250 - --- 251 - common: 252 - enabled: true 253 - log_level: info 254 - services: 255 - - name: web 256 - <<: 257 - enabled: true 258 - log_level: info 259 - port: 80 260 - - name: api 261 - <<: 262 - enabled: true 263 - log_level: info 264 - port: 3000 265 - - name: worker 266 - <<: 267 - enabled: true 268 - log_level: info 269 - threads: 4 270 - --- 271 - empty: {} 272 - config: 273 - <<: {} 274 - key: value 275 - --- 276 - metadata: 277 - created: 2023-01-01 278 - author: Admin 279 - tags: 280 - - v1 281 - - stable 282 - document: 283 - <<: 284 - created: 2023-01-01 285 - author: Admin 286 - tags: 287 - - v1 288 - - stable 289 - title: Important Document 290 - content: Some content here 291 - --- 292 - level1: 293 - a: 1 294 - b: 2 295 - level2: 296 - <<: 297 - a: 1 298 - b: 2 299 - c: 3 300 - level3: 301 - <<: 302 - <<: 303 - a: 1 304 - b: 2 305 - c: 3 306 - d: 4 307 - --- 308 - first: 309 - name: First 310 - value: 100 311 - priority: low 312 - second: 313 - name: Second 314 - value: 200 315 - category: important 316 - combined: 317 - <<: 318 - - name: First 319 - value: 100 320 - priority: low 321 - - name: Second 322 - value: 200 323 - category: important 324 - name: Combined 325 - --- 326 - numbers: 327 - count: 42 328 - ratio: 3.14 329 - active: true 330 - derived: 331 - <<: 332 - count: 42 333 - ratio: 3.14 334 - active: true 335 - label: Test 336 - --- 337 - db_defaults: 338 - pool_size: 5 339 - timeout: 30 340 - ssl: false 341 - cache_defaults: 342 - ttl: 3600 343 - max_size: 1000 344 - development: 345 - database: 346 - <<: 347 - pool_size: 5 348 - timeout: 30 349 - ssl: false 350 - host: localhost 351 - name: dev_db 352 - cache: 353 - <<: 354 - ttl: 3600 355 - max_size: 1000 356 - backend: memory 357 - production: 358 - database: 359 - <<: 360 - pool_size: 5 361 - timeout: 30 362 - ssl: false 363 - host: prod.example.com 364 - name: prod_db 365 - ssl: true 366 - pool_size: 20 367 - cache: 368 - <<: 369 - ttl: 3600 370 - max_size: 1000 371 - backend: redis 372 - ttl: 7200 373 - 374 - Note: The anchor test files also use multiple documents, so they fail 375 - with multi-document errors before even hitting anchor issues.
-119
yaml/ocaml-yamle/tests/cram/bomb.t
··· 1 - Billion laughs attack protection tests 2 - 3 - Create a small bomb file for testing: 4 - 5 - $ cat > bomb_small.yml << 'EOF' 6 - > # Small "billion laughs" style YAML bomb for testing 7 - > # Expands to 9^4 = 6561 nodes when aliases are resolved 8 - > a: &a [1, 2, 3, 4, 5, 6, 7, 8, 9] 9 - > b: &b [*a, *a, *a, *a, *a, *a, *a, *a, *a] 10 - > c: &c [*b, *b, *b, *b, *b, *b, *b, *b, *b] 11 - > d: &d [*c, *c, *c, *c, *c, *c, *c, *c, *c] 12 - > EOF 13 - 14 - Test with a tight node limit (small bomb would expand to ~6561 nodes): 15 - 16 - $ yamlcat --max-nodes 100 --json bomb_small.yml 17 - Error: alias expansion exceeded node limit (100 nodes) 18 - [1] 19 - 20 - Test with a limit that allows the small bomb: 21 - 22 - $ yamlcat --max-nodes 10000 --json bomb_small.yml | head -c 100 23 - {"a": [1, 2, 3, 4, 5, 6, 7, 8, 9], "b": [[1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9], [ 24 - 25 - Test depth limit with a nested alias chain: 26 - 27 - $ cat > depth_bomb.yml << 'EOF' 28 - > a: &a [x, y, z] 29 - > b: &b [*a, *a] 30 - > c: &c [*b, *b] 31 - > d: &d [*c, *c] 32 - > e: &e [*d, *d] 33 - > result: *e 34 - > EOF 35 - 36 - $ yamlcat --max-depth 2 --json depth_bomb.yml 37 - Error: alias expansion exceeded depth limit (2 levels) 38 - [1] 39 - 40 - $ yamlcat --max-depth 10 --json depth_bomb.yml | head -c 50 41 - {"a": ["x", "y", "z"], "b": [["x", "y", "z"], ["x" 42 - 43 - Test that --no-resolve-aliases keeps aliases as-is (in debug mode): 44 - 45 - $ cat > simple_alias.yml << 'EOF' 46 - > anchor: &anc hello 47 - > alias: *anc 48 - > EOF 49 - 50 - $ yamlcat --no-resolve-aliases --debug simple_alias.yml 51 - Document 1: 52 - document( 53 - implicit_start=true, 54 - implicit_end=true, 55 - root=mapping( 56 - style=block, 57 - members={ 58 - scalar("anchor", style=plain): 59 - scalar("hello", anchor=anc, style=plain), 60 - scalar("alias", style=plain): *anc 61 - }) 62 - ) 63 - 64 - With resolve (default), aliases are expanded: 65 - 66 - $ yamlcat --json simple_alias.yml 67 - {"anchor": "hello", "alias": "hello"} 68 - 69 - Create a full bomb (like the one in ocaml-yaml): 70 - 71 - $ cat > bomb.yml << 'EOF' 72 - > a: &a ["lol","lol","lol","lol","lol","lol","lol","lol","lol"] 73 - > b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a] 74 - > c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b] 75 - > d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c] 76 - > e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d] 77 - > f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e] 78 - > g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f] 79 - > h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g] 80 - > i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h] 81 - > EOF 82 - 83 - Test the full bomb is rejected with default limits: 84 - 85 - $ yamlcat --json bomb.yml 2>&1 | head -1 86 - Error: alias expansion exceeded node limit (10000000 nodes) 87 - 88 - With a very small limit: 89 - 90 - $ yamlcat --max-nodes 50 --json bomb.yml 91 - Error: alias expansion exceeded node limit (50 nodes) 92 - [1] 93 - 94 - Test that valid YAML with aliases works: 95 - 96 - $ cat > valid.yml << 'EOF' 97 - > defaults: &defaults 98 - > timeout: 30 99 - > retries: 3 100 - > production: 101 - > <<: *defaults 102 - > port: 8080 103 - > EOF 104 - 105 - $ yamlcat --json valid.yml 106 - {"defaults": {"timeout": 30, "retries": 3}, "production": {"<<": {"timeout": 30, "retries": 3}, "port": 8080}} 107 - 108 - Test help includes the new options: 109 - 110 - $ yamlcat --help=plain | grep 'max-nodes' 111 - --max-nodes=N (absent=10000000) 112 - yamlcat --max-nodes 1000 --max-depth 10 untrusted.yaml 113 - 114 - $ yamlcat --help=plain | grep 'max-depth' 115 - --max-depth=N (absent=100) 116 - yamlcat --max-nodes 1000 --max-depth 10 untrusted.yaml 117 - 118 - $ yamlcat --help=plain | grep 'no-resolve-aliases' 119 - --no-resolve-aliases
-6
yaml/ocaml-yamle/tests/cram/bomb_small.yml
··· 1 - # Small "billion laughs" style YAML bomb for testing 2 - # Expands to 9^4 = 6561 nodes when aliases are resolved 3 - a: &a [1, 2, 3, 4, 5, 6, 7, 8, 9] 4 - b: &b [*a, *a, *a, *a, *a, *a, *a, *a, *a] 5 - c: &c [*b, *b, *b, *b, *b, *b, *b, *b, *b] 6 - d: &d [*c, *c, *c, *c, *c, *c, *c, *c, *c]
-863
yaml/ocaml-yamle/tests/cram/collections.t
··· 1 - Test collections_block.yml - Block style collections 2 - 3 - $ yamlcat ../yaml/collections_block.yml 4 - simple_sequence: 5 - - apple 6 - - banana 7 - - cherry 8 - - date 9 - simple_mapping: 10 - name: John Doe 11 - age: 30 12 - city: New York 13 - country: USA 14 - nested_sequences: 15 - - 16 - - alpha 17 - - beta 18 - - gamma 19 - - 20 - - one 21 - - two 22 - - three 23 - - 24 - - red 25 - - green 26 - - blue 27 - nested_mappings: 28 - person: 29 - name: Alice 30 - contact: 31 - email: alice@example.com 32 - phone: 555-1234 33 - address: 34 - street: 123 Main St 35 - city: Boston 36 - mapping_with_sequences: 37 - colors: 38 - - red 39 - - green 40 - - blue 41 - sizes: 42 - - small 43 - - medium 44 - - large 45 - numbers: 46 - - 1 47 - - 2 48 - - 3 49 - sequence_with_mappings: 50 - - name: Alice 51 - age: 25 52 - role: developer 53 - - name: Bob 54 - age: 30 55 - role: designer 56 - - name: Charlie 57 - age: 35 58 - role: manager 59 - deep_nesting: 60 - level1: 61 - level2: 62 - level3: 63 - level4: 64 - - deeply 65 - - nested 66 - - values 67 - another_key: value 68 - items: 69 - - item1 70 - - item2 71 - metadata: 72 - created: 2024-01-01 73 - modified: 2024-12-04 74 - complex_structure: 75 - database: 76 - connections: 77 - - host: db1.example.com 78 - port: 5432 79 - credentials: 80 - username: admin 81 - password: secret 82 - - host: db2.example.com 83 - port: 5432 84 - credentials: 85 - username: readonly 86 - password: public 87 - services: 88 - - name: api 89 - endpoints: 90 - - /users 91 - - /posts 92 - - /comments 93 - config: 94 - timeout: 30 95 - retries: 3 96 - - name: worker 97 - tasks: 98 - - email 99 - - reports 100 - config: 101 - concurrency: 10 102 - empty_collections: 103 - empty_sequence: [] 104 - empty_mapping: {} 105 - sequence_with_empty: 106 - - value1 107 - - [] 108 - - value2 109 - mapping_with_empty: 110 - key1: value1 111 - key2: {} 112 - key3: value3 113 - 114 - Test collections_block.yml with JSON output 115 - 116 - $ yamlcat --json ../yaml/collections_block.yml 117 - {"simple_sequence": ["apple", "banana", "cherry", "date"], "simple_mapping": {"name": "John Doe", "age": 30, "city": "New York", "country": "USA"}, "nested_sequences": [["alpha", "beta", "gamma"], ["one", "two", "three"], ["red", "green", "blue"]], "nested_mappings": {"person": {"name": "Alice", "contact": {"email": "alice@example.com", "phone": "555-1234"}, "address": {"street": "123 Main St", "city": "Boston"}}}, "mapping_with_sequences": {"colors": ["red", "green", "blue"], "sizes": ["small", "medium", "large"], "numbers": [1, 2, 3]}, "sequence_with_mappings": [{"name": "Alice", "age": 25, "role": "developer"}, {"name": "Bob", "age": 30, "role": "designer"}, {"name": "Charlie", "age": 35, "role": "manager"}], "deep_nesting": {"level1": {"level2": {"level3": {"level4": ["deeply", "nested", "values"], "another_key": "value"}, "items": ["item1", "item2"]}, "metadata": {"created": "2024-01-01", "modified": "2024-12-04"}}}, "complex_structure": {"database": {"connections": [{"host": "db1.example.com", "port": 5432, "credentials": {"username": "admin", "password": "secret"}}, {"host": "db2.example.com", "port": 5432, "credentials": {"username": "readonly", "password": "public"}}]}, "services": [{"name": "api", "endpoints": ["/users", "/posts", "/comments"], "config": {"timeout": 30, "retries": 3}}, {"name": "worker", "tasks": ["email", "reports"], "config": {"concurrency": 10}}]}, "empty_collections": {"empty_sequence": [], "empty_mapping": {}, "sequence_with_empty": ["value1", [], "value2"], "mapping_with_empty": {"key1": "value1", "key2": {}, "key3": "value3"}}} 118 - 119 - Test collections_block.yml with flow output 120 - 121 - $ yamlcat --flow ../yaml/collections_block.yml 122 - {simple_sequence: [apple, banana, cherry, date]simple_mapping, {name: John Doe, age: 30, city: New York, country: USA}, nested_sequences: [[alpha, beta, gamma], [one, two, three], [red, green, blue]]nested_mappings, {person: {name: Alice, contact: {email: alice@example.com, phone: 555-1234}address, {street: 123 Main St, city: Boston}}}, mapping_with_sequences: {colors: [red, green, blue]sizes, [small, medium, large], numbers: [1, 2, 3]}sequence_with_mappings, [{name: Alice, age: 25, role: developer}, {name: Bob, age: 30, role: designer}, {name: Charlie, age: 35, role: manager}], deep_nesting: {level1: {level2: {level3: {level4: [deeply, nested, values]another_key, value: }items, [item1, item2]}metadata, {created: 2024-01-01, modified: 2024-12-04}}}complex_structure, {database: {connections: [{host: db1.example.com, port: 5432, credentials: {username: admin, password: secret}}, {host: db2.example.com, port: 5432, credentials: {username: readonly, password: public}}]}services, [{name: api, endpoints: [/users, /posts, /comments]config, {timeout: 30, retries: 3}}, {name: worker, tasks: [email, reports]config, {concurrency: 10}}]}, empty_collections: {empty_sequence: []empty_mapping, {}, sequence_with_empty: [value1, [], value2]mapping_with_empty, {key1: value1, key2: {}key3, value3: }}} 123 - 124 - Test collections_compact.yml - Compact notation 125 - 126 - $ yamlcat ../yaml/collections_compact.yml 127 - compact_sequence: 128 - - name: Alice 129 - age: 25 130 - city: Boston 131 - - name: Bob 132 - age: 30 133 - city: Seattle 134 - - name: Charlie 135 - age: 35 136 - city: Portland 137 - compact_nested: 138 - - id: 1 139 - details: 140 - type: admin 141 - permissions: 142 - - read 143 - - write 144 - - delete 145 - - id: 2 146 - details: 147 - type: user 148 - permissions: 149 - - read 150 - compact_complex: 151 - - key1: value1 152 - key2: value2 153 - nested: 154 - sub1: val1 155 - sub2: val2 156 - - key1: value3 157 - key2: value4 158 - nested: 159 - sub1: val3 160 - sub2: val4 161 - users: 162 - - username: alice 163 - email: alice@example.com 164 - active: true 165 - - username: bob 166 - email: bob@example.com 167 - active: false 168 - compact_with_flow: 169 - - name: service1 170 - ports: 171 - - 8080 172 - - 8443 173 - env: 174 - DEBUG: true 175 - MODE: production 176 - - name: service2 177 - ports: 178 - - 3000 179 - env: 180 - DEBUG: false 181 - MODE: development 182 - deep_compact: 183 - - category: electronics 184 - items: 185 - - name: laptop 186 - specs: 187 - cpu: Intel i7 188 - ram: 16GB 189 - storage: 512GB SSD 190 - - name: phone 191 - specs: 192 - os: Android 193 - ram: 8GB 194 - storage: 256GB 195 - - category: furniture 196 - items: 197 - - name: desk 198 - dimensions: 199 - width: 150cm 200 - depth: 75cm 201 - height: 75cm 202 - - name: chair 203 - dimensions: 204 - width: 60cm 205 - depth: 60cm 206 - height: 120cm 207 - mixed_compact: 208 - databases: 209 - - type: postgresql 210 - connection: 211 - host: localhost 212 - port: 5432 213 - credentials: 214 - user: admin 215 - password: secret 216 - - type: mongodb 217 - connection: 218 - host: localhost 219 - port: 27017 220 - credentials: 221 - user: root 222 - password: root 223 - single_line_compact: 224 - - name: Alice 225 - age: 25 226 - role: developer 227 - - name: Bob 228 - age: 30 229 - role: designer 230 - - name: Charlie 231 - age: 35 232 - role: manager 233 - sequences_in_compact: 234 - - title: Project A 235 - members: 236 - - Alice 237 - - Bob 238 - - Charlie 239 - tags: 240 - - urgent 241 - - backend 242 - - title: Project B 243 - members: 244 - - David 245 - - Eve 246 - tags: 247 - - frontend 248 - - design 249 - compact_with_empty: 250 - - id: 1 251 - data: [] 252 - meta: {} 253 - - id: 2 254 - data: 255 - - item1 256 - meta: 257 - key: value 258 - compact_complex_nesting: 259 - - level: 1 260 - children: 261 - - level: 2a 262 - children: 263 - - level: 3a 264 - value: leaf1 265 - - level: 3b 266 - value: leaf2 267 - - level: 2b 268 - children: 269 - - level: 3c 270 - value: leaf3 271 - api_endpoints: 272 - - path: /users 273 - method: GET 274 - auth: required 275 - params: 276 - - name: page 277 - type: integer 278 - default: 1 279 - - name: limit 280 - type: integer 281 - default: 10 282 - - path: '/users/:id' 283 - method: GET 284 - auth: required 285 - params: [] 286 - - path: /users 287 - method: POST 288 - auth: required 289 - body: 290 - username: string 291 - email: string 292 - password: string 293 - compact_types: 294 - - string_val: hello 295 - number_val: 42 296 - float_val: 3.14 297 - bool_val: true 298 - null_val: null 299 - - string_val: world 300 - number_val: 100 301 - float_val: 2.71 302 - bool_val: false 303 - null_val: null 304 - minimal: 305 - - a: 1 306 - - b: 2 307 - - c: 3 308 - 309 - Test collections_compact.yml with JSON output 310 - 311 - $ yamlcat --json ../yaml/collections_compact.yml 312 - {"compact_sequence": [{"name": "Alice", "age": 25, "city": "Boston"}, {"name": "Bob", "age": 30, "city": "Seattle"}, {"name": "Charlie", "age": 35, "city": "Portland"}], "compact_nested": [{"id": 1, "details": {"type": "admin", "permissions": ["read", "write", "delete"]}}, {"id": 2, "details": {"type": "user", "permissions": ["read"]}}], "compact_complex": [{"key1": "value1", "key2": "value2", "nested": {"sub1": "val1", "sub2": "val2"}}, {"key1": "value3", "key2": "value4", "nested": {"sub1": "val3", "sub2": "val4"}}], "users": [{"username": "alice", "email": "alice@example.com", "active": true}, {"username": "bob", "email": "bob@example.com", "active": false}], "compact_with_flow": [{"name": "service1", "ports": [8080, 8443], "env": {"DEBUG": true, "MODE": "production"}}, {"name": "service2", "ports": [3000], "env": {"DEBUG": false, "MODE": "development"}}], "deep_compact": [{"category": "electronics", "items": [{"name": "laptop", "specs": {"cpu": "Intel i7", "ram": "16GB", "storage": "512GB SSD"}}, {"name": "phone", "specs": {"os": "Android", "ram": "8GB", "storage": "256GB"}}]}, {"category": "furniture", "items": [{"name": "desk", "dimensions": {"width": "150cm", "depth": "75cm", "height": "75cm"}}, {"name": "chair", "dimensions": {"width": "60cm", "depth": "60cm", "height": "120cm"}}]}], "mixed_compact": {"databases": [{"type": "postgresql", "connection": {"host": "localhost", "port": 5432}, "credentials": {"user": "admin", "password": "secret"}}, {"type": "mongodb", "connection": {"host": "localhost", "port": 27017}, "credentials": {"user": "root", "password": "root"}}]}, "single_line_compact": [{"name": "Alice", "age": 25, "role": "developer"}, {"name": "Bob", "age": 30, "role": "designer"}, {"name": "Charlie", "age": 35, "role": "manager"}], "sequences_in_compact": [{"title": "Project A", "members": ["Alice", "Bob", "Charlie"], "tags": ["urgent", "backend"]}, {"title": "Project B", "members": ["David", "Eve"], "tags": ["frontend", "design"]}], "compact_with_empty": [{"id": 1, "data": [], "meta": {}}, {"id": 2, "data": ["item1"], "meta": {"key": "value"}}], "compact_complex_nesting": [{"level": 1, "children": [{"level": "2a", "children": [{"level": "3a", "value": "leaf1"}, {"level": "3b", "value": "leaf2"}]}, {"level": "2b", "children": [{"level": "3c", "value": "leaf3"}]}]}], "api_endpoints": [{"path": "/users", "method": "GET", "auth": "required", "params": [{"name": "page", "type": "integer", "default": 1}, {"name": "limit", "type": "integer", "default": 10}]}, {"path": "/users/:id", "method": "GET", "auth": "required", "params": []}, {"path": "/users", "method": "POST", "auth": "required", "body": {"username": "string", "email": "string", "password": "string"}}], "compact_types": [{"string_val": "hello", "number_val": 42, "float_val": 3.14, "bool_val": true, "null_val": null}, {"string_val": "world", "number_val": 100, "float_val": 2.71, "bool_val": false, "null_val": null}], "minimal": [{"a": 1}, {"b": 2}, {"c": 3}]} 313 - 314 - Test collections_compact.yml with flow output 315 - 316 - $ yamlcat --flow ../yaml/collections_compact.yml 317 - {compact_sequence: [{name: Alice, age: 25, city: Boston}, {name: Bob, age: 30, city: Seattle}, {name: Charlie, age: 35, city: Portland}]compact_nested, [{id: 1, details: {type: admin, permissions: [read, write, delete]}}, {id: 2, details: {type: user, permissions: [read]}}], compact_complex: [{key1: value1, key2: value2, nested: {sub1: val1, sub2: val2}}, {key1: value3, key2: value4, nested: {sub1: val3, sub2: val4}}]users, [{username: alice, email: alice@example.com, active: true}, {username: bob, email: bob@example.com, active: false}], compact_with_flow: [{name: service1, ports: [8080, 8443]env, {DEBUG: true, MODE: production}}, {name: service2, ports: [3000]env, {DEBUG: false, MODE: development}}]deep_compact, [{category: electronics, items: [{name: laptop, specs: {cpu: Intel i7, ram: 16GB, storage: 512GB SSD}}, {name: phone, specs: {os: Android, ram: 8GB, storage: 256GB}}]}, {category: furniture, items: [{name: desk, dimensions: {width: 150cm, depth: 75cm, height: 75cm}}, {name: chair, dimensions: {width: 60cm, depth: 60cm, height: 120cm}}]}], mixed_compact: {databases: [{type: postgresql, connection: {host: localhost, port: 5432}credentials, {user: admin, password: secret}}, {type: mongodb, connection: {host: localhost, port: 27017}credentials, {user: root, password: root}}]}single_line_compact, [{name: Alice, age: 25, role: developer}, {name: Bob, age: 30, role: designer}, {name: Charlie, age: 35, role: manager}], sequences_in_compact: [{title: Project A, members: [Alice, Bob, Charlie]tags, [urgent, backend]}, {title: Project B, members: [David, Eve]tags, [frontend, design]}]compact_with_empty, [{id: 1, data: []meta, {}}, {id: 2, data: [item1]meta, {key: value}}], compact_complex_nesting: [{level: 1, children: [{level: 2a, children: [{level: 3a, value: leaf1}, {level: 3b, value: leaf2}]}, {level: 2b, children: [{level: 3c, value: leaf3}]}]}]api_endpoints, [{path: /users, method: GET, auth: required, params: [{name: page, type: integer, default: 1}, {name: limit, type: integer, default: 10}]}, {path: '/users/:id', method: GET, auth: required, params: []}, {path: /users, method: POST, auth: required, body: {username: string, email: string, password: string}}], compact_types: [{string_val: hello, number_val: 42, float_val: 3.14, bool_val: true, null_val: null}, {string_val: world, number_val: 100, float_val: 2.71, bool_val: false, null_val: null}]minimal, [{a: 1}, {b: 2}, {c: 3}]} 318 - 319 - Test collections_flow.yml - Flow style collections 320 - 321 - $ yamlcat ../yaml/collections_flow.yml 322 - simple_flow_sequence: 323 - - apple 324 - - banana 325 - - cherry 326 - - date 327 - simple_flow_mapping: 328 - name: John 329 - age: 30 330 - city: New York 331 - nested_flow_sequences: 332 - - 333 - - a 334 - - b 335 - - c 336 - - 337 - - 1 338 - - 2 339 - - 3 340 - - 341 - - red 342 - - green 343 - - blue 344 - nested_flow_mappings: 345 - person: 346 - name: Alice 347 - age: 25 348 - contact: 349 - email: alice@example.com 350 - phone: 555-1234 351 - flow_seq_with_maps: 352 - - name: Alice 353 - role: dev 354 - - name: Bob 355 - role: ops 356 - - name: Charlie 357 - role: qa 358 - flow_map_with_seqs: 359 - colors: 360 - - red 361 - - green 362 - - blue 363 - sizes: 364 - - S 365 - - M 366 - - L 367 - numbers: 368 - - 1 369 - - 2 370 - - 3 371 - deep_flow_nesting: 372 - level1: 373 - level2: 374 - level3: 375 - level4: 376 - - a 377 - - b 378 - - c 379 - empty_flow: 380 - empty_seq: [] 381 - empty_map: {} 382 - both: 383 - - [] 384 - - {} 385 - flow_in_block: 386 - sequence: 387 - - 1 388 - - 2 389 - - 3 390 - - 4 391 - - 5 392 - mapping: 393 - a: 1 394 - b: 2 395 - c: 3 396 - nested: 397 - items: 398 - - x 399 - - y 400 - - z 401 - config: 402 - timeout: 30 403 - retries: 3 404 - block_in_flow: 405 - users: 406 - - name: Alice 407 - tags: 408 - - dev 409 - - senior 410 - - name: Bob 411 - tags: 412 - - ops 413 - - junior 414 - mixed_structure: 415 - services: 416 - - name: api 417 - ports: 418 - - 8080 419 - - 8443 420 - env: 421 - DEBUG: true 422 - LOG_LEVEL: info 423 - - name: db 424 - ports: 425 - - 5432 426 - env: 427 - POSTGRES_DB: mydb 428 - POSTGRES_USER: admin 429 - config: 430 - version: 1 431 - enabled: true 432 - flow_types: 433 - strings: 434 - - hello 435 - - world 436 - - foo 437 - - bar 438 - numbers: 439 - - 1 440 - - 2 441 - - 3 442 - - 42 443 - - 100 444 - mixed: 445 - - string 446 - - 123 447 - - true 448 - - false 449 - - null 450 - quoted: 451 - - with spaces 452 - - 'special:chars' 453 - - commas, here 454 - flow_map_types: 455 - string: value 456 - number: 42 457 - boolean: true 458 - null_value: null 459 - float: 3.14 460 - nested_mixed: 461 - - id: 1 462 - data: 463 - - a 464 - - b 465 - - c 466 - meta: 467 - type: first 468 - - id: 2 469 - data: 470 - - d 471 - - e 472 - - f 473 - meta: 474 - type: second 475 - - id: 3 476 - data: 477 - - g 478 - - h 479 - - i 480 - meta: 481 - type: third 482 - multiline_flow: 483 - long_sequence: 484 - - item1 485 - - item2 486 - - item3 487 - - item4 488 - long_mapping: 489 - key1: value1 490 - key2: value2 491 - key3: value3 492 - edge_cases: 493 - single_item_seq: 494 - - alone 495 - single_item_map: 496 - only: one 497 - nested_empty: 498 - - [] 499 - - 500 - - {} 501 - - 502 - - {} 503 - - [] 504 - all_empty: 505 - - {} 506 - - [] 507 - - a: [] 508 - - b: {} 509 - 510 - Test collections_flow.yml with JSON output 511 - 512 - $ yamlcat --json ../yaml/collections_flow.yml 513 - {"simple_flow_sequence": ["apple", "banana", "cherry", "date"], "simple_flow_mapping": {"name": "John", "age": 30, "city": "New York"}, "nested_flow_sequences": [["a", "b", "c"], [1, 2, 3], ["red", "green", "blue"]], "nested_flow_mappings": {"person": {"name": "Alice", "age": 25}, "contact": {"email": "alice@example.com", "phone": "555-1234"}}, "flow_seq_with_maps": [{"name": "Alice", "role": "dev"}, {"name": "Bob", "role": "ops"}, {"name": "Charlie", "role": "qa"}], "flow_map_with_seqs": {"colors": ["red", "green", "blue"], "sizes": ["S", "M", "L"], "numbers": [1, 2, 3]}, "deep_flow_nesting": {"level1": {"level2": {"level3": {"level4": ["a", "b", "c"]}}}}, "empty_flow": {"empty_seq": [], "empty_map": {}, "both": [[], {}]}, "flow_in_block": {"sequence": [1, 2, 3, 4, 5], "mapping": {"a": 1, "b": 2, "c": 3}, "nested": {"items": ["x", "y", "z"], "config": {"timeout": 30, "retries": 3}}}, "block_in_flow": {"users": [{"name": "Alice", "tags": ["dev", "senior"]}, {"name": "Bob", "tags": ["ops", "junior"]}]}, "mixed_structure": {"services": [{"name": "api", "ports": [8080, 8443], "env": {"DEBUG": true, "LOG_LEVEL": "info"}}, {"name": "db", "ports": [5432], "env": {"POSTGRES_DB": "mydb", "POSTGRES_USER": "admin"}}], "config": {"version": 1, "enabled": true}}, "flow_types": {"strings": ["hello", "world", "foo", "bar"], "numbers": [1, 2, 3, 42, 100], "mixed": ["string", 123, true, false, null], "quoted": ["with spaces", "special:chars", "commas, here"]}, "flow_map_types": {"string": "value", "number": 42, "boolean": true, "null_value": null, "float": 3.14}, "nested_mixed": [{"id": 1, "data": ["a", "b", "c"], "meta": {"type": "first"}}, {"id": 2, "data": ["d", "e", "f"], "meta": {"type": "second"}}, {"id": 3, "data": ["g", "h", "i"], "meta": {"type": "third"}}], "multiline_flow": {"long_sequence": ["item1", "item2", "item3", "item4"], "long_mapping": {"key1": "value1", "key2": "value2", "key3": "value3"}}, "edge_cases": {"single_item_seq": ["alone"], "single_item_map": {"only": "one"}, "nested_empty": [[], [{}], [{}, []]], "all_empty": [{}, [], {"a": []}, {"b": {}}]}} 514 - 515 - Test collections_flow.yml with flow output 516 - 517 - $ yamlcat --flow ../yaml/collections_flow.yml 518 - {simple_flow_sequence: [apple, banana, cherry, date]simple_flow_mapping, {name: John, age: 30, city: New York}, nested_flow_sequences: [[a, b, c], [1, 2, 3], [red, green, blue]]nested_flow_mappings, {person: {name: Alice, age: 25}contact, {email: alice@example.com, phone: 555-1234}}, flow_seq_with_maps: [{name: Alice, role: dev}, {name: Bob, role: ops}, {name: Charlie, role: qa}]flow_map_with_seqs, {colors: [red, green, blue]sizes, [S, M, L], numbers: [1, 2, 3]}, deep_flow_nesting: {level1: {level2: {level3: {level4: [a, b, c]}}}}empty_flow, {empty_seq: []empty_map, {}, both: [[], {}]}, flow_in_block: {sequence: [1, 2, 3, 4, 5]mapping, {a: 1, b: 2, c: 3}, nested: {items: [x, y, z]config, {timeout: 30, retries: 3}}}block_in_flow, {users: [{name: Alice, tags: [dev, senior]}, {name: Bob, tags: [ops, junior]}]}, mixed_structure: {services: [{name: api, ports: [8080, 8443]env, {DEBUG: true, LOG_LEVEL: info}}, {name: db, ports: [5432]env, {POSTGRES_DB: mydb, POSTGRES_USER: admin}}]config, {version: 1, enabled: true}}flow_types, {strings: [hello, world, foo, bar]numbers, [1, 2, 3, 42, 100], mixed: [string, 123, true, false, null]quoted, [with spaces, 'special:chars', commas, here]}, flow_map_types: {string: value, number: 42, boolean: true, null_value: null, float: 3.14}nested_mixed, [{id: 1, data: [a, b, c]meta, {type: first}}, {id: 2, data: [d, e, f]meta, {type: second}}, {id: 3, data: [g, h, i]meta, {type: third}}], multiline_flow: {long_sequence: [item1, item2, item3, item4]long_mapping, {key1: value1, key2: value2, key3: value3}}edge_cases, {single_item_seq: [alone]single_item_map, {only: one}, nested_empty: [[], [{}], [{}, []]]all_empty, [{}, [], {a: []}, {b: {}}]}} 519 - 520 - Inline test: Simple sequence 521 - 522 - $ echo '- a 523 - > - b 524 - > - c' | yamlcat 525 - - a 526 - - b 527 - - c 528 - 529 - $ echo '- a 530 - > - b 531 - > - c' | yamlcat --json 532 - ["a", "b", "c"] 533 - 534 - $ echo '- a 535 - > - b 536 - > - c' | yamlcat --flow 537 - [a, b, c] 538 - 539 - Inline test: Simple mapping 540 - 541 - $ echo 'key1: value1 542 - > key2: value2 543 - > key3: value3' | yamlcat 544 - key1: value1 545 - key2: value2 546 - key3: value3 547 - 548 - $ echo 'key1: value1 549 - > key2: value2 550 - > key3: value3' | yamlcat --json 551 - {"key1": "value1", "key2": "value2", "key3": "value3"} 552 - 553 - $ echo 'key1: value1 554 - > key2: value2 555 - > key3: value3' | yamlcat --flow 556 - {key1: value1, key2: value2, key3: value3} 557 - 558 - Inline test: Nested sequences 559 - 560 - $ echo 'outer: 561 - > - - inner1 562 - > - inner2 563 - > - - inner3 564 - > - inner4' | yamlcat 565 - outer: 566 - - 567 - - inner1 568 - - inner2 569 - - 570 - - inner3 571 - - inner4 572 - 573 - $ echo 'outer: 574 - > - - inner1 575 - > - inner2 576 - > - - inner3 577 - > - inner4' | yamlcat --json 578 - {"outer": [["inner1", "inner2"], ["inner3", "inner4"]]} 579 - 580 - $ echo 'outer: 581 - > - - inner1 582 - > - inner2 583 - > - - inner3 584 - > - inner4' | yamlcat --flow 585 - {outer: [[inner1, inner2], [inner3, inner4]]} 586 - 587 - Inline test: Nested mappings 588 - 589 - $ echo 'level1: 590 - > level2: 591 - > level3: 592 - > key: value' | yamlcat 593 - level1: 594 - level2: 595 - level3: 596 - key: value 597 - 598 - $ echo 'level1: 599 - > level2: 600 - > level3: 601 - > key: value' | yamlcat --json 602 - {"level1": {"level2": {"level3": {"key": "value"}}}} 603 - 604 - $ echo 'level1: 605 - > level2: 606 - > level3: 607 - > key: value' | yamlcat --flow 608 - {level1: {level2: {level3: {key: value}}}} 609 - 610 - Inline test: Flow sequence 611 - 612 - $ echo '[a, b, c]' | yamlcat 613 - - a 614 - - b 615 - - c 616 - 617 - $ echo '[a, b, c]' | yamlcat --json 618 - ["a", "b", "c"] 619 - 620 - $ echo '[a, b, c]' | yamlcat --flow 621 - [a, b, c] 622 - 623 - Inline test: Flow mapping 624 - 625 - $ echo '{a: 1, b: 2, c: 3}' | yamlcat 626 - a: 1 627 - b: 2 628 - c: 3 629 - 630 - $ echo '{a: 1, b: 2, c: 3}' | yamlcat --json 631 - {"a": 1, "b": 2, "c": 3} 632 - 633 - $ echo '{a: 1, b: 2, c: 3}' | yamlcat --flow 634 - {a: 1, b: 2, c: 3} 635 - 636 - Inline test: Nested flow collections 637 - 638 - $ echo '[[1, 2], [3, 4], [5, 6]]' | yamlcat 639 - - 640 - - 1 641 - - 2 642 - - 643 - - 3 644 - - 4 645 - - 646 - - 5 647 - - 6 648 - 649 - $ echo '[[1, 2], [3, 4], [5, 6]]' | yamlcat --json 650 - [[1, 2], [3, 4], [5, 6]] 651 - 652 - $ echo '[[1, 2], [3, 4], [5, 6]]' | yamlcat --flow 653 - [[1, 2], [3, 4], [5, 6]] 654 - 655 - Inline test: Flow mapping with nested mapping 656 - 657 - $ echo '{outer: {inner: value}}' | yamlcat 658 - outer: 659 - inner: value 660 - 661 - $ echo '{outer: {inner: value}}' | yamlcat --json 662 - {"outer": {"inner": "value"}} 663 - 664 - $ echo '{outer: {inner: value}}' | yamlcat --flow 665 - {outer: {inner: value}} 666 - 667 - Inline test: Mixed block and flow (flow in block) 668 - 669 - $ echo 'block_key: 670 - > flow_seq: [1, 2, 3] 671 - > flow_map: {a: 1, b: 2}' | yamlcat 672 - block_key: 673 - flow_seq: 674 - - 1 675 - - 2 676 - - 3 677 - flow_map: 678 - a: 1 679 - b: 2 680 - 681 - $ echo 'block_key: 682 - > flow_seq: [1, 2, 3] 683 - > flow_map: {a: 1, b: 2}' | yamlcat --json 684 - {"block_key": {"flow_seq": [1, 2, 3], "flow_map": {"a": 1, "b": 2}}} 685 - 686 - $ echo 'block_key: 687 - > flow_seq: [1, 2, 3] 688 - > flow_map: {a: 1, b: 2}' | yamlcat --flow 689 - {block_key: {flow_seq: [1, 2, 3]flow_map, {a: 1, b: 2}}} 690 - 691 - Inline test: Mixed block and flow (block in flow) 692 - 693 - $ echo '{users: [{name: Alice, age: 30}, {name: Bob, age: 25}]}' | yamlcat 694 - users: 695 - - name: Alice 696 - age: 30 697 - - name: Bob 698 - age: 25 699 - 700 - $ echo '{users: [{name: Alice, age: 30}, {name: Bob, age: 25}]}' | yamlcat --json 701 - {"users": [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]} 702 - 703 - $ echo '{users: [{name: Alice, age: 30}, {name: Bob, age: 25}]}' | yamlcat --flow 704 - {users: [{name: Alice, age: 30}, {name: Bob, age: 25}]} 705 - 706 - Inline test: Compact notation - sequence of mappings 707 - 708 - $ echo '- name: Alice 709 - > role: dev 710 - > - name: Bob 711 - > role: ops' | yamlcat 712 - - name: Alice 713 - role: dev 714 - - name: Bob 715 - role: ops 716 - 717 - $ echo '- name: Alice 718 - > role: dev 719 - > - name: Bob 720 - > role: ops' | yamlcat --json 721 - [{"name": "Alice", "role": "dev"}, {"name": "Bob", "role": "ops"}] 722 - 723 - $ echo '- name: Alice 724 - > role: dev 725 - > - name: Bob 726 - > role: ops' | yamlcat --flow 727 - [{name: Alice, role: dev}, {name: Bob, role: ops}] 728 - 729 - Inline test: Compact with nested collections 730 - 731 - $ echo '- id: 1 732 - > tags: [a, b, c] 733 - > config: 734 - > enabled: true 735 - > - id: 2 736 - > tags: [x, y, z] 737 - > config: 738 - > enabled: false' | yamlcat 739 - - id: 1 740 - tags: 741 - - a 742 - - b 743 - - c 744 - config: 745 - enabled: true 746 - - id: 2 747 - tags: 748 - - x 749 - - y 750 - - z 751 - config: 752 - enabled: false 753 - 754 - $ echo '- id: 1 755 - > tags: [a, b, c] 756 - > config: 757 - > enabled: true 758 - > - id: 2 759 - > tags: [x, y, z] 760 - > config: 761 - > enabled: false' | yamlcat --json 762 - [{"id": 1, "tags": ["a", "b", "c"], "config": {"enabled": true}}, {"id": 2, "tags": ["x", "y", "z"], "config": {"enabled": false}}] 763 - 764 - $ echo '- id: 1 765 - > tags: [a, b, c] 766 - > config: 767 - > enabled: true 768 - > - id: 2 769 - > tags: [x, y, z] 770 - > config: 771 - > enabled: false' | yamlcat --flow 772 - [{id: 1, tags: [a, b, c]config, {enabled: true}}, {id: 2, tags: [x, y, z]config, {enabled: false}}] 773 - 774 - Inline test: Empty collections 775 - 776 - $ echo 'empty_seq: [] 777 - > empty_map: {}' | yamlcat 778 - empty_seq: [] 779 - empty_map: {} 780 - 781 - $ echo 'empty_seq: [] 782 - > empty_map: {}' | yamlcat --json 783 - {"empty_seq": [], "empty_map": {}} 784 - 785 - $ echo 'empty_seq: [] 786 - > empty_map: {}' | yamlcat --flow 787 - {empty_seq: []empty_map, {}} 788 - 789 - Inline test: Sequence with mapping values 790 - 791 - $ echo 'items: 792 - > - apple 793 - > - banana 794 - > config: 795 - > mode: fast' | yamlcat 796 - items: 797 - - apple 798 - - banana 799 - config: 800 - mode: fast 801 - 802 - $ echo 'items: 803 - > - apple 804 - > - banana 805 - > config: 806 - > mode: fast' | yamlcat --json 807 - {"items": ["apple", "banana"], "config": {"mode": "fast"}} 808 - 809 - $ echo 'items: 810 - > - apple 811 - > - banana 812 - > config: 813 - > mode: fast' | yamlcat --flow 814 - {items: [apple, banana]config, {mode: fast}} 815 - 816 - Inline test: Complex nested structure 817 - 818 - $ echo 'services: 819 - > - name: web 820 - > ports: 821 - > - 80 822 - > - 443 823 - > env: 824 - > DEBUG: false 825 - > MODE: prod' | yamlcat 826 - services: 827 - - name: web 828 - ports: 829 - - 80 830 - - 443 831 - env: 832 - DEBUG: false 833 - MODE: prod 834 - 835 - $ echo 'services: 836 - > - name: web 837 - > ports: 838 - > - 80 839 - > - 443 840 - > env: 841 - > DEBUG: false 842 - > MODE: prod' | yamlcat --json 843 - {"services": [{"name": "web", "ports": [80, 443], "env": {"DEBUG": false, "MODE": "prod"}}]} 844 - 845 - $ echo 'services: 846 - > - name: web 847 - > ports: 848 - > - 80 849 - > - 443 850 - > env: 851 - > DEBUG: false 852 - > MODE: prod' | yamlcat --flow 853 - {services: [{name: web, ports: [80, 443]env, {DEBUG: false, MODE: prod}}]} 854 - 855 - Inline test: Flow sequence with various types 856 - 857 - $ echo '[string, 42, true, false, null, 3.14]' | yamlcat --json 858 - ["string", 42, true, false, null, 3.14] 859 - 860 - Inline test: Flow mapping with various types 861 - 862 - $ echo '{str: hello, num: 42, bool: true, nil: null, float: 3.14}' | yamlcat --json 863 - {"str": "hello", "num": 42, "bool": true, "nil": null, "float": 3.14}
-197
yaml/ocaml-yamle/tests/cram/comments.t
··· 1 - Test comments.yml file with various comment styles 2 - 3 - $ yamlcat ../yaml/comments.yml 4 - name: John Doe 5 - age: 30 6 - address: 7 - street: 123 Main St 8 - city: Springfield 9 - zip: 12345 10 - items: 11 - - apple 12 - - banana 13 - - cherry 14 - - date 15 - flow_seq: 16 - - 1 17 - - 2 18 - - 3 19 - flow_map: 20 - key1: value1 21 - key2: value2 22 - nested: 23 - level1: 24 - level2: 25 - value: deeply nested 26 - multi_comment_key: value 27 - special: 'value with # hash inside quotes' 28 - empty_value: null 29 - final_key: final_value 30 - 31 - Test comments.yml roundtrip with JSON to verify parsed values 32 - 33 - $ yamlcat --json ../yaml/comments.yml 34 - {"name": "John Doe", "age": 30, "address": {"street": "123 Main St", "city": "Springfield", "zip": 12345}, "items": ["apple", "banana", "cherry", "date"], "flow_seq": [1, 2, 3], "flow_map": {"key1": "value1", "key2": "value2"}, "nested": {"level1": {"level2": {"value": "deeply nested"}}}, "multi_comment_key": "value", "special": "value with # hash inside quotes", "empty_value": null, "final_key": "final_value"} 35 - 36 - Test full line comments are ignored 37 - 38 - $ echo '# This is a full line comment 39 - > name: Alice 40 - > # Another comment 41 - > age: 25' | yamlcat --json 42 - {"name": "Alice", "age": 25} 43 - 44 - Test end of line comments after scalars 45 - 46 - $ echo 'name: Bob # This is an end of line comment 47 - > age: 35 # Another end of line comment' | yamlcat --json 48 - {"name": "Bob", "age": 35} 49 - 50 - Test comments after sequence items 51 - 52 - $ echo 'fruits: 53 - > - apple # First fruit 54 - > - banana # Second fruit 55 - > - cherry # Third fruit' | yamlcat --json 56 - {"fruits": ["apple", "banana", "cherry"]} 57 - 58 - Test comments between sequence items 59 - 60 - $ echo 'numbers: 61 - > - 1 62 - > # Comment between items 63 - > - 2 64 - > # Another comment 65 - > - 3' | yamlcat --json 66 - {"numbers": [1, 2, 3]} 67 - 68 - Test comments after flow sequences 69 - 70 - $ echo 'flow: [1, 2, 3] # Comment after flow sequence' | yamlcat --json 71 - {"flow": [1, 2, 3]} 72 - 73 - Test comments after flow mappings 74 - 75 - $ echo 'flow: {a: 1, b: 2} # Comment after flow mapping' | yamlcat --json 76 - {"flow": {"a": 1, "b": 2}} 77 - 78 - Test comments between mapping entries 79 - 80 - $ echo 'first: value1 81 - > # Comment between entries 82 - > second: value2 83 - > # Another comment 84 - > third: value3' | yamlcat --json 85 - {"first": "value1", "second": "value2", "third": "value3"} 86 - 87 - Test multiple consecutive comments 88 - 89 - $ echo '# First comment 90 - > # Second comment 91 - > # Third comment 92 - > key: value' | yamlcat --json 93 - {"key": "value"} 94 - 95 - Test comments in nested structures 96 - 97 - $ echo 'outer: 98 - > # Comment in nested level 99 - > inner: 100 - > # Comment in deeper level 101 - > key: value # End of line comment' | yamlcat --json 102 - {"outer": {"inner": {"key": "value"}}} 103 - 104 - Test comments with special characters 105 - 106 - $ echo '# Comment with !@#$%^&*() 107 - > key: value' | yamlcat --json 108 - {"key": "value"} 109 - 110 - Test that hash in quoted strings is not treated as comment 111 - 112 - $ echo 'text: "This # is not a comment" 113 - > other: '"'"'Also # not a comment'"'"'' | yamlcat --json 114 - {"text": "This # is not a comment", "other": "Also # not a comment"} 115 - 116 - Test comment before empty value (null) 117 - 118 - $ echo 'key: # Comment, value is null' | yamlcat --json 119 - {"key": null} 120 - 121 - Test comments at document start 122 - 123 - $ echo '# Comment at very start 124 - > # Another at start 125 - > data: value' | yamlcat --json 126 - {"data": "value"} 127 - 128 - Test comments at document end 129 - 130 - $ echo 'data: value 131 - > # Comment at end 132 - > # Another at end' | yamlcat --json 133 - {"data": "value"} 134 - 135 - Test comments with various indentation levels 136 - 137 - $ echo 'level1: 138 - > # Indented comment 139 - > level2: 140 - > # More indented comment 141 - > value: data' | yamlcat --json 142 - {"level1": {"level2": {"value": "data"}}} 143 - 144 - Test empty lines with comments 145 - 146 - $ echo 'first: 1 147 - > 148 - > # Comment after empty line 149 - > 150 - > second: 2' | yamlcat --json 151 - {"first": 1, "second": 2} 152 - 153 - Test comment after sequence with nested mapping 154 - 155 - $ echo 'items: 156 - > - name: item1 # Comment after nested value 157 - > value: 10 158 - > # Comment between sequence items 159 - > - name: item2 160 - > value: 20 # Another comment' | yamlcat --json 161 - {"items": [{"name": "item1", "value": 10}, {"name": "item2", "value": 20}]} 162 - 163 - Test comment only lines between complex structures 164 - 165 - $ echo 'mapping1: 166 - > key: value 167 - > # Comment between mappings 168 - > mapping2: 169 - > key: value' | yamlcat --json 170 - {"mapping1": {"key": "value"}, "mapping2": {"key": "value"}} 171 - 172 - Test comments do not affect block scalars 173 - 174 - $ echo 'literal: | 175 - > # This looks like a comment 176 - > but it is part of the literal text 177 - > key: value' | yamlcat --json 178 - {"literal": "# This looks like a comment\nbut it is part of the literal text\n", "key": "value"} 179 - 180 - Test comments do not affect folded scalars 181 - 182 - $ echo 'folded: > 183 - > # This also looks like a comment 184 - > but is part of folded text 185 - > key: value' | yamlcat --json 186 - {"folded": "# This also looks like a comment but is part of folded text\n", "key": "value"} 187 - 188 - Test whitespace preservation around comments 189 - 190 - $ echo 'key1: value1 # Comment with spaces' | yamlcat --json 191 - {"key1": "value1"} 192 - 193 - Test comment after colon but before value 194 - 195 - $ echo 'key: # Comment before value 196 - > value' | yamlcat --json 197 - {"key": "value"}
-204
yaml/ocaml-yamle/tests/cram/documents.t
··· 1 - Test YAML directives and single document parsing 2 - 3 - This test suite covers YAML directives (%YAML, %TAG) and various single document formats. 4 - Multi-document streams are not yet supported and are not tested here. 5 - 6 - Test 1: Basic YAML 1.2 directive 7 - ==================================== 8 - 9 - $ yamlcat ../yaml/directives.yml 10 - version: '1.2' 11 - content: This document uses YAML 1.2 12 - 13 - Test 2: YAML 1.1 directive 14 - ==================================== 15 - 16 - $ yamlcat ../yaml/directives_yaml11.yml 17 - version: '1.1' 18 - content: This document uses YAML 1.1 19 - booleans: 20 - - true 21 - - false 22 - - true 23 - - false 24 - 25 - Test 3: TAG directive with custom prefix 26 - ==================================== 27 - 28 - $ yamlcat ../yaml/directives_tag.yml 29 - shape: 30 - radius: 5 31 - color: red 32 - point: 33 - x: 10 34 - y: 20 35 - 36 - Test 4: Multiple TAG directives 37 - ==================================== 38 - 39 - $ yamlcat ../yaml/directives_multiple_tags.yml 40 - user: 41 - name: Alice 42 - age: 30 43 - location: 44 - lat: 40.7128 45 - lon: -74.006 46 - config: 47 - debug: true 48 - timeout: 30 49 - 50 - Test 5: Implicit document (no markers) 51 - ==================================== 52 - 53 - $ yamlcat ../yaml/documents_single.yml 54 - key1: value1 55 - key2: value2 56 - nested: 57 - inner: data 58 - list: 59 - - item1 60 - - item2 61 - - item3 62 - 63 - Test 6: Explicit start marker (---) 64 - ==================================== 65 - 66 - $ yamlcat ../yaml/documents_single_explicit_start.yml 67 - key1: value1 68 - key2: value2 69 - nested: 70 - inner: data 71 - list: 72 - - item1 73 - - item2 74 - - item3 75 - 76 - Test 7: Explicit start and end markers (--- ... ) 77 - ==================================== 78 - 79 - $ yamlcat ../yaml/documents_single_explicit_both.yml 80 - key1: value1 81 - key2: value2 82 - nested: 83 - inner: data 84 - list: 85 - - item1 86 - - item2 87 - - item3 88 - 89 - Test 8: Document with YAML directive 90 - ==================================== 91 - 92 - $ yamlcat ../yaml/documents_single_with_directive.yml 93 - key1: value1 94 - key2: value2 95 - nested: 96 - inner: data 97 - list: 98 - - item1 99 - - item2 100 - - item3 101 - 102 - Test 9: Inline - implicit document (no markers) 103 - ==================================== 104 - 105 - $ echo 'name: John 106 - > age: 30 107 - > city: New York' | yamlcat 108 - name: John 109 - age: 30 110 - city: New York 111 - 112 - Test 10: Inline - explicit start marker 113 - ==================================== 114 - 115 - $ echo '--- 116 - > name: Jane 117 - > age: 25' | yamlcat 118 - name: Jane 119 - age: 25 120 - 121 - Test 11: Inline - explicit start and end markers 122 - ==================================== 123 - 124 - $ echo '--- 125 - > title: Example 126 - > content: data 127 - > ...' | yamlcat 128 - title: Example 129 - content: data 130 - 131 - Test 12: Inline - document with %YAML 1.2 directive 132 - ==================================== 133 - 134 - $ echo '%YAML 1.2 135 - > --- 136 - > version: 1.2 137 - > enabled: true' | yamlcat 138 - version: 1.2 139 - enabled: true 140 - 141 - Test 13: Inline - document with comment before content 142 - ==================================== 143 - 144 - $ echo '# This is a comment 145 - > name: Alice 146 - > # Another comment 147 - > value: 42' | yamlcat 148 - name: Alice 149 - value: 42 150 - 151 - Test 14: Inline - document with comment after directive 152 - ==================================== 153 - 154 - $ echo '%YAML 1.2 155 - > # Comment after directive 156 - > --- 157 - > key: value' | yamlcat 158 - key: value 159 - 160 - Test 15: Inline - explicit markers with comments 161 - ==================================== 162 - 163 - $ echo '--- # Document start 164 - > data: content 165 - > # Mid-document comment 166 - > more: values 167 - > ... # Document end' | yamlcat 168 - data: content 169 - more: values 170 - 171 - Test 16: Verify JSON roundtrip for directive file 172 - ==================================== 173 - 174 - $ yamlcat --json ../yaml/directives.yml 175 - {"version": "1.2", "content": "This document uses YAML 1.2"} 176 - 177 - Test 17: Verify JSON roundtrip for explicit markers 178 - ==================================== 179 - 180 - $ yamlcat --json ../yaml/documents_single_explicit_both.yml 181 - {"key1": "value1", "key2": "value2", "nested": {"inner": "data"}, "list": ["item1", "item2", "item3"]} 182 - 183 - Test 18: Empty document with explicit markers 184 - ==================================== 185 - 186 - $ echo '--- 187 - > ...' | yamlcat 188 - null 189 - 190 - Test 19: Document with only whitespace and markers 191 - ==================================== 192 - 193 - $ echo '--- 194 - > 195 - > ...' | yamlcat 196 - null 197 - 198 - Test 20: Directive followed by content without explicit start 199 - ==================================== 200 - 201 - $ echo '%YAML 1.2 202 - > simple: document' | yamlcat 203 - Error: expected document start '---' at line 2, columns 1-1 204 - [1]
-4
yaml/ocaml-yamle/tests/cram/dune
··· 1 - (cram 2 - (deps 3 - (package yamle) 4 - (glob_files ../yaml/*.yml)))
-49
yaml/ocaml-yamle/tests/cram/empty.t
··· 1 - Empty Collection YAML Emission 2 - 3 - These tests verify that empty sequences and mappings are correctly emitted 4 - as [] and {} in YAML output. 5 - 6 - Test: Empty sequence 7 - 8 - $ echo 'empty_seq: []' | yamlcat 9 - empty_seq: [] 10 - 11 - Test: Empty mapping 12 - 13 - $ echo 'empty_map: {}' | yamlcat 14 - empty_map: {} 15 - 16 - Test: Multiple empty collections 17 - 18 - $ echo 'seq: [] 19 - > map: {} 20 - > data: value' | yamlcat 21 - seq: [] 22 - map: {} 23 - data: value 24 - 25 - Test: Nested empty collections 26 - 27 - $ echo 'outer: 28 - > inner_seq: [] 29 - > inner_map: {}' | yamlcat 30 - outer: 31 - inner_seq: [] 32 - inner_map: {} 33 - 34 - Test: Empty collection in sequence 35 - 36 - $ echo 'items: 37 - > - first 38 - > - [] 39 - > - third' | yamlcat 40 - items: 41 - - first 42 - - [] 43 - - third 44 - 45 - Test: Verify JSON output is correct (for comparison) 46 - 47 - $ echo 'empty_seq: [] 48 - > empty_map: {}' | yamlcat --json 49 - {"empty_seq": [], "empty_map": {}}
-45
yaml/ocaml-yamle/tests/cram/failing_escapes.t
··· 1 - Escape Sequence Issues (documentation of known edge cases) 2 - 3 - These tests document escape sequence handling edge cases. 4 - 5 - The primary issue is with \U (capital U) in double-quoted strings. 6 - In YAML, \U is a 32-bit unicode escape that expects 8 hex digits. 7 - When users write paths like "C:\Users" the \U is interpreted as 8 - a unicode escape but "sers" are not valid hex digits. 9 - 10 - Test: Capital U interpreted as unicode escape 11 - 12 - $ echo 'path: "C:\\Users\\Name"' | yamlcat --json 2>&1 13 - Error: invalid hex escape: at line 1, columns 12-12 14 - [1] 15 - 16 - This fails because: 17 - - Shell: echo 'C:\\Users\\Name' produces C:\Users\Name 18 - - YAML sees: "C:\Users\Name" 19 - - \U is a 32-bit unicode escape (expects \UHHHHHHHH) 20 - - "sers" are not 8 hex digits, so it fails 21 - 22 - Test: Lowercase u unicode escape works 23 - 24 - $ echo 'unicode: "\\u0041"' | yamlcat --json 25 - {"unicode": "A"} 26 - 27 - Test: Uppercase U requires 8 hex digits 28 - 29 - $ echo 'unicode: "\\U00000041"' | yamlcat --json 30 - {"unicode": "A"} 31 - 32 - Test: Backslash escaping works for non-unicode 33 - 34 - $ echo 'escaped: "one\\\\two\\\\three"' | yamlcat --json 35 - {"escaped": "one\\two\\three"} 36 - 37 - Test: Mixed valid escapes 38 - 39 - $ echo 'text: "Tab:\\t Newline:\\n Quote:\\\\"' | yamlcat --json 40 - {"text": "Tab:\t Newline:\n Quote:\\"} 41 - 42 - Test: Backslash a is bell character 43 - 44 - $ echo 'text: "test\\a"' | yamlcat --json 45 - {"text": "test\007"}
-407
yaml/ocaml-yamle/tests/cram/multidoc.t
··· 1 - Multi-document stream support (currently not supported) 2 - 3 - These tests document expected behavior for multi-document YAML streams. 4 - They currently fail with "multiple documents found when single expected". 5 - 6 - Test: Two documents separated by --- 7 - 8 - $ echo '--- 9 - > first: document 10 - > --- 11 - > second: document' | yamlcat 2>&1 12 - first: document 13 - --- 14 - second: document 15 - 16 - Test: Three documents with different types 17 - 18 - $ echo '--- 19 - > mapping: value 20 - > --- 21 - > - sequence 22 - > - items 23 - > --- 24 - > scalar value' | yamlcat 2>&1 25 - mapping: value 26 - --- 27 - - sequence 28 - - items 29 - --- 30 - scalar value 31 - 32 - Test: Documents with explicit end markers 33 - 34 - $ echo '--- 35 - > doc1: value 36 - > ... 37 - > --- 38 - > doc2: value 39 - > ...' | yamlcat 2>&1 40 - doc1: value 41 - --- 42 - doc2: value 43 - 44 - Test: Empty documents 45 - 46 - $ echo '--- 47 - > --- 48 - > content: here 49 - > ---' | yamlcat 2>&1 50 - null 51 - --- 52 - content: here 53 - --- 54 - null 55 - 56 - Test: Multi-document file 57 - 58 - $ yamlcat ../yaml/documents_multi.yml 2>&1 59 - document: first 60 - type: mapping 61 - data: 62 - key1: value1 63 - key2: value2 64 - --- 65 - document: second 66 - type: mapping 67 - data: 68 - key3: value3 69 - key4: value4 70 - 71 - $ yamlcat ../yaml/documents_multi_three.yml 2>&1 72 - name: John Doe 73 - age: 30 74 - city: New York 75 - --- 76 - - apple 77 - - banana 78 - - orange 79 - - grape 80 - --- 81 - This is a plain scalar document 82 - 83 - $ yamlcat ../yaml/documents_multi_with_end.yml 2>&1 84 - first: 85 - document: data1 86 - value: 100 87 - --- 88 - second: 89 - document: data2 90 - value: 200 91 - --- 92 - third: 93 - document: data3 94 - value: 300 95 - 96 - $ yamlcat ../yaml/documents_multi_empty.yml 2>&1 97 - null 98 - --- 99 - key: value 100 - --- 101 - null 102 - --- 103 - - item1 104 - - item2 105 - 106 - Test: Anchors file (uses multiple documents) 107 - 108 - $ yamlcat ../yaml/anchors_basic.yml 2>&1 109 - scalar_anchor: Hello, World! 110 - scalar_alias: Hello, World! 111 - --- 112 - original: 42 113 - copy: 42 114 - another_copy: 42 115 - --- 116 - original_list: 117 - - apple 118 - - banana 119 - - cherry 120 - copied_list: 121 - - apple 122 - - banana 123 - - cherry 124 - --- 125 - original_map: 126 - name: Alice 127 - age: 30 128 - city: London 129 - copied_map: 130 - name: Alice 131 - age: 30 132 - city: London 133 - --- 134 - defaults: 135 - timeout: 30 136 - retries: 3 137 - colors: 138 - - red 139 - - green 140 - - blue 141 - config: 142 - settings: 143 - timeout: 30 144 - retries: 3 145 - palette: 146 - - red 147 - - green 148 - - blue 149 - --- 150 - template: 151 - metadata: 152 - version: 1 153 - author: John Doe 154 - settings: 155 - enabled: true 156 - debug: false 157 - instance1: 158 - metadata: 159 - version: 1 160 - author: John Doe 161 - settings: 162 - enabled: true 163 - debug: false 164 - instance2: 165 - metadata: 166 - version: 1 167 - author: John Doe 168 - settings: 169 - enabled: true 170 - debug: false 171 - --- 172 - items: 173 - - id: 1 174 - name: First 175 - - id: 2 176 - name: Second 177 - - id: 1 178 - name: First 179 - --- 180 - shared_value: 100 181 - calculations: 182 - base: 100 183 - doubled: 200 184 - reference: 100 185 - another_ref: 100 186 - --- 187 - feature_flag: true 188 - features: 189 - login: true 190 - signup: true 191 - export: true 192 - --- 193 - empty: null 194 - values: 195 - first: null 196 - second: null 197 - --- 198 - message: "This is a multi-line\nmessage with some\nspecial content!\n" 199 - output1: "This is a multi-line\nmessage with some\nspecial content!\n" 200 - output2: "This is a multi-line\nmessage with some\nspecial content!\n" 201 - --- 202 - database: 203 - primary: 204 - host: localhost 205 - port: 5432 206 - ssl: true 207 - replica: 208 - host: localhost 209 - port: 5432 210 - ssl: true 211 - backup: 212 - host: localhost 213 - port: 5432 214 - ssl: true 215 - 216 - $ yamlcat ../yaml/anchors_merge.yml 2>&1 217 - defaults: 218 - timeout: 30 219 - retries: 3 220 - verbose: false 221 - production: 222 - <<: 223 - timeout: 30 224 - retries: 3 225 - verbose: false 226 - environment: production 227 - --- 228 - base: 229 - color: red 230 - size: medium 231 - weight: 100 232 - custom: 233 - <<: 234 - color: red 235 - size: medium 236 - weight: 100 237 - color: blue 238 - shape: circle 239 - --- 240 - connection: 241 - host: localhost 242 - port: 8080 243 - authentication: 244 - username: admin 245 - password: secret 246 - server: 247 - <<: 248 - - host: localhost 249 - port: 8080 250 - - username: admin 251 - password: secret 252 - ssl: true 253 - --- 254 - defaults: 255 - timeout: 30 256 - retries: 3 257 - advanced: 258 - cache: true 259 - pool_size: 10 260 - config: 261 - <<: 262 - - timeout: 30 263 - retries: 3 264 - - cache: true 265 - pool_size: 10 266 - timeout: 60 267 - custom: value 268 - --- 269 - base_style: 270 - font: Arial 271 - size: 12 272 - heading_defaults: 273 - <<: 274 - font: Arial 275 - size: 12 276 - weight: bold 277 - main_heading: 278 - <<: 279 - <<: 280 - font: Arial 281 - size: 12 282 - weight: bold 283 - size: 18 284 - color: navy 285 - --- 286 - common: 287 - enabled: true 288 - log_level: info 289 - services: 290 - - name: web 291 - <<: 292 - enabled: true 293 - log_level: info 294 - port: 80 295 - - name: api 296 - <<: 297 - enabled: true 298 - log_level: info 299 - port: 3000 300 - - name: worker 301 - <<: 302 - enabled: true 303 - log_level: info 304 - threads: 4 305 - --- 306 - empty: {} 307 - config: 308 - <<: {} 309 - key: value 310 - --- 311 - metadata: 312 - created: 2023-01-01 313 - author: Admin 314 - tags: 315 - - v1 316 - - stable 317 - document: 318 - <<: 319 - created: 2023-01-01 320 - author: Admin 321 - tags: 322 - - v1 323 - - stable 324 - title: Important Document 325 - content: Some content here 326 - --- 327 - level1: 328 - a: 1 329 - b: 2 330 - level2: 331 - <<: 332 - a: 1 333 - b: 2 334 - c: 3 335 - level3: 336 - <<: 337 - <<: 338 - a: 1 339 - b: 2 340 - c: 3 341 - d: 4 342 - --- 343 - first: 344 - name: First 345 - value: 100 346 - priority: low 347 - second: 348 - name: Second 349 - value: 200 350 - category: important 351 - combined: 352 - <<: 353 - - name: First 354 - value: 100 355 - priority: low 356 - - name: Second 357 - value: 200 358 - category: important 359 - name: Combined 360 - --- 361 - numbers: 362 - count: 42 363 - ratio: 3.14 364 - active: true 365 - derived: 366 - <<: 367 - count: 42 368 - ratio: 3.14 369 - active: true 370 - label: Test 371 - --- 372 - db_defaults: 373 - pool_size: 5 374 - timeout: 30 375 - ssl: false 376 - cache_defaults: 377 - ttl: 3600 378 - max_size: 1000 379 - development: 380 - database: 381 - <<: 382 - pool_size: 5 383 - timeout: 30 384 - ssl: false 385 - host: localhost 386 - name: dev_db 387 - cache: 388 - <<: 389 - ttl: 3600 390 - max_size: 1000 391 - backend: memory 392 - production: 393 - database: 394 - <<: 395 - pool_size: 5 396 - timeout: 30 397 - ssl: false 398 - host: prod.example.com 399 - name: prod_db 400 - ssl: true 401 - pool_size: 20 402 - cache: 403 - <<: 404 - ttl: 3600 405 - max_size: 1000 406 - backend: redis 407 - ttl: 7200
-471
yaml/ocaml-yamle/tests/cram/scalars.t
··· 1 - YAML Scalar Parsing Tests 2 - 3 - This file tests various forms of YAML scalar values including plain, quoted, and block scalars. 4 - 5 - ================================================================================ 6 - PLAIN SCALARS 7 - ================================================================================ 8 - 9 - Simple plain scalars 10 - 11 - $ echo 'key: value' | yamlcat 12 - key: value 13 - 14 - $ echo 'name: Alice 15 - > age: 30 16 - > active: true' | yamlcat 17 - name: Alice 18 - age: 30 19 - active: true 20 - 21 - Plain scalars with special values 22 - 23 - $ echo 'null_val: null 24 - > bool_true: true 25 - > bool_false: false 26 - > number: 42 27 - > float: 3.14' | yamlcat --json 28 - {"null_val": null, "bool_true": true, "bool_false": false, "number": 42, "float": 3.14} 29 - 30 - ================================================================================ 31 - QUOTED SCALARS - SINGLE QUOTES 32 - ================================================================================ 33 - 34 - Single-quoted strings preserve literal text 35 - 36 - $ echo "single: 'hello world'" | yamlcat 37 - single: hello world 38 - 39 - Single-quoted strings with embedded double quotes 40 - 41 - $ echo "quote: 'He said \"hello\"'" | yamlcat 42 - quote: "He said \"hello\"" 43 - 44 - Single-quoted strings with escaped single quotes (doubled) 45 - 46 - $ echo "escaped: 'It''s a test'" | yamlcat 47 - escaped: It's a test 48 - 49 - Single-quoted multiline (newlines become spaces) 50 - 51 - $ echo "text: 'This is a 52 - > multi-line 53 - > string'" | yamlcat --json 54 - {"text": "This is a multi-line string"} 55 - 56 - Empty single-quoted string 57 - 58 - $ echo "empty: ''" | yamlcat 59 - empty: '' 60 - 61 - ================================================================================ 62 - QUOTED SCALARS - DOUBLE QUOTES 63 - ================================================================================ 64 - 65 - Simple double-quoted strings 66 - 67 - $ echo 'double: "hello world"' | yamlcat 68 - double: hello world 69 - 70 - Double-quoted with escaped newline 71 - 72 - $ echo 'text: "Line one\nLine two"' | yamlcat --json 73 - {"text": "Line one Line two"} 74 - 75 - Double-quoted with escaped tab 76 - 77 - $ echo 'text: "Col1\tCol2\tCol3"' | yamlcat --json 78 - {"text": "Col1\tCol2\tCol3"} 79 - 80 - Double-quoted with backslash escape 81 - 82 - $ echo 'path: "C:\\Users\\Name"' | yamlcat --json 83 - Error: invalid hex escape: at line 1, columns 12-12 84 - [1] 85 - 86 - Double-quoted with escaped quote 87 - 88 - $ echo 'text: "She said \"hello\""' | yamlcat --json 89 - {"text": "She said \"hello\""} 90 - 91 - Double-quoted with multiple escape sequences 92 - 93 - $ echo 'text: "Tab:\t Newline:\n Quote:\" Backslash:\\\\"' | yamlcat --json 94 - {"text": "Tab:\t Newline: Quote:\" Backslash:\\"} 95 - 96 - Empty double-quoted string 97 - 98 - $ echo 'empty: ""' | yamlcat 99 - empty: '' 100 - 101 - ================================================================================ 102 - BLOCK SCALARS - LITERAL STYLE (|) 103 - ================================================================================ 104 - 105 - Basic literal block scalar (preserves newlines) 106 - 107 - $ echo 'text: | 108 - > line one 109 - > line two 110 - > line three' | yamlcat --json 111 - {"text": "line one\nline two\nline three\n"} 112 - 113 - Literal with indentation 114 - 115 - $ echo 'text: | 116 - > First line 117 - > Indented line 118 - > Back to first' | yamlcat --json 119 - {"text": "First line\n Indented line\nBack to first\n"} 120 - 121 - Literal with blank lines 122 - 123 - $ echo 'text: | 124 - > First paragraph 125 - > 126 - > Second paragraph' | yamlcat --json 127 - {"text": "First paragraph\n\nSecond paragraph\n"} 128 - 129 - ================================================================================ 130 - BLOCK SCALARS - FOLDED STYLE (>) 131 - ================================================================================ 132 - 133 - Basic folded block scalar (newlines become spaces) 134 - 135 - $ echo 'text: > 136 - > This is a long paragraph 137 - > that will be folded into 138 - > a single line.' | yamlcat --json 139 - {"text": "This is a long paragraph that will be folded into a single line.\n"} 140 - 141 - Folded with paragraph separation (blank line preserved) 142 - 143 - $ echo 'text: > 144 - > First paragraph 145 - > flows together. 146 - > 147 - > Second paragraph 148 - > also flows.' | yamlcat --json 149 - {"text": "First paragraph flows together.\nSecond paragraph also flows.\n"} 150 - 151 - ================================================================================ 152 - CHOMPING INDICATORS 153 - ================================================================================ 154 - 155 - Strip chomping (-) removes trailing newlines 156 - 157 - $ echo 'text: |- 158 - > No trailing newline' | yamlcat --json 159 - {"text": "No trailing newline"} 160 - 161 - $ echo 'text: |- 162 - > Text here 163 - > 164 - > ' | yamlcat --json 165 - {"text": "Text here"} 166 - 167 - Folded with strip 168 - 169 - $ echo 'text: >- 170 - > Folded text 171 - > with stripped 172 - > trailing newlines 173 - > 174 - > ' | yamlcat --json 175 - {"text": "Folded text with stripped trailing newlines"} 176 - 177 - Clip chomping (default) keeps single trailing newline 178 - 179 - $ echo 'text: | 180 - > One trailing newline 181 - > 182 - > ' | yamlcat --json 183 - {"text": "One trailing newline\n"} 184 - 185 - $ echo 'text: > 186 - > Folded with one 187 - > trailing newline 188 - > 189 - > ' | yamlcat --json 190 - {"text": "Folded with one trailing newline\n"} 191 - 192 - Keep chomping (+) preserves all trailing newlines 193 - 194 - $ echo 'text: |+ 195 - > Keeps trailing newlines 196 - > 197 - > 198 - > ' | yamlcat --json 199 - {"text": "Keeps trailing newlines\n\n\n\n"} 200 - 201 - $ echo 'text: >+ 202 - > Folded text 203 - > keeps trailing 204 - > 205 - > 206 - > ' | yamlcat --json 207 - {"text": "Folded text keeps trailing\n\n\n\n"} 208 - 209 - ================================================================================ 210 - EXPLICIT INDENTATION INDICATORS 211 - ================================================================================ 212 - 213 - Literal with explicit 2-space indentation 214 - 215 - $ echo 'text: |2 216 - > Two space base 217 - > Second line 218 - > Extra indent' | yamlcat --json 219 - {"text": " Two space base\n Second line\n Extra indent\n"} 220 - 221 - Folded with explicit indentation 222 - 223 - $ echo 'text: >2 224 - > Text with two space 225 - > base indentation that 226 - > will be folded.' | yamlcat --json 227 - {"text": "Text with two space base indentation that will be folded.\n"} 228 - 229 - Combined indentation and chomping indicators 230 - 231 - $ echo 'text: |2- 232 - > Indented by 2 233 - > No trailing newlines 234 - > 235 - > ' | yamlcat --json 236 - {"text": " Indented by 2\n No trailing newlines"} 237 - 238 - $ echo 'text: |2+ 239 - > Indented by 2 240 - > Keeps trailing newlines 241 - > 242 - > 243 - > ' | yamlcat --json 244 - {"text": " Indented by 2\n Keeps trailing newlines\n\n\n\n"} 245 - 246 - ================================================================================ 247 - FILE TESTS - QUOTED SCALARS 248 - ================================================================================ 249 - 250 - Test parsing scalars_quoted.yml file 251 - 252 - $ yamlcat ../yaml/scalars_quoted.yml | head -20 253 - single_simple: hello world 254 - single_with_double: "He said \"hello\"" 255 - single_escaped_quote: 'It''s a single quote: ''example''' 256 - single_multiline: This is a multi-line single quoted string 257 - double_simple: hello world 258 - double_with_single: It's easy 259 - double_escaped_quote: "She said \"hello\"" 260 - escaped_newline: "Line one\nLine two\nLine three" 261 - escaped_tab: "Column1\tColumn2\tColumn3" 262 - escaped_backslash: "Path: C:\\Users\\Name" 263 - escaped_carriage: "Before\rAfter" 264 - escaped_bell: "Bell\x07" 265 - escaped_backspace: "Back\x08" 266 - escaped_formfeed: "Form\x0c" 267 - escaped_vertical: "Vertical\x0btab" 268 - unicode_16bit: 'Snowman: ☃' 269 - unicode_32bit: 'Emoji: 😀' 270 - unicode_hex: "Null byte: \x00" 271 - empty_single: '' 272 - empty_double: '' 273 - 274 - Test JSON output for quoted scalars 275 - 276 - $ yamlcat --json ../yaml/scalars_quoted.yml | head -c 500 277 - {"single_simple": "hello world", "single_with_double": "He said \"hello\"", "single_escaped_quote": "It's a single quote: 'example'", "single_multiline": "This is a multi-line single quoted string", "double_simple": "hello world", "double_with_single": "It's easy", "double_escaped_quote": "She said \"hello\"", "escaped_newline": "Line one\nLine two\nLine three", "escaped_tab": "Column1\tColumn2\tColumn3", "escaped_backslash": "Path: C:\\Users\\Name", "escaped_carriage": "Before\rAfter", "escaped 278 - 279 - Verify specific escape handling in JSON 280 - 281 - $ yamlcat --json ../yaml/scalars_quoted.yml | grep -o '"escaped_newline": "[^"]*"' 282 - "escaped_newline": "Line one\nLine two\nLine three" 283 - 284 - $ yamlcat --json ../yaml/scalars_quoted.yml | grep -o '"escaped_tab": "[^"]*"' 285 - "escaped_tab": "Column1\tColumn2\tColumn3" 286 - 287 - Verify Unicode handling 288 - 289 - $ yamlcat --json ../yaml/scalars_quoted.yml | grep -o '"unicode_16bit": "[^"]*"' 290 - "unicode_16bit": "Snowman: \226\152\131" 291 - 292 - $ yamlcat --json ../yaml/scalars_quoted.yml | grep -o '"unicode_32bit": "[^"]*"' 293 - "unicode_32bit": "Emoji: \240\159\152\128" 294 - 295 - Verify quoted strings preserve type indicators 296 - 297 - $ yamlcat --json ../yaml/scalars_quoted.yml | grep -o '"string_true": "[^"]*"' 298 - "string_true": "true" 299 - 300 - $ yamlcat --json ../yaml/scalars_quoted.yml | grep -o '"string_null": "[^"]*"' 301 - "string_null": "null" 302 - 303 - $ yamlcat --json ../yaml/scalars_quoted.yml | grep -o '"string_number": "[^"]*"' 304 - "string_number": "123" 305 - 306 - ================================================================================ 307 - FILE TESTS - BLOCK SCALARS 308 - ================================================================================ 309 - 310 - Test parsing scalars_block.yml file 311 - 312 - $ yamlcat ../yaml/scalars_block.yml | head -30 313 - literal_basic: "Line one\nLine two\nLine three\n" 314 - literal_with_indent: "First line\n Indented line\n More indented\n Back to second level\nBack to first level\n" 315 - folded_basic: "This is a long paragraph that will be folded into a single line with the newlines converted to spaces.\n" 316 - folded_paragraph: "First paragraph flows together into a single line.\nSecond paragraph after blank line also flows together.\n" 317 - literal_strip: No trailing newline 318 - literal_strip_multiple: Text here 319 - folded_strip: Folded text with stripped trailing newlines 320 - literal_clip: "One trailing newline\n" 321 - literal_clip_explicit: "This is the default behavior\n" 322 - folded_clip: "Folded with one trailing newline\n" 323 - literal_keep: "Keeps trailing newlines\n\n\n" 324 - literal_keep_multiple: "Text here\n\n\n" 325 - folded_keep: "Folded text keeps trailing\n\n\n" 326 - literal_indent_2: " Two space indentation\n is preserved here\n Extra indent\n Back to two\n" 327 - literal_indent_4: " Four space base indent\n Second line\n Extra indent\n Back to base\n" 328 - folded_indent_2: "Text with two space base indentation that will be folded.\n" 329 - folded_indent_3: "Three space indent for this folded text block.\n" 330 - literal_indent_strip: " Indented by 2\n No trailing newlines" 331 - folded_indent_strip: Folded with indent and stripped end 332 - literal_indent_keep: " Indented by 2\n Keeps trailing newlines\n\n\n" 333 - folded_indent_keep: "Folded indent 4 keeps all trailing\n\n\n" 334 - empty_literal: "\nempty_folded: >\n\n# Block scalar with only newlines\nonly_newlines_literal: |\n\n\nonly_newlines_folded: >\n\n\n# Complex indentation patterns\ncomplex_literal: |\nFirst level\n Second level\n Third level\n Back to second\nBack to first\n\nNew paragraph\n With indent\n\nFinal paragraph\n" 335 - complex_folded: "This paragraph flows together.\nThis is separate. This line starts more indented and continues.\nFinal thoughts here.\n" 336 - special_chars_literal: "Special: @#$%^&*()\nQuotes: \"double\" 'single'\nBrackets: [array] {object}\nSymbols: | > & * ? : -\n" 337 - special_chars_folded: "All special chars are literal in block scalars: []{}|>*&\n" 338 - sequence_with_blocks: 339 - - "First item\nliteral block\n" 340 - - "Second item folded block\n" 341 - - "Third item\nstripped" 342 - - "Fourth item\nkept\n\n\n" 343 - 344 - Test JSON output for block scalars 345 - 346 - $ yamlcat --json ../yaml/scalars_block.yml | grep -o '"literal_basic": "[^"]*"' 347 - "literal_basic": "Line one\nLine two\nLine three\n" 348 - 349 - $ yamlcat --json ../yaml/scalars_block.yml | grep -o '"folded_basic": "[^"]*"' | head -c 100 350 - "folded_basic": "This is a long paragraph that will be folded into a single line with the newlines c 351 - 352 - Verify strip chomping 353 - 354 - $ yamlcat --json ../yaml/scalars_block.yml | grep -o '"literal_strip": "[^"]*"' 355 - "literal_strip": "No trailing newline" 356 - 357 - $ yamlcat --json ../yaml/scalars_block.yml | grep -o '"folded_strip": "[^"]*"' 358 - "folded_strip": "Folded text with stripped trailing newlines" 359 - 360 - Verify clip chomping (single newline) 361 - 362 - $ yamlcat --json ../yaml/scalars_block.yml | grep -o '"literal_clip": "[^"]*"' 363 - "literal_clip": "One trailing newline\n" 364 - 365 - Verify keep chomping (all newlines) 366 - 367 - $ yamlcat --json ../yaml/scalars_block.yml | grep -o '"literal_keep": "[^"]*"' 368 - "literal_keep": "Keeps trailing newlines\n\n\n" 369 - 370 - $ yamlcat --json ../yaml/scalars_block.yml | grep -o '"folded_keep": "[^"]*"' 371 - "folded_keep": "Folded text keeps trailing\n\n\n" 372 - 373 - Verify indentation handling 374 - 375 - $ yamlcat --json ../yaml/scalars_block.yml | grep -o '"literal_indent_2": "[^"]*"' 376 - "literal_indent_2": " Two space indentation\n is preserved here\n Extra indent\n Back to two\n" 377 - 378 - Verify nested structures with block scalars 379 - 380 - $ yamlcat ../yaml/scalars_block.yml | tail -10 381 - special_chars_folded: "All special chars are literal in block scalars: []{}|>*&\n" 382 - sequence_with_blocks: 383 - - "First item\nliteral block\n" 384 - - "Second item folded block\n" 385 - - "Third item\nstripped" 386 - - "Fourth item\nkept\n\n\n" 387 - nested: 388 - description: "This is a folded description that spans multiple lines.\n" 389 - code: "def hello():\n print(\"Hello, World!\")\n return True\n" 390 - notes: "Final notes\nwith stripped end" 391 - 392 - ================================================================================ 393 - SPECIAL CASES AND EDGE CASES 394 - ================================================================================ 395 - 396 - Empty block scalars 397 - 398 - $ echo 'empty_literal: |' | yamlcat --json 399 - {"empty_literal": ""} 400 - 401 - $ echo 'empty_folded: >' | yamlcat --json 402 - {"empty_folded": ""} 403 - 404 - Block scalars with special characters (no escaping needed) 405 - 406 - $ echo 'code: | 407 - > Special: @#$%^&*() 408 - > Quotes: "double" '"'"'single'"'"' 409 - > Brackets: [array] {object}' | yamlcat --json 410 - {"code": "Special: @#$%^&*()\nQuotes: \"double\" 'single'\nBrackets: [array] {object}\n"} 411 - 412 - Plain scalar vs quoted string for special values 413 - 414 - $ echo 'unquoted_true: true 415 - > quoted_true: "true"' | yamlcat --json 416 - {"unquoted_true": true, "quoted_true": "true"} 417 - 418 - $ echo 'unquoted_null: null 419 - > quoted_null: "null"' | yamlcat --json 420 - {"unquoted_null": null, "quoted_null": "null"} 421 - 422 - Strings that need quoting to preserve leading/trailing spaces 423 - 424 - $ echo 'leading: " spaces" 425 - > trailing: "spaces " 426 - > both: " spaces "' | yamlcat --json 427 - {"leading": " spaces", "trailing": "spaces ", "both": " spaces "} 428 - 429 - Block scalars in sequences 430 - 431 - $ echo 'items: 432 - > - | 433 - > First item 434 - > multiline 435 - > - > 436 - > Second item 437 - > folded' | yamlcat --json 438 - {"items": ["First item\nmultiline\n", "Second item folded\n"]} 439 - 440 - Block scalars in nested mappings 441 - 442 - $ echo 'outer: 443 - > inner: 444 - > description: > 445 - > This is a folded 446 - > description. 447 - > code: | 448 - > def test(): 449 - > return True' | yamlcat --json 450 - {"outer": {"inner": {"description": "This is a folded description.\n", "code": "def test():\n return True\n"}}} 451 - 452 - Preserving indentation in literal blocks 453 - 454 - $ echo 'code: | 455 - > def hello(): 456 - > print("Hello") 457 - > if True: 458 - > return 42' | yamlcat --json 459 - {"code": "def hello():\n print(\"Hello\")\n if True:\n return 42\n"} 460 - 461 - Folded scalars preserve more-indented lines 462 - 463 - $ echo 'text: > 464 - > Normal paragraph 465 - > continues here. 466 - > 467 - > Indented block 468 - > preserved. 469 - > 470 - > Back to normal.' | yamlcat --json 471 - {"text": "Normal paragraph continues here.\nIndented block preserved.\nBack to normal.\n"}
-60
yaml/ocaml-yamle/tests/cram/tags.t
··· 1 - Tag Support Tests 2 - 3 - These tests verify YAML tag support including type coercion and 4 - different tag formats. 5 - 6 - Test: String tag shorthand 7 - 8 - $ printf '!!str 123' | yamlcat 9 - '123' 10 - 11 - The !!str tag forces the value to be treated as a string. 12 - 13 - Test: Integer tag shorthand 14 - 15 - $ printf '!!int "42"' | yamlcat 16 - 42 17 - 18 - The !!int tag coerces the quoted string to an integer. 19 - 20 - Test: Boolean tag shorthand 21 - 22 - $ printf '!!bool "yes"' | yamlcat 23 - true 24 - 25 - The !!bool tag coerces the string to a boolean. 26 - 27 - Test: Null tag shorthand 28 - 29 - $ printf '!!null ""' | yamlcat 30 - null 31 - 32 - The !!null tag coerces the value to null. 33 - 34 - Test: Float tag shorthand 35 - 36 - $ printf '!!float 3.14' | yamlcat 37 - 3.14 38 - 39 - The !!float tag specifies a floating-point number. 40 - 41 - Test: Tag shorthand in mapping value 42 - 43 - $ printf 'value: !!str 42' | yamlcat 44 - value: '42' 45 - 46 - Tags work in mapping values and force type coercion. 47 - 48 - Test: Local tags 49 - 50 - $ printf '!local_tag value' | yamlcat 51 - value 52 - 53 - Local tags (single !) are treated as unknown and default to string type. 54 - 55 - Test: Verbatim tags 56 - 57 - $ printf '!<tag:example.com:type> value' | yamlcat 58 - value 59 - 60 - Verbatim tags (!<...>) are treated as unknown and default to string type.
-444
yaml/ocaml-yamle/tests/cram/values.t
··· 1 - Test YAML null values from values_null.yml 2 - 3 - $ yamlcat ../yaml/values_null.yml 4 - explicit_null: null 5 - tilde_null: null 6 - empty_null: null 7 - flow_null: 8 - - null 9 - - null 10 - - null 11 - sequence_nulls: 12 - - null 13 - - null 14 - - null 15 - - explicit: null 16 - - tilde: null 17 - - empty: null 18 - mapping_nulls: 19 - key1: null 20 - key2: null 21 - key3: null 22 - "null": null key with string value 23 - "~": tilde key with string value 24 - nested: 25 - level1: 26 - null_value: null 27 - tilde_value: null 28 - empty_value: null 29 - list: 30 - - null 31 - - null 32 - - null 33 - - some_value 34 - map: 35 - a: null 36 - b: null 37 - c: null 38 - string_nulls: 39 - quoted_null: 'null' 40 - quoted_tilde: '~' 41 - null_in_string: this is null 42 - word_null: 'null' 43 - 44 - $ yamlcat --json ../yaml/values_null.yml 45 - {"explicit_null": null, "tilde_null": null, "empty_null": null, "flow_null": [null, null, null], "sequence_nulls": [null, null, null, {"explicit": null}, {"tilde": null}, {"empty": null}], "mapping_nulls": {"key1": null, "key2": null, "key3": null}, "null": "null key with string value", "~": "tilde key with string value", "nested": {"level1": {"null_value": null, "tilde_value": null, "empty_value": null, "list": [null, null, null, "some_value"], "map": {"a": null, "b": null, "c": null}}}, "string_nulls": {"quoted_null": "null", "quoted_tilde": "~", "null_in_string": "this is null", "word_null": "null"}} 46 - 47 - Test YAML boolean values from values_bool.yml 48 - 49 - $ yamlcat ../yaml/values_bool.yml 50 - bool_true: true 51 - bool_false: false 52 - capitalized_true: true 53 - capitalized_false: false 54 - yes_value: true 55 - no_value: false 56 - Yes_value: true 57 - No_value: false 58 - YES_value: true 59 - NO_value: false 60 - on_value: true 61 - off_value: false 62 - On_value: true 63 - Off_value: false 64 - ON_value: true 65 - OFF_value: false 66 - bool_sequence: 67 - - true 68 - - false 69 - - true 70 - - false 71 - - true 72 - - false 73 - flow_bools: 74 - - true 75 - - false 76 - - true 77 - - false 78 - bool_mapping: 79 - active: true 80 - disabled: false 81 - enabled: true 82 - stopped: false 83 - quoted_bools: 84 - quoted_true: 'true' 85 - quoted_false: 'false' 86 - quoted_yes: 'yes' 87 - quoted_no: 'no' 88 - single_true: 'true' 89 - single_false: 'false' 90 - nested_bools: 91 - settings: 92 - debug: true 93 - verbose: false 94 - legacy_yes: true 95 - legacy_no: false 96 - flags: 97 - - true 98 - - false 99 - - true 100 - - false 101 - mixed_case: 102 - "TRUE": true 103 - "FALSE": false 104 - "TrUe": true 105 - "FaLsE": false 106 - bool_like_strings: 107 - truthy: truely 108 - falsy: falsetto 109 - yes_sir: yessir 110 - no_way: noway 111 - 112 - $ yamlcat --json ../yaml/values_bool.yml 113 - {"bool_true": true, "bool_false": false, "capitalized_true": true, "capitalized_false": false, "yes_value": true, "no_value": false, "Yes_value": true, "No_value": false, "YES_value": true, "NO_value": false, "on_value": true, "off_value": false, "On_value": true, "Off_value": false, "ON_value": true, "OFF_value": false, "bool_sequence": [true, false, true, false, true, false], "flow_bools": [true, false, true, false], "bool_mapping": {"active": true, "disabled": false, "enabled": true, "stopped": false}, "quoted_bools": {"quoted_true": "true", "quoted_false": "false", "quoted_yes": "yes", "quoted_no": "no", "single_true": "true", "single_false": "false"}, "nested_bools": {"settings": {"debug": true, "verbose": false, "legacy_yes": true, "legacy_no": false}, "flags": [true, false, true, false]}, "mixed_case": {"TRUE": true, "FALSE": false, "TrUe": true, "FaLsE": false}, "bool_like_strings": {"truthy": "truely", "falsy": "falsetto", "yes_sir": "yessir", "no_way": "noway"}} 114 - 115 - Test YAML number values from values_numbers.yml 116 - 117 - $ yamlcat ../yaml/values_numbers.yml 118 - int_zero: 0 119 - int_positive: 42 120 - int_negative: -17 121 - int_large: 1000000 122 - int_with_underscores: 1000000 123 - octal_value: 12 124 - octal_zero: 0 125 - octal_large: 511 126 - hex_lowercase: 26 127 - hex_uppercase: 26 128 - hex_mixed: 3735928559 129 - hex_zero: 0 130 - float_simple: 3.14 131 - float_negative: -0.5 132 - float_zero: 0 133 - float_leading_dot: 0.5 134 - float_trailing_zero: 1 135 - scientific_positive: 10000000000 136 - scientific_negative: 0.0015 137 - scientific_uppercase: 250 138 - scientific_no_sign: 300000 139 - positive_infinity: .inf 140 - negative_infinity: -.inf 141 - not_a_number: .nan 142 - infinity_upper: .inf 143 - infinity_caps: .inf 144 - nan_upper: .nan 145 - nan_caps: .nan 146 - number_sequence: 147 - - 0 148 - - 42 149 - - -17 150 - - 3.14 151 - - 10000000000 152 - - .inf 153 - - .nan 154 - flow_numbers: 155 - - 0 156 - - 42 157 - - -17 158 - - 3.14 159 - - 26 160 - - 12 161 - number_mapping: 162 - count: 100 163 - price: 19.99 164 - discount: -5 165 - hex_color: 16734003 166 - octal_perms: 493 167 - scientific: 6.022e+23 168 - quoted_numbers: 169 - string_int: '42' 170 - string_float: '3.14' 171 - string_hex: '0x1A' 172 - string_octal: 0o14 173 - string_inf: '.inf' 174 - string_nan: '.nan' 175 - numeric_strings: 176 - phone: 555-1234 177 - version: 1.2.3 178 - code: 123 179 - leading_zero: 7 180 - plus_sign: 123 181 - edge_cases: 182 - min_int: -9.22337e+18 183 - max_int: 9.22337e+18 184 - very_small: 1e-100 185 - very_large: 1e+100 186 - negative_zero: -0 187 - positive_zero: 0 188 - nested_numbers: 189 - coordinates: 190 - x: 10.5 191 - y: -20.3 192 - z: 0 193 - measurements: 194 - - 1.1 195 - - 2.2 196 - - 3.3 197 - stats: 198 - count: 1000 199 - average: 45.67 200 - max: .inf 201 - min: -.inf 202 - legacy_octal: 14 203 - binary_like: 10 204 - format_tests: 205 - no_decimal: 42 206 - with_decimal: 42 207 - leading_zero_decimal: 0.42 208 - no_leading_digit: 0.42 209 - trailing_decimal: 42 210 - 211 - $ yamlcat --json ../yaml/values_numbers.yml 212 - {"int_zero": 0, "int_positive": 42, "int_negative": -17, "int_large": 1000000, "int_with_underscores": 1000000, "octal_value": 12, "octal_zero": 0, "octal_large": 511, "hex_lowercase": 26, "hex_uppercase": 26, "hex_mixed": 3735928559, "hex_zero": 0, "float_simple": 3.14, "float_negative": -0.5, "float_zero": 0, "float_leading_dot": 0.5, "float_trailing_zero": 1, "scientific_positive": 10000000000, "scientific_negative": 0.0015, "scientific_uppercase": 250, "scientific_no_sign": 300000, "positive_infinity": inf, "negative_infinity": -inf, "not_a_number": nan, "infinity_upper": inf, "infinity_caps": inf, "nan_upper": nan, "nan_caps": nan, "number_sequence": [0, 42, -17, 3.14, 10000000000, inf, nan], "flow_numbers": [0, 42, -17, 3.14, 26, 12], "number_mapping": {"count": 100, "price": 19.99, "discount": -5, "hex_color": 16734003, "octal_perms": 493, "scientific": 6.022e+23}, "quoted_numbers": {"string_int": "42", "string_float": "3.14", "string_hex": "0x1A", "string_octal": "0o14", "string_inf": ".inf", "string_nan": ".nan"}, "numeric_strings": {"phone": "555-1234", "version": "1.2.3", "code": 123, "leading_zero": 7, "plus_sign": 123}, "edge_cases": {"min_int": -9.22337e+18, "max_int": 9.22337e+18, "very_small": 1e-100, "very_large": 1e+100, "negative_zero": -0, "positive_zero": 0}, "nested_numbers": {"coordinates": {"x": 10.5, "y": -20.3, "z": 0}, "measurements": [1.1, 2.2, 3.3], "stats": {"count": 1000, "average": 45.67, "max": inf, "min": -inf}}, "legacy_octal": 14, "binary_like": 10, "format_tests": {"no_decimal": 42, "with_decimal": 42, "leading_zero_decimal": 0.42, "no_leading_digit": 0.42, "trailing_decimal": 42}} 213 - 214 - Test YAML timestamp values from values_timestamps.yml 215 - 216 - $ yamlcat ../yaml/values_timestamps.yml 217 - date_simple: 2001-12-15 218 - date_earliest: 1970-01-01 219 - date_leap_year: 2020-02-29 220 - date_current: 2025-12-04 221 - datetime_utc: '2001-12-15T02:59:43.1Z' 222 - datetime_utc_full: '2001-12-15T02:59:43.123456Z' 223 - datetime_utc_no_frac: '2001-12-15T02:59:43Z' 224 - datetime_offset_pos: '2001-12-15T02:59:43.1+05:30' 225 - datetime_offset_neg: '2001-12-15T02:59:43.1-05:00' 226 - datetime_offset_hours: '2001-12-15T02:59:43+05' 227 - datetime_spaced: '2001-12-14 21:59:43.10 -5' 228 - datetime_spaced_utc: '2001-12-15 02:59:43.1 Z' 229 - datetime_spaced_offset: '2001-12-14 21:59:43.10 -05:00' 230 - datetime_no_frac: '2001-12-15T14:30:00Z' 231 - date_only: 2001-12-15 232 - timestamp_formats: 233 - iso_date: 2001-12-15 234 - iso_datetime_z: '2001-12-15T02:59:43Z' 235 - iso_datetime_offset: '2001-12-15T02:59:43+00:00' 236 - spaced_datetime: '2001-12-14 21:59:43.10 -5' 237 - canonical: '2001-12-15T02:59:43.1Z' 238 - timestamp_sequence: 239 - - 2001-12-15 240 - - '2001-12-15T02:59:43.1Z' 241 - - '2001-12-14 21:59:43.10 -5' 242 - - '2025-01-01T00:00:00Z' 243 - events: 244 - created: '2001-12-15T02:59:43.1Z' 245 - modified: '2001-12-16T10:30:00Z' 246 - published: '2001-12-14 21:59:43.10 -5' 247 - quoted_timestamps: 248 - string_date: 2001-12-15 249 - string_datetime: '2001-12-15T02:59:43.1Z' 250 - string_spaced: '2001-12-14 21:59:43.10 -5' 251 - edge_cases: 252 - midnight: '2001-12-15T00:00:00Z' 253 - end_of_day: '2001-12-15T23:59:59Z' 254 - microseconds: '2001-12-15T02:59:43.123456Z' 255 - no_seconds: '2001-12-15T02:59Z' 256 - hour_only: 2001-12-15T02Z 257 - nested_timestamps: 258 - project: 259 - start_date: 2001-12-15 260 - milestones: 261 - - date: 2001-12-20 262 - time: '2001-12-20T14:00:00Z' 263 - - date: 2002-01-15 264 - time: '2002-01-15T09:30:00-05:00' 265 - metadata: 266 - created: '2001-12-14 21:59:43.10 -5' 267 - updated: '2001-12-15T02:59:43.1Z' 268 - invalid_timestamps: 269 - bad_date: 2001-13-45 270 - bad_time: '2001-12-15T25:99:99Z' 271 - incomplete: 2001-12 272 - no_leading_zero: 2001-1-5 273 - timezones: 274 - utc_z: '2001-12-15T02:59:43Z' 275 - utc_offset: '2001-12-15T02:59:43+00:00' 276 - est: '2001-12-14T21:59:43-05:00' 277 - ist: '2001-12-15T08:29:43+05:30' 278 - jst: '2001-12-15T11:59:43+09:00' 279 - date_range: 280 - past: 1900-01-01 281 - unix_epoch: '1970-01-01T00:00:00Z' 282 - y2k: '2000-01-01T00:00:00Z' 283 - present: 2025-12-04 284 - future: '2099-12-31T23:59:59Z' 285 - 286 - $ yamlcat --json ../yaml/values_timestamps.yml 287 - {"date_simple": "2001-12-15", "date_earliest": "1970-01-01", "date_leap_year": "2020-02-29", "date_current": "2025-12-04", "datetime_utc": "2001-12-15T02:59:43.1Z", "datetime_utc_full": "2001-12-15T02:59:43.123456Z", "datetime_utc_no_frac": "2001-12-15T02:59:43Z", "datetime_offset_pos": "2001-12-15T02:59:43.1+05:30", "datetime_offset_neg": "2001-12-15T02:59:43.1-05:00", "datetime_offset_hours": "2001-12-15T02:59:43+05", "datetime_spaced": "2001-12-14 21:59:43.10 -5", "datetime_spaced_utc": "2001-12-15 02:59:43.1 Z", "datetime_spaced_offset": "2001-12-14 21:59:43.10 -05:00", "datetime_no_frac": "2001-12-15T14:30:00Z", "date_only": "2001-12-15", "timestamp_formats": {"iso_date": "2001-12-15", "iso_datetime_z": "2001-12-15T02:59:43Z", "iso_datetime_offset": "2001-12-15T02:59:43+00:00", "spaced_datetime": "2001-12-14 21:59:43.10 -5", "canonical": "2001-12-15T02:59:43.1Z"}, "timestamp_sequence": ["2001-12-15", "2001-12-15T02:59:43.1Z", "2001-12-14 21:59:43.10 -5", "2025-01-01T00:00:00Z"], "events": {"created": "2001-12-15T02:59:43.1Z", "modified": "2001-12-16T10:30:00Z", "published": "2001-12-14 21:59:43.10 -5"}, "quoted_timestamps": {"string_date": "2001-12-15", "string_datetime": "2001-12-15T02:59:43.1Z", "string_spaced": "2001-12-14 21:59:43.10 -5"}, "edge_cases": {"midnight": "2001-12-15T00:00:00Z", "end_of_day": "2001-12-15T23:59:59Z", "microseconds": "2001-12-15T02:59:43.123456Z", "no_seconds": "2001-12-15T02:59Z", "hour_only": "2001-12-15T02Z"}, "nested_timestamps": {"project": {"start_date": "2001-12-15", "milestones": [{"date": "2001-12-20", "time": "2001-12-20T14:00:00Z"}, {"date": "2002-01-15", "time": "2002-01-15T09:30:00-05:00"}], "metadata": {"created": "2001-12-14 21:59:43.10 -5", "updated": "2001-12-15T02:59:43.1Z"}}}, "invalid_timestamps": {"bad_date": "2001-13-45", "bad_time": "2001-12-15T25:99:99Z", "incomplete": "2001-12", "no_leading_zero": "2001-1-5"}, "timezones": {"utc_z": "2001-12-15T02:59:43Z", "utc_offset": "2001-12-15T02:59:43+00:00", "est": "2001-12-14T21:59:43-05:00", "ist": "2001-12-15T08:29:43+05:30", "jst": "2001-12-15T11:59:43+09:00"}, "date_range": {"past": "1900-01-01", "unix_epoch": "1970-01-01T00:00:00Z", "y2k": "2000-01-01T00:00:00Z", "present": "2025-12-04", "future": "2099-12-31T23:59:59Z"}} 288 - 289 - Test inline null values 290 - 291 - $ echo 'explicit: null' | yamlcat --json 292 - {"explicit": null} 293 - 294 - $ echo 'tilde: ~' | yamlcat --json 295 - {"tilde": null} 296 - 297 - $ echo 'empty:' | yamlcat --json 298 - {"empty": null} 299 - 300 - $ echo '[null, ~, ]' | yamlcat --json 301 - [null, null, null] 302 - 303 - Test inline boolean values 304 - 305 - $ echo 'bool: true' | yamlcat --json 306 - {"bool": true} 307 - 308 - $ echo 'bool: false' | yamlcat --json 309 - {"bool": false} 310 - 311 - $ echo '[true, false]' | yamlcat --json 312 - [true, false] 313 - 314 - Test inline integer values 315 - 316 - $ echo 'positive: 42' | yamlcat --json 317 - {"positive": 42} 318 - 319 - $ echo 'negative: -17' | yamlcat --json 320 - {"negative": -17} 321 - 322 - $ echo 'zero: 0' | yamlcat --json 323 - {"zero": 0} 324 - 325 - $ echo '[0, 42, -17]' | yamlcat --json 326 - [0, 42, -17] 327 - 328 - Test inline float values 329 - 330 - $ echo 'simple: 3.14' | yamlcat --json 331 - {"simple": 3.14} 332 - 333 - $ echo 'negative: -0.5' | yamlcat --json 334 - {"negative": -0.5} 335 - 336 - $ echo 'leading_dot: .5' | yamlcat --json 337 - {"leading_dot": 0.5} 338 - 339 - $ echo '[3.14, -0.5, 0.0]' | yamlcat --json 340 - [3.14, -0.5, 0] 341 - 342 - Test scientific notation 343 - 344 - $ echo 'positive_exp: 1.5e10' | yamlcat --json 345 - {"positive_exp": 15000000000} 346 - 347 - $ echo 'negative_exp: 1.5e-3' | yamlcat --json 348 - {"negative_exp": 0.0015} 349 - 350 - $ echo 'uppercase: 2.5E+2' | yamlcat --json 351 - {"uppercase": 250} 352 - 353 - Test special float values 354 - 355 - $ echo 'pos_inf: .inf' | yamlcat --json 356 - {"pos_inf": inf} 357 - 358 - $ echo 'neg_inf: -.inf' | yamlcat --json 359 - {"neg_inf": -inf} 360 - 361 - $ echo 'not_a_num: .nan' | yamlcat --json 362 - {"not_a_num": nan} 363 - 364 - $ echo '[.inf, -.inf, .nan]' | yamlcat --json 365 - [inf, -inf, nan] 366 - 367 - Test hexadecimal numbers 368 - 369 - $ echo 'hex_lower: 0x1a' | yamlcat --json 370 - {"hex_lower": 26} 371 - 372 - $ echo 'hex_upper: 0x1A' | yamlcat --json 373 - {"hex_upper": 26} 374 - 375 - $ echo 'hex_mixed: 0xDeadBeef' | yamlcat --json 376 - {"hex_mixed": 3735928559} 377 - 378 - Test octal numbers 379 - 380 - $ echo 'octal: 0o17' | yamlcat --json 381 - {"octal": 15} 382 - 383 - $ echo 'octal_zero: 0o0' | yamlcat --json 384 - {"octal_zero": 0} 385 - 386 - $ echo 'octal_large: 0o755' | yamlcat --json 387 - {"octal_large": 493} 388 - 389 - Test mixed special values 390 - 391 - $ echo 'null: null 392 - > bool: true 393 - > int: 42 394 - > float: 3.14 395 - > sci: 1.5e10' | yamlcat --json 396 - {"null": null, "bool": true, "int": 42, "float": 3.14, "sci": 15000000000} 397 - 398 - Test quoted vs unquoted special values 399 - 400 - $ echo 'unquoted_null: null 401 - > quoted_null: "null"' | yamlcat --json 402 - {"unquoted_null": null, "quoted_null": "null"} 403 - 404 - $ echo 'unquoted_bool: true 405 - > quoted_bool: "true"' | yamlcat --json 406 - {"unquoted_bool": true, "quoted_bool": "true"} 407 - 408 - $ echo 'unquoted_num: 42 409 - > quoted_num: "42"' | yamlcat --json 410 - {"unquoted_num": 42, "quoted_num": "42"} 411 - 412 - Test edge case numbers 413 - 414 - $ echo 'positive_zero: +0.0' | yamlcat --json 415 - {"positive_zero": 0} 416 - 417 - $ echo 'negative_zero: -0.0' | yamlcat --json 418 - {"negative_zero": -0} 419 - 420 - $ echo 'very_large: 1.0e100' | yamlcat --json 421 - {"very_large": 1e+100} 422 - 423 - $ echo 'very_small: 1.0e-100' | yamlcat --json 424 - {"very_small": 1e-100} 425 - 426 - Test YAML 1.2 boolean strictness (only true/false are booleans) 427 - 428 - $ echo 'yes: yes' | yamlcat --json 429 - {"yes": true} 430 - 431 - $ echo 'no: no' | yamlcat --json 432 - {"no": false} 433 - 434 - $ echo 'on: on' | yamlcat --json 435 - {"on": true} 436 - 437 - $ echo 'off: off' | yamlcat --json 438 - {"off": false} 439 - 440 - $ echo 'True: True' | yamlcat --json 441 - {"True": true} 442 - 443 - $ echo 'FALSE: FALSE' | yamlcat --json 444 - {"FALSE": false}
-161
yaml/ocaml-yamle/tests/cram/yamlcat.t
··· 1 - Test yamlcat with simple YAML 2 - 3 - $ echo 'hello: world' | yamlcat 4 - hello: world 5 - 6 - $ echo 'name: Alice 7 - > age: 30' | yamlcat 8 - name: Alice 9 - age: 30 10 - 11 - Test nested mappings 12 - 13 - $ echo 'server: 14 - > host: localhost 15 - > port: 8080 16 - > database: 17 - > name: mydb' | yamlcat 18 - server: 19 - host: localhost 20 - port: 8080 21 - database: 22 - name: mydb 23 - 24 - Test sequences 25 - 26 - $ echo '- apple 27 - > - banana 28 - > - cherry' | yamlcat 29 - - apple 30 - - banana 31 - - cherry 32 - 33 - Test mapping with sequence value 34 - 35 - $ echo 'fruits: 36 - > - apple 37 - > - banana' | yamlcat 38 - fruits: 39 - - apple 40 - - banana 41 - 42 - Test flow style output 43 - 44 - $ echo 'name: Alice 45 - > hobbies: 46 - > - reading 47 - > - coding' | yamlcat --flow 48 - {name: Alice, hobbies: [reading, coding]} 49 - 50 - Test JSON output 51 - 52 - $ echo 'name: Alice 53 - > age: 30' | yamlcat --json 54 - {"name": "Alice", "age": 30} 55 - 56 - Test seq.yml file (multiline plain scalar) 57 - 58 - $ yamlcat ../yaml/seq.yml 59 - - hello - whats - up 60 - - foo 61 - - bar 62 - 63 - Test seq.yml roundtrip preserves data 64 - 65 - $ yamlcat --json ../yaml/seq.yml 66 - ["hello - whats - up", "foo", "bar"] 67 - 68 - Test cohttp.yml 69 - 70 - $ yamlcat ../yaml/cohttp.yml 71 - language: c 72 - sudo: false 73 - services: 74 - - docker 75 - install: 'wget https://raw.githubusercontent.com/ocaml/ocaml-travisci-skeleton/master/.travis-docker.sh' 76 - script: bash -ex ./.travis-docker.sh 77 - env: 78 - global: 79 - - "EXTRA_REMOTES=\"https://github.com/mirage/mirage-dev.git\"" 80 - - "PINS=\"cohttp-top:. cohttp-async:. cohttp-lwt-unix:. cohttp-lwt-jsoo:. cohttp-lwt:. cohttp-mirage:. cohttp:.\"" 81 - matrix: 82 - - "PACKAGE=\"cohttp\" DISTRO=\"alpine-3.5\" OCAML_VERSION=\"4.06.0\"" 83 - - "PACKAGE=\"cohttp-async\" DISTRO=\"alpine\" OCAML_VERSION=\"4.06.0\"" 84 - - "PACKAGE=\"cohttp-lwt\" DISTRO=\"debian-unstable\" OCAML_VERSION=\"4.03.0\"" 85 - - "PACKAGE=\"cohttp-mirage\" DISTRO=\"debian-unstable\" OCAML_VERSION=\"4.03.0\"" 86 - notifications: 87 - webhooks: 88 - urls: 89 - - 'https://webhooks.gitter.im/e/6ee5059c7420709f4ad1' 90 - on_success: change 91 - on_failure: always 92 - on_start: false 93 - 94 - Test cohttp.yml roundtrip with JSON 95 - 96 - $ yamlcat --json ../yaml/cohttp.yml 97 - {"language": "c", "sudo": false, "services": ["docker"], "install": "wget https://raw.githubusercontent.com/ocaml/ocaml-travisci-skeleton/master/.travis-docker.sh", "script": "bash -ex ./.travis-docker.sh", "env": {"global": ["EXTRA_REMOTES=\"https://github.com/mirage/mirage-dev.git\"", "PINS=\"cohttp-top:. cohttp-async:. cohttp-lwt-unix:. cohttp-lwt-jsoo:. cohttp-lwt:. cohttp-mirage:. cohttp:.\""], "matrix": ["PACKAGE=\"cohttp\" DISTRO=\"alpine-3.5\" OCAML_VERSION=\"4.06.0\"", "PACKAGE=\"cohttp-async\" DISTRO=\"alpine\" OCAML_VERSION=\"4.06.0\"", "PACKAGE=\"cohttp-lwt\" DISTRO=\"debian-unstable\" OCAML_VERSION=\"4.03.0\"", "PACKAGE=\"cohttp-mirage\" DISTRO=\"debian-unstable\" OCAML_VERSION=\"4.03.0\""]}, "notifications": {"webhooks": {"urls": ["https://webhooks.gitter.im/e/6ee5059c7420709f4ad1"], "on_success": "change", "on_failure": "always", "on_start": false}}} 98 - 99 - Test special values 100 - 101 - $ echo 'null_val: null 102 - > bool_true: true 103 - > bool_false: false 104 - > number: 42 105 - > float: 3.14' | yamlcat --json 106 - {"null_val": null, "bool_true": true, "bool_false": false, "number": 42, "float": 3.14} 107 - 108 - Test quoted strings 109 - 110 - $ echo 'single: '"'"'hello world'"'"' 111 - > double: "hello world"' | yamlcat 112 - single: hello world 113 - double: hello world 114 - 115 - Test literal block scalar 116 - 117 - $ echo 'text: | 118 - > line one 119 - > line two' | yamlcat --json 120 - {"text": "line one\nline two\n"} 121 - 122 - Test folded block scalar 123 - 124 - $ echo 'text: > 125 - > line one 126 - > line two' | yamlcat --json 127 - {"text": "line one line two\n"} 128 - 129 - Test linuxkit.yml (sequences of mappings) 130 - 131 - $ yamlcat ../yaml/linuxkit.yml | head -30 132 - kernel: 133 - image: 'linuxkit/kernel:4.9.40' 134 - cmdline: console=tty0 console=ttyS0 135 - init: 136 - - 'linuxkit/init:906e174b3f2e07f97d6fd693a2e8518e98dafa58' 137 - - 'linuxkit/runc:90e45f13e1d0a0983f36ef854621e3eac91cf541' 138 - - 'linuxkit/containerd:7c986fb7df33bea73b5c8097b46989e46f49d875' 139 - - 'linuxkit/ca-certificates:e44b0a66df5a102c0e220f0066b0d904710dcb10' 140 - onboot: 141 - - name: sysctl 142 - image: 'linuxkit/sysctl:184c914d23a017062d7b53d7fc1dfaf47764bef6' 143 - - name: dhcpcd 144 - image: 'linuxkit/dhcpcd:f3f5413abb78fae9020e35bd4788fa93df4530b7' 145 - command: 146 - - /sbin/dhcpcd 147 - - '--nobackground' 148 - - '-f' 149 - - /dhcpcd.conf 150 - - '-1' 151 - onshutdown: 152 - - name: shutdown 153 - image: 'busybox:latest' 154 - command: 155 - - /bin/echo 156 - - so long and thanks for all the fish 157 - services: 158 - - name: getty 159 - image: 'linuxkit/getty:2c841cdc34396e3fa8f25b62d112808f63f16df6' 160 - env: 161 - - INSECURE=true
-9
yaml/ocaml-yamle/tests/dune
··· 1 - (test 2 - (name test_yamle) 3 - (modules test_yamle) 4 - (libraries yamle alcotest)) 5 - 6 - (executable 7 - (name run_all_tests) 8 - (modules run_all_tests) 9 - (libraries yamle test_suite_lib))
-389
yaml/ocaml-yamle/tests/run_all_tests.ml
··· 1 - (* Run all yaml-test-suite tests with optional HTML output *) 2 - open Yamle 3 - module TL = Test_suite_lib.Test_suite_loader 4 - module TF = Test_suite_lib.Tree_format 5 - 6 - (* Configuration - single variable for test suite path *) 7 - let test_suite_path = "../yaml-test-suite" 8 - 9 - (* HTML escape function *) 10 - let html_escape s = 11 - let buf = Buffer.create (String.length s) in 12 - String.iter (function 13 - | '<' -> Buffer.add_string buf "&lt;" 14 - | '>' -> Buffer.add_string buf "&gt;" 15 - | '&' -> Buffer.add_string buf "&amp;" 16 - | '"' -> Buffer.add_string buf "&quot;" 17 - | c -> Buffer.add_char buf c 18 - ) s; 19 - Buffer.contents buf 20 - 21 - let normalize_tree s = 22 - let lines = String.split_on_char '\n' s in 23 - let lines = List.filter (fun l -> String.trim l <> "") lines in 24 - String.concat "\n" lines 25 - 26 - type test_result = { 27 - id : string; 28 - name : string; 29 - yaml : string; 30 - expected_tree : string option; 31 - is_error_test : bool; 32 - status : [`Pass | `Fail of string | `Skip]; 33 - output : string; 34 - } 35 - 36 - let run_test (test : TL.test_case) : test_result = 37 - let base = { 38 - id = test.id; 39 - name = test.name; 40 - yaml = test.yaml; 41 - expected_tree = test.tree; 42 - is_error_test = test.fail; 43 - status = `Skip; 44 - output = ""; 45 - } in 46 - if test.fail then begin 47 - try 48 - let parser = Parser.of_string test.yaml in 49 - let events = Parser.to_list parser in 50 - let tree = TF.of_spanned_events events in 51 - { base with 52 - status = `Fail "Expected parsing to fail"; 53 - output = tree; 54 - } 55 - with 56 - | Yamle_error e -> 57 - { base with 58 - status = `Pass; 59 - output = Format.asprintf "%a" Error.pp e; 60 - } 61 - | exn -> 62 - { base with 63 - status = `Pass; 64 - output = Printexc.to_string exn; 65 - } 66 - end 67 - else begin 68 - match test.tree with 69 - | None -> 70 - (* No expected tree - check if json indicates expected success *) 71 - (match test.json with 72 - | Some _ -> 73 - (* Has json output, so should parse successfully *) 74 - (try 75 - let parser = Parser.of_string test.yaml in 76 - let events = Parser.to_list parser in 77 - let tree = TF.of_spanned_events events in 78 - { base with status = `Pass; output = tree } 79 - with exn -> 80 - { base with 81 - status = `Fail (Printf.sprintf "Should parse but got: %s" (Printexc.to_string exn)); 82 - output = Printexc.to_string exn; 83 - }) 84 - | None -> 85 - (* No tree, no json, no fail - ambiguous edge case, skip *) 86 - { base with status = `Skip; output = "(no expected tree or json)" }) 87 - | Some expected -> 88 - try 89 - let parser = Parser.of_string test.yaml in 90 - let events = Parser.to_list parser in 91 - let actual = TF.of_spanned_events events in 92 - let expected_norm = normalize_tree expected in 93 - let actual_norm = normalize_tree actual in 94 - if expected_norm = actual_norm then 95 - { base with status = `Pass; output = actual } 96 - else 97 - { base with 98 - status = `Fail (Printf.sprintf "Tree mismatch"); 99 - output = Printf.sprintf "Expected:\n%s\n\nActual:\n%s" expected_norm actual_norm; 100 - } 101 - with exn -> 102 - { base with 103 - status = `Fail (Printf.sprintf "Exception: %s" (Printexc.to_string exn)); 104 - output = Printexc.to_string exn; 105 - } 106 - end 107 - 108 - let status_class = function 109 - | `Pass -> "pass" 110 - | `Fail _ -> "fail" 111 - | `Skip -> "skip" 112 - 113 - let status_text = function 114 - | `Pass -> "PASS" 115 - | `Fail _ -> "FAIL" 116 - | `Skip -> "SKIP" 117 - 118 - let generate_html results output_file = 119 - let oc = open_out output_file in 120 - let pass_count = List.length (List.filter (fun r -> r.status = `Pass) results) in 121 - let fail_count = List.length (List.filter (fun r -> match r.status with `Fail _ -> true | _ -> false) results) in 122 - let skip_count = List.length (List.filter (fun r -> r.status = `Skip) results) in 123 - let total = List.length results in 124 - 125 - Printf.fprintf oc {|<!DOCTYPE html> 126 - <html lang="en"> 127 - <head> 128 - <meta charset="UTF-8"> 129 - <meta name="viewport" content="width=device-width, initial-scale=1.0"> 130 - <title>Yamle Test Results</title> 131 - <style> 132 - :root { 133 - --pass-color: #22c55e; 134 - --fail-color: #ef4444; 135 - --skip-color: #f59e0b; 136 - --bg-color: #1a1a2e; 137 - --card-bg: #16213e; 138 - --text-color: #e2e8f0; 139 - --border-color: #334155; 140 - } 141 - * { box-sizing: border-box; margin: 0; padding: 0; } 142 - body { 143 - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 144 - background: var(--bg-color); 145 - color: var(--text-color); 146 - line-height: 1.6; 147 - padding: 2rem; 148 - } 149 - .container { max-width: 1400px; margin: 0 auto; } 150 - h1 { margin-bottom: 1.5rem; font-size: 2rem; } 151 - .summary { 152 - display: flex; 153 - gap: 1rem; 154 - margin-bottom: 2rem; 155 - flex-wrap: wrap; 156 - } 157 - .stat { 158 - background: var(--card-bg); 159 - padding: 1rem 1.5rem; 160 - border-radius: 8px; 161 - border-left: 4px solid var(--border-color); 162 - } 163 - .stat.pass { border-left-color: var(--pass-color); } 164 - .stat.fail { border-left-color: var(--fail-color); } 165 - .stat.skip { border-left-color: var(--skip-color); } 166 - .stat-value { font-size: 2rem; font-weight: bold; } 167 - .stat-label { font-size: 0.875rem; opacity: 0.8; } 168 - .filters { 169 - margin-bottom: 1.5rem; 170 - display: flex; 171 - gap: 0.5rem; 172 - flex-wrap: wrap; 173 - } 174 - .filter-btn { 175 - padding: 0.5rem 1rem; 176 - border: 1px solid var(--border-color); 177 - background: var(--card-bg); 178 - color: var(--text-color); 179 - border-radius: 4px; 180 - cursor: pointer; 181 - transition: all 0.2s; 182 - } 183 - .filter-btn:hover { border-color: var(--text-color); } 184 - .filter-btn.active { background: var(--text-color); color: var(--bg-color); } 185 - .search { 186 - padding: 0.5rem 1rem; 187 - border: 1px solid var(--border-color); 188 - background: var(--card-bg); 189 - color: var(--text-color); 190 - border-radius: 4px; 191 - width: 200px; 192 - } 193 - .tests { display: flex; flex-direction: column; gap: 1rem; } 194 - .test { 195 - background: var(--card-bg); 196 - border-radius: 8px; 197 - border: 1px solid var(--border-color); 198 - overflow: hidden; 199 - } 200 - .test-header { 201 - padding: 1rem; 202 - display: flex; 203 - align-items: center; 204 - gap: 1rem; 205 - cursor: pointer; 206 - border-bottom: 1px solid var(--border-color); 207 - } 208 - .test-header:hover { background: rgba(255,255,255,0.05); } 209 - .badge { 210 - padding: 0.25rem 0.5rem; 211 - border-radius: 4px; 212 - font-size: 0.75rem; 213 - font-weight: bold; 214 - text-transform: uppercase; 215 - } 216 - .badge.pass { background: var(--pass-color); color: #000; } 217 - .badge.fail { background: var(--fail-color); color: #fff; } 218 - .badge.skip { background: var(--skip-color); color: #000; } 219 - .badge.error-test { background: #8b5cf6; color: #fff; margin-left: auto; } 220 - .test-id { font-family: monospace; font-weight: bold; } 221 - .test-name { opacity: 0.8; flex: 1; } 222 - .test-content { display: none; padding: 1rem; } 223 - .test.expanded .test-content { display: block; } 224 - .section { margin-bottom: 1rem; } 225 - .section-title { 226 - font-size: 0.875rem; 227 - text-transform: uppercase; 228 - opacity: 0.6; 229 - margin-bottom: 0.5rem; 230 - letter-spacing: 0.05em; 231 - } 232 - pre { 233 - background: #0f172a; 234 - padding: 1rem; 235 - border-radius: 4px; 236 - overflow-x: auto; 237 - font-size: 0.875rem; 238 - white-space: pre-wrap; 239 - word-break: break-all; 240 - } 241 - .expand-icon { transition: transform 0.2s; } 242 - .test.expanded .expand-icon { transform: rotate(90deg); } 243 - </style> 244 - </head> 245 - <body> 246 - <div class="container"> 247 - <h1>Yamle Test Results</h1> 248 - <div class="summary"> 249 - <div class="stat pass"> 250 - <div class="stat-value">%d</div> 251 - <div class="stat-label">Passed</div> 252 - </div> 253 - <div class="stat fail"> 254 - <div class="stat-value">%d</div> 255 - <div class="stat-label">Failed</div> 256 - </div> 257 - <div class="stat skip"> 258 - <div class="stat-value">%d</div> 259 - <div class="stat-label">Skipped</div> 260 - </div> 261 - <div class="stat"> 262 - <div class="stat-value">%d</div> 263 - <div class="stat-label">Total</div> 264 - </div> 265 - </div> 266 - <div class="filters"> 267 - <button class="filter-btn active" data-filter="all">All</button> 268 - <button class="filter-btn" data-filter="pass">Pass</button> 269 - <button class="filter-btn" data-filter="fail">Fail</button> 270 - <button class="filter-btn" data-filter="skip">Skip</button> 271 - <input type="text" class="search" placeholder="Search by ID or name..."> 272 - </div> 273 - <div class="tests"> 274 - |} pass_count fail_count skip_count total; 275 - 276 - List.iter (fun result -> 277 - let error_badge = if result.is_error_test then 278 - {|<span class="badge error-test">Error Test</span>|} 279 - else "" in 280 - Printf.fprintf oc {| <div class="test" data-status="%s" data-id="%s" data-name="%s"> 281 - <div class="test-header" onclick="this.parentElement.classList.toggle('expanded')"> 282 - <span class="expand-icon">▶</span> 283 - <span class="badge %s">%s</span> 284 - <span class="test-id">%s</span> 285 - <span class="test-name">%s</span> 286 - %s 287 - </div> 288 - <div class="test-content"> 289 - <div class="section"> 290 - <div class="section-title">YAML Input</div> 291 - <pre>%s</pre> 292 - </div> 293 - <div class="section"> 294 - <div class="section-title">Yamle Output</div> 295 - <pre>%s</pre> 296 - </div> 297 - </div> 298 - </div> 299 - |} 300 - (status_class result.status) 301 - (html_escape result.id) 302 - (html_escape (String.lowercase_ascii result.name)) 303 - (status_class result.status) 304 - (status_text result.status) 305 - (html_escape result.id) 306 - (html_escape result.name) 307 - error_badge 308 - (html_escape result.yaml) 309 - (html_escape result.output) 310 - ) results; 311 - 312 - Printf.fprintf oc {| </div> 313 - </div> 314 - <script> 315 - document.querySelectorAll('.filter-btn').forEach(btn => { 316 - btn.addEventListener('click', () => { 317 - document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); 318 - btn.classList.add('active'); 319 - filterTests(); 320 - }); 321 - }); 322 - document.querySelector('.search').addEventListener('input', filterTests); 323 - function filterTests() { 324 - const filter = document.querySelector('.filter-btn.active').dataset.filter; 325 - const search = document.querySelector('.search').value.toLowerCase(); 326 - document.querySelectorAll('.test').forEach(test => { 327 - const status = test.dataset.status; 328 - const id = test.dataset.id.toLowerCase(); 329 - const name = test.dataset.name; 330 - const matchesFilter = filter === 'all' || status === filter; 331 - const matchesSearch = !search || id.includes(search) || name.includes(search); 332 - test.style.display = matchesFilter && matchesSearch ? '' : 'none'; 333 - }); 334 - } 335 - </script> 336 - </body> 337 - </html> 338 - |}; 339 - close_out oc 340 - 341 - let () = 342 - let html_output = ref None in 343 - let show_skipped = ref false in 344 - let args = [ 345 - "--html", Arg.String (fun s -> html_output := Some s), 346 - "<file> Generate HTML report to file"; 347 - "--show-skipped", Arg.Set show_skipped, 348 - " Show details of skipped tests"; 349 - ] in 350 - Arg.parse args (fun _ -> ()) "Usage: run_all_tests [--html <file>] [--show-skipped]"; 351 - 352 - let all_tests = TL.load_directory test_suite_path in 353 - Printf.printf "Total tests loaded: %d\n%!" (List.length all_tests); 354 - 355 - let results = List.map run_test all_tests in 356 - 357 - let pass_count = List.length (List.filter (fun r -> r.status = `Pass) results) in 358 - let fail_count = List.length (List.filter (fun r -> match r.status with `Fail _ -> true | _ -> false) results) in 359 - let skip_count = List.length (List.filter (fun r -> r.status = `Skip) results) in 360 - 361 - Printf.printf "\nResults: %d pass, %d fail, %d skip (total: %d)\n%!" 362 - pass_count fail_count skip_count (pass_count + fail_count + skip_count); 363 - 364 - if fail_count > 0 then begin 365 - Printf.printf "\nFailing tests:\n"; 366 - List.iter (fun r -> 367 - match r.status with 368 - | `Fail msg -> Printf.printf " %s: %s - %s\n" r.id r.name msg 369 - | _ -> () 370 - ) results 371 - end; 372 - 373 - if !show_skipped && skip_count > 0 then begin 374 - Printf.printf "\nSkipped tests (no expected tree):\n"; 375 - List.iter (fun r -> 376 - if r.status = `Skip then begin 377 - Printf.printf " %s: %s\n" r.id r.name; 378 - Printf.printf " YAML (%d chars): %S\n" (String.length r.yaml) 379 - (if String.length r.yaml <= 60 then r.yaml 380 - else String.sub r.yaml 0 60 ^ "...") 381 - end 382 - ) results 383 - end; 384 - 385 - match !html_output with 386 - | Some file -> 387 - generate_html results file; 388 - Printf.printf "\nHTML report generated: %s\n" file 389 - | None -> ()
-4
yaml/ocaml-yamle/tests/test_suite_lib/dune
··· 1 - (library 2 - (name test_suite_lib) 3 - (modules test_suite_loader tree_format) 4 - (libraries yamle))
-97
yaml/ocaml-yamle/tests/test_suite_lib/test_suite_loader.ml
··· 1 - (* Load yaml-test-suite test cases from data branch format *) 2 - 3 - type test_case = { 4 - id : string; 5 - name : string; 6 - yaml : string; 7 - tree : string option; 8 - json : string option; 9 - fail : bool; 10 - } 11 - 12 - let read_file path = 13 - try 14 - let ic = open_in path in 15 - let n = in_channel_length ic in 16 - let s = really_input_string ic n in 17 - close_in ic; 18 - Some s 19 - with _ -> None 20 - 21 - let read_file_required path = 22 - match read_file path with 23 - | Some s -> s 24 - | None -> "" 25 - 26 - let file_exists path = 27 - Sys.file_exists path 28 - 29 - let is_directory path = 30 - Sys.file_exists path && Sys.is_directory path 31 - 32 - (* Load a single test from a directory *) 33 - let load_test_dir base_id dir_path = 34 - let name_file = Filename.concat dir_path "===" in 35 - let yaml_file = Filename.concat dir_path "in.yaml" in 36 - let tree_file = Filename.concat dir_path "test.event" in 37 - let json_file = Filename.concat dir_path "in.json" in 38 - let error_file = Filename.concat dir_path "error" in 39 - 40 - (* Must have in.yaml to be a valid test *) 41 - if not (file_exists yaml_file) then None 42 - else 43 - let name = match read_file name_file with 44 - | Some s -> String.trim s 45 - | None -> base_id 46 - in 47 - let yaml = read_file_required yaml_file in 48 - let tree = read_file tree_file in 49 - let json = read_file json_file in 50 - let fail = file_exists error_file in 51 - Some { id = base_id; name; yaml; tree; json; fail } 52 - 53 - (* Load tests from a test ID directory (may have subdirectories for variants) *) 54 - let load_test_id test_suite_path test_id = 55 - let dir_path = Filename.concat test_suite_path test_id in 56 - if not (is_directory dir_path) then [] 57 - else 58 - (* Check if this directory has variant subdirectories (00, 01, etc.) *) 59 - let entries = Sys.readdir dir_path in 60 - let has_variants = Array.exists (fun e -> 61 - let subdir = Filename.concat dir_path e in 62 - is_directory subdir && 63 - String.length e >= 2 && 64 - e.[0] >= '0' && e.[0] <= '9' 65 - ) entries in 66 - 67 - if has_variants then 68 - (* Load each variant subdirectory *) 69 - let variants = Array.to_list entries 70 - |> List.filter (fun e -> 71 - let subdir = Filename.concat dir_path e in 72 - is_directory subdir && String.length e >= 2 && e.[0] >= '0' && e.[0] <= '9') 73 - |> List.sort String.compare 74 - in 75 - List.filter_map (fun variant -> 76 - let variant_path = Filename.concat dir_path variant in 77 - let variant_id = Printf.sprintf "%s:%s" test_id variant in 78 - load_test_dir variant_id variant_path 79 - ) variants 80 - else 81 - (* Single test in this directory *) 82 - match load_test_dir test_id dir_path with 83 - | Some t -> [t] 84 - | None -> [] 85 - 86 - let load_directory test_suite_path = 87 - if not (is_directory test_suite_path) then [] 88 - else 89 - let entries = Sys.readdir test_suite_path in 90 - let test_ids = Array.to_list entries 91 - |> List.filter (fun e -> 92 - is_directory (Filename.concat test_suite_path e) && 93 - String.length e >= 4 && (* Test IDs are 4 chars *) 94 - e.[0] >= '0' && e.[0] <= 'Z') (* Start with alphanumeric *) 95 - |> List.sort String.compare 96 - in 97 - List.concat_map (load_test_id test_suite_path) test_ids
-69
yaml/ocaml-yamle/tests/test_suite_lib/tree_format.ml
··· 1 - (* Format parser events as tree notation compatible with yaml-test-suite *) 2 - 3 - open Yamle 4 - 5 - let escape_string s = 6 - let buf = Buffer.create (String.length s * 2) in 7 - String.iter (fun c -> 8 - match c with 9 - | '\n' -> Buffer.add_string buf "\\n" 10 - | '\t' -> Buffer.add_string buf "\\t" 11 - | '\r' -> Buffer.add_string buf "\\r" 12 - | '\\' -> Buffer.add_string buf "\\\\" 13 - | '\x00' -> Buffer.add_string buf "\\0" 14 - | '\x07' -> Buffer.add_string buf "\\a" 15 - | '\x08' -> Buffer.add_string buf "\\b" 16 - | '\x0b' -> Buffer.add_string buf "\\v" 17 - | '\x0c' -> Buffer.add_string buf "\\f" 18 - | '\x1b' -> Buffer.add_string buf "\\e" 19 - | '\xa0' -> Buffer.add_string buf "\\_" 20 - | c -> Buffer.add_char buf c 21 - ) s; 22 - Buffer.contents buf 23 - 24 - let style_char = function 25 - | Scalar_style.Plain -> ':' 26 - | Scalar_style.Single_quoted -> '\'' 27 - | Scalar_style.Double_quoted -> '"' 28 - | Scalar_style.Literal -> '|' 29 - | Scalar_style.Folded -> '>' 30 - | Scalar_style.Any -> ':' 31 - 32 - let format_event { Event.event; span = _span } = 33 - match event with 34 - | Event.Stream_start _ -> "+STR" 35 - | Event.Stream_end -> "-STR" 36 - | Event.Document_start { implicit; _ } -> 37 - if implicit then "+DOC" 38 - else "+DOC ---" 39 - | Event.Document_end { implicit } -> 40 - if implicit then "-DOC" 41 - else "-DOC ..." 42 - | Event.Mapping_start { anchor; tag; style; _ } -> 43 - let anchor_str = match anchor with Some a -> " &" ^ a | None -> "" in 44 - let tag_str = match tag with Some t -> " <" ^ t ^ ">" | None -> "" in 45 - let flow_str = match style with Layout_style.Flow -> " {}" | _ -> "" in 46 - Printf.sprintf "+MAP%s%s%s" flow_str anchor_str tag_str 47 - | Event.Mapping_end -> "-MAP" 48 - | Event.Sequence_start { anchor; tag; style; _ } -> 49 - let anchor_str = match anchor with Some a -> " &" ^ a | None -> "" in 50 - let tag_str = match tag with Some t -> " <" ^ t ^ ">" | None -> "" in 51 - let flow_str = match style with Layout_style.Flow -> " []" | _ -> "" in 52 - Printf.sprintf "+SEQ%s%s%s" flow_str anchor_str tag_str 53 - | Event.Sequence_end -> "-SEQ" 54 - | Event.Scalar { anchor; tag; value; style; _ } -> 55 - let anchor_str = match anchor with Some a -> " &" ^ a | None -> "" in 56 - let tag_str = match tag with Some t -> " <" ^ t ^ ">" | None -> "" in 57 - let style_c = style_char style in 58 - Printf.sprintf "=VAL%s%s %c%s" anchor_str tag_str style_c (escape_string value) 59 - | Event.Alias { anchor } -> 60 - Printf.sprintf "=ALI *%s" anchor 61 - 62 - let of_spanned_events events = 63 - let buf = Buffer.create 256 in 64 - List.iter (fun (e : Event.spanned) -> 65 - let line = format_event e in 66 - Buffer.add_string buf line; 67 - Buffer.add_char buf '\n' 68 - ) events; 69 - Buffer.contents buf
-353
yaml/ocaml-yamle/tests/test_yamle.ml
··· 1 - (** Tests for the Yamle library *) 2 - 3 - open Yamle 4 - 5 - (** Test helpers *) 6 - 7 - let check_value msg expected actual = 8 - Alcotest.(check bool) msg true (Value.equal expected actual) 9 - 10 - let _check_string msg expected actual = 11 - Alcotest.(check string) msg expected actual 12 - 13 - (** Scanner tests *) 14 - 15 - let test_scanner_simple () = 16 - let scanner = Scanner.of_string "hello: world" in 17 - let tokens = Scanner.to_list scanner in 18 - let token_types = List.map (fun (t : Token.spanned) -> t.token) tokens in 19 - Alcotest.(check int) "token count" 8 (List.length token_types); 20 - (* Stream_start, Block_mapping_start, Key, Scalar, Value, Scalar, Block_end, Stream_end *) 21 - match token_types with 22 - | Token.Stream_start _ :: Token.Block_mapping_start :: Token.Key :: 23 - Token.Scalar { value = "hello"; _ } :: Token.Value :: 24 - Token.Scalar { value = "world"; _ } :: Token.Block_end :: Token.Stream_end :: [] -> 25 - () 26 - | _ -> 27 - Alcotest.fail "unexpected token sequence" 28 - 29 - let test_scanner_sequence () = 30 - let scanner = Scanner.of_string "- one\n- two\n- three" in 31 - let tokens = Scanner.to_list scanner in 32 - Alcotest.(check bool) "has tokens" true (List.length tokens > 0) 33 - 34 - let test_scanner_flow () = 35 - let scanner = Scanner.of_string "[1, 2, 3]" in 36 - let tokens = Scanner.to_list scanner in 37 - let has_flow_start = List.exists (fun (t : Token.spanned) -> 38 - match t.token with Token.Flow_sequence_start -> true | _ -> false 39 - ) tokens in 40 - Alcotest.(check bool) "has flow sequence start" true has_flow_start 41 - 42 - let scanner_tests = [ 43 - "simple mapping", `Quick, test_scanner_simple; 44 - "sequence", `Quick, test_scanner_sequence; 45 - "flow sequence", `Quick, test_scanner_flow; 46 - ] 47 - 48 - (** Parser tests *) 49 - 50 - let test_parser_events () = 51 - let parser = Parser.of_string "key: value" in 52 - let events = Parser.to_list parser in 53 - Alcotest.(check bool) "has events" true (List.length events > 0); 54 - let has_stream_start = List.exists (fun (e : Event.spanned) -> 55 - match e.event with Event.Stream_start _ -> true | _ -> false 56 - ) events in 57 - Alcotest.(check bool) "has stream start" true has_stream_start 58 - 59 - let test_parser_sequence_events () = 60 - let parser = Parser.of_string "- a\n- b" in 61 - let events = Parser.to_list parser in 62 - let has_seq_start = List.exists (fun (e : Event.spanned) -> 63 - match e.event with Event.Sequence_start _ -> true | _ -> false 64 - ) events in 65 - Alcotest.(check bool) "has sequence start" true has_seq_start 66 - 67 - let parser_tests = [ 68 - "parse events", `Quick, test_parser_events; 69 - "sequence events", `Quick, test_parser_sequence_events; 70 - ] 71 - 72 - (** Value parsing tests *) 73 - 74 - let test_parse_null () = 75 - check_value "null" `Null (of_string "null"); 76 - check_value "~" `Null (of_string "~"); 77 - check_value "empty" `Null (of_string "") 78 - 79 - let test_parse_bool () = 80 - check_value "true" (`Bool true) (of_string "true"); 81 - check_value "false" (`Bool false) (of_string "false"); 82 - check_value "yes" (`Bool true) (of_string "yes"); 83 - check_value "no" (`Bool false) (of_string "no") 84 - 85 - let test_parse_number () = 86 - check_value "integer" (`Float 42.0) (of_string "42"); 87 - check_value "negative" (`Float (-17.0)) (of_string "-17"); 88 - check_value "float" (`Float 3.14) (of_string "3.14") 89 - 90 - let test_parse_string () = 91 - check_value "plain" (`String "hello") (of_string "hello world" |> function `String s -> `String (String.sub s 0 5) | v -> v); 92 - check_value "quoted" (`String "hello") (of_string {|"hello"|}) 93 - 94 - let test_parse_sequence () = 95 - let result = of_string "- one\n- two\n- three" in 96 - match result with 97 - | `A [_; _; _] -> () 98 - | _ -> Alcotest.fail "expected sequence with 3 elements" 99 - 100 - let test_parse_mapping () = 101 - let result = of_string "name: Alice\nage: 30" in 102 - match result with 103 - | `O pairs when List.length pairs = 2 -> () 104 - | _ -> Alcotest.fail "expected mapping with 2 pairs" 105 - 106 - let test_parse_nested () = 107 - let yaml = {| 108 - person: 109 - name: Bob 110 - hobbies: 111 - - reading 112 - - coding 113 - |} in 114 - let result = of_string yaml in 115 - match result with 116 - | `O [("person", `O _)] -> () 117 - | _ -> Alcotest.fail "expected nested structure" 118 - 119 - let test_parse_flow_sequence () = 120 - let result = of_string "[1, 2, 3]" in 121 - match result with 122 - | `A [`Float 1.0; `Float 2.0; `Float 3.0] -> () 123 - | _ -> Alcotest.fail "expected flow sequence [1, 2, 3]" 124 - 125 - let test_parse_flow_mapping () = 126 - let result = of_string "{a: 1, b: 2}" in 127 - match result with 128 - | `O [("a", `Float 1.0); ("b", `Float 2.0)] -> () 129 - | _ -> Alcotest.fail "expected flow mapping {a: 1, b: 2}" 130 - 131 - let test_parse_flow_mapping_trailing_comma () = 132 - let result = of_string "{ a: 1, }" in 133 - match result with 134 - | `O [("a", `Float 1.0)] -> () 135 - | `O pairs -> 136 - Alcotest.failf "expected 1 pair but got %d pairs (trailing comma should not create empty entry)" 137 - (List.length pairs) 138 - | _ -> Alcotest.fail "expected flow mapping with 1 pair" 139 - 140 - let value_tests = [ 141 - "parse null", `Quick, test_parse_null; 142 - "parse bool", `Quick, test_parse_bool; 143 - "parse number", `Quick, test_parse_number; 144 - "parse string", `Quick, test_parse_string; 145 - "parse sequence", `Quick, test_parse_sequence; 146 - "parse mapping", `Quick, test_parse_mapping; 147 - "parse nested", `Quick, test_parse_nested; 148 - "parse flow sequence", `Quick, test_parse_flow_sequence; 149 - "parse flow mapping", `Quick, test_parse_flow_mapping; 150 - "flow mapping trailing comma", `Quick, test_parse_flow_mapping_trailing_comma; 151 - ] 152 - 153 - (** Emitter tests *) 154 - 155 - let test_emit_null () = 156 - let result = to_string `Null in 157 - Alcotest.(check bool) "contains null" true (String.length result > 0) 158 - 159 - let starts_with prefix s = 160 - String.length s >= String.length prefix && 161 - String.sub s 0 (String.length prefix) = prefix 162 - 163 - let test_emit_mapping () = 164 - let value = `O [("name", `String "Alice"); ("age", `Float 30.0)] in 165 - let result = to_string value in 166 - let trimmed = String.trim result in 167 - Alcotest.(check bool) "contains name" true (starts_with "name" trimmed || starts_with "\"name\"" trimmed) 168 - 169 - let test_roundtrip_simple () = 170 - let yaml = "name: Alice" in 171 - let value = of_string yaml in 172 - let _ = to_string value in 173 - (* Just check it doesn't crash *) 174 - () 175 - 176 - let test_roundtrip_sequence () = 177 - let yaml = "- one\n- two\n- three" in 178 - let value = of_string yaml in 179 - match value with 180 - | `A items when List.length items = 3 -> 181 - let _ = to_string value in 182 - () 183 - | _ -> Alcotest.fail "roundtrip failed" 184 - 185 - let emitter_tests = [ 186 - "emit null", `Quick, test_emit_null; 187 - "emit mapping", `Quick, test_emit_mapping; 188 - "roundtrip simple", `Quick, test_roundtrip_simple; 189 - "roundtrip sequence", `Quick, test_roundtrip_sequence; 190 - ] 191 - 192 - (** YAML-specific tests *) 193 - 194 - let test_yaml_anchor () = 195 - let yaml = "&anchor hello" in 196 - let result = yaml_of_string yaml in 197 - match result with 198 - | `Scalar s when Scalar.anchor s = Some "anchor" -> () 199 - | _ -> Alcotest.fail "expected scalar with anchor" 200 - 201 - let test_yaml_alias () = 202 - let yaml = {| 203 - defaults: &defaults 204 - timeout: 30 205 - production: 206 - <<: *defaults 207 - port: 8080 208 - |} in 209 - (* Just check it parses without error *) 210 - let _ = yaml_of_string yaml in 211 - () 212 - 213 - let yaml_tests = [ 214 - "yaml anchor", `Quick, test_yaml_anchor; 215 - "yaml alias", `Quick, test_yaml_alias; 216 - ] 217 - 218 - (** Multiline scalar tests *) 219 - 220 - let test_literal_block () = 221 - let yaml = {|description: | 222 - This is a 223 - multi-line 224 - description 225 - |} in 226 - let result = of_string yaml in 227 - match result with 228 - | `O [("description", `String _)] -> () 229 - | _ -> Alcotest.fail "expected mapping with literal block" 230 - 231 - let test_folded_block () = 232 - let yaml = {|description: > 233 - This is a 234 - folded 235 - description 236 - |} in 237 - let result = of_string yaml in 238 - match result with 239 - | `O [("description", `String _)] -> () 240 - | _ -> Alcotest.fail "expected mapping with folded block" 241 - 242 - let multiline_tests = [ 243 - "literal block", `Quick, test_literal_block; 244 - "folded block", `Quick, test_folded_block; 245 - ] 246 - 247 - (** Error handling tests *) 248 - 249 - let test_error_position () = 250 - try 251 - let _ = of_string "key: [unclosed" in 252 - Alcotest.fail "expected error" 253 - with 254 - | Yamle_error e -> 255 - Alcotest.(check bool) "has span" true (e.span <> None) 256 - 257 - let error_tests = [ 258 - "error position", `Quick, test_error_position; 259 - ] 260 - 261 - (** Alias expansion limit tests (billion laughs protection) *) 262 - 263 - let test_node_limit () = 264 - (* Small bomb that would expand to 9^4 = 6561 nodes *) 265 - let yaml = {| 266 - a: &a [1,2,3,4,5,6,7,8,9] 267 - b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a] 268 - c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b] 269 - d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c] 270 - |} in 271 - (* Should fail with a small node limit *) 272 - try 273 - let _ = of_string ~max_nodes:100 yaml in 274 - Alcotest.fail "expected node limit error" 275 - with 276 - | Yamle_error e -> 277 - (match e.Error.kind with 278 - | Error.Alias_expansion_node_limit _ -> () 279 - | _ -> Alcotest.fail "expected Alias_expansion_node_limit error") 280 - 281 - let test_depth_limit () = 282 - (* Create deeply nested alias chain: 283 - *e -> [*d,*d] -> [*c,*c] -> [*b,*b] -> [*a,*a] -> [x,y,z] 284 - Each alias resolution increases depth by 1 *) 285 - let yaml = {| 286 - a: &a [x, y, z] 287 - b: &b [*a, *a] 288 - c: &c [*b, *b] 289 - d: &d [*c, *c] 290 - e: &e [*d, *d] 291 - result: *e 292 - |} in 293 - (* Should fail with a small depth limit (depth 3 means max 3 alias hops) *) 294 - try 295 - let _ = of_string ~max_depth:3 yaml in 296 - Alcotest.fail "expected depth limit error" 297 - with 298 - | Yamle_error e -> 299 - (match e.Error.kind with 300 - | Error.Alias_expansion_depth_limit _ -> () 301 - | _ -> Alcotest.fail ("expected Alias_expansion_depth_limit error, got: " ^ Error.kind_to_string e.Error.kind)) 302 - 303 - let test_normal_aliases_work () = 304 - (* Normal alias usage should work fine *) 305 - let yaml = {| 306 - defaults: &defaults 307 - timeout: 30 308 - retries: 3 309 - production: 310 - <<: *defaults 311 - port: 8080 312 - |} in 313 - let result = of_string yaml in 314 - match result with 315 - | `O _ -> () 316 - | _ -> Alcotest.fail "expected mapping" 317 - 318 - let test_resolve_aliases_false () = 319 - (* With resolve_aliases=false, aliases should remain unresolved *) 320 - let yaml = {| 321 - a: &anchor value 322 - b: *anchor 323 - |} in 324 - let result = yaml_of_string ~resolve_aliases:false yaml in 325 - (* Check that alias is preserved *) 326 - match result with 327 - | `O map -> 328 - let pairs = Mapping.members map in 329 - (match List.assoc_opt (`Scalar (Scalar.make "b")) pairs with 330 - | Some (`Alias "anchor") -> () 331 - | _ -> Alcotest.fail "expected alias to be preserved") 332 - | _ -> Alcotest.fail "expected mapping" 333 - 334 - let alias_limit_tests = [ 335 - "node limit", `Quick, test_node_limit; 336 - "depth limit", `Quick, test_depth_limit; 337 - "normal aliases work", `Quick, test_normal_aliases_work; 338 - "resolve_aliases false", `Quick, test_resolve_aliases_false; 339 - ] 340 - 341 - (** Run all tests *) 342 - 343 - let () = 344 - Alcotest.run "yamle" [ 345 - "scanner", scanner_tests; 346 - "parser", parser_tests; 347 - "value", value_tests; 348 - "emitter", emitter_tests; 349 - "yaml", yaml_tests; 350 - "multiline", multiline_tests; 351 - "errors", error_tests; 352 - "alias_limits", alias_limit_tests; 353 - ]
-24
yaml/ocaml-yamle/tests/yaml/anchor.yml
··· 1 - datetime: 2001-12-15T02:59:43.1Z 2 - datetime_with_spaces: 2001-12-14 21:59:43.10 -5 3 - date: 2002-12-14 4 - 5 - # The !!binary tag indicates that a string is actually a base64-encoded 6 - # representation of a binary blob. 7 - gif_file: !!binary | 8 - R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 9 - OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ 10 - +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC 11 - AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= 12 - 13 - # YAML also has a set type, which looks like this: 14 - set: 15 - ? item1 16 - ? item2 17 - ? item3 18 - 19 - # Like Python, sets are just maps with null values; the above is equivalent to: 20 - set2: 21 - item1: null 22 - item2: null 23 - item3: null 24 -
-125
yaml/ocaml-yamle/tests/yaml/anchors_basic.yml
··· 1 - # Basic Anchor and Alias Test Cases 2 - # Tests fundamental anchor (&) and alias (*) functionality 3 - 4 - # Test 1: Simple scalar anchor and alias 5 - --- 6 - scalar_anchor: &simple_scalar "Hello, World!" 7 - scalar_alias: *simple_scalar 8 - # Expected: both should have the value "Hello, World!" 9 - 10 - # Test 2: Numeric scalar anchor 11 - --- 12 - original: &num 42 13 - copy: *num 14 - another_copy: *num 15 - # Expected: all three should have the value 42 16 - 17 - # Test 3: Sequence anchor and alias 18 - --- 19 - original_list: &my_list 20 - - apple 21 - - banana 22 - - cherry 23 - 24 - copied_list: *my_list 25 - # Expected: both lists should be identical 26 - 27 - # Test 4: Mapping anchor and alias 28 - --- 29 - original_map: &person 30 - name: Alice 31 - age: 30 32 - city: London 33 - 34 - copied_map: *person 35 - # Expected: both maps should be identical 36 - 37 - # Test 5: Multiple anchors in same document 38 - --- 39 - defaults: &defaults 40 - timeout: 30 41 - retries: 3 42 - 43 - colors: &colors 44 - - red 45 - - green 46 - - blue 47 - 48 - config: 49 - settings: *defaults 50 - palette: *colors 51 - # Expected: config.settings should have timeout and retries, config.palette should have the color list 52 - 53 - # Test 6: Nested structure with anchor 54 - --- 55 - template: &template 56 - metadata: 57 - version: 1.0 58 - author: John Doe 59 - settings: 60 - enabled: true 61 - debug: false 62 - 63 - instance1: *template 64 - instance2: *template 65 - # Expected: both instances should be identical copies of template 66 - 67 - # Test 7: Anchor in sequence 68 - --- 69 - items: 70 - - &first_item 71 - id: 1 72 - name: First 73 - - id: 2 74 - name: Second 75 - - *first_item 76 - # Expected: first and third items should be identical 77 - 78 - # Test 8: Multiple uses of same alias 79 - --- 80 - shared_value: &shared 100 81 - calculations: 82 - base: *shared 83 - doubled: 200 # Just a value, not calculated 84 - reference: *shared 85 - another_ref: *shared 86 - # Expected: base, reference, and another_ref should all be 100 87 - 88 - # Test 9: Boolean anchor 89 - --- 90 - feature_flag: &enabled true 91 - features: 92 - login: *enabled 93 - signup: *enabled 94 - export: *enabled 95 - # Expected: all features should be true 96 - 97 - # Test 10: Null anchor 98 - --- 99 - empty: &null_value ~ 100 - values: 101 - first: *null_value 102 - second: *null_value 103 - # Expected: all should be null 104 - 105 - # Test 11: String with special characters 106 - --- 107 - message: &msg | 108 - This is a multi-line 109 - message with some 110 - special content! 111 - 112 - output1: *msg 113 - output2: *msg 114 - # Expected: both outputs should have the same multi-line string 115 - 116 - # Test 12: Anchor in mapping value 117 - --- 118 - database: 119 - primary: &db_config 120 - host: localhost 121 - port: 5432 122 - ssl: true 123 - replica: *db_config 124 - backup: *db_config 125 - # Expected: primary, replica, and backup should all have identical configuration
-194
yaml/ocaml-yamle/tests/yaml/anchors_merge.yml
··· 1 - # Merge Key Test Cases 2 - # Tests YAML 1.1 merge key (<<) functionality 3 - # Note: Merge keys are a YAML 1.1 feature and may not be supported in YAML 1.2 4 - 5 - # Test 1: Basic merge key 6 - --- 7 - defaults: &defaults 8 - timeout: 30 9 - retries: 3 10 - verbose: false 11 - 12 - production: 13 - <<: *defaults 14 - environment: production 15 - # Expected: production should have timeout, retries, verbose from defaults, plus environment 16 - 17 - # Test 2: Override after merge 18 - --- 19 - base: &base 20 - color: red 21 - size: medium 22 - weight: 100 23 - 24 - custom: 25 - <<: *base 26 - color: blue 27 - shape: circle 28 - # Expected: custom should have size and weight from base, but color should be blue, and add shape 29 - 30 - # Test 3: Merging multiple anchors 31 - --- 32 - connection: &connection 33 - host: localhost 34 - port: 8080 35 - 36 - authentication: &auth 37 - username: admin 38 - password: secret 39 - 40 - server: 41 - <<: [*connection, *auth] 42 - ssl: true 43 - # Expected: server should have host, port, username, password, and ssl 44 - 45 - # Test 4: Multiple merges with override 46 - --- 47 - defaults: &defaults 48 - timeout: 30 49 - retries: 3 50 - 51 - advanced: &advanced 52 - cache: true 53 - pool_size: 10 54 - 55 - config: 56 - <<: [*defaults, *advanced] 57 - timeout: 60 58 - custom: value 59 - # Expected: config should have all fields from both anchors, with timeout overridden to 60 60 - 61 - # Test 5: Nested merge 62 - --- 63 - base_style: &base_style 64 - font: Arial 65 - size: 12 66 - 67 - heading_defaults: &heading 68 - <<: *base_style 69 - weight: bold 70 - 71 - main_heading: 72 - <<: *heading 73 - size: 18 74 - color: navy 75 - # Expected: main_heading should inherit from heading (which inherits from base_style) with overrides 76 - 77 - # Test 6: Merge in sequence context 78 - --- 79 - common: &common 80 - enabled: true 81 - log_level: info 82 - 83 - services: 84 - - name: web 85 - <<: *common 86 - port: 80 87 - - name: api 88 - <<: *common 89 - port: 3000 90 - - name: worker 91 - <<: *common 92 - threads: 4 93 - # Expected: each service should have enabled and log_level, plus their specific fields 94 - 95 - # Test 7: Empty merge (edge case) 96 - --- 97 - empty: &empty {} 98 - 99 - config: 100 - <<: *empty 101 - key: value 102 - # Expected: config should just have key: value 103 - 104 - # Test 8: Merge with nested structures 105 - --- 106 - metadata: &metadata 107 - created: 2023-01-01 108 - author: Admin 109 - tags: 110 - - v1 111 - - stable 112 - 113 - document: 114 - <<: *metadata 115 - title: Important Document 116 - content: Some content here 117 - # Expected: document should have all metadata fields plus title and content 118 - 119 - # Test 9: Chain of merges 120 - --- 121 - level1: &l1 122 - a: 1 123 - b: 2 124 - 125 - level2: &l2 126 - <<: *l1 127 - c: 3 128 - 129 - level3: 130 - <<: *l2 131 - d: 4 132 - # Expected: level3 should have a, b, c, and d 133 - 134 - # Test 10: Merge with conflicting keys 135 - --- 136 - first: &first 137 - name: First 138 - value: 100 139 - priority: low 140 - 141 - second: &second 142 - name: Second 143 - value: 200 144 - category: important 145 - 146 - combined: 147 - <<: [*first, *second] 148 - name: Combined 149 - # Expected: later merges and direct assignments take precedence 150 - 151 - # Test 11: Merge preserving types 152 - --- 153 - numbers: &numbers 154 - count: 42 155 - ratio: 3.14 156 - active: true 157 - 158 - derived: 159 - <<: *numbers 160 - label: Test 161 - # Expected: types should be preserved (int, float, bool) 162 - 163 - # Test 12: Complex real-world example 164 - --- 165 - db_defaults: &db_defaults 166 - pool_size: 5 167 - timeout: 30 168 - ssl: false 169 - 170 - cache_defaults: &cache_defaults 171 - ttl: 3600 172 - max_size: 1000 173 - 174 - development: 175 - database: 176 - <<: *db_defaults 177 - host: localhost 178 - name: dev_db 179 - cache: 180 - <<: *cache_defaults 181 - backend: memory 182 - 183 - production: 184 - database: 185 - <<: *db_defaults 186 - host: prod.example.com 187 - name: prod_db 188 - ssl: true 189 - pool_size: 20 190 - cache: 191 - <<: *cache_defaults 192 - backend: redis 193 - ttl: 7200 194 - # Expected: each environment should inherit defaults with environment-specific overrides
-23
yaml/ocaml-yamle/tests/yaml/cohttp.yml
··· 1 - language: c 2 - sudo: false 3 - services: 4 - - docker 5 - install: wget https://raw.githubusercontent.com/ocaml/ocaml-travisci-skeleton/master/.travis-docker.sh 6 - script: bash -ex ./.travis-docker.sh 7 - env: 8 - global: 9 - - EXTRA_REMOTES="https://github.com/mirage/mirage-dev.git" 10 - - PINS="cohttp-top:. cohttp-async:. cohttp-lwt-unix:. cohttp-lwt-jsoo:. cohttp-lwt:. cohttp-mirage:. cohttp:." 11 - matrix: 12 - - PACKAGE="cohttp" DISTRO="alpine-3.5" OCAML_VERSION="4.06.0" 13 - - PACKAGE="cohttp-async" DISTRO="alpine" OCAML_VERSION="4.06.0" 14 - - PACKAGE="cohttp-lwt" DISTRO="debian-unstable" OCAML_VERSION="4.03.0" 15 - - PACKAGE="cohttp-mirage" DISTRO="debian-unstable" OCAML_VERSION="4.03.0" 16 - notifications: 17 - webhooks: 18 - urls: 19 - - https://webhooks.gitter.im/e/6ee5059c7420709f4ad1 20 - on_success: change 21 - on_failure: always 22 - on_start: false 23 -
-126
yaml/ocaml-yamle/tests/yaml/collections_block.yml
··· 1 - # Block Style Collections Test File 2 - # Testing various block-style collection structures 3 - 4 - # Simple sequence 5 - simple_sequence: 6 - - apple 7 - - banana 8 - - cherry 9 - - date 10 - 11 - # Simple mapping 12 - simple_mapping: 13 - name: John Doe 14 - age: 30 15 - city: New York 16 - country: USA 17 - 18 - # Nested sequences 19 - nested_sequences: 20 - - - alpha 21 - - beta 22 - - gamma 23 - - - one 24 - - two 25 - - three 26 - - - red 27 - - green 28 - - blue 29 - 30 - # Nested mappings 31 - nested_mappings: 32 - person: 33 - name: Alice 34 - contact: 35 - email: alice@example.com 36 - phone: 555-1234 37 - address: 38 - street: 123 Main St 39 - city: Boston 40 - 41 - # Mapping containing sequences 42 - mapping_with_sequences: 43 - colors: 44 - - red 45 - - green 46 - - blue 47 - sizes: 48 - - small 49 - - medium 50 - - large 51 - numbers: 52 - - 1 53 - - 2 54 - - 3 55 - 56 - # Sequence containing mappings 57 - sequence_with_mappings: 58 - - name: Alice 59 - age: 25 60 - role: developer 61 - - name: Bob 62 - age: 30 63 - role: designer 64 - - name: Charlie 65 - age: 35 66 - role: manager 67 - 68 - # Deep nesting (4 levels) 69 - deep_nesting: 70 - level1: 71 - level2: 72 - level3: 73 - level4: 74 - - deeply 75 - - nested 76 - - values 77 - another_key: value 78 - items: 79 - - item1 80 - - item2 81 - metadata: 82 - created: 2024-01-01 83 - modified: 2024-12-04 84 - 85 - # Mixed complex structure 86 - complex_structure: 87 - database: 88 - connections: 89 - - host: db1.example.com 90 - port: 5432 91 - credentials: 92 - username: admin 93 - password: secret 94 - - host: db2.example.com 95 - port: 5432 96 - credentials: 97 - username: readonly 98 - password: public 99 - services: 100 - - name: api 101 - endpoints: 102 - - /users 103 - - /posts 104 - - /comments 105 - config: 106 - timeout: 30 107 - retries: 3 108 - - name: worker 109 - tasks: 110 - - email 111 - - reports 112 - config: 113 - concurrency: 10 114 - 115 - # Empty sequences and mappings in block style 116 - empty_collections: 117 - empty_sequence: [] 118 - empty_mapping: {} 119 - sequence_with_empty: 120 - - value1 121 - - [] 122 - - value2 123 - mapping_with_empty: 124 - key1: value1 125 - key2: {} 126 - key3: value3
-198
yaml/ocaml-yamle/tests/yaml/collections_compact.yml
··· 1 - # Compact Notation Collections Test File 2 - # Testing compact block notation and mixed styles 3 - 4 - # Compact nested mapping in sequence (most common form) 5 - compact_sequence: 6 - - name: Alice 7 - age: 25 8 - city: Boston 9 - - name: Bob 10 - age: 30 11 - city: Seattle 12 - - name: Charlie 13 - age: 35 14 - city: Portland 15 - 16 - # Compact with nested structures 17 - compact_nested: 18 - - id: 1 19 - details: 20 - type: admin 21 - permissions: 22 - - read 23 - - write 24 - - delete 25 - - id: 2 26 - details: 27 - type: user 28 - permissions: 29 - - read 30 - 31 - # Multiple keys in same sequence entry with sub-structures 32 - compact_complex: 33 - - key1: value1 34 - key2: value2 35 - nested: 36 - sub1: val1 37 - sub2: val2 38 - - key1: value3 39 - key2: value4 40 - nested: 41 - sub1: val3 42 - sub2: val4 43 - 44 - # Compact block mappings with inline values 45 - users: 46 - - username: alice 47 - email: alice@example.com 48 - active: true 49 - - username: bob 50 - email: bob@example.com 51 - active: false 52 - 53 - # Compact with flow collections 54 - compact_with_flow: 55 - - name: service1 56 - ports: [8080, 8443] 57 - env: {DEBUG: true, MODE: production} 58 - - name: service2 59 - ports: [3000] 60 - env: {DEBUG: false, MODE: development} 61 - 62 - # Deeply nested compact notation 63 - deep_compact: 64 - - category: electronics 65 - items: 66 - - name: laptop 67 - specs: 68 - cpu: Intel i7 69 - ram: 16GB 70 - storage: 512GB SSD 71 - - name: phone 72 - specs: 73 - os: Android 74 - ram: 8GB 75 - storage: 256GB 76 - - category: furniture 77 - items: 78 - - name: desk 79 - dimensions: 80 - width: 150cm 81 - depth: 75cm 82 - height: 75cm 83 - - name: chair 84 - dimensions: 85 - width: 60cm 86 - depth: 60cm 87 - height: 120cm 88 - 89 - # Compact with mixed indentation styles 90 - mixed_compact: 91 - databases: 92 - - type: postgresql 93 - connection: 94 - host: localhost 95 - port: 5432 96 - credentials: 97 - user: admin 98 - password: secret 99 - - type: mongodb 100 - connection: 101 - host: localhost 102 - port: 27017 103 - credentials: 104 - user: root 105 - password: root 106 - 107 - # Single-line compact entries 108 - single_line_compact: 109 - - {name: Alice, age: 25, role: developer} 110 - - {name: Bob, age: 30, role: designer} 111 - - {name: Charlie, age: 35, role: manager} 112 - 113 - # Compact notation with sequences as values 114 - sequences_in_compact: 115 - - title: Project A 116 - members: 117 - - Alice 118 - - Bob 119 - - Charlie 120 - tags: 121 - - urgent 122 - - backend 123 - - title: Project B 124 - members: 125 - - David 126 - - Eve 127 - tags: 128 - - frontend 129 - - design 130 - 131 - # Compact with empty values 132 - compact_with_empty: 133 - - id: 1 134 - data: [] 135 - meta: {} 136 - - id: 2 137 - data: 138 - - item1 139 - meta: 140 - key: value 141 - 142 - # Compact notation with complex nesting 143 - compact_complex_nesting: 144 - - level: 1 145 - children: 146 - - level: 2a 147 - children: 148 - - level: 3a 149 - value: leaf1 150 - - level: 3b 151 - value: leaf2 152 - - level: 2b 153 - children: 154 - - level: 3c 155 - value: leaf3 156 - 157 - # Real-world example: API endpoints 158 - api_endpoints: 159 - - path: /users 160 - method: GET 161 - auth: required 162 - params: 163 - - name: page 164 - type: integer 165 - default: 1 166 - - name: limit 167 - type: integer 168 - default: 10 169 - - path: /users/:id 170 - method: GET 171 - auth: required 172 - params: [] 173 - - path: /users 174 - method: POST 175 - auth: required 176 - body: 177 - username: string 178 - email: string 179 - password: string 180 - 181 - # Compact with various data types 182 - compact_types: 183 - - string_val: hello 184 - number_val: 42 185 - float_val: 3.14 186 - bool_val: true 187 - null_val: null 188 - - string_val: world 189 - number_val: 100 190 - float_val: 2.71 191 - bool_val: false 192 - null_val: ~ 193 - 194 - # Edge case: minimal compact notation 195 - minimal: 196 - - a: 1 197 - - b: 2 198 - - c: 3
-96
yaml/ocaml-yamle/tests/yaml/collections_flow.yml
··· 1 - # Flow Style Collections Test File 2 - # Testing various flow-style collection structures 3 - 4 - # Simple flow sequence 5 - simple_flow_sequence: [apple, banana, cherry, date] 6 - 7 - # Simple flow mapping 8 - simple_flow_mapping: {name: John, age: 30, city: New York} 9 - 10 - # Nested flow sequences 11 - nested_flow_sequences: [[a, b, c], [1, 2, 3], [red, green, blue]] 12 - 13 - # Nested flow mappings 14 - nested_flow_mappings: {person: {name: Alice, age: 25}, contact: {email: alice@example.com, phone: 555-1234}} 15 - 16 - # Flow sequence with mappings 17 - flow_seq_with_maps: [{name: Alice, role: dev}, {name: Bob, role: ops}, {name: Charlie, role: qa}] 18 - 19 - # Flow mapping with sequences 20 - flow_map_with_seqs: {colors: [red, green, blue], sizes: [S, M, L], numbers: [1, 2, 3]} 21 - 22 - # Deeply nested flow collections 23 - deep_flow_nesting: {level1: {level2: {level3: {level4: [a, b, c]}}}} 24 - 25 - # Empty flow collections 26 - empty_flow: {empty_seq: [], empty_map: {}, both: [[], {}]} 27 - 28 - # Mixed flow and block - flow in block 29 - flow_in_block: 30 - sequence: [1, 2, 3, 4, 5] 31 - mapping: {a: 1, b: 2, c: 3} 32 - nested: 33 - items: [x, y, z] 34 - config: {timeout: 30, retries: 3} 35 - 36 - # Mixed flow and block - block in flow 37 - block_in_flow: { 38 - users: [ 39 - {name: Alice, tags: [dev, senior]}, 40 - {name: Bob, tags: [ops, junior]} 41 - ] 42 - } 43 - 44 - # Complex mixed structure 45 - mixed_structure: 46 - services: 47 - - name: api 48 - ports: [8080, 8443] 49 - env: {DEBUG: true, LOG_LEVEL: info} 50 - - name: db 51 - ports: [5432] 52 - env: {POSTGRES_DB: mydb, POSTGRES_USER: admin} 53 - config: {version: 1.0, enabled: true} 54 - 55 - # Flow sequences with various types 56 - flow_types: 57 - strings: [hello, world, foo, bar] 58 - numbers: [1, 2, 3, 42, 100] 59 - mixed: [string, 123, true, false, null] 60 - quoted: ["with spaces", "special:chars", "commas, here"] 61 - 62 - # Flow mappings with various types 63 - flow_map_types: { 64 - string: value, 65 - number: 42, 66 - boolean: true, 67 - null_value: null, 68 - float: 3.14 69 - } 70 - 71 - # Nested mixed collections 72 - nested_mixed: 73 - - {id: 1, data: [a, b, c], meta: {type: first}} 74 - - {id: 2, data: [d, e, f], meta: {type: second}} 75 - - {id: 3, data: [g, h, i], meta: {type: third}} 76 - 77 - # Flow with multiline (should still be valid) 78 - multiline_flow: 79 - long_sequence: [ 80 - item1, 81 - item2, 82 - item3, 83 - item4 84 - ] 85 - long_mapping: { 86 - key1: value1, 87 - key2: value2, 88 - key3: value3 89 - } 90 - 91 - # Edge cases 92 - edge_cases: 93 - single_item_seq: [alone] 94 - single_item_map: {only: one} 95 - nested_empty: [[], [{}], [{}, []]] 96 - all_empty: [{}, [], {a: []}, {b: {}}]
-53
yaml/ocaml-yamle/tests/yaml/comments.yml
··· 1 - # Full line comment at the beginning 2 - # This is a YAML file testing comment handling 3 - 4 - # Comment before a mapping 5 - name: John Doe # End of line comment after a scalar value 6 - age: 30 # Another end of line comment 7 - 8 - # Comment between mapping entries 9 - address: 10 - # Comment inside nested mapping 11 - street: 123 Main St # End of line comment in nested value 12 - city: Springfield 13 - # Comment between nested entries 14 - zip: 12345 15 - 16 - # Comment before sequence 17 - items: 18 - - apple # Comment after sequence item 19 - - banana 20 - # Comment between sequence items 21 - - cherry 22 - - date # Last item comment 23 - 24 - # Comment before flow sequence 25 - flow_seq: [1, 2, 3] # Comment after flow sequence 26 - 27 - # Comment before flow mapping 28 - flow_map: {key1: value1, key2: value2} # Comment after flow mapping 29 - 30 - # Comments with various indentation levels 31 - nested: 32 - # Indented comment level 1 33 - level1: 34 - # Indented comment level 2 35 - level2: 36 - # Indented comment level 3 37 - value: deeply nested # End comment at depth 38 - 39 - # Multiple consecutive comments 40 - # Line 1 41 - # Line 2 42 - # Line 3 43 - multi_comment_key: value 44 - 45 - # Comment with special characters: !@#$%^&*() 46 - special: "value with # hash inside quotes" 47 - 48 - # Empty value with comment 49 - empty_value: # This key has no value (null) 50 - 51 - # Comment before document end 52 - final_key: final_value 53 - # Final comment at end of file
-8
yaml/ocaml-yamle/tests/yaml/directives.yml
··· 1 - # YAML directive tests 2 - 3 - # Test 1: %YAML 1.2 directive 4 - %YAML 1.2 5 - --- 6 - version: "1.2" 7 - content: This document uses YAML 1.2 8 - ...
-15
yaml/ocaml-yamle/tests/yaml/directives_multiple_tags.yml
··· 1 - # Test 4: Multiple TAG directives 2 - %YAML 1.2 3 - %TAG !e! tag:example.com,2025: 4 - %TAG !app! tag:myapp.org,2025:types: 5 - %TAG !geo! tag:geography.net,2025:shapes: 6 - --- 7 - user: !e!person 8 - name: Alice 9 - age: 30 10 - location: !geo!coordinates 11 - lat: 40.7128 12 - lon: -74.0060 13 - config: !app!settings 14 - debug: true 15 - timeout: 30
-10
yaml/ocaml-yamle/tests/yaml/directives_tag.yml
··· 1 - # Test 3: %TAG directive with custom prefix 2 - %YAML 1.2 3 - %TAG !custom! tag:example.com,2025: 4 - --- 5 - shape: !custom!circle 6 - radius: 5 7 - color: red 8 - point: !custom!point 9 - x: 10 10 - y: 20
-10
yaml/ocaml-yamle/tests/yaml/directives_yaml11.yml
··· 1 - # Test 2: %YAML 1.1 directive 2 - %YAML 1.1 3 - --- 4 - version: "1.1" 5 - content: This document uses YAML 1.1 6 - booleans: 7 - - yes 8 - - no 9 - - on 10 - - off
-15
yaml/ocaml-yamle/tests/yaml/documents_multi.yml
··· 1 - # Multiple document variations 2 - 3 - # Test 1: Two documents separated by --- 4 - --- 5 - document: first 6 - type: mapping 7 - data: 8 - key1: value1 9 - key2: value2 10 - --- 11 - document: second 12 - type: mapping 13 - data: 14 - key3: value3 15 - key4: value4
-10
yaml/ocaml-yamle/tests/yaml/documents_multi_empty.yml
··· 1 - # Test 4: Empty documents 2 - --- 3 - # Empty document (implicitly null) 4 - --- 5 - key: value 6 - --- 7 - # Another empty document 8 - --- 9 - - item1 10 - - item2
-15
yaml/ocaml-yamle/tests/yaml/documents_multi_three.yml
··· 1 - # Test 2: Three documents with different content types 2 - --- 3 - # First document: mapping 4 - name: John Doe 5 - age: 30 6 - city: New York 7 - --- 8 - # Second document: sequence 9 - - apple 10 - - banana 11 - - orange 12 - - grape 13 - --- 14 - # Third document: scalar 15 - This is a plain scalar document
-16
yaml/ocaml-yamle/tests/yaml/documents_multi_with_end.yml
··· 1 - # Test 3: Documents with explicit end markers 2 - --- 3 - first: 4 - document: data1 5 - value: 100 6 - ... 7 - --- 8 - second: 9 - document: data2 10 - value: 200 11 - ... 12 - --- 13 - third: 14 - document: data3 15 - value: 300 16 - ...
-11
yaml/ocaml-yamle/tests/yaml/documents_single.yml
··· 1 - # Single document variations 2 - 3 - # Test 1: Implicit document (no markers) 4 - key1: value1 5 - key2: value2 6 - nested: 7 - inner: data 8 - list: 9 - - item1 10 - - item2 11 - - item3
-11
yaml/ocaml-yamle/tests/yaml/documents_single_explicit_both.yml
··· 1 - # Test 3: Explicit start and end (--- ... ) 2 - --- 3 - key1: value1 4 - key2: value2 5 - nested: 6 - inner: data 7 - list: 8 - - item1 9 - - item2 10 - - item3 11 - ...
-10
yaml/ocaml-yamle/tests/yaml/documents_single_explicit_start.yml
··· 1 - # Test 2: Explicit start (---) 2 - --- 3 - key1: value1 4 - key2: value2 5 - nested: 6 - inner: data 7 - list: 8 - - item1 9 - - item2 10 - - item3
-11
yaml/ocaml-yamle/tests/yaml/documents_single_with_directive.yml
··· 1 - # Test 4: With %YAML directive 2 - %YAML 1.2 3 - --- 4 - key1: value1 5 - key2: value2 6 - nested: 7 - inner: data 8 - list: 9 - - item1 10 - - item2 11 - - item3
-155
yaml/ocaml-yamle/tests/yaml/edge_cases.yml
··· 1 - # Edge cases test file for YAML parsing 2 - 3 - # Case 1: Keys with colons (must be quoted) 4 - "key:with:colons": value 5 - "http://example.com": url_as_key 6 - "time:12:30": time_value 7 - 8 - # Case 2: Values starting with indicators (must be quoted or escaped) 9 - indicator_square: "[this starts with bracket]" 10 - indicator_curly: "{this starts with brace}" 11 - indicator_star: "*this starts with star" 12 - indicator_amp: "&this starts with ampersand" 13 - indicator_question: "?this starts with question" 14 - indicator_pipe: "|this starts with pipe" 15 - indicator_gt: ">this starts with gt" 16 - indicator_dash: "-this starts with dash" 17 - indicator_hash: "#this starts with hash" 18 - 19 - # Case 3: Special string values that look like other types 20 - string_true: "true" 21 - string_false: "false" 22 - string_null: "null" 23 - string_number: "123" 24 - string_float: "45.67" 25 - string_yes: "yes" 26 - string_no: "no" 27 - 28 - # Case 4: Actual special values 29 - bool_true: true 30 - bool_false: false 31 - null_value: null 32 - null_tilde: ~ 33 - number_int: 123 34 - number_float: 45.67 35 - number_exp: 1.23e4 36 - number_hex: 0x1F 37 - number_oct: 0o17 38 - 39 - # Case 5: Empty values 40 - empty_string: "" 41 - empty_list: [] 42 - empty_map: {} 43 - null_implicit: 44 - 45 - # Case 6: Very long lines 46 - very_long_key: "This is a very long value that contains a lot of text to test how the parser handles long lines. It should be able to handle lines that are much longer than typical lines in most YAML files. This continues for quite a while to make sure we test the boundaries of reasonable line lengths. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." 47 - 48 - very_long_literal: | 49 - This is a very long literal block that should preserve all the whitespace and newlines exactly as written. It can contain very long lines that go on and on and on without breaking. This tests whether the parser can handle long content in literal blocks properly. Lorem ipsum dolor sit amet, consectetur adipiscing elit. 50 - 51 - # Case 7: Unicode and special characters 52 - unicode_emoji: "Hello 🌍 World 🚀" 53 - unicode_chars: "Héllo Wörld 你好 مرحبا" 54 - unicode_key_🔑: unicode_value 55 - escaped_chars: "Line1\nLine2\tTabbed" 56 - 57 - # Case 8: Nested empty structures 58 - nested_empty: 59 - level1: {} 60 - level2: 61 - inner: [] 62 - level3: 63 - inner: 64 - deep: null 65 - 66 - # Case 9: Complex keys (flow collections as keys) 67 - ? [complex, key] 68 - : complex_value 69 - ? {nested: key} 70 - : another_value 71 - 72 - # Case 10: Multi-line keys and values 73 - ? | 74 - This is a multi-line 75 - key using literal block 76 - : | 77 - This is a multi-line 78 - value using literal block 79 - 80 - # Case 11: Quoted strings with escape sequences 81 - single_quoted: 'It''s a single-quoted string with doubled quotes' 82 - double_quoted: "It's a \"double-quoted\" string with escapes" 83 - backslash: "Path\\to\\file" 84 - newline_escape: "First line\nSecond line" 85 - 86 - # Case 12: Anchors and aliases at edge positions 87 - anchor_list: &anchor_ref 88 - - item1 89 - - item2 90 - - item3 91 - 92 - alias_usage: *anchor_ref 93 - 94 - nested_anchor: 95 - data: &nested_ref 96 - key: value 97 - reference: *nested_ref 98 - 99 - # Case 13: Mixed flow and block styles 100 - mixed_style: 101 - block_key: 102 - - flow_in_block: [1, 2, 3] 103 - - another: {a: 1, b: 2} 104 - flow_key: {block_in_flow: 105 - - item1 106 - - item2} 107 - 108 - # Case 14: Trailing commas in flow (typically invalid in YAML) 109 - # flow_trailing: [1, 2, 3,] # This would be invalid 110 - 111 - # Case 15: Strings that need quoting 112 - needs_quote_1: "value with # in it" 113 - needs_quote_2: "value with: colon" 114 - needs_quote_3: "value with @ at sign" 115 - needs_quote_4: "value with ` backtick" 116 - 117 - # Case 16: Multiple documents separator (not starting a new document) 118 - not_doc_separator: "--- this is just a string value" 119 - 120 - # Case 17: Extremely nested structures 121 - deeply_nested: 122 - l1: 123 - l2: 124 - l3: 125 - l4: 126 - l5: 127 - l6: 128 - l7: 129 - l8: 130 - l9: 131 - l10: "deep value" 132 - 133 - # Case 18: Large sequence 134 - large_sequence: 135 - - item_001 136 - - item_002 137 - - item_003 138 - - item_004 139 - - item_005 140 - - item_006 141 - - item_007 142 - - item_008 143 - - item_009 144 - - item_010 145 - 146 - # Case 19: Keys and values with only whitespace differences 147 - " key": "value with leading space in key" 148 - "key ": "value with trailing space in key" 149 - " spaced ": " spaced " 150 - 151 - # Case 20: Binary-looking values 152 - binary_string: "0b101010" 153 - hex_string: "0xDEADBEEF" 154 - 155 - # End of edge cases test file
-59
yaml/ocaml-yamle/tests/yaml/linuxkit.yml
··· 1 - kernel: 2 - image: linuxkit/kernel:4.9.40 3 - cmdline: "console=tty0 console=ttyS0" 4 - init: 5 - - linuxkit/init:906e174b3f2e07f97d6fd693a2e8518e98dafa58 6 - - linuxkit/runc:90e45f13e1d0a0983f36ef854621e3eac91cf541 7 - - linuxkit/containerd:7c986fb7df33bea73b5c8097b46989e46f49d875 8 - - linuxkit/ca-certificates:e44b0a66df5a102c0e220f0066b0d904710dcb10 9 - onboot: 10 - - name: sysctl 11 - image: linuxkit/sysctl:184c914d23a017062d7b53d7fc1dfaf47764bef6 12 - - name: dhcpcd 13 - image: linuxkit/dhcpcd:f3f5413abb78fae9020e35bd4788fa93df4530b7 14 - command: ["/sbin/dhcpcd", "--nobackground", "-f", "/dhcpcd.conf", "-1"] 15 - onshutdown: 16 - - name: shutdown 17 - image: busybox:latest 18 - command: ["/bin/echo", "so long and thanks for all the fish"] 19 - services: 20 - - name: getty 21 - image: linuxkit/getty:2c841cdc34396e3fa8f25b62d112808f63f16df6 22 - env: 23 - - INSECURE=true 24 - - name: rngd 25 - image: linuxkit/rngd:b2f4bdcb55aa88a25c86733e294628614504f383 26 - - name: nginx 27 - image: nginx:alpine 28 - capabilities: 29 - - CAP_NET_BIND_SERVICE 30 - - CAP_CHOWN 31 - - CAP_SETUID 32 - - CAP_SETGID 33 - - CAP_DAC_OVERRIDE 34 - files: 35 - - path: etc/containerd/config.toml 36 - contents: | 37 - state = "/run/containerd" 38 - root = "/var/lib/containerd" 39 - snapshotter = "io.containerd.snapshotter.v1.overlayfs" 40 - differ = "io.containerd.differ.v1.base-diff" 41 - subreaper = false 42 - 43 - [grpc] 44 - address = "/run/containerd/containerd.sock" 45 - uid = 0 46 - gid = 0 47 - 48 - [debug] 49 - address = "/run/containerd/debug.sock" 50 - level = "info" 51 - 52 - [metrics] 53 - address = ":13337" 54 - - path: etc/linuxkit-config 55 - metadata: yaml 56 - trust: 57 - org: 58 - - linuxkit 59 - - library
-192
yaml/ocaml-yamle/tests/yaml/scalars_block.yml
··· 1 - # Block scalars - literal and folded styles 2 - --- 3 - # Literal style (|) - preserves newlines 4 - literal_basic: | 5 - Line one 6 - Line two 7 - Line three 8 - 9 - literal_with_indent: | 10 - First line 11 - Indented line 12 - More indented 13 - Back to second level 14 - Back to first level 15 - 16 - # Folded style (>) - converts newlines to spaces 17 - folded_basic: > 18 - This is a long paragraph 19 - that will be folded into 20 - a single line with the 21 - newlines converted to spaces. 22 - 23 - folded_paragraph: > 24 - First paragraph flows together 25 - into a single line. 26 - 27 - Second paragraph after blank line 28 - also flows together. 29 - 30 - # Chomping indicators 31 - # Strip (-) - removes trailing newlines 32 - literal_strip: |- 33 - No trailing newline 34 - 35 - 36 - literal_strip_multiple: |- 37 - Text here 38 - 39 - 40 - folded_strip: >- 41 - Folded text 42 - with stripped 43 - trailing newlines 44 - 45 - 46 - # Clip (default) - keeps single trailing newline 47 - literal_clip: | 48 - One trailing newline 49 - 50 - 51 - literal_clip_explicit: | 52 - This is the default behavior 53 - 54 - 55 - folded_clip: > 56 - Folded with one 57 - trailing newline 58 - 59 - 60 - # Keep (+) - preserves all trailing newlines 61 - literal_keep: |+ 62 - Keeps trailing newlines 63 - 64 - 65 - literal_keep_multiple: |+ 66 - Text here 67 - 68 - 69 - folded_keep: >+ 70 - Folded text 71 - keeps trailing 72 - 73 - 74 - # Explicit indentation indicators 75 - literal_indent_2: |2 76 - Two space indentation 77 - is preserved here 78 - Extra indent 79 - Back to two 80 - 81 - literal_indent_4: |4 82 - Four space base indent 83 - Second line 84 - Extra indent 85 - Back to base 86 - 87 - folded_indent_2: >2 88 - Text with two space 89 - base indentation that 90 - will be folded. 91 - 92 - folded_indent_3: >3 93 - Three space indent 94 - for this folded 95 - text block. 96 - 97 - # Combinations of indicators 98 - literal_indent_strip: |2- 99 - Indented by 2 100 - No trailing newlines 101 - 102 - 103 - folded_indent_strip: >3- 104 - Folded with indent 105 - and stripped end 106 - 107 - 108 - literal_indent_keep: |2+ 109 - Indented by 2 110 - Keeps trailing newlines 111 - 112 - 113 - folded_indent_keep: >4+ 114 - Folded indent 4 115 - keeps all trailing 116 - 117 - 118 - # Empty block scalars 119 - empty_literal: | 120 - 121 - empty_folded: > 122 - 123 - # Block scalar with only newlines 124 - only_newlines_literal: | 125 - 126 - 127 - only_newlines_folded: > 128 - 129 - 130 - # Complex indentation patterns 131 - complex_literal: | 132 - First level 133 - Second level 134 - Third level 135 - Back to second 136 - Back to first 137 - 138 - New paragraph 139 - With indent 140 - 141 - Final paragraph 142 - 143 - complex_folded: > 144 - This paragraph 145 - flows together. 146 - 147 - This is separate. 148 - This line starts more indented 149 - and continues. 150 - 151 - Final thoughts here. 152 - 153 - # Special characters in block scalars 154 - special_chars_literal: | 155 - Special: @#$%^&*() 156 - Quotes: "double" 'single' 157 - Brackets: [array] {object} 158 - Symbols: | > & * ? : - 159 - 160 - special_chars_folded: > 161 - All special chars are literal 162 - in block scalars: []{}|>*& 163 - 164 - # Block scalars in sequences 165 - sequence_with_blocks: 166 - - | 167 - First item 168 - literal block 169 - - > 170 - Second item 171 - folded block 172 - - |- 173 - Third item 174 - stripped 175 - - |+ 176 - Fourth item 177 - kept 178 - 179 - 180 - # Block scalars in nested mappings 181 - nested: 182 - description: > 183 - This is a folded 184 - description that spans 185 - multiple lines. 186 - code: | 187 - def hello(): 188 - print("Hello, World!") 189 - return True 190 - notes: |- 191 - Final notes 192 - with stripped end
-60
yaml/ocaml-yamle/tests/yaml/scalars_plain.yml
··· 1 - # Plain scalars - no quotes needed 2 - --- 3 - # Simple words 4 - simple_word: hello 5 - single_character: x 6 - number_like: 123 7 - boolean_like: true 8 - null_like: null 9 - 10 - # Multi-word values (no special meaning characters) 11 - sentence: this is a plain scalar 12 - phrase: plain scalars can have spaces 13 - 14 - # Numbers and special values that remain strings in context 15 - age: 42 16 - pi: 3.14159 17 - negative: -273 18 - scientific: 1.23e-4 19 - hex_like: 0x1A2B 20 - octal_like: 0o755 21 - 22 - # Special characters that are valid in plain scalars 23 - with_colon: "value: with colon needs quotes in value" 24 - with_comma: "commas, need quotes in flow context" 25 - with_hash: "# needs quotes if starting with hash" 26 - hyphen_start: "- needs quotes if starting like list" 27 - question_start: "? needs quotes if starting like mapping key" 28 - 29 - # Plain scalars with valid special characters 30 - email: user@example.com 31 - url: http://example.com/path 32 - path: /usr/local/bin 33 - ratio: 16:9 34 - version: v1.2.3 35 - 36 - # Multi-line plain scalars (line folding) 37 - # Newlines become spaces, blank lines become newlines 38 - folded_plain: This is a long 39 - plain scalar that spans 40 - multiple lines and will 41 - be folded into a single line 42 - with spaces. 43 - 44 - another_folded: First paragraph 45 - continues here and here. 46 - 47 - Second paragraph after blank line. 48 - Also continues. 49 - 50 - # Trailing and leading spaces are trimmed in plain scalars 51 - spaces_trimmed: value with spaces 52 - 53 - # Plain scalars can contain most punctuation 54 - punctuation: Hello, world! How are you? I'm fine. 55 - symbols: $100 & 50% off @ store #1 56 - math: 2+2=4 and 3*3=9 57 - 58 - # Empty plain scalar (becomes null) 59 - empty_implicit: 60 - explicit_empty: ""
-81
yaml/ocaml-yamle/tests/yaml/scalars_quoted.yml
··· 1 - # Quoted scalars - single and double quoted strings 2 - --- 3 - # Single-quoted strings 4 - single_simple: 'hello world' 5 - single_with_double: 'He said "hello"' 6 - single_escaped_quote: 'It''s a single quote: ''example''' 7 - single_multiline: 'This is a 8 - multi-line single 9 - quoted string' 10 - 11 - # Double-quoted strings 12 - double_simple: "hello world" 13 - double_with_single: "It's easy" 14 - double_escaped_quote: "She said \"hello\"" 15 - 16 - # Escape sequences in double-quoted strings 17 - escaped_newline: "Line one\nLine two\nLine three" 18 - escaped_tab: "Column1\tColumn2\tColumn3" 19 - escaped_backslash: "Path: C:\\Users\\Name" 20 - escaped_carriage: "Before\rAfter" 21 - escaped_bell: "Bell\a" 22 - escaped_backspace: "Back\b" 23 - escaped_formfeed: "Form\f" 24 - escaped_vertical: "Vertical\vtab" 25 - 26 - # Unicode escapes 27 - unicode_16bit: "Snowman: \u2603" 28 - unicode_32bit: "Emoji: \U0001F600" 29 - unicode_hex: "Null byte: \x00" 30 - 31 - # Empty strings 32 - empty_single: '' 33 - empty_double: "" 34 - 35 - # Strings that would be interpreted as other types if unquoted 36 - string_true: "true" 37 - string_false: "false" 38 - string_null: "null" 39 - string_number: "123" 40 - string_float: "45.67" 41 - string_octal: "0o755" 42 - string_hex: "0xFF" 43 - 44 - # Special YAML characters that need quoting 45 - starts_with_at: "@username" 46 - starts_with_backtick: "`command`" 47 - starts_with_ampersand: "&reference" 48 - starts_with_asterisk: "*alias" 49 - starts_with_exclamation: "!tag" 50 - starts_with_pipe: "|literal" 51 - starts_with_gt: ">folded" 52 - starts_with_percent: "%directive" 53 - 54 - # Flow indicators that need quoting 55 - with_brackets: "[not a list]" 56 - with_braces: "{not: a map}" 57 - with_comma: "a, b, c" 58 - with_colon_space: "key: value" 59 - 60 - # Quoted strings preserve leading/trailing whitespace 61 - leading_space: " spaces before" 62 - trailing_space: "spaces after " 63 - both_spaces: " spaces both " 64 - 65 - # Multi-line quoted strings 66 - double_multiline: "This is a string 67 - that spans multiple 68 - lines with escaped newlines." 69 - 70 - single_fold: 'This single quoted 71 - string will fold 72 - lines into spaces.' 73 - 74 - # Complex escape sequences 75 - complex_escapes: "Tab:\t Newline:\n Quote:\" Backslash:\\ Unicode:\u0041" 76 - 77 - # Edge cases 78 - only_spaces_single: ' ' 79 - only_spaces_double: " " 80 - only_newlines: "\n\n\n" 81 - mixed_quotes: "She said 'it''s a beautiful day'"
-5
yaml/ocaml-yamle/tests/yaml/seq.yml
··· 1 - - hello 2 - - whats 3 - - up 4 - - foo 5 - - bar
-82
yaml/ocaml-yamle/tests/yaml/values_bool.yml
··· 1 - # Boolean value test cases for YAML 1.2 2 - # Note: YAML 1.2 only recognizes 'true' and 'false' as booleans 3 - # Other values like yes/no, on/off are treated as strings in 1.2 4 - 5 - # Standard YAML 1.2 booleans (lowercase) 6 - bool_true: true 7 - bool_false: false 8 - 9 - # Capitalized forms (should be strings in YAML 1.2) 10 - capitalized_true: True 11 - capitalized_false: False 12 - 13 - # YAML 1.1 style booleans (should be strings in YAML 1.2) 14 - yes_value: yes 15 - no_value: no 16 - Yes_value: Yes 17 - No_value: No 18 - YES_value: YES 19 - NO_value: NO 20 - 21 - # On/Off style (should be strings in YAML 1.2) 22 - on_value: on 23 - off_value: off 24 - On_value: On 25 - Off_value: Off 26 - ON_value: ON 27 - OFF_value: OFF 28 - 29 - # Booleans in sequences 30 - bool_sequence: 31 - - true 32 - - false 33 - - yes 34 - - no 35 - - on 36 - - off 37 - 38 - # Booleans in flow style 39 - flow_bools: [true, false, yes, no] 40 - 41 - # Booleans in mappings 42 - bool_mapping: 43 - active: true 44 - disabled: false 45 - enabled: yes 46 - stopped: no 47 - 48 - # String literals that should NOT be parsed as booleans 49 - quoted_bools: 50 - quoted_true: "true" 51 - quoted_false: "false" 52 - quoted_yes: "yes" 53 - quoted_no: "no" 54 - single_true: 'true' 55 - single_false: 'false' 56 - 57 - # Nested boolean values 58 - nested_bools: 59 - settings: 60 - debug: true 61 - verbose: false 62 - legacy_yes: yes 63 - legacy_no: no 64 - flags: 65 - - true 66 - - false 67 - - on 68 - - off 69 - 70 - # Mixed case variations 71 - mixed_case: 72 - TRUE: TRUE 73 - FALSE: FALSE 74 - TrUe: TrUe 75 - FaLsE: FaLsE 76 - 77 - # Boolean-like strings that should remain strings 78 - bool_like_strings: 79 - truthy: truely 80 - falsy: falsetto 81 - yes_sir: yessir 82 - no_way: noway
-55
yaml/ocaml-yamle/tests/yaml/values_null.yml
··· 1 - # Null value test cases for YAML 1.2 2 - 3 - # Explicit null keyword 4 - explicit_null: null 5 - 6 - # Tilde shorthand for null 7 - tilde_null: ~ 8 - 9 - # Empty value (implicit null) 10 - empty_null: 11 - 12 - # Null in flow style 13 - flow_null: [null, ~, ] 14 - 15 - # Null in sequences 16 - sequence_nulls: 17 - - null 18 - - ~ 19 - - 20 - - explicit: null 21 - - tilde: ~ 22 - - empty: 23 - 24 - # Null in mappings 25 - mapping_nulls: 26 - key1: null 27 - key2: ~ 28 - key3: 29 - 30 - # Null as key 31 - null: "null key with string value" 32 - ~: "tilde key with string value" 33 - 34 - # Mixed null values in nested structures 35 - nested: 36 - level1: 37 - null_value: null 38 - tilde_value: ~ 39 - empty_value: 40 - list: 41 - - null 42 - - ~ 43 - - 44 - - some_value 45 - map: 46 - a: null 47 - b: ~ 48 - c: 49 - 50 - # String literals that contain "null" (should NOT be parsed as null) 51 - string_nulls: 52 - quoted_null: "null" 53 - quoted_tilde: "~" 54 - null_in_string: "this is null" 55 - word_null: 'null'
-120
yaml/ocaml-yamle/tests/yaml/values_numbers.yml
··· 1 - # Numeric value test cases for YAML 1.2 2 - 3 - # Integers 4 - int_zero: 0 5 - int_positive: 42 6 - int_negative: -17 7 - int_large: 1000000 8 - int_with_underscores: 1_000_000 9 - 10 - # Octal notation (YAML 1.2 style with 0o prefix) 11 - octal_value: 0o14 12 - octal_zero: 0o0 13 - octal_large: 0o777 14 - 15 - # Hexadecimal notation 16 - hex_lowercase: 0x1a 17 - hex_uppercase: 0x1A 18 - hex_mixed: 0xDeadBeef 19 - hex_zero: 0x0 20 - 21 - # Floating point numbers 22 - float_simple: 3.14 23 - float_negative: -0.5 24 - float_zero: 0.0 25 - float_leading_dot: .5 26 - float_trailing_zero: 1.0 27 - 28 - # Scientific notation 29 - scientific_positive: 1.0e10 30 - scientific_negative: 1.5e-3 31 - scientific_uppercase: 2.5E+2 32 - scientific_no_sign: 3.0e5 33 - 34 - # Special floating point values 35 - positive_infinity: .inf 36 - negative_infinity: -.inf 37 - not_a_number: .nan 38 - infinity_upper: .Inf 39 - infinity_caps: .INF 40 - nan_upper: .NaN 41 - nan_caps: .NAN 42 - 43 - # Numbers in sequences 44 - number_sequence: 45 - - 0 46 - - 42 47 - - -17 48 - - 3.14 49 - - 1.0e10 50 - - .inf 51 - - .nan 52 - 53 - # Numbers in flow style 54 - flow_numbers: [0, 42, -17, 3.14, 0x1A, 0o14] 55 - 56 - # Numbers in mappings 57 - number_mapping: 58 - count: 100 59 - price: 19.99 60 - discount: -5.0 61 - hex_color: 0xFF5733 62 - octal_perms: 0o755 63 - scientific: 6.022e23 64 - 65 - # String literals that look like numbers (quoted) 66 - quoted_numbers: 67 - string_int: "42" 68 - string_float: "3.14" 69 - string_hex: "0x1A" 70 - string_octal: "0o14" 71 - string_inf: ".inf" 72 - string_nan: ".nan" 73 - 74 - # Numeric strings that should remain strings 75 - numeric_strings: 76 - phone: 555-1234 77 - version: 1.2.3 78 - code: 00123 79 - leading_zero: 007 80 - plus_sign: +123 81 - 82 - # Edge cases 83 - edge_cases: 84 - min_int: -9223372036854775808 85 - max_int: 9223372036854775807 86 - very_small: 1.0e-100 87 - very_large: 1.0e100 88 - negative_zero: -0.0 89 - positive_zero: +0.0 90 - 91 - # Nested numeric values 92 - nested_numbers: 93 - coordinates: 94 - x: 10.5 95 - y: -20.3 96 - z: 0.0 97 - measurements: 98 - - 1.1 99 - - 2.2 100 - - 3.3 101 - stats: 102 - count: 1000 103 - average: 45.67 104 - max: .inf 105 - min: -.inf 106 - 107 - # YAML 1.1 style octals (no 0o prefix) - should be strings in YAML 1.2 108 - legacy_octal: 014 109 - 110 - # Binary notation (not part of YAML 1.2 core, but sometimes supported) 111 - # These should be treated as strings in strict YAML 1.2 112 - binary_like: 0b1010 113 - 114 - # Numbers with various formats 115 - format_tests: 116 - no_decimal: 42 117 - with_decimal: 42.0 118 - leading_zero_decimal: 0.42 119 - no_leading_digit: .42 120 - trailing_decimal: 42.
-101
yaml/ocaml-yamle/tests/yaml/values_timestamps.yml
··· 1 - # Timestamp value test cases for YAML 1.1 2 - # Note: YAML 1.2 does not have a timestamp type in the core schema 3 - # These are recognized in YAML 1.1 and some extended schemas 4 - 5 - # ISO 8601 date format (YYYY-MM-DD) 6 - date_simple: 2001-12-15 7 - date_earliest: 1970-01-01 8 - date_leap_year: 2020-02-29 9 - date_current: 2025-12-04 10 - 11 - # ISO 8601 datetime with timezone (UTC) 12 - datetime_utc: 2001-12-15T02:59:43.1Z 13 - datetime_utc_full: 2001-12-15T02:59:43.123456Z 14 - datetime_utc_no_frac: 2001-12-15T02:59:43Z 15 - 16 - # ISO 8601 datetime with timezone offset 17 - datetime_offset_pos: 2001-12-15T02:59:43.1+05:30 18 - datetime_offset_neg: 2001-12-15T02:59:43.1-05:00 19 - datetime_offset_hours: 2001-12-15T02:59:43+05 20 - 21 - # Spaced datetime format (YAML 1.1 style) 22 - datetime_spaced: 2001-12-14 21:59:43.10 -5 23 - datetime_spaced_utc: 2001-12-15 02:59:43.1 Z 24 - datetime_spaced_offset: 2001-12-14 21:59:43.10 -05:00 25 - 26 - # Datetime without fractional seconds 27 - datetime_no_frac: 2001-12-15T14:30:00Z 28 - 29 - # Date only (no time component) 30 - date_only: 2001-12-15 31 - 32 - # Various formats 33 - timestamp_formats: 34 - iso_date: 2001-12-15 35 - iso_datetime_z: 2001-12-15T02:59:43Z 36 - iso_datetime_offset: 2001-12-15T02:59:43+00:00 37 - spaced_datetime: 2001-12-14 21:59:43.10 -5 38 - canonical: 2001-12-15T02:59:43.1Z 39 - 40 - # Timestamps in sequences 41 - timestamp_sequence: 42 - - 2001-12-15 43 - - 2001-12-15T02:59:43.1Z 44 - - 2001-12-14 21:59:43.10 -5 45 - - 2025-01-01T00:00:00Z 46 - 47 - # Timestamps in mappings 48 - events: 49 - created: 2001-12-15T02:59:43.1Z 50 - modified: 2001-12-16T10:30:00Z 51 - published: 2001-12-14 21:59:43.10 -5 52 - 53 - # String literals that look like timestamps (quoted) 54 - quoted_timestamps: 55 - string_date: "2001-12-15" 56 - string_datetime: "2001-12-15T02:59:43.1Z" 57 - string_spaced: "2001-12-14 21:59:43.10 -5" 58 - 59 - # Edge cases and variations 60 - edge_cases: 61 - midnight: 2001-12-15T00:00:00Z 62 - end_of_day: 2001-12-15T23:59:59Z 63 - microseconds: 2001-12-15T02:59:43.123456Z 64 - no_seconds: 2001-12-15T02:59Z 65 - hour_only: 2001-12-15T02Z 66 - 67 - # Nested timestamp values 68 - nested_timestamps: 69 - project: 70 - start_date: 2001-12-15 71 - milestones: 72 - - date: 2001-12-20 73 - time: 2001-12-20T14:00:00Z 74 - - date: 2002-01-15 75 - time: 2002-01-15T09:30:00-05:00 76 - metadata: 77 - created: 2001-12-14 21:59:43.10 -5 78 - updated: 2001-12-15T02:59:43.1Z 79 - 80 - # Invalid timestamp formats (should be treated as strings) 81 - invalid_timestamps: 82 - bad_date: 2001-13-45 83 - bad_time: 2001-12-15T25:99:99Z 84 - incomplete: 2001-12 85 - no_leading_zero: 2001-1-5 86 - 87 - # Different timezone representations 88 - timezones: 89 - utc_z: 2001-12-15T02:59:43Z 90 - utc_offset: 2001-12-15T02:59:43+00:00 91 - est: 2001-12-14T21:59:43-05:00 92 - ist: 2001-12-15T08:29:43+05:30 93 - jst: 2001-12-15T11:59:43+09:00 94 - 95 - # Historical and future dates 96 - date_range: 97 - past: 1900-01-01 98 - unix_epoch: 1970-01-01T00:00:00Z 99 - y2k: 2000-01-01T00:00:00Z 100 - present: 2025-12-04 101 - future: 2099-12-31T23:59:59Z
-105
yaml/ocaml-yamle/tests/yaml/whitespace.yml
··· 1 - # Whitespace handling test file 2 - 3 - # Section 1: Different indentation levels (2 spaces) 4 - two_space_indent: 5 - level1: 6 - level2: 7 - level3: value 8 - 9 - # Section 2: Four space indentation 10 - four_space_indent: 11 - level1: 12 - level2: 13 - level3: value 14 - 15 - # Section 3: Mixed content with blank lines 16 - 17 - first_key: first_value 18 - 19 - 20 - second_key: second_value 21 - 22 - 23 - 24 - third_key: third_value 25 - 26 - # Section 4: Sequences with varying indentation 27 - sequence_2space: 28 - - item1 29 - - item2 30 - - nested: 31 - - nested_item1 32 - - nested_item2 33 - 34 - sequence_4space: 35 - - item1 36 - - item2 37 - - nested: 38 - - nested_item1 39 - - nested_item2 40 - 41 - # Section 5: Trailing whitespace (spaces after values - invisible but present) 42 - trailing_spaces: value 43 - another_key: another_value 44 - 45 - # Section 6: Leading whitespace preservation in literals 46 - literal_block: | 47 - This is a literal block 48 - with preserved indentation 49 - including extra spaces 50 - and blank lines 51 - 52 - like this one above 53 - 54 - folded_block: > 55 - This is a folded block 56 - that will be folded into 57 - a single line but preserves 58 - 59 - paragraph breaks like above 60 - 61 - # Section 7: Whitespace in flow collections 62 - flow_with_spaces: [ item1 , item2 , item3 ] 63 - flow_tight: [item1,item2,item3] 64 - flow_map_spaces: { key1: value1 , key2: value2 } 65 - flow_map_tight: {key1:value1,key2:value2} 66 - 67 - # Section 8: Multiple consecutive blank lines between top-level keys 68 - key_before_blanks: value1 69 - 70 - 71 - 72 - 73 - key_after_blanks: value2 74 - 75 - # Section 9: Indentation in mappings 76 - mapping_indent: 77 - key1: value1 78 - key2: value2 79 - nested: 80 - nested_key1: nested_value1 81 - nested_key2: nested_value2 82 - deep_nested: 83 - deep_key: deep_value 84 - 85 - # Section 10: Whitespace around colons and hyphens 86 - no_space_colon:value 87 - space_after_colon: value 88 - spaces_around: value 89 - - sequence_item_no_space 90 - - nested_sequence 91 - 92 - # Section 11: Empty lines in sequences 93 - sequence_with_blanks: 94 - - item1 95 - 96 - - item2 97 - 98 - - item3 99 - 100 - # Section 12: Whitespace-only mapping values (implicit null) 101 - explicit_null: null 102 - implicit_null: 103 - space_only: 104 - 105 - # End of whitespace test file
-3
yaml/ocaml-yamle/tests/yaml/yaml-1.2.yml
··· 1 - - {"when the key is quoted":"space after colon can be omitted."} 2 - - "quoted slashes \/ are allowed." 3 - - {?"a key can be looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooger": "than 1024 when parsing is unambiguous before seeing the colon."}
-32
yaml/ocaml-yamle/yamle.opam
··· 1 - # This file is generated by dune, edit dune-project instead 2 - opam-version: "2.0" 3 - version: "0.1.0" 4 - synopsis: "Pure OCaml YAML 1.2 parser and emitter" 5 - description: 6 - "A pure OCaml implementation of YAML 1.2 parsing and emission, with no C dependencies." 7 - maintainer: ["yamle@example.com"] 8 - authors: ["Yamle Authors"] 9 - license: "ISC" 10 - homepage: "https://github.com/ocaml/yamle" 11 - bug-reports: "https://github.com/ocaml/yamle/issues" 12 - depends: [ 13 - "ocaml" {>= "4.14.0"} 14 - "dune" {>= "3.0" & >= "3.0"} 15 - "alcotest" {with-test} 16 - "odoc" {with-doc} 17 - ] 18 - build: [ 19 - ["dune" "subst"] {dev} 20 - [ 21 - "dune" 22 - "build" 23 - "-p" 24 - name 25 - "-j" 26 - jobs 27 - "@install" 28 - "@runtest" {with-test} 29 - "@doc" {with-doc} 30 - ] 31 - ] 32 - dev-repo: "git+https://github.com/ocaml/yamle.git"