├── .github └── workflows │ ├── gh-pages.yml │ └── main.yml ├── .gitignore ├── .header ├── .ocamlformat ├── .screen1.png ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── docs ├── foo.html ├── printbox-ext-plot-example_Map.png ├── printbox-ext-plot-example_half_moons.png ├── printbox-ext-plot-example_linear.png └── printbox-ext-plot-example_nested.png ├── dune ├── dune-project ├── examples ├── dune └── lambda.ml ├── printbox-ext-plot.opam ├── printbox-html.opam ├── printbox-md.opam ├── printbox-text.opam ├── printbox.opam ├── src ├── PrintBox.ml ├── PrintBox.mli ├── dune ├── printbox-ext-plot │ ├── PrintBox_ext_plot.ml │ ├── PrintBox_ext_plot.mli │ ├── README.md │ └── dune ├── printbox-html │ ├── PrintBox_html.ml │ ├── PrintBox_html.mli │ └── dune ├── printbox-md │ ├── PrintBox_md.ml │ ├── PrintBox_md.mli │ ├── README.md │ ├── dune │ └── readme.ml └── printbox-text │ ├── PrintBox_text.ml │ ├── PrintBox_text.mli │ └── dune └── test ├── dune ├── extend_html_specific.expected ├── extend_html_specific.ml ├── extend_md.expected ├── extend_md.ml ├── plotting.expected ├── plotting.ml ├── plotting_half_moons.expected ├── plotting_half_moons.ml ├── plotting_linear.expected ├── plotting_linear.ml ├── plotting_nested.expected ├── plotting_nested.ml ├── reg_45.expected ├── reg_45.ml ├── test1.expected ├── test1.ml ├── test_ann_0_3.expected ├── test_ann_0_3.ml ├── test_blending.expected ├── test_blending.ml ├── test_html.expected ├── test_html.ml ├── test_md.expected ├── test_md.expected.md ├── test_md.ml ├── test_text_uri.expected └── test_text_uri.ml /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master # Set a branch name to trigger deployment 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@main 13 | 14 | - name: Cache opam 15 | id: cache-opam 16 | uses: actions/cache@v2 17 | with: 18 | path: ~/.opam 19 | key: opam-ubuntu-latest-4.12.0 20 | 21 | - uses: avsm/setup-ocaml@v3 22 | with: 23 | ocaml-version: '4.12.0' 24 | 25 | - name: Pin 26 | run: opam pin -n . 27 | 28 | - name: Depext 29 | run: opam depext -yt printbox printbox-html printbox-md printbox-text 30 | 31 | - name: Deps 32 | run: opam install -d . --deps-only 33 | 34 | - name: Build 35 | run: opam exec -- dune build @doc 36 | 37 | - name: Deploy 38 | uses: peaceiris/actions-gh-pages@v3 39 | with: 40 | github_token: ${{ secrets.GITHUB_TOKEN }} 41 | publish_dir: ./_build/default/_doc/_html/ 42 | destination_dir: dev 43 | enable_jekyll: true 44 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | run: 9 | name: Build 10 | strategy: 11 | matrix: 12 | os: 13 | - ubuntu-latest 14 | #- windows-latest 15 | #- macos-latest 16 | ocaml-compiler: 17 | - 4.08.x 18 | - 4.12.x 19 | - 5.1.x 20 | runs-on: ${{ matrix.os }} 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: ocaml/setup-ocaml@v3 24 | with: 25 | ocaml-compiler: ${{ matrix.ocaml-compiler }} 26 | dune-cache: true 27 | allow-prerelease-opam: true 28 | - run: opam pin -n . 29 | - run: opam depext -yt printbox printbox-html printbox-md printbox-text 30 | - run: opam install -t . --deps-only 31 | - run: opam exec -- dune build @all 32 | - run: opam exec -- dune runtest 33 | if: ${{ matrix.os == 'ubuntu-latest'}} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | *.native 3 | *.docdir 4 | *.install 5 | setup.log 6 | setup.data 7 | .merlin 8 | *.install 9 | -------------------------------------------------------------------------------- /.header: -------------------------------------------------------------------------------- 1 | (* This file is free software. See file "license" for more details. *) 2 | -------------------------------------------------------------------------------- /.ocamlformat: -------------------------------------------------------------------------------- 1 | version = 0.26.2 2 | profile=conventional 3 | margin=80 4 | if-then-else=k-r 5 | parens-ite=true 6 | parens-tuple=multi-line-only 7 | sequence-style=terminator 8 | type-decl=sparse 9 | break-cases=toplevel 10 | cases-exp-indent=2 11 | field-space=tight-decl 12 | leading-nested-match-parens=true 13 | module-item-spacing=compact 14 | quiet=true 15 | ocaml-version=4.08.0 16 | -------------------------------------------------------------------------------- /.screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c-cube/printbox/e71562baaf9469b50a8f80a9cf1ebc378736e563/.screen1.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 0.12 3 | 4 | - Remove fallback to and dependency on `printbox-html` from `printbox-md` (@lukstafi) 5 | - introduce notion of extensions (@lukstafi) 6 | - add `printbox-ext-plot` extension for text and HTML plots (@lukstafi) 7 | - feat: add `?stretch` param to `frame` 8 | 9 | - fix #45, problem with nested frames 10 | 11 | ## 0.11 12 | 13 | - Anchors (with self-links if inner is non-empty) 14 | - Support `hlist` and `vlist` inside summaries Implemented as poor-man's support of arbitrary grids. 15 | 16 | ## 0.10 17 | 18 | - Fixes #10: ANSI encoded hyperlinks for printbox-text 19 | - Fixes #39: more compact markdown output Remove double empty lines after ``. 20 | - More compact html output: no empty class annotations 21 | - Provide context for the `line` exception 22 | 23 | ## 0.9 24 | 25 | - fix `PrintBox.text` will correctly handle newlines 26 | - new `printbox-md` backend, generating markdown (by @lukstafi) 27 | 28 | ## 0.8 29 | 30 | - require dune 3.0 31 | - Fixes #28: no misleading uptick for empty tree nodes 32 | - HTML: Allow frames in the summary / tree header 33 | - Output frames as div borders in HTML 34 | 35 | ## 0.7 36 | 37 | - move to 4.08 as lower bound 38 | - `preformatted` text style instead of global setting 39 | - PrintBox_html: 40 | * Optionally wrap text with the `
` HTML element
41 | * Output text consistently as ``, not ``
42 | * Use `` for collapsible trees
43 |
44 | - fix: Tree connectors touching frames (#26)
45 |
46 | ## 0.6.1
47 |
48 | - compat with dune 3
49 |
50 | ## 0.6
51 |
52 | - move text rendering into a new printbox-text library
53 | - Changing visuals for hlines and vlines connections, and tree structure
54 | using unicode characters for box borders
55 |
56 | ## 0.5
57 |
58 | - reenable mdx for tests
59 | - custom classes/attributes for html translation in `PrintBox_html`
60 | - add `link` case
61 | - examples: add lambda.ml
62 |
63 | ## 0.4
64 |
65 | - remove `` in rendering text to html
66 | - add `grid_map_l` and `v_record`
67 | - add another test
68 |
69 | ## 0.3
70 |
71 | - improve code readability in text rendering
72 | - add `align` and `center`
73 | - add basic styling for text (ansi codes/html styles)
74 | - add `printbox_unicode` for setting up proper unicode printing
75 | - add `grid_l`, `grid_text_l`, and `record` helpers
76 |
77 | - use a more accurate length estimate for unicode, add test
78 | - remove mdx as a test dep
79 | - fix rendering bugs related to align right, and padding
80 |
81 | ## 0.2
82 |
83 | - make the box type opaque, with a view function
84 | - require OCaml 4.03
85 |
86 | - add `PrintBox_text.pp`
87 | - expose a few new functions to build boxes
88 | - change `Text` type, work on string slices when rendering
89 |
90 | - automatic testing using dune and mdx
91 | - migrate to dune and opam 2
92 |
93 | ## 0.1
94 |
95 | initial release
96 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, Simon Cruanes, Guillaume Bury
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer. Redistributions in binary
9 | form must reproduce the above copyright notice, this list of conditions and
10 | the following disclaimer in the documentation and/or other materials
11 | provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | dev: build test
3 |
4 | build:
5 | @dune build @install
6 |
7 | test:
8 | @dune runtest --no-buffer --force
9 | test-autopromote:
10 | @dune runtest --no-buffer --force --auto-promote
11 |
12 | install: build
13 | @dune install
14 |
15 | doc:
16 | @dune build @doc
17 |
18 | clean:
19 | @dune clean
20 |
21 | VERSION=$(shell awk '/^version:/ {print $$2}' printbox.opam)
22 | update_next_tag:
23 | @echo "update version to $(VERSION)..."
24 | sed -i "s/NEXT_RELEASE/$(VERSION)/g" $(wildcard src/**/*.ml) $(wildcard src/**/*.mli)
25 | sed -i "s/NEXT_RELEASE/$(VERSION)/g" $(wildcard src/*.ml) $(wildcard src/*.mli)
26 |
27 | WATCH?="@all"
28 | watch:
29 | @dune build $(WATCH) -w
30 |
31 | .PHONY: all build test clean doc watch
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PrintBox [](https://github.com/c-cube/printbox/actions/workflows/main.yml)
2 |
3 | Allows to print nested boxes, lists, arrays, tables in several formats,
4 | including:
5 |
6 | - text (assuming monospace font)
7 | - HTML (using [tyxml](https://github.com/ocsigen/tyxml/) )
8 | - LaTeX (*not implemented yet*)
9 |
10 |
11 | ## Documentation
12 |
13 | See https://c-cube.github.io/printbox/
14 |
15 | See the [test/](test/) and [examples/](examples/) directories for illustrations of potential usage.
16 |
17 | ## License
18 |
19 | BSD-2-clauses
20 |
21 | ## Build
22 |
23 | Ideally, use [opam](http://opam.ocaml.org/) with OCaml >= 4.08:
24 |
25 | ```sh non-deterministic=command
26 | $ opam install printbox printbox-text
27 | ```
28 |
29 | Manually:
30 |
31 | ```sh non-deterministic=command
32 | $ make install
33 | ```
34 |
35 | ## A few examples
36 |
37 | #### importing the module
38 |
39 | ```ocaml
40 | # #require "printbox";;
41 | # #require "printbox-text";;
42 |
43 | # module B = PrintBox;;
44 | module B = PrintBox
45 | ```
46 |
47 | #### simple box
48 |
49 | ```ocaml
50 | # let box = B.(hlist [ text "hello"; text "world"; ]);;
51 | val box : B.t =
52 |
53 | # PrintBox_text.output stdout box;;
54 | hello│world
55 | - : unit = ()
56 | ```
57 |
58 | #### less simple boxes
59 |
60 | ```ocaml
61 | # let box =
62 | B.(hlist
63 | [ text "I love\nto\npress\nenter";
64 | grid_text [| [|"a"; "bbb"|];
65 | [|"c"; "hello world"|] |]
66 | ])
67 | |> B.frame;;
68 | val box : B.t =
69 |
70 | # PrintBox_text.output stdout box;;
71 | ┌──────┬─┬───────────┐
72 | │I love│a│bbb │
73 | │to ├─┼───────────┤
74 | │press │c│hello world│
75 | │enter │ │ │
76 | └──────┴─┴───────────┘
77 | - : unit = ()
78 | ```
79 |
80 | #### printing a table
81 |
82 | ```ocaml
83 | # let square n =
84 | (* function to make a square *)
85 | Array.init n
86 | (fun i -> Array.init n (fun j -> B.sprintf "(%d,%d)" i j))
87 | |> B.grid ;;
88 | val square : int -> B.t =
89 |
90 | # let sq = square 5;;
91 | val sq : B.t =
92 | # PrintBox_text.output stdout sq;;
93 | (0,0)│(0,1)│(0,2)│(0,3)│(0,4)
94 | ─────┼─────┼─────┼─────┼─────
95 | (1,0)│(1,1)│(1,2)│(1,3)│(1,4)
96 | ─────┼─────┼─────┼─────┼─────
97 | (2,0)│(2,1)│(2,2)│(2,3)│(2,4)
98 | ─────┼─────┼─────┼─────┼─────
99 | (3,0)│(3,1)│(3,2)│(3,3)│(3,4)
100 | ─────┼─────┼─────┼─────┼─────
101 | (4,0)│(4,1)│(4,2)│(4,3)│(4,4)
102 | - : unit = ()
103 | ```
104 |
105 | #### frame
106 |
107 | Why not put a frame around this? That's easy.
108 |
109 | ```ocaml
110 | # let sq2 = square 3 |> B.frame ;;
111 | val sq2 : B.t =
112 |
113 | # PrintBox_text.output stdout sq2;;
114 | ┌─────┬─────┬─────┐
115 | │(0,0)│(0,1)│(0,2)│
116 | ├─────┼─────┼─────┤
117 | │(1,0)│(1,1)│(1,2)│
118 | ├─────┼─────┼─────┤
119 | │(2,0)│(2,1)│(2,2)│
120 | └─────┴─────┴─────┘
121 | - : unit = ()
122 | ```
123 |
124 | #### tree
125 |
126 | We can also create trees and display them using indentation:
127 |
128 | ```ocaml
129 | # let tree =
130 | B.tree (B.text "root")
131 | [ B.tree (B.text "a") [B.text "a1\na1"; B.text "a2\na2\na2"];
132 | B.tree (B.text "b") [B.text "b1\nb1"; B.text "b2"; B.text "b3"];
133 | ];;
134 | val tree : B.t =
135 |
136 | # PrintBox_text.output stdout tree;;
137 | root
138 | ├─a
139 | │ ├─a1
140 | │ │ a1
141 | │ └─a2
142 | │ a2
143 | │ a2
144 | └─b
145 | ├─b1
146 | │ b1
147 | ├─b2
148 | └─b3
149 | - : unit = ()
150 | ```
151 |
152 | #### Installing the pretty-printer in the toplevel
153 |
154 | `PrintBox_text` contains a `Format`-compatible pretty-printer that
155 | can be used as a default printer for boxes.
156 |
157 | ```ocaml
158 | # #install_printer PrintBox_text.pp;;
159 | # PrintBox.(frame @@ frame @@ init_grid ~line:3 ~col:2 (fun ~line:i ~col:j -> sprintf "%d.%d" i j));;
160 | - : B.t =
161 | ┌─────────┐
162 | │┌───┬───┐│
163 | ││0.0│0.1││
164 | │├───┼───┤│
165 | ││1.0│1.1││
166 | │├───┼───┤│
167 | ││2.0│2.1││
168 | │└───┴───┘│
169 | └─────────┘
170 | # #remove_printer PrintBox_text.pp;;
171 | ```
172 |
173 | Note that this pretty-printer plays nicely with `Format` boxes:
174 |
175 | ```ocaml
176 | # let b = PrintBox.(frame @@ hlist [text "a\nb"; text "c"]);;
177 | val b : B.t =
178 | # Format.printf "some text %a around@." PrintBox_text.pp b;;
179 | some text ┌─┬─┐
180 | │a│c│
181 | │b│ │
182 | └─┴─┘ around
183 | - : unit = ()
184 | ```
185 |
186 | Also works with basic styling on text now:
187 |
188 | ```ocaml
189 | # let b2 = PrintBox.(
190 | let style = Style.(fg_color Red) in
191 | frame @@ hlist [text_with_style style "a\nb"; text "c"]);;
192 | val b2 : B.t =
193 | # Format.printf "some text %a around@." (PrintBox_text.pp_with ~style:true) b2;;
194 | some text ┌─┬─┐
195 | │a│c│
196 | │b│ │
197 | └─┴─┘ around
198 | - : unit = ()
199 | ```
200 |
201 | ```ocaml non-deterministic=command
202 | # let b3 = PrintBox.(
203 | let style = Style.(fg_color Red) in
204 | frame @@ grid_l [
205 | [text_with_style style "a\nb";
206 | line_with_style Style.(set_bold true @@ bg_color Green) "OH!"];
207 | [text "c"; text "ballot"];
208 | ])
209 | val b3 : PrintBox.t =
210 | utop [1]: print_endline @@ PrintBox_text.to_string b3;;
211 | ```
212 |
213 | gives .
214 |
215 | #### Handling unicode
216 |
217 | Unicode (utf8) text is handled.
218 |
219 | ```ocaml
220 | # let b =
221 | PrintBox.(frame @@
222 | hlist [
223 | vlist[text "oï ωεird nums:\nπ/2\nτ/4";
224 | tree (text "0")[text "1"; tree (text "ω") [text "ω²"]]];
225 | frame @@ vlist [text "sum=Σ_i a·xᵢ²\n—————\n1+1"; text "Ōₒ\nÀ"]]);;
226 | val b : B.t =
227 |
228 | # print_endline @@ PrintBox_text.to_string b;;
229 | ┌──────────────┬───────────────┐
230 | │oï ωεird nums:│┌─────────────┐│
231 | │π/2 ││sum=Σ_i a·xᵢ²││
232 | │τ/4 ││————— ││
233 | ├──────────────┤│1+1 ││
234 | │0 │├─────────────┤│
235 | │├─1 ││Ōₒ ││
236 | │└─ω ││À ││
237 | │ └─ω² │└─────────────┘│
238 | └──────────────┴───────────────┘
239 | - : unit = ()
240 | ```
241 |
242 | #### HTML output (with `tyxml`)
243 |
244 | Assuming you have loaded `printbox-html` somehow:
245 |
246 | ```ocaml non-deterministic=command
247 | let out = open_out "/tmp/foo.html";;
248 | output_string out (PrintBox_html.to_string_doc (square 5));;
249 | ```
250 |
251 | which prints some HTML in the file [foo.html](docs/foo.html).
252 | Note that trees are printed in HTML using nested lists, and
253 | that `PrintBox_html.to_string_doc` will insert some javascript to
254 | make sub-lists fold/unfold on click (this is useful to display very large
255 | trees compactly and exploring them incrementally). But there is also
256 | an alternative solution where trees are printed in HTML using the
257 | `` element. To activate it, use the `tree_summary` config:
258 |
259 | ```ocaml
260 | # #require "printbox-html";;
261 | # print_endline PrintBox_html.(to_string
262 | ~config:Config.(tree_summary true default)
263 | B.(tree (text "0")[text "1"; tree (text "ω") [text "ω²"]]));;
264 | 0
- 1
ω
- ω²
265 |
266 | - : unit = ()
267 | ```
268 |
--------------------------------------------------------------------------------
/docs/foo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | (0,0)
(0,1)
(0,2)
(0,3)
(0,4)
(1,0)
(1,1)
(1,2)
(1,3)
(1,4)
(2,0)
(2,1)
(2,2)
(2,3)
(2,4)
(3,0)
(3,1)
(3,2)
(3,3)
(3,4)
(4,0)
(4,1)
(4,2)
(4,3)
(4,4)
4 |
--------------------------------------------------------------------------------
/docs/printbox-ext-plot-example_Map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/c-cube/printbox/e71562baaf9469b50a8f80a9cf1ebc378736e563/docs/printbox-ext-plot-example_Map.png
--------------------------------------------------------------------------------
/docs/printbox-ext-plot-example_half_moons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/c-cube/printbox/e71562baaf9469b50a8f80a9cf1ebc378736e563/docs/printbox-ext-plot-example_half_moons.png
--------------------------------------------------------------------------------
/docs/printbox-ext-plot-example_linear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/c-cube/printbox/e71562baaf9469b50a8f80a9cf1ebc378736e563/docs/printbox-ext-plot-example_linear.png
--------------------------------------------------------------------------------
/docs/printbox-ext-plot-example_nested.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/c-cube/printbox/e71562baaf9469b50a8f80a9cf1ebc378736e563/docs/printbox-ext-plot-example_nested.png
--------------------------------------------------------------------------------
/dune:
--------------------------------------------------------------------------------
1 | (mdx
2 | (package printbox-html)
3 | (libraries printbox printbox-text printbox-html)
4 | (files README.md))
5 |
--------------------------------------------------------------------------------
/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.0)
2 | (name printbox)
3 | (using mdx 0.2)
4 | (generate_opam_files true)
5 | (version 0.12)
6 | (license "BSD-2-Clause")
7 |
8 | (authors "Simon Cruanes" "Guillaume Bury" "lukstafi")
9 | (maintainers c-cube lukstafi)
10 | (source (github c-cube/printbox))
11 |
12 | (package
13 | (name printbox)
14 | (synopsis "Allows to print nested boxes, lists, arrays, tables in several formats")
15 | (depends (ocaml (>= 4.08)) (odoc :with-doc))
16 | (tags ("print" "box" "table" "tree")))
17 |
18 | (package
19 | (name printbox-text)
20 | (synopsis "Text renderer for printbox, using unicode edges")
21 | (depends (printbox (= :version))
22 | (uutf (>= 1.0))
23 | (uucp (>= 2.0))
24 | (odoc :with-test)))
25 |
26 | (package
27 | (name printbox-html)
28 | (synopsis "Printbox unicode handling")
29 | (description "
30 | Adds html output handling to the printbox package.
31 | Printbox allows to print nested boxes, lists, arrays, tables in several formats")
32 | (depends (printbox (= :version))
33 | (printbox-text (and :with-test (= :version)))
34 | (odoc :with-test)
35 | (tyxml (>= 4.3))
36 | (mdx (and (>= 1.4) :with-test))))
37 |
38 | (package
39 | (name printbox-md)
40 | (synopsis "Printbox Markdown rendering")
41 | (description "
42 | Adds Markdown output handling to the printbox package, with fallback to text and simplified HTML.
43 | Printbox allows to print nested boxes, lists, arrays, tables in several formats")
44 | (depends (printbox (= :version))
45 | (printbox-text (and (= :version)))
46 | (odoc :with-test)))
47 |
48 | (package
49 | (name printbox-ext-plot)
50 | (synopsis "Printbox extension for plotting")
51 | (description "
52 | Extends Printbox with the ability to print scatter plots, line plots, decision boundaries.
53 | Printbox allows to print nested boxes, lists, arrays, tables in several formats")
54 | (depends (printbox (= :version))
55 | (printbox-text (and (= :version)))
56 | (printbox-html (and (= :version)))
57 | (printbox-md (and (= :version)))
58 | (tyxml (>= 4.3))
59 | (odoc :with-test)))
60 |
--------------------------------------------------------------------------------
/examples/dune:
--------------------------------------------------------------------------------
1 | (executable
2 | (name lambda)
3 | (libraries printbox printbox-text)
4 | (modules lambda))
5 |
--------------------------------------------------------------------------------
/examples/lambda.ml:
--------------------------------------------------------------------------------
1 | (** Example of printing trees: lambda-term evaluation *)
2 |
3 | type term =
4 | | Lambda of string * term
5 | | App of term * term
6 | | Var of string
7 |
8 | let _gensym =
9 | let r = ref 0 in
10 | fun () ->
11 | let s = Printf.sprintf "x%d" !r in
12 | incr r;
13 | s
14 |
15 | module SSet = Set.Make (String)
16 | module SMap = Map.Make (String)
17 |
18 | let rec fvars t =
19 | match t with
20 | | Var s -> SSet.singleton s
21 | | Lambda (v, t') ->
22 | let set' = fvars t' in
23 | SSet.remove v set'
24 | | App (t1, t2) -> SSet.union (fvars t1) (fvars t2)
25 |
26 | (* replace [var] with the term [by] *)
27 | let rec replace t ~var ~by =
28 | match t with
29 | | Var s ->
30 | if s = var then
31 | by
32 | else
33 | t
34 | | App (t1, t2) -> App (replace t1 ~var ~by, replace t2 ~var ~by)
35 | | Lambda (v, _t') when v = var -> t (* no risk *)
36 | | Lambda (v, t') -> Lambda (v, replace t' ~var ~by)
37 |
38 | (* rename [t] so that [var] doesn't occur in it *)
39 | let rename ~var t =
40 | if SSet.mem var (fvars t) then
41 | replace t ~var ~by:(Var (_gensym ()))
42 | else
43 | t
44 |
45 | let ( >>= ) o f =
46 | match o with
47 | | None -> None
48 | | Some x -> f x
49 |
50 | let rec one_step t =
51 | match t with
52 | | App (Lambda (var, t1), t2) ->
53 | let t2' = rename ~var t2 in
54 | Some (replace t1 ~var ~by:t2')
55 | | App (t1, t2) ->
56 | (match one_step t1 with
57 | | None -> one_step t2 >>= fun t2' -> Some (App (t1, t2'))
58 | | Some t1' -> Some (App (t1', t2)))
59 | | Var _ -> None
60 | | Lambda (v, t') -> one_step t' >>= fun t'' -> Some (Lambda (v, t''))
61 |
62 | let normal_form t =
63 | let rec aux acc t =
64 | match one_step t with
65 | | None -> List.rev (t :: acc)
66 | | Some t' -> aux (t :: acc) t'
67 | in
68 | aux [] t
69 |
70 | let _split_fuel f =
71 | assert (f >= 2);
72 | if f = 2 then
73 | 1, 1
74 | else (
75 | let x = 1 + Random.int (f - 1) in
76 | f - x, x
77 | )
78 |
79 | let _random_var () =
80 | let v = [| "x"; "y"; "z"; "u"; "w" |] in
81 | v.(Random.int (Array.length v))
82 |
83 | let _choose_var ~vars =
84 | match vars with
85 | | [] -> Var (_random_var ())
86 | | _ :: _ ->
87 | let i = Random.int (List.length vars) in
88 | List.nth vars i
89 |
90 | let rec _random_term fuel vars =
91 | match Random.int 2 with
92 | | _ when fuel = 1 -> _choose_var ~vars
93 | | 0 ->
94 | let f1, f2 = _split_fuel fuel in
95 | App (_random_term f1 vars, _random_term f2 vars)
96 | | 1 ->
97 | let v = _random_var () in
98 | Lambda (v, _random_term (fuel - 1) (Var v :: vars))
99 | | _ -> assert false
100 |
101 | let print_term t =
102 | PrintBox.mk_tree
103 | (function
104 | | Var v -> PrintBox.line v, []
105 | | App (t1, t2) -> PrintBox.line "app", [ t1; t2 ]
106 | | Lambda (v, t') -> PrintBox.line "lambda", [ Var v; t' ])
107 | t
108 |
109 | let print_reduction t =
110 | let l = normal_form t in
111 | let l = List.map (fun t -> PrintBox.pad (print_term t)) l in
112 | PrintBox.vlist ~bars:false l
113 |
114 | let () =
115 | Random.self_init ();
116 | let t = _random_term (5 + Random.int 20) [] in
117 | PrintBox_text.output ~indent:2 stdout (print_reduction t)
118 |
--------------------------------------------------------------------------------
/printbox-ext-plot.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | version: "0.12"
4 | synopsis: "Printbox extension for plotting"
5 | description: """
6 |
7 | Extends Printbox with the ability to print scatter plots, line plots, decision boundaries.
8 | Printbox allows to print nested boxes, lists, arrays, tables in several formats"""
9 | maintainer: ["c-cube" "lukstafi"]
10 | authors: ["Simon Cruanes" "Guillaume Bury" "lukstafi"]
11 | license: "BSD-2-Clause"
12 | homepage: "https://github.com/c-cube/printbox"
13 | bug-reports: "https://github.com/c-cube/printbox/issues"
14 | depends: [
15 | "dune" {>= "3.0"}
16 | "printbox" {= version}
17 | "printbox-text" {= version}
18 | "printbox-html" {= version}
19 | "printbox-md" {= version}
20 | "tyxml" {>= "4.3"}
21 | "odoc" {with-test}
22 | "odoc" {with-doc}
23 | ]
24 | build: [
25 | ["dune" "subst"] {dev}
26 | [
27 | "dune"
28 | "build"
29 | "-p"
30 | name
31 | "-j"
32 | jobs
33 | "@install"
34 | "@runtest" {with-test}
35 | "@doc" {with-doc}
36 | ]
37 | ]
38 | dev-repo: "git+https://github.com/c-cube/printbox.git"
39 |
--------------------------------------------------------------------------------
/printbox-html.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | version: "0.12"
4 | synopsis: "Printbox unicode handling"
5 | description: """
6 |
7 | Adds html output handling to the printbox package.
8 | Printbox allows to print nested boxes, lists, arrays, tables in several formats"""
9 | maintainer: ["c-cube" "lukstafi"]
10 | authors: ["Simon Cruanes" "Guillaume Bury" "lukstafi"]
11 | license: "BSD-2-Clause"
12 | homepage: "https://github.com/c-cube/printbox"
13 | bug-reports: "https://github.com/c-cube/printbox/issues"
14 | depends: [
15 | "dune" {>= "3.0"}
16 | "printbox" {= version}
17 | "printbox-text" {with-test & = version}
18 | "odoc" {with-test}
19 | "tyxml" {>= "4.3"}
20 | "mdx" {>= "1.4" & with-test}
21 | "odoc" {with-doc}
22 | ]
23 | build: [
24 | ["dune" "subst"] {dev}
25 | [
26 | "dune"
27 | "build"
28 | "-p"
29 | name
30 | "-j"
31 | jobs
32 | "@install"
33 | "@runtest" {with-test}
34 | "@doc" {with-doc}
35 | ]
36 | ]
37 | dev-repo: "git+https://github.com/c-cube/printbox.git"
38 |
--------------------------------------------------------------------------------
/printbox-md.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | version: "0.12"
4 | synopsis: "Printbox Markdown rendering"
5 | description: """
6 |
7 | Adds Markdown output handling to the printbox package, with fallback to text and simplified HTML.
8 | Printbox allows to print nested boxes, lists, arrays, tables in several formats"""
9 | maintainer: ["c-cube" "lukstafi"]
10 | authors: ["Simon Cruanes" "Guillaume Bury" "lukstafi"]
11 | license: "BSD-2-Clause"
12 | homepage: "https://github.com/c-cube/printbox"
13 | bug-reports: "https://github.com/c-cube/printbox/issues"
14 | depends: [
15 | "dune" {>= "3.0"}
16 | "printbox" {= version}
17 | "printbox-text" {= version}
18 | "odoc" {with-test}
19 | "odoc" {with-doc}
20 | ]
21 | build: [
22 | ["dune" "subst"] {dev}
23 | [
24 | "dune"
25 | "build"
26 | "-p"
27 | name
28 | "-j"
29 | jobs
30 | "@install"
31 | "@runtest" {with-test}
32 | "@doc" {with-doc}
33 | ]
34 | ]
35 | dev-repo: "git+https://github.com/c-cube/printbox.git"
36 |
--------------------------------------------------------------------------------
/printbox-text.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | version: "0.12"
4 | synopsis: "Text renderer for printbox, using unicode edges"
5 | maintainer: ["c-cube" "lukstafi"]
6 | authors: ["Simon Cruanes" "Guillaume Bury" "lukstafi"]
7 | license: "BSD-2-Clause"
8 | homepage: "https://github.com/c-cube/printbox"
9 | bug-reports: "https://github.com/c-cube/printbox/issues"
10 | depends: [
11 | "dune" {>= "3.0"}
12 | "printbox" {= version}
13 | "uutf" {>= "1.0"}
14 | "uucp" {>= "2.0"}
15 | "odoc" {with-test}
16 | "odoc" {with-doc}
17 | ]
18 | build: [
19 | ["dune" "subst"] {dev}
20 | [
21 | "dune"
22 | "build"
23 | "-p"
24 | name
25 | "-j"
26 | jobs
27 | "@install"
28 | "@runtest" {with-test}
29 | "@doc" {with-doc}
30 | ]
31 | ]
32 | dev-repo: "git+https://github.com/c-cube/printbox.git"
33 |
--------------------------------------------------------------------------------
/printbox.opam:
--------------------------------------------------------------------------------
1 | # This file is generated by dune, edit dune-project instead
2 | opam-version: "2.0"
3 | version: "0.12"
4 | synopsis:
5 | "Allows to print nested boxes, lists, arrays, tables in several formats"
6 | maintainer: ["c-cube" "lukstafi"]
7 | authors: ["Simon Cruanes" "Guillaume Bury" "lukstafi"]
8 | license: "BSD-2-Clause"
9 | tags: ["print" "box" "table" "tree"]
10 | homepage: "https://github.com/c-cube/printbox"
11 | bug-reports: "https://github.com/c-cube/printbox/issues"
12 | depends: [
13 | "dune" {>= "3.0"}
14 | "ocaml" {>= "4.08"}
15 | "odoc" {with-doc}
16 | ]
17 | build: [
18 | ["dune" "subst"] {dev}
19 | [
20 | "dune"
21 | "build"
22 | "-p"
23 | name
24 | "-j"
25 | jobs
26 | "@install"
27 | "@runtest" {with-test}
28 | "@doc" {with-doc}
29 | ]
30 | ]
31 | dev-repo: "git+https://github.com/c-cube/printbox.git"
32 |
--------------------------------------------------------------------------------
/src/PrintBox.ml:
--------------------------------------------------------------------------------
1 | (* This file is free software. See file "license" for more details. *)
2 |
3 | (** {1 Pretty-Printing of Boxes} *)
4 |
5 | type position = {
6 | x: int;
7 | y: int;
8 | }
9 |
10 | module Style = struct
11 | type color =
12 | | Black
13 | | Red
14 | | Yellow
15 | | Green
16 | | Blue
17 | | Magenta
18 | | Cyan
19 | | White
20 |
21 | type t = {
22 | bold: bool;
23 | bg_color: color option;
24 | fg_color: color option;
25 | preformatted: bool;
26 | }
27 |
28 | let default =
29 | { bold = false; bg_color = None; fg_color = None; preformatted = false }
30 |
31 | let set_bg_color c self = { self with bg_color = Some c }
32 | let set_fg_color c self = { self with fg_color = Some c }
33 | let set_bold b self = { self with bold = b }
34 | let set_preformatted b self = { self with preformatted = b }
35 | let bold : t = set_bold true default
36 | let preformatted : t = set_preformatted true default
37 | let bg_color c : t = set_bg_color c default
38 | let fg_color c : t = set_fg_color c default
39 | end
40 |
41 | type ext = ..
42 |
43 | type view =
44 | | Empty
45 | | Text of {
46 | l: string list;
47 | style: Style.t;
48 | }
49 | | Frame of {
50 | sub: t;
51 | stretch: bool;
52 | }
53 | | Pad of position * t (* vertical and horizontal padding *)
54 | | Align of {
55 | h: [ `Left | `Center | `Right ];
56 | v: [ `Top | `Center | `Bottom ];
57 | inner: t;
58 | }
59 | | Grid of [ `Bars | `None ] * t array array
60 | | Tree of int * t * t array
61 | | Link of {
62 | uri: string;
63 | inner: t;
64 | }
65 | | Anchor of {
66 | id: string;
67 | inner: t;
68 | }
69 | | Ext of {
70 | key: string;
71 | ext: ext;
72 | }
73 |
74 | and t = view
75 |
76 | let empty = Empty
77 | let[@inline] view (t : t) : view = t
78 | let[@inline] line_ s = Text { l = [ s ]; style = Style.default }
79 |
80 | let line_with_style style s =
81 | if String.contains s '\n' then invalid_arg @@ "PrintBox.line: " ^ s;
82 | Text { l = [ s ]; style }
83 |
84 | let line s = line_with_style Style.default s
85 |
86 | let[@inline] mk_text_ s : string list =
87 | if String.contains s '\n' then
88 | String.split_on_char '\n' s
89 | else
90 | [ s ]
91 |
92 | let[@inline] text s = Text { l = mk_text_ s; style = Style.default }
93 | let[@inline] text_with_style style s = Text { l = mk_text_ s; style }
94 |
95 | let sprintf_with_style style format =
96 | let buffer = Buffer.create 64 in
97 | Printf.kbprintf
98 | (fun _ -> text_with_style style (Buffer.contents buffer))
99 | buffer format
100 |
101 | let sprintf format = sprintf_with_style Style.default format
102 | let asprintf format = Format.kasprintf text format
103 |
104 | let asprintf_with_style style format =
105 | Format.kasprintf (text_with_style style) format
106 |
107 | let[@inline] lines l = Text { l; style = Style.default }
108 | let[@inline] lines_with_style style l = Text { l; style }
109 | let int x = line_ (string_of_int x)
110 | let float x = line_ (string_of_float x)
111 | let bool x = line_ (string_of_bool x)
112 | let int_ = int
113 | let float_ = float
114 | let bool_ = bool
115 | let[@inline] frame ?(stretch = false) b = Frame { sub = b; stretch }
116 |
117 | let pad' ~col ~lines b =
118 | assert (col >= 0 || lines >= 0);
119 | if col = 0 && lines = 0 then
120 | b
121 | else
122 | Pad ({ x = col; y = lines }, b)
123 |
124 | let pad b = pad' ~col:1 ~lines:1 b
125 | let hpad col b = pad' ~col ~lines:0 b
126 | let vpad lines b = pad' ~col:0 ~lines b
127 | let align ~h ~v b : t = Align { h; v; inner = b }
128 | let align_bottom b = align ~h:`Left ~v:`Bottom b
129 | let align_right b = align ~h:`Right ~v:`Top b
130 | let align_bottom_right b = align ~h:`Right ~v:`Bottom b
131 | let center_h b = align ~h:`Center ~v:`Top b
132 | let center_v b = align ~h:`Left ~v:`Center b
133 | let center_hv b = align ~h:`Center ~v:`Center b
134 | let map_matrix f m = Array.map (Array.map f) m
135 |
136 | let grid ?(pad = fun b -> b) ?(bars = true) m =
137 | let m = map_matrix pad m in
138 | Grid
139 | ( (if bars then
140 | `Bars
141 | else
142 | `None),
143 | m )
144 |
145 | let grid_l ?pad ?bars l =
146 | grid ?pad ?bars (Array.of_list l |> Array.map Array.of_list)
147 |
148 | let init_grid ?bars ~line ~col f =
149 | let m =
150 | Array.init line (fun j -> Array.init col (fun i -> f ~line:j ~col:i))
151 | in
152 | grid ?bars m
153 |
154 | let vlist ?pad ?bars l =
155 | let a = Array.of_list l in
156 | grid ?pad ?bars (Array.map (fun line -> [| line |]) a)
157 |
158 | let hlist ?pad ?bars l = grid ?pad ?bars [| Array.of_list l |]
159 | let hlist_map ?bars f l = hlist ?bars (List.map f l)
160 | let vlist_map ?bars f l = vlist ?bars (List.map f l)
161 | let grid_map ?bars f m = grid ?bars (Array.map (Array.map f) m)
162 | let grid_map_l ?bars f m = grid_l ?bars (List.map (List.map f) m)
163 |
164 | let grid_text ?(pad = fun x -> x) ?bars m =
165 | grid_map ?bars (fun x -> pad (text x)) m
166 |
167 | let grid_text_l ?pad ?bars l =
168 | grid_text ?pad ?bars (Array.of_list l |> Array.map Array.of_list)
169 |
170 | let record ?pad ?bars l =
171 | let fields, vals = List.split l in
172 | grid_l ?pad ?bars [ List.map text fields; vals ]
173 |
174 | let v_record ?pad ?bars l =
175 | grid_l ?pad ?bars (List.map (fun (f, v) -> [ text f; v ]) l)
176 |
177 | let dim_matrix m =
178 | if Array.length m = 0 then
179 | { x = 0; y = 0 }
180 | else
181 | { y = Array.length m; x = Array.length m.(0) }
182 |
183 | let transpose m =
184 | let dim = dim_matrix m in
185 | Array.init dim.x (fun i -> Array.init dim.y (fun j -> m.(j).(i)))
186 |
187 | let tree ?(indent = 0) node children =
188 | if indent < 0 then invalid_arg "tree: need non-negative indent";
189 | let children =
190 | List.filter
191 | (function
192 | | Empty -> false
193 | | _ -> true)
194 | children
195 | in
196 | match children with
197 | | [] -> node
198 | | _ :: _ ->
199 | let children = Array.of_list children in
200 | Tree (indent, node, children)
201 |
202 | let mk_tree ?indent f root =
203 | let rec make x =
204 | match f x with
205 | | b, [] -> b
206 | | b, children -> tree ?indent b (List.map make children)
207 | in
208 | make root
209 |
210 | let link ~uri inner : t = Link { uri; inner }
211 | let anchor ~id inner : t = Anchor { id; inner }
212 | let extension ~key ext = Ext { key; ext }
213 |
214 | (** {2 Simple Structural Interface} *)
215 |
216 | type 'a ktree = unit -> [ `Nil | `Node of 'a * 'a ktree list ]
217 | type box = t
218 |
219 | module Simple = struct
220 | type t =
221 | [ `Empty
222 | | `Pad of t
223 | | `Text of string
224 | | `Vlist of t list
225 | | `Hlist of t list
226 | | `Table of t array array
227 | | `Tree of t * t list
228 | ]
229 | (** The simple interface does not support extensions. *)
230 |
231 | let rec to_box = function
232 | | `Empty -> empty
233 | | `Pad b -> pad (to_box b)
234 | | `Text t -> text t
235 | | `Vlist l -> vlist (List.map to_box l)
236 | | `Hlist l -> hlist (List.map to_box l)
237 | | `Table a -> grid (map_matrix to_box a)
238 | | `Tree (b, l) -> tree (to_box b) (List.map to_box l)
239 |
240 | let rec of_ktree t =
241 | match t () with
242 | | `Nil -> `Empty
243 | | `Node (x, l) -> `Tree (x, List.map of_ktree l)
244 |
245 | let rec map_ktree f t =
246 | match t () with
247 | | `Nil -> `Empty
248 | | `Node (x, l) -> `Tree (f x, List.map (map_ktree f) l)
249 |
250 | let sprintf format =
251 | let buffer = Buffer.create 64 in
252 | Printf.kbprintf (fun _ -> `Text (Buffer.contents buffer)) buffer format
253 |
254 | let asprintf format = Format.kasprintf (fun s -> `Text s) format
255 | end
256 |
--------------------------------------------------------------------------------
/src/PrintBox.mli:
--------------------------------------------------------------------------------
1 | (* This file is free software. See file "license" for more details. *)
2 |
3 | (** {1 Pretty-Printing of nested Boxes}
4 |
5 | Allows to print nested boxes, lists, arrays, tables in a nice way
6 | on any monospaced support.
7 |
8 | {[
9 | # let b = PrintBox.(
10 | frame
11 | (vlist [ line "hello";
12 | hlist [line "world"; line "yolo"]])
13 | );;
14 | val b : t =
15 |
16 | # PrintBox_text.output ~indent:2 stdout b;;
17 | +----------+
18 | |hello |
19 | |----------|
20 | |world|yolo|
21 | +----------+
22 | - : unit = ()
23 |
24 | # let b2 = PrintBox.(
25 | frame
26 | (hlist [ text "I love\nto\npress\nenter";
27 | grid_text [| [|"a"; "bbb"|];
28 | [|"c"; "hello world"|] |]])
29 | );;
30 | val b2 : PrintBox.t =
31 |
32 | # PrintBox_text.output stdout b2;;
33 | +--------------------+
34 | |I love|a|bbb |
35 | |to |-+-----------|
36 | |press |c|hello world|
37 | |enter | | |
38 | +--------------------+
39 |
40 | - : unit = ()
41 |
42 | ]}
43 |
44 | Since 0.3 there is also basic support for coloring text:
45 |
46 | {[
47 | # let b = PrintBox.(
48 | frame
49 | (vlist [ line_with_style Style.(bg_color Green) "hello";
50 | hlist [line "world"; line_with_style Style.(fg_color Red) "yolo"]])
51 | );;
52 | val b : t =
53 | ]}
54 |
55 | *)
56 |
57 | type position = {
58 | x: int;
59 | y: int;
60 | }
61 | (** Positions are relative to the upper-left corner, that is,
62 | when [x] increases we go toward the right, and when [y] increases
63 | we go toward the bottom (same order as a printer) *)
64 |
65 | (** {2 Style}
66 |
67 | @since 0.3 *)
68 | module Style : sig
69 | type color =
70 | | Black
71 | | Red
72 | | Yellow
73 | | Green
74 | | Blue
75 | | Magenta
76 | | Cyan
77 | | White
78 |
79 | type t = {
80 | bold: bool;
81 | bg_color: color option; (** backgroud color *)
82 | fg_color: color option; (** foreground color *)
83 | preformatted: bool;
84 | (** where supported, the text rendering should be monospaced and respect whitespace *)
85 | }
86 | (** Basic styling (color, bold).
87 | @since 0.3 *)
88 |
89 | val default : t
90 | val set_bg_color : color -> t -> t
91 | val set_fg_color : color -> t -> t
92 | val set_bold : bool -> t -> t
93 | val set_preformatted : bool -> t -> t
94 | val bg_color : color -> t
95 | val fg_color : color -> t
96 | val bold : t
97 | val preformatted : t
98 | end
99 |
100 | (** {2 Box Combinators} *)
101 |
102 | type t
103 | (** Main type for a document composed of nested boxes.
104 | @since 0.2 the type [t] is opaque *)
105 |
106 | type ext = ..
107 | (** Extensions of the representation.
108 |
109 | @since 0.12
110 | *)
111 |
112 | (** The type [view] can be used to observe the inside of the box,
113 | now that [t] is opaque.
114 |
115 | @since 0.3 added [Align]
116 | @since 0.5 added [Link]
117 | @since 0.11 added [Anchor]
118 | @since 0.12 added [Stretch]
119 | *)
120 | type view = private
121 | | Empty
122 | | Text of {
123 | l: string list;
124 | style: Style.t;
125 | }
126 | | Frame of {
127 | sub: t;
128 | stretch: bool;
129 | }
130 | | Pad of position * t (* vertical and horizontal padding *)
131 | | Align of {
132 | h: [ `Left | `Center | `Right ];
133 | v: [ `Top | `Center | `Bottom ];
134 | inner: t;
135 | } (** Alignment within the surrounding box *)
136 | | Grid of [ `Bars | `None ] * t array array
137 | | Tree of int * t * t array (* int: indent *)
138 | | Link of {
139 | uri: string;
140 | inner: t;
141 | }
142 | | Anchor of {
143 | id: string;
144 | inner: t;
145 | }
146 | | Ext of {
147 | key: string;
148 | ext: ext;
149 | }
150 |
151 | val view : t -> view
152 | (** Observe the content of the box.
153 | @since 0.2 *)
154 |
155 | (** A box, either empty, containing directly text, or a table or
156 | tree of sub-boxes *)
157 |
158 | val empty : t
159 | (** Empty box, of size 0 *)
160 |
161 | val line : string -> t
162 | (** Make a single-line box.
163 | @raise Invalid_argument if the string contains ['\n'] *)
164 |
165 | val text : string -> t
166 | (** Any text, possibly with several lines *)
167 |
168 | val sprintf : ('a, Buffer.t, unit, t) format4 -> 'a
169 | (** Formatting for {!text} *)
170 |
171 | val asprintf : ('a, Format.formatter, unit, t) format4 -> 'a
172 | (** Formatting for {!text}.
173 | @since 0.2 *)
174 |
175 | val lines : string list -> t
176 | (** Shortcut for {!text}, with a list of lines.
177 | [lines l] is the same as [text (String.concat "\n" l)]. *)
178 |
179 | val int_ : int -> t
180 | val bool_ : bool -> t
181 | val float_ : float -> t
182 |
183 | val int : int -> t
184 | (** @since 0.2 *)
185 |
186 | val bool : bool -> t
187 | (** @since 0.2 *)
188 |
189 | val float : float -> t
190 | (** @since 0.2 *)
191 |
192 | val frame : ?stretch:bool -> t -> t
193 | (** Put a single frame around the box.
194 | @param stretch if true (default false), the frame expands to
195 | fill the available space. Present since 0.12 *)
196 |
197 | val pad : t -> t
198 | (** Pad the given box with some free space *)
199 |
200 | val pad' : col:int -> lines:int -> t -> t
201 | (** Pad with the given number of free cells for lines and columns *)
202 |
203 | val vpad : int -> t -> t
204 | (** Pad vertically by [n] spaces *)
205 |
206 | val hpad : int -> t -> t
207 | (** Pad horizontally by [n] spaces *)
208 |
209 | val align :
210 | h:[ `Left | `Right | `Center ] -> v:[ `Top | `Bottom | `Center ] -> t -> t
211 | (** Control alignment of the given box wrt its surrounding box, if any.
212 | @param h horizontal alignment
213 | @param v vertical alignment
214 | @since 0.3 *)
215 |
216 | val align_right : t -> t
217 | (** Left-pad to the size of the surrounding box, as in [align ~h:`Right ~v:`Top]
218 | @since 0.3 *)
219 |
220 | val align_bottom : t -> t
221 | (** Align to the bottom, as in [align ~h:`Left ~v:`Bottom]
222 | @since 0.3 *)
223 |
224 | val align_bottom_right : t -> t
225 | (** Align to the right and to the bottom, as in [align ~h:`Right ~v:`Bottom]
226 | @since 0.3 *)
227 |
228 | val center_h : t -> t
229 | (** Horizontal center, as in .
230 | @since 0.3 *)
231 |
232 | val center_v : t -> t
233 | (** Vertical center.
234 | @since 0.3 *)
235 |
236 | val center_hv : t -> t
237 | (** Try to center within the surrounding box, as in [align ~h:`Center ~v:`Center]
238 | @since 0.3 *)
239 |
240 | val grid : ?pad:(t -> t) -> ?bars:bool -> t array array -> t
241 | (** Grid of boxes (no frame between boxes). The matrix is indexed
242 | with lines first, then columns. The array must be a proper matrix,
243 | that is, all lines must have the same number of columns!
244 | @param pad used to pad each cell (for example with {!vpad} or {!hpad}),
245 | default doesn't do anything
246 | @param bars if true, each item of the grid will be framed.
247 | default value is [true] *)
248 |
249 | val grid_text : ?pad:(t -> t) -> ?bars:bool -> string array array -> t
250 | (** Same as {!grid}, but wraps every cell into a {!text} box *)
251 |
252 | val transpose : 'a array array -> 'a array array
253 | (** Transpose a matrix *)
254 |
255 | val init_grid :
256 | ?bars:bool -> line:int -> col:int -> (line:int -> col:int -> t) -> t
257 | (** Same as {!grid} but takes the matrix as a function *)
258 |
259 | val grid_l : ?pad:(t -> t) -> ?bars:bool -> t list list -> t
260 | (** Same as {!grid} but from lists.
261 | @since 0.3 *)
262 |
263 | val grid_text_l : ?pad:(t -> t) -> ?bars:bool -> string list list -> t
264 | (** Same as {!grid_text} but from lists.
265 | @since 0.3 *)
266 |
267 | val record : ?pad:(t -> t) -> ?bars:bool -> (string * t) list -> t
268 | (** A record displayed as a table, each field being a columng [(label,value)].
269 | {[
270 | # frame @@ record ["a", int 1; "b", float 3.14; "c", bool true];;
271 | - : t = +-----------+
272 | |a|b |c |
273 | |-+----+----|
274 | |1|3.14|true|
275 | +-----------+
276 | ]}
277 | @since 0.3 *)
278 |
279 | val v_record : ?pad:(t -> t) -> ?bars:bool -> (string * t) list -> t
280 | (** Like {!record}, but printed vertically rather than horizontally.
281 | {[
282 | # frame @@ v_record ["a", int 1; "b", float 3.14; "c", bool true];;
283 | - : t = +------+
284 | |a|1 |
285 | |-+----|
286 | |b|3.14|
287 | |-+----|
288 | |c|true|
289 | +------+
290 | ]}
291 | @since 0.4 *)
292 |
293 | val vlist : ?pad:(t -> t) -> ?bars:bool -> t list -> t
294 | (** Vertical list of boxes *)
295 |
296 | val hlist : ?pad:(t -> t) -> ?bars:bool -> t list -> t
297 | (** Horizontal list of boxes *)
298 |
299 | val grid_map : ?bars:bool -> ('a -> t) -> 'a array array -> t
300 |
301 | val grid_map_l : ?bars:bool -> ('a -> t) -> 'a list list -> t
302 | (** Same as {!grid_map} but with lists.
303 | @since 0.4 *)
304 |
305 | val vlist_map : ?bars:bool -> ('a -> t) -> 'a list -> t
306 | val hlist_map : ?bars:bool -> ('a -> t) -> 'a list -> t
307 |
308 | val tree : ?indent:int -> t -> t list -> t
309 | (** Tree structure, with a node label and a list of children nodes *)
310 |
311 | val mk_tree : ?indent:int -> ('a -> t * 'a list) -> 'a -> t
312 | (** Definition of a tree with a local function that maps nodes to
313 | their content and children *)
314 |
315 | val link : uri:string -> t -> t
316 | (** [link ~uri inner] points to the given URI, with the visible description
317 | being [inner].
318 | Will render in HTML as a "" element.
319 | @since 0.5
320 | *)
321 |
322 | val anchor : id:string -> t -> t
323 | (** [anchor ~id inner] provides an anchor with the given ID, with the visible hyperlink description
324 | being [inner].
325 | Will render in HTML as an "" element, and as a link in ANSI stylized text.
326 | If [inner] is non-empty, the rendered link URI is ["#" ^ id].
327 | @since 0.11
328 | *)
329 |
330 | val extension : key:string -> ext -> t
331 | (** [extension ~key ext] embeds an extended representation [ext] as a box. [ext] must be
332 | recognized by the used backends as an extension registered under [key].
333 | @since 0.12
334 | *)
335 |
336 | (** {2 Styling combinators} *)
337 |
338 | val line_with_style : Style.t -> string -> t
339 | (** Like {!line} but with additional styling.
340 | @since 0.3 *)
341 |
342 | val lines_with_style : Style.t -> string list -> t
343 | (** Like {!lines} but with additional styling.
344 | @since 0.3 *)
345 |
346 | val text_with_style : Style.t -> string -> t
347 | (** Like {!text} but with additional styling.
348 | @since 0.3 *)
349 |
350 | val sprintf_with_style : Style.t -> ('a, Buffer.t, unit, t) format4 -> 'a
351 | (** Formatting for {!text}, with style
352 | @since 0.3 *)
353 |
354 | val asprintf_with_style :
355 | Style.t -> ('a, Format.formatter, unit, t) format4 -> 'a
356 | (** Formatting for {!text}, with style.
357 | @since 0.3 *)
358 |
359 | (** {2 Simple Structural Interface} *)
360 |
361 | type 'a ktree = unit -> [ `Nil | `Node of 'a * 'a ktree list ]
362 | type box = t
363 |
364 | module Simple : sig
365 | type t =
366 | [ `Empty
367 | | `Pad of t
368 | | `Text of string
369 | | `Vlist of t list
370 | | `Hlist of t list
371 | | `Table of t array array
372 | | `Tree of t * t list
373 | ]
374 |
375 | val of_ktree : t ktree -> t
376 | (** Helper to convert trees *)
377 |
378 | val map_ktree : ('a -> t) -> 'a ktree -> t
379 | (** Helper to map trees into recursive boxes *)
380 |
381 | val to_box : t -> box
382 |
383 | val sprintf : ('a, Buffer.t, unit, t) format4 -> 'a
384 | (** Formatting for [`Text] *)
385 |
386 | val asprintf : ('a, Format.formatter, unit, t) format4 -> 'a
387 | (** Formatting for [`Text].
388 | @since 0.2 *)
389 | end
390 |
391 | (**/**)
392 |
393 | (** Utils *)
394 |
395 | val dim_matrix : _ array array -> position
396 | val map_matrix : ('a -> 'b) -> 'a array array -> 'b array array
397 |
398 | (**/**)
399 |
--------------------------------------------------------------------------------
/src/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name printbox)
3 | (public_name printbox)
4 | (wrapped false)
5 | (flags :standard -warn-error -a+8 -safe-string))
6 |
--------------------------------------------------------------------------------
/src/printbox-ext-plot/PrintBox_ext_plot.ml:
--------------------------------------------------------------------------------
1 | open Tyxml
2 | module B = PrintBox
3 | module H = Html
4 |
5 | type plot_spec =
6 | | Scatterplot of {
7 | points: (float * float) array;
8 | content: B.t;
9 | }
10 | | Scatterbag of { points: ((float * float) * B.t) array }
11 | | Line_plot of {
12 | points: float array;
13 | content: B.t;
14 | }
15 | | Boundary_map of {
16 | callback: float * float -> bool;
17 | content_true: B.t;
18 | content_false: B.t;
19 | }
20 | | Map of { callback: float * float -> B.t }
21 | | Line_plot_adaptive of {
22 | callback: float -> float;
23 | cache: (float, float) Hashtbl.t;
24 | content: B.t;
25 | }
26 | [@@deriving sexp_of]
27 |
28 | type graph = {
29 | specs: plot_spec list;
30 | x_label: string;
31 | y_label: string;
32 | size: int * int;
33 | axes: bool;
34 | prec: int;
35 | }
36 |
37 | let default_config =
38 | {
39 | specs = [];
40 | x_label = "x";
41 | y_label = "y";
42 | size = 800, 800;
43 | axes = true;
44 | prec = 3;
45 | }
46 |
47 | type PrintBox.ext += Plot of graph
48 |
49 | let box graph = B.extension ~key:"Plot" (Plot graph)
50 |
51 | let plot_canvas ?canvas ?(size : (int * int) option) ?(sparse = false)
52 | (specs : plot_spec list) =
53 | (* Unfortunately "x" and "y" of a "matrix" are opposite to how we want them displayed --
54 | the first dimension (i.e. "x") as the horizontal axis. *)
55 | let (dimx, dimy, canvas) : int * int * (int * B.t) list array array =
56 | (* The integer in the cells is the priority number: lower number = more visible. *)
57 | match canvas, size with
58 | | None, None ->
59 | invalid_arg "PrintBox_ext_plot.plot: provide ~canvas or ~size"
60 | | None, Some (dimx, dimy) -> dimx, dimy, Array.make_matrix dimy dimx []
61 | | Some canvas, None ->
62 | let dimy = Array.length canvas in
63 | let dimx = Array.length canvas.(0) in
64 | dimx, dimy, canvas
65 | | Some canvas, Some (dimx, dimy) ->
66 | assert (dimy = Array.length canvas);
67 | assert (dimx = Array.length canvas.(0));
68 | dimx, dimy, canvas
69 | in
70 | let all_x_points =
71 | specs
72 | |> List.map (function
73 | | Scatterplot { points; _ } -> Array.map fst points
74 | | Scatterbag { points } -> Array.map (fun ((x, _), _) -> x) points
75 | | Line_plot _ -> [||]
76 | | Map _ | Boundary_map _ -> [||]
77 | | Line_plot_adaptive _ -> [||])
78 | |> Array.concat
79 | in
80 | let given_y_points =
81 | specs
82 | |> List.map (function
83 | | Scatterplot { points; _ } -> Array.map snd points
84 | | Scatterbag { points } -> Array.map (fun ((_, y), _) -> y) points
85 | | Line_plot { points; _ } -> points
86 | | Map _ | Boundary_map _ -> [||]
87 | | Line_plot_adaptive _ -> [||])
88 | |> Array.concat
89 | in
90 | let minx =
91 | if all_x_points = [||] then
92 | 0.
93 | else
94 | Array.fold_left min all_x_points.(0) all_x_points
95 | in
96 | let maxx =
97 | if given_y_points = [||] then
98 | 1.
99 | else if all_x_points = [||] then
100 | Float.of_int (Array.length given_y_points - 1)
101 | else
102 | Array.fold_left max all_x_points.(0) all_x_points
103 | in
104 | let spanx = maxx -. minx in
105 | let spanx =
106 | if spanx <= epsilon_float then
107 | 1.0
108 | else
109 | spanx
110 | in
111 | let scale_x x = Float.(to_int (of_int (dimx - 1) *. (x -. minx) /. spanx)) in
112 | let unscale_x i = Float.(of_int i *. spanx /. of_int (dimx - 1)) +. minx in
113 | let extra_y_points =
114 | specs
115 | |> List.map (function
116 | | Line_plot_adaptive { callback; cache; _ } ->
117 | Array.init
118 | (if sparse then
119 | dimx / 5
120 | else
121 | dimx)
122 | (fun i ->
123 | let x =
124 | unscale_x
125 | (if sparse then
126 | i * 5
127 | else
128 | i)
129 | in
130 | let y =
131 | match Hashtbl.find_opt cache x with
132 | | Some y -> y
133 | | None -> callback x
134 | in
135 | if not (Hashtbl.mem cache x) then Hashtbl.add cache x y;
136 | y)
137 | | _ -> [||])
138 | |> Array.concat
139 | in
140 | let all_y_points = Array.append given_y_points extra_y_points in
141 | let miny =
142 | if all_y_points = [||] then
143 | 0.
144 | else
145 | Array.fold_left min all_y_points.(0) all_y_points
146 | in
147 | let maxy =
148 | if all_y_points = [||] then
149 | maxx -. minx
150 | else
151 | Array.fold_left max all_y_points.(0) all_y_points
152 | in
153 | let spany = maxy -. miny in
154 | let spany =
155 | if spany <= epsilon_float then
156 | 1.0
157 | else
158 | spany
159 | in
160 | let scale_1d y =
161 | try Some Float.(to_int @@ (of_int (dimy - 1) *. (y -. miny) /. spany))
162 | with Invalid_argument _ -> None
163 | in
164 | let scale_2d (x, y) =
165 | try
166 | Some Float.(scale_x x, to_int (of_int (dimy - 1) *. (y -. miny) /. spany))
167 | with Invalid_argument _ -> None
168 | in
169 | let spread ~i ~dmj =
170 | if sparse then
171 | i mod 10 = 0 && dmj mod 10 = 0
172 | else
173 | true
174 | in
175 | let update ~i ~dmj px =
176 | if i >= 0 && dmj >= 0 && i < dimx && dmj < dimy then
177 | canvas.(dmj).(i) <- px :: canvas.(dmj).(i)
178 | in
179 | let prerender_scatter ~priority points =
180 | Array.iter
181 | (fun (p, content) ->
182 | match scale_2d p with
183 | | Some (i, j) -> update ~i ~dmj:(dimy - 1 - j) (priority, content)
184 | | None -> ())
185 | points
186 | in
187 | let prerender_map ~priority callback =
188 | canvas
189 | |> Array.iteri (fun dmj ->
190 | Array.iteri (fun i _ ->
191 | if spread ~i ~dmj then (
192 | let x =
193 | Float.(of_int i *. spanx /. of_int (dimx - 1)) +. minx
194 | in
195 | let y =
196 | Float.(of_int (dimy - 1 - dmj) *. spany /. of_int (dimy - 1))
197 | +. miny
198 | in
199 | update ~i ~dmj (priority, callback (x, y))
200 | )))
201 | in
202 | specs
203 | |> List.iteri (fun priority -> function
204 | | Scatterplot { points; content } ->
205 | prerender_scatter ~priority (Array.map (fun p -> p, content) points)
206 | | Scatterbag { points } -> prerender_scatter ~priority points
207 | | Line_plot { points; content } ->
208 | let points = Array.map scale_1d points in
209 | let npoints = Float.of_int (Array.length points) in
210 | let rescale_x i =
211 | Float.(to_int @@ (of_int i *. of_int dimx /. npoints))
212 | in
213 | (* TODO: implement interpolation if not enough points. *)
214 | points
215 | |> Array.iteri (fun i ->
216 | Option.iter (fun j ->
217 | update ~i:(rescale_x i)
218 | ~dmj:(dimy - 1 - j)
219 | (priority, content)))
220 | | Boundary_map { callback; content_true; content_false } ->
221 | prerender_map ~priority (fun point ->
222 | if callback point then
223 | content_true
224 | else
225 | content_false)
226 | | Map { callback } -> prerender_map ~priority callback
227 | | Line_plot_adaptive { callback; cache; content } ->
228 | canvas.(0)
229 | |> Array.iteri (fun i _ ->
230 | if (not sparse) || i mod 5 = 0 then (
231 | let x = unscale_x i in
232 | let y =
233 | match Hashtbl.find_opt cache x with
234 | | Some y -> y
235 | | None ->
236 | let y = callback x in
237 | Hashtbl.add cache x y;
238 | y
239 | in
240 | scale_1d y
241 | |> Option.iter (fun j ->
242 | update ~i ~dmj:(dimy - 1 - j) (priority, content))
243 | )));
244 | minx, miny, maxx, maxy, canvas
245 |
246 | let concise_float = ref (fun ~prec -> Printf.sprintf "%.*g" prec)
247 |
248 | let plot ~prec ~axes ?canvas ?size ~x_label ~y_label ~sparse embed_canvas specs
249 | =
250 | let minx, miny, maxx, maxy, canvas =
251 | plot_canvas ?canvas ?size ~sparse specs
252 | in
253 | let open PrintBox in
254 | let y_label_l =
255 | List.map Char.escaped @@ List.of_seq @@ String.to_seq y_label
256 | in
257 | if not axes then
258 | embed_canvas canvas
259 | else
260 | grid_l
261 | [
262 | [
263 | hlist ~bars:false
264 | [
265 | align ~h:`Left ~v:`Center @@ lines y_label_l;
266 | vlist ~bars:false
267 | [
268 | line @@ !concise_float ~prec maxy;
269 | align_bottom @@ line @@ !concise_float ~prec miny;
270 | ];
271 | ];
272 | embed_canvas canvas;
273 | ];
274 | [
275 | empty;
276 | vlist ~bars:false
277 | [
278 | hlist ~bars:false
279 | [
280 | line @@ !concise_float ~prec minx;
281 | align_right @@ line @@ !concise_float ~prec maxx;
282 | ];
283 | align ~h:`Center ~v:`Bottom @@ line x_label;
284 | ];
285 | ];
286 | ]
287 |
288 | let scale_size_for_text = ref (0.125, 0.05)
289 |
290 | let explode s =
291 | let s_len = String.length s in
292 | let rec loop pos =
293 | let char_len = ref 1 in
294 | let cur_len () = PrintBox_text.str_display_width s pos !char_len in
295 | while pos + !char_len <= s_len && cur_len () = 0 do
296 | incr char_len
297 | done;
298 | if cur_len () > 0 then
299 | String.sub s pos !char_len :: loop (pos + !char_len)
300 | else
301 | []
302 | in
303 | loop 0
304 |
305 | let flatten_text_canvas ~num_specs canvas =
306 | let outputs =
307 | B.map_matrix
308 | (fun bs ->
309 | (* Fortunately, PrintBox_text does not insert \r by itself. *)
310 | List.map
311 | (fun (prio, b) ->
312 | let lines =
313 | String.split_on_char '\n' @@ PrintBox_text.to_string b
314 | in
315 | prio, List.map explode lines)
316 | bs)
317 | canvas
318 | in
319 | let dimj = Array.length canvas in
320 | let dimi = Array.length canvas.(0) in
321 | let canvas = Array.make_matrix dimj dimi (num_specs, " ") in
322 | let update ~i ~j (prio, box) =
323 | if i >= 0 && i < dimi && j >= 0 && j < dimj then
324 | List.iteri
325 | (fun dj ->
326 | List.iteri (fun di char ->
327 | let j' = j + dj and i' = i + di in
328 | if i' >= 0 && i' < dimi && j' >= 0 && j' < dimj then (
329 | let old_prio, _ = canvas.(j').(i') in
330 | if prio <= old_prio then canvas.(j').(i') <- prio, char
331 | )))
332 | box
333 | in
334 | Array.iteri
335 | (fun j row ->
336 | Array.iteri
337 | (fun i boxes -> List.iter (fun box -> update ~i ~j box) boxes)
338 | row)
339 | outputs;
340 | Array.map
341 | (fun row -> String.concat "" @@ List.map snd @@ Array.to_list row)
342 | canvas
343 |
344 | let text_based_handler ~render ext =
345 | match ext with
346 | | Plot { specs; x_label; y_label; size = sx, sy; axes; prec } ->
347 | let cx, cy = !scale_size_for_text in
348 | let size =
349 | Float.(to_int @@ (cx *. of_int sx), to_int @@ (cy *. of_int sy))
350 | in
351 | render
352 | (B.frame
353 | @@ plot ~prec ~axes ~size ~x_label ~y_label ~sparse:false
354 | (fun canvas ->
355 | B.lines @@ Array.to_list
356 | @@ flatten_text_canvas ~num_specs:(List.length specs) canvas)
357 | specs)
358 | | _ -> invalid_arg "PrintBox_ext_plot.text_handler: unrecognized extension"
359 |
360 | let text_handler ~style =
361 | text_based_handler ~render:(PrintBox_text.to_string_with ~style)
362 |
363 | let md_handler config =
364 | text_based_handler ~render:(PrintBox_md.to_string config)
365 |
366 | let embed_canvas_html ~num_specs canvas =
367 | let size_y = Array.length canvas in
368 | let size_x = Array.length canvas.(0) in
369 | let cells =
370 | canvas
371 | |> Array.mapi (fun y row ->
372 | row
373 | |> Array.mapi (fun x cell ->
374 | List.map
375 | (fun (priority, cell) ->
376 | let is_framed =
377 | match PrintBox.view cell with
378 | | B.Frame _ | B.Grid (`Bars, _) -> true
379 | | _ -> false
380 | in
381 | let frame =
382 | if is_framed then
383 | ";background-color:rgba(255,255,255,1)"
384 | else
385 | ""
386 | in
387 | let z_index =
388 | ";z-index:" ^ Int.to_string (num_specs - priority)
389 | in
390 | let cell =
391 | PrintBox_html.((to_html cell :> toplevel_html))
392 | in
393 | H.div
394 | ~a:
395 | [
396 | H.a_style
397 | ("position:absolute;top:" ^ Int.to_string y
398 | ^ "px;left:" ^ Int.to_string x ^ "px" ^ z_index
399 | ^ frame);
400 | ]
401 | [ cell ])
402 | cell)
403 | |> Array.to_list |> List.concat)
404 | in
405 | let result =
406 | Array.to_list cells |> List.concat
407 | |> H.div
408 | ~a:
409 | [
410 | H.a_style @@ "border:thin dotted;position:relative;width:"
411 | ^ Int.to_string size_x ^ ";height:" ^ Int.to_string size_y;
412 | ]
413 | in
414 | PrintBox_html.embed_html result
415 |
416 | let html_handler config ext =
417 | match ext with
418 | | Plot { specs; x_label; y_label; size; axes; prec } ->
419 | (PrintBox_html.to_html ~config
420 | (B.frame
421 | @@ plot ~prec ~axes ~size ~x_label ~y_label ~sparse:true
422 | (embed_canvas_html ~num_specs:(List.length specs))
423 | specs)
424 | :> PrintBox_html.toplevel_html)
425 | | _ -> invalid_arg "PrintBox_ext_plot.html_handler: unrecognized extension"
426 |
427 | let () =
428 | PrintBox_text.register_extension ~key:"Plot" text_handler;
429 | PrintBox_md.register_extension ~key:"Plot" md_handler;
430 | PrintBox_html.register_extension ~key:"Plot" html_handler
431 |
--------------------------------------------------------------------------------
/src/printbox-ext-plot/PrintBox_ext_plot.mli:
--------------------------------------------------------------------------------
1 | (* This file is free software. See file "license" for more details. *)
2 |
3 | (** {1 Extend {!PrintBox.t} with plots of scatter graphs and line graphs} *)
4 |
5 | (** Specifies a layer of plotting to be rendered on a graph, where all layers share
6 | the same coordinate space. A coordinate pair has the horizontal position first. *)
7 | type plot_spec =
8 | | Scatterplot of {
9 | points: (float * float) array;
10 | content: PrintBox.t;
11 | } (** Places the [content] box at each of the [points] coordinates. *)
12 | | Scatterbag of { points: ((float * float) * PrintBox.t) array }
13 | (** For each element of [points], places the given box at the given coordinates. *)
14 | | Line_plot of {
15 | points: float array;
16 | content: PrintBox.t;
17 | }
18 | (** Places the [content] box at vertical coordinates [points],
19 | evenly horizontally spread. *)
20 | | Boundary_map of {
21 | callback: float * float -> bool;
22 | content_true: PrintBox.t;
23 | content_false: PrintBox.t;
24 | }
25 | (** At evenly and densely spread coordinates across the graph, places either
26 | [content_true] or [content_false], depending on the result of [callback]. *)
27 | | Map of { callback: float * float -> PrintBox.t }
28 | (** At evenly and densely spread coordinates across the graph, places the box
29 | returned by [callback]. *)
30 | | Line_plot_adaptive of {
31 | callback: float -> float;
32 | cache: (float, float) Hashtbl.t;
33 | content: PrintBox.t;
34 | }
35 | (** At evenly and densely spread horizontal coordinates, places the [content] box
36 | at the vertical coordinate returned by [callback] for the horizontal coordinate
37 | of the placement position. *)
38 | [@@deriving sexp_of]
39 |
40 | type graph = {
41 | specs: plot_spec list;
42 | (** Earlier plots in the list take precedence: in case of overlap, their contents
43 | are on top. For HTML, we ensure that framed boxes and grids with bars are opaque. *)
44 | x_label: string; (** Horizontal axis label. *)
45 | y_label: string; (** Vertical axis label. *)
46 | size: int * int;
47 | (** Size of the graphing area in pixels. Scale for characters is configured by
48 | {!scale_size_for_text}. *)
49 | axes: bool;
50 | (** If false, only the graphing area is output (skipping the axes box). *)
51 | prec: int; (** Precision for numerical labels on axes. *)
52 | }
53 | (** A graph of plot layers, with a fixed rendering size but a coordinate window
54 | that adapts to the specified points. *)
55 |
56 | val default_config : graph
57 | (** A suggested configuration for plotting, with intended use:
58 | [Plot {default_config with specs = ...; ...}]. The default values are:
59 | [{ specs = []; x_label = "x"; y_label = "y"; size = 800, 800; axes = true; prec = 3 }] *)
60 |
61 | type PrintBox.ext +=
62 | | Plot of graph
63 | (** PrintBox extension for plotting: scatterplots, linear graphs, decision boundaries...
64 | See {!graph} and {!plot_spec} for details. *)
65 |
66 | val box : graph -> PrintBox.t
67 | (** [box graph] is the same as [PrintBox.extension ~key:"Plot" (Plot graph)]. *)
68 |
69 | val concise_float : (prec:int -> float -> string) ref
70 | (** The conversion function for labeling axes. Defaults to [sprintf "%.*g"]. *)
71 |
72 | val scale_size_for_text : (float * float) ref
73 | (** To provide a unified experience across the text and html backends, we treat
74 | the size specification as measured in pixels, and scale it by [!scale_size_for_text]
75 | to get a size measured in characters. The default value is [(0.125, 0.05)]. *)
76 |
--------------------------------------------------------------------------------
/src/printbox-ext-plot/README.md:
--------------------------------------------------------------------------------
1 | # PrintBox extension for rendering plots over a 2D canvas
2 |
3 | See https://ocaml.org/p/printbox-ext-plot/latest/doc/PrintBox_ext_plot/index.html ([source](PrintBox_ext_plot.mli)).
4 |
5 | ## Example: Map, Scatterbag
6 |
7 | [Source.](../../test/plotting.ml)
8 |
9 | The `specs` of this graph:
10 |
11 | ```ocaml
12 | Scatterbag
13 | { points = [| ((0., 1.), B.line "Y"); ((1., 0.), B.line "X"); ((0.75, 0.75), B.line "M") |] };
14 | Map { callback = (fun (x, y) ->
15 | let s = ((x ** 2.) +. (y ** 2.)) ** 0.5 in
16 | B.line @@
17 | if s < 0.3 then " "
18 | else if s < 0.6 then "."
19 | else if s < 0.9 then ","
20 | else if s < 1.2 then ":"
21 | else ";"); }
22 | ```
23 |
24 | Text rendering:
25 |
26 | ```verbatim
27 | ┌──┬────────────────────────────────────────────────────────────────────────────────────────────────────┐
28 | │ 1│Y:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;│
29 | │ │::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;│
30 | │ │:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;│
31 | │ │::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;│
32 | │ │,,,,,,,::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;│
33 | │ │,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;│
34 | │ │,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;│
35 | │ │,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;│
36 | │ │,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;│
37 | │ │,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;│
38 | │ │,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::::::::::::M:::::::::::::::::::;;;;;;│
39 | │ │,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::::::::::::::::::::::::::::::::;;;;│
40 | │ │,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::::::::::::::::::::::::::::::;;│
41 | │ │,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::::::::::::::::::::::::::::;│
42 | │ │,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::::::::::::::::::::::::::│
43 | │ │,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::::::::::::::::::::::::│
44 | │ │...........,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::::::::::::::::::::::│
45 | │ │.....................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::::::::::::::::::::│
46 | │ │...........................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::::::::::::::::::│
47 | │y │...............................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::::::::::::::::│
48 | │ │...................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::::::::::::::│
49 | │ │......................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::::::::::::│
50 | │ │.........................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::::::::::::│
51 | │ │............................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::::::::::│
52 | │ │..............................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::::::::│
53 | │ │................................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::::::::│
54 | │ │..................................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::::::│
55 | │ │...................................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::::::│
56 | │ │ ..........................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::::│
57 | │ │ ......................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::::│
58 | │ │ ....................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::│
59 | │ │ ..................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::::│
60 | │ │ .................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::│
61 | │ │ ................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::::│
62 | │ │ ................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::│
63 | │ │ ...............................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::│
64 | │ │ ..............................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::│
65 | │ │ ..............................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::::│
66 | │ │ ..............................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,::::::::::│
67 | │ 0│ ..............................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:::::::::X│
68 | ├──┼────────────────────────────────────────────────────────────────────────────────────────────────────┤
69 | │ │0 1│
70 | │ │ x │
71 | └──┴────────────────────────────────────────────────────────────────────────────────────────────────────┘
72 | ```
73 |
74 | HTML rendering image:
75 | 
76 |
77 | ## Example: Line_plot_adaptive, Boundary_map
78 |
79 | [Source.](../../test/plotting_linear.ml)
80 |
81 | The `specs` of this graph:
82 |
83 | ```ocaml
84 | Line_plot_adaptive
85 | { callback = (fun x -> sin x); content = B.line "#"; cache = Hashtbl.create 20 };
86 | Line_plot_adaptive
87 | { callback = (fun x -> x ** 2.); content = B.line "%"; cache = Hashtbl.create 20 };
88 | Boundary_map
89 | { content_false = B.line "."; content_true = B.line ","; callback = (fun (x, y) -> x > y) }
90 | ```
91 |
92 | Text rendering:
93 |
94 | ```verbatim
95 | ┌──┬────────────────────────────────────────────────────────────────────────────────────────────────────┐
96 | │ 1│...................................................................................................%│
97 | │ │.................................................................................................,%,│
98 | │ │..............................................................................................,,,%,,│
99 | │ │............................................................................................,,,,%,,,│
100 | │ │.........................................................................................,,,,,%%,,,,│
101 | │ │.......................................................................................,,,,,,%,,,,,,│
102 | │ │....................................................................................,,,,,,,,%,,,,,,,│
103 | │ │..................................................................................,,,,,,,,%%,,,,####│
104 | │ │...............................................................................,,,,,,,,,,%,#####,,,,│
105 | │ │.............................................................................,,,,,,,,,,####,,,,,,,,,│
106 | │ │..........................................................................,,,,,,,,,,###,,,,,,,,,,,,,│
107 | │ │........................................................................,,,,,,,,####%%,,,,,,,,,,,,,,│
108 | │ │.....................................................................,,,,,,,####,,,%,,,,,,,,,,,,,,,,│
109 | │ │...................................................................,,,,,,###,,,,,%%,,,,,,,,,,,,,,,,,│
110 | │ │................................................................,,,,,####,,,,,,,%,,,,,,,,,,,,,,,,,,,│
111 | │ │.............................................................,,,,,###,,,,,,,,,%%,,,,,,,,,,,,,,,,,,,,│
112 | │ │...........................................................,,,,###,,,,,,,,,,,%,,,,,,,,,,,,,,,,,,,,,,│
113 | │ │........................................................,,,,###,,,,,,,,,,,,%%,,,,,,,,,,,,,,,,,,,,,,,│
114 | │ │......................................................,,,###,,,,,,,,,,,,,%%,,,,,,,,,,,,,,,,,,,,,,,,,│
115 | │y │...................................................,,,###,,,,,,,,,,,,,,%%,,,,,,,,,,,,,,,,,,,,,,,,,,,│
116 | │ │.................................................,,###,,,,,,,,,,,,,,,,%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
117 | │ │..............................................,,###,,,,,,,,,,,,,,,,,%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
118 | │ │............................................,###,,,,,,,,,,,,,,,,,,%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
119 | │ │.........................................,###,,,,,,,,,,,,,,,,,,,%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
120 | │ │.......................................,##,,,,,,,,,,,,,,,,,,,,%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
121 | │ │....................................,###,,,,,,,,,,,,,,,,,,,,%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
122 | │ │..................................###,,,,,,,,,,,,,,,,,,,,,%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
123 | │ │...............................###,,,,,,,,,,,,,,,,,,,,,%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
124 | │ │............................,##,,,,,,,,,,,,,,,,,,,,,,%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
125 | │ │..........................###,,,,,,,,,,,,,,,,,,,,,,%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
126 | │ │.......................,##,,,,,,,,,,,,,,,,,,,,,,%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
127 | │ │.....................###,,,,,,,,,,,,,,,,,,,,,%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
128 | │ │..................###,,,,,,,,,,,,,,,,,,,,,%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
129 | │ │................##,,,,,,,,,,,,,,,,,,,,,%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
130 | │ │.............###,,,,,,,,,,,,,,,,,,,,%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
131 | │ │...........##,,,,,,,,,,,,,,,,,,,%%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
132 | │ │........###,,,,,,,,,,,,,,,,,%%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
133 | │ │......##,,,,,,,,,,,,,,,%%%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
134 | │ │...###,,,,,,,,,,%%%%%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
135 | │ 0│###%%%%%%%%%%%%%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
136 | ├──┼────────────────────────────────────────────────────────────────────────────────────────────────────┤
137 | │ │0 1│
138 | │ │ x │
139 | └──┴────────────────────────────────────────────────────────────────────────────────────────────────────┘
140 | ```
141 |
142 | HTML rendering image:
143 | 
144 |
145 | ### Example: nested boxes
146 |
147 | [Source.](../../test/plotting_nested.ml)
148 |
149 | The `specs` of this graph:
150 |
151 | ```ocaml
152 | Scatterbag { points = [| ((0.06, 0.95), reg_45); ((0.3, 0.3), reg_45) |] };
153 | Scatterbag {
154 | points = [|
155 | ((0., 1.), nice_unicode);
156 | ((0.08, 0.9), nice_unicode);
157 | ((1., 0.), for_3);
158 | ((0.3, 0.3), nice_unicode);
159 | ((0.75, 0.75), nice_unicode);
160 | ((0.8, 0.8), nice_unicode); |]; };
161 | Map {
162 | callback = (fun (x, y) ->
163 | let s = ((x ** 2.) +. (y ** 2.)) ** 0.5 in
164 | B.line
165 | @@
166 | if s < 0.3 then " "
167 | else if s < 0.6 then "."
168 | else if s < 0.9 then ","
169 | else if s < 1.2 then ":"
170 | else ";"); }
171 | ```
172 |
173 | Text rendering:
174 |
175 | ```verbatim
176 | ┌──┬─────────────────────────────────────────────────────────────────────────────────────────────────────┐
177 | │ 1│┌────────────────────────────────────┐::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; │
178 | │ ││nice unicode! 💪 │:::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;│
179 | │ │├────┌─────────┐─────────────────────┤:::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;; │
180 | │ ││┌───│123456789│┬───────────────────┐│::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;; │
181 | │ │││oï ├─────────┤────────────────────────────┐::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;; │
182 | │ │││π/2│┌───────┐│code! 💪 │::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;│
183 | │ │││τ/4││ ....││────────────────────────────┤::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;; │
184 | │ ││├───│├───────┤│───────┬───────────────────┐│::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;; │
185 | │ │││ ││. ││d nums:│┌─────────────────┐││::::::::::::::::::::::::::::::::::┌──────────────────── │
186 | │ │││ │└───────┘│ ││┌───────────────┐│││,,::::::::::::::::::::::::::::::::│nice unicode! 💪 │
187 | │ │││ └─────────┘ │││┌─────────────┐││││,,,,,,:::::::::::::::::::::::┌───────────────────────── │
188 | │ │││ ├─│├──────────────┤│││sum=Σ_i a·xᵢ²│││││,,,,,,,,,::::::::::::::::::::│nice unicode! 💪 │
189 | │ │││ └─││ ││││————— │││││,,,,,,,,,,,,:::::::::::::::::├───────────────────────── │
190 | │ │││ ││ ││││1+1 │││││,,,,,,,,,,,,,,,::::::::::::::│┌──────────────┬───────── │
191 | │ │││ ││ 0 │││├─────────────┤││││,,,,,,,,,,,,,,,,,,:::::::::::││oï ωεird nums:│┌──────── │
192 | │ │││ ││ ├─1 ││││ Ōₒ│││││,,,,,,,,,,,,,,,,,,,,,::::::::││π/2 ││┌─────── │
193 | │ ││└─────││ └─ω ││││ À │││││,,,,,,,,,,,,,,,,,,,,,,,::::::││τ/4 │││┌────── │
194 | │ │└──────││ └─ω² │││└─────────────┘││││,,,,,,,,,,,,,,,,,,,,,,,,,::::│├──────────────┤│││sum=Σ_ │
195 | │ │.......││ ││└───────────────┘│││,,,,,,,,,,,,,,,,,,,,,,,,,,,::││ ││││————— │
196 | │y │.......││ │└─────────────────┘││,,,,,,,,,,,,,,,,,,,,,,,,,,,,,││ ││││1+1 │
197 | │ │.......│└──────────────┴───────────────────┘│,,,,,,,,,,,,,,,,,,,,,,,,,,,,,││ 0 │││├────── │
198 | │ │.......└────────────────────────────────────┘,,,,,,,,,,,,,,,,,,,,,,,,,,,,,││ ├─1 ││││ │
199 | │ │.........................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,││ └─ω ││││ │
200 | │ │............................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,││ └─ω² │││└────── │
201 | │ │..............................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,││ ││└─────── │
202 | │ │................................................,,,,,,,,,,,,,,,,,,,,,,,,,,││ │└──────── │
203 | │ │..................................................,,,,,,,,,,,,,,,,,,,,,,,,│└──────────────┴───────── │
204 | │ │...................................................,,,,,,,,,,,,,,,,,,,,,,,└───────────────────────── │
205 | │ │ ..................┌─────────┐──────────────────────────┐,,,,,,,,,,,,,,,,,,::::::::::::::: │
206 | │ │ .............│123456789│de! 💪 │,,,,,,,,,,,,,,,,,,,,::::::::::::::│
207 | │ │ ..........├─────────┤──────────────────────────┤,,,,,,,,,,,,,,,,,,,,::::::::::::: │
208 | │ │ .......│┌───────┐│─────┬───────────────────┐│,,,,,,,,,,,,,,,,,,,,::::::::::::: │
209 | │ │ .....││ ....││nums:│┌─────────────────┐││,,,,,,,,,,,,,,,,,,,,,:::::::::::: │
210 | │ │ ...│├───────┤│ ││┌───────────────┐│││,,,,,,,,,,,,,,,,,,,,,:::::::::::: │
211 | │ │ ..││. ││ │││┌─────────────┐││││,,,,,,,,,,,,,,,,,,,,,,::::::::::: │
212 | │ │ .│└───────┘│─────┤│││sum=Σ_i a·xᵢ²│││││,,,,,,,,,,,,,,,,,,,,,,::::::::::: │
213 | │ │ └─────────┘ ││││————— │││││,,,,,,,,,,,,,,,,,,,,,,::::::::::: │
214 | │ │ ││ ││││1+1 │││││,,,,,,,,,,,,,,,,,,,,,,::::::::::: │
215 | │ │ ││ 0 │││├─────────────┤││││,,,,,,,,,,,,,,,,,,,,,,,:::::::::: │
216 | │ 0│ ││ ├─1 ││││ Ōₒ│││││,,,,,,,,,,,,,,,,,,,,,,,:::::::::( │
217 | ├──┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
218 | │ │0 1│
219 | │ │ x │
220 | └──┴─────────────────────────────────────────────────────────────────────────────────────────────────────┘
221 | ```
222 |
223 | HTML rendering image:
224 | 
225 |
226 | ## Example: Scatterplot
227 |
228 | [Source.](../../test/plotting_nested.ml)
229 |
230 | The `specs` of this graph:
231 |
232 | ```ocaml
233 | Scatterplot { points = points1; content = B.line "#" };
234 | Scatterplot { points = points2; content = B.line "%" };
235 | Boundary_map { content_false = B.line "."; content_true = B.line ",";
236 | callback = fun (x, y) -> x > y }
237 | ```
238 |
239 | Text rendering:
240 |
241 | ```verbatim
242 | ┌───────┬────────────────────────────────────────────────────────────────────────────────────────────────────┐
243 | │ 1.09 │............................#.........................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
244 | │ │.............................##.#...##..##..........................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
245 | │ │......................########.###..#.....#.##.....................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
246 | │ │.....................#.#..####.#...#.#..##.#...#.....#...........,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
247 | │ │..............#....##########.##....####.#...#.##.#.............,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
248 | │ │.............#.#####.###.#.#.#.##....###..##.##..##............,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
249 | │ │.................#.#.#####.###...####..#..#.##.#.##.##.......,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
250 | │ │.........#....##..##.#..#..####.###......###..##.####.##.##.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
251 | │ │.........#.....#...###.##.##...#.#.#.#...#.....#.##.#####.,,#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
252 | │ │..........###....#.#####.##.#..........#..#.....#####..###,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
253 | │ │.......#..#.#..#.#....#.....#................#..##...#.#,,#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
254 | │ │......###.###..##.##..........................#....#..###,#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
255 | │ │...#....#.###.##.##..........%.....................##,,#,#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
256 | │ │......#######....#...................%%..........#.,,##,#,###,,,,,,,,,,,,,,,,,,,,,,,,,,,,%,,,,,%,,,,│
257 | │ │.##.##...##.#...##...............%...%............,,,,##,##,###,#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,│
258 | │ │.#.#.#.###.##.##.................%%...%.........,,,,,,,,###,,,##,,#,,,,,,,,,,,,,,,,,,,,,,,,,%%%,%%,,│
259 | │ │.....#.#..##...................%..%%%%%........,,,,,,,,,###,###,#,,,,,,,,,,,,,,,,,,,,,,,%%,,,%%%%,,,│
260 | │ │#.##.###.#.#................%.%...%.%%%......,,,,,,,,,#,,,#,,,,#,,#,,,,,,,,,,,,,,,,,,,,%,,,%,,%,,,,,│
261 | │ │#####.#...##.................%..%.%.....%...,,,,,,,,,,,,,#,,##,###,##,,,,,,,,,,,,,,,,,%%%,,,,%%,%,%,│
262 | │y │#.#.###.#.##.................%%.%.%%%..%...,,,,,,,,,,,,#,,,,,,,#,#,#,,,,,,,,,,,,,,,,,,%%,%,,,,,,%,%%│
263 | │ │##...###..#..................%%..%..%.%..,,,,,,,,,,,,,,,,##,,####,,,,#,,,,,,,,,,,,,,,%,,%,,,%,%,,%%,│
264 | │ │##......###..........................%%.%,%,,,,,,,,,,,,,,,,,,,,#,####,,,,,,,,,,,,,,,,,,,%%,,,,,%,%%,│
265 | │ │...#.#.###...................%..%%%%..%,,%,,,,,,,,,,,,,,,,##,,#######,,,,,,,,,,,,,,,,%%%,,,,,%,%,,,,│
266 | │ │......##.......................%...%.%%%,,,,,,,,,,,,,,,,,#,#,#,,,,,,#,,,,,,,,,,,,,,,,,,%,,%%,,,%%%,,│
267 | │ │....#..#...........................%,%,,%,,%,,,,,,,,,,,,,,,,#,,,,,,#,,,,,,,,,,,,,,,%,%%%%,,%%,,%,,,,│
268 | │ │........##.......................%,%%,,,%,%,,,,,,,,,,,,,,,,,,,#,,##,,,,,,,,,,,,,,,%%,,,%,,%%%,%,,,,,│
269 | │ │#.#............................%.,%,,%,,%%,,,,%%,,,,,,,,,#,,#,,,,,#,#,,,,,,,,,,,,,,,,%%%,%%,,,,,,,,,│
270 | │ │.#...#.........................,%%%,,%%%%%,%,,,,,,,,,,,,,,,,,#,,,,#,,,,,,,,,,%,,,,,%%%%,%,%,,,%,,,,,│
271 | │ │..............................,,,,,,,%,,%%%,,,%,%%%,%,,,,#,,,,,,,,,,,,,,,,,,,,%,%,,%,%%,%,%%,,%,,,,,│
272 | │ │............................,,,,,,%,%%%%%,%,%,%,%,,,,,,%,,,,,,,,,,,,,,,,,,,,,,%%,,%%,%,%,%,,,,%,,,,,│
273 | │ │...........................,,,,,,,,%,,,%%%%%,%%%,,,,,,,%,,,,,,,,,,,,,,,,%,%%,,%,%%%%%,%%,,,,,,,,,,,,│
274 | │ │.........................,,,,,,,,,,,,,,,,%,,,,,%%%%%,%,%,%%,,,,,,,,,,%,%%%%%%,%,,%,,,,,,,,,,,,,,,,,,│
275 | │ │........................,,,,,,,,,,,,%,,,%,%%,%,,,,,,,%,,%%,,%%,%,,,%%%%%%%,,%,%,%,,,,,%%,,,,,,,,,,,,│
276 | │ │.......................,,,,,,,,,,,,,,,%,,%,%%,%%%,,,,%,%%%,%%,,,%,%%,%,%,%%%,,%%%%%,,,,,,%,,,,,,,,,,│
277 | │ │.....................,,,,,,,,,,,,,,,,,,%%%%,%,,%,,,,,,,%%%,,%,,%%%%,%%,,,%,%,,%%,,%,,,,,,,,,,,,,,,,,│
278 | │ │....................,,,,,,,,,,,,,,,,,,,,,,,,,%,,%%,,%,%,%,%%,%%,,,%,,,%,%%,,,%%%,,,%%%,,,,,,,,,,,,,,│
279 | │ │..................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,%,,%%%,,%%%%,,%%%%,,,%%,%%%%%,,,,,,%,,,,,,,,,,,,,,│
280 | │ │.................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,%%%,,,,,,,,%%%%%%,%%,%,,%%,%,,,,%,,,,,,,,,,,,,,,,,,,│
281 | │ │...............,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,%,%%,%%%,%%%,,%%%%,,,%%,,,,,,,,,,,,,,,,,,,,,,,,,,│
282 | │ -0.794│..............,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,%,,,,,,%,,,,,%,,,%,,,,%%,,,,,,,,,,,,,,,,,,,,,,,,,│
283 | ├───────┼────────────────────────────────────────────────────────────────────────────────────────────────────┤
284 | │ │-1.25 2.1│
285 | │ │ x │
286 | └───────┴────────────────────────────────────────────────────────────────────────────────────────────────────┘
287 | ```
288 |
289 | HTML rendering image:
290 | 
291 |
--------------------------------------------------------------------------------
/src/printbox-ext-plot/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name printbox_ext_plot)
3 | (public_name printbox-ext-plot)
4 | (wrapped false)
5 | (modules PrintBox_ext_plot)
6 | (flags :standard -w +a-3-4-44-29 -safe-string)
7 | (libraries printbox tyxml printbox-text printbox-html printbox-md))
8 |
--------------------------------------------------------------------------------
/src/printbox-html/PrintBox_html.ml:
--------------------------------------------------------------------------------
1 | (* This file is free software. See file "license" for more details. *)
2 |
3 | (** {1 Output HTML} *)
4 |
5 | open Tyxml
6 | module B = PrintBox
7 | module H = Html
8 |
9 | type 'a html = 'a Html.elt
10 | type toplevel_html = Html_types.li_content_fun html
11 | type summary_html = Html_types.span_content_fun html
12 | type link_html = Html_types.flow5_without_interactive html
13 | type PrintBox.ext += Embed_html of toplevel_html | Embed_summary_html of summary_html | Embed_link_html of link_html
14 |
15 | let prelude =
16 | let l =
17 | [
18 | "table, th, td { border-collapse: collapse; }";
19 | "table.framed { border: 2px solid black; }";
20 | "table.framed th, table.framed td { border: 1px solid black; }";
21 | "th, td { padding: 3px; }";
22 | "tr:nth-child(even) { background-color: #eee; }";
23 | "tr:nth-child(odd) { background-color: #fff; }";
24 | ".align-right { text-align: right; }";
25 | ".center { text-align: center; }";
26 | ]
27 | in
28 | H.style (List.map H.pcdata l)
29 |
30 | let prelude_str = Format.asprintf "%a@." (H.pp_elt ()) prelude
31 |
32 | let attrs_of_style (s : B.Style.t) : _ list * _ =
33 | let open B.Style in
34 | let { bold; bg_color; fg_color; preformatted } = s in
35 | let encode_color = function
36 | | Red -> "red"
37 | | Blue -> "blue"
38 | | Green -> "green"
39 | | Yellow -> "yellow"
40 | | Cyan -> "cyan"
41 | | Black -> "black"
42 | | Magenta -> "magenta"
43 | | White -> "white"
44 | in
45 | let s =
46 | (match bg_color with
47 | | None -> []
48 | | Some c -> [ "background-color", encode_color c ])
49 | @ (match fg_color with
50 | | None -> []
51 | | Some c -> [ "color", encode_color c ])
52 | @
53 | if preformatted then
54 | [ "font-family", "monospace" ]
55 | else
56 | []
57 | in
58 | let a =
59 | match s with
60 | | [] -> []
61 | | s ->
62 | [
63 | H.a_style @@ String.concat ";"
64 | @@ List.map (fun (k, v) -> k ^ ": " ^ v) s;
65 | ]
66 | in
67 | a, bold
68 |
69 | let a_class l =
70 | if List.exists (fun s -> s <> "") l then
71 | [ H.a_class l ]
72 | else
73 | []
74 |
75 | module Config = struct
76 | type t = {
77 | cls_table: string list;
78 | a_table: Html_types.table_attrib Html.attrib list;
79 | cls_text: string list;
80 | a_text: Html_types.span_attrib Html.attrib list;
81 | cls_row: string list;
82 | a_row: Html_types.div_attrib Html.attrib list;
83 | cls_col: string list;
84 | a_col: Html_types.div_attrib Html.attrib list;
85 | tree_summary: bool;
86 | }
87 |
88 | let default : t =
89 | {
90 | cls_table = [];
91 | a_table = [];
92 | cls_text = [];
93 | a_text = [];
94 | cls_row = [];
95 | a_row = [];
96 | cls_col = [];
97 | a_col = [];
98 | tree_summary = false;
99 | }
100 |
101 | let cls_table x c = { c with cls_table = x }
102 | let a_table x c = { c with a_table = x }
103 | let cls_text x c = { c with cls_text = x }
104 | let a_text x c = { c with a_text = x }
105 | let cls_row x c = { c with cls_row = x }
106 | let a_row x c = { c with a_row = x }
107 | let cls_col x c = { c with cls_col = x }
108 | let a_col x c = { c with a_col = x }
109 | let tree_summary x c = { c with tree_summary = x }
110 | end
111 |
112 | let extensions_toplevel : (string, Config.t -> PrintBox.ext -> toplevel_html) Hashtbl.t =
113 | Hashtbl.create 4
114 |
115 | let extensions_summary : (string, Config.t -> PrintBox.ext -> summary_html) Hashtbl.t =
116 | Hashtbl.create 4
117 |
118 | let extensions_link : (string, Config.t -> PrintBox.ext -> link_html) Hashtbl.t =
119 | Hashtbl.create 4
120 |
121 | let register_extension ~key handler =
122 | if Hashtbl.mem extensions_toplevel key then
123 | invalid_arg @@ "PrintBox_html.register_extension: already registered " ^ key;
124 | Hashtbl.add extensions_toplevel key handler
125 |
126 | let register_summary_extension ~key handler =
127 | if Hashtbl.mem extensions_summary key then
128 | invalid_arg @@ "PrintBox_html.register_summary_extension: already registered " ^ key;
129 | Hashtbl.add extensions_summary key handler
130 |
131 | let register_link_extension ~key handler =
132 | if Hashtbl.mem extensions_link key then
133 | invalid_arg @@ "PrintBox_html.register_link_extension: already registered " ^ key;
134 | Hashtbl.add extensions_link key handler
135 |
136 | let embed_html html = B.extension ~key:"Embed_html" (Embed_html html)
137 | let embed_summary_html html = B.extension ~key:"Embed_summary_html" (Embed_summary_html html)
138 | let embed_link_html html = B.extension ~key:"Embed_link_html" (Embed_link_html html)
139 | let sep_spans sep l =
140 | let len = List.length l in
141 | List.concat
142 | @@ List.mapi
143 | (fun i x ->
144 | x
145 | ::
146 | (if i < len - 1 then
147 | [ sep () ]
148 | else
149 | []))
150 | l
151 |
152 | let br_lines ~bold l =
153 | sep_spans (H.br ?a:None)
154 | @@ List.map (fun x ->
155 | if bold then
156 | H.b [ H.txt x ]
157 | else
158 | H.txt x)
159 | @@ List.concat
160 | @@ List.map (String.split_on_char '\n') l
161 |
162 | exception Summary_not_supported
163 | exception Link_not_supported
164 |
165 | let to_html_funs ~config =
166 | let open Config in
167 | let br_text_to_html ?(border = false) ~l ~style () =
168 | let a, bold = attrs_of_style style in
169 | let l = br_lines ~bold l in
170 | let a_border =
171 | if border then
172 | [ H.a_style "border:thin solid" ]
173 | else
174 | []
175 | in
176 | H.span ~a:(a_class config.cls_text @ a_border @ a @ config.a_text) l
177 | in
178 | let v_text_to_html ?(border = false) ~l ~style () =
179 | let a, bold = attrs_of_style style in
180 | let a_border =
181 | if border then
182 | [ H.a_style "border:thin solid" ]
183 | else
184 | []
185 | in
186 | if style.B.Style.preformatted then
187 | H.pre
188 | ~a:(a_class config.cls_text @ a_border @ a @ config.a_text)
189 | [ H.txt @@ String.concat "\n" l ]
190 | else (
191 | (* TODO: remove possible trailing '\r' *)
192 | let l = br_lines ~bold l in
193 | H.div ~a:(a_class config.cls_text @ a_border @ a @ config.a_text) l
194 | )
195 | in
196 | let rec to_html_summary b =
197 | match B.view b with
198 | | B.Empty ->
199 | (* Not really a case of unsupported summarization,
200 | but rather a request to not summarize. *)
201 | raise Summary_not_supported
202 | | B.Text { l; style } -> br_text_to_html ~l ~style ()
203 | | B.Pad (_, b) ->
204 | (* FIXME: not implemented yet *)
205 | to_html_summary b
206 | | B.Frame { sub = b; stretch = _ } ->
207 | H.span ~a:[ H.a_style "border:thin solid" ] [ to_html_summary b ]
208 | | B.Align { h = `Right; inner = b; v = _ } ->
209 | H.span ~a:[ H.a_class [ "align-right" ] ] [ to_html_summary b ]
210 | | B.Align { h = `Center; inner = b; v = _ } ->
211 | H.span ~a:[ H.a_class [ "center" ] ] [ to_html_summary b ]
212 | | B.Align { inner = b; _ } -> to_html_summary b
213 | | B.Grid (bars, a) ->
214 | (* TODO: support selected table styles. *)
215 | let a_border =
216 | if bars = `Bars then
217 | [ H.a_style "border:thin dotted" ]
218 | else
219 | []
220 | in
221 | let to_row a =
222 | let cols =
223 | Array.to_list a
224 | |> List.map (fun b ->
225 | H.span
226 | ~a:(a_class config.cls_col @ config.a_col @ a_border)
227 | [ to_html_summary b ])
228 | in
229 | H.span ~a:a_border @@ sep_spans H.space cols
230 | in
231 | let rows = Array.to_list a |> List.map to_row in
232 | H.span @@ sep_spans (H.br ?a:None) rows
233 | | B.Anchor { id; inner } ->
234 | (match B.view inner with
235 | | B.Empty -> H.a ~a:[ H.a_id id ] []
236 | | _ -> raise Summary_not_supported)
237 | | B.Ext { key = _; ext = Embed_summary_html result } -> result
238 | | B.Ext { key = _; ext = Embed_link_html _ } -> raise Summary_not_supported
239 | | B.Ext { key = _; ext = Embed_html _ } -> raise Summary_not_supported
240 | | B.Ext { key; ext } ->
241 | (match Hashtbl.find_opt extensions_summary key with
242 | | Some handler -> handler config ext
243 | | None -> raise Summary_not_supported)
244 | | B.Tree _ | B.Link _ -> raise Summary_not_supported
245 | in
246 | let loop :
247 | 'tags.
248 | (B.t ->
249 | ([< Html_types.flow5 > `Pre `Span `Div `Ul `Table `P ] as 'tags) html) ->
250 | B.t ->
251 | 'tags html =
252 | fun fix b ->
253 | match B.view b with
254 | | B.Empty ->
255 | (H.div [] :> [< Html_types.flow5 > `Pre `Span `Div `P `Table `Ul ] html)
256 | | B.Text { l; style } when style.B.Style.preformatted ->
257 | v_text_to_html ~l ~style ()
258 | | B.Text { l; style } -> v_text_to_html ~l ~style ()
259 | | B.Pad (_, b) ->
260 | (* FIXME: not implemented yet *)
261 | fix b
262 | | B.Frame { sub = b; _ } ->
263 | H.div ~a:[ H.a_style "border:thin solid" ] [ fix b ]
264 | | B.Align { h = `Right; inner = b; v = _ } ->
265 | H.div ~a:[ H.a_class [ "align-right" ] ] [ fix b ]
266 | | B.Align { h = `Center; inner = b; v = _ } ->
267 | H.div ~a:[ H.a_class [ "center" ] ] [ fix b ]
268 | | B.Align { inner = b; _ } -> fix b
269 | | B.Grid (bars, a) ->
270 | let class_ =
271 | match bars with
272 | | `Bars -> "framed"
273 | | `None -> "non-framed"
274 | in
275 | let to_row a =
276 | Array.to_list a
277 | |> List.map (fun b ->
278 | H.td ~a:(a_class config.cls_col @ config.a_col) [ fix b ])
279 | |> fun x -> H.tr ~a:(a_class config.cls_row @ config.a_row) x
280 | in
281 | let rows = Array.to_list a |> List.map to_row in
282 | H.table ~a:(a_class (class_ :: config.cls_table) @ config.a_table) rows
283 | | B.Tree (_, b, l) ->
284 | let l = Array.to_list l in
285 | H.div [ fix b; H.ul (List.map (fun x -> H.li [ fix x ]) l) ]
286 | | B.Anchor _ -> assert false
287 | | B.Link _ -> assert false
288 | | B.Ext _ -> assert false
289 | in
290 |
291 | let rec to_html_rec b =
292 | match B.view b with
293 | | B.Tree (_, b, l) when config.tree_summary ->
294 | let l = Array.to_list l in
295 | let body = H.ul (List.map (fun x -> H.li [ to_html_rec x ]) l) in
296 | (try H.details (H.summary [ to_html_summary b ]) [ body ]
297 | with Summary_not_supported -> H.div [ to_html_rec b; body ])
298 | | B.Link { uri; inner } ->
299 | (try H.div [ H.a ~a:[ H.a_href uri ] [ to_html_nondet_rec inner ] ]
300 | with Link_not_supported ->
301 | H.div [ H.a ~a:[ H.a_href uri ] [ H.txt ("["^uri^"]") ]; to_html_rec inner ] )
302 | | B.Anchor { id; inner } ->
303 | (match B.view inner with
304 | | B.Empty -> H.a ~a:[ H.a_id id ] []
305 | | _ ->
306 | try H.a ~a:[ H.a_id id; H.a_href @@ "#" ^ id ] [ to_html_nondet_rec inner ]
307 | with Link_not_supported ->
308 | H.div [ H.a ~a:[ H.a_id id; H.a_href @@ "#" ^ id ] [ H.txt ("[#"^id^"]")] ; to_html_rec inner ])
309 | | B.Ext { key = _; ext = Embed_html result } -> result
310 | | B.Ext { key = _; ext = Embed_summary_html result } -> (result :> toplevel_html)
311 | | B.Ext { key; ext } ->
312 | (match Hashtbl.find_opt extensions_toplevel key with
313 | | Some handler -> handler config ext
314 | | None ->
315 | failwith @@ "PrintBox_html.to_html: missing extension handler for "
316 | ^ key)
317 | | _ -> loop to_html_rec b
318 | and to_html_nondet_rec b =
319 | match B.view b with
320 | | B.Empty -> H.span []
321 | | B.Text { l; style } -> v_text_to_html ~l ~style ()
322 | | B.Link { uri; inner } ->
323 | H.div [ H.a ~a:[ H.a_href uri ] [ to_html_nondet_rec inner ] ]
324 | | B.Ext { key = _; ext = Embed_link_html result } -> result
325 | | B.Ext { key = _; ext = Embed_summary_html _ } -> raise Link_not_supported
326 | | B.Ext { key = _; ext = Embed_html _ } -> raise Link_not_supported
327 | | B.Ext { key; ext } ->
328 | (match Hashtbl.find_opt extensions_link key with
329 | | Some handler -> handler config ext
330 | | None -> raise Link_not_supported)
331 | | _ -> loop to_html_nondet_rec b
332 | in
333 | to_html_rec, to_html_summary, to_html_nondet_rec
334 |
335 | let to_html ?(config = Config.default) =
336 | let to_html_rec, _, _ = to_html_funs ~config in
337 | fun b -> H.div [ to_html_rec b ]
338 |
339 | let to_summary_html ?(config = Config.default) =
340 | let _, to_html_summary, _ = to_html_funs ~config in
341 | to_html_summary
342 |
343 | let to_link_html ?(config = Config.default) =
344 | let _, _, to_html_nondet_rec = to_html_funs ~config in
345 | to_html_nondet_rec
346 |
347 | let to_string ?config b =
348 | Format.asprintf "@[%a@]@." (H.pp_elt ()) (to_html ?config b)
349 |
350 | let to_string_indent ?config b =
351 | Format.asprintf "@[%a@]@." (H.pp_elt ~indent:true ()) (to_html ?config b)
352 |
353 | let pp ?(flush = true) ?config ?indent () pp b =
354 | if flush then
355 | Format.fprintf pp "@[%a@]@." (H.pp_elt ?indent ()) (to_html ?config b)
356 | else
357 | Format.fprintf pp "@[%a@]" (H.pp_elt ?indent ()) (to_html ?config b)
358 |
359 | let to_string_doc ?config b =
360 | let meta_str =
361 | ""
362 | in
363 | let footer_str =
364 | ""
369 | in
370 | Format.asprintf "%s%s@[%a@]%s@." meta_str
371 | prelude_str (H.pp_elt ()) (to_html ?config b) footer_str
372 |
--------------------------------------------------------------------------------
/src/printbox-html/PrintBox_html.mli:
--------------------------------------------------------------------------------
1 | (* This file is free software. See file "license" for more details. *)
2 |
3 | (** {1 Output HTML} *)
4 |
5 | open Tyxml
6 |
7 | type 'a html = 'a Html.elt
8 | type toplevel_html = Html_types.li_content_fun html
9 | type summary_html = Html_types.span_content_fun html
10 | type link_html = Html_types.flow5_without_interactive html
11 |
12 | type PrintBox.ext +=
13 | | Embed_html of toplevel_html
14 | (** Injects HTML into a box. It is handled natively by [PrintBox_html].
15 | NOTE: this extension is unlikely to be supported by other backends! *)
16 | | Embed_summary_html of summary_html
17 | (** Injects HTML intended for tree nodes into a box.
18 | It is handled natively by [PrintBox_html].
19 | NOTE: this extension is unlikely to be supported by other backends! *)
20 | | Embed_link_html of link_html
21 | (** Injects HTML intended for link bodies.
22 | It is handled natively by [PrintBox_html].
23 | NOTE: this extension is unlikely to be supported by other backends! *)
24 |
25 | val embed_html : toplevel_html -> PrintBox.t
26 | (** Injects HTML into a box. NOTE: this is unlikely to be supported by other backends! *)
27 |
28 | val embed_summary_html : summary_html -> PrintBox.t
29 | (** Injects HTML intended for tree nodes into a box.
30 | NOTE: this is unlikely to be supported by other backends! *)
31 |
32 | val embed_link_html : link_html -> PrintBox.t
33 | (** Injects HTML intended for link bodies into a box.
34 | NOTE: this is unlikely to be supported by other backends! *)
35 |
36 | val prelude : [> Html_types.style ] html
37 | (** HTML text to embed in the "", defining the style for tables *)
38 |
39 | val prelude_str : string
40 |
41 | (** {2 Classes and attributes}
42 |
43 | Custom classes and attributes for tables, lists, etc.
44 |
45 | @since 0.5 *)
46 | module Config : sig
47 | type t
48 |
49 | val default : t
50 | val cls_table : string list -> t -> t
51 | val a_table : Html_types.table_attrib Html.attrib list -> t -> t
52 | val cls_text : string list -> t -> t
53 | val a_text : Html_types.div_attrib Html.attrib list -> t -> t
54 | val cls_row : string list -> t -> t
55 | val a_row : Html_types.div_attrib Html.attrib list -> t -> t
56 | val cls_col : string list -> t -> t
57 | val a_col : Html_types.div_attrib Html.attrib list -> t -> t
58 |
59 | val tree_summary : bool -> t -> t
60 | (** When set to true, the trees are rendered collapsed
61 | using the [] HTML5 element. *)
62 | end
63 |
64 | val register_extension :
65 | key:string -> (Config.t -> PrintBox.ext -> toplevel_html) -> unit
66 | (** Add support for the extension with the given key to this rendering backend. *)
67 |
68 | val register_summary_extension :
69 | key:string -> (Config.t -> PrintBox.ext -> summary_html) -> unit
70 | (** Add support for the extension with the given key to this rendering backend. *)
71 |
72 | val register_link_extension :
73 | key:string -> (Config.t -> PrintBox.ext -> link_html) -> unit
74 | (** Add support for the extension with the given key to this rendering backend. *)
75 |
76 | val to_html : ?config:Config.t -> PrintBox.t -> [ `Div ] html
77 | (** HTML for one box *)
78 |
79 | exception Summary_not_supported
80 | (** Raised by {!to_summary_html} if the box cannot be rendered suitably
81 | for a summary part of the details element. *)
82 |
83 | val to_summary_html : ?config:Config.t -> PrintBox.t -> summary_html
84 | (** HTML suitable for tree nodes. Raises {!Summary_not_supported} if the box
85 | cannot be rendered in this form. *)
86 |
87 | exception Link_not_supported
88 | (** Raised by {!to_link_html} if the box cannot be rendered suitably
89 | for a link body. *)
90 |
91 | val to_link_html : ?config:Config.t -> PrintBox.t -> link_html
92 | (** HTML suitable for link bodies. Raises {!Link_not_supported} if the box
93 | cannot be rendered in this form. *)
94 |
95 | val pp :
96 | ?flush:bool ->
97 | ?config:Config.t ->
98 | ?indent:bool ->
99 | unit ->
100 | Format.formatter ->
101 | PrintBox.t ->
102 | unit
103 |
104 | val to_string : ?config:Config.t -> PrintBox.t -> string
105 | val to_string_indent : ?config:Config.t -> PrintBox.t -> string
106 |
107 | val to_string_doc : ?config:Config.t -> PrintBox.t -> string
108 | (** Same as {!to_string}, but adds the prelude and some footer *)
109 |
--------------------------------------------------------------------------------
/src/printbox-html/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name printbox_html)
3 | (public_name printbox-html)
4 | (wrapped false)
5 | (flags :standard -w +a-3-4-44-29 -safe-string)
6 | (libraries printbox tyxml))
7 |
--------------------------------------------------------------------------------
/src/printbox-md/PrintBox_md.ml:
--------------------------------------------------------------------------------
1 | (* This file is free software. See file "license" for more details. *)
2 |
3 | module B = PrintBox
4 |
5 | module Config = struct
6 | type preformatted =
7 | | Code_block
8 | | Code_quote
9 |
10 | type t = {
11 | vlists: [ `Line_break | `List | `As_table ];
12 | hlists: [ `Minimal | `As_table ];
13 | foldable_trees: bool;
14 | multiline_preformatted: preformatted;
15 | one_line_preformatted: preformatted;
16 | frames: [ `Quotation | `As_table ];
17 | tab_width: int;
18 | }
19 |
20 | let default =
21 | {
22 | vlists = `List;
23 | hlists = `Minimal;
24 | foldable_trees = false;
25 | multiline_preformatted = Code_block;
26 | one_line_preformatted = Code_quote;
27 | frames = `Quotation;
28 | tab_width = 4;
29 | }
30 |
31 | let uniform =
32 | {
33 | vlists = `Line_break;
34 | hlists = `As_table;
35 | foldable_trees = true;
36 | multiline_preformatted = Code_block;
37 | one_line_preformatted = Code_quote;
38 | frames = `As_table;
39 | tab_width = 4;
40 | }
41 |
42 | let vlists x c = { c with vlists = x }
43 | let hlists x c = { c with hlists = x }
44 | let foldable_trees c = { c with foldable_trees = true }
45 | let unfolded_trees c = { c with foldable_trees = false }
46 | let multiline_preformatted x c = { c with multiline_preformatted = x }
47 | let one_line_preformatted x c = { c with one_line_preformatted = x }
48 | let tab_width x c = { c with tab_width = x }
49 | let quotation_frames c = { c with frames = `Quotation }
50 | let table_frames c = { c with frames = `As_table }
51 | end
52 |
53 | let extensions : (string, Config.t -> PrintBox.ext -> string) Hashtbl.t =
54 | Hashtbl.create 4
55 |
56 | let register_extension ~key handler =
57 | if Hashtbl.mem extensions key then
58 | invalid_arg @@ "PrintBox_text.register_extension: already registered " ^ key;
59 | Hashtbl.add extensions key handler
60 |
61 | let style_format c ~no_md ~multiline (s : B.Style.t) =
62 | let open B.Style in
63 | (* Colors require support for styles: see issue #37 *)
64 | let { bold; bg_color = _; fg_color = _; preformatted } = s in
65 | let preformatted_conf =
66 | if multiline then
67 | c.Config.multiline_preformatted
68 | else
69 | c.Config.one_line_preformatted
70 | in
71 | let code_block =
72 | (not no_md) && preformatted && preformatted_conf = Config.Code_block
73 | in
74 | let code_quote =
75 | (not no_md) && preformatted && preformatted_conf = Config.Code_quote
76 | in
77 | let bold_pre, bold_post =
78 | match bold, no_md with
79 | | false, _ -> "", ""
80 | | true, false -> "**", "**"
81 | | true, true -> "", ""
82 | in
83 | let code_pre, code_post =
84 | if code_block || code_quote || not preformatted then
85 | "", ""
86 | else
87 | "", "
"
88 | in
89 | bold_pre ^ code_pre, code_post ^ bold_post, code_block, code_quote
90 |
91 | let break_lines l =
92 | let lines = List.concat @@ List.map (String.split_on_char '\n') l in
93 | List.filter_map
94 | (fun s ->
95 | let len = String.length s in
96 | if len = 0 then
97 | None
98 | else if s.[len - 1] = '\r' then
99 | Some (String.sub s 0 (len - 1))
100 | else
101 | Some s)
102 | lines
103 |
104 | let pp_string_escaped ~tab_width ~code_block ~code_quote ~html out s =
105 | let open Format in
106 | if code_block then
107 | pp_print_string out s
108 | else (
109 | let len = String.length s in
110 | let opt_char i =
111 | if i < len then
112 | Some s.[i]
113 | else
114 | None
115 | in
116 | let print_spaces nbsp n_spaces =
117 | if n_spaces > 0 then (
118 | let halfsp = Array.to_list @@ Array.make ((n_spaces + 1) / 2) " " in
119 | let trailing =
120 | if n_spaces mod 2 = 0 then
121 | nbsp
122 | else
123 | ""
124 | in
125 | fprintf out "%s%s" (String.concat nbsp halfsp) trailing
126 | )
127 | in
128 | let print_next_spaces i =
129 | match opt_char i, opt_char (i + 1), opt_char (i + 2) with
130 | | Some ' ', Some ' ', Some ' ' when i = 0 ->
131 | pp_print_string out "·";
132 | 1
133 | | Some ' ', Some ' ', Some ' ' ->
134 | pp_print_string out " ·";
135 | 2
136 | | Some ' ', Some ' ', _ ->
137 | pp_print_string out "· ";
138 | 2
139 | | Some '\t', Some ' ', _ when i = 0 && tab_width mod 2 = 0 ->
140 | pp_print_string out "·";
141 | print_spaces "·" tab_width;
142 | 2
143 | | Some '\t', Some '\t', Some ' ' when i = 0 && tab_width mod 2 = 0 ->
144 | pp_print_string out "·";
145 | print_spaces "·" (tab_width - 1);
146 | pp_print_string out "·";
147 | print_spaces "·" tab_width;
148 | 2
149 | | Some '\t', _, _ when i = 0 ->
150 | pp_print_string out "·";
151 | print_spaces "·" (tab_width - 1);
152 | 1
153 | | Some '\t', Some ' ', _ when tab_width mod 2 = 1 ->
154 | print_spaces "·" (tab_width + 1);
155 | 2
156 | | Some '\t', Some '\t', _ when tab_width mod 2 = 1 ->
157 | print_spaces "·" (2 * tab_width);
158 | 2
159 | | Some '\t', _, _ ->
160 | print_spaces "·" tab_width;
161 | 1
162 | | Some ' ', _, _ ->
163 | pp_print_string out " ";
164 | 1
165 | | _ -> assert false
166 | in
167 | let print_next_chars =
168 | if html then
169 | fun i ->
170 | match opt_char i with
171 | | Some '<' ->
172 | pp_print_string out "<";
173 | 1
174 | | Some '>' ->
175 | pp_print_string out ">";
176 | 1
177 | | Some '&' ->
178 | pp_print_string out "&";
179 | 1
180 | | Some '\t' | Some ' ' -> print_next_spaces i
181 | | Some c ->
182 | pp_print_char out c;
183 | 1
184 | | None -> len
185 | else if code_quote then
186 | fun i ->
187 | match opt_char i with
188 | | Some '\t' | Some ' ' -> print_next_spaces i
189 | | Some c ->
190 | pp_print_char out c;
191 | 1
192 | | None -> len
193 | else
194 | fun i ->
195 | match opt_char i, opt_char (i + 1), opt_char (i + 2) with
196 | | Some '<', _, _ ->
197 | pp_print_string out "\\<";
198 | 1
199 | | Some '>', _, _ ->
200 | pp_print_string out "\\>";
201 | 1
202 | | Some '`', _, _ ->
203 | pp_print_string out "\\`";
204 | 1
205 | | Some ' ', Some '*', Some ' ' ->
206 | pp_print_string out " * ";
207 | 3
208 | | Some '*', _, _ ->
209 | pp_print_string out "\\*";
210 | 1
211 | | Some ' ', Some '_', Some ' ' ->
212 | pp_print_string out " _ ";
213 | 3
214 | | Some c1, Some '_', Some c2 when c1 <> ' ' && c2 <> ' ' ->
215 | fprintf out "%c_%c" c1 c2;
216 | 3
217 | | Some '_', _, _ ->
218 | pp_print_string out "\\_";
219 | 1
220 | | Some '\t', _, _ | Some ' ', _, _ -> print_next_spaces i
221 | | Some c, _, _ ->
222 | pp_print_char out c;
223 | 1
224 | | _ -> len
225 | in
226 | let i = ref 0 in
227 | let quote_pre, quote_post =
228 | if code_quote && String.contains s '`' then
229 | "`` ", " ``"
230 | else
231 | "`", "`"
232 | in
233 | if code_quote then pp_print_string out quote_pre;
234 | while !i < len do
235 | i := !i + print_next_chars !i
236 | done;
237 | if code_quote then pp_print_string out quote_post
238 | )
239 |
240 | let rec multiline_heuristic c b =
241 | match B.view b with
242 | | B.Empty -> false
243 | | B.Text { l = []; _ } -> false
244 | | B.Text { l = [ s ]; _ } -> String.contains s '\n'
245 | | B.Text _ -> true
246 | | B.Frame _ when c.Config.frames = `As_table -> true
247 | | B.Frame { sub = b; _ } -> multiline_heuristic c b
248 | | B.Pad (_, _) -> true
249 | | B.Align { inner; _ } -> multiline_heuristic c inner
250 | | B.Grid (_, [| _ |]) when c.Config.hlists = `As_table -> true
251 | | B.Grid (_, rows) ->
252 | Array.length rows > 1
253 | || Array.exists (Array.exists @@ multiline_heuristic c) rows
254 | | B.Tree (_, header, children) ->
255 | Array.length children > 0 || multiline_heuristic c header
256 | | B.Link { inner; _ } | B.Anchor { inner; _ } -> multiline_heuristic c inner
257 | | B.Ext { key; ext } ->
258 | (match Hashtbl.find_opt extensions key with
259 | | Some handler -> String.contains (handler c ext) '\n'
260 | | None ->
261 | failwith @@ "PrintBox_html.to_html: missing extension handler for " ^ key)
262 |
263 | let rec line_of_length_heuristic_exn c b =
264 | match B.view b with
265 | | B.Empty | B.Text { l = []; _ } -> 0
266 | | B.Text { l = [ s ]; style } ->
267 | let from_bold =
268 | if style.B.Style.bold then
269 | 4
270 | else
271 | 0
272 | in
273 | let from_code =
274 | if style.B.Style.preformatted then
275 | if String.contains s '`' then
276 | 6
277 | else
278 | 2
279 | else
280 | 0
281 | in
282 | if String.contains s '\n' then
283 | raise Not_found
284 | else
285 | String.length s + from_bold + from_code
286 | | B.Text _ -> raise Not_found
287 | | B.Frame _ when c.Config.frames = `As_table -> raise Not_found
288 | | B.Frame { sub = b; _ } ->
289 | (* "> " or "[]" *)
290 | line_of_length_heuristic_exn c b + 2
291 | | B.Pad (_, _) -> raise Not_found
292 | | B.Align { inner; _ } -> line_of_length_heuristic_exn c inner
293 | | B.Grid (_, [||]) | B.Grid (_, [| [||] |]) -> 0
294 | | B.Grid (`None, [| row |]) when c.Config.hlists = `Minimal ->
295 | (* " " *)
296 | ((Array.length row - 1) * 8)
297 | + Array.fold_left ( + ) 0 (Array.map (line_of_length_heuristic_exn c) row)
298 | | B.Grid (`Bars, [| row |]) when c.Config.hlists = `Minimal ->
299 | (* " | " *)
300 | ((Array.length row - 1) * 3)
301 | + Array.fold_left ( + ) 0 (Array.map (line_of_length_heuristic_exn c) row)
302 | | B.Grid _ -> raise Not_found
303 | | B.Tree (_, header, [||]) -> line_of_length_heuristic_exn c header
304 | | B.Tree _ -> raise Not_found
305 | | B.Link { inner; uri } ->
306 | line_of_length_heuristic_exn c inner + String.length uri + 4
307 | | B.Anchor { inner; id } ->
308 | let link_len =
309 | match B.view inner with
310 | | B.Empty -> String.length id + 13
311 | (* *)
312 | | _ -> (2 * String.length id) + 22
313 | (* INNER *)
314 | in
315 | line_of_length_heuristic_exn c inner + link_len
316 | | B.Ext { key; ext } ->
317 | (match Hashtbl.find_opt extensions key with
318 | | Some handler ->
319 | let s = handler c ext in
320 | if String.contains s '\n' then
321 | raise Not_found
322 | else
323 | String.length s
324 | | None -> failwith @@ "PrintBox_md: missing extension handler for " ^ key)
325 |
326 | let is_native_table c rows =
327 | let rec header h =
328 | match B.view h with
329 | | B.Text { l = [ _ ]; style = { B.Style.bold = true; _ } } -> true
330 | | B.Frame { sub = b; _ } -> header b
331 | | _ -> false
332 | in
333 | Array.for_all header rows.(0)
334 | && Array.for_all
335 | (fun row -> Array.for_all (Fun.negate @@ multiline_heuristic c) row)
336 | rows
337 |
338 | let rec remove_bold b =
339 | match B.view b with
340 | | B.Empty | B.Text { l = []; _ } -> B.empty
341 | | B.Text { l; style } -> B.lines_with_style (B.Style.set_bold false style) l
342 | | B.Frame { sub = b; stretch } -> B.frame ~stretch @@ remove_bold b
343 | | B.Pad (pos, b) -> B.pad' ~col:pos.B.x ~lines:pos.B.y @@ remove_bold b
344 | | B.Align { h; v; inner } -> B.align ~h ~v @@ remove_bold inner
345 | | B.Grid _ -> assert false
346 | | B.Tree (_, header, [||]) -> remove_bold header
347 | | B.Tree _ -> assert false
348 | | B.Link { inner; uri } -> B.link ~uri @@ remove_bold inner
349 | | B.Anchor { inner; id } -> B.anchor ~id @@ remove_bold inner
350 | | B.Ext _ -> (* TODO: non-ideal but avoid complexity for now. *) b
351 |
352 | let pp c out b =
353 | let open Format in
354 | (* We cannot use Format for indentation, because we need to insert ">" at the right places. *)
355 | let rec loop ~no_block ~no_md ~prefix b =
356 | let br =
357 | if no_md then
358 | "
"
359 | else
360 | " "
361 | in
362 | match B.view b with
363 | | B.Empty -> ()
364 | | B.Text { l; style } ->
365 | let l = break_lines l in
366 | let multiline = List.length l > 1 in
367 | let sty_pre, sty_post, code_block, code_quote =
368 | style_format c ~no_md ~multiline style
369 | in
370 | let preformat =
371 | pp_string_escaped ~tab_width:c.Config.tab_width ~code_block ~code_quote
372 | ~html:no_md
373 | in
374 | pp_print_string out sty_pre;
375 | if code_block then fprintf out "@,%s```@,%s" prefix prefix;
376 | pp_print_list
377 | ~pp_sep:(fun out () ->
378 | if not code_block then pp_print_string out br;
379 | fprintf out "@,%s" prefix)
380 | preformat out l;
381 | if code_block then fprintf out "@,%s```@,%s" prefix prefix;
382 | pp_print_string out sty_post
383 | | B.Frame { sub = fb; _ } ->
384 | (match c.Config.frames, no_block with
385 | | `As_table, _ ->
386 | let style = B.Style.preformatted in
387 | let l = break_lines [ PrintBox_text.to_string_with ~style:false b ] in
388 | loop ~no_block ~no_md ~prefix (B.lines_with_style style l)
389 | | _, true ->
390 | (* E.g. in a first Markdown table cell, "> " would mess up rendering. *)
391 | fprintf out "[%a]"
392 | (fun _out -> loop ~no_block ~no_md ~prefix:(prefix ^ " "))
393 | fb
394 | | _ ->
395 | fprintf out "> %a"
396 | (fun _out -> loop ~no_block ~no_md ~prefix:(prefix ^ "> "))
397 | fb)
398 | | B.Pad (_, b) ->
399 | (* NOT IMPLEMENTED YET *)
400 | loop ~no_block ~no_md ~prefix b
401 | | B.Align { h = _; v = _; inner } ->
402 | (* NOT IMPLEMENTED YET *)
403 | loop ~no_block ~no_md ~prefix inner
404 | | B.Grid (bars, [| row |])
405 | when c.Config.hlists = `Minimal
406 | && Array.for_all (Fun.negate @@ multiline_heuristic c) row ->
407 | let len = Array.length row in
408 | Array.iteri
409 | (fun i r ->
410 | loop ~no_block:true ~no_md ~prefix r;
411 | if i < len - 1 then
412 | if bars = `Bars then
413 | fprintf out " | "
414 | else
415 | fprintf out " ")
416 | row
417 | | B.Grid (bars, rows)
418 | when c.Config.vlists <> `As_table
419 | && Array.for_all (fun row -> Array.length row = 1) rows ->
420 | let len = Array.length rows in
421 | (match c.Config.vlists with
422 | | `As_table -> assert false
423 | | `List ->
424 | Array.iteri
425 | (fun i r ->
426 | pp_print_string out "- ";
427 | loop ~no_block ~no_md ~prefix:(prefix ^ " ") r.(0);
428 | if i < len - 1 then (
429 | if bars = `Bars then fprintf out "@,%s > ---" prefix;
430 | fprintf out "@,%s" prefix
431 | ))
432 | rows
433 | | `Line_break ->
434 | Array.iteri
435 | (fun i r ->
436 | loop ~no_block ~no_md ~prefix r.(0);
437 | if i < len - 1 then
438 | if bars = `Bars then
439 | fprintf out "%s@,%s> ---@,%s" br prefix prefix
440 | else
441 | fprintf out "%s@,%s@,%s" br prefix prefix)
442 | rows)
443 | | B.Grid (_, [||]) -> ()
444 | | B.Grid (bars, rows) when bars <> `None && is_native_table c rows ->
445 | let n_rows = Array.length rows and n_cols = Array.length rows.(0) in
446 | let lengths =
447 | Array.fold_left
448 | (Array.map2 (fun len b -> max len @@ line_of_length_heuristic_exn c b))
449 | (Array.map (fun b -> line_of_length_heuristic_exn c b - 4) rows.(0))
450 | @@ Array.sub rows 1 (n_rows - 1)
451 | in
452 | Array.iteri
453 | (fun i header ->
454 | let header = remove_bold header in
455 | loop ~no_block:true ~no_md ~prefix:"" header;
456 | if i < n_cols - 1 then (
457 | let len = line_of_length_heuristic_exn c header in
458 | fprintf out "%s|" (String.make (max 0 @@ (lengths.(i) - len)) ' ')
459 | ))
460 | rows.(0);
461 | fprintf out "@,%s" prefix;
462 | Array.iteri
463 | (fun j _ ->
464 | pp_print_string out @@ String.make lengths.(j) '-';
465 | if j < n_cols - 1 then pp_print_char out '|')
466 | rows.(0);
467 | Array.iteri
468 | (fun i row ->
469 | if i > 0 then
470 | Array.iteri
471 | (fun j b ->
472 | loop ~no_block:true ~no_md ~prefix:"" b;
473 | if j < n_cols - 1 then (
474 | let len = line_of_length_heuristic_exn c b in
475 | fprintf out "%s|"
476 | (String.make (max 0 @@ (lengths.(j) - len)) ' ')
477 | ))
478 | row;
479 | if i < n_rows - 1 then fprintf out "@,%s" prefix)
480 | rows
481 | | B.Grid (_, _) ->
482 | let style = B.Style.preformatted in
483 | let l = break_lines [ PrintBox_text.to_string_with ~style:false b ] in
484 | loop ~no_block ~no_md ~prefix (B.lines_with_style style l);
485 | if not no_md then fprintf out "@,%s@,%s" prefix prefix
486 | | B.Tree (_extra_indent, header, [||]) ->
487 | loop ~no_block ~no_md ~prefix header
488 | | B.Tree (extra_indent, header, body) ->
489 | if c.Config.foldable_trees then
490 | fprintf out "%a
@,%s@,%s- "
491 | (fun _out -> loop ~no_block:true ~no_md:true ~prefix)
492 | header prefix prefix
493 | else (
494 | loop ~no_block ~no_md ~prefix header;
495 | fprintf out "@,%s- " prefix
496 | );
497 | let pp_sep out () = fprintf out "@,%s- " prefix in
498 | let subprefix = prefix ^ String.make (2 + extra_indent) ' ' in
499 | pp_print_list ~pp_sep
500 | (fun _out sub -> loop ~no_block ~no_md ~prefix:subprefix sub)
501 | out
502 | @@ Array.to_list body;
503 | (* Note: vlist and tree element separators move to a new line.
504 | A non-html hlist or table as a parent would not produce correct results. *)
505 | if c.Config.foldable_trees then
506 | fprintf out "@,%s@,%s" prefix prefix
507 | | B.Link { uri; inner } ->
508 | pp_print_string out "[";
509 | loop ~no_block:true ~no_md ~prefix:(prefix ^ " ") inner;
510 | fprintf out "](%s)" uri
511 | | B.Anchor { id; inner } ->
512 | (match B.view inner with
513 | | B.Empty -> fprintf out {||} id
514 | | _ -> fprintf out {||} id id);
515 | loop ~no_block:true ~no_md ~prefix:(prefix ^ " ") inner;
516 | pp_print_string out ""
517 | | B.Ext { key; ext } ->
518 | (match Hashtbl.find_opt extensions key with
519 | | Some handler -> pp_print_string out @@ handler c ext
520 | | None ->
521 | failwith @@ "PrintBox_html.to_html: missing extension handler for "
522 | ^ key)
523 | in
524 | pp_open_vbox out 0;
525 | loop ~no_block:false ~no_md:false ~prefix:"" b;
526 | pp_close_box out ()
527 |
528 | let to_string c b = Format.asprintf "%a@." (pp c) b
529 |
--------------------------------------------------------------------------------
/src/printbox-md/PrintBox_md.mli:
--------------------------------------------------------------------------------
1 | (** Output Markdown.
2 |
3 | @since 0.9 *)
4 |
5 | (* This file is free software. See file "license" for more details. *)
6 |
7 | (** {2 Markdown configuration} *)
8 | module Config : sig
9 | type preformatted =
10 | | Code_block
11 | | Code_quote
12 | (** The output option for preformatted-style text, and for outputting tables as text.
13 | - [Code_block]: use Markdown's backquoted-block style: [```], equivalent to HTML's [].
14 | Downside: Markdown's style classes make it extra prominent.
15 | - [Code_quote]: use Markdown's inline code style: single quote [`].
16 | Downside: does not respect whitespace. We double spaces to "· " for indentation. *)
17 |
18 | type t
19 |
20 | val default : t
21 | (** The configuration that leads to more readable Markdown source code. *)
22 |
23 | val uniform : t
24 | (** The configuration that leads to more lightweight and uniform rendering. *)
25 |
26 | val vlists : [ `Line_break | `List | `As_table ] -> t -> t
27 | (** How to output {!PrintBox.vlist} boxes, i.e. single-column grids.
28 | - [`Line_break]: when the {!PrintBox.vlist} has bars, it puts a quoted horizontal rule
29 | ["> ---"] at the bottom of a row, otherwise puts an extra empty line.
30 | It is set in the {!uniform} config.
31 | - [`List]: puts each row as a separate list item; in addition, when the {!PrintBox.vlist}
32 | has bars, it puts a quoted horizontal rule ["> ---"] at the bottom of a row.
33 | It is set in the {!default} config.
34 | - [`As_table] falls back to the general table printing mechanism. *)
35 |
36 | val hlists : [ `Minimal | `As_table ] -> t -> t
37 | (** How to output {!PrintBox.hlist} boxes, i.e. single-row grids, curently only if they fit
38 | in one line.
39 | - [`Minimal] uses spaces and a horizontal bar [" | "] to separate columns.
40 | It is set in the {!default} config.
41 | - [`As_table] falls back to the general table printing mechanism. *)
42 |
43 | val foldable_trees : t -> t
44 | (** Output trees so every node with children is foldable.
45 | Already the case for the {!uniform} config. *)
46 |
47 | val unfolded_trees : t -> t
48 | (** Output trees so every node is just the header followed by a list of children.
49 | Already the case for the {!default} config. *)
50 |
51 | val multiline_preformatted : preformatted -> t -> t
52 | (* How to output multiline preformatted text, including tables when output as text. *)
53 |
54 | val one_line_preformatted : preformatted -> t -> t
55 | (* How to output single-line preformatted text. *)
56 |
57 | val tab_width : int -> t -> t
58 | (* One tab is this many spaces. *)
59 |
60 | val quotation_frames : t -> t
61 | (** Output frames using Markdown's quotation syntax [> ], or surrouding by [[]] if inline.
62 | Already the case for the {!default} config. *)
63 |
64 | val table_frames : t -> t
65 | (** Output frames by falling back to the mechanism used to output tables.
66 | Already the case for the {!uniform} config. *)
67 | end
68 |
69 | val register_extension :
70 | key:string -> (Config.t -> PrintBox.ext -> string) -> unit
71 | (** Add support for the extension with the given key to this rendering backend.
72 | Note: the string returned by the handler can have line breaks. *)
73 |
74 | val pp : Config.t -> Format.formatter -> PrintBox.t -> unit
75 | (** Pretty-print the Markdown source code into this formatter. *)
76 |
77 | val to_string : Config.t -> PrintBox.t -> string
78 | (** A string with the Markdown source code. *)
79 |
--------------------------------------------------------------------------------
/src/printbox-md/README.md:
--------------------------------------------------------------------------------
1 | # PrintBox-md: a Markdown backend for PrintBox
2 |
3 | - [This file was generated by the readme executable.](readme.ml)
4 | - [(Link to the foldable trees example.)](#FoldableTreeAnchor)
5 |
6 | ## Coverage of Markdown and `PrintBox` constructions
7 |
8 | ### Single-line and multiline text
9 |
10 | Multiline text is printed using Markdown's syntax for forced line breaks:
11 | · a pair of trailing whitespace.
12 | · · Line wrapping is not prevented unless the text is styled as preformatted.
13 | · However, we pay attention to whitespace in the text · · -- ··
14 | we don't allow HTML to ignore the spaces.
15 |
16 |
17 | ```
18 | Preformatted text like this one can be output in two different styles:
19 | Code_block and Code_quote.
20 | The style can be changed for both multiline and single-line text.
21 | ```
22 |
23 |
24 | `So it is possible to use [Code_quote] even with multiline text,`
25 | `· which leads to a contrasting visual effect.`
26 | `· · Since Markdown's code quotes would otherwise · · ignore whitespace,`
27 | `· we use our trick to preserve --> · · · · · · · · · these spaces.`
28 |
29 | ### Horizontal boxes i.e. `PrintBox.hlist`
30 |
31 | The `` `Minimal `` style for horizontal boxes simply puts all entries on a line, separated by extra spaces,
32 |
33 | or if \`Bars are set, | by the | vertical dash.
34 |
35 |
36 | ```
37 | It only works when │logically speaking,│on a single line.
38 | all the elements fit│ │
39 | ```
40 |
41 |
42 |
43 |
44 | `` Otherwise, the fallback behavior is as if`As_tablewas used to configure horizontal boxes. ``
45 |
46 |
47 |
48 | ### Vertical boxes i.e. `PrintBox.vlist`
49 |
50 | - Vertical boxes can be configured in three ways:
51 | - `` `Line_break `` which simply adds an empty line after each entry
52 | - `` `List `` which lists the entries
53 | - and the fallback we saw already, `` `As_table ``
54 |
55 | Vertical boxes with bars
56 | > ---
57 | `(vlist ~bars:true)` use a quoted horizontal ruler
58 | > ---
59 | to separate the entries (here with style \`Line_break).
60 |
61 | ### Frames
62 |
63 | > Frames use quotation to make their content prominent
64 | > > ---
65 | > except when in a non-block position [then they use] square brackets
66 | > > ---
67 | > (which also helps with conciseness).
68 |
69 |
70 | ```
71 | ┌─────────────────────────────────────────────────────────────────┐
72 | │There is also a fallback │
73 | ├─────────────────────────────────────────────────────────────────┤
74 | │which generates all┌─────────────┐the same approach as for tables│
75 | │ │frames, using│ │
76 | │ └─────────────┘ │
77 | └─────────────────────────────────────────────────────────────────┘
78 | ```
79 |
80 |
81 | - This even works OK-ish
82 | - when the frame
83 | -
84 | ```
85 | ┌─────────┐
86 | │is nested│
87 | └─────────┘
88 | ```
89 |
90 | - inside Markdown.
91 |
92 | ### Trees
93 |
94 | Trees are rendered as:
95 | - The head element
96 | - > followed by
97 | - a list of the child elements.
98 |
99 | Trees can be made foldable:
100 |
101 | - The head element
102 | - > is the summary
103 | - and the children...
104 |
105 | - **are the details.**
106 |
107 |
108 |
109 |
110 |
111 | ### Tables
112 |
113 | There is a special case carved out for Markdown syntax tables.
114 |
115 | Header|cells |[must be] |bold.
116 | ------|---------|----------|-----------------
117 | Rows |[must be]|single |line.
118 | [Only]|then |**we get**|a Markdown table.
119 |
120 |
121 | ```
122 | Tables │that meet │┌───────┐ │of:
123 | │ ││neither│ │
124 | │ │└───────┘ │
125 | ───────────────────┼─────────────┼──────────────┼─────────────
126 | ┌─────────────────┐│restrictions,│special cases:│hlist
127 | │Markdown's native││ │ │vlist
128 | └─────────────────┘│ │ │
129 | ───────────────────┼─────────────┼──────────────┼─────────────
130 | End up │as │the fallback: │printbox-text
131 | ```
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/src/printbox-md/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name printbox_md)
3 | (public_name printbox-md)
4 | (wrapped false)
5 | (modules PrintBox_md)
6 | (flags :standard -w +a-3-4-44-29 -safe-string)
7 | (libraries printbox printbox-text))
8 |
9 | (executable
10 | (name readme)
11 | (modules readme)
12 | (libraries printbox printbox-md))
13 |
14 | (rule
15 | (alias runtest)
16 | (target README.md)
17 | (mode (promote))
18 | (action
19 | (with-outputs-to
20 | %{target}
21 | (run %{dep:readme.exe}))))
22 |
--------------------------------------------------------------------------------
/src/printbox-md/readme.ml:
--------------------------------------------------------------------------------
1 | (* This file is free software. See file "license" for more details. *)
2 |
3 | module B = PrintBox
4 | module MD = PrintBox_md
5 |
6 | let () = print_endline {|# PrintBox-md: a Markdown backend for PrintBox
7 | |}
8 |
9 | let () =
10 | print_endline
11 | MD.(
12 | to_string Config.default
13 | @@ B.vlist ~bars:false
14 | B.
15 | [
16 | link ~uri:"readme.ml"
17 | @@ line "This file was generated by the readme executable.";
18 | link ~uri:"#FoldableTreeAnchor"
19 | @@ line "(Link to the foldable trees example.)";
20 | ])
21 |
22 | let () =
23 | print_endline
24 | {|## Coverage of Markdown and `PrintBox` constructions
25 |
26 | ### Single-line and multiline text
27 | |}
28 |
29 | let () =
30 | print_endline
31 | MD.(
32 | to_string Config.default
33 | @@ B.lines
34 | [
35 | "Multiline text is printed using Markdown's syntax for forced \
36 | line breaks:";
37 | " a pair of trailing whitespace.";
38 | " Line wrapping is not prevented unless the text is styled as \
39 | preformatted.";
40 | " However, we pay attention to whitespace in the text -- ";
41 | "we don't allow HTML to ignore the spaces.";
42 | ])
43 |
44 | let () =
45 | print_endline
46 | MD.(
47 | to_string Config.default
48 | @@ B.lines_with_style B.Style.preformatted
49 | [
50 | "Preformatted text like this one can be output in two different \
51 | styles:";
52 | " Code_block and Code_quote.";
53 | " The style can be changed for both multiline and single-line \
54 | text.";
55 | ])
56 |
57 | let () =
58 | print_endline
59 | MD.(
60 | to_string Config.(multiline_preformatted Code_quote default)
61 | @@ B.lines_with_style B.Style.preformatted
62 | [
63 | "So it is possible to use [Code_quote] even with multiline text,";
64 | " which leads to a contrasting visual effect.";
65 | " Since Markdown's code quotes would otherwise ignore \
66 | whitespace,";
67 | " we use our trick to preserve --> these \
68 | spaces.";
69 | ])
70 |
71 | let () = print_endline {|### Horizontal boxes i.e. `PrintBox.hlist`
72 | |}
73 |
74 | let () =
75 | print_endline
76 | MD.(
77 | to_string Config.default
78 | @@ B.(
79 | hlist ~bars:false
80 | [
81 | line "The";
82 | line_with_style Style.preformatted "`Minimal";
83 | line
84 | "style for horizontal boxes simply puts all entries on a \
85 | line, ";
86 | line "separated by extra spaces,";
87 | ]))
88 |
89 | let () =
90 | print_endline
91 | MD.(
92 | to_string Config.default
93 | @@ B.(
94 | hlist ~bars:true
95 | [
96 | line "or if `Bars are set,";
97 | line " by the";
98 | line "vertical dash.";
99 | ]))
100 |
101 | let () =
102 | print_endline
103 | MD.(
104 | to_string Config.default
105 | @@ B.(
106 | hlist ~bars:true
107 | [
108 | lines [ "It only works when"; "all the elements fit" ];
109 | line "logically speaking,";
110 | line_with_style Style.bold "on a single line.";
111 | ]))
112 |
113 | let () =
114 | print_endline
115 | MD.(
116 | to_string Config.(hlists `As_table default)
117 | @@ B.(
118 | hlist ~bars:false
119 | [
120 | line "Otherwise, the fallback behavior is as if";
121 | line_with_style Style.preformatted "`As_table";
122 | line "was used to configure horizontal boxes.";
123 | ]))
124 |
125 | let () = print_endline {|### Vertical boxes i.e. `PrintBox.vlist`
126 | |}
127 |
128 | let () =
129 | print_endline
130 | MD.(
131 | to_string Config.(vlists `List default)
132 | @@ B.(
133 | vlist ~bars:false
134 | [
135 | line "Vertical boxes can be configured in three ways:";
136 | hlist ~bars:false
137 | [
138 | line_with_style Style.preformatted "`Line_break";
139 | line "which simply adds an empty line after each entry";
140 | ];
141 | hlist ~bars:false
142 | [
143 | line_with_style Style.preformatted "`List";
144 | line "which lists the entries";
145 | ];
146 | hlist ~bars:false
147 | [
148 | line "and the fallback we saw already,";
149 | line_with_style Style.preformatted "`As_table";
150 | ];
151 | ]))
152 |
153 | let () =
154 | print_endline
155 | MD.(
156 | to_string Config.(vlists `Line_break default)
157 | @@ B.(
158 | vlist ~bars:true
159 | [
160 | line "Vertical boxes with bars";
161 | hlist ~bars:false
162 | [
163 | line_with_style Style.preformatted "(vlist ~bars:true)";
164 | line "use a quoted horizontal ruler";
165 | ];
166 | line "to separate the entries (here with style `Line_break).";
167 | ]))
168 |
169 | let () = print_endline {|### Frames
170 | |}
171 |
172 | let () =
173 | print_endline
174 | MD.(
175 | to_string Config.(vlists `Line_break default)
176 | @@ B.(
177 | frame
178 | @@ vlist ~bars:true
179 | [
180 | line "Frames use quotation to make their content prominent";
181 | hlist ~bars:false
182 | [
183 | line "except when in a non-block position";
184 | frame @@ line "then they use";
185 | line "square brackets";
186 | ];
187 | line "(which also helps with conciseness).";
188 | ]))
189 |
190 | let () =
191 | print_endline
192 | MD.(
193 | to_string Config.(table_frames @@ vlists `Line_break default)
194 | @@ B.(
195 | frame
196 | @@ vlist ~bars:true
197 | [
198 | line "There is also a fallback";
199 | hlist ~bars:false
200 | [
201 | line "which generates all";
202 | frame @@ line "frames, using";
203 | line "the same approach as for tables";
204 | ];
205 | ]))
206 |
207 | let () =
208 | print_endline
209 | MD.(
210 | to_string Config.(table_frames @@ vlists `List default)
211 | @@ B.(
212 | vlist ~bars:false
213 | [
214 | line "This even works OK-ish";
215 | line "when the frame";
216 | frame @@ line "is nested";
217 | line "inside Markdown.";
218 | ]))
219 |
220 | let () = print_endline {|### Trees
221 | |}
222 |
223 | let () =
224 | print_endline
225 | MD.(
226 | to_string Config.default
227 | @@ B.(
228 | tree
229 | (hlist ~bars:false
230 | [
231 | anchor ~id:"TreeAnchor" @@ line "Trees";
232 | line "are rendered as:";
233 | ])
234 | [
235 | line "The head element";
236 | frame @@ line "followed by";
237 | line "a list of the child elements.";
238 | ]))
239 |
240 | let () =
241 | print_endline
242 | MD.(
243 | to_string Config.(foldable_trees default)
244 | @@ B.(
245 | tree
246 | (hlist ~bars:false
247 | [
248 | anchor ~id:"FoldableTreeAnchor" @@ empty;
249 | line "Trees can be made foldable:";
250 | ])
251 | [
252 | line "The head element";
253 | frame @@ line "is the summary";
254 | tree
255 | (line "and the children...")
256 | [ line_with_style Style.bold "are the details." ];
257 | ]))
258 |
259 | let () =
260 | print_endline
261 | {|### Tables
262 |
263 | There is a special case carved out for Markdown syntax tables.
264 | |}
265 |
266 | let () =
267 | print_endline
268 | MD.(
269 | to_string Config.default
270 | @@ B.(
271 | let bold = text_with_style Style.bold in
272 | grid_l
273 | [
274 | [
275 | bold "Header";
276 | bold "cells";
277 | frame @@ bold "must be";
278 | bold "bold.";
279 | ];
280 | [
281 | line "Rows";
282 | frame @@ line "must be";
283 | line "single";
284 | line "line.";
285 | ];
286 | [
287 | frame @@ line "Only";
288 | line "then";
289 | bold "we get";
290 | line "a Markdown table.";
291 | ];
292 | ]))
293 |
294 | let () =
295 | print_endline
296 | MD.(
297 | to_string Config.default
298 | @@ B.(
299 | let bold = text_with_style Style.bold in
300 | let code = text_with_style Style.preformatted in
301 | grid_l
302 | [
303 | [
304 | bold "Tables";
305 | bold "that meet";
306 | frame @@ bold "neither";
307 | bold "of:";
308 | ];
309 | [
310 | frame @@ bold "Markdown's native";
311 | line "restrictions,";
312 | line "special cases:";
313 | code "hlist\nvlist";
314 | ];
315 | [
316 | line "End up";
317 | line "as";
318 | line "the fallback:";
319 | code "printbox-text";
320 | ];
321 | ]))
322 |
--------------------------------------------------------------------------------
/src/printbox-text/PrintBox_text.mli:
--------------------------------------------------------------------------------
1 | (* This file is free software. See file "license" for more details. *)
2 |
3 | (** {1 Render to Text}
4 |
5 | This module should be used to output boxes directly to a terminal, or
6 | another area of monospace text *)
7 |
8 | val register_extension :
9 | key:string -> (style:bool -> PrintBox.ext -> string) -> unit
10 | (** Add support for the extension with the given key to this rendering backend.
11 | If [style = true], the extension can use ANSI codes for styling.
12 | Note: the string returned by the handler can have line breaks. *)
13 |
14 | val set_string_len : (String.t -> int -> int -> int) -> unit
15 | (** Set which function is used to compute string length. Typically
16 | to be used with a unicode-sensitive length function.
17 | An example of such a function for utf8 encoded strings is the following
18 | (it uses the [Uutf] and [Uucp] libraries):
19 | {[
20 | let string_leng s i len =
21 | Uutf.String.fold_utf_8 ~pos:i ~len
22 | (fun n _ c -> n+ max 0 (Uucp.Break.tty_width_hint c)) 0 s
23 | ]}
24 | Note that this function assumes there is no newline character in the given string.
25 |
26 | @since 0.3, this is also used in [printbox_unicode] to basically install the code above
27 | *)
28 |
29 | val to_string : PrintBox.t -> string
30 | (** Returns a string representation of the given structure.
31 | @param style if true, emit ANSI codes for styling (default true) (@since 0.3) *)
32 |
33 | val to_string_with : style:bool -> PrintBox.t -> string
34 | (** Returns a string representation of the given structure, with style.
35 | @param style if true, emit ANSI codes for styling
36 | @since 0.3
37 | *)
38 |
39 | val output : ?style:bool -> ?indent:int -> out_channel -> PrintBox.t -> unit
40 | (** Outputs the given structure on the channel.
41 | @param indent initial indentation to use
42 | @param style if true, emit ANSI codes for styling (default true) (@since 0.3)
43 | *)
44 |
45 | val pp : Format.formatter -> PrintBox.t -> unit
46 | (** Pretty-print the box into this formatter.
47 | @since 0.2 *)
48 |
49 | val pp_with : style:bool -> Format.formatter -> PrintBox.t -> unit
50 | (** Pretty-print the box into this formatter, with style.
51 | @param style if true, emit ANSI codes for styling
52 | @since 0.3
53 | *)
54 |
55 | (** {2 Support for Representation Extensions} *)
56 |
57 | val str_display_width : String.t -> int -> int -> int
58 | (** [str_display_width s pos len] computes the width in visible characters
59 | of the string [s] starting at string position [pos] and stopping right before [pos + len].
60 | See {!set_string_len}.
61 | @since 0.12
62 | *)
63 |
--------------------------------------------------------------------------------
/src/printbox-text/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name printbox_text)
3 | (public_name printbox-text)
4 | (wrapped false)
5 | (flags :standard -w +a-3-4-44-29 -safe-string)
6 | (libraries printbox uutf uucp))
7 |
--------------------------------------------------------------------------------
/test/dune:
--------------------------------------------------------------------------------
1 | (env
2 | (_
3 | (flags :standard -warn-error -a)))
4 |
5 | (test
6 | (name test_ann_0_3)
7 | (modules test_ann_0_3)
8 | (package printbox-text)
9 | (libraries printbox printbox-text))
10 |
11 | (test
12 | (name test1)
13 | (modules test1)
14 | (package printbox-text)
15 | (libraries printbox printbox-text))
16 |
17 | (test
18 | (name test_blending)
19 | (modules test_blending)
20 | (package printbox-text)
21 | (libraries printbox printbox-text))
22 |
23 | (test
24 | (name test_text_uri)
25 | (modules test_text_uri)
26 | (package printbox-text)
27 | (libraries printbox printbox-text))
28 |
29 | (test
30 | (name test_html)
31 | (modules test_html)
32 | (package printbox-html)
33 | (libraries printbox printbox-html))
34 |
35 | (test
36 | (name test_md)
37 | (modules test_md)
38 | (package printbox-md)
39 | (libraries printbox printbox-md))
40 |
41 | (rule
42 | (alias runtest)
43 | (deps test_md.expected)
44 | (targets test_md.expected.md)
45 | (mode promote)
46 | (action
47 | (copy %{deps} %{targets})))
48 |
49 | (test
50 | (name reg_45)
51 | (modules reg_45)
52 | (package printbox-text)
53 | (libraries printbox printbox-text))
54 |
55 | (test
56 | (name extend_md)
57 | (modules extend_md)
58 | (package printbox-md)
59 | (libraries printbox printbox-md))
60 |
61 | (test
62 | (name extend_html_specific)
63 | (modules extend_html_specific)
64 | (package printbox-html)
65 | (libraries printbox tyxml printbox-html))
66 |
67 | (test
68 | (name plotting)
69 | (modules plotting)
70 | (package printbox-ext-plot)
71 | (libraries printbox printbox-ext-plot printbox-text printbox-html))
72 |
73 | (test
74 | (name plotting_half_moons)
75 | (modules plotting_half_moons)
76 | (package printbox-ext-plot)
77 | (libraries printbox printbox-ext-plot printbox-text printbox-html))
78 |
79 | (test
80 | (name plotting_linear)
81 | (modules plotting_linear)
82 | (package printbox-ext-plot)
83 | (libraries printbox printbox-ext-plot printbox-text printbox-html))
84 |
85 | (test
86 | (name plotting_nested)
87 | (modules plotting_nested)
88 | (package printbox-ext-plot)
89 | (libraries printbox printbox-ext-plot printbox-text printbox-html))
90 |
--------------------------------------------------------------------------------
/test/extend_html_specific.expected:
--------------------------------------------------------------------------------
1 |
2 | HTML output simple:
3 |
4 |
5 |
6 | HTML output with header:
7 | Greetings!
8 |
9 |
10 | HTML output with details and summary:
11 | Greetings!
Greetings!
12 |
13 |
--------------------------------------------------------------------------------
/test/extend_html_specific.ml:
--------------------------------------------------------------------------------
1 | open Tyxml
2 | module B = PrintBox
3 | module H = Html
4 |
5 | (* This tests extensions with HTML-specific support (as well as text backend support). *)
6 |
7 | type B.ext += Hello_html of string | Hello_with_header of string
8 |
9 | let html_handler config ext =
10 | match ext with
11 | | Hello_html txt -> (H.button [ H.txt txt ] :> PrintBox_html.toplevel_html)
12 | | Hello_with_header txt ->
13 | let result = H.button [ H.txt txt ] in
14 | (PrintBox_html.to_html ~config
15 | B.(
16 | tree (text "Greetings!")
17 | [ B.extension ~key:"Embed_html" (PrintBox_html.Embed_html result) ])
18 | :> PrintBox_html.toplevel_html)
19 | | _ -> invalid_arg "html_handler: unknown extension"
20 |
21 | let summary_handler config ext =
22 | match ext with
23 | | Hello_html txt -> (H.button [ H.txt txt ] :> PrintBox_html.summary_html)
24 | | Hello_with_header txt ->
25 | let result = H.button [ H.txt txt ] in
26 | (PrintBox_html.to_summary_html ~config
27 | B.(
28 | hlist ~bars:true [text "Greetings!"
29 | ; B.extension ~key:"Embed_summary_html" (PrintBox_html.Embed_summary_html result) ])
30 | :> PrintBox_html.summary_html)
31 | | _ -> invalid_arg "html_handler: unknown extension"
32 |
33 | let () =
34 | PrintBox_html.register_extension ~key:"Hello_world" html_handler;
35 | PrintBox_html.register_summary_extension ~key:"Hello_world" summary_handler
36 |
37 | let test1 = B.extension ~key:"Hello_world" @@ Hello_html "Hello world!"
38 |
39 | let test2 =
40 | B.extension ~key:"Hello_world" @@ Hello_with_header "Hello wide world!"
41 |
42 | let test3 = B.tree test2 [test1; test2]
43 |
44 | let () =
45 | print_endline "\nHTML output simple:";
46 | print_endline @@ PrintBox_html.to_string test1;
47 | print_endline "\nHTML output with header:";
48 | print_endline
49 | @@ PrintBox_html.(to_string ~config:Config.(tree_summary true default) test2);
50 | print_endline "\nHTML output with details and summary:";
51 | print_endline
52 | @@ PrintBox_html.(to_string ~config:Config.(tree_summary true default) test3)
53 |
--------------------------------------------------------------------------------
/test/extend_md.expected:
--------------------------------------------------------------------------------
1 |
2 | Markdown output:
3 | > Hello world!
4 |
5 |
--------------------------------------------------------------------------------
/test/extend_md.ml:
--------------------------------------------------------------------------------
1 | module B = PrintBox
2 |
3 | (* This tests that all backends support extensions that expand into boxes. *)
4 |
5 | type B.ext += Hello_world of string
6 |
7 | let md_handler config ext =
8 | match ext with
9 | | Hello_world txt ->
10 | String.trim PrintBox_md.(to_string config (B.frame @@ B.text txt))
11 | | _ -> invalid_arg "text_handler: unrecognized extension"
12 |
13 | let () = PrintBox_md.register_extension ~key:"Hello_world" md_handler
14 | let test = B.extension ~key:"Hello_world" @@ Hello_world "Hello world!"
15 |
16 | let () =
17 | print_endline "\nMarkdown output:";
18 | print_endline @@ PrintBox_md.(to_string Config.default test)
19 |
--------------------------------------------------------------------------------
/test/plotting.ml:
--------------------------------------------------------------------------------
1 | module B = PrintBox
2 | module BPlot = PrintBox_ext_plot
3 |
4 | (* This tests the printbox-ext-plot extension. *)
5 |
6 | let test ~size =
7 | BPlot.(
8 | box
9 | {
10 | default_config with
11 | size;
12 | specs =
13 | [
14 | Scatterbag
15 | {
16 | points =
17 | [|
18 | (0., 1.), B.line "Y";
19 | (1., 0.), B.line "X";
20 | (0.75, 0.75), B.line "M";
21 | |];
22 | };
23 | Map
24 | {
25 | callback =
26 | (fun (x, y) ->
27 | let s = ((x ** 2.) +. (y ** 2.)) ** 0.5 in
28 | B.line
29 | @@
30 | if s < 0.3 then
31 | " "
32 | else if s < 0.6 then
33 | "."
34 | else if s < 0.9 then
35 | ","
36 | else if s < 1.2 then
37 | ":"
38 | else
39 | ";");
40 | };
41 | ];
42 | })
43 |
44 | let () =
45 | print_endline "Text output:";
46 | print_endline @@ PrintBox_text.to_string
47 | @@ test ~size:BPlot.default_config.size;
48 | print_endline "\nHTML output:";
49 | print_endline @@ PrintBox_html.to_string @@ test ~size:(800, 800)
50 |
--------------------------------------------------------------------------------
/test/plotting_half_moons.ml:
--------------------------------------------------------------------------------
1 | module B = PrintBox
2 | module BPlot = PrintBox_ext_plot
3 |
4 | module Rand = struct
5 | let rand = ref (1l : Int32.t)
6 |
7 | let rand_int32 () =
8 | let open Int32 in
9 | rand := logxor !rand @@ shift_left !rand 13;
10 | rand := logxor !rand @@ shift_right_logical !rand 17;
11 | rand := logxor !rand @@ shift_left !rand 5;
12 | !rand
13 |
14 | let float_range low high =
15 | let raw = Int32.(to_float @@ rem (rand_int32 ()) 10000l) in
16 | (raw /. 10000. *. (high -. low)) +. low
17 |
18 | let int high = Int32.(to_int @@ rem (rand_int32 ()) (of_int high))
19 | end
20 |
21 | let noise () = Rand.float_range (-0.1) 0.1
22 |
23 | (* [Array.split] only available since OCaml 4.12. *)
24 | let array_split x =
25 | let a0, b0 = x.(0) in
26 | let n = Array.length x in
27 | let a = Array.make n a0 in
28 | let b = Array.make n b0 in
29 | for i = 1 to n - 1 do
30 | let ai, bi = x.(i) in
31 | a.(i) <- ai;
32 | b.(i) <- bi
33 | done;
34 | a, b
35 |
36 | (* This tests the printbox-ext-plot extension. *)
37 | let moons data_len =
38 | let npairs = data_len / 2 in
39 | array_split
40 | (Array.init npairs (fun _pos ->
41 | let i = Rand.int npairs in
42 | let v = Float.(of_int (i / 2) *. pi /. of_int npairs) in
43 | let c = cos v and s = sin v in
44 | ( (s +. noise (), c +. noise ()),
45 | (1.0 -. s +. noise (), 0.5 -. c +. noise ()) )))
46 |
47 | let points1, points2 = moons 1024
48 | let callback (x, y) = Float.compare x y > 0
49 |
50 | let test =
51 | BPlot.(
52 | box
53 | {
54 | default_config with
55 | specs =
56 | [
57 | Scatterplot { points = points1; content = B.line "#" };
58 | Scatterplot { points = points2; content = B.line "%" };
59 | Boundary_map
60 | {
61 | content_false = B.line ".";
62 | content_true = B.line ",";
63 | callback;
64 | };
65 | ];
66 | })
67 |
68 | let () =
69 | print_endline "Text output:";
70 | print_endline @@ PrintBox_text.to_string test;
71 | print_endline "\nHTML output:";
72 | print_endline @@ PrintBox_html.to_string test
73 |
--------------------------------------------------------------------------------
/test/plotting_linear.ml:
--------------------------------------------------------------------------------
1 | module B = PrintBox
2 | module BPlot = PrintBox_ext_plot
3 |
4 | (* This tests the printbox-ext-plot extension. *)
5 |
6 | let test =
7 | BPlot.(
8 | box
9 | {
10 | default_config with
11 | specs =
12 | [
13 | Line_plot_adaptive
14 | {
15 | callback = (fun x -> sin x);
16 | content = B.line "#";
17 | cache = Hashtbl.create 20;
18 | };
19 | Line_plot_adaptive
20 | {
21 | callback = (fun x -> x ** 2.);
22 | content = B.line "%";
23 | cache = Hashtbl.create 20;
24 | };
25 | Boundary_map
26 | {
27 | content_false = B.line ".";
28 | content_true = B.line ",";
29 | callback = (fun (x, y) -> x > y);
30 | };
31 | ];
32 | })
33 |
34 | let () =
35 | print_endline "Text output:";
36 | print_endline @@ PrintBox_text.to_string test;
37 | print_endline "\nHTML output:";
38 | print_endline @@ PrintBox_html.to_string test
39 |
--------------------------------------------------------------------------------
/test/plotting_nested.ml:
--------------------------------------------------------------------------------
1 | module B = PrintBox
2 | module BPlot = PrintBox_ext_plot
3 |
4 | (* This tests the printbox-ext-plot extension. *)
5 |
6 | let reg_45 =
7 | B.(
8 | frame
9 | (vlist
10 | [
11 | text "123456789";
12 | frame ~stretch:true
13 | (vlist [ text "...." |> align ~h:`Right ~v:`Top; text "." ]);
14 | ]))
15 |
16 | let for_3 =
17 | let n = 3 in
18 | Array.init n (fun i -> Array.init n (fun j -> B.sprintf "(%d,%d)" i j))
19 | |> B.grid
20 |
21 | let nice_unicode =
22 | B.(
23 | frame
24 | @@ vlist
25 | [
26 | text "nice unicode! 💪";
27 | frame
28 | @@ hlist
29 | [
30 | vlist
31 | [
32 | text "oï ωεird nums:\nπ/2\nτ/4";
33 | center_hv
34 | @@ tree (text "0")
35 | [ text "1"; tree (text "ω") [ text "ω²" ] ];
36 | ];
37 | frame @@ frame @@ frame
38 | @@ vlist
39 | [
40 | text "sum=Σ_i a·xᵢ²\n—————\n1+1";
41 | align_right @@ text "Ōₒ\nÀ";
42 | ];
43 | ];
44 | ])
45 |
46 | let test ~size =
47 | BPlot.(
48 | box
49 | {
50 | default_config with
51 | size;
52 | specs =
53 | [
54 | Scatterbag
55 | { points = [| (0.06, 0.95), reg_45; (0.3, 0.3), reg_45 |] };
56 | Scatterbag
57 | {
58 | points =
59 | [|
60 | (0., 1.), nice_unicode;
61 | (0.08, 0.9), nice_unicode;
62 | (1., 0.), for_3;
63 | (0.3, 0.3), nice_unicode;
64 | (0.75, 0.75), nice_unicode;
65 | (0.8, 0.8), nice_unicode;
66 | |];
67 | };
68 | Map
69 | {
70 | callback =
71 | (fun (x, y) ->
72 | let s = ((x ** 2.) +. (y ** 2.)) ** 0.5 in
73 | B.line
74 | @@
75 | if s < 0.3 then
76 | " "
77 | else if s < 0.6 then
78 | "."
79 | else if s < 0.9 then
80 | ","
81 | else if s < 1.2 then
82 | ":"
83 | else
84 | ";");
85 | };
86 | ];
87 | })
88 |
89 | let () =
90 | print_endline "Text output:";
91 | print_endline @@ PrintBox_text.to_string
92 | @@ test ~size:BPlot.default_config.size;
93 | print_endline "\nHTML output:";
94 | print_endline @@ PrintBox_html.to_string @@ test ~size:(800, 800)
95 |
--------------------------------------------------------------------------------
/test/reg_45.expected:
--------------------------------------------------------------------------------
1 | ┌─────────┐
2 | │123456789│
3 | ├─────────┤
4 | │┌─┐ │
5 | ││.│ │
6 | │├─┤ │
7 | ││.│ │
8 | │└─┘ │
9 | └─────────┘
10 | ┌─────────┐
11 | │123456789│
12 | ├─────────┤
13 | │┌────┐ │
14 | ││....│ │
15 | │├────┤ │
16 | ││. │ │
17 | │└────┘ │
18 | └─────────┘
19 | ┌─────────┐
20 | │123456789│
21 | ├─────────┤
22 | │┌───────┐│
23 | ││ .││
24 | │├───────┤│
25 | ││. ││
26 | │└───────┘│
27 | └─────────┘
28 | ┌─────────┐
29 | │123456789│
30 | ├─────────┤
31 | │┌───────┐│
32 | ││ ....││
33 | │├───────┤│
34 | ││. ││
35 | │└───────┘│
36 | └─────────┘
37 |
--------------------------------------------------------------------------------
/test/reg_45.ml:
--------------------------------------------------------------------------------
1 | let () =
2 | PrintBox.(
3 | PrintBox_text.output stdout
4 | @@ frame
5 | (vlist
6 | [
7 | text "123456789";
8 | frame (vlist [ text "." |> align ~h:`Right ~v:`Top; text "." ]);
9 | ]));
10 | print_endline ""
11 |
12 | let () =
13 | PrintBox.(
14 | PrintBox_text.output stdout
15 | @@ frame
16 | (vlist
17 | [
18 | text "123456789";
19 | frame (vlist [ text "...." |> align ~h:`Right ~v:`Top; text "." ]);
20 | ]));
21 | print_endline ""
22 |
23 | (* now with stretch *)
24 |
25 | let () =
26 | PrintBox.(
27 | PrintBox_text.output stdout
28 | @@ frame
29 | (vlist
30 | [
31 | text "123456789";
32 | frame ~stretch:true
33 | (vlist [ text "." |> align ~h:`Right ~v:`Top; text "." ]);
34 | ]));
35 | print_endline ""
36 |
37 | let () =
38 | PrintBox.(
39 | PrintBox_text.output stdout
40 | @@ frame
41 | (vlist
42 | [
43 | text "123456789";
44 | frame ~stretch:true
45 | (vlist [ text "...." |> align ~h:`Right ~v:`Top; text "." ]);
46 | ]));
47 | print_endline ""
48 |
--------------------------------------------------------------------------------
/test/test1.ml:
--------------------------------------------------------------------------------
1 | module B = PrintBox
2 |
3 | (* make a square *)
4 | let square n =
5 | Array.init n (fun i -> Array.init n (fun j -> B.sprintf "(%d,%d)" i j))
6 | |> B.grid
7 |
8 | let () =
9 | for i = 1 to 20 do
10 | Printf.printf "for %d:\n%a\n\n" i
11 | (PrintBox_text.output ~style:true ?indent:None)
12 | (square i)
13 | done
14 |
15 | let tree =
16 | B.tree (B.text "root")
17 | [
18 | B.tree (B.text "a") [ B.text "a1\na1"; B.text "a2\na2\na2" ];
19 | B.tree (B.text "b") [ B.text "b1\nb1"; B.text "b2"; B.text "b3" ];
20 | ]
21 |
22 | let () = PrintBox_text.output stdout tree
23 | let () = Printf.printf "\n\n"
24 |
25 | let grid =
26 | B.frame
27 | @@ B.grid_l
28 | [
29 | [ B.text "the center of the triangle is"; B.empty ];
30 | [
31 | B.center_hv @@ B.text "lil' ol' me";
32 | B.pad' ~col:0 ~lines:6 @@ B.text "t\na\nl\nl";
33 | ];
34 | [ B.align_right (B.text "i'm aligned right"); B.empty ];
35 | [ B.text "loooooooooooooooooooooooooooooooooong"; B.empty ];
36 | ]
37 |
38 | let () =
39 | PrintBox_text.output stdout grid;
40 | print_endline ""
41 |
42 | let b2 =
43 | PrintBox.(
44 | let style = Style.(fg_color Red) in
45 | frame
46 | @@ grid_l
47 | [
48 | [
49 | text_with_style style "a\nb";
50 | line_with_style Style.(set_bold true @@ bg_color Green) "OH!";
51 | ];
52 | [ text "c"; text "ballot" ];
53 | ])
54 |
55 | let () =
56 | PrintBox_text.output stdout b2;
57 | Printf.printf "\n\n"
58 |
59 | let grid2 =
60 | B.frame
61 | @@ B.record ~pad:B.align_right [ "name1", B.int 1; "foo", B.bool true ]
62 |
63 | let () =
64 | PrintBox_text.output stdout grid2;
65 | print_endline ""
66 |
67 | let grid3 =
68 | B.frame
69 | @@ B.v_record ~pad:B.center_h
70 | [ "name_int_long", B.int 1; "foo", B.bool true; "bar!", B.int 42 ]
71 |
72 | let () =
73 | PrintBox_text.output stdout grid3;
74 | print_endline ""
75 |
76 | module Box_in = struct
77 | let b =
78 | let open B in
79 | frame
80 | @@ grid_l
81 | [
82 | [ text "a"; text "looooooooooooooooooooooooo\noonng" ];
83 | [
84 | text "bx"; center_hv @@ frame @@ record [ "x", int 1; "y", int 2 ];
85 | ];
86 | [
87 | pad' ~col:2 ~lines:2 @@ text "?";
88 | center_hv @@ record [ "x", int 10; "y", int 20 ];
89 | ];
90 | ]
91 |
92 | let () = print_endline @@ PrintBox_text.to_string b
93 | end
94 |
95 | let _b =
96 | let open PrintBox in
97 | frame
98 | @@ record
99 | [
100 | "subject", text_with_style Style.bold "announce: printbox 0.3";
101 | ( "explanation",
102 | frame
103 | @@ text
104 | {|PrintBox is a library for rendering nested tables,
105 | trees, and similar structures in monospace text or HTML.|}
106 | );
107 | ( "github",
108 | text_with_style
109 | Style.(bg_color Blue)
110 | "https://github.com/c-cube/printbox/releases/tag/0.3" );
111 | ( "contributors",
112 | vlist_map
113 | (text_with_style Style.(fg_color Green))
114 | [ "Simon"; "Guillaume"; "Matt" ] );
115 | ( "dependencies",
116 | tree empty
117 | [
118 | tree (text "mandatory") [ text "dune"; text "bytes" ];
119 | tree (text "optional") [ text "uutf"; text "uucp"; text "tyxml" ];
120 | ] );
121 | "expected reaction", text "🎉";
122 | ]
123 |
124 | module Unicode = struct
125 | let b =
126 | B.(
127 | frame
128 | @@ vlist
129 | [
130 | text "nice unicode! 💪";
131 | frame
132 | @@ hlist
133 | [
134 | vlist
135 | [
136 | text "oï ωεird nums:\nπ/2\nτ/4";
137 | center_hv
138 | @@ tree (text "0")
139 | [ text "1"; tree (text "ω") [ text "ω²" ] ];
140 | ];
141 | frame @@ frame @@ frame
142 | @@ vlist
143 | [
144 | text "sum=Σ_i a·xᵢ²\n—————\n1+1";
145 | align_right @@ text "Ōₒ\nÀ";
146 | ];
147 | ];
148 | ])
149 |
150 | let () = print_endline @@ PrintBox_text.to_string b
151 | end
152 |
--------------------------------------------------------------------------------
/test/test_ann_0_3.expected:
--------------------------------------------------------------------------------
1 | ┌─────────────────┬──────────────────────────────────────────────────────────────┐
2 | │subject │[1mannounce: printbox 0.3[0m │
3 | ├─────────────────┼──────────────────────────────────────────────────────────────┤
4 | │explanation │┌────────────────────────────────────────────────────────────┐│
5 | │ ││PrintBox is a library for rendering nested tables, ││
6 | │ ││ trees, and similar structures in monospace text or HTML.││
7 | │ │└────────────────────────────────────────────────────────────┘│
8 | ├─────────────────┼──────────────────────────────────────────────────────────────┤
9 | │github │[44mhttps://github.com/c-cube/printbox/releases/tag/0.3[0m │
10 | ├─────────────────┼──────────────────────────────────────────────────────────────┤
11 | │contributors │[32mSimon[0m │
12 | │ ├──────────────────────────────────────────────────────────────┤
13 | │ │[32mGuillaume[0m │
14 | │ ├──────────────────────────────────────────────────────────────┤
15 | │ │[32mMatt[0m │
16 | ├─────────────────┼──────────────────────────────────────────────────────────────┤
17 | │dependencies │┬─mandatory │
18 | │ ││ ├─dune │
19 | │ ││ ├─bytes │
20 | │ ││ ├─uutf │
21 | │ ││ └─uucp │
22 | │ │└─optional │
23 | │ │ └─tyxml │
24 | ├─────────────────┼──────────────────────────────────────────────────────────────┤
25 | │expected reaction│🎉 │
26 | └─────────────────┴──────────────────────────────────────────────────────────────┘
27 |
--------------------------------------------------------------------------------
/test/test_ann_0_3.ml:
--------------------------------------------------------------------------------
1 | let b =
2 | let open PrintBox in
3 | frame
4 | @@ grid_l
5 | [
6 | [ text "subject"; text_with_style Style.bold "announce: printbox 0.3" ];
7 | [
8 | text "explanation";
9 | frame
10 | @@ text
11 | {|PrintBox is a library for rendering nested tables,
12 | trees, and similar structures in monospace text or HTML.|};
13 | ];
14 | [
15 | text "github";
16 | text_with_style
17 | Style.(bg_color Blue)
18 | "https://github.com/c-cube/printbox/releases/tag/0.3";
19 | ];
20 | [
21 | text "contributors";
22 | vlist_map
23 | (text_with_style Style.(fg_color Green))
24 | [ "Simon"; "Guillaume"; "Matt" ];
25 | ];
26 | [
27 | text "dependencies";
28 | tree empty
29 | [
30 | tree (text "mandatory")
31 | [ text "dune"; text "bytes"; text "uutf"; text "uucp" ];
32 | tree (text "optional") [ text "tyxml" ];
33 | ];
34 | ];
35 | [ text "expected reaction"; text "🎉" ];
36 | ]
37 |
38 | let () = print_endline @@ PrintBox_text.to_string b
39 |
--------------------------------------------------------------------------------
/test/test_blending.expected:
--------------------------------------------------------------------------------
1 | ┌────┐
2 | │root│
3 | ├────┘
4 | ├─┬───────┐
5 | │ │child 1│
6 | │ └───────┘
7 | ├─child 2
8 | ├─┬──────────────────┐
9 | │ │──┬────────┐ │
10 | │ │ │header 3│ │
11 | │ │ ├────────┘ │
12 | │ │ └─┬──────────┐ │
13 | │ │ │subchild 3│ │
14 | │ │ └──────────┘ │
15 | │ └──────────────────┘
16 | ├───┬────────┐
17 | │ │header 4│
18 | │ ├────────┘
19 | │ └─┬──────────┐
20 | │ │subchild 4│
21 | │ └──────────┘
22 | ├─┬───────┐
23 | │ │child 5│
24 | │ └───────┘
25 | └─┬──────────────────┐
26 | │┌────────┐ │
27 | ││header 6│ │
28 | │├────────┘ │
29 | │└─┬───────┐ │
30 | │ │child 6│ │
31 | │ ├───────┘ │
32 | │ └─┬──────────┐ │
33 | │ │subchild 6│ │
34 | │ └──────────┘ │
35 | └──────────────────┘
36 | ┌────┐
37 | │root│
38 | ├──┬─┘
39 | ├─a│b
40 | │ ─┼─
41 | │ c│d
42 | ├─hello I'm a long strip don't break me
43 | └─e│f
44 | ─┼─
45 | g│h
46 |
--------------------------------------------------------------------------------
/test/test_blending.ml:
--------------------------------------------------------------------------------
1 | let b =
2 | let open PrintBox in
3 | tree
4 | (frame @@ text "root")
5 | [
6 | frame @@ text "child 1";
7 | text "child 2";
8 | frame
9 | @@ tree empty
10 | [ tree (frame @@ text "header 3") [ frame @@ text "subchild 3" ] ];
11 | tree empty
12 | [ tree (frame @@ text "header 4") [ frame @@ text "subchild 4" ] ];
13 | frame @@ text "child 5";
14 | frame
15 | @@ tree
16 | (frame @@ text "header 6")
17 | [ tree (frame @@ text "child 6") [ frame @@ text "subchild 6" ] ];
18 | ]
19 |
20 | let () = print_endline @@ PrintBox_text.to_string b
21 |
22 | let grid_invasion =
23 | let open PrintBox in
24 | tree
25 | (frame @@ text "root")
26 | [
27 | grid_text_l ~bars:true [ [ "a"; "b" ]; [ "c"; "d" ] ];
28 | text "hello I'm a long strip don't break me";
29 | grid_text_l ~bars:true [ [ "e"; "f" ]; [ "g"; "h" ] ];
30 | ]
31 |
32 | let () = print_endline @@ PrintBox_text.to_string grid_invasion
33 |
34 |
--------------------------------------------------------------------------------
/test/test_html.expected:
--------------------------------------------------------------------------------
1 | root
1 2 3 4 5
- child 1
- child 2
header 3
- subchild 3
header 4
- subchild 4
header 5
- subchild 5
- child 5
- separator
entry 0.1 entry 0.2
- child 5.5
- separator
entry 1 entry 2
- child 6
- anchor (visible)
- separator after hidden anchor
entry 5
entry 6
- child 8
- separator
entry 7
entry 8
- child 9
2 |
3 |
--------------------------------------------------------------------------------
/test/test_html.ml:
--------------------------------------------------------------------------------
1 | let b =
2 | let open PrintBox in
3 | tree
4 | (frame @@ text "root")
5 | [
6 | link ~uri:"#HiddenAnchor" @@ text "link to a hidden anchor";
7 | vlist ~bars:true [ text "1"; text "2"; text "3"; text "4"; text "5" ];
8 | frame @@ text "child 1";
9 | text "child 2";
10 | frame
11 | @@ tree empty
12 | [ tree (frame @@ text "header 3") [ frame @@ text "subchild 3" ] ];
13 | tree empty [ tree (frame @@ text "header 4") [ text "subchild 4" ] ];
14 | frame @@ tree (text "header 5") [ text "subchild 5" ];
15 | frame @@ text "child 5";
16 | text "separator";
17 | tree
18 | (hlist ~bars:false [ text "entry 0.1"; text "entry 0.2" ])
19 | [ text "child 5.5" ];
20 | text "separator";
21 | tree
22 | (hlist ~bars:false [ text "entry 1"; frame @@ text "entry 2" ])
23 | [ text "child 6" ];
24 | anchor ~id:"VisibleAnchor" @@ text "anchor (visible)";
25 | tree
26 | (hlist ~bars:true
27 | [
28 | anchor ~id:"HiddenAnchor" empty;
29 | text "entry 3";
30 | frame @@ text "entry 4";
31 | ])
32 | [ text "child 7" ];
33 | text "separator after hidden anchor";
34 | anchor ~id:"HiddenAnchor2" empty;
35 | tree
36 | (vlist ~bars:false [ text "entry 5"; frame @@ text "entry 6" ])
37 | [ text "child 8" ];
38 | text "separator";
39 | tree
40 | (vlist ~bars:true [ text "entry 7"; frame @@ text "entry 8" ])
41 | [ text "child 9" ];
42 | ]
43 |
44 | let () =
45 | print_endline
46 | @@ PrintBox_html.(to_string ~config:Config.(tree_summary true default)) b
47 |
--------------------------------------------------------------------------------
/test/test_md.expected:
--------------------------------------------------------------------------------
1 | Test default:
2 | > root
3 | - > child 1
4 | - `child 2`
5 | - line 1
6 | line 2
7 | line 3
8 | - - a row 1
9 | - a row 2.1
10 | a row 2.2
11 | - > a row 3
12 | - - b row 1
13 | > ---
14 | - b row 2.1
15 | b row 2.2
16 | > ---
17 | - **b row 3**
18 | - **a longiiish column 1** a longiiish column 2 [a longiiish column 3] a longiiish column 4
19 | - b longiiish column 1 | **b longiiish column 2** | b longiiish column 3 | [b longiiish column 4]
20 | - > - c row 1
21 | > > ---
22 | > - c row 2.1
23 | > c row 2.2
24 | > > ---
25 | > - c row 3
26 | - >
27 | > - > header 3
28 | > - > subchild 3
29 | -
30 | - > header 4
31 | - \
32 | - ``
33 | - & \*\*subchild\*\* 4
34 | - > `header 5`
35 | > -
36 | > ```
37 | > subchild 5
38 | > body 5
39 | > subbody 5
40 | > one tab end of sub 5
41 | > end of 5
42 | > ```
43 | >
44 | - > >
45 | > > ```
46 | > > a │looooooooooooooooooooooooo
47 | > > │oonng
48 | > > ─────┼──────────────────────────
49 | > > bx │ ┌─┬─┐
50 | > > │ │x│y│
51 | > > │ ├─┼─┤
52 | > > │ │1│2│
53 | > > │ └─┴─┘
54 | > > ─────┼──────────────────────────
55 | > > │
56 | > > │ x │y
57 | > > ? │ ──┼──
58 | > > │ 10│20
59 | > > │
60 | > > ```
61 | > >
62 | > >
63 | > >
64 | - header 1 |header 2 |[header 3]
65 | ----------|----------|------------
66 | cell 1.1 |[cell 1.2]|cell 1.3
67 | [cell 2.1]|cell 2.2 |**cell 2.3**
68 | - > header 1 |header 2 |[header 3]
69 | > ----------|----------|------------
70 | > cell 1.1 |[cell 1.2]|cell 1.3
71 | > [cell 2.1]|cell 2.2 |**cell 2.3**
72 |
73 | Test uniform unfolded:
74 |
75 |
76 | ```
77 | ┌────┐
78 | │root│
79 | └────┘
80 | ```
81 |
82 | -
83 | ```
84 | ┌───────┐
85 | │child 1│
86 | └───────┘
87 | ```
88 |
89 | - `child 2`
90 | - line 1
91 | line 2
92 | line 3
93 | - a row 1
94 |
95 | a row 2.1
96 | a row 2.2
97 |
98 |
99 | ```
100 | ┌───────┐
101 | │a row 3│
102 | └───────┘
103 | ```
104 |
105 | - b row 1
106 | > ---
107 | b row 2.1
108 | b row 2.2
109 | > ---
110 | **b row 3**
111 | -
112 | ```
113 | a longiiish column 1a longiiish column 2┌────────────────────┐a longiiish column 4
114 | │a longiiish column 3│
115 | └────────────────────┘
116 | ```
117 |
118 |
119 |
120 | -
121 | ```
122 | b longiiish column 1│b longiiish column 2│b longiiish column 3│┌────────────────────┐
123 | │ │ ││b longiiish column 4│
124 | │ │ │└────────────────────┘
125 | ```
126 |
127 |
128 |
129 | -
130 | ```
131 | ┌─────────┐
132 | │c row 1 │
133 | ├─────────┤
134 | │c row 2.1│
135 | │c row 2.2│
136 | ├─────────┤
137 | │c row 3 │
138 | └─────────┘
139 | ```
140 |
141 | -
142 | ```
143 | ┌──────────────────┐
144 | │──┬────────┐ │
145 | │ │header 3│ │
146 | │ ├────────┘ │
147 | │ └─┬──────────┐ │
148 | │ │subchild 3│ │
149 | │ └──────────┘ │
150 | └──────────────────┘
151 | ```
152 |
153 | -
154 | -
155 | ```
156 | ┌────────┐
157 | │header 4│
158 | └────────┘
159 | ```
160 |
161 | - \
162 | - ``
163 | - & \*\*subchild\*\* 4
164 | -
165 | ```
166 | ┌───────────────────────┐
167 | │header 5 │
168 | │└─subchild 5 │
169 | │ body 5 │
170 | │ subbody 5 │
171 | │ one tab end of sub 5 │
172 | │ end of 5 │
173 | └───────────────────────┘
174 | ```
175 |
176 | -
177 | ```
178 | ┌──────────────────────────────────┐
179 | │┌─────┬──────────────────────────┐│
180 | ││a │looooooooooooooooooooooooo││
181 | ││ │oonng ││
182 | │├─────┼──────────────────────────┤│
183 | ││bx │ ┌─┬─┐ ││
184 | ││ │ │x│y│ ││
185 | ││ │ ├─┼─┤ ││
186 | ││ │ │1│2│ ││
187 | ││ │ └─┴─┘ ││
188 | │├─────┼──────────────────────────┤│
189 | ││ │ ││
190 | ││ │ x │y ││
191 | ││ ? │ ──┼── ││
192 | ││ │ 10│20 ││
193 | ││ │ ││
194 | │└─────┴──────────────────────────┘│
195 | └──────────────────────────────────┘
196 | ```
197 |
198 | -
199 | ```
200 | header 1 │header 2 │┌────────┐
201 | │ ││header 3│
202 | │ │└────────┘
203 | ──────────┼──────────┼──────────
204 | cell 1.1 │┌────────┐│cell 1.3
205 | ││cell 1.2││
206 | │└────────┘│
207 | ──────────┼──────────┼──────────
208 | ┌────────┐│cell 2.2 │cell 2.3
209 | │cell 2.1││ │
210 | └────────┘│ │
211 | ```
212 |
213 |
214 |
215 | -
216 | ```
217 | ┌──────────┬──────────┬──────────┐
218 | │header 1 │header 2 │┌────────┐│
219 | │ │ ││header 3││
220 | │ │ │└────────┘│
221 | ├──────────┼──────────┼──────────┤
222 | │cell 1.1 │┌────────┐│cell 1.3 │
223 | │ ││cell 1.2││ │
224 | │ │└────────┘│ │
225 | ├──────────┼──────────┼──────────┤
226 | │┌────────┐│cell 2.2 │cell 2.3 │
227 | ││cell 2.1││ │ │
228 | │└────────┘│ │ │
229 | └──────────┴──────────┴──────────┘
230 | ```
231 |
232 |
233 | Test foldable:
234 | [root]
235 |
236 | - > child 1
237 | - `child 2`
238 | - line 1
239 | line 2
240 | line 3
241 | - - a row 1
242 | - a row 2.1
243 | a row 2.2
244 | - > a row 3
245 | - - b row 1
246 | > ---
247 | - b row 2.1
248 | b row 2.2
249 | > ---
250 | - **b row 3**
251 | - **a longiiish column 1** a longiiish column 2 [a longiiish column 3] a longiiish column 4
252 | - b longiiish column 1 | **b longiiish column 2** | b longiiish column 3 | [b longiiish column 4]
253 | - > - c row 1
254 | > > ---
255 | > - c row 2.1
256 | > c row 2.2
257 | > > ---
258 | > - c row 3
259 | - >
260 | >
261 | > - [header 3]
262 | >
263 | > - > subchild 3
264 | >
265 | >
266 | >
267 | >
268 | -
269 |
270 | - [header 4]
271 |
272 | - <returns>
273 |
274 | - ``
275 |
276 |
277 | - & \*\*subchild\*\* 4
278 |
279 |
280 |
281 |
282 | - > header 5
283 | >
284 | > -
285 | > ```
286 | > subchild 5
287 | > body 5
288 | > subbody 5
289 | > one tab end of sub 5
290 | > end of 5
291 | > ```
292 | >
293 | >
294 | >
295 | - > >
296 | > > ```
297 | > > a │looooooooooooooooooooooooo
298 | > > │oonng
299 | > > ─────┼──────────────────────────
300 | > > bx │ ┌─┬─┐
301 | > > │ │x│y│
302 | > > │ ├─┼─┤
303 | > > │ │1│2│
304 | > > │ └─┴─┘
305 | > > ─────┼──────────────────────────
306 | > > │
307 | > > │ x │y
308 | > > ? │ ──┼──
309 | > > │ 10│20
310 | > > │
311 | > > ```
312 | > >
313 | > >
314 | > >
315 | - header 1 |header 2 |[header 3]
316 | ----------|----------|------------
317 | cell 1.1 |[cell 1.2]|cell 1.3
318 | [cell 2.1]|cell 2.2 |**cell 2.3**
319 | - > header 1 |header 2 |[header 3]
320 | > ----------|----------|------------
321 | > cell 1.1 |[cell 1.2]|cell 1.3
322 | > [cell 2.1]|cell 2.2 |**cell 2.3**
323 |
324 |
325 |
326 | Test uniform tab=2:
327 | ┌────┐
328 | │root│
329 | └────┘
330 |
331 | -
332 | ```
333 | ┌───────┐
334 | │child 1│
335 | └───────┘
336 | ```
337 |
338 | - `child 2`
339 | - line 1
340 | line 2
341 | line 3
342 | - a row 1
343 |
344 | a row 2.1
345 | a row 2.2
346 |
347 |
348 | ```
349 | ┌───────┐
350 | │a row 3│
351 | └───────┘
352 | ```
353 |
354 | - b row 1
355 | > ---
356 | b row 2.1
357 | b row 2.2
358 | > ---
359 | **b row 3**
360 | -
361 | ```
362 | a longiiish column 1a longiiish column 2┌────────────────────┐a longiiish column 4
363 | │a longiiish column 3│
364 | └────────────────────┘
365 | ```
366 |
367 |
368 |
369 | -
370 | ```
371 | b longiiish column 1│b longiiish column 2│b longiiish column 3│┌────────────────────┐
372 | │ │ ││b longiiish column 4│
373 | │ │ │└────────────────────┘
374 | ```
375 |
376 |
377 |
378 | -
379 | ```
380 | ┌─────────┐
381 | │c row 1 │
382 | ├─────────┤
383 | │c row 2.1│
384 | │c row 2.2│
385 | ├─────────┤
386 | │c row 3 │
387 | └─────────┘
388 | ```
389 |
390 | -
391 | ```
392 | ┌──────────────────┐
393 | │──┬────────┐ │
394 | │ │header 3│ │
395 | │ ├────────┘ │
396 | │ └─┬──────────┐ │
397 | │ │subchild 3│ │
398 | │ └──────────┘ │
399 | └──────────────────┘
400 | ```
401 |
402 | -
403 |
404 | - ┌────────┐
405 | │header 4│
406 | └────────┘
407 |
408 | - <returns>
409 |
410 | - ``
411 |
412 |
413 | - & \*\*subchild\*\* 4
414 |
415 |
416 |
417 |
418 | -
419 | ```
420 | ┌───────────────────────┐
421 | │header 5 │
422 | │└─subchild 5 │
423 | │ body 5 │
424 | │ subbody 5 │
425 | │ one tab end of sub 5 │
426 | │ end of 5 │
427 | └───────────────────────┘
428 | ```
429 |
430 | -
431 | ```
432 | ┌──────────────────────────────────┐
433 | │┌─────┬──────────────────────────┐│
434 | ││a │looooooooooooooooooooooooo││
435 | ││ │oonng ││
436 | │├─────┼──────────────────────────┤│
437 | ││bx │ ┌─┬─┐ ││
438 | ││ │ │x│y│ ││
439 | ││ │ ├─┼─┤ ││
440 | ││ │ │1│2│ ││
441 | ││ │ └─┴─┘ ││
442 | │├─────┼──────────────────────────┤│
443 | ││ │ ││
444 | ││ │ x │y ││
445 | ││ ? │ ──┼── ││
446 | ││ │ 10│20 ││
447 | ││ │ ││
448 | │└─────┴──────────────────────────┘│
449 | └──────────────────────────────────┘
450 | ```
451 |
452 | -
453 | ```
454 | header 1 │header 2 │┌────────┐
455 | │ ││header 3│
456 | │ │└────────┘
457 | ──────────┼──────────┼──────────
458 | cell 1.1 │┌────────┐│cell 1.3
459 | ││cell 1.2││
460 | │└────────┘│
461 | ──────────┼──────────┼──────────
462 | ┌────────┐│cell 2.2 │cell 2.3
463 | │cell 2.1││ │
464 | └────────┘│ │
465 | ```
466 |
467 |
468 |
469 | -
470 | ```
471 | ┌──────────┬──────────┬──────────┐
472 | │header 1 │header 2 │┌────────┐│
473 | │ │ ││header 3││
474 | │ │ │└────────┘│
475 | ├──────────┼──────────┼──────────┤
476 | │cell 1.1 │┌────────┐│cell 1.3 │
477 | │ ││cell 1.2││ │
478 | │ │└────────┘│ │
479 | ├──────────┼──────────┼──────────┤
480 | │┌────────┐│cell 2.2 │cell 2.3 │
481 | ││cell 2.1││ │ │
482 | │└────────┘│ │ │
483 | └──────────┴──────────┴──────────┘
484 | ```
485 |
486 |
487 |
488 |
489 | Test single quote tab=2:
490 | ┌────┐
491 | │root│
492 | └────┘
493 |
494 | - `┌───────┐`
495 | `│child 1│`
496 | `└───────┘`
497 | - `child 2`
498 | - line 1
499 | line 2
500 | line 3
501 | - a row 1
502 |
503 | a row 2.1
504 | a row 2.2
505 |
506 | `┌───────┐`
507 | `│a row 3│`
508 | `└───────┘`
509 | - b row 1
510 | > ---
511 | b row 2.1
512 | b row 2.2
513 | > ---
514 | **b row 3**
515 | - `a longiiish column 1a longiiish column 2┌────────────────────┐a longiiish column 4`
516 | `· · · · · · · · · · · · · · · · · · · · │a longiiish column 3│`
517 | `· · · · · · · · · · · · · · · · · · · · └────────────────────┘`
518 |
519 |
520 | - `b longiiish column 1│b longiiish column 2│b longiiish column 3│┌────────────────────┐`
521 | `· · · · · · · · · · │ · · · · · · · · ·· │ · · · · · · · · ·· ││b longiiish column 4│`
522 | `· · · · · · · · · · │ · · · · · · · · ·· │ · · · · · · · · ·· │└────────────────────┘`
523 |
524 |
525 | - `┌─────────┐`
526 | `│c row 1· │`
527 | `├─────────┤`
528 | `│c row 2.1│`
529 | `│c row 2.2│`
530 | `├─────────┤`
531 | `│c row 3· │`
532 | `└─────────┘`
533 | - `┌──────────────────┐`
534 | `│──┬────────┐ · ·· │`
535 | `│· │header 3│ · ·· │`
536 | `│· ├────────┘ · ·· │`
537 | `│· └─┬──────────┐· │`
538 | `│ ·· │subchild 3│· │`
539 | `│ ·· └──────────┘· │`
540 | `└──────────────────┘`
541 | -
542 |
543 | - ┌────────┐
544 | │header 4│
545 | └────────┘
546 |
547 | - <returns>
548 |
549 | - ``
550 |
551 |
552 | - & \*\*subchild\*\* 4
553 |
554 |
555 |
556 |
557 | - `┌───────────────────────┐`
558 | `│header 5 · · · · · · · │`
559 | `│└─subchild 5 · · · · · │`
560 | `│ ·· body 5 · · · · · · │`
561 | `│ · ·· subbody 5 · · ·· │`
562 | `│· ·one tab end of sub 5 │`
563 | `│· end of 5 · · · · · · │`
564 | `└───────────────────────┘`
565 | - `┌──────────────────────────────────┐`
566 | `│┌─────┬──────────────────────────┐│`
567 | `││a ·· │looooooooooooooooooooooooo││`
568 | `││ · · │oonng · · · · · · · · · · ││`
569 | `│├─────┼──────────────────────────┤│`
570 | `││bx · │ · · · ·· ┌─┬─┐ · · · · · ││`
571 | `││ · · │ · · · ·· │x│y│ · · · · · ││`
572 | `││ · · │ · · · ·· ├─┼─┤ · · · · · ││`
573 | `││ · · │ · · · ·· │1│2│ · · · · · ││`
574 | `││ · · │ · · · ·· └─┴─┘ · · · · · ││`
575 | `│├─────┼──────────────────────────┤│`
576 | `││ · · │ · · · · · · · · · · · ·· ││`
577 | `││ · · │ · · · ·· x │y · · · · ·· ││`
578 | `││· ?· │ · · · ·· ──┼── · · · · · ││`
579 | `││ · · │ · · · ·· 10│20 · · · · · ││`
580 | `││ · · │ · · · · · · · · · · · ·· ││`
581 | `│└─────┴──────────────────────────┘│`
582 | `└──────────────────────────────────┘`
583 | - `header 1· │header 2· │┌────────┐`
584 | `· · · · · │ · · · ·· ││header 3│`
585 | `· · · · · │ · · · ·· │└────────┘`
586 | `──────────┼──────────┼──────────`
587 | `cell 1.1· │┌────────┐│cell 1.3`
588 | `· · · · · ││cell 1.2││`
589 | `· · · · · │└────────┘│`
590 | `──────────┼──────────┼──────────`
591 | `┌────────┐│cell 2.2· │cell 2.3`
592 | `│cell 2.1││ · · · ·· │`
593 | `└────────┘│ · · · ·· │`
594 |
595 |
596 | - `┌──────────┬──────────┬──────────┐`
597 | `│header 1· │header 2· │┌────────┐│`
598 | `│ · · · ·· │ · · · ·· ││header 3││`
599 | `│ · · · ·· │ · · · ·· │└────────┘│`
600 | `├──────────┼──────────┼──────────┤`
601 | `│cell 1.1· │┌────────┐│cell 1.3· │`
602 | `│ · · · ·· ││cell 1.2││ · · · ·· │`
603 | `│ · · · ·· │└────────┘│ · · · ·· │`
604 | `├──────────┼──────────┼──────────┤`
605 | `│┌────────┐│cell 2.2· │cell 2.3· │`
606 | `││cell 2.1││ · · · ·· │ · · · ·· │`
607 | `│└────────┘│ · · · ·· │ · · · ·· │`
608 | `└──────────┴──────────┴──────────┘`
609 |
610 |
611 |
612 | The end.
613 |
--------------------------------------------------------------------------------
/test/test_md.expected.md:
--------------------------------------------------------------------------------
1 | Test default:
2 | > root
3 | - > child 1
4 | - `child 2`
5 | - line 1
6 | line 2
7 | line 3
8 | - - a row 1
9 | - a row 2.1
10 | a row 2.2
11 | - > a row 3
12 | - - b row 1
13 | > ---
14 | - b row 2.1
15 | b row 2.2
16 | > ---
17 | - **b row 3**
18 | - **a longiiish column 1** a longiiish column 2 [a longiiish column 3] a longiiish column 4
19 | - b longiiish column 1 | **b longiiish column 2** | b longiiish column 3 | [b longiiish column 4]
20 | - > - c row 1
21 | > > ---
22 | > - c row 2.1
23 | > c row 2.2
24 | > > ---
25 | > - c row 3
26 | - >
27 | > - > header 3
28 | > - > subchild 3
29 | -
30 | - > header 4
31 | - \
32 | - ``
33 | - & \*\*subchild\*\* 4
34 | - > `header 5`
35 | > -
36 | > ```
37 | > subchild 5
38 | > body 5
39 | > subbody 5
40 | > one tab end of sub 5
41 | > end of 5
42 | > ```
43 | >
44 | - > >
45 | > > ```
46 | > > a │looooooooooooooooooooooooo
47 | > > │oonng
48 | > > ─────┼──────────────────────────
49 | > > bx │ ┌─┬─┐
50 | > > │ │x│y│
51 | > > │ ├─┼─┤
52 | > > │ │1│2│
53 | > > │ └─┴─┘
54 | > > ─────┼──────────────────────────
55 | > > │
56 | > > │ x │y
57 | > > ? │ ──┼──
58 | > > │ 10│20
59 | > > │
60 | > > ```
61 | > >
62 | > >
63 | > >
64 | - header 1 |header 2 |[header 3]
65 | ----------|----------|------------
66 | cell 1.1 |[cell 1.2]|cell 1.3
67 | [cell 2.1]|cell 2.2 |**cell 2.3**
68 | - > header 1 |header 2 |[header 3]
69 | > ----------|----------|------------
70 | > cell 1.1 |[cell 1.2]|cell 1.3
71 | > [cell 2.1]|cell 2.2 |**cell 2.3**
72 |
73 | Test uniform unfolded:
74 |
75 |
76 | ```
77 | ┌────┐
78 | │root│
79 | └────┘
80 | ```
81 |
82 | -
83 | ```
84 | ┌───────┐
85 | │child 1│
86 | └───────┘
87 | ```
88 |
89 | - `child 2`
90 | - line 1
91 | line 2
92 | line 3
93 | - a row 1
94 |
95 | a row 2.1
96 | a row 2.2
97 |
98 |
99 | ```
100 | ┌───────┐
101 | │a row 3│
102 | └───────┘
103 | ```
104 |
105 | - b row 1
106 | > ---
107 | b row 2.1
108 | b row 2.2
109 | > ---
110 | **b row 3**
111 | -
112 | ```
113 | a longiiish column 1a longiiish column 2┌────────────────────┐a longiiish column 4
114 | │a longiiish column 3│
115 | └────────────────────┘
116 | ```
117 |
118 |
119 |
120 | -
121 | ```
122 | b longiiish column 1│b longiiish column 2│b longiiish column 3│┌────────────────────┐
123 | │ │ ││b longiiish column 4│
124 | │ │ │└────────────────────┘
125 | ```
126 |
127 |
128 |
129 | -
130 | ```
131 | ┌─────────┐
132 | │c row 1 │
133 | ├─────────┤
134 | │c row 2.1│
135 | │c row 2.2│
136 | ├─────────┤
137 | │c row 3 │
138 | └─────────┘
139 | ```
140 |
141 | -
142 | ```
143 | ┌──────────────────┐
144 | │──┬────────┐ │
145 | │ │header 3│ │
146 | │ ├────────┘ │
147 | │ └─┬──────────┐ │
148 | │ │subchild 3│ │
149 | │ └──────────┘ │
150 | └──────────────────┘
151 | ```
152 |
153 | -
154 | -
155 | ```
156 | ┌────────┐
157 | │header 4│
158 | └────────┘
159 | ```
160 |
161 | - \
162 | - ``
163 | - & \*\*subchild\*\* 4
164 | -
165 | ```
166 | ┌───────────────────────┐
167 | │header 5 │
168 | │└─subchild 5 │
169 | │ body 5 │
170 | │ subbody 5 │
171 | │ one tab end of sub 5 │
172 | │ end of 5 │
173 | └───────────────────────┘
174 | ```
175 |
176 | -
177 | ```
178 | ┌──────────────────────────────────┐
179 | │┌─────┬──────────────────────────┐│
180 | ││a │looooooooooooooooooooooooo││
181 | ││ │oonng ││
182 | │├─────┼──────────────────────────┤│
183 | ││bx │ ┌─┬─┐ ││
184 | ││ │ │x│y│ ││
185 | ││ │ ├─┼─┤ ││
186 | ││ │ │1│2│ ││
187 | ││ │ └─┴─┘ ││
188 | │├─────┼──────────────────────────┤│
189 | ││ │ ││
190 | ││ │ x │y ││
191 | ││ ? │ ──┼── ││
192 | ││ │ 10│20 ││
193 | ││ │ ││
194 | │└─────┴──────────────────────────┘│
195 | └──────────────────────────────────┘
196 | ```
197 |
198 | -
199 | ```
200 | header 1 │header 2 │┌────────┐
201 | │ ││header 3│
202 | │ │└────────┘
203 | ──────────┼──────────┼──────────
204 | cell 1.1 │┌────────┐│cell 1.3
205 | ││cell 1.2││
206 | │└────────┘│
207 | ──────────┼──────────┼──────────
208 | ┌────────┐│cell 2.2 │cell 2.3
209 | │cell 2.1││ │
210 | └────────┘│ │
211 | ```
212 |
213 |
214 |
215 | -
216 | ```
217 | ┌──────────┬──────────┬──────────┐
218 | │header 1 │header 2 │┌────────┐│
219 | │ │ ││header 3││
220 | │ │ │└────────┘│
221 | ├──────────┼──────────┼──────────┤
222 | │cell 1.1 │┌────────┐│cell 1.3 │
223 | │ ││cell 1.2││ │
224 | │ │└────────┘│ │
225 | ├──────────┼──────────┼──────────┤
226 | │┌────────┐│cell 2.2 │cell 2.3 │
227 | ││cell 2.1││ │ │
228 | │└────────┘│ │ │
229 | └──────────┴──────────┴──────────┘
230 | ```
231 |
232 |
233 | Test foldable:
234 | [root]
235 |
236 | - > child 1
237 | - `child 2`
238 | - line 1
239 | line 2
240 | line 3
241 | - - a row 1
242 | - a row 2.1
243 | a row 2.2
244 | - > a row 3
245 | - - b row 1
246 | > ---
247 | - b row 2.1
248 | b row 2.2
249 | > ---
250 | - **b row 3**
251 | - **a longiiish column 1** a longiiish column 2 [a longiiish column 3] a longiiish column 4
252 | - b longiiish column 1 | **b longiiish column 2** | b longiiish column 3 | [b longiiish column 4]
253 | - > - c row 1
254 | > > ---
255 | > - c row 2.1
256 | > c row 2.2
257 | > > ---
258 | > - c row 3
259 | - >
260 | >
261 | > - [header 3]
262 | >
263 | > - > subchild 3
264 | >
265 | >
266 | >
267 | >
268 | -
269 |
270 | - [header 4]
271 |
272 | - <returns>
273 |
274 | - ``
275 |
276 |
277 | - & \*\*subchild\*\* 4
278 |
279 |
280 |
281 |
282 | - > header 5
283 | >
284 | > -
285 | > ```
286 | > subchild 5
287 | > body 5
288 | > subbody 5
289 | > one tab end of sub 5
290 | > end of 5
291 | > ```
292 | >
293 | >
294 | >
295 | - > >
296 | > > ```
297 | > > a │looooooooooooooooooooooooo
298 | > > │oonng
299 | > > ─────┼──────────────────────────
300 | > > bx │ ┌─┬─┐
301 | > > │ │x│y│
302 | > > │ ├─┼─┤
303 | > > │ │1│2│
304 | > > │ └─┴─┘
305 | > > ─────┼──────────────────────────
306 | > > │
307 | > > │ x │y
308 | > > ? │ ──┼──
309 | > > │ 10│20
310 | > > │
311 | > > ```
312 | > >
313 | > >
314 | > >
315 | - header 1 |header 2 |[header 3]
316 | ----------|----------|------------
317 | cell 1.1 |[cell 1.2]|cell 1.3
318 | [cell 2.1]|cell 2.2 |**cell 2.3**
319 | - > header 1 |header 2 |[header 3]
320 | > ----------|----------|------------
321 | > cell 1.1 |[cell 1.2]|cell 1.3
322 | > [cell 2.1]|cell 2.2 |**cell 2.3**
323 |
324 |
325 |
326 | Test uniform tab=2:
327 | ┌────┐
328 | │root│
329 | └────┘
330 |
331 | -
332 | ```
333 | ┌───────┐
334 | │child 1│
335 | └───────┘
336 | ```
337 |
338 | - `child 2`
339 | - line 1
340 | line 2
341 | line 3
342 | - a row 1
343 |
344 | a row 2.1
345 | a row 2.2
346 |
347 |
348 | ```
349 | ┌───────┐
350 | │a row 3│
351 | └───────┘
352 | ```
353 |
354 | - b row 1
355 | > ---
356 | b row 2.1
357 | b row 2.2
358 | > ---
359 | **b row 3**
360 | -
361 | ```
362 | a longiiish column 1a longiiish column 2┌────────────────────┐a longiiish column 4
363 | │a longiiish column 3│
364 | └────────────────────┘
365 | ```
366 |
367 |
368 |
369 | -
370 | ```
371 | b longiiish column 1│b longiiish column 2│b longiiish column 3│┌────────────────────┐
372 | │ │ ││b longiiish column 4│
373 | │ │ │└────────────────────┘
374 | ```
375 |
376 |
377 |
378 | -
379 | ```
380 | ┌─────────┐
381 | │c row 1 │
382 | ├─────────┤
383 | │c row 2.1│
384 | │c row 2.2│
385 | ├─────────┤
386 | │c row 3 │
387 | └─────────┘
388 | ```
389 |
390 | -
391 | ```
392 | ┌──────────────────┐
393 | │──┬────────┐ │
394 | │ │header 3│ │
395 | │ ├────────┘ │
396 | │ └─┬──────────┐ │
397 | │ │subchild 3│ │
398 | │ └──────────┘ │
399 | └──────────────────┘
400 | ```
401 |
402 | -
403 |
404 | - ┌────────┐
405 | │header 4│
406 | └────────┘
407 |
408 | - <returns>
409 |
410 | - ``
411 |
412 |
413 | - & \*\*subchild\*\* 4
414 |
415 |
416 |
417 |
418 | -
419 | ```
420 | ┌───────────────────────┐
421 | │header 5 │
422 | │└─subchild 5 │
423 | │ body 5 │
424 | │ subbody 5 │
425 | │ one tab end of sub 5 │
426 | │ end of 5 │
427 | └───────────────────────┘
428 | ```
429 |
430 | -
431 | ```
432 | ┌──────────────────────────────────┐
433 | │┌─────┬──────────────────────────┐│
434 | ││a │looooooooooooooooooooooooo││
435 | ││ │oonng ││
436 | │├─────┼──────────────────────────┤│
437 | ││bx │ ┌─┬─┐ ││
438 | ││ │ │x│y│ ││
439 | ││ │ ├─┼─┤ ││
440 | ││ │ │1│2│ ││
441 | ││ │ └─┴─┘ ││
442 | │├─────┼──────────────────────────┤│
443 | ││ │ ││
444 | ││ │ x │y ││
445 | ││ ? │ ──┼── ││
446 | ││ │ 10│20 ││
447 | ││ │ ││
448 | │└─────┴──────────────────────────┘│
449 | └──────────────────────────────────┘
450 | ```
451 |
452 | -
453 | ```
454 | header 1 │header 2 │┌────────┐
455 | │ ││header 3│
456 | │ │└────────┘
457 | ──────────┼──────────┼──────────
458 | cell 1.1 │┌────────┐│cell 1.3
459 | ││cell 1.2││
460 | │└────────┘│
461 | ──────────┼──────────┼──────────
462 | ┌────────┐│cell 2.2 │cell 2.3
463 | │cell 2.1││ │
464 | └────────┘│ │
465 | ```
466 |
467 |
468 |
469 | -
470 | ```
471 | ┌──────────┬──────────┬──────────┐
472 | │header 1 │header 2 │┌────────┐│
473 | │ │ ││header 3││
474 | │ │ │└────────┘│
475 | ├──────────┼──────────┼──────────┤
476 | │cell 1.1 │┌────────┐│cell 1.3 │
477 | │ ││cell 1.2││ │
478 | │ │└────────┘│ │
479 | ├──────────┼──────────┼──────────┤
480 | │┌────────┐│cell 2.2 │cell 2.3 │
481 | ││cell 2.1││ │ │
482 | │└────────┘│ │ │
483 | └──────────┴──────────┴──────────┘
484 | ```
485 |
486 |
487 |
488 |
489 | Test single quote tab=2:
490 | ┌────┐
491 | │root│
492 | └────┘
493 |
494 | - `┌───────┐`
495 | `│child 1│`
496 | `└───────┘`
497 | - `child 2`
498 | - line 1
499 | line 2
500 | line 3
501 | - a row 1
502 |
503 | a row 2.1
504 | a row 2.2
505 |
506 | `┌───────┐`
507 | `│a row 3│`
508 | `└───────┘`
509 | - b row 1
510 | > ---
511 | b row 2.1
512 | b row 2.2
513 | > ---
514 | **b row 3**
515 | - `a longiiish column 1a longiiish column 2┌────────────────────┐a longiiish column 4`
516 | `· · · · · · · · · · · · · · · · · · · · │a longiiish column 3│`
517 | `· · · · · · · · · · · · · · · · · · · · └────────────────────┘`
518 |
519 |
520 | - `b longiiish column 1│b longiiish column 2│b longiiish column 3│┌────────────────────┐`
521 | `· · · · · · · · · · │ · · · · · · · · ·· │ · · · · · · · · ·· ││b longiiish column 4│`
522 | `· · · · · · · · · · │ · · · · · · · · ·· │ · · · · · · · · ·· │└────────────────────┘`
523 |
524 |
525 | - `┌─────────┐`
526 | `│c row 1· │`
527 | `├─────────┤`
528 | `│c row 2.1│`
529 | `│c row 2.2│`
530 | `├─────────┤`
531 | `│c row 3· │`
532 | `└─────────┘`
533 | - `┌──────────────────┐`
534 | `│──┬────────┐ · ·· │`
535 | `│· │header 3│ · ·· │`
536 | `│· ├────────┘ · ·· │`
537 | `│· └─┬──────────┐· │`
538 | `│ ·· │subchild 3│· │`
539 | `│ ·· └──────────┘· │`
540 | `└──────────────────┘`
541 | -
542 |
543 | - ┌────────┐
544 | │header 4│
545 | └────────┘
546 |
547 | - <returns>
548 |
549 | - ``
550 |
551 |
552 | - & \*\*subchild\*\* 4
553 |
554 |
555 |
556 |
557 | - `┌───────────────────────┐`
558 | `│header 5 · · · · · · · │`
559 | `│└─subchild 5 · · · · · │`
560 | `│ ·· body 5 · · · · · · │`
561 | `│ · ·· subbody 5 · · ·· │`
562 | `│· ·one tab end of sub 5 │`
563 | `│· end of 5 · · · · · · │`
564 | `└───────────────────────┘`
565 | - `┌──────────────────────────────────┐`
566 | `│┌─────┬──────────────────────────┐│`
567 | `││a ·· │looooooooooooooooooooooooo││`
568 | `││ · · │oonng · · · · · · · · · · ││`
569 | `│├─────┼──────────────────────────┤│`
570 | `││bx · │ · · · ·· ┌─┬─┐ · · · · · ││`
571 | `││ · · │ · · · ·· │x│y│ · · · · · ││`
572 | `││ · · │ · · · ·· ├─┼─┤ · · · · · ││`
573 | `││ · · │ · · · ·· │1│2│ · · · · · ││`
574 | `││ · · │ · · · ·· └─┴─┘ · · · · · ││`
575 | `│├─────┼──────────────────────────┤│`
576 | `││ · · │ · · · · · · · · · · · ·· ││`
577 | `││ · · │ · · · ·· x │y · · · · ·· ││`
578 | `││· ?· │ · · · ·· ──┼── · · · · · ││`
579 | `││ · · │ · · · ·· 10│20 · · · · · ││`
580 | `││ · · │ · · · · · · · · · · · ·· ││`
581 | `│└─────┴──────────────────────────┘│`
582 | `└──────────────────────────────────┘`
583 | - `header 1· │header 2· │┌────────┐`
584 | `· · · · · │ · · · ·· ││header 3│`
585 | `· · · · · │ · · · ·· │└────────┘`
586 | `──────────┼──────────┼──────────`
587 | `cell 1.1· │┌────────┐│cell 1.3`
588 | `· · · · · ││cell 1.2││`
589 | `· · · · · │└────────┘│`
590 | `──────────┼──────────┼──────────`
591 | `┌────────┐│cell 2.2· │cell 2.3`
592 | `│cell 2.1││ · · · ·· │`
593 | `└────────┘│ · · · ·· │`
594 |
595 |
596 | - `┌──────────┬──────────┬──────────┐`
597 | `│header 1· │header 2· │┌────────┐│`
598 | `│ · · · ·· │ · · · ·· ││header 3││`
599 | `│ · · · ·· │ · · · ·· │└────────┘│`
600 | `├──────────┼──────────┼──────────┤`
601 | `│cell 1.1· │┌────────┐│cell 1.3· │`
602 | `│ · · · ·· ││cell 1.2││ · · · ·· │`
603 | `│ · · · ·· │└────────┘│ · · · ·· │`
604 | `├──────────┼──────────┼──────────┤`
605 | `│┌────────┐│cell 2.2· │cell 2.3· │`
606 | `││cell 2.1││ · · · ·· │ · · · ·· │`
607 | `│└────────┘│ · · · ·· │ · · · ·· │`
608 | `└──────────┴──────────┴──────────┘`
609 |
610 |
611 |
612 | The end.
613 |
--------------------------------------------------------------------------------
/test/test_md.ml:
--------------------------------------------------------------------------------
1 | let b =
2 | let open PrintBox in
3 | let table =
4 | frame
5 | @@ grid_l
6 | [
7 | [ text "a"; text "looooooooooooooooooooooooo\noonng" ];
8 | [
9 | text "bx"; center_hv @@ frame @@ record [ "x", int 1; "y", int 2 ];
10 | ];
11 | [
12 | pad' ~col:2 ~lines:2 @@ text "?";
13 | center_hv @@ record [ "x", int 10; "y", int 20 ];
14 | ];
15 | ]
16 | in
17 | let bold = text_with_style Style.bold in
18 | let native =
19 | grid_l
20 | [
21 | [ bold "header 1"; bold "header 2"; frame @@ bold "header 3" ];
22 | [ line "cell 1.1"; frame @@ line "cell 1.2"; line "cell 1.3" ];
23 | [ frame @@ line "cell 2.1"; line "cell 2.2"; bold "cell 2.3" ];
24 | ]
25 | in
26 | tree
27 | (frame @@ text "root")
28 | [
29 | frame @@ text "child 1";
30 | text_with_style Style.preformatted "child 2";
31 | lines [ "line 1"; "line 2"; "line 3" ];
32 | vlist ~bars:false
33 | [
34 | line "a row 1";
35 | lines [ "a row 2.1"; "a row 2.2" ];
36 | frame @@ line "a row 3";
37 | ];
38 | vlist ~bars:true
39 | [ line "b row 1"; lines [ "b row 2.1"; "b row 2.2" ]; bold "b row 3" ];
40 | hlist ~bars:false
41 | [
42 | bold "a longiiish column 1";
43 | line "a longiiish column 2";
44 | frame @@ line "a longiiish column 3";
45 | line "a longiiish column 4";
46 | ];
47 | hlist ~bars:true
48 | [
49 | line "b longiiish column 1";
50 | bold "b longiiish column 2";
51 | line "b longiiish column 3";
52 | frame @@ line "b longiiish column 4";
53 | ];
54 | frame
55 | @@ vlist ~bars:true
56 | [
57 | line "c row 1"; lines [ "c row 2.1"; "c row 2.2" ]; line "c row 3";
58 | ];
59 | frame
60 | @@ tree empty
61 | [ tree (frame @@ text "header 3") [ frame @@ text "subchild 3" ] ];
62 | tree empty
63 | [
64 | tree
65 | (frame @@ text "header 4")
66 | [
67 | tree (text "")
68 | [ text_with_style Style.preformatted "" ];
69 | text "& **subchild** 4";
70 | ];
71 | ];
72 | frame
73 | @@ tree
74 | (text_with_style Style.preformatted "header 5")
75 | [
76 | lines_with_style Style.preformatted
77 | [
78 | "subchild 5";
79 | " body 5";
80 | " subbody 5";
81 | "\tone tab end of sub 5";
82 | "end of 5";
83 | ];
84 | ];
85 | frame table;
86 | native;
87 | frame native;
88 | ]
89 |
90 | let () = print_endline "Test default:"
91 | let () = print_endline @@ PrintBox_md.(to_string Config.default) b
92 | let () = print_endline "Test uniform unfolded:\n"
93 |
94 | let () =
95 | print_endline @@ PrintBox_md.(to_string Config.(unfolded_trees uniform)) b
96 |
97 | let () = print_endline "Test foldable:"
98 |
99 | let () =
100 | print_endline @@ PrintBox_md.(to_string Config.(foldable_trees default)) b
101 |
102 | let () = print_endline "Test uniform tab=2:"
103 | let () = print_endline @@ PrintBox_md.(to_string Config.(tab_width 2 uniform)) b
104 | let () = print_endline "Test single quote tab=2:"
105 |
106 | let () =
107 | print_endline
108 | @@ PrintBox_md.(
109 | to_string
110 | Config.(tab_width 2 @@ multiline_preformatted Code_quote uniform))
111 | b
112 |
113 | let () = print_endline "The end."
114 |
--------------------------------------------------------------------------------
/test/test_text_uri.expected:
--------------------------------------------------------------------------------
1 | Output with ANSI styling:
2 | ┌────────────────────────────────┐
3 | │]8;;#SecondAnchor\Link to a within-document anchor]8;;\│
4 | └────────────────────────────────┘
5 | ────────────────────────────────────────────────────
6 | ┌───────┐
7 | │]8;;https://example.com/1\child 1]8;;\│
8 | └───────┘
9 | ────────────────────────────────────────────────────
10 | ]8;;https://example.com/2\child 2]8;;\
11 | ────────────────────────────────────────────────────
12 | ┌──────────────────┐
13 | │──┬────────┐ │
14 | │ │]8;;https://example.com/4\header 3]8;;\│ │
15 | │ ├────────┘ │
16 | │ └─┬──────────┐ │
17 | │ │]8;;https://example.com/4\subchild 3]8;;\│ │
18 | │ └──────────┘ │
19 | └──────────────────┘
20 | ────────────────────────────────────────────────────
21 | ──┬────────┐
22 | │]8;;https://example.com/5\header 4]8;;\│
23 | ├────────┘
24 | └─┬──────────┐
25 | │]8;;https://example.com/5\subchild 4]8;;\│
26 | └──────────┘
27 | ────────────────────────────────────────────────────
28 | ┌───────┐
29 | │child 5│
30 | └───────┘
31 | ────────────────────────────────────────────────────
32 | ┌──────────────────┐
33 | │┌────────┐ │
34 | ││]8;;https://example.com/6\header 6]8;;\│ │
35 | │├────────┘ │
36 | │└─┬───────┐ │
37 | │ │]8;;https://example.com/6\child 6]8;;\│ │
38 | │ ├───────┘ │
39 | │ └─┬──────────┐ │
40 | │ │]8;;https://example.com/7\subchild 6]8;;\│ │
41 | │ └──────────┘ │
42 | └──────────────────┘
43 | ────────────────────────────────────────────────────
44 | {#FirstAnchor}┌──────────────────┐
45 | │anchor self-link 1│
46 | └──────────────────┘
47 | ────────────────────────────────────────────────────
48 | {#SecondAnchor}silent anchor
49 | └─subchild 7
50 | ────────────────────────────────────────────────────
51 | {#ThirdAnchor}anchor self-link 2 after anchor link 2
52 | └─subchild 8
53 | ────────────────────────────────────────────────────
54 | ]8;;https://example.com/8\external link 8]8;;\ after external link 8
55 | Output without ANSI styling:
56 | uri │#SecondAnchor
57 | ─────┼──────────────────────────────────────────────
58 | inner│┌────────────────────────────────┐
59 | ││Link to a within-document anchor│
60 | │└────────────────────────────────┘
61 | ─────┼──────────────────────────────────────────────
62 | uri │https://example.com/1
63 | ─────┼──────────────────────────────────────────────
64 | inner│┌───────┐
65 | ││child 1│
66 | │└───────┘
67 | ─────┼──────────────────────────────────────────────
68 | uri │https://example.com/2
69 | ─────┼──────────────────────────────────────────────
70 | inner│child 2
71 | ─────┴──────────────────────────────────────────────
72 | ┌─────┬────────────────────────┐
73 | │uri │https://example.com/3 │
74 | ├─────┼───────────────────── │
75 | │inner│ │
76 | │└─uri │https://example.com/4 │
77 | │ ─────┼───────────────────── │
78 | │ inner│┌────────┐ │
79 | │ ││header 3│ │
80 | │ │├────────┘ │
81 | │ │└─┬──────────┐ │
82 | │ │ │subchild 3│ │
83 | │ │ └──────────┘ │
84 | └───────┴──────────────────────┘
85 | ─────┬──────────────────────────────────────────────
86 | uri │https://example.com/5
87 | ─────┼──────────────────────────────────────────────
88 | inner│──┬────────┐
89 | │ │header 4│
90 | │ ├────────┘
91 | │ └─┬──────────┐
92 | │ │subchild 4│
93 | │ └──────────┘
94 | ─────┴──────────────────────────────────────────────
95 | ┌───────┐
96 | │child 5│
97 | └───────┘
98 | ─────┬──────────────────────────────────────────────
99 | uri │https://example.com/6
100 | ─────┼──────────────────────────────────────────────
101 | inner│┌─────────────────────────────────┐
102 | ││┌────────┐ │
103 | │││header 6│ │
104 | ││├────────┘ │
105 | ││└─┬───────┐ │
106 | ││ │child 6│ │
107 | ││ ├──────┬┘ │
108 | ││ └─uri │https://example.com/7 │
109 | ││ ─────┼───────────────────── │
110 | ││ inner│┌──────────┐ │
111 | ││ ││subchild 6│ │
112 | ││ │└──────────┘ │
113 | │└─────────┴───────────────────────┘
114 | ─────┴──────────────────────────────────────────────
115 | {#FirstAnchor}┌──────────────────┐
116 | │anchor self-link 1│
117 | └──────────────────┘
118 | ────────────────────────────────────────────────────
119 | {#SecondAnchor}silent anchor
120 | └─subchild 7
121 | ────────────────────────────────────────────────────
122 | {#ThirdAnchor}anchor self-link 2 after anchor link 2
123 | └─subchild 8
124 | ─────┬──────────────────────────────────────────────
125 | uri │https://example.com/8 after external link 8
126 | ─────┼─────────────────────
127 | inner│external link 8
128 |
--------------------------------------------------------------------------------
/test/test_text_uri.ml:
--------------------------------------------------------------------------------
1 | let b =
2 | let open PrintBox in
3 | vlist
4 | [
5 | link ~uri:"#SecondAnchor" @@ frame
6 | @@ text "Link to a within-document anchor";
7 | link ~uri:"https://example.com/1" @@ frame @@ text "child 1";
8 | link ~uri:"https://example.com/2" @@ text "child 2";
9 | frame
10 | @@ tree
11 | (link ~uri:"https://example.com/3" empty)
12 | [
13 | link ~uri:"https://example.com/4"
14 | @@ tree (frame @@ text "header 3") [ frame @@ text "subchild 3" ];
15 | ];
16 | link ~uri:"https://example.com/5"
17 | @@ tree empty
18 | [ tree (frame @@ text "header 4") [ frame @@ text "subchild 4" ] ];
19 | frame @@ text "child 5";
20 | link ~uri:"https://example.com/6"
21 | @@ frame
22 | @@ tree
23 | (frame @@ text "header 6")
24 | [
25 | tree
26 | (frame @@ text "child 6")
27 | [
28 | link ~uri:"https://example.com/7" @@ frame @@ text "subchild 6";
29 | ];
30 | ];
31 | anchor ~id:"FirstAnchor" @@ frame @@ text "anchor self-link 1";
32 | tree
33 | (hlist ~bars:false
34 | [ anchor ~id:"SecondAnchor" empty; text "silent anchor" ])
35 | [ text "subchild 7" ];
36 | tree
37 | (hlist ~bars:false
38 | [
39 | anchor ~id:"ThirdAnchor" @@ text "anchor self-link 2";
40 | text " after anchor link 2";
41 | ])
42 | [ text "subchild 8" ];
43 | hlist ~bars:false
44 | [
45 | link ~uri:"https://example.com/8" @@ text "external link 8";
46 | text " after external link 8";
47 | ];
48 | ]
49 |
50 | let () = print_endline "Output with ANSI styling:"
51 | let () = print_endline @@ PrintBox_text.to_string b
52 | let () = print_endline "Output without ANSI styling:"
53 | let () = print_endline @@ PrintBox_text.to_string_with ~style:false b
54 |
--------------------------------------------------------------------------------