├── .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 [![build](https://github.com/c-cube/printbox/actions/workflows/main.yml/badge.svg)](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 ![the following image](./.screen1.png). 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 | ![A plot as above rendered with a white background](../../docs/printbox-ext-plot-example_Map.png) 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 | ![A plot as above rendered with a white background](../../docs/printbox-ext-plot-example_linear.png) 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 | ![A plot as above rendered with a white background](../../docs/printbox-ext-plot-example_nested.png) 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 | ![A plot as above rendered with a white background](../../docs/printbox-ext-plot-example_half_moons.png) 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 │announce: printbox 0.3 │ 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 │https://github.com/c-cube/printbox/releases/tag/0.3 │ 10 | ├─────────────────┼──────────────────────────────────────────────────────────────┤ 11 | │contributors │Simon │ 12 | │ ├──────────────────────────────────────────────────────────────┤ 13 | │ │Guillaume │ 14 | │ ├──────────────────────────────────────────────────────────────┤ 15 | │ │Matt │ 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)
  •  entry 3 entry 4
    • child 7
  • 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 | --------------------------------------------------------------------------------