// 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' },
] },
],
},
};