mirror of
https://github.com/c-cube/tiny_httpd.git
synced 2025-12-06 11:15:35 -05:00
add html sub-library
This commit is contained in:
parent
5f321774e1
commit
8e2cf23e27
4 changed files with 599 additions and 0 deletions
65
src/html/Tiny_httpd_html.ml
Normal file
65
src/html/Tiny_httpd_html.ml
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
(** HTML combinators.
|
||||||
|
|
||||||
|
This module provides combinators to produce html. It doesn't enforce
|
||||||
|
the well-formedness of the html, unlike Tyxml, but it's simple and should
|
||||||
|
be reasonably efficient.
|
||||||
|
@since 0.12
|
||||||
|
*)
|
||||||
|
|
||||||
|
module IO = Tiny_httpd_io
|
||||||
|
|
||||||
|
include Html_
|
||||||
|
(** @inline *)
|
||||||
|
|
||||||
|
(** Write an HTML element to this output.
|
||||||
|
@param top if true, add DOCTYPE at the beginning. The top element should then
|
||||||
|
be a "html" tag.
|
||||||
|
@since 0.14
|
||||||
|
*)
|
||||||
|
let to_output ?(top = false) (self : elt) (out : IO.Output.t) : unit =
|
||||||
|
let out = Out.create_of_out out in
|
||||||
|
if top then Out.add_string out "<!DOCTYPE html>\n";
|
||||||
|
self out;
|
||||||
|
Out.add_format_nl out;
|
||||||
|
Out.flush out
|
||||||
|
|
||||||
|
(** Convert a HTML element to a string.
|
||||||
|
@param top if true, add DOCTYPE at the beginning. The top element should then
|
||||||
|
be a "html" tag. *)
|
||||||
|
let to_string ?top (self : elt) : string =
|
||||||
|
let buf = Buffer.create 64 in
|
||||||
|
let out = IO.Output.of_buffer buf in
|
||||||
|
to_output ?top self out;
|
||||||
|
Buffer.contents buf
|
||||||
|
|
||||||
|
(** Convert a list of HTML elements to a string.
|
||||||
|
This is designed for fragments of HTML that are to be injected inside
|
||||||
|
a bigger context, as it's invalid to have multiple elements at the toplevel
|
||||||
|
of a HTML document. *)
|
||||||
|
let to_string_l (l : elt list) =
|
||||||
|
let buf = Buffer.create 64 in
|
||||||
|
let out = Out.create_of_buffer buf in
|
||||||
|
List.iter
|
||||||
|
(fun f ->
|
||||||
|
f out;
|
||||||
|
Out.add_format_nl out)
|
||||||
|
l;
|
||||||
|
Buffer.contents buf
|
||||||
|
|
||||||
|
let to_string_top = to_string ~top:true
|
||||||
|
|
||||||
|
(** Write a toplevel element to an output channel.
|
||||||
|
@since 0.14 *)
|
||||||
|
let to_out_channel_top = to_output ~top:true
|
||||||
|
|
||||||
|
(** Produce a streaming writer from this HTML element.
|
||||||
|
@param top if true, add a DOCTYPE. See {!to_out_channel}.
|
||||||
|
@since 0.14 *)
|
||||||
|
let to_writer ?top (self : elt) : IO.Writer.t =
|
||||||
|
let write oc = to_output ?top self oc in
|
||||||
|
IO.Writer.make ~write ()
|
||||||
|
|
||||||
|
(** Convert a HTML element to a stream. This might just convert
|
||||||
|
it to a string first, do not assume it to be more efficient. *)
|
||||||
|
let to_stream (self : elt) : Tiny_httpd_stream.t =
|
||||||
|
Tiny_httpd_stream.of_string @@ to_string self
|
||||||
15
src/html/dune
Normal file
15
src/html/dune
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
|
||||||
|
(library
|
||||||
|
(name tiny_httpd_html)
|
||||||
|
(public_name tiny_httpd.html)
|
||||||
|
(libraries seq tiny_httpd.html))
|
||||||
|
|
||||||
|
(rule
|
||||||
|
(targets html_.ml)
|
||||||
|
(deps
|
||||||
|
(:bin ./gen/gentags.exe))
|
||||||
|
(action
|
||||||
|
(with-stdout-to
|
||||||
|
%{targets}
|
||||||
|
(run %{bin}))))
|
||||||
2
src/html/gen/dune
Normal file
2
src/html/gen/dune
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
(executables
|
||||||
|
(names gentags))
|
||||||
517
src/html/gen/gentags.ml
Normal file
517
src/html/gen/gentags.ml
Normal file
|
|
@ -0,0 +1,517 @@
|
||||||
|
(* adapted from https://github.com/sindresorhus/html-tags (MIT licensed) *)
|
||||||
|
|
||||||
|
let pf = Printf.printf
|
||||||
|
let spf = Printf.sprintf
|
||||||
|
|
||||||
|
let void =
|
||||||
|
[
|
||||||
|
"area";
|
||||||
|
"base";
|
||||||
|
"br";
|
||||||
|
"col";
|
||||||
|
"embed";
|
||||||
|
"hr";
|
||||||
|
"img";
|
||||||
|
"input";
|
||||||
|
"link";
|
||||||
|
"menuitem";
|
||||||
|
"meta";
|
||||||
|
"param";
|
||||||
|
"source";
|
||||||
|
"track";
|
||||||
|
"wbr";
|
||||||
|
]
|
||||||
|
|
||||||
|
let normal =
|
||||||
|
[
|
||||||
|
"a";
|
||||||
|
"abbr";
|
||||||
|
"address";
|
||||||
|
"area";
|
||||||
|
"article";
|
||||||
|
"aside";
|
||||||
|
"audio";
|
||||||
|
"b";
|
||||||
|
"base";
|
||||||
|
"bdi";
|
||||||
|
"bdo";
|
||||||
|
"blockquote";
|
||||||
|
"body";
|
||||||
|
"br";
|
||||||
|
"button";
|
||||||
|
"canvas";
|
||||||
|
"caption";
|
||||||
|
"cite";
|
||||||
|
"code";
|
||||||
|
"col";
|
||||||
|
"colgroup";
|
||||||
|
"data";
|
||||||
|
"datalist";
|
||||||
|
"dd";
|
||||||
|
"del";
|
||||||
|
"details";
|
||||||
|
"dfn";
|
||||||
|
"dialog";
|
||||||
|
"div";
|
||||||
|
"dl";
|
||||||
|
"dt";
|
||||||
|
"em";
|
||||||
|
"embed";
|
||||||
|
"fieldset";
|
||||||
|
"figcaption";
|
||||||
|
"figure";
|
||||||
|
"footer";
|
||||||
|
"form";
|
||||||
|
"h1";
|
||||||
|
"h2";
|
||||||
|
"h3";
|
||||||
|
"h4";
|
||||||
|
"h5";
|
||||||
|
"h6";
|
||||||
|
"head";
|
||||||
|
"header";
|
||||||
|
"hgroup";
|
||||||
|
"hr";
|
||||||
|
"html";
|
||||||
|
"i";
|
||||||
|
"iframe";
|
||||||
|
"img";
|
||||||
|
"input";
|
||||||
|
"ins";
|
||||||
|
"kbd";
|
||||||
|
"label";
|
||||||
|
"legend";
|
||||||
|
"li";
|
||||||
|
"link";
|
||||||
|
"main";
|
||||||
|
"map";
|
||||||
|
"mark";
|
||||||
|
"math";
|
||||||
|
"menu";
|
||||||
|
"menuitem";
|
||||||
|
"meta";
|
||||||
|
"meter";
|
||||||
|
"nav";
|
||||||
|
"noscript";
|
||||||
|
"object";
|
||||||
|
"ol";
|
||||||
|
"optgroup";
|
||||||
|
"option";
|
||||||
|
"output";
|
||||||
|
"p";
|
||||||
|
"param";
|
||||||
|
"picture";
|
||||||
|
"pre";
|
||||||
|
"progress";
|
||||||
|
"q";
|
||||||
|
"rb";
|
||||||
|
"rp";
|
||||||
|
"rt";
|
||||||
|
"rtc";
|
||||||
|
"ruby";
|
||||||
|
"s";
|
||||||
|
"samp";
|
||||||
|
"script";
|
||||||
|
"section";
|
||||||
|
"select";
|
||||||
|
"slot";
|
||||||
|
"small";
|
||||||
|
"source";
|
||||||
|
"span";
|
||||||
|
"strong";
|
||||||
|
"style";
|
||||||
|
"sub";
|
||||||
|
"summary";
|
||||||
|
"sup";
|
||||||
|
"svg";
|
||||||
|
"table";
|
||||||
|
"tbody";
|
||||||
|
"td";
|
||||||
|
"template";
|
||||||
|
"textarea";
|
||||||
|
"tfoot";
|
||||||
|
"th";
|
||||||
|
"thead";
|
||||||
|
"time";
|
||||||
|
"title";
|
||||||
|
"tr";
|
||||||
|
"track";
|
||||||
|
"u";
|
||||||
|
"ul";
|
||||||
|
"var";
|
||||||
|
"video";
|
||||||
|
"wbr";
|
||||||
|
]
|
||||||
|
|> List.filter (fun s -> not (List.mem s void))
|
||||||
|
|
||||||
|
(* obtained via:
|
||||||
|
{[
|
||||||
|
l = Array(...document.querySelectorAll('div tbody td code a')).map(
|
||||||
|
x => x.firstChild.textContent);
|
||||||
|
JSON.stringify(l)
|
||||||
|
]}
|
||||||
|
on https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
|
||||||
|
*)
|
||||||
|
let attrs =
|
||||||
|
[
|
||||||
|
"accept";
|
||||||
|
"accept-charset";
|
||||||
|
"accesskey";
|
||||||
|
"action";
|
||||||
|
"align";
|
||||||
|
"allow";
|
||||||
|
"alt";
|
||||||
|
"async";
|
||||||
|
"autocapitalize";
|
||||||
|
"autocomplete";
|
||||||
|
"autofocus";
|
||||||
|
"autoplay";
|
||||||
|
"buffered";
|
||||||
|
"capture";
|
||||||
|
"challenge";
|
||||||
|
"charset";
|
||||||
|
"checked";
|
||||||
|
"cite";
|
||||||
|
"class";
|
||||||
|
"code";
|
||||||
|
"codebase";
|
||||||
|
"cols";
|
||||||
|
"colspan";
|
||||||
|
"content";
|
||||||
|
"contenteditable";
|
||||||
|
"contextmenu";
|
||||||
|
"controls";
|
||||||
|
"coords";
|
||||||
|
"crossorigin";
|
||||||
|
"csp";
|
||||||
|
"data";
|
||||||
|
"data-*";
|
||||||
|
"datetime";
|
||||||
|
"decoding";
|
||||||
|
"default";
|
||||||
|
"defer";
|
||||||
|
"dir";
|
||||||
|
"dirname";
|
||||||
|
"disabled";
|
||||||
|
"download";
|
||||||
|
"draggable";
|
||||||
|
"enctype";
|
||||||
|
"enterkeyhint";
|
||||||
|
"for";
|
||||||
|
"form";
|
||||||
|
"formaction";
|
||||||
|
"formenctype";
|
||||||
|
"formmethod";
|
||||||
|
"formnovalidate";
|
||||||
|
"formtarget";
|
||||||
|
"headers";
|
||||||
|
"hidden";
|
||||||
|
"high";
|
||||||
|
"href";
|
||||||
|
"hreflang";
|
||||||
|
"http-equiv";
|
||||||
|
"icon";
|
||||||
|
"id";
|
||||||
|
"importance";
|
||||||
|
"integrity";
|
||||||
|
"ismap";
|
||||||
|
"itemprop";
|
||||||
|
"keytype";
|
||||||
|
"kind";
|
||||||
|
"label";
|
||||||
|
"lang";
|
||||||
|
"language";
|
||||||
|
"list";
|
||||||
|
"loop";
|
||||||
|
"low";
|
||||||
|
"manifest";
|
||||||
|
"max";
|
||||||
|
"maxlength";
|
||||||
|
"minlength";
|
||||||
|
"media";
|
||||||
|
"method";
|
||||||
|
"min";
|
||||||
|
"multiple";
|
||||||
|
"muted";
|
||||||
|
"name";
|
||||||
|
"novalidate";
|
||||||
|
"open";
|
||||||
|
"optimum";
|
||||||
|
"pattern";
|
||||||
|
"ping";
|
||||||
|
"placeholder";
|
||||||
|
"poster";
|
||||||
|
"preload";
|
||||||
|
"radiogroup";
|
||||||
|
"readonly";
|
||||||
|
"referrerpolicy";
|
||||||
|
"rel";
|
||||||
|
"required";
|
||||||
|
"reversed";
|
||||||
|
"rows";
|
||||||
|
"rowspan";
|
||||||
|
"sandbox";
|
||||||
|
"scope";
|
||||||
|
"scoped";
|
||||||
|
"selected";
|
||||||
|
"shape";
|
||||||
|
"size";
|
||||||
|
"sizes";
|
||||||
|
"slot";
|
||||||
|
"span";
|
||||||
|
"spellcheck";
|
||||||
|
"src";
|
||||||
|
"srcdoc";
|
||||||
|
"srclang";
|
||||||
|
"srcset";
|
||||||
|
"start";
|
||||||
|
"step";
|
||||||
|
"style";
|
||||||
|
"summary";
|
||||||
|
"tabindex";
|
||||||
|
"target";
|
||||||
|
"title";
|
||||||
|
"translate";
|
||||||
|
"Text";
|
||||||
|
"type";
|
||||||
|
"usemap";
|
||||||
|
"value";
|
||||||
|
"width";
|
||||||
|
"wrap";
|
||||||
|
]
|
||||||
|
|
||||||
|
let prelude =
|
||||||
|
{|
|
||||||
|
(** Output for HTML combinators.
|
||||||
|
|
||||||
|
This output type is used to produce a string reasonably efficiently from
|
||||||
|
a tree of combinators.
|
||||||
|
|
||||||
|
{b NOTE}: this is experimental and an unstable API.
|
||||||
|
|
||||||
|
@since 0.12
|
||||||
|
@open *)
|
||||||
|
module Out : sig
|
||||||
|
type t
|
||||||
|
val create_of_buffer : Buffer.t -> t
|
||||||
|
val create_of_out: Tiny_httpd_io.Output.t -> t
|
||||||
|
val flush : t -> unit
|
||||||
|
val add_char : t -> char -> unit
|
||||||
|
val add_string : t -> string -> unit
|
||||||
|
val add_format_nl : t -> unit
|
||||||
|
val with_no_format_nl : t -> (unit -> 'a) -> 'a
|
||||||
|
end = struct
|
||||||
|
module IO = Tiny_httpd_io
|
||||||
|
type t = {
|
||||||
|
out: IO.Output.t;
|
||||||
|
mutable fmt_nl: bool; (* if true, we print [\n] around tags to format the html *)
|
||||||
|
}
|
||||||
|
let create_of_out out = {out; fmt_nl=true}
|
||||||
|
let create_of_buffer buf : t = create_of_out (IO.Output.of_buffer buf)
|
||||||
|
let[@inline] flush self : unit = IO.Output.flush self.out
|
||||||
|
let[@inline] add_char self c = IO.Output.output_char self.out c
|
||||||
|
let[@inline] add_string self s = IO.Output.output_string self.out s
|
||||||
|
let[@inline] add_format_nl self = if self.fmt_nl then add_char self '\n'
|
||||||
|
let with_no_format_nl self f =
|
||||||
|
if self.fmt_nl then (
|
||||||
|
self.fmt_nl <- false;
|
||||||
|
try let x=f() in self.fmt_nl <- true; x with e -> self.fmt_nl <- true; raise e
|
||||||
|
) else f()
|
||||||
|
end
|
||||||
|
|
||||||
|
type attribute = string * string
|
||||||
|
(** An attribute, i.e. a key/value pair *)
|
||||||
|
|
||||||
|
type elt = Out.t -> unit
|
||||||
|
(** A html element. It is represented by its output function, so we
|
||||||
|
can directly print it. *)
|
||||||
|
|
||||||
|
type void = ?if_:bool -> attribute list -> elt
|
||||||
|
(** Element without children. *)
|
||||||
|
|
||||||
|
type nary = ?if_:bool -> attribute list -> elt list -> elt
|
||||||
|
(** Element with children, represented as a list.
|
||||||
|
@param if_ if false, do not print anything (default true) *)
|
||||||
|
|
||||||
|
(** A chunk of sub-elements, possibly empty.
|
||||||
|
@inline *)
|
||||||
|
type sub_elt = [ `E of elt | `L of elt list | `S of elt Seq.t | `Nil]
|
||||||
|
|
||||||
|
type nary' = ?if_:bool -> attribute list -> sub_elt list -> elt
|
||||||
|
(** Element with children, represented as a list of {!sub_elt} to be flattened
|
||||||
|
@param if_ if false, do not print anything (default true) *)
|
||||||
|
|
||||||
|
(**/**)
|
||||||
|
module Helpers_ = struct
|
||||||
|
|
||||||
|
(** Escape string so it can be safely embedded in HTML text. *)
|
||||||
|
let _str_escape (out:Out.t) (s:string) : unit =
|
||||||
|
String.iter (function
|
||||||
|
| '<' -> Out.add_string out "<"
|
||||||
|
| '>' -> Out.add_string out ">"
|
||||||
|
| '&' -> Out.add_string out "&"
|
||||||
|
| '"' -> Out.add_string out """
|
||||||
|
| '\'' -> Out.add_string out "'"
|
||||||
|
| c -> Out.add_char out c)
|
||||||
|
s
|
||||||
|
|
||||||
|
(** Print the value part of an attribute *)
|
||||||
|
let _attr_escape (out:Out.t) (s:string) =
|
||||||
|
Out.add_char out '"';
|
||||||
|
_str_escape out s;
|
||||||
|
Out.add_char out '"'
|
||||||
|
|
||||||
|
(** Output a list of attributes. *)
|
||||||
|
let _write_attrs (out:Out.t) (l:attribute list) : unit =
|
||||||
|
List.iter
|
||||||
|
(fun (k,v) ->
|
||||||
|
Out.add_char out ' ';
|
||||||
|
Out.add_string out k;
|
||||||
|
Out.add_char out '=';
|
||||||
|
_attr_escape out v)
|
||||||
|
l
|
||||||
|
|
||||||
|
(** Write sub-elements of a {!nary'} element, returns [true] iff
|
||||||
|
at least one sub-element was written. *)
|
||||||
|
let _write_subs (out:Out.t) (l:sub_elt list) : bool =
|
||||||
|
let has_sub = ref false in
|
||||||
|
let prepend_white () = has_sub := true; Out.add_format_nl out; in
|
||||||
|
let emit1 = function
|
||||||
|
| `E x -> prepend_white(); x out
|
||||||
|
| `L l -> List.iter (fun e -> prepend_white(); e out) l
|
||||||
|
| `S l -> Seq.iter (fun e -> prepend_white(); e out) l
|
||||||
|
| `Nil -> ()
|
||||||
|
in
|
||||||
|
List.iter emit1 l;
|
||||||
|
!has_sub
|
||||||
|
|
||||||
|
(** Write a tag, with its attributes.
|
||||||
|
@param void if true, end with "/>", otherwise end with ">" *)
|
||||||
|
let _write_tag_attrs ~void (out:Out.t) (tag:string) (attrs:attribute list) : unit =
|
||||||
|
Out.add_string out "<";
|
||||||
|
Out.add_string out tag;
|
||||||
|
_write_attrs out attrs;
|
||||||
|
if void then Out.add_string out "/>" else Out.add_string out ">"
|
||||||
|
|
||||||
|
end
|
||||||
|
open Helpers_
|
||||||
|
(**/**)
|
||||||
|
|
||||||
|
(** Sub-element with a single element inside. *)
|
||||||
|
let[@inline] sub_e (elt:elt) : sub_elt = `E elt
|
||||||
|
|
||||||
|
(** Sub-element with a list of items inside. *)
|
||||||
|
let[@inline] sub_l (l:elt list) : sub_elt = `L l
|
||||||
|
|
||||||
|
(** Sub-element with a sequence ({!Seq.t}) of items inside. *)
|
||||||
|
let[@inline] sub_seq (l:elt Seq.t) : sub_elt = `S l
|
||||||
|
|
||||||
|
(** Helper to build a {!Seq.t} from an array. *)
|
||||||
|
let seq_of_array (a:_ array) : _ Seq.t =
|
||||||
|
let rec loop i () =
|
||||||
|
if i=Array.length a then Seq.Nil
|
||||||
|
else Seq.Cons (a.(i), loop (i+1))
|
||||||
|
in loop 0
|
||||||
|
|
||||||
|
(** Sub-element with nothing inside. Useful in conditionals, when one
|
||||||
|
decides not to emit a sub-element at all. *)
|
||||||
|
let sub_empty : sub_elt = `Nil
|
||||||
|
|
||||||
|
(** Emit a string value, which will be escaped. *)
|
||||||
|
let txt (txt:string) : elt = fun out -> _str_escape out txt
|
||||||
|
|
||||||
|
(** Formatted version of {!txt} *)
|
||||||
|
let txtf fmt = Format.kasprintf (fun s -> fun out -> _str_escape out s) fmt
|
||||||
|
|
||||||
|
(** Emit raw HTML. Caution, this can lead to injection vulnerabilities,
|
||||||
|
never use with text that comes from untrusted users. *)
|
||||||
|
let raw_html (s:string) : elt = fun out -> Out.add_string out s
|
||||||
|
|}
|
||||||
|
|
||||||
|
let oname = function
|
||||||
|
| "object" -> "object_"
|
||||||
|
| "class" -> "class_"
|
||||||
|
| "method" -> "method_"
|
||||||
|
| "data-*" -> "data_star"
|
||||||
|
| "for" -> "for_"
|
||||||
|
| "open" -> "open_"
|
||||||
|
| "Text" -> "text"
|
||||||
|
| "type" -> "type_"
|
||||||
|
| name ->
|
||||||
|
String.map
|
||||||
|
(function
|
||||||
|
| '-' -> '_'
|
||||||
|
| c -> c)
|
||||||
|
name
|
||||||
|
|
||||||
|
let emit_void name =
|
||||||
|
let oname = oname name in
|
||||||
|
pf
|
||||||
|
"(** tag %S, see \
|
||||||
|
{{:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/%s} mdn} *)\n"
|
||||||
|
name name;
|
||||||
|
pf "let %s : void = fun ?(if_=true) attrs out ->\n" oname;
|
||||||
|
pf " if if_ then (\n";
|
||||||
|
pf " _write_tag_attrs ~void:true out %S attrs;\n" name;
|
||||||
|
pf " )";
|
||||||
|
pf "\n\n";
|
||||||
|
()
|
||||||
|
|
||||||
|
let emit_normal name =
|
||||||
|
let oname = oname name in
|
||||||
|
|
||||||
|
pf
|
||||||
|
"(** tag %S, see \
|
||||||
|
{{:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/%s} mdn} *)\n"
|
||||||
|
name name;
|
||||||
|
pf "let %s : nary = fun ?(if_=true) attrs sub out ->\n" oname;
|
||||||
|
pf " if if_ then (\n";
|
||||||
|
(* for <pre>, newlines actually matter *)
|
||||||
|
if name = "pre" then pf " Out.with_no_format_nl out @@ fun () ->\n";
|
||||||
|
pf " _write_tag_attrs ~void:false out %S attrs;\n" name;
|
||||||
|
pf " List.iter (fun sub -> Out.add_format_nl out; sub out) sub;\n";
|
||||||
|
pf " if sub <> [] then Out.add_format_nl out;\n";
|
||||||
|
pf " Out.add_string out \"</%s>\")" name;
|
||||||
|
pf "\n\n";
|
||||||
|
|
||||||
|
(* block version *)
|
||||||
|
let oname = oname ^ "'" in
|
||||||
|
pf
|
||||||
|
"(** tag %S, see \
|
||||||
|
{{:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/%s} mdn} *)\n"
|
||||||
|
name name;
|
||||||
|
pf "let %s : nary' = fun ?(if_=true) attrs l out ->\n" oname;
|
||||||
|
pf " if if_ then (\n";
|
||||||
|
if name = "pre" then pf " Out.with_no_format_nl out @@ fun () ->\n";
|
||||||
|
pf " _write_tag_attrs ~void:false out %S attrs;\n" name;
|
||||||
|
pf " let has_sub = _write_subs out l in\n";
|
||||||
|
pf " if has_sub then Out.add_format_nl out;\n";
|
||||||
|
pf " Out.add_string out \"</%s>\")" name;
|
||||||
|
pf "\n\n";
|
||||||
|
|
||||||
|
()
|
||||||
|
|
||||||
|
let doc_attrs =
|
||||||
|
{|Attributes.
|
||||||
|
|
||||||
|
This module contains combinator for the standard attributes.
|
||||||
|
One can also just use a pair of strings. |}
|
||||||
|
|
||||||
|
let emit_attr name =
|
||||||
|
let oname = oname name in
|
||||||
|
pf " (** Attribute %S. *)\n" name;
|
||||||
|
pf " let %s : t = fun v -> %S, v\n" oname name;
|
||||||
|
pf "\n"
|
||||||
|
|
||||||
|
let () =
|
||||||
|
pf "%s\n" prelude;
|
||||||
|
List.iter emit_void void;
|
||||||
|
List.iter emit_normal normal;
|
||||||
|
pf "(** %s *)\n" doc_attrs;
|
||||||
|
pf "module A = struct\n";
|
||||||
|
pf " type t = string -> attribute\n";
|
||||||
|
pf " (** Attribute builder *)\n";
|
||||||
|
pf "\n";
|
||||||
|
List.iter emit_attr attrs;
|
||||||
|
pf "end\n";
|
||||||
|
()
|
||||||
Loading…
Add table
Reference in a new issue