My agentic slop goes here. Not intended for anyone else!
1(*
2 * Copyright (c) 2014, OCaml.org project
3 * Copyright (c) 2015 KC Sivaramakrishnan <sk826@cl.cam.ac.uk>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 *)
17
18(** Feed format conversion and export. *)
19
20module Atom : sig
21 (** Atom 1.0 format support. *)
22
23 val entry_of_post : Post.t -> Syndic.Atom.entry
24 (** [entry_of_post post] converts a post to an Atom entry. *)
25
26 val entries_of_posts : Post.t list -> Syndic.Atom.entry list
27 (** [entries_of_posts posts] converts posts to Atom entries. *)
28
29 val feed_of_entries :
30 title:string ->
31 ?id:string ->
32 ?authors:(string * string option) list ->
33 Syndic.Atom.entry list ->
34 Syndic.Atom.feed
35 (** [feed_of_entries ~title entries] creates an Atom feed from entries.
36
37 @param title The feed title
38 @param id Optional feed ID (default: "urn:river:merged")
39 @param authors Optional list of (name, email) tuples *)
40
41 val to_string : Syndic.Atom.feed -> string
42 (** [to_string feed] serializes an Atom feed to XML string. *)
43end
44
45module Rss2 : sig
46 (** RSS 2.0 format support. *)
47
48 val of_feed : Feed.t -> Syndic.Rss2.channel option
49 (** [of_feed feed] extracts RSS2 channel if the feed is RSS2 format.
50
51 Returns None if the feed is not RSS2. *)
52end
53
54module Jsonfeed : sig
55 (** JSON Feed 1.1 format support. *)
56
57 val item_of_post : Post.t -> Jsonfeed.Item.t
58 (** [item_of_post post] converts a post to a JSONFeed item. *)
59
60 val items_of_posts : Post.t list -> Jsonfeed.Item.t list
61 (** [items_of_posts posts] converts posts to JSONFeed items. *)
62
63 val feed_of_items :
64 title:string ->
65 ?home_page_url:string ->
66 ?feed_url:string ->
67 ?description:string ->
68 ?icon:string ->
69 ?favicon:string ->
70 Jsonfeed.Item.t list ->
71 Jsonfeed.t
72 (** [feed_of_items ~title items] creates a JSONFeed from items.
73
74 @param title The feed title (required)
75 @param home_page_url The URL of the website the feed represents
76 @param feed_url The URL of the feed itself
77 @param description A description of the feed
78 @param icon URL of an icon for the feed (512x512 recommended)
79 @param favicon URL of a favicon for the feed (64x64 recommended) *)
80
81 val feed_of_posts :
82 title:string ->
83 ?home_page_url:string ->
84 ?feed_url:string ->
85 ?description:string ->
86 ?icon:string ->
87 ?favicon:string ->
88 Post.t list ->
89 Jsonfeed.t
90 (** [feed_of_posts ~title posts] creates a JSONFeed from posts.
91
92 Convenience function that combines [items_of_posts] and [feed_of_items]. *)
93
94 val to_string : ?minify:bool -> Jsonfeed.t -> (string, string) result
95 (** [to_string ?minify feed] serializes a JSONFeed to JSON string.
96
97 @param minify If true, output compact JSON; if false, pretty-print (default: false) *)
98
99 val of_feed : Feed.t -> Jsonfeed.t option
100 (** [of_feed feed] extracts JSONFeed if the feed is JSONFeed format.
101
102 Returns None if the feed is not JSONFeed. *)
103end
104
105module Html : sig
106 (** HTML static site generation. *)
107
108 val format_date : Ptime.t -> string
109 (** [format_date date] formats a date in human-readable format (e.g., "November 23, 2025"). *)
110
111 val html_escape : string -> string
112 (** [html_escape s] escapes HTML special characters in string. *)
113
114 val full_content_from_html : string -> string
115 (** [full_content_from_html html_content] converts HTML content to clean markdown-derived HTML.
116
117 @param html_content The HTML content to convert *)
118
119 val post_excerpt_from_html : string -> max_length:int -> string
120 (** [post_excerpt_from_html html_content ~max_length] generates an excerpt from HTML content.
121
122 Converts HTML to markdown, truncates to max_length, and converts back to simple HTML.
123
124 @param html_content The HTML content to excerpt
125 @param max_length Maximum length of the excerpt in characters *)
126
127 val render_post_html : post:Post.t -> author_username:string -> string
128 (** [render_post_html ~post ~author_username] renders a single post as HTML.
129
130 @param post The post to render
131 @param author_username The username of the author (for linking) *)
132
133 val render_posts_page :
134 title:string ->
135 posts:string list ->
136 current_page:int ->
137 total_pages:int ->
138 base_path:string ->
139 nav_current:string ->
140 string
141 (** [render_posts_page ~title ~posts ~current_page ~total_pages ~base_path ~nav_current]
142 renders a complete HTML page with posts and pagination.
143
144 @param title Page title
145 @param posts List of pre-rendered post HTML strings
146 @param current_page Current page number (1-indexed)
147 @param total_pages Total number of pages
148 @param base_path Base path for pagination links (e.g., "" for root, "authors/" for author pages)
149 @param nav_current Which nav item is current ("posts", "authors", "categories", "links") *)
150
151 val page_template : title:string -> nav_current:string -> string -> string
152 (** [page_template ~title ~nav_current content] wraps content in the HTML page template.
153
154 @param title Page title
155 @param nav_current Which nav item is current
156 @param content The main content HTML *)
157end