// Tutorial test definitions for OCaml libraries // Each entry is a self-contained interactive tutorial const U = { // ── Bunzli libraries ── 'fmt.0.9.0': '9901393f978b0a6627c5eab595111f50', 'fmt.0.10.0': 'd8140118651d08430f933d410a909e3b', 'fmt.0.11.0': '7663cce356513833b908ae5e4f521106', 'cmdliner.1.0.4': '0dd34259dc0892e543b03b3afb0a77fa', 'cmdliner.1.3.0': '258e7979b874502ea546e90a0742184a', 'cmdliner.2.0.0': '91c3d96cea9b89ddd24cf7b78786a5ca', 'cmdliner.2.1.0': 'f3e665d5388ac380a70c5ed67f465bbb', 'mtime.1.3.0': 'b6735658fd307bba23a7c5f21519b910', 'mtime.1.4.0': 'ebccfc43716c6da0ca4a065e60d0f875', 'mtime.2.1.0': '7db699c334606d6f66e65c8b515d298d', 'logs.0.7.0': '2c014cfbbee1d278b162002eae03eaa8', 'logs.0.10.0': '07a565e7588ce100ffd7c8eb8b52df07', 'uucp.14.0.0': '60e1409eb30c0650c4d4cbcf3c453e65', 'uucp.15.0.0': '6a96a3f145249f110bf14739c78e758c', 'uucp.16.0.0': '2bf0fbf12aa05c8f99989a759d2dc8cf', 'uucp.17.0.0': '58b9c48e9528ce99586b138d8f4778c2', 'uunf.14.0.0': 'cac36534f1bf353fd2192efd015dd0e6', 'uunf.17.0.0': '96704cd9810ea1ed504e4ed71cde82b0', 'astring.0.8.5': '1cdbe76f0ec91a6eb12bd0279a394492', 'jsonm.1.0.2': 'ac28e00ecd46c9464f5575c461b5d48f', 'xmlm.1.4.0': 'c4c22d0db3ea01343c1a868bab35e1b4', 'ptime.1.2.0': 'd57c69f3dd88b91454622c1841971354', 'react.1.2.2': 'f438ba61693a5448718c73116b228f3c', 'hmap.0.8.1': '753d7c421afb866e7ffe07ddea3b8349', 'gg.1.0.0': '02a9bababc92d6639cdbaf20233597ba', 'note.0.0.3': '2545f914c274aa806d29749eb96836fa', 'otfm.0.4.0': '4f870a70ee71e41dff878af7123b2cd6', 'vg.0.9.5': '0e2e71cfd8fe2e81bff124849421f662', 'bos.0.2.1': '0e04faa6cc5527bc124d8625bded34fc', 'fpath.0.7.3': '6c4fe09a631d871865fd38aa15cd61d4', 'uutf.1.0.4': 'ac04fa0671533316f94dacbd14ffe0bf', 'uuseg.14.0.0': '406ca4903030ee122ff6c61b61446ddc', 'uuseg.15.0.0': '62ea8502ec4e6c386a070cc75ec8377a', 'uuseg.16.0.0': '3a191102f91addba06efdd712ba037b2', 'uuseg.17.0.0': '7d9b8800252a9bec2a9be496e02eb9da', 'b0.0.0.6': 'bfc34a228f53ac5ced707eed285a6e5c', // ── Serialization ── 'yojson.1.7.0': '0273d3484c1256a463fc6b5d822ba4ae', 'yojson.2.0.2': 'b02baa519ba5bedf95d1b42b5e66381a', 'yojson.2.1.2': '5efcef16114ee98834c3f4cf9a7f45b4', 'yojson.2.2.2': '739ca5bed6c1201d906f0f3132274687', 'yojson.3.0.0': 'e52f084da1b654e881d2dba81775b440', 'ezjsonm.1.1.0': '899976ac0dc15192e669f652bf29f29e', 'ezjsonm.1.2.0': 'b93294aee1f9361bfe1916f4127fa56c', 'ezjsonm.1.3.0': '98ee39eafcb78d7c102a291c7faa302e', 'sexplib0.v0.15.1':'f6fb7feeb446b4a67adb486a2392bf3e', 'sexplib0.v0.16.0':'8ec78baf83bdc6a0a181b58efb909869', 'sexplib0.v0.17.0':'08fe6d134ac413075564220297b2554f', 'csexp.1.5.2': '8443eb56f5227050537a4eb47b26fd10', 'base64.3.4.0': '9befb8850a0bcfb0556f8a7d2de8d3bd', 'base64.3.5.2': 'dee9a00f3ec355e7dab15121d7cb5a3c', // ── Text / Parsing ── 're.1.10.4': '4697515ef0ed56df99029cfa8b6a4c1a', 're.1.11.0': '87fd99e341a1468e36de4973044ba1cb', 're.1.12.0': '3bc6cdc9f1fd39cd5ee61b89f423f51a', 're.1.13.2': 'e080307b8290a25f41d4ad87427c3cc0', 're.1.14.0': '7f3c1f0452e7156dea56c1a52e2096a4', 'angstrom.0.15.0':'12fe7a4d575b34f30551cf6eaaed4a0b', 'angstrom.0.16.1':'f46aa50b81b7e6a0dd7ee69d247920c0', 'tyre.0.5': 'ffdb349acdd211cf2699a689ed1491d3', 'tyre.1.0': 'e413ed92802108a275144c27e0f9efa8', // ── Data structures ── 'containers.3.17': '62a1dfab4e79dda21e6775fc35bac90b', 'iter.1.7': '4aca16dd3c74db420f49a18cc54fd66f', 'iter.1.8': '7724b461d742c869a4abfaa870879763', 'iter.1.9': '73e7b9c9b638abf269affd1967509ce6', 'ocamlgraph.2.0.0':'bcfe5c830a54c4fc55121d6bc69d52d4', 'ocamlgraph.2.1.0':'e867dbcc2de571de4cb84d9a45e554bd', 'ocamlgraph.2.2.0':'ab9aa04f9746bf7c5b275cfddfc9dc20', // ── Crypto / Encoding ── 'digestif.1.1.2': '33d25472185fc31bd41d277d488478f2', 'digestif.1.3.0': 'c3664212cf01a38aa9af7c54123056cf', 'hex.1.4.0': '0bff54cafa851851e4ddb617126d4ce6', 'hex.1.5.0': 'a46b45c6915570ff2966d96d9101258c', 'eqaf.0.9': '39499417427d1d35a028fc9101ecbfb2', 'eqaf.0.10': 'eed017d4f8c09e4fcabf2f9320361e64', // ── Networking types ── 'uri.4.2.0': '473a4aaa6884b7d04af481a4bcf573e6', 'uri.4.4.0': '3f9567317844352b63256b5d7075e595', 'ipaddr.5.6.0': 'ca33bd0287b9b4cd9f67a4c6464b0bd9', 'ipaddr.5.6.1': '516728912d49b2b8b007b762f0cd985f', 'domain-name.0.4.1':'55bf622c2e1dacb9e5c7da2cf9195e95', 'domain-name.0.5.0':'e069e9e37be7c2d8264f41a661136c60', // ── Math ── 'zarith.1.13': '5b98616ce2f37ecfbefd3d8c7c1f45a9', 'zarith.1.14': '3abb9b1ae0690526d21d9630f3f27153', // ── Testing ── 'qcheck-core.0.25':'c338cf74d7ad14da542181619f55fbda', 'qcheck-core.0.27':'eb7a98de039353471656e141c6107fc3', 'qcheck-core.0.91':'c1307fa49614dc884aa0fec68b55c832', }; // ── Factory: Fmt (same API across 0.9–0.11) ──────────────────────────── function fmtTutorial(version, universe) { return { name: 'Fmt', version, opam: 'fmt', description: 'OCaml Format pretty-printer combinators', universe, require: ['fmt'], sections: [ { title: 'String Formatting', description: 'Fmt.str works like Printf.sprintf, building a string from a format string.', steps: [ { code: 'Fmt.str "%d" 42;;', expect: '"42"', description: 'Format an integer into a string' }, { code: 'Fmt.str "Hello, %s!" "world";;', expect: '"Hello, world!"', description: 'Interpolate a string value' }, { code: 'Fmt.str "%d + %d = %d" 1 2 3;;', expect: '"1 + 2 = 3"', description: 'Multiple format arguments' }, { code: 'Fmt.str "%a" Fmt.int 42;;', expect: '"42"', description: 'Use a typed formatter with %a' }, ] }, { title: 'Typed Formatters', description: 'Fmt provides typed formatter values (type \'a Fmt.t = Format.formatter -> \'a -> unit) for common types.', steps: [ { code: 'Fmt.str "%a" Fmt.bool true;;', expect: '"true"', description: 'Format a boolean' }, { code: 'Fmt.str "%a" Fmt.float 3.14;;', expect: '3.14', description: 'Format a float' }, { code: 'Fmt.str "%a" Fmt.string "hi";;', expect: '"hi"', description: 'Format a string with the string formatter' }, ] }, { title: 'Collection Formatters', description: 'Fmt can format lists, options, pairs, and results with configurable separators.', steps: [ { code: 'Fmt.str "%a" Fmt.(list int) [1; 2; 3];;', expect: '1', description: 'Format a list of ints (default separator)' }, { code: 'Fmt.str "%a" Fmt.(list ~sep:comma int) [1; 2; 3];;', expect: '1, 2', description: 'Format a list with comma separators' }, { code: 'Fmt.str "%a" Fmt.(list ~sep:(any " ") int) [10; 20];;', expect: '"10 20"', description: 'Format with space separators using Fmt.any' }, { code: 'Fmt.str "%a" Fmt.(option int) (Some 5);;', expect: '5', description: 'Format an option value' }, { code: 'Fmt.str "%a" Fmt.(option int) None;;', expect: '', description: 'Format None (empty output by default)' }, { code: 'Fmt.str "%a" Fmt.(pair ~sep:comma int string) (42, "hi");;', expect: '42', description: 'Format a pair' }, ] }, { title: 'Output to stdout', description: 'Fmt.pr prints directly to stdout. Use @. for a newline flush.', steps: [ { code: 'Fmt.pr "value: %d@." 42;;', expectStdout: 'value: 42', description: 'Print formatted output to stdout' }, { code: 'Fmt.pr "%a@." Fmt.(list ~sep:sp int) [1; 2; 3];;', expectStdout: '1', description: 'Print a list to stdout' }, ] }, { title: 'Combinators', description: 'Higher-order combinators transform formatters.', steps: [ { code: 'let pp_len = Fmt.using String.length Fmt.int;;', expect: 'Fmt.t', description: 'Fmt.using transforms input before formatting' }, { code: 'Fmt.str "%a" pp_len "hello";;', expect: '"5"', description: 'pp_len formats the length of a string' }, { code: 'Fmt.str "%a" (Fmt.Dump.list Fmt.int) [1; 2; 3];;', expect: '[1; 2; 3]', description: 'Fmt.Dump formats with OCaml syntax (brackets)' }, ] }, ], }; } // ── Factory: Uucp (same API across 14–17, different Unicode version) ─── function uucpTutorial(version, universe, unicodeVer) { return { name: 'Uucp', version, opam: 'uucp', description: `Unicode character properties (Unicode ${unicodeVer})`, universe, require: ['uucp'], sections: [ { title: 'Unicode Version', description: 'Each Uucp release tracks a specific Unicode standard version.', steps: [ { code: 'Uucp.unicode_version;;', expect: `"${unicodeVer}"`, description: 'Check which Unicode version this release implements' }, ] }, { title: 'General Category', description: 'Uucp.Gc.general_category returns the Unicode General Category of a character as a polymorphic variant.', steps: [ { code: 'Uucp.Gc.general_category (Uchar.of_int 0x0041);;', expect: '`Lu', description: "'A' (U+0041) is an uppercase letter (Lu)" }, { code: 'Uucp.Gc.general_category (Uchar.of_int 0x0061);;', expect: '`Ll', description: "'a' (U+0061) is a lowercase letter (Ll)" }, { code: 'Uucp.Gc.general_category (Uchar.of_int 0x0030);;', expect: '`Nd', description: "'0' (U+0030) is a decimal digit (Nd)" }, { code: 'Uucp.Gc.general_category (Uchar.of_int 0x0020);;', expect: '`Zs', description: "Space (U+0020) is a space separator (Zs)" }, ] }, { title: 'Script Detection', description: 'Uucp.Script.script identifies which writing system a character belongs to.', steps: [ { code: 'Uucp.Script.script (Uchar.of_int 0x03B1);;', expect: '`Grek', description: "Greek alpha (U+03B1) is in the Greek script" }, { code: 'Uucp.Script.script (Uchar.of_int 0x4E16);;', expect: '`Hani', description: "CJK character (U+4E16) is in the Han script" }, { code: 'Uucp.Script.script (Uchar.of_int 0x0041);;', expect: '`Latn', description: "'A' is in the Latin script" }, ] }, { title: 'Character Properties', description: 'Uucp provides boolean property lookups for whitespace, alphabetic characters, and more.', steps: [ { code: 'Uucp.White.is_white_space (Uchar.of_int 0x0020);;', expect: 'true', description: 'Space is whitespace' }, { code: 'Uucp.White.is_white_space (Uchar.of_int 0x0041);;', expect: 'false', description: "'A' is not whitespace" }, { code: 'Uucp.White.is_white_space (Uchar.of_int 0x00A0);;', expect: 'true', description: 'Non-breaking space (U+00A0) is whitespace' }, ] }, ], }; } // ── Factory: Uunf (same API, different Unicode version) ──────────────── function uunfTutorial(version, universe, unicodeVer) { return { name: 'Uunf', version, opam: 'uunf', description: `Unicode text normalization (Unicode ${unicodeVer})`, universe, require: ['uunf'], sections: [ { title: 'Unicode Version', description: 'Each Uunf release implements normalization according to a specific Unicode version.', steps: [ { code: 'Uunf.unicode_version;;', expect: `"${unicodeVer}"`, description: 'Check the Unicode version' }, ] }, { title: 'Normalization Forms', description: 'Unicode defines four normalization forms: NFC, NFD, NFKC, and NFKD. Uunf.create selects which form to use.', steps: [ { code: 'let nfc = Uunf.create `NFC;;', expect: 'Uunf.t', description: 'Create an NFC normalizer' }, { code: 'let nfd = Uunf.create `NFD;;', expect: 'Uunf.t', description: 'Create an NFD normalizer (canonical decomposition)' }, { code: 'let nfkc = Uunf.create `NFKC;;', expect: 'Uunf.t', description: 'Create an NFKC normalizer (compatibility composition)' }, { code: 'let nfkd = Uunf.create `NFKD;;', expect: 'Uunf.t', description: 'Create an NFKD normalizer (compatibility decomposition)' }, ] }, { title: 'Adding Characters', description: 'Feed characters to the normalizer with Uunf.add. It returns `Uchar for output characters and `Await when ready for more input.', steps: [ { code: 'let n = Uunf.create `NFC;;', expect: 'Uunf.t', description: 'Create a fresh NFC normalizer' }, { code: 'Uunf.add n (`Uchar (Uchar.of_int 0x0041));;', expect: '', description: "Add 'A' to the normalizer" }, { code: 'Uunf.add n `End;;', expect: '', description: 'Signal end of input' }, ] }, ], }; } // ── Factory: Mtime 1.x (1.3 and 1.4 share the same API) ─────────────── function mtime1_3Tutorial(version, universe) { // Mtime 1.3.0: no named constants (ns/ms/s), uses float conversion functions return { name: 'Mtime', version, opam: 'mtime', description: 'Monotonic wall-clock time for OCaml', universe, require: ['mtime'], sections: [ { title: 'Time Span Basics', description: 'Mtime.Span represents monotonic time durations in nanoseconds.', steps: [ { code: 'Mtime.Span.zero;;', expect: 'Mtime.span', description: 'The zero-length span' }, { code: 'Mtime.Span.one;;', expect: 'Mtime.span', description: 'One nanosecond' }, { code: 'Mtime.Span.max_span;;', expect: 'Mtime.span', description: 'The maximum representable span' }, { code: 'Mtime.Span.of_uint64_ns 1_000_000_000L;;', expect: 'Mtime.span', description: 'Create a 1-second span from nanoseconds' }, ] }, { title: 'Span Arithmetic', description: 'Spans support addition, comparison, and absolute difference.', steps: [ { code: 'let one_sec = Mtime.Span.of_uint64_ns 1_000_000_000L;;', expect: 'Mtime.span', description: 'One second' }, { code: 'let two_sec = Mtime.Span.add one_sec one_sec;;', expect: 'Mtime.span', description: 'Add two spans: 1s + 1s = 2s' }, { code: 'Mtime.Span.to_uint64_ns two_sec;;', expect: '2000000000L', description: '2 seconds in nanoseconds' }, { code: 'Mtime.Span.equal Mtime.Span.zero Mtime.Span.zero;;', expect: 'true', description: 'Zero equals zero' }, { code: 'Mtime.Span.compare one_sec Mtime.Span.zero;;', expect: '1', description: '1 second is greater than zero' }, ] }, { title: 'Float Conversions', description: 'Convert spans to floating-point representations in various units.', steps: [ { code: 'Mtime.Span.to_ns one_sec;;', expect: '1000000000.', description: '1 second = 1e9 nanoseconds' }, { code: 'Mtime.Span.to_ms one_sec;;', expect: '1000.', description: '1 second = 1000 milliseconds' }, { code: 'Mtime.Span.to_s one_sec;;', expect: '1.', description: '1 second as a float' }, { code: 'Mtime.Span.to_us one_sec;;', expect: '1000000.', description: '1 second = 1e6 microseconds' }, ] }, ], }; } function mtime1_4Tutorial(version, universe) { // Mtime 1.4.0: has named constants (ns/ms/s) and is_shorter/is_longer return { name: 'Mtime', version, opam: 'mtime', description: 'Monotonic wall-clock time for OCaml', universe, require: ['mtime'], sections: [ { title: 'Time Span Constants', description: 'Mtime 1.4 added named constants for common time durations.', steps: [ { code: 'Mtime.Span.zero;;', expect: 'Mtime.span', description: 'The zero-length span' }, { code: 'Mtime.Span.ns;;', expect: 'Mtime.span', description: '1 nanosecond' }, { code: 'Mtime.Span.ms;;', expect: 'Mtime.span', description: '1 millisecond' }, { code: 'Mtime.Span.s;;', expect: 'Mtime.span', description: '1 second' }, { code: 'Mtime.Span.min;;', expect: 'Mtime.span', description: '1 minute' }, ] }, { title: 'Span Arithmetic', description: 'Spans support addition, scaling, and comparison.', steps: [ { code: 'let two_sec = Mtime.Span.add Mtime.Span.s Mtime.Span.s;;', expect: 'Mtime.span', description: '1s + 1s = 2s' }, { code: 'Mtime.Span.to_uint64_ns two_sec;;', expect: '2000000000L', description: '2 seconds in nanoseconds' }, { code: 'Mtime.Span.compare Mtime.Span.ms Mtime.Span.s;;', expect: '-1', description: '1ms is less than 1s' }, { code: 'Mtime.Span.equal Mtime.Span.zero Mtime.Span.zero;;', expect: 'true', description: 'Zero equals zero' }, ] }, { title: 'Conversions', description: 'Convert spans to floating-point representations in various units.', steps: [ { code: 'Mtime.Span.to_ms Mtime.Span.s;;', expect: '1000.', description: '1 second = 1000 milliseconds' }, { code: 'Mtime.Span.to_s Mtime.Span.s;;', expect: '1.', description: '1 second as a float' }, { code: 'Mtime.Span.of_uint64_ns 500_000_000L |> Mtime.Span.to_ms;;', expect: '500.', description: '500ms round-trip through nanoseconds' }, ] }, ], }; } export const TUTORIALS = { // ═══════════════════════════════════════════════════════════════════════ // Fmt // ═══════════════════════════════════════════════════════════════════════ 'fmt.0.9.0': fmtTutorial('0.9.0', U['fmt.0.9.0']), 'fmt.0.10.0': fmtTutorial('0.10.0', U['fmt.0.10.0']), 'fmt.0.11.0': fmtTutorial('0.11.0', U['fmt.0.11.0']), // ═══════════════════════════════════════════════════════════════════════ // Cmdliner // ═══════════════════════════════════════════════════════════════════════ 'cmdliner.1.0.4': { name: 'Cmdliner', version: '1.0.4', opam: 'cmdliner', description: 'Declarative definition of command line interfaces (v1 API)', universe: U['cmdliner.1.0.4'], require: ['cmdliner'], sections: [ { title: 'Argument Info', description: 'Cmdliner.Arg.info describes command-line arguments with names, docs, and metadata.', steps: [ { code: 'let verbose_info = Cmdliner.Arg.info ["v"; "verbose"] ~doc:"Be verbose";;', expect: 'Cmdliner.Arg.info', description: 'Create info for a --verbose/-v flag' }, { code: 'let name_info = Cmdliner.Arg.info [] ~docv:"NAME" ~doc:"The name";;', expect: 'Cmdliner.Arg.info', description: 'Create info for a positional argument' }, ] }, { title: 'Argument Definitions', description: 'Arguments are built from converters + info, then lifted into terms with Arg.value.', steps: [ { code: 'Cmdliner.Arg.string;;', expect: 'string Cmdliner.Arg.conv', description: 'Built-in string converter' }, { code: 'Cmdliner.Arg.int;;', expect: 'int Cmdliner.Arg.conv', description: 'Built-in int converter' }, { code: 'let verbose = Cmdliner.Arg.(value (flag (info ["v";"verbose"])));;', expect: 'bool Cmdliner.Term.t', description: 'Define a boolean flag term' }, { code: 'let count = Cmdliner.Arg.(value (opt int 0 (info ["c";"count"])));;', expect: 'int Cmdliner.Term.t', description: 'Define an optional int argument with default 0' }, ] }, { title: 'Terms (v1 API)', description: 'In Cmdliner 1.0.x, Term.const and Term.($) combine argument terms into a program term.', steps: [ { code: 'let greet = Cmdliner.Term.const (fun v n -> Printf.sprintf "%s%s" (if v then "HI " else "hi ") n);;', expect: 'Cmdliner.Term.t', description: 'A constant function lifted into a term' }, { code: 'Cmdliner.Term.info "greet" ~doc:"A greeting program";;', expect: 'Cmdliner.Term.info', description: 'Term.info describes the command (v1 API)' }, { code: 'Cmdliner.Term.eval;;', expect: 'Term.result', description: 'Term.eval runs a term — available in 1.0.x' }, ] }, ], }, 'cmdliner.1.3.0': { name: 'Cmdliner', version: '1.3.0', opam: 'cmdliner', description: 'Declarative definition of command line interfaces (transitional)', universe: U['cmdliner.1.3.0'], require: ['cmdliner'], sections: [ { title: 'Argument Building', description: 'Cmdliner 1.3 is a transitional release supporting both the old Term API and the new Cmd API.', steps: [ { code: 'let verbose = Cmdliner.Arg.(value (flag (info ["v";"verbose"] ~doc:"Be verbose")));;', expect: 'bool Cmdliner.Term.t', description: 'Define a verbose flag' }, { code: 'let greeting = Cmdliner.Arg.(value (pos 0 string "world" (info [] ~docv:"NAME")));;', expect: 'string Cmdliner.Term.t', description: 'Define a positional name argument' }, ] }, { title: 'New Cmd API (introduced in 1.1+)', description: 'The Cmd module provides a structured way to define commands, replacing Term.info + Term.eval.', steps: [ { code: 'Cmdliner.Cmd.info "hello" ~doc:"Say hello";;', expect: 'Cmdliner.Cmd.info', description: 'Cmd.info creates command metadata' }, { code: 'let hello_t = Cmdliner.Term.(const (fun v n -> ()) $ verbose $ greeting);;', expect: 'Cmdliner.Term.t', description: 'Combine arguments with Term.const and ($)' }, { code: 'Cmdliner.Cmd.v (Cmdliner.Cmd.info "hello") hello_t;;', expect: 'Cmdliner.Cmd.t', description: 'Create a command from info + term' }, ] }, { title: 'Backward Compatibility', description: 'The old Term.eval API still works in 1.3 for migration.', steps: [ { code: 'Cmdliner.Term.eval;;', expect: 'Term.result', description: 'Term.eval is still available (deprecated but functional)' }, { code: 'Cmdliner.Term.info "test";;', expect: 'Cmdliner.Term.info', description: 'Term.info still works for backward compat' }, ] }, ], }, 'cmdliner.2.0.0': { name: 'Cmdliner', version: '2.0.0', opam: 'cmdliner', description: 'Declarative definition of command line interfaces (v2 API)', universe: U['cmdliner.2.0.0'], require: ['cmdliner'], sections: [ { title: 'Arguments', description: 'Arguments are defined the same way as in earlier versions.', steps: [ { code: 'let verbose = Cmdliner.Arg.(value (flag (info ["v";"verbose"])));;', expect: 'bool Cmdliner.Term.t', description: 'A boolean flag term' }, { code: 'let name = Cmdliner.Arg.(value (pos 0 string "world" (info [])));;', expect: 'string Cmdliner.Term.t', description: 'A positional string argument' }, ] }, { title: 'Cmd Module (v2 API)', description: 'In Cmdliner 2.x, Cmd replaces Term.info/Term.eval entirely.', steps: [ { code: 'Cmdliner.Cmd.info "greet" ~doc:"Greet someone";;', expect: 'Cmdliner.Cmd.info', description: 'Create command info' }, { code: 'let t = Cmdliner.Term.(const (fun _ _ -> ()) $ verbose $ name);;', expect: 'Cmdliner.Term.t', description: 'Build the term' }, { code: 'let cmd = Cmdliner.Cmd.v (Cmdliner.Cmd.info "greet") t;;', expect: 'Cmdliner.Cmd.t', description: 'Package into a command' }, { code: 'Cmdliner.Cmd.name cmd;;', expect: '"greet"', description: 'Extract the command name' }, ] }, { title: 'Removed APIs', description: 'Term.eval was removed in 2.0. Use Cmd.eval_value instead.', steps: [ { code: 'Cmdliner.Cmd.eval_value;;', expect: 'eval_ok', description: 'Cmd.eval_value is the new entry point' }, ] }, ], }, 'cmdliner.2.1.0': { name: 'Cmdliner', version: '2.1.0', opam: 'cmdliner', description: 'Declarative definition of command line interfaces (v2 API)', universe: U['cmdliner.2.1.0'], require: ['cmdliner'], sections: [ { title: 'Arguments', description: 'Define typed command-line arguments with converters and info.', steps: [ { code: 'let verbose = Cmdliner.Arg.(value (flag (info ["v";"verbose"] ~doc:"Increase verbosity")));;', expect: 'bool Cmdliner.Term.t', description: 'A verbose flag' }, { code: 'let file = Cmdliner.Arg.(required (pos 0 (some string) None (info [] ~docv:"FILE")));;', expect: 'string Cmdliner.Term.t', description: 'A required positional file argument' }, { code: 'let count = Cmdliner.Arg.(value (opt int 1 (info ["n";"count"] ~doc:"Repeat count")));;', expect: 'int Cmdliner.Term.t', description: 'An optional integer with default' }, ] }, { title: 'Commands', description: 'Commands combine a term with metadata. Groups can nest subcommands.', steps: [ { code: 'let info = Cmdliner.Cmd.info "process" ~version:"1.0" ~doc:"Process files";;', expect: 'Cmdliner.Cmd.info', description: 'Command info with version' }, { code: 'let t = Cmdliner.Term.(const (fun _ _ _ -> ()) $ verbose $ file $ count);;', expect: 'Cmdliner.Term.t', description: 'Combine all arguments' }, { code: 'let cmd = Cmdliner.Cmd.v info t;;', expect: 'Cmdliner.Cmd.t', description: 'Create the command' }, { code: 'Cmdliner.Cmd.name cmd;;', expect: '"process"', description: 'Retrieve the command name' }, ] }, { title: 'Custom Converters', description: 'Arg.conv creates custom argument converters from a parser/printer pair.', steps: [ { code: 'let color_parser s = match s with "red" -> Ok `Red | "blue" -> Ok `Blue | _ -> Error (`Msg "unknown color");;', expect: 'val color_parser', description: 'Define a parser function' }, { code: 'let color_pp ppf c = Format.pp_print_string ppf (match c with `Red -> "red" | `Blue -> "blue");;', expect: 'val color_pp', description: 'Define a printer' }, { code: 'let color_conv = Cmdliner.Arg.conv (color_parser, color_pp);;', expect: 'Cmdliner.Arg.conv', description: 'Build a custom converter' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Mtime // ═══════════════════════════════════════════════════════════════════════ 'mtime.1.3.0': mtime1_3Tutorial('1.3.0', U['mtime.1.3.0']), 'mtime.1.4.0': mtime1_4Tutorial('1.4.0', U['mtime.1.4.0']), 'mtime.2.1.0': { name: 'Mtime', version: '2.1.0', opam: 'mtime', description: 'Monotonic wall-clock time for OCaml', universe: U['mtime.2.1.0'], require: ['mtime'], sections: [ { title: 'Time Span Constants', description: 'Mtime.Span provides named constants for common durations.', steps: [ { code: 'Mtime.Span.zero;;', expect: 'Mtime.span', description: 'Zero-length span' }, { code: 'Mtime.Span.s;;', expect: 'Mtime.span', description: '1 second' }, { code: 'Mtime.Span.min;;', expect: 'Mtime.span', description: '1 minute' }, { code: 'Mtime.Span.hour;;', expect: 'Mtime.span', description: '1 hour' }, ] }, { title: 'Span Arithmetic', description: 'Spans support addition, comparison, and predicate-based comparisons (new in 2.x).', steps: [ { code: 'let two_sec = Mtime.Span.add Mtime.Span.s Mtime.Span.s;;', expect: 'Mtime.span', description: '1s + 1s' }, { code: 'Mtime.Span.to_uint64_ns two_sec;;', expect: '2000000000L', description: '2 seconds in nanoseconds' }, { code: 'Mtime.Span.is_shorter Mtime.Span.ms ~than:Mtime.Span.s;;', expect: 'true', description: '1ms is shorter than 1s (new in 2.x)' }, { code: 'Mtime.Span.is_longer Mtime.Span.hour ~than:Mtime.Span.min;;', expect: 'true', description: '1 hour is longer than 1 minute (new in 2.x)' }, ] }, { title: 'New in 2.x: Float Conversions', description: 'Mtime 2.x adds Span.of_float_ns for creating spans from floating-point nanoseconds.', steps: [ { code: 'Mtime.Span.of_float_ns 1e9;;', expect: 'Some', description: '1e9 ns = 1 second' }, { code: 'Mtime.Span.of_float_ns (-1.);;', expect: 'None', description: 'Negative values return None' }, { code: 'Mtime.Span.of_float_ns infinity;;', expect: 'None', description: 'Non-finite values return None' }, { code: 'Mtime.Span.to_float_ns Mtime.Span.s;;', expect: '1000000000.', description: 'Convert 1 second to float nanoseconds' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Logs // ═══════════════════════════════════════════════════════════════════════ 'logs.0.7.0': { name: 'Logs', version: '0.7.0', opam: 'logs', description: 'Logging infrastructure for OCaml', universe: U['logs.0.7.0'], require: ['logs'], sections: [ { title: 'Log Sources', description: 'A Logs.Src.t identifies a log source with a name and optional documentation.', steps: [ { code: 'let src = Logs.Src.create "myapp" ~doc:"My application";;', expect: 'Logs.src', description: 'Create a named log source' }, { code: 'Logs.Src.name src;;', expect: '"myapp"', description: 'Retrieve the source name' }, { code: 'Logs.Src.doc src;;', expect: '"My application"', description: 'Retrieve the source documentation' }, ] }, { title: 'Log Levels', description: 'Logs has five levels: App, Error, Warning, Info, Debug. The global level controls what gets logged.', steps: [ { code: 'Logs.level ();;', expect: 'option', description: 'Get the current global log level' }, { code: 'Logs.set_level (Some Logs.Debug);;', expect: 'unit', description: 'Set the global level to Debug (most verbose)' }, { code: 'Logs.level ();;', expect: 'Some', description: 'Verify the level was set' }, ] }, { title: 'Error Counting', description: 'Logs tracks error and warning counts globally.', steps: [ { code: 'Logs.err_count ();;', expect: 'int', description: 'Count of errors logged so far' }, { code: 'Logs.warn_count ();;', expect: 'int', description: 'Count of warnings logged so far' }, ] }, ], }, 'logs.0.10.0': { name: 'Logs', version: '0.10.0', opam: 'logs', description: 'Logging infrastructure for OCaml', universe: U['logs.0.10.0'], require: ['logs'], sections: [ { title: 'Log Sources', description: 'Create and inspect named log sources.', steps: [ { code: 'let src = Logs.Src.create "test" ~doc:"A test source";;', expect: 'Logs.src', description: 'Create a log source' }, { code: 'Logs.Src.name src;;', expect: '"test"', description: 'Get the source name' }, { code: 'Logs.Src.doc src;;', expect: '"A test source"', description: 'Get the documentation string' }, { code: 'Logs.Src.list ();;', expect: 'Logs.src list', description: 'List all registered sources' }, ] }, { title: 'Level Management', description: 'Control log verbosity at the global and per-source levels.', steps: [ { code: 'Logs.set_level (Some Logs.Info);;', expect: 'unit', description: 'Set global level to Info' }, { code: 'Logs.level ();;', expect: 'Some', description: 'Check the global level' }, { code: 'Logs.Src.set_level src (Some Logs.Debug);;', expect: 'unit', description: 'Override the level for a specific source' }, { code: 'Logs.Src.level src;;', expect: 'Some', description: 'Check the per-source level' }, ] }, { title: 'Error Tracking', description: 'Logs maintains error and warning counters.', steps: [ { code: 'Logs.err_count ();;', expect: 'int', description: 'Number of errors logged' }, { code: 'Logs.warn_count ();;', expect: 'int', description: 'Number of warnings logged' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Uucp // ═══════════════════════════════════════════════════════════════════════ 'uucp.14.0.0': uucpTutorial('14.0.0', U['uucp.14.0.0'], '14.0.0'), 'uucp.15.0.0': uucpTutorial('15.0.0', U['uucp.15.0.0'], '15.0.0'), 'uucp.16.0.0': uucpTutorial('16.0.0', U['uucp.16.0.0'], '16.0.0'), 'uucp.17.0.0': uucpTutorial('17.0.0', U['uucp.17.0.0'], '17.0.0'), // ═══════════════════════════════════════════════════════════════════════ // Uunf // ═══════════════════════════════════════════════════════════════════════ 'uunf.14.0.0': uunfTutorial('14.0.0', U['uunf.14.0.0'], '14.0.0'), 'uunf.17.0.0': uunfTutorial('17.0.0', U['uunf.17.0.0'], '17.0.0'), // ═══════════════════════════════════════════════════════════════════════ // Astring // ═══════════════════════════════════════════════════════════════════════ 'astring.0.8.5': { name: 'Astring', version: '0.8.5', opam: 'astring', description: 'Alternative String module for OCaml', universe: U['astring.0.8.5'], require: ['astring'], sections: [ { title: 'String Splitting', description: 'Astring.String provides powerful splitting functions that work with string separators.', steps: [ { code: 'Astring.String.cuts ~sep:"," "a,b,c";;', expect: '["a"; "b"; "c"]', description: 'Split on comma' }, { code: 'Astring.String.cuts ~sep:"::" "a::b::c";;', expect: '["a"; "b"; "c"]', description: 'Split on multi-char separator' }, { code: 'Astring.String.cut ~sep:"=" "key=value";;', expect: 'Some ("key", "value")', description: 'Cut at first separator occurrence' }, { code: 'Astring.String.cut ~rev:true ~sep:"." "a.b.c";;', expect: 'Some ("a.b", "c")', description: 'Cut at last separator with ~rev:true' }, ] }, { title: 'String Building', description: 'Concatenation and transformation functions.', steps: [ { code: 'Astring.String.concat ~sep:"-" ["x"; "y"; "z"];;', expect: '"x-y-z"', description: 'Join strings with separator' }, { code: 'Astring.String.concat ~sep:", " ["hello"; "world"];;', expect: '"hello, world"', description: 'Join with comma-space' }, ] }, { title: 'String Testing', description: 'Predicate functions for string content.', steps: [ { code: 'Astring.String.is_prefix ~affix:"http" "http://example.com";;', expect: 'true', description: 'Check for a prefix' }, { code: 'Astring.String.is_suffix ~affix:".ml" "main.ml";;', expect: 'true', description: 'Check for a suffix' }, { code: 'Astring.String.is_prefix ~affix:"ftp" "http://example.com";;', expect: 'false', description: 'Prefix not found' }, { code: 'Astring.String.find_sub ~sub:"world" "hello world";;', expect: 'Some 6', description: 'Find substring position' }, ] }, { title: 'String Trimming', description: 'Remove whitespace or specific characters from strings.', steps: [ { code: 'Astring.String.trim " hello ";;', expect: '"hello"', description: 'Trim whitespace from both ends' }, { code: 'Astring.String.trim ~drop:(fun c -> c = \'/\') "/path/to/";;', expect: '"path/to"', description: 'Trim custom characters' }, ] }, { title: 'Substrings', description: 'Astring.String.Sub provides zero-copy substring operations.', steps: [ { code: 'Astring.String.Sub.(to_string (v "hello world" ~start:6));;', expect: '"world"', description: 'Extract a substring from position 6' }, { code: 'Astring.String.Sub.(to_string (v "hello world" ~stop:5));;', expect: '"hello"', description: 'Extract first 5 characters' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Jsonm // ═══════════════════════════════════════════════════════════════════════ 'jsonm.1.0.2': { name: 'Jsonm', version: '1.0.2', opam: 'jsonm', description: 'Non-blocking streaming JSON codec for OCaml', universe: U['jsonm.1.0.2'], require: ['jsonm'], sections: [ { title: 'Decoding JSON Values', description: 'Jsonm.decoder creates a streaming decoder. Each Jsonm.decode call returns one lexeme.', steps: [ { code: 'let d = Jsonm.decoder (`String "42");;', expect: 'Jsonm.decoder', description: 'Create a decoder from a JSON string' }, { code: 'Jsonm.decode d;;', expect: '`Lexeme (`Float 42.)', description: 'Decode the number 42 (JSON numbers are floats)' }, { code: 'Jsonm.decode d;;', expect: '`End', description: 'End of input' }, ] }, { title: 'Decoding Strings and Booleans', description: 'JSON strings, booleans, and null each produce a single lexeme.', steps: [ { code: 'let d2 = Jsonm.decoder (`String {|"hello"|});;', expect: 'Jsonm.decoder', description: 'Decode a JSON string' }, { code: 'Jsonm.decode d2;;', expect: '`Lexeme (`String "hello")', description: 'String lexeme' }, { code: 'let d3 = Jsonm.decoder (`String "true");;', expect: 'Jsonm.decoder', description: 'Decode a JSON boolean' }, { code: 'Jsonm.decode d3;;', expect: '`Lexeme (`Bool true)', description: 'Boolean lexeme' }, { code: 'let dn = Jsonm.decoder (`String "null");;', expect: 'Jsonm.decoder', description: 'Decode null' }, { code: 'Jsonm.decode dn;;', expect: '`Lexeme `Null', description: 'Null lexeme' }, ] }, { title: 'Decoding Arrays', description: 'Arrays produce `As (array start) and `Ae (array end) lexemes around their elements.', steps: [ { code: 'let da = Jsonm.decoder (`String "[1, 2, 3]");;', expect: 'Jsonm.decoder', description: 'Create decoder for a JSON array' }, { code: 'Jsonm.decode da;;', expect: '`Lexeme `As', description: 'Array start' }, { code: 'Jsonm.decode da;;', expect: '`Lexeme (`Float 1.)', description: 'First element' }, { code: 'Jsonm.decode da;;', expect: '`Lexeme (`Float 2.)', description: 'Second element' }, ] }, { title: 'Encoding JSON', description: 'Jsonm.encoder creates an encoder that writes lexemes to a buffer.', steps: [ { code: 'let buf = Buffer.create 64;;', expect: 'Buffer.t', description: 'Create an output buffer' }, { code: 'let e = Jsonm.encoder (`Buffer buf);;', expect: 'Jsonm.encoder', description: 'Create an encoder' }, { code: 'Jsonm.encode e (`Lexeme (`Float 42.));;', expect: '`Ok', description: 'Encode a number' }, { code: 'Jsonm.encode e `End;;', expect: '`Ok', description: 'End encoding' }, { code: 'Buffer.contents buf;;', expect: '42', description: 'The buffer contains the JSON output' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Xmlm // ═══════════════════════════════════════════════════════════════════════ 'xmlm.1.4.0': { name: 'Xmlm', version: '1.4.0', opam: 'xmlm', description: 'Streaming XML codec for OCaml', universe: U['xmlm.1.4.0'], require: ['xmlm'], sections: [ { title: 'Parsing XML Input', description: 'Xmlm.make_input creates a streaming parser. Each Xmlm.input call returns one signal.', steps: [ { code: 'let i = Xmlm.make_input (`String (0, ""));;', expect: 'Xmlm.input', description: 'Create an input from a string' }, { code: 'Xmlm.input i;;', expect: '`Dtd', description: 'First signal is the DTD (None for no doctype)' }, { code: 'Xmlm.input i;;', expect: '`El_start', description: 'Element start: ' }, { code: 'Xmlm.input i;;', expect: '`El_end', description: 'Element end: (self-closing)' }, ] }, { title: 'Parsing with Attributes', description: 'Element start signals include the tag name and attributes.', steps: [ { code: 'let i2 = Xmlm.make_input (`String (0, {|
text
|}));;', expect: 'Xmlm.input', description: 'Parse XML with attributes' }, { code: 'Xmlm.input i2;;', expect: '`Dtd', description: 'DTD signal' }, { code: 'Xmlm.input i2;;', expect: '`El_start', description: 'Element start with attributes' }, { code: 'Xmlm.input i2;;', expect: '`Data "text"', description: 'Text content' }, { code: 'Xmlm.input i2;;', expect: '`El_end', description: 'Element end' }, ] }, { title: 'XML Output', description: 'Xmlm can also write XML to a buffer.', steps: [ { code: 'let buf = Buffer.create 64;;', expect: 'Buffer.t', description: 'Create output buffer' }, { code: 'let o = Xmlm.make_output (`Buffer buf);;', expect: 'Xmlm.output', description: 'Create an XML output' }, { code: 'Xmlm.output o (`Dtd None);;', expect: 'unit', description: 'Write empty DTD' }, { code: 'Xmlm.output o (`El_start (("", "item"), []));;', expect: 'unit', description: 'Start element' }, { code: 'Xmlm.output o (`Data "hello");;', expect: 'unit', description: 'Write text content' }, { code: 'Xmlm.output o `El_end;;', expect: 'unit', description: 'Close the element' }, { code: 'Buffer.contents buf;;', expect: 'hello', description: 'The output XML' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Ptime // ═══════════════════════════════════════════════════════════════════════ 'ptime.1.2.0': { name: 'Ptime', version: '1.2.0', opam: 'ptime', description: 'POSIX time for OCaml', universe: U['ptime.1.2.0'], require: ['ptime'], sections: [ { title: 'The Epoch', description: 'Ptime.epoch represents 1970-01-01 00:00:00 UTC, the Unix epoch.', steps: [ { code: 'Ptime.epoch;;', expect: 'Ptime.t', description: 'The epoch timestamp' }, { code: 'Ptime.to_float_s Ptime.epoch;;', expect: '0.', description: 'Epoch as float seconds = 0' }, ] }, { title: 'Creating Timestamps', description: 'Ptime.of_date_time creates a timestamp from a date-time tuple.', steps: [ { code: 'let t = Ptime.of_date_time ((2024, 1, 1), ((12, 0, 0), 0));;', expect: 'Some', description: 'January 1, 2024, noon UTC' }, { code: 'let t = match t with Some t -> t | None -> assert false;;', expect: 'Ptime.t', description: 'Unwrap the option' }, { code: 'Ptime.to_date_time t;;', expect: '(2024, 1, 1)', description: 'Convert back to date-time tuple' }, { code: 'Ptime.to_rfc3339 t;;', expect: '2024-01-01', description: 'Format as RFC 3339 string' }, ] }, { title: 'Time Arithmetic', description: 'Add and subtract time spans from timestamps.', steps: [ { code: 'let one_day = Ptime.Span.of_int_s (24 * 3600);;', expect: 'Ptime.span', description: 'A span of one day (86400 seconds)' }, { code: 'let tomorrow = Ptime.add_span t one_day;;', expect: 'Some', description: 'Add one day to our timestamp' }, { code: 'Ptime.to_rfc3339 (Option.get tomorrow);;', expect: '2024-01-02', description: 'January 2nd' }, { code: 'Ptime.Span.to_int_s (Ptime.diff (Option.get tomorrow) t);;', expect: 'Some 86400', description: 'Difference is exactly 86400 seconds' }, ] }, { title: 'Time Spans', description: 'Ptime.Span represents durations in days and picoseconds.', steps: [ { code: 'Ptime.Span.zero;;', expect: 'Ptime.span', description: 'Zero duration' }, { code: 'Ptime.Span.of_int_s 3600;;', expect: 'Ptime.span', description: 'One hour in seconds' }, { code: 'Ptime.Span.to_int_s (Ptime.Span.of_int_s 3600);;', expect: 'Some 3600', description: 'Round-trip: int -> span -> int' }, { code: 'Ptime.Span.to_float_s (Ptime.Span.of_int_s 90);;', expect: '90.', description: '90 seconds as a float' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // React // ═══════════════════════════════════════════════════════════════════════ 'react.1.2.2': { name: 'React', version: '1.2.2', opam: 'react', description: 'Declarative events and signals for OCaml (FRP)', universe: U['react.1.2.2'], require: ['react'], sections: [ { title: 'Creating Signals', description: 'React.S.create returns a signal and a setter function. Signals always have a current value.', steps: [ { code: 'let counter, set_counter = React.S.create 0;;', expect: 'React.signal', description: 'Create a signal with initial value 0' }, { code: 'React.S.value counter;;', expect: '0', description: 'Read the current value' }, { code: 'set_counter 42;;', expect: 'unit', description: 'Update the signal value' }, { code: 'React.S.value counter;;', expect: '42', description: 'The value has changed' }, ] }, { title: 'Derived Signals', description: 'React.S.map creates a signal that automatically updates when its source changes.', steps: [ { code: 'let doubled = React.S.map (fun x -> x * 2) counter;;', expect: 'React.signal', description: 'A signal that is always 2x the counter' }, { code: 'React.S.value doubled;;', expect: '84', description: '42 * 2 = 84' }, { code: 'set_counter 10;;', expect: 'unit', description: 'Update the counter' }, { code: 'React.S.value doubled;;', expect: '20', description: 'Doubled automatically updates: 10 * 2 = 20' }, ] }, { title: 'Combining Signals', description: 'React.S.l2 combines two signals with a function. React.S.pair creates a signal of pairs.', steps: [ { code: 'let name, set_name = React.S.create "world";;', expect: 'React.signal', description: 'A name signal' }, { code: 'let greeting = React.S.l2 (fun n c -> Printf.sprintf "Hello %s (count=%d)" n c) name counter;;', expect: 'React.signal', description: 'Combine name and counter' }, { code: 'React.S.value greeting;;', expect: '"Hello world (count=10)"', description: 'The combined value' }, { code: 'set_name "OCaml";;', expect: 'unit', description: 'Update the name' }, { code: 'React.S.value greeting;;', expect: '"Hello OCaml (count=10)"', description: 'Greeting updates automatically' }, ] }, { title: 'Events', description: 'React.E.create returns an event and a trigger. Unlike signals, events are discrete occurrences.', steps: [ { code: 'let clicks, send_click = React.E.create ();;', expect: 'React.event', description: 'Create a click event' }, { code: 'let click_count = React.S.hold 0 (React.E.map (fun _ -> React.S.value counter) clicks);;', expect: 'React.signal', description: 'Hold the counter value at each click' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Hmap // ═══════════════════════════════════════════════════════════════════════ 'hmap.0.8.1': { name: 'Hmap', version: '0.8.1', opam: 'hmap', description: 'Heterogeneous value maps for OCaml', universe: U['hmap.0.8.1'], require: ['hmap'], sections: [ { title: 'Creating Keys', description: 'Hmap keys are created with Key.create. Each key carries a type witness, allowing the map to hold values of different types.', steps: [ { code: 'let k_int : int Hmap.key = Hmap.Key.create ();;', expect: 'Hmap.key', description: 'A key for int values' }, { code: 'let k_str : string Hmap.key = Hmap.Key.create ();;', expect: 'Hmap.key', description: 'A key for string values' }, { code: 'let k_list : int list Hmap.key = Hmap.Key.create ();;', expect: 'Hmap.key', description: 'A key for int list values' }, ] }, { title: 'Building Maps', description: 'Start from Hmap.empty and add values with Hmap.add. Each key-value pair is type-safe.', steps: [ { code: 'let m = Hmap.empty;;', expect: 'Hmap.t', description: 'An empty heterogeneous map' }, { code: 'Hmap.is_empty m;;', expect: 'true', description: 'Verify it is empty' }, { code: 'let m = m |> Hmap.add k_int 42 |> Hmap.add k_str "hello" |> Hmap.add k_list [1;2;3];;', expect: 'Hmap.t', description: 'Add values of different types' }, { code: 'Hmap.cardinal m;;', expect: '3', description: 'Three bindings in the map' }, ] }, { title: 'Querying Maps', description: 'Hmap.find returns an option. The return type matches the key\'s type parameter.', steps: [ { code: 'Hmap.find k_int m;;', expect: 'Some 42', description: 'Find the int value — type-safe!' }, { code: 'Hmap.find k_str m;;', expect: 'Some "hello"', description: 'Find the string value' }, { code: 'Hmap.find k_list m;;', expect: 'Some [1; 2; 3]', description: 'Find the int list value' }, { code: 'Hmap.mem k_int m;;', expect: 'true', description: 'Check membership' }, { code: 'let m2 = Hmap.rem k_int m;;', expect: 'Hmap.t', description: 'Remove a binding' }, { code: 'Hmap.find k_int m2;;', expect: 'None', description: 'Key is no longer bound' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Gg // ═══════════════════════════════════════════════════════════════════════ 'gg.1.0.0': { name: 'Gg', version: '1.0.0', opam: 'gg', description: 'Basic types for computer graphics in OCaml', universe: U['gg.1.0.0'], require: ['gg'], sections: [ { title: '2D Vectors', description: 'Gg.V2 provides 2D vector operations. Vectors are immutable float pairs.', steps: [ { code: 'let v = Gg.V2.v 3.0 4.0;;', expect: 'Gg.v2', description: 'Create a 2D vector (3, 4)' }, { code: 'Gg.V2.x v;;', expect: '3.', description: 'X component' }, { code: 'Gg.V2.y v;;', expect: '4.', description: 'Y component' }, { code: 'Gg.V2.norm v;;', expect: '5.', description: 'Vector magnitude: sqrt(9 + 16) = 5' }, ] }, { title: 'Vector Arithmetic', description: 'Vectors support addition, subtraction, scalar multiplication, and dot products.', steps: [ { code: 'let a = Gg.V2.v 1.0 0.0;;', expect: 'Gg.v2', description: 'Unit vector along x-axis' }, { code: 'let b = Gg.V2.v 0.0 1.0;;', expect: 'Gg.v2', description: 'Unit vector along y-axis' }, { code: 'Gg.V2.add a b |> Gg.V2.x;;', expect: '1.', description: 'Addition: (1,0) + (0,1) → x = 1' }, { code: 'Gg.V2.dot a b;;', expect: '0.', description: 'Dot product of perpendicular vectors = 0' }, { code: 'Gg.V2.smul 3.0 a |> Gg.V2.x;;', expect: '3.', description: 'Scalar multiply: 3 * (1,0) → x = 3' }, ] }, { title: '3D Vectors and Cross Product', description: 'Gg.V3 adds a third dimension and the cross product operation.', steps: [ { code: 'let i = Gg.V3.v 1.0 0.0 0.0;;', expect: 'Gg.v3', description: 'X-axis unit vector' }, { code: 'let j = Gg.V3.v 0.0 1.0 0.0;;', expect: 'Gg.v3', description: 'Y-axis unit vector' }, { code: 'let k = Gg.V3.cross i j;;', expect: 'Gg.v3', description: 'Cross product i × j = k (z-axis)' }, { code: 'Gg.V3.z k;;', expect: '1.', description: 'Z component is 1 (right-hand rule)' }, ] }, { title: 'Colors', description: 'Gg.Color represents colors in linear sRGB with alpha.', steps: [ { code: 'Gg.Color.red;;', expect: 'Gg.color', description: 'Predefined red color' }, { code: 'Gg.Color.r Gg.Color.red;;', expect: '1.', description: 'Red component = 1.0' }, { code: 'Gg.Color.g Gg.Color.red;;', expect: '0.', description: 'Green component = 0.0' }, { code: 'let c = Gg.Color.v 0.5 0.8 0.2 1.0;;', expect: 'Gg.color', description: 'Create a custom RGBA color' }, { code: 'Gg.Color.a c;;', expect: '1.', description: 'Alpha component' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Vg // ═══════════════════════════════════════════════════════════════════════ 'vg.0.9.5': { name: 'Vg', version: '0.9.5', opam: 'vg', description: 'Declarative 2D vector graphics for OCaml', universe: U['vg.0.9.5'], require: ['vg', 'gg'], sections: [ { title: 'Building Paths', description: 'Vg.P builds immutable path values by chaining operations on P.empty.', steps: [ { code: 'let p = Vg.P.empty;;', expect: 'Vg.path', description: 'Start with an empty path' }, { code: 'let p = Vg.P.empty |> Vg.P.sub (Gg.P2.v 0. 0.) |> Vg.P.line (Gg.P2.v 1. 1.);;', expect: 'Vg.path', description: 'A line from (0,0) to (1,1)' }, { code: 'let circ = Vg.P.empty |> Vg.P.circle (Gg.P2.v 0.5 0.5) 0.3;;', expect: 'Vg.path', description: 'A circle centered at (0.5, 0.5) with radius 0.3' }, { code: 'let rect = Vg.P.empty |> Vg.P.rect (Gg.Box2.v (Gg.P2.v 0. 0.) (Gg.Size2.v 1. 1.));;', expect: 'Vg.path', description: 'A unit rectangle' }, ] }, { title: 'Creating Images', description: 'Vg.I constructs images from colors, paths, and compositing operations.', steps: [ { code: 'let red_fill = Vg.I.const Gg.Color.red;;', expect: 'Vg.image', description: 'A solid red infinite image' }, { code: 'let red_circle = Vg.I.cut circ red_fill;;', expect: 'Vg.image', description: 'Cut the red fill to the circle path' }, { code: 'let blue_rect = Vg.I.cut rect (Vg.I.const Gg.Color.blue);;', expect: 'Vg.image', description: 'A blue rectangle' }, ] }, { title: 'Compositing Images', description: 'Vg.I.blend composites images. I.tr applies affine transforms via Gg.M3 matrices.', steps: [ { code: 'let scene = Vg.I.blend red_circle blue_rect;;', expect: 'Vg.image', description: 'Blend circle over rectangle' }, { code: 'Vg.I.void;;', expect: 'Vg.image', description: 'The empty (transparent) image' }, { code: 'let moved = Vg.I.move (Gg.V2.v 0.5 0.5) red_circle;;', expect: 'Vg.image', description: 'Translate the circle by (0.5, 0.5)' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Note // ═══════════════════════════════════════════════════════════════════════ 'note.0.0.3': { name: 'Note', version: '0.0.3', opam: 'note', description: 'Declarative events and signals for OCaml', universe: U['note.0.0.3'], require: ['note'], sections: [ { title: 'Constant Signals', description: 'Note.S.const creates a signal with a fixed value. Signals always have a current value.', steps: [ { code: 'let s = Note.S.const 42;;', expect: 'Note.signal', description: 'A constant signal with value 42' }, { code: 'Note.S.value s;;', expect: '42', description: 'Read the signal value' }, ] }, { title: 'Mutable Signals', description: 'Note.S.create returns a signal and a setter function for updating the value.', steps: [ { code: 'let counter, set_counter = Note.S.create 0;;', expect: 'Note.signal', description: 'Create a mutable signal starting at 0' }, { code: 'Note.S.value counter;;', expect: '0', description: 'Initial value' }, { code: 'set_counter 10;;', expect: 'unit', description: 'Update the value to 10' }, { code: 'Note.S.value counter;;', expect: '10', description: 'Value has changed' }, ] }, { title: 'Signal Transformations', description: 'Note.S.map and Note.S.l2 derive new signals from existing ones.', steps: [ { code: 'let doubled = Note.S.map (( * ) 2) counter;;', expect: 'Note.signal', description: 'A derived signal: always 2x the counter' }, { code: 'Note.S.value doubled;;', expect: '20', description: '10 * 2 = 20' }, { code: 'let label = Note.S.map (fun n -> Printf.sprintf "count=%d" n) counter;;', expect: 'Note.signal', description: 'Map counter to a string label' }, { code: 'Note.S.value label;;', expect: '"count=10"', description: 'Label reflects the current counter value' }, { code: 'let sum = Note.S.l2 ( + ) counter doubled;;', expect: 'Note.signal', description: 'Combine two signals with l2' }, { code: 'Note.S.value sum;;', expect: '30', description: '10 + 20 = 30' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Otfm // ═══════════════════════════════════════════════════════════════════════ 'otfm.0.4.0': { name: 'Otfm', version: '0.4.0', opam: 'otfm', description: 'OpenType font decoder for OCaml', universe: U['otfm.0.4.0'], require: ['otfm'], sections: [ { title: 'Decoder Creation', description: 'Otfm.decoder creates a decoder from font byte data. Most operations require valid font data.', steps: [ { code: 'Otfm.decoder;;', expect: '-> Otfm.decoder', description: 'The decoder constructor (takes a `String source)' }, { code: 'let d = Otfm.decoder (`String "");;', expect: 'Otfm.decoder', description: 'Create a decoder (with empty data for exploration)' }, ] }, { title: 'Querying Font Data', description: 'With valid font data, you can query tables, glyph counts, and PostScript names.', steps: [ { code: 'Otfm.flavour d;;', expect: 'Error', description: 'Flavour fails on empty data (expected)' }, { code: 'Otfm.postscript_name d;;', expect: '', description: 'PostScript name query (fails gracefully on empty data)' }, { code: 'Otfm.glyph_count d;;', expect: '', description: 'Glyph count query' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Fpath // ═══════════════════════════════════════════════════════════════════════ 'fpath.0.7.3': { name: 'Fpath', version: '0.7.3', opam: 'fpath', description: 'File system paths for OCaml', universe: U['fpath.0.7.3'], require: ['fpath'], sections: [ { title: 'Creating Paths', description: 'Fpath.v creates a path from a string. Paths are validated on creation.', steps: [ { code: 'Fpath.v "/usr/local/bin";;', expect: 'Fpath.t', description: 'Create an absolute path' }, { code: 'Fpath.v "/usr/local/bin" |> Fpath.to_string;;', expect: '"/usr/local/bin"', description: 'Convert back to string' }, { code: 'Fpath.v "src/main.ml";;', expect: 'Fpath.t', description: 'A relative path' }, ] }, { title: 'Path Composition', description: 'Fpath.(/) appends a segment. Paths compose naturally.', steps: [ { code: 'Fpath.(v "/usr" / "local" / "bin") |> Fpath.to_string;;', expect: '"/usr/local/bin"', description: 'Build paths by appending segments' }, { code: 'Fpath.(v "src" / "lib" / "main.ml") |> Fpath.to_string;;', expect: '"src/lib/main.ml"', description: 'Relative path composition' }, ] }, { title: 'Path Components', description: 'Extract parts of a path: parent directory, basename, filename.', steps: [ { code: 'Fpath.v "/usr/local/bin" |> Fpath.parent |> Fpath.to_string;;', expect: '"/usr/local/"', description: 'Parent directory' }, { code: 'Fpath.v "/usr/local/bin" |> Fpath.basename;;', expect: '"bin"', description: 'Basename (last segment)' }, { code: 'Fpath.v "/usr/local/bin" |> Fpath.filename;;', expect: '"bin"', description: 'Filename (last non-empty segment)' }, { code: 'Fpath.v "/a/b/" |> Fpath.basename;;', expect: '"b"', description: 'Basename of a directory path' }, { code: 'Fpath.segs (Fpath.v "/a/b/c");;', expect: '[""; "a"; "b"; "c"]', description: 'All segments (empty first = absolute)' }, ] }, { title: 'File Extensions', description: 'Query and manipulate file extensions.', steps: [ { code: 'Fpath.has_ext ".ml" (Fpath.v "main.ml");;', expect: 'true', description: 'Check for .ml extension' }, { code: 'Fpath.get_ext (Fpath.v "archive.tar.gz");;', expect: '".gz"', description: 'Get the last extension' }, { code: 'Fpath.get_ext ~multi:true (Fpath.v "archive.tar.gz");;', expect: '".tar.gz"', description: 'Get the full multi-extension' }, { code: 'Fpath.rem_ext (Fpath.v "main.ml") |> Fpath.to_string;;', expect: '"main"', description: 'Remove the extension' }, ] }, { title: 'Path Properties', description: 'Test whether paths are absolute, relative, file paths, or directory paths.', steps: [ { code: 'Fpath.is_abs (Fpath.v "/usr/bin");;', expect: 'true', description: 'Absolute path check' }, { code: 'Fpath.is_rel (Fpath.v "src/main.ml");;', expect: 'true', description: 'Relative path check' }, { code: 'Fpath.is_dir_path (Fpath.v "/usr/bin/");;', expect: 'true', description: 'Directory path (ends with /)' }, { code: 'Fpath.is_file_path (Fpath.v "/usr/bin");;', expect: 'true', description: 'File path (does not end with /)' }, { code: 'Fpath.normalize (Fpath.v "/a/b/../c") |> Fpath.to_string;;', expect: '"/a/c"', description: 'Normalize resolves .. components' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Uutf // ═══════════════════════════════════════════════════════════════════════ 'uutf.1.0.4': { name: 'Uutf', version: '1.0.4', opam: 'uutf', description: 'Non-blocking streaming Unicode codec for OCaml', universe: U['uutf.1.0.4'], require: ['uutf'], sections: [ { title: 'UTF-8 Decoding', description: 'Uutf.decoder creates a streaming decoder. Each Uutf.decode call returns one character or a signal.', steps: [ { code: 'let d = Uutf.decoder ~encoding:`UTF_8 (`String "ABC");;', expect: 'Uutf.decoder', description: 'Create a UTF-8 decoder for "ABC"' }, { code: 'Uutf.decode d;;', expect: '`Uchar', description: 'Decode first character: A' }, { code: 'Uutf.decode d;;', expect: '`Uchar', description: 'Decode second character: B' }, { code: 'Uutf.decode d;;', expect: '`Uchar', description: 'Decode third character: C' }, { code: 'Uutf.decode d;;', expect: '`End', description: 'End of input' }, ] }, { title: 'Multi-byte Characters', description: 'UTF-8 encodes non-ASCII characters in multiple bytes. Uutf handles this transparently.', steps: [ { code: 'let d2 = Uutf.decoder ~encoding:`UTF_8 (`String "caf\\xC3\\xA9");;', expect: 'Uutf.decoder', description: 'Decode "cafe" with e-acute (U+00E9)' }, { code: 'Uutf.decode d2;;', expect: '`Uchar', description: 'c' }, { code: 'Uutf.decode d2;;', expect: '`Uchar', description: 'a' }, { code: 'Uutf.decode d2;;', expect: '`Uchar', description: 'f' }, { code: 'Uutf.decode d2;;', expect: '`Uchar', description: 'e-acute (U+00E9, decoded from 2 bytes)' }, ] }, { title: 'UTF-8 Encoding', description: 'Uutf.encoder writes Unicode characters to a buffer in a specified encoding.', steps: [ { code: 'let buf = Buffer.create 16;;', expect: 'Buffer.t', description: 'Create an output buffer' }, { code: 'let e = Uutf.encoder `UTF_8 (`Buffer buf);;', expect: 'Uutf.encoder', description: 'Create a UTF-8 encoder' }, { code: 'Uutf.encode e (`Uchar (Uchar.of_int 0x41));;', expect: '`Ok', description: "Encode 'A'" }, { code: 'Uutf.encode e (`Uchar (Uchar.of_int 0xE9));;', expect: '`Ok', description: "Encode e-acute" }, { code: 'Uutf.encode e `End;;', expect: '`Ok', description: 'Flush the encoder' }, { code: 'Buffer.length buf;;', expect: '3', description: "A (1 byte) + e-acute (2 bytes) = 3 bytes" }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // B0 // ═══════════════════════════════════════════════════════════════════════ 'b0.0.0.6': { name: 'B0', version: '0.0.6', opam: 'b0', description: 'Software construction and deployment kit', universe: U['b0.0.0.6'], require: ['b0.std'], sections: [ { title: 'File Paths (B0_std.Fpath)', description: 'B0_std provides its own Fpath module for file path manipulation.', steps: [ { code: 'B0_std.Fpath.v "/usr/bin";;', expect: 'B0_std.Fpath.t', description: 'Create a path' }, { code: 'B0_std.Fpath.(v "/usr" / "local" / "bin") |> B0_std.Fpath.to_string;;', expect: '"/usr/local/bin"', description: 'Path composition with (/)' }, { code: 'B0_std.Fpath.basename (B0_std.Fpath.v "/usr/local/bin");;', expect: '"bin"', description: 'Get the basename' }, { code: 'B0_std.Fpath.parent (B0_std.Fpath.v "/usr/local/bin") |> B0_std.Fpath.to_string;;', expect: '"/usr/local/"', description: 'Get parent directory' }, ] }, { title: 'Command Lines (B0_std.Cmd)', description: 'B0_std.Cmd builds command-line invocations declaratively.', steps: [ { code: 'let cmd = B0_std.Cmd.(tool "ocamlfind" % "query" % "-format" % "%d" % "fmt");;', expect: 'B0_std.Cmd.t', description: 'Build a command line' }, { code: 'B0_std.Cmd.to_list cmd;;', expect: '["ocamlfind"', description: 'Convert to a list of strings' }, { code: 'B0_std.Cmd.is_empty B0_std.Cmd.empty;;', expect: 'true', description: 'Check for empty command' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Bos // ═══════════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════════ // Yojson // ═══════════════════════════════════════════════════════════════════════ 'yojson.1.7.0': { name: 'Yojson', version: '1.7.0', opam: 'yojson', description: 'JSON parsing and printing for OCaml (1.x API)', universe: U['yojson.1.7.0'], require: ['yojson'], sections: [ { title: 'Parsing JSON', description: 'Yojson.Safe.from_string parses a JSON string into an algebraic type.', steps: [ { code: 'Yojson.Safe.from_string {|{"name": "Alice", "age": 30}|};;', expect: '`Assoc', description: 'Parse a JSON object' }, { code: 'Yojson.Safe.from_string "[1, 2, 3]";;', expect: '`List', description: 'Parse a JSON array' }, { code: 'Yojson.Safe.from_string "42";;', expect: '`Int 42', description: 'Parse a JSON number' }, ] }, { title: 'Building JSON', description: 'JSON values are polymorphic variants: `Null, `Bool, `Int, `Float, `String, `List, `Assoc.', steps: [ { code: 'Yojson.Safe.to_string (`Assoc [("x", `Int 1); ("y", `Int 2)]);;', expect: '"x"', description: 'Serialize an object to string' }, { code: 'Yojson.Safe.to_string (`List [`String "a"; `Bool true]);;', expect: '"a"', description: 'Serialize a list' }, ] }, { title: 'Util Module', description: 'Yojson.Safe.Util provides accessor functions for extracting values from JSON.', steps: [ { code: 'let j = Yojson.Safe.from_string {|{"name": "Bob"}|};;', expect: '`Assoc', description: 'Parse a JSON object' }, { code: 'Yojson.Safe.Util.member "name" j;;', expect: '`String "Bob"', description: 'Extract a field by name' }, { code: 'Yojson.Safe.Util.member "name" j |> Yojson.Safe.Util.to_string;;', expect: '"Bob"', description: 'Extract as an OCaml string' }, { code: 'Yojson.Safe.Util.keys (`Assoc [("a", `Int 1); ("b", `Int 2)]);;', expect: '["a"; "b"]', description: 'Get all keys of an object' }, ] }, ], }, 'yojson.2.0.2': { name: 'Yojson', version: '2.0.2', opam: 'yojson', description: 'JSON parsing and printing for OCaml (2.x API)', universe: U['yojson.2.0.2'], require: ['yojson'], sections: [ { title: 'Parsing JSON', description: 'Yojson 2.x removed biniou dependency. The core API is the same.', steps: [ { code: 'Yojson.Safe.from_string {|{"key": "value"}|};;', expect: '`Assoc', description: 'Parse a JSON object' }, { code: 'Yojson.Safe.from_string "true";;', expect: '`Bool true', description: 'Parse a boolean' }, ] }, { title: 'Building and Serializing', steps: [ { code: 'Yojson.Safe.to_string (`Assoc [("n", `Int 42)]);;', expect: '"n"', description: 'Serialize to compact JSON' }, { code: 'Yojson.Safe.pretty_to_string (`Assoc [("n", `Int 42)]);;', expect: '"n"', description: 'Pretty-print with indentation' }, ] }, { title: 'Util Accessors', description: 'Extract typed values from JSON trees.', steps: [ { code: 'Yojson.Safe.Util.to_int (`Int 42);;', expect: '42', description: 'Extract an int' }, { code: 'Yojson.Safe.Util.to_list (`List [`Int 1; `Int 2]);;', expect: '[`Int 1', description: 'Extract a list' }, { code: 'Yojson.Safe.Util.to_bool (`Bool true);;', expect: 'true', description: 'Extract a bool' }, { code: 'Yojson.Safe.Util.member "x" (`Assoc [("x", `Float 3.14)]);;', expect: '`Float 3.14', description: 'Navigate into an object' }, ] }, ], }, 'yojson.2.1.2': { name: 'Yojson', version: '2.1.2', opam: 'yojson', description: 'JSON parsing and printing for OCaml', universe: U['yojson.2.1.2'], require: ['yojson'], sections: [ { title: 'Parsing and Serializing', steps: [ { code: 'Yojson.Safe.from_string {|[1, "two", true]|};;', expect: '`List', description: 'Parse a heterogeneous array' }, { code: 'Yojson.Safe.to_string (`List [`Int 1; `String "two"; `Bool true]);;', expect: '1', description: 'Serialize back to JSON' }, ] }, { title: 'Util Navigation', steps: [ { code: 'let data = Yojson.Safe.from_string {|{"users": [{"name": "A"}, {"name": "B"}]}|};;', expect: '`Assoc', description: 'Parse nested JSON' }, { code: 'Yojson.Safe.Util.(member "users" data |> to_list |> List.map (member "name"));;', expect: '[`String "A"', description: 'Navigate and extract nested values' }, { code: 'Yojson.Safe.Util.(member "users" data |> index 0 |> member "name" |> to_string);;', expect: '"A"', description: 'Index into arrays' }, ] }, ], }, 'yojson.2.2.2': { name: 'Yojson', version: '2.2.2', opam: 'yojson', description: 'JSON parsing and printing for OCaml', universe: U['yojson.2.2.2'], require: ['yojson'], sections: [ { title: 'Round-Trip JSON', steps: [ { code: 'let j = `Assoc [("list", `List [`Int 1; `Int 2; `Int 3])];;', expect: '`Assoc', description: 'Build a JSON value' }, { code: 'let s = Yojson.Safe.to_string j;;', expect: 'string', description: 'Serialize to string' }, { code: 'Yojson.Safe.from_string s = j;;', expect: 'true', description: 'Round-trip preserves structure' }, ] }, { title: 'Util Combinators', steps: [ { code: 'Yojson.Safe.Util.combine (`Assoc [("a", `Int 1)]) (`Assoc [("b", `Int 2)]);;', expect: '`Assoc', description: 'Merge two objects' }, { code: 'Yojson.Safe.Util.to_assoc (`Assoc [("x", `Int 1)]);;', expect: '[("x"', description: 'Convert to association list' }, { code: 'Yojson.Safe.Util.filter_member "name" [`Assoc [("name", `String "A")]; `Assoc []];;', expect: '[`String "A"', description: 'Filter objects by field presence' }, ] }, ], }, 'yojson.3.0.0': { name: 'Yojson', version: '3.0.0', opam: 'yojson', description: 'JSON parsing and printing for OCaml (3.x, strict types)', universe: U['yojson.3.0.0'], require: ['yojson'], sections: [ { title: 'Strict JSON Types', description: 'Yojson 3.0 removed non-standard Tuple and Variant constructors from Safe.t.', steps: [ { code: 'Yojson.Safe.from_string {|{"clean": true}|};;', expect: '`Assoc', description: 'Parse standard JSON' }, { code: 'Yojson.Safe.to_string (`Assoc [("v", `Intlit "999999999999999")]);;', expect: '999999999999999', description: 'Intlit preserves large integers as strings' }, ] }, { title: 'Util Module', steps: [ { code: 'Yojson.Safe.Util.member "x" (`Assoc [("x", `Null)]);;', expect: '`Null', description: 'Access a null field' }, { code: 'Yojson.Safe.Util.values (`Assoc [("a", `Int 1); ("b", `Int 2)]);;', expect: '[`Int 1', description: 'Extract all values' }, { code: 'Yojson.Safe.Util.to_string_option (`Null);;', expect: 'None', description: 'Safe accessor returns None for wrong type' }, { code: 'Yojson.Safe.Util.to_string_option (`String "hi");;', expect: 'Some "hi"', description: 'Safe accessor returns Some for correct type' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Ezjsonm // ═══════════════════════════════════════════════════════════════════════ 'ezjsonm.1.1.0': { name: 'Ezjsonm', version: '1.1.0', opam: 'ezjsonm', description: 'Easy JSON manipulation for OCaml', universe: U['ezjsonm.1.1.0'], require: ['ezjsonm'], sections: [ { title: 'Building Values', description: 'Ezjsonm provides typed constructors for JSON values.', steps: [ { code: 'Ezjsonm.string "hello";;', expect: '`String "hello"', description: 'Create a JSON string' }, { code: 'Ezjsonm.int 42;;', expect: '`Float 42.', description: 'Create a JSON number (stored as float internally)' }, { code: 'Ezjsonm.bool true;;', expect: '`Bool true', description: 'Create a JSON boolean' }, { code: 'Ezjsonm.list Ezjsonm.int [1; 2; 3];;', expect: '`A', description: 'Create a JSON array from a list' }, ] }, { title: 'Serialization', description: 'Convert values to and from strings.', steps: [ { code: 'Ezjsonm.value_to_string (Ezjsonm.string "hi");;', expect: 'string', description: 'Serialize a value (JSON-encoded string with quotes)' }, { code: 'Ezjsonm.value_to_string (Ezjsonm.list Ezjsonm.int [1;2;3]);;', expect: '[1', description: 'Serialize an array' }, { code: 'Ezjsonm.value_from_string "42";;', expect: '`Float 42.', description: 'Parse a JSON value from string' }, ] }, { title: 'Extracting Values', description: 'get_* functions extract OCaml values from JSON.', steps: [ { code: 'Ezjsonm.get_string (Ezjsonm.string "test");;', expect: '"test"', description: 'Extract a string' }, { code: 'Ezjsonm.get_int (Ezjsonm.int 42);;', expect: '42', description: 'Extract an int' }, { code: 'Ezjsonm.get_list Ezjsonm.get_int (Ezjsonm.list Ezjsonm.int [1;2;3]);;', expect: '[1; 2; 3]', description: 'Extract a list of ints' }, ] }, ], }, 'ezjsonm.1.2.0': { name: 'Ezjsonm', version: '1.2.0', opam: 'ezjsonm', description: 'Easy JSON manipulation for OCaml', universe: U['ezjsonm.1.2.0'], require: ['ezjsonm'], sections: [ { title: 'Building and Querying', steps: [ { code: 'let doc = Ezjsonm.dict [("name", Ezjsonm.string "Alice"); ("age", Ezjsonm.int 30)];;', expect: '`O', description: 'Build a JSON object with dict' }, { code: 'Ezjsonm.value_to_string doc;;', expect: 'string', description: 'Serialize the object to JSON' }, { code: 'Ezjsonm.get_dict doc;;', expect: '[("name"', description: 'Extract as association list' }, ] }, { title: 'Navigating Documents', description: 'Ezjsonm.find navigates into nested JSON using a path of string keys.', steps: [ { code: 'let j = Ezjsonm.from_string {|{"user": {"name": "Bob"}}|};;', expect: '`O', description: 'Parse a nested document' }, { code: 'Ezjsonm.find j ["user"; "name"];;', expect: '`String "Bob"', description: 'Navigate by key path' }, { code: 'Ezjsonm.mem j ["user"; "name"];;', expect: 'true', description: 'Check if a path exists' }, { code: 'Ezjsonm.find_opt j ["user"; "email"];;', expect: 'None', description: 'Safe navigation returns None for missing paths' }, ] }, ], }, 'ezjsonm.1.3.0': { name: 'Ezjsonm', version: '1.3.0', opam: 'ezjsonm', description: 'Easy JSON manipulation for OCaml', universe: U['ezjsonm.1.3.0'], require: ['ezjsonm'], sections: [ { title: 'Value Constructors', steps: [ { code: 'Ezjsonm.string "hello";;', expect: '`String', description: 'A JSON string value' }, { code: 'Ezjsonm.unit ();;', expect: '`Null', description: 'JSON null' }, { code: 'Ezjsonm.list Ezjsonm.string ["a"; "b"];;', expect: '`A', description: 'Array of strings' }, ] }, { title: 'Documents', description: 'Documents (Ezjsonm.t) must be arrays or objects at the top level.', steps: [ { code: 'let doc = Ezjsonm.from_string {|{"x": [1, 2, 3]}|};;', expect: '`O', description: 'Parse a document' }, { code: 'Ezjsonm.find doc ["x"] |> Ezjsonm.get_list Ezjsonm.get_int;;', expect: '[1; 2; 3]', description: 'Navigate and extract typed values' }, { code: 'Ezjsonm.to_string ~minify:false doc;;', expect: 'string', description: 'Pretty-print a document' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Sexplib0 // ═══════════════════════════════════════════════════════════════════════ 'sexplib0.v0.15.1': { name: 'Sexplib0', version: 'v0.15.1', opam: 'sexplib0', description: 'S-expression type and printing (minimal, no parsing)', universe: U['sexplib0.v0.15.1'], require: ['sexplib0'], sections: [ { title: 'S-expression Type', description: 'Sexplib0.Sexp.t has two constructors: Atom of string and List of t list.', steps: [ { code: 'Sexplib0.Sexp.Atom "hello";;', expect: 'Sexplib0.Sexp.t', description: 'An atomic S-expression' }, { code: 'Sexplib0.Sexp.List [Atom "add"; Atom "1"; Atom "2"];;', expect: 'Sexplib0.Sexp.t', description: 'A list S-expression' }, ] }, { title: 'Printing', description: 'to_string produces compact output, to_string_hum produces indented output.', steps: [ { code: 'Sexplib0.Sexp.to_string (List [Atom "name"; Atom "Alice"]);;', expect: '"(name Alice)"', description: 'Compact string representation' }, { code: 'Sexplib0.Sexp.to_string_hum (List [Atom "config"; List [Atom "port"; Atom "8080"]]);;', expect: '(config', description: 'Human-readable indented output' }, ] }, { title: 'Comparison', steps: [ { code: 'Sexplib0.Sexp.equal (Atom "x") (Atom "x");;', expect: 'true', description: 'Structural equality' }, { code: 'Sexplib0.Sexp.equal (Atom "x") (Atom "y");;', expect: 'false', description: 'Different atoms are not equal' }, ] }, ], }, 'sexplib0.v0.16.0': { name: 'Sexplib0', version: 'v0.16.0', opam: 'sexplib0', description: 'S-expression type and printing (minimal, no parsing)', universe: U['sexplib0.v0.16.0'], require: ['sexplib0'], sections: [ { title: 'Building S-expressions', steps: [ { code: 'open Sexplib0.Sexp;; let s = List [Atom "person"; List [Atom "name"; Atom "Bob"]; List [Atom "age"; Atom "25"]];;', expect: 'Sexplib0.Sexp.t', description: 'Build a nested S-expression' }, { code: 'Sexplib0.Sexp.to_string s;;', expect: '"(person (name Bob) (age 25))"', description: 'Serialize to compact string' }, { code: 'Sexplib0.Sexp.to_string_hum s;;', expect: '(person', description: 'Pretty-print with indentation' }, ] }, { title: 'Error Messages', description: 'Sexp.message builds structured error S-expressions.', steps: [ { code: 'Sexplib0.Sexp.message "invalid input" ["value", Atom "42"; "expected", Atom "string"];;', expect: 'Sexplib0.Sexp.t', description: 'Build a structured error message' }, ] }, ], }, 'sexplib0.v0.17.0': { name: 'Sexplib0', version: 'v0.17.0', opam: 'sexplib0', description: 'S-expression type and printing (minimal, no parsing)', universe: U['sexplib0.v0.17.0'], require: ['sexplib0'], sections: [ { title: 'S-expression Basics', steps: [ { code: 'let open Sexplib0.Sexp in Atom "hello";;', expect: 'Sexplib0.Sexp.t', description: 'An atom' }, { code: 'let open Sexplib0.Sexp in List [Atom "list"; List [Atom "1"; Atom "2"; Atom "3"]];;', expect: 'Sexplib0.Sexp.t', description: 'Nested S-expression' }, { code: 'Sexplib0.Sexp.(to_string (List [Atom "a"; Atom "b"; Atom "c"]));;', expect: '"(a b c)"', description: 'Serialize to string' }, ] }, { title: 'Comparison and Equality', steps: [ { code: 'Sexplib0.Sexp.compare (Atom "a") (Atom "b");;', expect: '-1', description: 'Lexicographic comparison' }, { code: 'Sexplib0.Sexp.equal (List [Atom "x"]) (List [Atom "x"]);;', expect: 'true', description: 'Deep structural equality' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Csexp // ═══════════════════════════════════════════════════════════════════════ 'csexp.1.5.2': { name: 'Csexp', version: '1.5.2', opam: 'csexp', description: 'Canonical S-expressions (length-prefixed binary format)', universe: U['csexp.1.5.2'], require: ['csexp'], sections: [ { title: 'Encoding', description: 'Canonical S-expressions use length-prefixed format: "5:hello" instead of "hello".', steps: [ { code: 'Csexp.to_string (Csexp.Atom "hello");;', expect: '"5:hello"', description: 'Encode an atom (5 bytes, colon, data)' }, { code: 'Csexp.to_string (Csexp.List [Csexp.Atom "a"; Csexp.Atom "bc"]);;', expect: '"(1:a2:bc)"', description: 'Encode a list' }, { code: 'Csexp.serialised_length (Csexp.Atom "test");;', expect: '6', description: '"1:test" would be wrong; "4:test" = 6 bytes' }, ] }, { title: 'Decoding', description: 'parse_string decodes canonical S-expressions.', steps: [ { code: 'Csexp.parse_string "5:hello";;', expect: 'Ok', description: 'Parse a single atom' }, { code: 'Csexp.parse_string "(1:a2:bc)";;', expect: 'Ok', description: 'Parse a list' }, { code: 'Csexp.parse_string_many "1:a1:b";;', expect: 'Ok', description: 'Parse multiple S-expressions' }, { code: 'Csexp.parse_string "bad";;', expect: 'Error', description: 'Invalid input returns Error' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Base64 // ═══════════════════════════════════════════════════════════════════════ 'base64.3.4.0': { name: 'Base64', version: '3.4.0', opam: 'base64', description: 'Base64 encoding and decoding for OCaml', universe: U['base64.3.4.0'], require: ['base64'], sections: [ { title: 'Encoding', steps: [ { code: 'Base64.encode_string "Hello, World!";;', expect: 'SGVsbG8sIFdvcmxkIQ==', description: 'Encode a string to base64' }, { code: 'Base64.encode_string "";;', expect: '""', description: 'Empty string encodes to empty' }, { code: 'Base64.encode_string "a";;', expect: 'YQ==', description: 'Single character with padding' }, ] }, { title: 'Decoding', steps: [ { code: 'Base64.decode_exn "SGVsbG8sIFdvcmxkIQ==";;', expect: '"Hello, World!"', description: 'Decode base64 back to string' }, { code: 'Base64.decode "YQ==";;', expect: 'Ok "a"', description: 'Safe decode returns result' }, { code: 'Base64.decode "!!invalid!!";;', expect: 'Error', description: 'Invalid base64 returns Error' }, ] }, ], }, 'base64.3.5.2': { name: 'Base64', version: '3.5.2', opam: 'base64', description: 'Base64 encoding and decoding for OCaml', universe: U['base64.3.5.2'], require: ['base64'], sections: [ { title: 'Standard Encoding', steps: [ { code: 'Base64.encode_string "OCaml";;', expect: 'T0NhbWw=', description: 'Encode "OCaml" to base64' }, { code: 'Base64.decode_exn "T0NhbWw=";;', expect: '"OCaml"', description: 'Decode back to original' }, ] }, { title: 'Round-Trip', steps: [ { code: 'let test s = Base64.decode_exn (Base64.encode_string s) = s;;', expect: 'val test', description: 'Define a round-trip test function' }, { code: 'test "hello world";;', expect: 'true', description: 'Round-trip preserves data' }, { code: 'test "";;', expect: 'true', description: 'Empty string round-trips' }, { code: 'test "\\x00\\xff";;', expect: 'true', description: 'Binary data round-trips' }, ] }, ], }, 'bos.0.2.1': { name: 'Bos', version: '0.2.1', opam: 'bos', description: 'Basic OS interaction for OCaml', universe: U['bos.0.2.1'], require: ['bos'], sections: [ { title: 'Command Construction', description: 'Bos.Cmd builds shell commands declaratively with type-safe combinators.', steps: [ { code: 'let cmd = Bos.Cmd.(v "echo" % "hello" % "world");;', expect: 'Bos.Cmd.t', description: 'Build: echo hello world' }, { code: 'Bos.Cmd.to_string cmd;;', expect: 'echo', description: 'Convert to shell string' }, { code: 'Bos.Cmd.to_list cmd;;', expect: '["echo"; "hello"; "world"]', description: 'Convert to argument list' }, ] }, { title: 'Command Combinators', description: 'Commands support appending, conditional inclusion, and inspection.', steps: [ { code: 'let base = Bos.Cmd.(v "gcc" % "-O2");;', expect: 'Bos.Cmd.t', description: 'Base compiler command' }, { code: 'let full = Bos.Cmd.(base % "-o" % "main" %% v "main.c");;', expect: 'Bos.Cmd.t', description: 'Append arguments and a sub-command' }, { code: 'Bos.Cmd.to_list full;;', expect: '["gcc"', description: 'Full argument list' }, { code: 'Bos.Cmd.line_tool full;;', expect: 'Some "gcc"', description: 'Extract the tool name' }, { code: 'Bos.Cmd.is_empty Bos.Cmd.empty;;', expect: 'true', description: 'Empty command check' }, ] }, { title: 'Conditional Arguments', description: 'Bos.Cmd.on conditionally includes arguments.', steps: [ { code: 'let debug = true;;', expect: 'true', description: 'A debug flag' }, { code: 'Bos.Cmd.(v "gcc" %% on debug (v "-g") % "main.c") |> Bos.Cmd.to_list;;', expect: '["gcc"; "-g"; "main.c"]', description: 'Debug flag is included when true' }, { code: 'Bos.Cmd.(v "gcc" %% on false (v "-g") % "main.c") |> Bos.Cmd.to_list;;', expect: '["gcc"; "main.c"]', description: 'Debug flag is omitted when false' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Re (regular expressions) // ═══════════════════════════════════════════════════════════════════════ 're.1.10.4': { name: 'Re', version: '1.10.4', opam: 're', description: 'Regular expression library for OCaml', universe: U['re.1.10.4'], require: ['re'], sections: [ { title: 'Compiling and Matching', description: 'Re works in two steps: build a regex value, then compile it before matching.', steps: [ { code: 'let re = Re.Pcre.re "\\\\d+" |> Re.compile;;', expect: 'Re.re', description: 'Compile a PCRE-style regex for digits' }, { code: 'Re.execp re "abc123";;', expect: 'true', description: 'Test if the string matches anywhere' }, { code: 'Re.execp re "no digits";;', expect: 'false', description: 'No match returns false' }, ] }, { title: 'Extracting Matches', description: 'Re.exec returns a group object, and Re.Group.get extracts matched substrings.', steps: [ { code: 'let g = Re.exec re "abc123def";;', expect: 'Re.Group.t', description: 'Execute and get the match group' }, { code: 'Re.Group.get g 0;;', expect: '"123"', description: 'Group 0 is the whole match' }, ] }, { title: 'Finding All Matches', steps: [ { code: 'Re.all re "a1b22c333" |> List.map (fun g -> Re.Group.get g 0);;', expect: '["1"; "22"; "333"]', description: 'Find all digit sequences' }, { code: 'Re.split (Re.compile (Re.Pcre.re ",")) "a,b,c";;', expect: '["a"; "b"; "c"]', description: 'Split on comma' }, ] }, ], }, 're.1.11.0': { name: 'Re', version: '1.11.0', opam: 're', description: 'Regular expression library for OCaml', universe: U['re.1.11.0'], require: ['re'], sections: [ { title: 'PCRE Syntax', steps: [ { code: 'let word = Re.Pcre.re "[a-zA-Z]+" |> Re.compile;;', expect: 'Re.re', description: 'Compile a word pattern' }, { code: 'Re.all word "hello world" |> List.map (fun g -> Re.Group.get g 0);;', expect: '["hello"; "world"]', description: 'Find all words' }, ] }, { title: 'Replacement', steps: [ { code: 'Re.replace_string (Re.compile (Re.Pcre.re "\\\\d+")) ~by:"N" "abc123def456";;', expect: '"abcNdefN"', description: 'Replace all digit sequences' }, ] }, { title: 'Combinatorial API', description: 'Re also has a combinator API for building regexes without string syntax.', steps: [ { code: 'let re = Re.(seq [bos; rep1 digit; eos]) |> Re.compile;;', expect: 'Re.re', description: 'Match strings that are all digits' }, { code: 'Re.execp re "12345";;', expect: 'true', description: 'All digits matches' }, { code: 'Re.execp re "123abc";;', expect: 'false', description: 'Mixed string does not match' }, ] }, ], }, 're.1.12.0': { name: 'Re', version: '1.12.0', opam: 're', description: 'Regular expression library for OCaml', universe: U['re.1.12.0'], require: ['re'], sections: [ { title: 'Pattern Matching', steps: [ { code: 'let email_re = Re.Pcre.re "[^@]+@[^@]+" |> Re.compile;;', expect: 'Re.re', description: 'Simple email pattern' }, { code: 'Re.execp email_re "user@example.com";;', expect: 'true', description: 'Matches an email-like string' }, { code: 'Re.execp email_re "not-an-email";;', expect: 'false', description: 'No @ sign means no match' }, ] }, { title: 'Groups', description: 'Capture groups extract sub-matches.', steps: [ { code: 'let kv = Re.Pcre.re "(\\\\w+)=(\\\\w+)" |> Re.compile;;', expect: 'Re.re', description: 'Key=value pattern with groups' }, { code: 'let g = Re.exec kv "name=Alice";;', expect: 'Re.Group.t', description: 'Execute the match' }, { code: 'Re.Group.get g 1;;', expect: '"name"', description: 'Group 1: the key' }, { code: 'Re.Group.get g 2;;', expect: '"Alice"', description: 'Group 2: the value' }, ] }, ], }, 're.1.13.2': { name: 'Re', version: '1.13.2', opam: 're', description: 'Regular expression library for OCaml', universe: U['re.1.13.2'], require: ['re'], sections: [ { title: 'Splitting and Replacing', steps: [ { code: 'Re.split (Re.compile (Re.Pcre.re "\\\\s+")) "hello world foo";;', expect: '["hello"; "world"; "foo"]', description: 'Split on whitespace' }, { code: 'Re.replace_string (Re.compile (Re.Pcre.re "[aeiou]")) ~by:"*" "hello";;', expect: '"h*ll*"', description: 'Replace vowels' }, ] }, { title: 'Posix Character Classes', steps: [ { code: 'let re = Re.(rep1 alpha |> compile);;', expect: 'Re.re', description: 'Match alphabetic characters' }, { code: 'Re.execp re "hello";;', expect: 'true', description: 'All alpha matches' }, { code: 'Re.all re "abc123def" |> List.map (fun g -> Re.Group.get g 0);;', expect: '["abc"; "def"]', description: 'Find all alphabetic runs' }, ] }, ], }, 're.1.14.0': { name: 'Re', version: '1.14.0', opam: 're', description: 'Regular expression library for OCaml', universe: U['re.1.14.0'], require: ['re'], sections: [ { title: 'Combinatorial API', steps: [ { code: 'let hex = Re.(alt [rg \'0\' \'9\'; rg \'a\' \'f\'; rg \'A\' \'F\']) |> Re.rep1 |> Re.compile;;', expect: 'Re.re', description: 'Match hex strings' }, { code: 'Re.execp hex "deadBEEF";;', expect: 'true', description: 'Valid hex matches' }, { code: 'Re.all hex "ff0099" |> List.map (fun g -> Re.Group.get g 0);;', expect: '["ff0099"]', description: 'Extract hex values' }, ] }, { title: 'Capture Groups', steps: [ { code: 'let re = Re.Pcre.re "(\\\\d{4})-(\\\\d{2})" |> Re.compile;;', expect: 'Re.re', description: 'Date pattern with capture groups' }, { code: 'let g = Re.exec re "date: 2024-01";;', expect: 'Re.Group.t', description: 'Execute match' }, { code: 'Re.Group.get g 1;;', expect: '"2024"', description: 'First capture group (year)' }, { code: 'Re.Group.get g 2;;', expect: '"01"', description: 'Second capture group (month)' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Angstrom // ═══════════════════════════════════════════════════════════════════════ 'angstrom.0.15.0': { name: 'Angstrom', version: '0.15.0', opam: 'angstrom', description: 'Parser combinators for OCaml', universe: U['angstrom.0.15.0'], require: ['angstrom'], sections: [ { title: 'Basic Parsers', description: 'Angstrom provides primitive parsers and combinators for building complex parsers.', steps: [ { code: 'Angstrom.parse_string ~consume:Prefix (Angstrom.string "hello") "hello world";;', expect: 'Ok "hello"', description: 'Match a literal string' }, { code: 'Angstrom.parse_string ~consume:All (Angstrom.string "hello") "hello";;', expect: 'Ok "hello"', description: 'Consume:All requires full input match' }, { code: 'Angstrom.parse_string ~consume:All (Angstrom.string "hello") "hello world";;', expect: 'Error', description: 'Consume:All fails with leftover input' }, ] }, { title: 'Character Parsers', steps: [ { code: 'let digits = Angstrom.take_while1 (function \'0\'..\'9\' -> true | _ -> false);;', expect: 'Angstrom.t', description: 'Parser for one or more digits' }, { code: 'Angstrom.parse_string ~consume:Prefix digits "123abc";;', expect: 'Ok "123"', description: 'Consume digits, stop at letters' }, ] }, { title: 'Combinators', description: 'Combine parsers with sep_by, many, choice, and operators.', steps: [ { code: 'let word = Angstrom.take_while1 (function \'a\'..\'z\' | \'A\'..\'Z\' -> true | _ -> false);;', expect: 'Angstrom.t', description: 'Parser for words' }, { code: 'let csv = Angstrom.sep_by (Angstrom.char \',\') word;;', expect: 'Angstrom.t', description: 'Comma-separated words parser' }, { code: 'Angstrom.parse_string ~consume:All csv "foo,bar,baz";;', expect: 'Ok ["foo"; "bar"; "baz"]', description: 'Parse CSV into a list' }, { code: 'Angstrom.parse_string ~consume:Prefix (Angstrom.many (Angstrom.char \'a\')) "aaab";;', expect: 'Ok', description: 'many matches zero or more' }, ] }, ], }, 'angstrom.0.16.1': { name: 'Angstrom', version: '0.16.1', opam: 'angstrom', description: 'Parser combinators for OCaml', universe: U['angstrom.0.16.1'], require: ['angstrom'], sections: [ { title: 'Parsing Structured Data', steps: [ { code: 'let is_digit c = c >= \'0\' && c <= \'9\';;', expect: 'val is_digit', description: 'Helper: digit predicate' }, { code: 'let integer = Angstrom.(take_while1 is_digit >>| int_of_string);;', expect: 'Angstrom.t', description: 'Integer parser using >>| (map)' }, { code: 'Angstrom.parse_string ~consume:Prefix integer "42rest";;', expect: 'Ok 42', description: 'Parse and convert to int' }, ] }, { title: 'Sequencing and Alternatives', description: 'Use *> to discard left, <* to discard right, <|> for alternatives.', steps: [ { code: 'let bool_p = Angstrom.((string "true" >>| fun _ -> true) <|> (string "false" >>| fun _ -> false));;', expect: 'Angstrom.t', description: 'Boolean parser with alternatives' }, { code: 'Angstrom.parse_string ~consume:All bool_p "true";;', expect: 'Ok true', description: 'Parse "true"' }, { code: 'Angstrom.parse_string ~consume:All bool_p "false";;', expect: 'Ok false', description: 'Parse "false"' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Tyre // ═══════════════════════════════════════════════════════════════════════ 'tyre.0.5': { name: 'Tyre', version: '0.5', opam: 'tyre', description: 'Typed regular expressions for OCaml', universe: U['tyre.0.5'], require: ['tyre'], sections: [ { title: 'Basic Typed Matching', description: 'Tyre combines regex matching with type extraction.', steps: [ { code: 'let re = Tyre.compile Tyre.int;;', expect: 'Tyre.re', description: 'Compile a typed regex for integers' }, { code: 'Tyre.exec re "42";;', expect: 'Ok 42', description: 'Match and extract an int' }, { code: 'Tyre.exec re "abc";;', expect: 'Error', description: 'Non-matching input returns Error' }, ] }, { title: 'Combining Patterns', description: 'Use <&> to sequence patterns (returns tuples) and *> or <* to discard parts.', steps: [ { code: 'let re = Tyre.compile Tyre.(str "v" *> int);;', expect: 'Tyre.re', description: 'Match "v" prefix then extract an int' }, { code: 'Tyre.exec re "v42";;', expect: 'Ok 42', description: 'Extract version number' }, { code: 'let dim = Tyre.compile Tyre.(int <&> str "x" *> int);;', expect: 'Tyre.re', description: 'Match WxH dimension pattern' }, { code: 'Tyre.exec dim "800x600";;', expect: 'Ok (800, 600)', description: 'Extract both dimensions as a tuple' }, ] }, ], }, 'tyre.1.0': { name: 'Tyre', version: '1.0', opam: 'tyre', description: 'Typed regular expressions for OCaml', universe: U['tyre.1.0'], require: ['tyre'], sections: [ { title: 'Typed Extraction', steps: [ { code: 'Tyre.exec (Tyre.compile Tyre.int) "123";;', expect: 'Ok 123', description: 'Extract an integer' }, { code: 'Tyre.exec (Tyre.compile Tyre.float) "3.14";;', expect: 'Ok 3.14', description: 'Extract a float' }, { code: 'Tyre.exec (Tyre.compile Tyre.bool) "true";;', expect: 'Ok true', description: 'Extract a boolean' }, ] }, { title: 'Optional and Repeated', steps: [ { code: 'let re = Tyre.compile Tyre.(opt int);;', expect: 'Tyre.re', description: 'Optional integer pattern' }, { code: 'Tyre.exec re "42";;', expect: 'Ok (Some 42)', description: 'Present value gives Some' }, { code: 'Tyre.exec re "";;', expect: 'Ok None', description: 'Empty input gives None' }, ] }, { title: 'Bidirectional: Eval', description: 'Tyre.eval converts values back to strings (unparse).', steps: [ { code: 'Tyre.eval Tyre.int 42;;', expect: '"42"', description: 'Unparse an integer' }, { code: 'Tyre.eval Tyre.(str "v" *> int) 3;;', expect: '"v3"', description: 'Unparse with literal prefix' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Uuseg // ═══════════════════════════════════════════════════════════════════════ 'uuseg.14.0.0': { name: 'Uuseg', version: '14.0.0', opam: 'uuseg', description: 'Unicode text segmentation (Unicode 14.0.0)', universe: U['uuseg.14.0.0'], require: ['uuseg'], sections: [ { title: 'Unicode Version', steps: [ { code: 'Uuseg.unicode_version;;', expect: '"14.0.0"', description: 'Check the Unicode version' }, ] }, { title: 'Segmenter Creation', description: 'Uuseg.create makes a segmenter for grapheme clusters, words, or sentences.', steps: [ { code: 'let seg = Uuseg.create `Grapheme_cluster;;', expect: 'Uuseg.t', description: 'Create a grapheme cluster segmenter' }, { code: 'let wseg = Uuseg.create `Word;;', expect: 'Uuseg.t', description: 'Create a word segmenter' }, ] }, ], }, 'uuseg.15.0.0': { name: 'Uuseg', version: '15.0.0', opam: 'uuseg', description: 'Unicode text segmentation (Unicode 15.0.0)', universe: U['uuseg.15.0.0'], require: ['uuseg'], sections: [ { title: 'Unicode Version', steps: [ { code: 'Uuseg.unicode_version;;', expect: '"15.0.0"', description: 'Check the Unicode version' }, ] }, { title: 'Segmenter Types', steps: [ { code: 'let _ = Uuseg.create `Grapheme_cluster;;', expect: 'Uuseg.t', description: 'Grapheme cluster segmentation' }, { code: 'let _ = Uuseg.create `Word;;', expect: 'Uuseg.t', description: 'Word segmentation' }, { code: 'let _ = Uuseg.create `Sentence;;', expect: 'Uuseg.t', description: 'Sentence segmentation' }, { code: 'let _ = Uuseg.create `Line_break;;', expect: 'Uuseg.t', description: 'Line break opportunity segmentation' }, ] }, ], }, 'uuseg.16.0.0': { name: 'Uuseg', version: '16.0.0', opam: 'uuseg', description: 'Unicode text segmentation (Unicode 16.0.0)', universe: U['uuseg.16.0.0'], require: ['uuseg'], sections: [ { title: 'Unicode Version', steps: [ { code: 'Uuseg.unicode_version;;', expect: '"16.0.0"', description: 'Check the Unicode version' }, ] }, { title: 'Segmenter API', description: 'Feed Uchars to a segmenter and it reports segment boundaries.', steps: [ { code: 'let seg = Uuseg.create `Grapheme_cluster;;', expect: 'Uuseg.t', description: 'Create a grapheme cluster segmenter' }, { code: 'Uuseg.add seg (`Uchar (Uchar.of_int 0x0041));;', expect: '', description: "Add 'A' to the segmenter" }, { code: 'Uuseg.add seg `End;;', expect: '', description: 'Signal end of input' }, ] }, ], }, 'uuseg.17.0.0': { name: 'Uuseg', version: '17.0.0', opam: 'uuseg', description: 'Unicode text segmentation (Unicode 17.0.0)', universe: U['uuseg.17.0.0'], require: ['uuseg'], sections: [ { title: 'Unicode Version', steps: [ { code: 'Uuseg.unicode_version;;', expect: '"17.0.0"', description: 'Check the Unicode version' }, ] }, { title: 'Segmenter Types', steps: [ { code: 'let _ = Uuseg.create `Grapheme_cluster;;', expect: 'Uuseg.t', description: 'Grapheme cluster boundaries' }, { code: 'let _ = Uuseg.create `Word;;', expect: 'Uuseg.t', description: 'Word boundaries' }, { code: 'let _ = Uuseg.create `Sentence;;', expect: 'Uuseg.t', description: 'Sentence boundaries' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Containers // ═══════════════════════════════════════════════════════════════════════ 'containers.3.17': { name: 'Containers', version: '3.17', opam: 'containers', description: 'A modular extension of the OCaml standard library', universe: U['containers.3.17'], require: ['containers'], sections: [ { title: 'CCList Advanced', steps: [ { code: 'CCList.product (fun a b -> (a, b)) [1; 2] ["a"; "b"];;', expect: '[(1, "a")', description: 'Cartesian product' }, { code: 'CCList.pure 42;;', expect: '[42]', description: 'Wrap a value in a singleton list' }, ] }, { title: 'CCString', steps: [ { code: 'CCString.take 5 "hello world";;', expect: '"hello"', description: 'Take first 5 characters' }, { code: 'CCString.drop 6 "hello world";;', expect: '"world"', description: 'Drop first 6 characters' }, { code: 'CCString.chop_prefix ~pre:"http://" "http://example.com";;', expect: 'Some "example.com"', description: 'Remove prefix if present' }, { code: 'CCString.chop_suffix ~suf:".ml" "main.ml";;', expect: 'Some "main"', description: 'Remove suffix if present' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Iter // ═══════════════════════════════════════════════════════════════════════ 'iter.1.7': { name: 'Iter', version: '1.7', opam: 'iter', description: 'Simple, efficient iterators for OCaml', universe: U['iter.1.7'], require: ['iter'], sections: [ { title: 'Creating Iterators', description: 'Iter.t is (\'a -> unit) -> unit — a continuation-based iterator.', steps: [ { code: 'Iter.of_list [1; 2; 3] |> Iter.to_list;;', expect: '[1; 2; 3]', description: 'Round-trip through Iter' }, { code: 'Iter.(1 -- 5) |> Iter.to_list;;', expect: '[1; 2; 3; 4; 5]', description: 'Integer range (inclusive)' }, { code: 'Iter.init (fun i -> i * i) |> Iter.take 5 |> Iter.to_list;;', expect: '[0; 1; 4; 9; 16]', description: 'Infinite sequence, take first 5' }, ] }, { title: 'Transformations', steps: [ { code: 'Iter.(1 -- 10) |> Iter.filter (fun x -> x mod 2 = 0) |> Iter.to_list;;', expect: '[2; 4; 6; 8; 10]', description: 'Filter even numbers' }, { code: 'Iter.(1 -- 5) |> Iter.map (fun x -> x * 2) |> Iter.to_list;;', expect: '[2; 4; 6; 8; 10]', description: 'Map doubling' }, { code: 'Iter.(1 -- 5) |> Iter.fold (+) 0;;', expect: '15', description: 'Fold to compute sum' }, ] }, ], }, 'iter.1.8': { name: 'Iter', version: '1.8', opam: 'iter', description: 'Simple, efficient iterators for OCaml', universe: U['iter.1.8'], require: ['iter'], sections: [ { title: 'Iterator Basics', steps: [ { code: 'Iter.empty |> Iter.to_list;;', expect: '[]', description: 'Empty iterator' }, { code: 'Iter.singleton 42 |> Iter.to_list;;', expect: '[42]', description: 'Single-element iterator' }, { code: 'Iter.repeat 3 |> Iter.take 4 |> Iter.to_list;;', expect: '[3; 3; 3; 3]', description: 'Infinite repetition, take 4' }, ] }, { title: 'Flat Map and Product', steps: [ { code: 'Iter.(1 -- 3) |> Iter.flat_map (fun x -> Iter.of_list [x; x*10]) |> Iter.to_list;;', expect: '[1; 10; 2; 20; 3; 30]', description: 'Flat map' }, { code: 'Iter.product (Iter.of_list [1;2]) (Iter.of_list ["a";"b"]) |> Iter.to_list;;', expect: '[(1, "a")', description: 'Cartesian product' }, ] }, ], }, 'iter.1.9': { name: 'Iter', version: '1.9', opam: 'iter', description: 'Simple, efficient iterators for OCaml', universe: U['iter.1.9'], require: ['iter'], sections: [ { title: 'Aggregation', steps: [ { code: 'Iter.(1 -- 100) |> Iter.fold (+) 0;;', expect: '5050', description: 'Sum 1 to 100' }, { code: 'Iter.(1 -- 10) |> Iter.length;;', expect: '10', description: 'Count elements' }, { code: 'Iter.of_list ["hello"; "world"] |> Iter.for_all (fun s -> String.length s > 3);;', expect: 'true', description: 'Check a predicate for all elements' }, { code: 'Iter.of_list [1; 2; 3] |> Iter.exists (fun x -> x > 2);;', expect: 'true', description: 'Check if any element matches' }, ] }, { title: 'Conversion', steps: [ { code: 'Iter.of_list [1; 2; 3] |> Iter.to_rev_list;;', expect: '[3; 2; 1]', description: 'Convert to reversed list' }, { code: 'Iter.of_list [("a",1); ("b",2)] |> Iter.to_hashtbl;;', expect: 'Hashtbl', description: 'Convert to hashtable' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // OCamlgraph // ═══════════════════════════════════════════════════════════════════════ 'ocamlgraph.2.0.0': { name: 'OCamlgraph', version: '2.0.0', opam: 'ocamlgraph', description: 'Graph library for OCaml', universe: U['ocamlgraph.2.0.0'], require: ['ocamlgraph'], sections: [ { title: 'Building Graphs', description: 'Graph.Pack.Digraph provides an easy-to-use imperative directed graph. Vertices must be reused (not re-created).', steps: [ { code: 'let module G = Graph.Pack.Digraph in let g = G.create () in let v1 = G.V.create 1 in let v2 = G.V.create 2 in let v3 = G.V.create 3 in G.add_edge g v1 v2; G.add_edge g v2 v3; G.nb_vertex g;;', expect: '3', description: 'Create a graph with 3 vertices' }, { code: 'let module G = Graph.Pack.Digraph in let g = G.create () in let v1 = G.V.create 1 in let v2 = G.V.create 2 in let v3 = G.V.create 3 in G.add_edge g v1 v2; G.add_edge g v1 v3; G.nb_edges g;;', expect: '2', description: '2 edges from vertex 1' }, ] }, ], }, 'ocamlgraph.2.1.0': { name: 'OCamlgraph', version: '2.1.0', opam: 'ocamlgraph', description: 'Graph library for OCaml', universe: U['ocamlgraph.2.1.0'], require: ['ocamlgraph'], sections: [ { title: 'Imperative Graphs', steps: [ { code: 'let module G = Graph.Pack.Digraph in let g = G.create () in let v1 = G.V.create 10 in let v2 = G.V.create 20 in G.add_edge g v1 v2; G.mem_edge g v1 v2;;', expect: 'true', description: 'Check edge existence' }, { code: 'let module G = Graph.Pack.Digraph in let g = G.create () in G.add_edge g (G.V.create 1) (G.V.create 2); G.add_edge g (G.V.create 2) (G.V.create 3); G.add_edge g (G.V.create 3) (G.V.create 1); G.nb_edges g;;', expect: '3', description: 'A cycle with 3 edges' }, ] }, ], }, 'ocamlgraph.2.2.0': { name: 'OCamlgraph', version: '2.2.0', opam: 'ocamlgraph', description: 'Graph library for OCaml', universe: U['ocamlgraph.2.2.0'], require: ['ocamlgraph'], sections: [ { title: 'Graph Operations', steps: [ { code: 'let module G = Graph.Pack.Digraph in let g = G.create () in let vs = Array.init 6 G.V.create in for i = 0 to 4 do G.add_edge g vs.(i) vs.(i+1) done; G.nb_vertex g;;', expect: '6', description: 'A chain of 6 vertices' }, { code: 'let module G = Graph.Pack.Digraph in let g = G.create () in let vs = Array.init 6 G.V.create in for i = 0 to 4 do G.add_edge g vs.(i) vs.(i+1) done; G.nb_edges g;;', expect: '5', description: '5 edges in the chain' }, { code: 'let module G = Graph.Pack.Digraph in let g = G.create () in let v0 = G.V.create 0 in let v1 = G.V.create 1 in let v2 = G.V.create 2 in G.add_edge g v0 v1; G.add_edge g v0 v2; G.out_degree g v0;;', expect: '2', description: 'Out-degree of vertex 0' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Digestif // ═══════════════════════════════════════════════════════════════════════ 'digestif.1.1.2': { name: 'Digestif', version: '1.1.2', opam: 'digestif', description: 'Cryptographic hash functions for OCaml', universe: U['digestif.1.1.2'], require: ['digestif'], sections: [ { title: 'SHA-256', description: 'Digestif.SHA256 provides SHA-256 hashing with hex encoding.', steps: [ { code: 'Digestif.SHA256.digest_string "hello" |> Digestif.SHA256.to_hex;;', expect: '2cf24dba', description: 'SHA-256 of "hello"' }, { code: 'Digestif.SHA256.digest_string "" |> Digestif.SHA256.to_hex;;', expect: 'e3b0c442', description: 'SHA-256 of empty string' }, { code: 'let h1 = Digestif.SHA256.digest_string "test" in let h2 = Digestif.SHA256.digest_string "test" in Digestif.SHA256.equal h1 h2;;', expect: 'true', description: 'Same input produces same hash (constant-time equal)' }, ] }, { title: 'MD5', steps: [ { code: 'Digestif.MD5.digest_string "hello" |> Digestif.MD5.to_hex;;', expect: '5d41402a', description: 'MD5 of "hello"' }, ] }, ], }, 'digestif.1.3.0': { name: 'Digestif', version: '1.3.0', opam: 'digestif', description: 'Cryptographic hash functions for OCaml', universe: U['digestif.1.3.0'], require: ['digestif'], sections: [ { title: 'Multiple Algorithms', steps: [ { code: 'Digestif.SHA256.digest_string "OCaml" |> Digestif.SHA256.to_hex;;', expect: 'string', description: 'SHA-256 hash' }, { code: 'Digestif.SHA512.digest_string "OCaml" |> Digestif.SHA512.to_hex;;', expect: 'string', description: 'SHA-512 hash' }, { code: 'Digestif.SHA1.digest_string "OCaml" |> Digestif.SHA1.to_hex;;', expect: 'string', description: 'SHA-1 hash' }, ] }, { title: 'HMAC', description: 'HMAC provides keyed hashing for authentication.', steps: [ { code: 'Digestif.SHA256.hmac_string ~key:"secret" "message" |> Digestif.SHA256.to_hex;;', expect: 'string', description: 'HMAC-SHA256 with a key' }, { code: 'let h1 = Digestif.SHA256.hmac_string ~key:"k" "m" in let h2 = Digestif.SHA256.hmac_string ~key:"k" "m" in Digestif.SHA256.equal h1 h2;;', expect: 'true', description: 'Same key+message = same HMAC' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Hex // ═══════════════════════════════════════════════════════════════════════ 'hex.1.4.0': { name: 'Hex', version: '1.4.0', opam: 'hex', description: 'Hex encoding and decoding for OCaml', universe: U['hex.1.4.0'], require: ['hex'], sections: [ { title: 'Encoding and Decoding', steps: [ { code: 'Hex.of_string "Hello";;', expect: '`Hex', description: 'Encode string to hex' }, { code: 'Hex.to_string (`Hex "48656c6c6f");;', expect: '"Hello"', description: 'Decode hex to string' }, { code: 'Hex.hexdump_s (Hex.of_string "Hello, World!");;', expect: '4865', description: 'Hexdump for debugging' }, ] }, ], }, 'hex.1.5.0': { name: 'Hex', version: '1.5.0', opam: 'hex', description: 'Hex encoding and decoding for OCaml', universe: U['hex.1.5.0'], require: ['hex'], sections: [ { title: 'Hex Encoding', steps: [ { code: 'Hex.of_string "\\x00\\xff";;', expect: '`Hex "00ff"', description: 'Binary data to hex' }, { code: 'Hex.to_string (`Hex "00ff");;', expect: 'string', description: 'Hex back to binary' }, { code: 'Hex.show (Hex.of_string "AB");;', expect: '"4142"', description: 'Show hex representation' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Eqaf // ═══════════════════════════════════════════════════════════════════════ 'eqaf.0.9': { name: 'Eqaf', version: '0.9', opam: 'eqaf', description: 'Constant-time string comparison for OCaml', universe: U['eqaf.0.9'], require: ['eqaf'], sections: [ { title: 'Constant-Time Comparison', description: 'Eqaf.equal compares strings in constant time, preventing timing attacks.', steps: [ { code: 'Eqaf.equal "secret" "secret";;', expect: 'true', description: 'Equal strings' }, { code: 'Eqaf.equal "secret" "wrong!";;', expect: 'false', description: 'Different strings' }, { code: 'Eqaf.equal "" "";;', expect: 'true', description: 'Empty strings are equal' }, ] }, ], }, 'eqaf.0.10': { name: 'Eqaf', version: '0.10', opam: 'eqaf', description: 'Constant-time string comparison for OCaml', universe: U['eqaf.0.10'], require: ['eqaf'], sections: [ { title: 'Constant-Time Operations', steps: [ { code: 'Eqaf.equal "abc" "abc";;', expect: 'true', description: 'Same strings (constant time)' }, { code: 'Eqaf.equal "abc" "xyz";;', expect: 'false', description: 'Different strings (same timing as equal case)' }, { code: 'Eqaf.compare_be "a" "b";;', expect: '-1', description: 'Constant-time big-endian comparison' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Uri // ═══════════════════════════════════════════════════════════════════════ 'uri.4.2.0': { name: 'Uri', version: '4.2.0', opam: 'uri', description: 'URI parsing and manipulation for OCaml', universe: U['uri.4.2.0'], require: ['uri'], sections: [ { title: 'Parsing URIs', steps: [ { code: 'let u = Uri.of_string "https://example.com:8080/path?q=1#frag";;', expect: 'Uri.t', description: 'Parse a full URI' }, { code: 'Uri.scheme u;;', expect: 'Some "https"', description: 'Extract the scheme' }, { code: 'Uri.host u;;', expect: 'Some "example.com"', description: 'Extract the host' }, { code: 'Uri.port u;;', expect: 'Some 8080', description: 'Extract the port' }, { code: 'Uri.path u;;', expect: '"/path"', description: 'Extract the path' }, { code: 'Uri.fragment u;;', expect: 'Some "frag"', description: 'Extract the fragment' }, ] }, { title: 'Query Parameters', steps: [ { code: 'let u = Uri.of_string "http://example.com?a=1&b=2";;', expect: 'Uri.t', description: 'URI with query params' }, { code: 'Uri.get_query_param u "a";;', expect: 'Some "1"', description: 'Get a single query parameter' }, { code: 'Uri.query u;;', expect: '[("a"', description: 'Get all query parameters' }, ] }, { title: 'Building URIs', steps: [ { code: 'Uri.make ~scheme:"https" ~host:"example.com" ~path:"/api" () |> Uri.to_string;;', expect: 'https://example.com/api', description: 'Build a URI from components' }, { code: 'Uri.with_query\' (Uri.of_string "http://x.com") [("key", "val")] |> Uri.to_string;;', expect: 'key=val', description: 'Add query parameters' }, ] }, ], }, 'uri.4.4.0': { name: 'Uri', version: '4.4.0', opam: 'uri', description: 'URI parsing and manipulation for OCaml', universe: U['uri.4.4.0'], require: ['uri'], sections: [ { title: 'URI Components', steps: [ { code: 'let u = Uri.of_string "ftp://user@host/file.txt";;', expect: 'Uri.t', description: 'Parse an FTP URI' }, { code: 'Uri.scheme u;;', expect: 'Some "ftp"', description: 'FTP scheme' }, { code: 'Uri.userinfo u;;', expect: 'Some "user"', description: 'Extract userinfo' }, { code: 'Uri.host u;;', expect: 'Some "host"', description: 'Extract host' }, { code: 'Uri.path u;;', expect: '"/file.txt"', description: 'Extract path' }, ] }, { title: 'URI Manipulation', steps: [ { code: 'let u = Uri.of_string "http://example.com/old" in Uri.with_path u "/new" |> Uri.to_string;;', expect: 'http://example.com/new', description: 'Replace the path' }, { code: 'Uri.resolve "http" (Uri.of_string "http://example.com/a/b") (Uri.of_string "../c") |> Uri.to_string;;', expect: 'example.com', description: 'Resolve a relative reference' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Ipaddr // ═══════════════════════════════════════════════════════════════════════ 'ipaddr.5.6.0': { name: 'Ipaddr', version: '5.6.0', opam: 'ipaddr', description: 'IP address parsing and manipulation for OCaml', universe: U['ipaddr.5.6.0'], require: ['ipaddr'], sections: [ { title: 'IPv4 Addresses', steps: [ { code: 'Ipaddr.V4.of_string_exn "192.168.1.1";;', expect: 'Ipaddr.V4.t', description: 'Parse an IPv4 address' }, { code: 'Ipaddr.V4.to_string (Ipaddr.V4.of_string_exn "10.0.0.1");;', expect: '"10.0.0.1"', description: 'Convert back to string' }, { code: 'Ipaddr.V4.localhost |> Ipaddr.V4.to_string;;', expect: '"127.0.0.1"', description: 'Localhost constant' }, ] }, { title: 'IPv6 Addresses', steps: [ { code: 'Ipaddr.V6.of_string_exn "::1" |> Ipaddr.V6.to_string;;', expect: '"::1"', description: 'IPv6 loopback' }, { code: 'Ipaddr.V6.localhost |> Ipaddr.V6.to_string;;', expect: '"::1"', description: 'IPv6 localhost constant' }, ] }, { title: 'Generic IP', steps: [ { code: 'Ipaddr.of_string_exn "192.168.1.1" |> Ipaddr.to_string;;', expect: '"192.168.1.1"', description: 'Parse any IP address' }, { code: 'Ipaddr.of_string_exn "::1" |> Ipaddr.to_string;;', expect: '"::1"', description: 'Parse IPv6 through generic interface' }, ] }, ], }, 'ipaddr.5.6.1': { name: 'Ipaddr', version: '5.6.1', opam: 'ipaddr', description: 'IP address parsing and manipulation for OCaml', universe: U['ipaddr.5.6.1'], require: ['ipaddr'], sections: [ { title: 'Address Operations', steps: [ { code: 'Ipaddr.V4.of_string "invalid";;', expect: 'Error', description: 'Invalid address returns Error' }, { code: 'Ipaddr.V4.of_string "10.0.0.1";;', expect: 'Ok', description: 'Valid address returns Ok' }, { code: 'Ipaddr.V4.(compare localhost (of_string_exn "127.0.0.1"));;', expect: '0', description: 'Localhost equals 127.0.0.1' }, ] }, { title: 'CIDR Prefixes', steps: [ { code: 'let prefix = Ipaddr.V4.Prefix.of_string_exn "192.168.0.0/24";;', expect: 'Ipaddr.V4.Prefix.t', description: 'Parse a CIDR prefix' }, { code: 'Ipaddr.V4.Prefix.mem (Ipaddr.V4.of_string_exn "192.168.0.42") prefix;;', expect: 'true', description: 'Address is in the prefix' }, { code: 'Ipaddr.V4.Prefix.mem (Ipaddr.V4.of_string_exn "192.168.1.1") prefix;;', expect: 'false', description: 'Address is not in the prefix' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Domain_name // ═══════════════════════════════════════════════════════════════════════ 'domain-name.0.4.1': { name: 'Domain_name', version: '0.4.1', opam: 'domain-name', description: 'Domain name parsing and validation for OCaml', universe: U['domain-name.0.4.1'], require: ['domain-name'], sections: [ { title: 'Parsing Domain Names', steps: [ { code: 'Domain_name.of_string_exn "example.com";;', expect: 'Domain_name.t', description: 'Parse a domain name' }, { code: 'Domain_name.to_string (Domain_name.of_string_exn "www.example.com");;', expect: '"www.example.com"', description: 'Convert back to string' }, { code: 'Domain_name.of_string "invalid..domain";;', expect: 'Error', description: 'Double dots are invalid' }, ] }, { title: 'Domain Name Operations', steps: [ { code: 'Domain_name.of_string_exn "sub.example.com" |> Domain_name.count_labels;;', expect: '3', description: 'Count labels (sub, example, com)' }, { code: 'Domain_name.equal (Domain_name.of_string_exn "A.COM") (Domain_name.of_string_exn "a.com");;', expect: 'true', description: 'Domain names are case-insensitive' }, ] }, ], }, 'domain-name.0.5.0': { name: 'Domain_name', version: '0.5.0', opam: 'domain-name', description: 'Domain name parsing and validation for OCaml', universe: U['domain-name.0.5.0'], require: ['domain-name'], sections: [ { title: 'Domain Names', steps: [ { code: 'Domain_name.of_string_exn "mail.example.org";;', expect: 'Domain_name.t', description: 'Parse a domain name' }, { code: 'Domain_name.of_string_exn "example.com" |> Domain_name.count_labels;;', expect: '2', description: 'Two labels' }, { code: 'Domain_name.is_subdomain ~subdomain:(Domain_name.of_string_exn "sub.example.com") ~domain:(Domain_name.of_string_exn "example.com");;', expect: 'true', description: 'Check subdomain relationship' }, ] }, { title: 'Host Names', description: 'Domain_name.host_exn validates a domain name as a valid hostname.', steps: [ { code: 'Domain_name.host_exn (Domain_name.of_string_exn "example.com");;', expect: 'Domain_name.t', description: 'Valid hostname' }, { code: 'Domain_name.to_string (Domain_name.of_string_exn "example.com");;', expect: '"example.com"', description: 'Convert back to string' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // Zarith // ═══════════════════════════════════════════════════════════════════════ 'zarith.1.13': { name: 'Zarith', version: '1.13', opam: 'zarith', description: 'Arbitrary-precision integers and rationals for OCaml', universe: U['zarith.1.13'], require: ['zarith'], sections: [ { title: 'Big Integers', description: 'Z.t represents arbitrary-precision integers.', steps: [ { code: 'Z.of_int 42;;', expect: '42', description: 'Create from int' }, { code: 'Z.of_string "999999999999999999999";;', expect: '999999999999999999999', description: 'Create from string (exceeds int range)' }, { code: 'Z.add (Z.of_int 1) (Z.of_string "999999999999999999999");;', expect: '1000000000000000000000', description: 'Arbitrary-precision addition' }, ] }, { title: 'Arithmetic', steps: [ { code: 'Z.mul (Z.of_int 1000000) (Z.of_int 1000000);;', expect: '1000000000000', description: 'Multiplication' }, { code: 'Z.pow (Z.of_int 2) 100 |> Z.to_string;;', expect: '1267650600228229401496703205376', description: '2^100 as a string' }, { code: 'Z.rem (Z.of_int 17) (Z.of_int 5);;', expect: '2', description: 'Remainder' }, { code: 'Z.gcd (Z.of_int 12) (Z.of_int 18);;', expect: '6', description: 'Greatest common divisor' }, ] }, { title: 'Comparison', steps: [ { code: 'Z.compare (Z.of_int 10) (Z.of_int 20);;', expect: '-1', description: '10 < 20' }, { code: 'Z.equal Z.zero Z.zero;;', expect: 'true', description: 'Zero equals zero' }, { code: 'Z.sign (Z.of_int (-5));;', expect: '-1', description: 'Sign of negative number' }, ] }, ], }, 'zarith.1.14': { name: 'Zarith', version: '1.14', opam: 'zarith', description: 'Arbitrary-precision integers and rationals for OCaml', universe: U['zarith.1.14'], require: ['zarith'], sections: [ { title: 'Big Integer Arithmetic', steps: [ { code: 'Z.(of_int 2 ** 256) |> Z.to_string |> String.length;;', expect: '78', description: '2^256 has 78 digits' }, { code: 'Z.probab_prime (Z.of_int 97) 25;;', expect: '2', description: '97 is prime (2 = definitely prime)' }, { code: 'Z.probab_prime (Z.of_int 100) 25;;', expect: '0', description: '100 is composite (0 = definitely not prime)' }, ] }, { title: 'Rationals (Q module)', description: 'Q.t represents exact rational numbers.', steps: [ { code: 'Q.of_ints 1 3;;', expect: '1/3', description: 'Create the fraction 1/3' }, { code: 'Q.add (Q.of_ints 1 3) (Q.of_ints 1 6);;', expect: '1/2', description: '1/3 + 1/6 = 1/2 (auto-simplified)' }, { code: 'Q.mul (Q.of_ints 2 3) (Q.of_ints 3 4);;', expect: '1/2', description: '2/3 * 3/4 = 1/2' }, { code: 'Q.to_float (Q.of_ints 1 3);;', expect: '0.333333', description: 'Convert to float (approximate)' }, ] }, ], }, // ═══════════════════════════════════════════════════════════════════════ // QCheck // ═══════════════════════════════════════════════════════════════════════ 'qcheck-core.0.25': { name: 'QCheck', version: '0.25', opam: 'qcheck-core', description: 'Property-based testing for OCaml', universe: U['qcheck-core.0.25'], require: ['qcheck-core'], sections: [ { title: 'Generators', description: 'QCheck2.Gen provides random value generators with integrated shrinking.', steps: [ { code: 'QCheck2.Gen.generate1 QCheck2.Gen.int;;', expect: 'int', description: 'Generate a random integer' }, { code: 'QCheck2.Gen.generate1 (QCheck2.Gen.return 42);;', expect: '42', description: 'Constant generator always returns 42' }, { code: 'QCheck2.Gen.generate1 (QCheck2.Gen.list QCheck2.Gen.small_int) |> List.length >= 0;;', expect: 'true', description: 'Generate a random list of small ints' }, ] }, { title: 'Property Tests', description: 'QCheck2.Test.make creates a test, check_exn runs it.', steps: [ { code: 'let t = QCheck2.Test.make ~name:"commutative" QCheck2.Gen.(pair int int) (fun (a, b) -> a + b = b + a);;', expect: 'QCheck2.Test.t', description: 'Addition is commutative' }, { code: 'QCheck2.Test.check_exn t;;', expect: 'unit', description: 'Test passes (no exception)' }, { code: 'let t2 = QCheck2.Test.make ~name:"rev rev" QCheck2.Gen.(list small_int) (fun l -> List.rev (List.rev l) = l);;', expect: 'QCheck2.Test.t', description: 'Double reverse is identity' }, { code: 'QCheck2.Test.check_exn t2;;', expect: 'unit', description: 'Test passes' }, ] }, ], }, 'qcheck-core.0.27': { name: 'QCheck', version: '0.27', opam: 'qcheck-core', description: 'Property-based testing for OCaml', universe: U['qcheck-core.0.27'], require: ['qcheck-core'], sections: [ { title: 'Generators', steps: [ { code: 'QCheck2.Gen.generate1 (QCheck2.Gen.oneof [QCheck2.Gen.return 1; QCheck2.Gen.return 2]);;', expect: 'int', description: 'Choose between generators randomly' }, { code: 'QCheck2.Gen.generate1 (QCheck2.Gen.map (fun x -> x * 2) QCheck2.Gen.small_int);;', expect: 'int', description: 'Map over a generator' }, ] }, { title: 'Testing Properties', steps: [ { code: 'let t = QCheck2.Test.make ~name:"sort idempotent" QCheck2.Gen.(list small_int) (fun l -> let s = List.sort compare l in List.sort compare s = s);;', expect: 'QCheck2.Test.t', description: 'Sorting is idempotent' }, { code: 'QCheck2.Test.check_exn t;;', expect: 'unit', description: 'Property holds' }, { code: 'let t = QCheck2.Test.make ~count:1000 ~name:"length" QCheck2.Gen.(list small_int) (fun l -> List.length (List.rev l) = List.length l);;', expect: 'QCheck2.Test.t', description: 'Rev preserves length (1000 tests)' }, { code: 'QCheck2.Test.check_exn t;;', expect: 'unit', description: 'Passes all 1000 tests' }, ] }, ], }, 'qcheck-core.0.91': { name: 'QCheck', version: '0.91', opam: 'qcheck-core', description: 'Property-based testing for OCaml', universe: U['qcheck-core.0.91'], require: ['qcheck-core'], sections: [ { title: 'Generators and Tests', steps: [ { code: 'QCheck2.Gen.generate1 (QCheck2.Gen.pair QCheck2.Gen.nat QCheck2.Gen.bool);;', expect: 'int * bool', description: 'Generate a pair of int and bool' }, { code: 'let t = QCheck2.Test.make ~name:"assoc" QCheck2.Gen.(triple int int int) (fun (a, b, c) -> (a + b) + c = a + (b + c));;', expect: 'QCheck2.Test.t', description: 'Addition is associative' }, { code: 'QCheck2.Test.check_exn t;;', expect: 'unit', description: 'Property holds' }, ] }, ], }, };