feat add highlighting engine module

Highlight.highlight takes a language name and source code string, returns a list of lines where each line is a list of dream-html span nodes with CSS classes following hilite's convention. Falls back gracefully to plain escaped text when the language is unknown or highlighting fails.

Commit
bbc2bc98389c5b9de196fefd65a9c37fcbc6f852
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
lib/highlight.ml
index 00000000..d4d4df49 000000..100644
@@ -0,0 +1,53 @@
1 Added: (* -*- mode: tuareg; -*- *)
2 Added:
3 Added: (** Server-side syntax highlighting engine.
4 Added:
5 Added: Tokenizes source code using TextMate grammars (via hilite) and produces
6 Added: {!Dream_html.node} spans ready for embedding in the page. Falls back
7 Added: gracefully to plain text when no grammar is available for the requested
8 Added: language. *)
9 Added:
10 Added: open Dream_html
11 Added:
12 Added: (** A single highlighted line: a list of HTML nodes (spans with classes). *)
13 Added: type line = node list
14 Added:
15 Added: (** Highlight source code for the given language.
16 Added:
17 Added: Returns a list of lines, each line being a list of [<span>] nodes with
18 Added: appropriate CSS classes. If [lang] is [None] or the language is not
19 Added: supported, returns plain-text lines (no spans, just escaped text).
20 Added:
21 Added: The CSS classes follow hilite's convention:
22 Added: [{lang_scope}-{token_scope_segments}], e.g.
23 Added: [source.python-storage-type-function]. *)
24 Added: let highlight ~lang source : line list =
25 Added: let plain_lines () =
26 Added: String.split_on_char '\n' source
27 Added: |> List.map (fun line -> [ txt "%s\n" line ])
28 Added: in
29 Added: match lang with
30 Added: | None -> plain_lines ()
31 Added: | Some lang_name -> (
32 Added: let scope = Highlight_grammars.scope_of_lang lang_name in
33 Added: match scope with
34 Added: | None -> plain_lines ()
35 Added: | Some scope_name -> (
36 Added: let tm = Lazy.force Highlight_grammars.registry in
37 Added: match
38 Added: Hilite.src_code_to_pairs ~escape:true ~lookup_method:`Scope_name
39 Added: ~tm ~lang:scope_name source
40 Added: with
41 Added: | Error _ -> plain_lines ()
42 Added: | Ok pairs ->
43 Added: List.map
44 Added: (fun line_pairs ->
45 Added: List.map
46 Added: (fun (css_class, content) ->
47 Added: if css_class = "" then txt ~raw:true "%s" content
48 Added: else
49 Added: HTML.span
50 Added: [ HTML.class_ "%s" css_class ]
51 Added: [ txt ~raw:true "%s" content ])
52 Added: line_pairs)
53 Added: pairs))