├── .gitignore ├── .merlin ├── .ocp-indent ├── B0.ml ├── BRZO ├── CHANGES.md ├── DEVEL.md ├── LICENSE.md ├── README.md ├── _tags ├── dev-env ├── doc ├── index.mld ├── manual.mld └── packaging.mld ├── opam ├── pkg ├── META └── pkg.ml ├── sample ├── gen.sh ├── intro.mld ├── publish.ml └── setup.sh ├── src ├── gh_pages_amend.ml ├── odig_main.ml ├── odig_odoc.ml ├── odig_odoc.mli ├── odig_odoc_page.ml ├── odig_odoc_page.mli ├── odig_support.ml ├── odig_support.mli └── odig_support.mllib └── themes ├── dark ├── fonts ├── manual.css ├── odoc.css └── theme.css ├── default ├── fonts ├── manual.css ├── odoc.css └── theme.css ├── fonts ├── DejaVuSansMono-Bold.woff2 ├── DejaVuSansMono-BoldOblique.woff2 ├── DejaVuSansMono-Oblique.woff2 ├── DejaVuSansMono.woff2 ├── PTC55F.woff2 ├── PTC75F.woff2 ├── PTS55F.woff2 ├── PTS56F.woff2 ├── PTS75F.woff2 ├── PTS76F.woff2 └── fonts.css ├── gruvbox.dark ├── fonts ├── manual.css ├── odoc.css └── theme.css ├── gruvbox.light ├── fonts ├── manual.css ├── odoc.css └── theme.css ├── gruvbox ├── fonts ├── manual.css ├── odoc.css └── theme.css ├── light ├── fonts ├── manual.css ├── odoc.css └── theme.css ├── manual.css ├── ocamldoc.css ├── odoc.css ├── solarized.dark ├── fonts ├── manual.css ├── odoc.css └── theme.css ├── solarized.light ├── fonts ├── manual.css ├── odoc.css └── theme.css └── solarized ├── fonts ├── manual.css ├── odoc.css └── theme.css /.gitignore: -------------------------------------------------------------------------------- 1 | _b0 2 | _opam 3 | _build 4 | tmp 5 | *.install 6 | -------------------------------------------------------------------------------- /.merlin: -------------------------------------------------------------------------------- 1 | PKG cmdliner b0.kit 2 | S src 3 | S test 4 | B _b0/** 5 | B _build/src/** -------------------------------------------------------------------------------- /.ocp-indent: -------------------------------------------------------------------------------- 1 | strict_with=always,match_clause=4,strict_else=never 2 | -------------------------------------------------------------------------------- /B0.ml: -------------------------------------------------------------------------------- 1 | open B0_kit.V000 2 | 3 | (* OCaml library names *) 4 | 5 | let b0_file = B0_ocaml.libname "b0.file" 6 | let b0_kit = B0_ocaml.libname "b0.kit" 7 | let b0_memo = B0_ocaml.libname "b0.memo" 8 | let b0_std = B0_ocaml.libname "b0.std" 9 | let cmdliner = B0_ocaml.libname "cmdliner" 10 | 11 | let odig_support = B0_ocaml.libname "odig.support" 12 | 13 | (* Units *) 14 | 15 | let odig_support_lib = 16 | let doc = "odig support library" in 17 | let srcs = 18 | [`Dir ~/"src"; `X ~/"src/gh_pages_amend.ml"; `X ~/"src/odig_main.ml"] 19 | in 20 | let requires = [ b0_std; b0_memo; b0_file; b0_kit; ] in 21 | B0_ocaml.lib odig_support ~srcs ~requires ~doc 22 | 23 | let odig_tool = 24 | let srcs = [`File ~/"src/odig_main.ml"] in 25 | let requires = [ cmdliner; b0_std; b0_memo; b0_file; b0_kit; odig_support ] in 26 | B0_ocaml.exe "odig" ~public:true ~requires ~srcs ~doc:"odig tool" 27 | 28 | let gh_pages_amend = 29 | let doc = "GitHub pages publication tool" in 30 | let requires = [ cmdliner; b0_std; b0_memo; b0_file; b0_kit ] in 31 | let srcs = [`File ~/"src/gh_pages_amend.ml"] in 32 | B0_ocaml.exe "gh-pages-amend" ~doc ~requires ~srcs 33 | 34 | (* Testing *) 35 | 36 | let publish_sample = 37 | let srcs = [ `File ~/"sample/publish.ml" ] in 38 | let requires = [ cmdliner; b0_std; b0_file (* For B0_cli *) ; b0_kit ] in 39 | B0_ocaml.exe "publish-sample" ~requires ~srcs ~doc:"Publish sample tool" 40 | 41 | (* Packs *) 42 | 43 | let default = 44 | let meta = 45 | B0_meta.empty 46 | |> ~~ B0_meta.authors ["The odig programmers"] 47 | |> ~~ B0_meta.maintainers ["Daniel Bünzli "] 48 | |> ~~ B0_meta.homepage "https://erratique.ch/software/odig" 49 | |> ~~ B0_meta.online_doc "https://erratique.ch/software/odig/doc" 50 | |> ~~ B0_meta.description_tags 51 | ["build"; "dev"; "doc"; "meta"; "packaging"; "org:erratique"; 52 | "org:b0-system"] 53 | |> ~~ B0_meta.licenses 54 | ["ISC"; "LicenseRef-ParaType-Free-Font-License"; 55 | "LicenseRef-DejaVu-fonts"] 56 | |> ~~ B0_meta.repo "git+https://erratique.ch/repos/odig.git" 57 | |> ~~ B0_meta.issues "https://github.com/b0-system/odig/issues" 58 | |> B0_meta.tag B0_opam.tag 59 | |> ~~ B0_opam.build 60 | {|[["ocaml" "pkg/pkg.ml" "build" "--dev-pkg" "%{dev}%"]]|} 61 | |> ~~ B0_opam.depends 62 | [ "ocaml", {|>= "4.08"|}; 63 | "ocamlfind", {|build|}; 64 | "ocamlbuild", {|build|}; 65 | "topkg", {|build & >= "1.0.3"|}; 66 | "cmdliner", {|>= "1.1.0"|}; 67 | "odoc", {|>= "2.0.0" |}; 68 | "b0", {|= "0.0.5"|}; ] 69 | in 70 | B0_pack.make "default" ~doc:"odig package" ~meta ~locked:true @@ 71 | B0_unit.list () 72 | -------------------------------------------------------------------------------- /BRZO: -------------------------------------------------------------------------------- 1 | (srcs-x B0.ml sample themes src/gh_pages_amend.ml pkg) -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - Improve built-in `index.mld` of the `ocaml` package to adapt 4 | to new upstream install structure and provide better access 5 | to stdlib modules. 6 | - Fix race condition in `odoc compiler-deps` invocations. Thanks to 7 | Raphaël Proust and José Nogueira for reporting (#77). 8 | - Track b0. `odig log --errors` becomes `odig log --failed`. 9 | 10 | v0.0.9 2023-06-04 Zagreb 11 | ------------------------ 12 | 13 | - CSS tweaks for record defs. 14 | - Track b0. 15 | - `odig doc -f`: do not quote paths (#68). 16 | 17 | 18 | v0.0.8 2022-02-09 La Forclaz (VS) 19 | --------------------------------- 20 | 21 | - Support Cmdliner 1.1.0. 22 | - Exit code 3 is now reported as 123. 23 | 24 | 25 | v0.0.7 2021-10-09 Zagreb 26 | ------------------------ 27 | 28 | - Stylesheet support for odoc 2.0.0. 29 | - `--index-intro` option. Fix option no longer interpreting 30 | relative files w.r.t. the cwd. 31 | - Add `--index-toc` option, to specify the package index table of 32 | content. If you used to define a table of contents in the 33 | `--index-intro` fragment you now need to define it via this 34 | option. The contents goes into the `odoc-toc` `nav` element. 35 | 36 | v0.0.6 2021-02-11 La Forclaz (VS) 37 | --------------------------------- 38 | 39 | - Stylesheets. Change strategy to make code spans unbreakable. 40 | The previous way broke Chrome in-page search. 41 | - Track `b0` changes. 42 | - Update link to OCaml manual (#59). 43 | - Require OCaml >= 4.08.0 44 | 45 | v0.0.5 2020-03-11 La Forclaz (VS) 46 | --------------------------------- 47 | 48 | - Rework the `odoc-theme` command. The `set` command now 49 | unconditionally writes to `~/.conf/odig/odoc-theme` and sets the 50 | theme for generated doc (the `--default` flag no longer exists). 51 | The `default` command is renamed to `get`, a `--config` option is 52 | added to get the theme actually written in the configuration file. 53 | - Add theme `odig.default`, `gruvbox` and `solarized`. These themes 54 | automatically switch between their corresponding light or dark 55 | version acccording to the user browser preference (#54). 56 | - Make `odig.default` the default theme instead of `odoc.default`. 57 | - Generate package index page even if some package fails (#57). 58 | - Hide anchoring links to screen readers on odig generated pages (#55). 59 | - Remove the `--trace` option of `odig odoc` and corresponding 60 | `ODIG_ODOC_TRACE` variable for generating a build log in Event trace 61 | format. See the `odig log` command. Use `odig log --trace-event` to 62 | generate what `--trace` did. 63 | - For consistency with other tools, options `--{cache,doc,lib,share}dir` 64 | are renamed to `--{cache,doc,lib,share}-dir` and corresponding 65 | environment variable from `ODIG_{CACHE,DOC,LIB,SHARE}DIR` to 66 | `ODIG_{CACHE,DOC,LIB,SHARE}_DIR`. 67 | - mld only packages: work around `odoc html-deps` bug (#50). 68 | - Package landing pages: fix cache invalidation. In particular opam metadata 69 | changes did not retrigger a rebuild. 70 | - `gh-pages-amend` tool, add a `--cname-file` option to set 71 | the `CNAME` file in gh-pages. 72 | - Fix `META` file (#52). Thanks to Kye W. Shi for the report. 73 | - Fix 4.08 `Pervasives` deprecation. 74 | - Require OCaml >= 4.05.0 75 | 76 | 77 | v0.0.4 2019-03-08 La Forclaz (VS) 78 | --------------------------------- 79 | 80 | - Support for odoc manuals (`.mld` files) and package page customization 81 | (`index.mld` file) (#31, #18). See the packaging conventions; if you are 82 | using `dune` and already authoring `.mld` files the right thing should 83 | be done automatically install-wise. 84 | - Support for odoc themes (#21). Themes can be distributed via `opam`, see 85 | command `odig odoc-theme` and the packaging conventions in `odig doc odig`. 86 | - Support for best-effort OCaml manual theming. Themes can provide a stylesheet 87 | to style the local manual installed by the `ocaml-manual` package and linked 88 | from the generated documentation sets. 89 | - Support for customizing the title and introduction of the package list 90 | page (#19). See the `--index-title` and `--index-intro` options of 91 | `odig odoc`. 92 | - Add `gh-pages-amend` a tool to easily push documentation sets on 93 | GitHub pages (see the odig manual and `--help` for details). 94 | - The `opam` metadata support needs an `opam` v2 binary in your `PATH`. 95 | - The odoc API documentation generation support needs an `odoc` v1.4.0 96 | binary in your `PATH`. 97 | - `odig doc` exit with non-zero on unknown package (#34). 98 | - `odig doc` add `-u` option to guarantee docset freshness (#4). 99 | - Depend only on `cmdliner` and `b0`. Drop dependency on `compiler-libs`, 100 | `rresult`, `asetmap`, `fpath`, `logs`, `mtime`, `bos`, `webbrowser` and 101 | `opam-format`. 102 | 103 | ### Removals 104 | 105 | - The best-effort `ocamldoc` support and corresponding command are dropped. 106 | - The `metagen` and `linkable` experimental tools are gone. 107 | - The data-driven toplevel loaders are gone. See the 108 | [`omod`](https://erratique.ch/software/omod) project if your 109 | are interested in this. 110 | - Removed JSON output from the commands that supported it. 111 | - The `help` command is dropped. Documentation is now in `odig`'s API 112 | docs and is where the manual and the packaging conventions can be 113 | found. Consult `odig doc odig`. 114 | - The `--docdir-href` option of `odig odoc` no longer exists. The 115 | docset in `$(odig cache path)/html` is self-contained and can be 116 | published as is (provided you follow symlinks). 117 | - The `authors`, `deps`, `maintainers`, `tags`, `version` and `repo` commands 118 | are gone but the lookups are available via the `show` command. 119 | - The `homepage`, `issues` and `online-doc` commands are available via 120 | the `show` and `browse` commands. 121 | - The `cobjs`, `graph` and `guess-deps` commands are dropped. 122 | 123 | v0.0.3 2017-10-31 Zagreb 124 | ------------------------ 125 | 126 | - Fix obscure build bug on 4.06.0 (#32) 127 | 128 | v0.0.2 2017-05-31 Cambridge (UK) 129 | -------------------------------- 130 | 131 | - Added experimental data-driven toplevel loaders. 132 | - The `odoc` API documentation is shown by default on `odig doc`. 133 | - The `mli`, `cmi`, `cmo`, `cmti`, `cmx` and `cmt` commands are grouped in 134 | the `cobjs` command. 135 | - Track latest cmdliner and mtime. 136 | 137 | v0.0.1 2016-09-23 Zagreb 138 | ------------------------ 139 | 140 | First release. The ocamldoc release. 141 | -------------------------------------------------------------------------------- /DEVEL.md: -------------------------------------------------------------------------------- 1 | Build and test 2 | -------------- 3 | 4 | source dev-env 5 | topkg build # or brzo -b 6 | odig # Uses a cache in /tmp/odig-cache 7 | 8 | Publish sample to gh-pages 9 | -------------------------- 10 | 11 | ``` 12 | cd sample 13 | ./setup.sh 14 | eval $(opam env) 15 | ./gen.sh 16 | cd .. 17 | topkg run publish 18 | ``` 19 | 20 | Working on themes 21 | ----------------- 22 | 23 | An easy way is to generate a representative docset and then: 24 | 25 | mkdir -p $(opam var share)/odig.dev 26 | ln -s $(pwd)/themes $(opam var share)/odig.dev/odoc-theme 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Odig copyrights 2 | 3 | The odig project is covered by the following copyrights. 4 | 5 | * The woff2 PT Sans fonts in [`theme/fonts`](theme/fonts) 6 | are covered by [this license](#pt-sans-fonts). 7 | * The woff2 DejaVu fonts in [`theme/fonts`](theme/fonts) 8 | license are covered by [this license](#dejavu-fonts). 9 | 10 | All the rest is covered by the following copyright. 11 | 12 | ``` 13 | Copyright (c) 2016 The odig programmers 14 | 15 | Permission to use, copy, modify, and/or distribute this software for any 16 | purpose with or without fee is hereby granted, provided that the above 17 | copyright notice and this permission notice appear in all copies. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 20 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 21 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 22 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 24 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 25 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 26 | ``` 27 | 28 | 29 | ## PT Sans fonts 30 | 31 | ``` 32 | Copyright (c) 2009, ParaType Ltd. All Rights Reserved. 33 | 34 | LICENSING AGREEMENT 35 | for the fonts with Original Name: PT Sans, PT Serif 36 | Version 1.2 - December 23, 2010 37 | 38 | GRANT OF LICENSE 39 | ParaType Ltd grants you the right to use, copy, modify the fonts and distribute 40 | modified and unmodified copies of the fonts by any means, including placing 41 | on Web servers for free downloading, embedding in documents and Web pages, 42 | bundling with commercial and non commercial products, if it does not conflict 43 | with the conditions listed below: 44 | 45 | - You may bundle the font with commercial software, but you may not sell the 46 | fonts by themselves. They are free. 47 | 48 | - You may distribute the fonts in modified or unmodified version only together 49 | with this Licensing Agreement and with above copyright notice. You have no 50 | right to modify the text of Licensing Agreement. It can be placed in a separate 51 | text file or inserted into the font file, but it must be easily viewed by users. 52 | 53 | - You may not distribute modified version of the font under the Original name 54 | or ‡ combination of Original name with any other words without explicit written 55 | permission from ParaType. 56 | 57 | TERMINATION & TERRITORY 58 | This license has no limits on time and territory, but it becomes null and void 59 | if any of the above conditions are not met. 60 | 61 | DISCLAIMER 62 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 63 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY 64 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 65 | PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, 66 | OR OTHER RIGHT. IN NO EVENT SHALL PARATYPE BE LIABLE FOR ANY 67 | CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, 68 | INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN 69 | AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 70 | OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER 71 | DEALINGS IN THE FONT SOFTWARE. 72 | 73 | ParaType Ltd 74 | http://www.paratype.ru 75 | 76 | ``` 77 | 78 | ## DejaVu fonts 79 | 80 | ``` 81 | Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. 82 | Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) 83 | 84 | 85 | Bitstream Vera Fonts Copyright 86 | ------------------------------ 87 | 88 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is 89 | a trademark of Bitstream, Inc. 90 | 91 | Permission is hereby granted, free of charge, to any person obtaining a copy 92 | of the fonts accompanying this license ("Fonts") and associated 93 | documentation files (the "Font Software"), to reproduce and distribute the 94 | Font Software, including without limitation the rights to use, copy, merge, 95 | publish, distribute, and/or sell copies of the Font Software, and to permit 96 | persons to whom the Font Software is furnished to do so, subject to the 97 | following conditions: 98 | 99 | The above copyright and trademark notices and this permission notice shall 100 | be included in all copies of one or more of the Font Software typefaces. 101 | 102 | The Font Software may be modified, altered, or added to, and in particular 103 | the designs of glyphs or characters in the Fonts may be modified and 104 | additional glyphs or characters may be added to the Fonts, only if the fonts 105 | are renamed to names not containing either the words "Bitstream" or the word 106 | "Vera". 107 | 108 | This License becomes null and void to the extent applicable to Fonts or Font 109 | Software that has been modified and is distributed under the "Bitstream 110 | Vera" names. 111 | 112 | The Font Software may be sold as part of a larger software package but no 113 | copy of one or more of the Font Software typefaces may be sold by itself. 114 | 115 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 116 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, 117 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, 118 | TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME 119 | FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING 120 | ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, 121 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 122 | THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE 123 | FONT SOFTWARE. 124 | 125 | Except as contained in this notice, the names of Gnome, the Gnome 126 | Foundation, and Bitstream Inc., shall not be used in advertising or 127 | otherwise to promote the sale, use or other dealings in this Font Software 128 | without prior written authorization from the Gnome Foundation or Bitstream 129 | Inc., respectively. For further information, contact: fonts at gnome dot 130 | org. 131 | 132 | Arev Fonts Copyright 133 | ------------------------------ 134 | 135 | Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. 136 | 137 | Permission is hereby granted, free of charge, to any person obtaining 138 | a copy of the fonts accompanying this license ("Fonts") and 139 | associated documentation files (the "Font Software"), to reproduce 140 | and distribute the modifications to the Bitstream Vera Font Software, 141 | including without limitation the rights to use, copy, merge, publish, 142 | distribute, and/or sell copies of the Font Software, and to permit 143 | persons to whom the Font Software is furnished to do so, subject to 144 | the following conditions: 145 | 146 | The above copyright and trademark notices and this permission notice 147 | shall be included in all copies of one or more of the Font Software 148 | typefaces. 149 | 150 | The Font Software may be modified, altered, or added to, and in 151 | particular the designs of glyphs or characters in the Fonts may be 152 | modified and additional glyphs or characters may be added to the 153 | Fonts, only if the fonts are renamed to names not containing either 154 | the words "Tavmjong Bah" or the word "Arev". 155 | 156 | This License becomes null and void to the extent applicable to Fonts 157 | or Font Software that has been modified and is distributed under the 158 | "Tavmjong Bah Arev" names. 159 | 160 | The Font Software may be sold as part of a larger software package but 161 | no copy of one or more of the Font Software typefaces may be sold by 162 | itself. 163 | 164 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 165 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 166 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 167 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL 168 | TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 169 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 170 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 171 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 172 | OTHER DEALINGS IN THE FONT SOFTWARE. 173 | 174 | Except as contained in this notice, the name of Tavmjong Bah shall not 175 | be used in advertising or otherwise to promote the sale, use or other 176 | dealings in this Font Software without prior written authorization 177 | from Tavmjong Bah. For further information, contact: tavmjong @ free 178 | . fr. 179 | 180 | TeX Gyre DJV Math 181 | ----------------- 182 | Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. 183 | 184 | Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski 185 | (on behalf of TeX users groups) are in public domain. 186 | 187 | Letters imported from Euler Fraktur from AMSfonts are (c) American 188 | Mathematical Society (see below). 189 | Bitstream Vera Fonts Copyright 190 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera 191 | is a trademark of Bitstream, Inc. 192 | 193 | Permission is hereby granted, free of charge, to any person obtaining a copy 194 | of the fonts accompanying this license (“Fonts”) and associated 195 | documentation 196 | files (the “Font Software”), to reproduce and distribute the Font Software, 197 | including without limitation the rights to use, copy, merge, publish, 198 | distribute, 199 | and/or sell copies of the Font Software, and to permit persons to whom 200 | the Font Software is furnished to do so, subject to the following 201 | conditions: 202 | 203 | The above copyright and trademark notices and this permission notice 204 | shall be 205 | included in all copies of one or more of the Font Software typefaces. 206 | 207 | The Font Software may be modified, altered, or added to, and in particular 208 | the designs of glyphs or characters in the Fonts may be modified and 209 | additional 210 | glyphs or characters may be added to the Fonts, only if the fonts are 211 | renamed 212 | to names not containing either the words “Bitstream” or the word “Vera”. 213 | 214 | This License becomes null and void to the extent applicable to Fonts or 215 | Font Software 216 | that has been modified and is distributed under the “Bitstream Vera” 217 | names. 218 | 219 | The Font Software may be sold as part of a larger software package but 220 | no copy 221 | of one or more of the Font Software typefaces may be sold by itself. 222 | 223 | THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 224 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, 225 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, 226 | TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME 227 | FOUNDATION 228 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, 229 | SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN 230 | ACTION 231 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR 232 | INABILITY TO USE 233 | THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. 234 | Except as contained in this notice, the names of GNOME, the GNOME 235 | Foundation, 236 | and Bitstream Inc., shall not be used in advertising or otherwise to promote 237 | the sale, use or other dealings in this Font Software without prior written 238 | authorization from the GNOME Foundation or Bitstream Inc., respectively. 239 | For further information, contact: fonts at gnome dot org. 240 | 241 | AMSFonts (v. 2.2) copyright 242 | 243 | The PostScript Type 1 implementation of the AMSFonts produced by and 244 | previously distributed by Blue Sky Research and Y&Y, Inc. are now freely 245 | available for general use. This has been accomplished through the 246 | cooperation 247 | of a consortium of scientific publishers with Blue Sky Research and Y&Y. 248 | Members of this consortium include: 249 | 250 | Elsevier Science IBM Corporation Society for Industrial and Applied 251 | Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS) 252 | 253 | In order to assure the authenticity of these fonts, copyright will be 254 | held by 255 | the American Mathematical Society. This is not meant to restrict in any way 256 | the legitimate use of the fonts, such as (but not limited to) electronic 257 | distribution of documents containing these fonts, inclusion of these fonts 258 | into other public domain or commercial font collections or computer 259 | applications, use of the outline data to create derivative fonts and/or 260 | faces, etc. However, the AMS does require that the AMS copyright notice be 261 | removed from any derivative versions of the fonts which have been altered in 262 | any way. In addition, to ensure the fidelity of TeX documents using Computer 263 | Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces, 264 | has requested that any alterations which yield different font metrics be 265 | given a different name. 266 | ``` 267 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | odig — Lookup documentation of installed OCaml packages 2 | ------------------------------------------------------------------------------- 3 | %%VERSION%% 4 | 5 | odig is a command line tool to lookup documentation of installed OCaml 6 | packages. It shows package metadata, readmes, change logs, licenses, 7 | cross-referenced `odoc` API documentation and manuals. 8 | 9 | odig is distributed under the ISC license. The theme fonts have their 10 | own [licenses](LICENSE.md). 11 | 12 | Homepage: https://erratique.ch/software/odig 13 | 14 | ## Installation 15 | 16 | odig can be installed with `opam`: 17 | 18 | opam install ocaml-manual odig 19 | 20 | If you don't use `opam` consult the [`opam`](opam) file for build 21 | instructions. 22 | 23 | ## Documentation 24 | 25 | A few commands to get you started: 26 | 27 | odig doc # Show API docs and manuals of installed packages 28 | odig readme odig # Consult the readme of odig 29 | odig changes odig # Consult the changelog of odig 30 | odig browse issues odig # Browse odig's issues. 31 | 32 | The manual and packaging conventions can be consulted [online][doc] or 33 | via `odig doc odig`. 34 | 35 | [doc]: https://b0-system.github.io/odig/doc/odig/ 36 | 37 | ## Sample odoc API documentation and manuals 38 | 39 | A sample output of generated API documentation and manuals on a 40 | best-effort maximal set of packages of the OCaml opam repository is 41 | available [here](https://b0-system.github.io/odig/doc/). 42 | 43 | The [Vg](https://b0-system.github.io/odig/doc/vg/Vg/index.html) module 44 | and its sub-modules is a good example to look at, it has a good mix of 45 | documentation cases. 46 | 47 | The different themes distributed with `odig` can be seen on the sample 48 | at the following addresses. 49 | 50 | * https://b0-system.github.io/odig/doc/ 51 | * https://b0-system.github.io/odig/doc@odig.default/ 52 | * https://b0-system.github.io/odig/doc@odig.dark/ 53 | * https://b0-system.github.io/odig/doc@odig.light/ 54 | * https://b0-system.github.io/odig/doc@odig.gruvbox/ 55 | * https://b0-system.github.io/odig/doc@odig.gruvbox.dark/ 56 | * https://b0-system.github.io/odig/doc@odig.gruvbox.light/ 57 | * https://b0-system.github.io/odig/doc@odig.solarized/ 58 | * https://b0-system.github.io/odig/doc@odig.solarized.dark/ 59 | * https://b0-system.github.io/odig/doc@odig.solarized.light/ 60 | * https://b0-system.github.io/odig/doc@odoc.default/ 61 | 62 | Note that for technical reasons the OCaml manual under these addresses 63 | is always themed according to `odig.default`. This is not what happens 64 | if you use `odig` for yourself, the manual will render according to 65 | your theme's color scheme. 66 | -------------------------------------------------------------------------------- /_tags: -------------------------------------------------------------------------------- 1 | true : bin_annot, safe_string 2 | true : package(b0.std b0.file b0.kit) 3 | 4 | <_opam> : -traverse 5 | <_b0> : -traverse 6 | : -traverse 7 | 8 | : include 9 | : package(cmdliner unix) 10 | : package(unix) 11 | : package(unix) 12 | : include 13 | : include 14 | -------------------------------------------------------------------------------- /dev-env: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # To run and test the odig built from the repo as if was in your PATH 4 | # issue: 5 | # 6 | # source dev-env 7 | # 8 | # odigs current opam prefix and uses a cache in /tmp/odig-cache. 9 | 10 | P=$(opam var prefix) 11 | export ODIG_CACHE_DIR=/tmp/odig-cache 12 | export ODIG_LIB_DIR=$P/lib 13 | export ODIG_DOC_DIR=$P/doc 14 | export ODIG_SHARE_DIR=$P/share 15 | 16 | ODIG_BRZO=$(brzo --path) 17 | ODIG_OCB=$(pwd)/_build/src/brzo_bin.native 18 | 19 | odig () 20 | { 21 | if [ -e $ODIG_BRZO ]; then 22 | $ODIG_BRZO $@ 23 | elif [ -e $ODIG_OCB ]; then 24 | $ODIG_OCB $@ 25 | else 26 | echo "No dev build found" 27 | fi 28 | } 29 | -------------------------------------------------------------------------------- /doc/index.mld: -------------------------------------------------------------------------------- 1 | {0 Odig {%html: %%VERSION%%%}} 2 | 3 | Odig is a command line tool to lookup documentation of installed OCaml 4 | packages. 5 | 6 | Consult the {{!page-manual}manual} or the {{!page-packaging}packaging 7 | conventions}. 8 | 9 | {1:quick_start Quick start} 10 | 11 | A few commands to get you started: 12 | 13 | {v 14 | odig doc # Show API docs and manuals of installed packages 15 | odig readme odig # Consult the readme of odig 16 | odig changes odig # Consult the changelog of odig 17 | odig browse issues odig # Browse odig's issues. 18 | v} 19 | 20 | {1:api API} 21 | 22 | This is an unstable API subject to change even between minor versions 23 | of the tool. Use at your own risk. 24 | 25 | {!modules: Odig_support Odig_odoc Odig_odoc_page} 26 | -------------------------------------------------------------------------------- /doc/manual.mld: -------------------------------------------------------------------------------- 1 | {0:manual Odig manual} 2 | 3 | [odig] helps you to access information about installed OCaml 4 | packages. The following shows basic [odig] usage, to understand how 5 | odig looks up that information see the {{!page-packaging}packaging 6 | conventions}. 7 | 8 | In this manual we use [odig] itself as the example package. 9 | 10 | {1:meta Package metadata} 11 | 12 | Basic information about packages and their metadata is available with 13 | the [pkg] command. 14 | 15 | {v 16 | odig pkg # List recognized packages and their version 17 | odig pkg odig --long # Show odig's package full metadata 18 | v} 19 | 20 | Metadata fields of packages can be queried individually with the [show] 21 | command: 22 | 23 | {v 24 | odig show repo odig # Show odig's repository address 25 | odig show homepage odig # Show odig's homepage address 26 | odig show license odig # Show odig's license(s) tags 27 | v} 28 | 29 | A few URI metadata fields can be opened directly in your browser with 30 | the [browse] command: 31 | 32 | {v 33 | odig browse homepage odig # Open odig's homepage in your browser 34 | odig browse issues odig # Open odig's issue tracker in your browser 35 | odig browse online-doc odig # Open odig's online docs in your browser 36 | v} 37 | 38 | {1:distrib Package distribution documentation} 39 | 40 | If the package installed them [odig] provides instant access to the 41 | the readme, change log and license files of a package via: 42 | 43 | {v 44 | odig readme odig # Show the readme of odig 45 | odig changes odig # Show the changes of odig 46 | odig license odig # Show the license of odig 47 | v} 48 | 49 | If you want to access the file paths rather than the content use 50 | [show]: 51 | 52 | {v 53 | odig show readme-files odig # Show path to the readme of odig 54 | odig show changes-files odig # Show path the changes of odig 55 | odig show license-files odig # Show path the license of odig 56 | v} 57 | 58 | {1:odoc_docs Package odoc API documentation and manuals} 59 | 60 | To open the HTML package list or the page of a package in 61 | your browser use: 62 | 63 | {v 64 | odig doc # Package list 65 | odig doc odig # Doc for the odig package 66 | v} 67 | 68 | In general if [odig doc] can't satisfy your request it will try to 69 | generate documentation for it unless prevented by the [-n] option. If 70 | the documentation for your request was already generated it will open 71 | it without checking if it's up-to-date, use the option [-u] to guarantee 72 | it's fresh. 73 | 74 | {v 75 | odig doc -u # Up-to-date package list and package docs 76 | odig doc -u odig # Up-to-date doc for the odig package 77 | v} 78 | 79 | If you only want to generate the documentation use the `odoc` 80 | command: 81 | 82 | {v 83 | odig odoc # Generate API docs and manuals for all packages 84 | odig odoc odig # Generate API docs and manuals for the odig package 85 | v} 86 | 87 | If the OCaml manual is installed as the [ocaml-manual] package 88 | (e.g. via [opam install ocaml-manual]), the local copy gets linked 89 | from the package list page. 90 | 91 | {1:odoc_themes odoc API documentation theme} 92 | 93 | The way new themes can be installed is described 94 | {{!page-packaging.theme_install}here}. 95 | 96 | The [odoc] theme used for odoc API documentation and manuals can be 97 | changed via the [odoc-theme] command: 98 | 99 | {v 100 | odig odoc-theme list # List available themes 101 | odig odoc-theme default # Show the default theme name 102 | v} 103 | 104 | The default theme is used and restored whenever a documentation 105 | generation action occurs either through [odig doc] or [odig odoc]. It 106 | is defined in order: 107 | 108 | {ol 109 | {- On the command line via the [--odoc-theme] option.} 110 | {- In the [ODIG_ODOC_THEME] environment variable.} 111 | {- In the [~/.config/odig/odoc-theme] file.} 112 | {- By [odoc.default]}} 113 | 114 | The theme can be set on the current documentation set via the [set] 115 | action: 116 | 117 | {v 118 | odig odoc-theme set odig.dark # Use the theme odig.dark 119 | odig odoc-theme set # Use the default theme 120 | v} 121 | 122 | However whenever a documentation generation action occurs this 123 | restores the default theme. If you want to persist your choice, use 124 | the [--default] option when you [set] the theme; this writes the theme 125 | name to [~/.config/odig/odoc-theme]. 126 | 127 | {v 128 | odig odoc-theme set --default odig.dark # Use theme and set as default 129 | v} 130 | 131 | {1:publish Publishing odoc documentation sets} 132 | 133 | In general the self-contained bundle of generated HTML files is 134 | available in: 135 | 136 | {v 137 | $(odig cache path)/html 138 | v} 139 | 140 | It does however have a few absolute symlinks that point outside the 141 | hierarchy so if you want to archive or copy the documentation set over 142 | to another machine make sure you follow the symlinks. 143 | 144 | To publish the documentation for a list of packages [$PKGS] write an 145 | [intro.mld] file that briefly documents the purpose of the 146 | documentation set, it will be used as a preamble on the package list 147 | page. You can also define your own table of contents on this page in a 148 | [toc.mld] file whose content will end up in the [nav.odoc-toc] 149 | element; otherwise a default table of content is generated. Continue 150 | with: 151 | 152 | {v 153 | export PKGS=... 154 | opam switch create . 155 | opam install odig $PKGS 156 | odig odoc -v --odoc-theme=$MYTHEME \ 157 | --index-title='My docset' --index-intro=intro.mld \ 158 | --index-toc=toc.mld \ 159 | --no-pkg-deps $PKGS 160 | v} 161 | 162 | If the tag index on the package list page is a bit overkill for your 163 | documentation set, use the [--no-tag-index] option to suppress it. 164 | 165 | Provided our webserver follows symlinks, you are now ready to publish 166 | your documentation set: 167 | 168 | {v 169 | ln -s $(odig cache path)/html /var/www/my-docset 170 | v} 171 | 172 | {2:publish_github Publishing on GitHub} 173 | 174 | To publish your documentation set on GitHub you can use the 175 | [gh-pages-amend] tool distributed with [odig]. In the git repository 176 | for which you want to publish the documentation set invoke: 177 | 178 | {v 179 | gh-pages-amend $(odig cache path)/html doc 180 | v} 181 | 182 | this fetches the [gh-pages] on the [origin] remote, replaces the 183 | directory [doc] with the contents of [$(odig cache path)/html] by 184 | amending the last commit and pushes it back on the remote. The 185 | [gh-pages] branch is created if it does not exist and your current 186 | working directory is left untouched by the procedure. 187 | 188 | See [gh-pages-amend --help] for more information and options. -------------------------------------------------------------------------------- /doc/packaging.mld: -------------------------------------------------------------------------------- 1 | {0:conventions Odig packaging conventions} 2 | 3 | Packages following these convention maximize [odig]'s mining 4 | capabilities. 5 | 6 | {1:package_install Package install structure} 7 | 8 | [odig] assumes all OCaml packages are installed in a library prefix 9 | called [LIBDIR] and have their distribution documentation installed in 10 | a prefix called [DOCDIR]. The values of these variables are 11 | automatically derived from the binary's installation path and can be 12 | overriden by environment variables, see [odig conf --help] for more 13 | information. 14 | 15 | All direct subdirectories [LIBDIR/P] define a package name [P] whose 16 | corresponding documentation directory is assumed to be [DOCDIR/P]. The 17 | library path of the [ocaml] package is determined by [ocamlc -where], 18 | not by [LIBDIR/ocaml] (for supporting opam system switches). 19 | 20 | {1:meta Metadata determination} 21 | 22 | Package metadata for package [P] is always read from a [LIBDIR/P/opam] 23 | file which must be a valid {{:https://opam.ocaml.org/}opam} file. If 24 | present, the following fields are consulted and used by odig to define 25 | its metadata data fields: 26 | 27 | {ul 28 | {- [authors:] the authors} 29 | {- [bug-reports:] the issues URI} 30 | {- [depends:] the package's declared dependencies (only informative)} 31 | {- [dev-repo:] the repository URI} 32 | {- [doc:] the online documentation URI} 33 | {- [homepage:] the homepage URI} 34 | {- [license:] license tags} 35 | {- [maintainers:] the maintainers} 36 | {- [synopsis:] the package synopsis} 37 | {- [tags:] classification tags} 38 | {- [version:] the version string}} 39 | 40 | {1:distrib Distribution documentation} 41 | 42 | The distribution documentation for a package [P] is determined by {e 43 | caseless} matching files in [DOCDIR/P]. The following matches are 44 | performed; multiple files are allowed to match, * denotes zero or more 45 | characters. 46 | 47 | {ul 48 | {- [DOCDIR/P/README*], determines readme files} 49 | {- [DOCDIR/P/CHANGE*], [DOCDIR/P/HISTORY*], [DOCDIR/P/NEWS*], 50 | determines change log files} 51 | {- [DOCDIR/P/LICENSE*], determines license files}} 52 | 53 | {1:odoc_api_and_manual odoc API documentation and manual} 54 | 55 | The API documentation and manual of a package [P] is generated by 56 | considering all [cmi] files in the hierarchy [LIBDIR/P], the files in 57 | [DOCDIR/P/odoc-assets] and the files in [DOCDIR/P/odoc-pages] 58 | 59 | For each [cmi] file if a corresponding [cmti] file is found at the 60 | same location it is used to generate the documentation of the module 61 | with [odoc]; if not, an existing [cmt] file is used and lacking this the 62 | [cmi] file is used as a last resort. 63 | 64 | Any file in [DOCDIR/P/odoc-assets] is copied over in an [_assets] 65 | directory at the root of the package's generated API documentation. 66 | 67 | Any [.mld] file in [DOCDIR/P/odoc-pages] is compiled with [odoc] to a 68 | manual page. In particular the file [index.mld] is used to define — 69 | modulo the chrome added by odig — the content of the package's landing 70 | page. If [index.mld] has a level 0 heading it is stripped. 71 | 72 | If no [index.mld] is provided the package's API modules — those 73 | installed with [cmi] files and without [__] in their name — are 74 | listed on the page. 75 | 76 | Finally if the [DOCDIR/ocaml-manual] package exists it is copied over 77 | to the documentation set, linked (and possibly 78 | {{!theme_install}themed}) from the package list page. 79 | 80 | {1:theme_install Odoc theme install structure} 81 | 82 | [odig] assumes themes are installed in a [share] directory called 83 | [SHAREDIR]. The value of this variable is automatically derived from 84 | the binary's installation path and can be overriden by an environment 85 | variable, see [odig conf --help] for more information. 86 | 87 | Each subdirectory [SHAREDIR/P/odoc-theme/T] defines a theme identified 88 | by [P.T]. This subdirectory is expected to at least hold an [odoc.css] 89 | file with the theme layout for the odoc documentation set. 90 | 91 | The theme can also contain a [manual.css] file in which case it is 92 | used to style the local copy of the OCaml manual held in 93 | [DOCDIR/ocaml-manual] (e.g. as installed via [opam install 94 | ocaml-manual]) and linked from the documentation set package list 95 | page. 96 | -------------------------------------------------------------------------------- /opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | name: "odig" 3 | synopsis: "Lookup documentation of installed OCaml packages" 4 | description: """\ 5 | odig is a command line tool to lookup documentation of installed OCaml 6 | packages. It shows package metadata, readmes, change logs, licenses, 7 | cross-referenced `odoc` API documentation and manuals. 8 | 9 | odig is distributed under the ISC license. The theme fonts have their 10 | own [licenses](LICENSE.md). 11 | 12 | Homepage: https://erratique.ch/software/odig""" 13 | maintainer: "Daniel Bünzli " 14 | authors: "The odig programmers" 15 | license: [ 16 | "ISC" "LicenseRef-ParaType-Free-Font-License" "LicenseRef-DejaVu-fonts" 17 | ] 18 | tags: [ 19 | "build" "dev" "doc" "meta" "packaging" "org:erratique" "org:b0-system" 20 | ] 21 | homepage: "https://erratique.ch/software/odig" 22 | doc: "https://erratique.ch/software/odig/doc" 23 | bug-reports: "https://github.com/b0-system/odig/issues" 24 | depends: [ 25 | "ocaml" {>= "4.08"} 26 | "ocamlfind" {build} 27 | "ocamlbuild" {build} 28 | "topkg" {build & >= "1.0.3"} 29 | "cmdliner" {>= "1.1.0"} 30 | "odoc" {>= "2.0.0"} 31 | "b0" {= "0.0.5"} 32 | ] 33 | build: ["ocaml" "pkg/pkg.ml" "build" "--dev-pkg" "%{dev}%"] 34 | dev-repo: "git+https://erratique.ch/repos/odig.git" 35 | -------------------------------------------------------------------------------- /pkg/META: -------------------------------------------------------------------------------- 1 | description = "Lookup documentation of installed OCaml packages" 2 | version = "%%VERSION_NUM%%" 3 | requires = "" 4 | 5 | package "support" ( 6 | directory = "support" 7 | description = "odig support library" 8 | version = "%%VERSION_NUM%%" 9 | requires = "b0.std b0.memo b0.file b0.kit" 10 | archive(byte) = "odig_support.cma" 11 | archive(native) = "odig_support.cmxa" 12 | plugin(byte) = "odig_support.cma" 13 | plugin(native) = "odig_support.cmxs" 14 | exists_if = "odig_support.cma odig_support.cmxa" 15 | ) 16 | -------------------------------------------------------------------------------- /pkg/pkg.ml: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ocaml 2 | #use "topfind" 3 | #require "topkg" 4 | open Topkg 5 | 6 | let theme t = 7 | let src f = strf "themes/%s/%s" t f in 8 | let dst f = strf "odoc-theme/%s/%s" t f in 9 | let mv f = Pkg.share (src f) ~dst:(dst f) in 10 | Pkg.flatten 11 | [ mv "odoc.css"; mv "theme.css"; mv "manual.css"; 12 | mv "fonts/fonts.css"; 13 | mv "fonts/DejaVuSansMono-Bold.woff2"; 14 | mv "fonts/DejaVuSansMono-BoldOblique.woff2"; 15 | mv "fonts/DejaVuSansMono-Oblique.woff2"; 16 | mv "fonts/DejaVuSansMono.woff2"; 17 | mv "fonts/PTC55F.woff2"; 18 | mv "fonts/PTC75F.woff2"; 19 | mv "fonts/PTS55F.woff2"; 20 | mv "fonts/PTS56F.woff2"; 21 | mv "fonts/PTS75F.woff2"; 22 | mv "fonts/PTS76F.woff2"; ] 23 | 24 | let () = 25 | Pkg.describe "odig" @@ fun c -> 26 | Ok [ Pkg.mllib "src/odig_support.mllib"; 27 | Pkg.bin "src/odig_main" ~dst:"odig"; 28 | Pkg.bin "src/gh_pages_amend" ~dst:"gh-pages-amend"; 29 | Pkg.etc "themes/ocamldoc.css"; (* still there for topkg doc support *) 30 | Pkg.doc "doc/index.mld" ~dst:"odoc-pages/index.mld"; 31 | Pkg.doc "doc/manual.mld" ~dst:"odoc-pages/manual.mld"; 32 | Pkg.doc "doc/packaging.mld" ~dst:"odoc-pages/packaging.mld"; 33 | theme "default"; 34 | theme "dark"; 35 | theme "light"; 36 | theme "solarized"; 37 | theme "solarized.dark"; 38 | theme "solarized.light"; 39 | theme "gruvbox"; 40 | theme "gruvbox.dark"; 41 | theme "gruvbox.light"; ] 42 | -------------------------------------------------------------------------------- /sample/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | eval $(opam env) 3 | opam upgrade odoc odig 4 | 5 | OCAML_VERSION=$(opam info -f version ocaml) 6 | ODOC_VERSION=$(opam info -f source-hash odoc) 7 | ODIG_VERSION=$(opam info -f source-hash odig) 8 | 9 | sed -e "s/OCAML_VERSION/$OCAML_VERSION/g" \ 10 | -e "s/ODOC_VERSION/$ODOC_VERSION/g" \ 11 | -e "s/ODIG_VERSION/$ODIG_VERSION/g" intro.mld > intro-subst.mld 12 | 13 | odig odoc -v --odoc-theme=odig.default --index-title $OCAML_VERSION \ 14 | --index-intro=intro-subst.mld 15 | 16 | rm intro-subst.mld 17 | -------------------------------------------------------------------------------- /sample/intro.mld: -------------------------------------------------------------------------------- 1 | {0:sample OCaml OCAML_VERSION sample package documentation} 2 | 3 | Browse {{:#by-name}by name}, {{:#by-tag}by tag}, 4 | the {{:ocaml/Stdlib/index.html#modules}standard library} 5 | and the {{:ocaml-manual/index.html}OCaml manual}. 6 | 7 | {%html:

Odig showcase package sample using odoc ODOC_VERSION 8 | and odig ODIG_VERSION

9 | %} 10 | -------------------------------------------------------------------------------- /sample/publish.ml: -------------------------------------------------------------------------------- 1 | (*--------------------------------------------------------------------------- 2 | Copyright (c) 2019 The odig programmers. All rights reserved. 3 | SPDX-License-Identifier: ISC 4 | ---------------------------------------------------------------------------*) 5 | 6 | open B0_std 7 | open Result.Syntax 8 | 9 | let versions () = 10 | let run = Os.Cmd.run_out ~trim:true in 11 | let* odig = run Cmd.(tool "odig" % "--version") in 12 | let* odoc = run Cmd.(tool "odoc" % "--version") in 13 | Ok (Fmt.str "odig %s and odoc %s" odig odoc) 14 | 15 | let odig_html () = 16 | let cache_path = Cmd.(tool "odig" % "cache" % "path") in 17 | let* path = Os.Cmd.run_out ~trim:true cache_path in 18 | let htmldir = Fpath.(v path / "html") in 19 | let add_element _ f _ acc = f :: acc in 20 | let* fs = Os.Dir.fold ~recurse:false add_element htmldir [] in 21 | Ok (htmldir, fs) 22 | 23 | let odig_theme_list () = 24 | let themes = Cmd.(tool "odig" % "odoc-theme" % "list" % "--long") in 25 | let* themes = Os.Cmd.run_out ~trim:true themes in 26 | let parse_theme p = match String.cut_left ~sep:" " (String.trim p) with 27 | | None -> Fmt.failwith "%S: could not parse theme" p 28 | | Some (tn, path) -> Fpath.v ("doc@" ^ tn), Fpath.v path 29 | in 30 | Ok (List.map parse_theme (String.cuts_left ~sep:"\n" (String.trim themes))) 31 | 32 | let link_themes dir htmldir_contents themes = 33 | let theme_link dir htmldir_contents (tdir, tcontents) = 34 | let dir = Fpath.(dir // tdir) in 35 | let rec loop = function 36 | | [] -> 37 | let dst = Fpath.(dir / "_odoc-theme") in 38 | Os.Path.copy ~make_path:true 39 | ~recurse:true tcontents ~dst |> Result.error_to_failure; 40 | B0_github.Pages.update ~follow_symlinks:false (Some dir) ~dst:tdir 41 | | f :: fs -> 42 | if f = "_odoc-theme" then loop fs else 43 | let src = Fpath.(v ".." / "doc" / f) in 44 | let dst = Fpath.(dir / f) in 45 | let force = true and make_path = true in 46 | Os.Path.symlink ~force ~make_path ~src dst |> Result.error_to_failure; 47 | loop fs 48 | in 49 | let _bool = Os.Dir.create ~make_path:true dir |> Result.error_to_failure in 50 | loop htmldir_contents 51 | in 52 | try Ok (List.map (theme_link dir htmldir_contents) themes) with 53 | | Failure e -> Error e 54 | 55 | let pp_updated ppf = function 56 | | false -> Fmt.string ppf "No update to publish on" 57 | | true -> Fmt.string ppf "Published docs on" 58 | 59 | let publish color log_level new_commit remote branch = 60 | let styler = B0_std_cli.get_styler color in 61 | let log_level = B0_std_cli.get_log_level log_level in 62 | B0_std_cli.setup styler log_level ~log_spawns:Log.Debug; 63 | Log.if_error ~use:1 @@ 64 | let* versions = versions () in 65 | let* htmldir, htmldir_contents = odig_html () in 66 | Log.stdout (fun m -> 67 | m "Publishing %a" (Fmt.st' [`Fg `Green] Fpath.pp_quoted) htmldir); 68 | let* themes = odig_theme_list () in 69 | Result.join @@ 70 | Os.Dir.with_tmp @@ fun dir -> 71 | let* theme_updates = link_themes dir htmldir_contents themes in 72 | let udoc = B0_github.Pages.update (Some htmldir) ~dst:(Fpath.v "doc") in 73 | let updates = B0_github.Pages.nojekyll :: udoc :: theme_updates in 74 | let* repo = B0_vcs_repo.get () in 75 | let msg = Fmt.str "Update sample output with %s." versions in 76 | let amend = not new_commit and force = true in 77 | let* updated = 78 | B0_github.Pages.commit_updates repo ~remote ~branch ~amend ~force ~msg 79 | updates 80 | in 81 | Log.stdout begin fun m -> 82 | m "[%a] %a %a" 83 | (Fmt.st [`Fg `Green]) "DONE" pp_updated updated 84 | B0_vcs_repo.Git.pp_remote_branch 85 | (remote, B0_github.Pages.default_branch) 86 | end; 87 | Ok 0 88 | 89 | let main () = 90 | let open Cmdliner in 91 | let cmd = 92 | let new_commit = 93 | let doc = "Make a new commit, do not amend the last one." in 94 | Arg.(value & flag & info ["c"; "new-commit"] ~doc) 95 | in 96 | let remote = 97 | let doc = "Publish on remote $(docv)." and docv = "REMOTE" in 98 | Arg.(value & opt string "origin" & info ["remote"] ~doc ~docv) 99 | in 100 | let branch = 101 | let doc = "Publish on branch $(docv)." and docv = "BRANCH" in 102 | let default = B0_github.Pages.default_branch in 103 | Arg.(value & opt string default & info ["b"; "branch"] ~doc ~docv) 104 | in 105 | let color = B0_std_cli.color () in 106 | let log_level = B0_std_cli.log_level () in 107 | let doc = "Updates odig's sample output on GitHub pages" in 108 | Cmd.v (Cmd.info "publish" ~version:"%%VERSION%%" ~doc) 109 | Term.(const publish $ color $ log_level $ new_commit $ 110 | remote $ branch) 111 | in 112 | Cmd.eval' cmd 113 | 114 | let () = if !Sys.interactive then exit (main ()) 115 | -------------------------------------------------------------------------------- /sample/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | opam update 3 | opam switch create . ocaml-base-compiler.4.11.1 4 | 5 | ## 2021 this no longer works even after a full day trying to find for a 6 | ## solution. 7 | 8 | export OPAMSOLVERTIMEOUT=3600; 9 | opam list --available -s | xargs opam install --best-effort --yes 10 | -------------------------------------------------------------------------------- /src/gh_pages_amend.ml: -------------------------------------------------------------------------------- 1 | (*--------------------------------------------------------------------------- 2 | Copyright (c) 2019 The odig programmers. All rights reserved. 3 | SPDX-License-Identifier: ISC 4 | ---------------------------------------------------------------------------*) 5 | 6 | open B0_std 7 | 8 | let pp_updated ppf = function 9 | | false -> Fmt.string ppf "No update to publish on" 10 | | true -> Fmt.string ppf "Published docs on" 11 | 12 | let get_msg ~src ~dst = function 13 | | Some m -> m 14 | | None -> 15 | Fmt.str "Update %a\n\nWith contents of %a" 16 | Fpath.pp_quoted dst Fpath.pp_quoted src 17 | 18 | let publish ~amend ~msg ~remote ~branch preserve_symlinks cname_file src dst = 19 | let follow_symlinks = not preserve_symlinks in 20 | let dst_upd = B0_github.Pages.update ~follow_symlinks (Some src) ~dst in 21 | let rupdates = [ B0_github.Pages.nojekyll; dst_upd ] in 22 | let rupdates = match cname_file with 23 | | None -> rupdates 24 | | Some f -> 25 | B0_github.Pages.update (Some f) ~dst:(Fpath.v "CNAME") :: rupdates 26 | in 27 | let updates = List.rev rupdates in 28 | Result.bind (B0_vcs_repo.get ()) @@ fun repo -> 29 | let force = true in 30 | B0_github.Pages.commit_updates 31 | repo ~amend ~force ~remote ~branch ~msg updates 32 | 33 | let publish_cmd 34 | color log_level new_commit remote branch msg preserve_symlinks 35 | cname_file src dst 36 | = 37 | let styler = B0_std_cli.get_styler color in 38 | let log_level = B0_std_cli.get_log_level log_level in 39 | B0_std_cli.setup styler log_level ~log_spawns:Log.Debug; 40 | Log.if_error ~use:1 @@ 41 | let msg = get_msg ~src ~dst msg in 42 | let amend = not new_commit in 43 | let pub = 44 | publish ~amend ~msg ~remote ~branch preserve_symlinks cname_file src dst 45 | in 46 | Result.bind pub @@ fun updated -> 47 | Log.stdout begin fun m -> 48 | m "[%a] %a %a" 49 | (Fmt.st [`Fg `Green]) "DONE" pp_updated updated 50 | B0_vcs_repo.Git.pp_remote_branch (remote, branch) 51 | end; 52 | Ok 0 53 | 54 | let main () = 55 | let open Cmdliner in 56 | let some_path = Arg.some B0_std_cli.fpath in 57 | let cmd = 58 | let preserve_symlinks = 59 | let doc = "Do not follow symlinks in $(i,SRC), preserve them." in 60 | Arg.(value & flag & info ["preserve-symlinks"] ~doc) 61 | in 62 | let new_commit = 63 | let doc = "Make a new commit, do not amend the last one." in 64 | Arg.(value & flag & info ["c"; "new-commit"] ~doc) 65 | in 66 | let remote = 67 | let doc = "Publish on remote $(docv)." and docv = "REMOTE" in 68 | Arg.(value & opt string "origin" & info ["remote"] ~doc ~docv) 69 | in 70 | let branch = 71 | let doc = "Publish on branch $(docv)." and docv = "BRANCH" in 72 | let default = B0_github.Pages.default_branch in 73 | Arg.(value & opt string default & info ["b"; "branch"] ~doc ~docv) 74 | in 75 | let msg = 76 | let doc = "$(docv) is the commit message. If unspecified one is 77 | made up using $(i,SRC) and $(i,DST)." 78 | in 79 | let docv = "MSG" in 80 | Arg.(value & opt (some string) None & info ["m"; "message"] ~doc ~docv) 81 | in 82 | let src = 83 | let doc = "$(docv) is the directory to publish." in 84 | Arg.(required & pos 0 some_path None & info [] ~doc ~docv:"SRC") 85 | in 86 | let dst = 87 | let doc = "$(docv) is the directory relative to the root of the \ 88 | repository checkout which is replaced by $(i,SRC)'s \ 89 | contents. Use . to publish at the root." 90 | in 91 | Arg.(required & pos 1 some_path None & info [] ~doc ~docv:"DST") 92 | in 93 | let cname_file = 94 | let doc = "If specified the contents of $(docv) is copied over to the \ 95 | path $(i,CNAME) at the root of the branch." 96 | in 97 | Arg.(value & opt some_path None & info ["cname-file"] ~doc ~docv:"FILE") 98 | in 99 | let color = B0_std_cli.color () in 100 | let log_level = B0_std_cli.log_level () in 101 | let doc = "Publish directories on GitHub pages" in 102 | let man = [ 103 | `S Manpage.s_description; 104 | `P "$(mname) replaces $(b,DST) by the contents of $(b,SRC) on the 105 | gh-pages branch of the current repository origin remote by 106 | amending the last commit."; 107 | `P "The various edge cases (no branch, no last commit, etc.) 108 | should be handled correctly. A $(b,.nojekyll) file is also 109 | unconditionally added at the root of the branch."; 110 | `S Manpage.s_bugs; 111 | `P "Report them, see $(i,%%PKG_HOMEPAGE%%) for contact information." ]; 112 | in 113 | Cmd.v (Cmd.info "gh-pages-amend" ~version:"%%VERSION%%" ~doc ~man) 114 | Term.(const publish_cmd $ color $ log_level $ new_commit $ remote $ 115 | branch $ msg $ preserve_symlinks $ cname_file $ src $ dst) 116 | in 117 | exit (Cmd.eval' cmd) 118 | 119 | let () = if !Sys.interactive then () else main () 120 | -------------------------------------------------------------------------------- /src/odig_main.ml: -------------------------------------------------------------------------------- 1 | (*--------------------------------------------------------------------------- 2 | Copyright (c) 2018 The odig programmers. All rights reserved. 3 | SPDX-License-Identifier: ISC 4 | ---------------------------------------------------------------------------*) 5 | 6 | open B0_std 7 | open Result.Syntax 8 | open Odig_support 9 | 10 | (* Exit codes *) 11 | 12 | module Exit = struct 13 | let no_such_name = 1 14 | let err_uri = 2 15 | let some_error = Cmdliner.Cmd.Exit.some_error 16 | end 17 | 18 | (* Commonalities *) 19 | 20 | let find_pkgs conf = function 21 | | [] -> Ok (Conf.pkgs conf) 22 | | ns -> 23 | let pkgs = Conf.pkgs conf in 24 | let by_name = Pkg.by_names pkgs in 25 | let add_name (fnd, miss) n = match String.Map.find n by_name with 26 | | exception Not_found -> (fnd, n :: miss) 27 | | pkg -> (pkg :: fnd, miss) 28 | in 29 | let fnd, miss = List.fold_left add_name ([], []) ns in 30 | match miss with 31 | | [] -> Ok (List.rev fnd) 32 | | miss -> 33 | let exists yield = List.iter yield (List.rev_map Pkg.name pkgs) in 34 | let add_error acc n = 35 | let kind = Fmt.any "package" in 36 | let unknown = Fmt.(unknown' ~kind Fmt.string ~hint:did_you_mean) in 37 | let err = Fmt.str "%a" unknown (n, String.spellcheck exists n) in 38 | err :: acc 39 | in 40 | Error (String.concat "\n" (List.fold_left add_error [] miss)) 41 | 42 | let odoc_gen conf 43 | ~force ~index_title ~index_intro ~index_toc ~pkg_deps ~tag_index pkgs 44 | = 45 | Log.stdout (fun m -> m "Updating documentation, this may take some time..."); 46 | Odig_odoc.gen 47 | conf ~force ~index_title ~index_intro ~index_toc ~pkg_deps ~tag_index pkgs 48 | 49 | (* Commands *) 50 | 51 | let browse_cmd conf background browser field pkg_names = 52 | Log.if_error ~use:Exit.no_such_name @@ 53 | let* pkgs = find_pkgs conf pkg_names in 54 | Log.if_error' ~use:Exit.some_error @@ 55 | let* browser = B0_web_browser.find ?cmd:browser () in 56 | let get_uris = match field with 57 | | `Homepage -> Opam.homepage 58 | | `Issues -> Opam.bug_reports 59 | | `Online_doc -> Opam.doc 60 | in 61 | let pkgs = Opam.query pkgs in 62 | let uris = List.concat (List.map (fun (_, o) -> get_uris o) pkgs) in 63 | let rec loop exit = function 64 | | [] -> Ok exit 65 | | u :: us -> 66 | match B0_web_browser.show ~background ~prefix:false browser u with 67 | | Error e -> Log.err (fun m -> m "%s" e); loop Exit.err_uri us 68 | | Ok () -> loop exit us 69 | in 70 | loop 0 uris 71 | 72 | let conf_cmd conf = Fmt.pr "%a@." Conf.pp conf; 0 73 | 74 | let cache_cmd conf = function 75 | | `Path -> Fmt.pr "%a@." Fpath.pp_unquoted (Conf.cache_dir conf); 0 76 | | `Clear -> 77 | let dir = Conf.cache_dir conf in 78 | Log.stdout begin fun m -> 79 | m "Deleting %a, this may take some time..." 80 | (Fmt.st' [`Fg `Green] Fpath.pp_quoted) dir 81 | end; 82 | Log.if_error ~use:Exit.some_error @@ 83 | let* _del = Os.Path.delete ~recurse:true dir in 84 | Ok 0 85 | | `Trim -> 86 | let b0_cache_dir = Conf.b0_cache_dir conf in 87 | Log.if_error ~use:Exit.some_error @@ 88 | let* exists = Os.Dir.exists b0_cache_dir in 89 | if not exists then Ok 0 else 90 | let pct = 50 and max_byte_size = max_int in 91 | let* c = B0_zero.File_cache.make b0_cache_dir in 92 | let* () = B0_zero.File_cache.trim_size c ~max_byte_size ~pct in 93 | Ok 0 94 | 95 | let doc_cmd conf background browser pkg_names update no_update show_files = 96 | let exists f = Os.File.exists f |> Log.if_error ~use:false in 97 | Log.if_error ~use:Exit.no_such_name @@ 98 | let* pkgs = match pkg_names with 99 | | [] -> Ok [] 100 | | ns -> find_pkgs conf pkg_names 101 | in 102 | Log.if_error' ~use:Exit.some_error @@ 103 | let* browser = B0_web_browser.find ?cmd:browser () in 104 | let* files = match pkgs with 105 | | [] -> 106 | let root_index = Fpath.(Conf.html_dir conf / "index.html") in 107 | begin match exists root_index with 108 | | true when not update || no_update -> Ok [root_index] 109 | | false when no_update -> Error "No doc found. Try with 'odig doc -u'." 110 | | _ -> 111 | let pkgs = Conf.pkgs conf in 112 | let index_title = None and index_intro = None and index_toc = None in 113 | let force = false and pkg_deps = true and tag_index = true in 114 | let* () = 115 | odoc_gen conf ~force ~index_title ~index_intro ~index_toc 116 | ~pkg_deps ~tag_index pkgs 117 | in 118 | Ok [root_index] 119 | end 120 | | pkgs -> 121 | let index p = Fpath.(Conf.html_dir conf / Pkg.name p / "index.html") in 122 | let files = List.rev (List.rev_map index pkgs) in 123 | match List.find_all (fun f -> not (exists f)) files with 124 | | [] when not update || no_update -> Ok files 125 | | files when no_update -> 126 | Fmt.error 127 | "@[No doc found for:@, %a@,@[Try with 'odig doc -u %a'@]@]" 128 | Fmt.(list Fpath.pp_quoted) files 129 | Fmt.(list Pkg.pp_name) pkgs 130 | | _ -> 131 | let index_title = None and index_intro = None and index_toc = None in 132 | let force = false and pkg_deps = true and tag_index = true in 133 | let* () = 134 | odoc_gen conf ~force ~index_title ~index_intro ~index_toc 135 | ~pkg_deps ~tag_index pkgs 136 | in 137 | Ok files 138 | in 139 | let does_not_exist = List.find_all (fun f -> not (exists f)) files in 140 | match does_not_exist with 141 | | [] when show_files -> 142 | Fmt.pr "@[%a@]@." (Fmt.list Fpath.pp_unquoted) files; Ok 0 143 | | [] -> 144 | let rec loop exit = function 145 | | [] -> Ok exit 146 | | f :: fs -> 147 | let file_uri p = Fmt.str "file://%a" Fpath.pp_unquoted p in 148 | let u = file_uri f in 149 | match B0_web_browser.show ~background ~prefix:false browser u with 150 | | Error e -> Log.err (fun m -> m "%s" e); loop Exit.err_uri fs 151 | | Ok () -> loop exit fs 152 | in 153 | loop 0 files 154 | | fs -> 155 | Fmt.error "@[No doc could be generated for:@,%a@]" 156 | (Fmt.list Fpath.pp_quoted) fs 157 | 158 | let log_cmd conf no_pager format kind op_selector = 159 | Log.if_error ~use:Exit.some_error @@ 160 | let don't = no_pager || format = `Trace_event in 161 | let* pager = B0_pager.find ~don't () in 162 | let* () = B0_pager.page_stdout pager in 163 | let log_file = Conf.b0_log_file conf in 164 | let* log = B0_memo_log.read log_file in 165 | B0_cli.Memo.Log.out Fmt.stdout format kind op_selector ~path:log_file log; 166 | Ok 0 167 | 168 | let odoc_cmd 169 | conf _odoc pkg_names index_title index_intro index_toc force no_pkg_deps 170 | no_tag_index 171 | = 172 | let pkg_deps = not no_pkg_deps in 173 | let tag_index = not no_tag_index in 174 | Log.if_error ~use:Exit.no_such_name @@ 175 | let* pkgs = find_pkgs conf pkg_names in 176 | Log.if_error' ~use:Exit.some_error @@ 177 | let* () = 178 | odoc_gen conf 179 | ~force ~index_title ~index_intro ~index_toc ~pkg_deps ~tag_index pkgs 180 | in 181 | Ok 0 182 | 183 | let pkg_cmd conf no_pager out_fmt pkg_names = 184 | Log.if_error ~use:Exit.no_such_name @@ 185 | let* pkgs = find_pkgs conf pkg_names in 186 | Log.if_error' ~use:Exit.some_error @@ 187 | let* pager = B0_pager.find ~don't:no_pager () in 188 | let* () = B0_pager.page_stdout pager in 189 | let pp_pkgs = match out_fmt with 190 | | `Short -> (fun ppf () -> (Fmt.list Pkg.pp_name) ppf pkgs) 191 | | `Normal -> 192 | let pp_pkg ppf (pkg, o) = 193 | Fmt.pf ppf "@[%a %a %a@]" 194 | Pkg.pp_name pkg Pkg.pp_version (Opam.version o) 195 | (Fmt.st' [`Faint] Fpath.pp_quoted) (Pkg.path pkg) 196 | in 197 | let pkgs = Opam.query pkgs in 198 | (fun ppf () -> (Fmt.list pp_pkg) ppf pkgs) 199 | | `Long -> 200 | let pp_pkg ppf (pkg, i) = 201 | Fmt.pf ppf "@[%a@,%a@]" Pkg.pp pkg Pkg_info.pp i 202 | in 203 | let pkgs = Pkg_info.query ~doc_dir:(Conf.doc_dir conf) pkgs in 204 | (fun ppf () -> (Fmt.list pp_pkg) ppf pkgs) 205 | in 206 | Fmt.pr "@[%a@]@." pp_pkgs (); Ok 0 207 | 208 | let show_cmd conf no_pager out_fmt show_empty field pkg_names = 209 | Log.if_error ~use:Exit.no_such_name @@ 210 | let* pkgs = find_pkgs conf pkg_names in 211 | Log.if_error' ~use:Exit.some_error @@ 212 | let* pager = B0_pager.find ~don't:no_pager () in 213 | let* () = B0_pager.page_stdout pager in 214 | let pp_field field out_fmt show_empty = match out_fmt with 215 | | `Short | `Normal -> 216 | (fun ppf (p, i) -> match Pkg_info.get field i with 217 | | [] -> if show_empty then Fmt.pf ppf "@," else () 218 | | vs -> Fmt.pf ppf "%a@," Fmt.(list string) vs) 219 | | `Long -> 220 | (fun ppf (p, i) -> match Pkg_info.get field i with 221 | | [] when not show_empty -> () 222 | | [] -> Fmt.pf ppf "@[%a@]@," Pkg.pp_name p 223 | | vs -> 224 | let pp_val ppf v = Fmt.pf ppf "@[%a %s@]" Pkg.pp_name p v in 225 | Fmt.pf ppf "%a@," Fmt.(list pp_val) vs) 226 | in 227 | let infos = Pkg_info.query ~doc_dir:(Conf.doc_dir conf) pkgs in 228 | let pp_field = pp_field field out_fmt show_empty in 229 | Fmt.pr "@[%a@]@?" Fmt.(list ~sep:Fmt.nop pp_field) infos; 230 | Ok 0 231 | 232 | let show_files_cmd conf no_pager pkg_names get_files = 233 | Log.if_error ~use:Exit.no_such_name @@ 234 | let* pkgs = find_pkgs conf pkg_names in 235 | Log.if_error' ~use:Exit.some_error @@ 236 | let* pager = B0_pager.find ~don't:no_pager () in 237 | let doc_dir = Conf.doc_dir conf in 238 | let doc_dirs = List.map (fun p -> p, (Doc_dir.of_pkg ~doc_dir p)) pkgs in 239 | let files = List.concat (List.map (fun (p, i) -> get_files i) doc_dirs) in 240 | let* () = B0_pager.page_files pager files in 241 | Ok 0 242 | 243 | 244 | let theme_list_cmd conf out_fmt = 245 | match B0_odoc.Theme.of_dir (Conf.share_dir conf) with 246 | | [] -> 0 247 | | ts -> 248 | let pp_theme = function 249 | | `Short -> B0_odoc.Theme.pp_name 250 | | `Normal | `Long -> B0_odoc.Theme.pp 251 | in 252 | Fmt.pr "@[%a@]@." (Fmt.list (pp_theme out_fmt)) ts; 0 253 | 254 | let theme_get_cmd conf read_conf = 255 | Log.if_error ~level:Log.Error ~use:Exit.some_error @@ 256 | let* name = match read_conf with 257 | | false -> Ok (Conf.odoc_theme conf) 258 | | true -> 259 | let* name = B0_odoc.Theme.get_user_preference () in 260 | Ok (Option.value ~default:B0_odoc.Theme.odig_default name) 261 | in 262 | Fmt.pr "%s@." name; Ok 0 263 | 264 | let theme_set_cmd conf theme = 265 | let ts = B0_odoc.Theme.of_dir (Conf.share_dir conf) in 266 | let theme = match theme with None -> Conf.odoc_theme conf | Some t -> t in 267 | Log.if_error ~level:Log.Error ~use:Exit.no_such_name @@ 268 | let* t = B0_odoc.Theme.find ~fallback:None theme ts in 269 | Log.if_error' ~use:Exit.some_error @@ 270 | let* () = Odig_odoc.install_theme conf (Some t) in 271 | let name = Some (B0_odoc.Theme.name t) in 272 | let* () = B0_odoc.Theme.set_user_preference name in 273 | Ok 0 274 | 275 | let theme_path_cmd conf theme = 276 | let ts = B0_odoc.Theme.of_dir (Conf.share_dir conf) in 277 | let theme = match theme with None -> Conf.odoc_theme conf | Some t -> t in 278 | Log.if_error ~level:Log.Error ~use:Exit.no_such_name @@ 279 | let* t = B0_odoc.Theme.find ~fallback:None theme ts in 280 | Fmt.pr "%a@." Fpath.pp_unquoted (B0_odoc.Theme.path t); Ok 0 281 | 282 | (* Command line interface *) 283 | 284 | open Cmdliner 285 | 286 | (* Arguments and commonalities *) 287 | 288 | let exits = 289 | Cmd.Exit.info Exit.no_such_name 290 | ~doc:"a specified entity name cannot be found." :: 291 | Cmd.Exit.info Exit.err_uri ~doc:"an URI cannot be shown in a browser." :: 292 | Cmd.Exit.defaults 293 | 294 | let format = B0_std_cli.output_format () 295 | let conf = 296 | let absent = "see below" in 297 | let path = B0_std_cli.fpath in 298 | let docs = Manpage.s_common_options in 299 | let docv = "PATH" in 300 | let doc dirname dir = 301 | Fmt.str 302 | "%s directory. If unspecified, $(b,\\$PREFIX)/%s with $(b,\\$PREFIX) \ 303 | the parent directory of $(mname)'s install directory." dirname dir 304 | in 305 | let b0_cache_dir = 306 | let env = Cmd.Env.info Env.b0_cache_dir in 307 | let doc_none = "$(b,.cache) in odig cache directory" in 308 | B0_cli.Memo.cache_dir ~opts:["b0-cache-dir"] ~doc_none ~env () 309 | in 310 | let b0_log_file = 311 | let env = Cmd.Env.info Env.b0_log_file in 312 | let doc_none = "$(b,.log) in odig cache directory" in 313 | B0_cli.Memo.log_file ~docs ~doc_none ~env () 314 | in 315 | let cache_dir = 316 | let doc = doc "Cache" "var/cache/odig" in 317 | let env = Cmd.Env.info Env.cache_dir in 318 | Arg.(value & opt (some path) None & 319 | info ["cache-dir"] ~absent ~doc ~docs ~env ~docv) 320 | in 321 | let doc_dir = 322 | let doc = doc "Documentation" "doc" in 323 | let env = Cmd.Env.info Env.doc_dir in 324 | Arg.(value & opt (some path) None & 325 | info ["doc-dir"] ~absent ~doc ~docs ~env ~docv) 326 | in 327 | let lib_dir = 328 | let doc = doc "Library" "lib" in 329 | let env = Cmd.Env.info Env.lib_dir in 330 | Arg.(value & opt (some path) None & 331 | info ["lib-dir"] ~absent ~doc ~docs ~env ~docv) 332 | in 333 | let odoc_theme = 334 | let doc = 335 | "Theme to use for odoc documentation. If unspecified, the theme can be \ 336 | specified in the file $(b,~/.config/odig/odoc-theme) or \ 337 | $(b,odig.default) is used." 338 | in 339 | let env = Cmd.Env.info Env.odoc_theme in 340 | Arg.(value & opt (some string) None & 341 | info ["odoc-theme"] ~doc ~docs ~env ~docv:"THEME") 342 | in 343 | let share_dir = 344 | let doc = doc "Share" "share" in 345 | let env = Cmd.Env.info Env.share_dir in 346 | Arg.(value & opt (some path) None & 347 | info ["share-dir"] ~absent ~doc ~docs ~env ~docv) 348 | in 349 | let jobs = B0_cli.Memo.jobs ~docs ~env:(Cmd.Env.info "ODIG_JOBS") () in 350 | let color = B0_std_cli.color ~env:(Cmd.Env.info Env.color) () in 351 | let log_level = B0_std_cli.log_level ~env:(Cmd.Env.info Env.verbosity) () in 352 | let conf 353 | b0_cache_dir b0_log_file cache_dir doc_dir jobs lib_dir log_level 354 | odoc_theme share_dir color 355 | = 356 | Result.map_error (fun s -> `Msg s) @@ 357 | Conf.setup_with_cli 358 | ~b0_cache_dir ~b0_log_file ~cache_dir ~doc_dir ~jobs ~lib_dir ~log_level 359 | ~odoc_theme ~share_dir ~color () 360 | in 361 | Term.term_result @@ 362 | Term.(const conf $ b0_cache_dir $ b0_log_file $ cache_dir $ doc_dir $ jobs $ 363 | lib_dir $ log_level $ odoc_theme $ share_dir $ color) 364 | 365 | let pkgs_pos1_nonempty, pkgs_pos, pkgs_pos1, pkgs_opt = 366 | let doc = "Package to consider (repeatable)." in 367 | let docv = "PKG" in 368 | Arg.(non_empty & pos_right 0 string [] & info [] ~doc ~docv), 369 | Arg.(value & pos_all string [] & info [] ~doc ~docv), 370 | Arg.(value & pos_right 0 string [] & info [] ~doc ~docv), 371 | Arg.(value & opt_all string [] & info ["p"; "pkg"] ~doc ~docv) 372 | 373 | let background = B0_web_browser.background () 374 | let browser = B0_web_browser.browser () 375 | let no_pager = B0_pager.don't () 376 | 377 | let show_files_cmd ?cmd ~kind get_files = 378 | let cname = match cmd with None -> kind | Some cmd -> cmd in 379 | let doc = Fmt.str "Show package %s files" kind in 380 | let envs = B0_pager.Env.infos in 381 | let man = 382 | [ `S "DESCRIPTION"; 383 | `P (Fmt.str "The $(tname) command shows package %s files. If \ 384 | invoked with $(b,--no-pager) and multiple files are output \ 385 | these are separated by a U+001C (file separator) control \ 386 | character." kind); 387 | `P "To output the file paths rather than their content use $(mname) \ 388 | $(b,show)." ] 389 | in 390 | Cmd.v (Cmd.info cname ~doc ~envs ~exits ~man) 391 | Term.(const show_files_cmd $ conf $ no_pager $ pkgs_pos $ const get_files) 392 | 393 | let subcmd ?(exits = exits) ?(envs = []) name ~doc ~descr term = 394 | let man = [`S Manpage.s_description; descr] in 395 | Cmd.v (Cmd.info name ~doc ~exits ~envs ~man) term 396 | 397 | (* Commands *) 398 | 399 | let browse_cmd = 400 | let doc = "Open package metadata URIs in your browser" in 401 | let man = [ 402 | `S Manpage.s_description; 403 | `P "$(tname) command opens or reloads metadata URI fields of packages 404 | in a WWW browser." ] 405 | in 406 | let field = 407 | let field = 408 | [ "homepage", `Homepage; "issues", `Issues; "online-doc", `Online_doc; ] 409 | in 410 | let alts = Arg.doc_alts_enum field in 411 | let doc = Fmt.str "The URI field to show. $(docv) must be %s." alts in 412 | let action = Arg.enum field in 413 | Arg.(required & pos 0 (some action) None & info [] ~doc ~docv:"FIELD") 414 | in 415 | Cmd.v (Cmd.info "browse" ~doc ~exits ~man) 416 | Term.(const browse_cmd $ conf $ background $ browser $ field $ 417 | pkgs_pos1_nonempty) 418 | 419 | let cache_cmd = 420 | let doc = "Operate on the odig cache" in 421 | let man = [ 422 | `S Manpage.s_description; 423 | `P "The $(tname) command operates on the odig cache."] 424 | in 425 | let clear_cmd = 426 | let doc = "Clear the cache" in 427 | let descr = `P "$(tname) clears the cache." in 428 | subcmd "clear" ~doc ~descr Term.(const cache_cmd $ conf $ const `Clear) 429 | in 430 | let path_cmd = 431 | let doc = "Show cache directory path" in 432 | let descr = `P "$(tname) outputs the path to the cache directory." in 433 | subcmd "path" ~doc ~descr Term.(const cache_cmd $ conf $ const `Path) 434 | in 435 | let trim_cmd = 436 | let doc = "Trim cache (does not affect generated docs)" in 437 | let descr = `P "$(mname) trims the cache without affecting generated \ 438 | documentation." 439 | in 440 | subcmd "trim" ~doc ~descr Term.(const cache_cmd $ conf $ const `Trim) 441 | in 442 | let subcmds = [clear_cmd; path_cmd; trim_cmd] in 443 | Cmd.group (Cmd.info "cache" ~doc ~exits ~man) subcmds 444 | 445 | let changes_cmd = 446 | show_files_cmd ~cmd:"changes" ~kind:"change log" Doc_dir.changes_files 447 | 448 | let conf_cmd = 449 | let doc = "Show odig configuration" in 450 | let man = [ 451 | `S Manpage.s_description; 452 | `P "$(tname) outputs the odig configuration."; 453 | `P "$(mname) needs to know the path to the library directory, the 454 | path to the documentation directory, the path to the share 455 | directory and the path to the odig cache."; 456 | `P "Each can be specified on the command line or via an environment 457 | variable. If none of this is done they are determined relative 458 | to the binary's install directory. See the options $(b,--lib-dir), 459 | $(b,--doc-dir), $(b,--share-dir) and $(b,--cache-dir) for details."; ] 460 | in 461 | Cmd.v (Cmd.info "conf" ~doc ~exits ~man) 462 | Term.(const conf_cmd $ conf) 463 | 464 | let doc_cmd = 465 | let doc = "Show odoc API documentation and manuals" in 466 | let man_xrefs = [ `Main; `Cmd "odoc" ] in 467 | let man = [ 468 | `S Manpage.s_description; 469 | `P "$(tname) shows API documentation and manuals as generated 470 | by $(mname) $(b,odoc)."; ] 471 | in 472 | let update = 473 | let doc = 474 | "Make sure docs for the request are up-to-date. This happens \ 475 | automatically if part of the request cannot be found, use \ 476 | $(b,--no-update) to prevent this." 477 | in 478 | Arg.(value & flag & info ["u"; "update"] ~doc) 479 | in 480 | let no_update = 481 | let doc = "Never try to update the docs. Takes over $(b,--update)." in 482 | Arg.(value & flag & info ["n"; "no-update"] ~doc) 483 | in 484 | let show_files = 485 | let doc = 486 | "Output files on stdout one by line, rather than trying to open them \ 487 | in a broken way." 488 | in 489 | Arg.(value & flag & info ["f"; "show-files"] ~doc) 490 | in 491 | Cmd.v (Cmd.info "doc" ~doc ~exits ~man ~man_xrefs) 492 | Term.(const doc_cmd $ conf $ background $ browser $ pkgs_pos $ update $ 493 | no_update $ show_files) 494 | 495 | let license_cmd = show_files_cmd ~kind:"license" Doc_dir.license_files 496 | 497 | let odoc_cmd = 498 | let doc = "Generate odoc API documentation and manuals" in 499 | let man = [ 500 | `S Manpage.s_description; 501 | `P "$(tname) generates the odoc API documentation and manual of packages."; 502 | `P "See the packaging conventions in $(mname) $(b,doc) $(mname) for 503 | generation details."; ] 504 | in 505 | let odoc = Term.const "odoc" 506 | (* let doc = "The odoc command to use." in 507 | let env = Cmd.Env.info "ODIG_ODOC" in 508 | Arg.(value & opt string "odoc" & info ["odoc"] ~env ~docv:"CMD" ~doc) *) 509 | in 510 | let force = Term.const false 511 | (* let doc = "Force generation even if files are up-to-date." in 512 | Arg.(value & flag & info ["f"; "force"] ~doc) *) 513 | in 514 | let index_title = 515 | let doc = "$(docv) is the title of the package list page." in 516 | let docv = "TITLE" in 517 | Arg.(value & opt (some string) None & info ["index-title"] ~docv ~doc) 518 | in 519 | let index_intro = 520 | let doc = "$(docv) is the .mld file to use to define the introduction 521 | text on the package list page." 522 | in 523 | let some_path = Arg.some B0_std_cli.fpath in 524 | Arg.(value & opt some_path None & info ["index-intro"] ~docv:"MLDFILE" ~doc) 525 | in 526 | let index_toc = 527 | let doc = "$(docv) is the .mld file to use to define the contents of the 528 | table of contents on the package list page." 529 | in 530 | let some_path = Arg.some B0_std_cli.fpath in 531 | Arg.(value & opt some_path None & info ["index-toc"] ~docv:"MLDFILE" ~doc) 532 | in 533 | let no_pkg_deps = 534 | let doc = "Restrict documentation generation to the packages mentioned \ 535 | on the command line, their dependencies are not automatically \ 536 | included in the result. Note that this may lead to broken \ 537 | links in the documentation set." 538 | in 539 | Arg.(value & flag & info ["no-pkg-deps"] ~doc) 540 | in 541 | let no_tag_index = 542 | let doc = "Do not generate the tag index on the package list page." in 543 | Arg.(value & flag & info ["no-tag-index"] ~doc) 544 | in 545 | Cmd.v (Cmd.info "odoc" ~doc ~exits ~man) 546 | Term.(const odoc_cmd $ conf $ odoc $ pkgs_pos $ index_title $ index_intro $ 547 | index_toc $ force $ no_pkg_deps $ no_tag_index) 548 | 549 | let odoc_theme_cmd = 550 | let theme = 551 | let doc = "Theme name." and docv = "THEME" in 552 | let absent = "value of $(b,odig odoc-theme get)" in 553 | Arg.(value & pos 0 (some string) None & info [] ~absent ~doc ~docv) 554 | in 555 | let get_cmd = 556 | let doc = "Get the theme used on documentation generation" in 557 | let descr = `Blocks [ 558 | `P "$(mname) shows the theme used on documentation generation."; 559 | `P "This is either, in order, the value of the $(b,--odoc-theme) \ 560 | option, the value of the $(b,ODIG_ODOC_THEME) environment \ 561 | variable, the stripped contents of the \ 562 | $(b,~/.config/odig/odoc-theme) file or $(b,odig.default)"; 563 | `P "Use $(b,--config) to get the value from \ 564 | the configuration file; $(b,odig.default) is returned if there is 565 | not such file."; ] 566 | in 567 | let read_conf = 568 | let doc = 569 | "Show the value written in $(b,~/.config/odig/odoc-theme) \ 570 | or $(b,odig.default) if there is no such file." 571 | in 572 | Arg.(value & flag & info ["conf"] ~doc) 573 | in 574 | subcmd "get" ~doc ~descr Term.(const theme_get_cmd $ conf $ read_conf) 575 | in 576 | let list_term = Term.(const theme_list_cmd $ conf $ format) in 577 | let list_cmd = 578 | let doc = "List available themes (default command)" in 579 | let descr = `P "$(tname) lists available themes." in 580 | subcmd "list" ~doc ~descr list_term 581 | in 582 | let path_cmd = 583 | let doc = "Show theme directory" in 584 | let descr = `P "$(tname) shows the directory of $(i,THEME)" 585 | in 586 | subcmd "path" ~doc ~descr Term.(const theme_path_cmd $ conf $ theme) 587 | in 588 | let set_cmd = 589 | let doc = "Set theme used on documentation generation" in 590 | let descr = 591 | `P "$(tname) changes the theme used on documentation generation 592 | to $(i,THEME) and persists the choice in \ 593 | $(b,~/.config/odig/odoc-theme)."; 594 | in 595 | subcmd "set" ~doc ~descr Term.(const theme_set_cmd $ conf $ theme) 596 | in 597 | let subcmds = [get_cmd; list_cmd; path_cmd; set_cmd] in 598 | let doc = "Manage themes for odoc API and manual documentation." in 599 | let man = [ 600 | `S Manpage.s_description; 601 | `P "$(tname) lists and sets the theme used by odoc documentation. The \ 602 | default command is $(tname) $(b,list)."; 603 | `P "See the packaging conventions in $(b,odig doc) $(mname) for the \ 604 | theme install structure."; 605 | `S Manpage.s_options; 606 | `S B0_std_cli.s_output_format_options; 607 | ] 608 | in 609 | Cmd.group (Cmd.info "odoc-theme" ~doc ~exits ~man) 610 | ~default:list_term subcmds 611 | 612 | let log_cmd = 613 | let doc = "Show odoc build log" in 614 | let envs = B0_pager.Env.infos in 615 | let man = [ 616 | `S Manpage.s_description; 617 | `P "The $(tname) command shows odoc build operations."; 618 | `S Manpage.s_options; 619 | `S B0_std_cli.s_output_format_options; 620 | `S B0_cli.Op.s_selection_options; 621 | `Blocks B0_cli.Op.query_man ] 622 | in 623 | Cmd.v (Cmd.info "log" ~doc ~exits ~envs ~man) 624 | Term.(const log_cmd $ conf $ no_pager $ 625 | B0_cli.Memo.Log.out_format_cli () $ format $ B0_cli.Op.query_cli ()) 626 | 627 | let pkg_term = Term.(const pkg_cmd $ conf $ no_pager $ format $ pkgs_pos) 628 | let pkg_cmd = 629 | let doc = "Show packages (default command)" in 630 | let envs = B0_pager.Env.infos in 631 | let man = [ 632 | `S Manpage.s_description; 633 | `P "The $(tname) command shows packages known to odig. If no packages 634 | are specified, all packages are shown."; 635 | `P "See the packaging conventions in $(b,odig doc) $(mname) for the package 636 | install structure."; 637 | `S Manpage.s_commands; 638 | `S Manpage.s_options; 639 | `S B0_std_cli.s_output_format_options; ] 640 | in 641 | Cmd.v (Cmd.info "pkg" ~doc ~envs ~exits ~man) pkg_term 642 | 643 | let readme_cmd = show_files_cmd ~kind:"readme" Doc_dir.readme_files 644 | let show_cmd = 645 | let doc = "Show package metadata" in 646 | let envs = B0_pager.Env.infos in 647 | let man = [ 648 | `S Manpage.s_description; 649 | `P "$(tname) outputs package metadata. If no packages 650 | are specified, information for all packages is shown."; 651 | `P "Outputs a single non-empty value per line; to output empty 652 | value use the $(b,--show-empty) option."; 653 | `P "To preceed values by the name of the package they apply to, use 654 | the $(b,--long) option."; 655 | `S Manpage.s_options; 656 | `S B0_std_cli.s_output_format_options; ] 657 | in 658 | let field = 659 | let field = Odig_support.Pkg_info.field_names in 660 | let alts = Arg.doc_alts_enum field in 661 | let doc = Fmt.str "The field to show. $(docv) must be %s." alts in 662 | let action = Arg.enum field in 663 | Arg.(required & pos 0 (some action) None & info [] ~doc ~docv:"FIELD") 664 | in 665 | let show_empty = 666 | let doc = "Show empty fields." in 667 | Arg.(value & flag & info ["e"; "show-empty"] ~doc) 668 | in 669 | Cmd.v (Cmd.info "show" ~doc ~envs ~exits ~man) 670 | Term.(const show_cmd $ conf $ no_pager $ format $ show_empty $ field $ 671 | pkgs_pos1) 672 | 673 | let subs = 674 | [ browse_cmd; cache_cmd; changes_cmd; conf_cmd; doc_cmd; license_cmd; 675 | log_cmd; odoc_cmd; odoc_theme_cmd; pkg_cmd; readme_cmd; show_cmd; ] 676 | 677 | let odig = 678 | let doc = "Lookup documentation of installed OCaml packages" in 679 | let man = [ 680 | `S Manpage.s_description; 681 | `P "$(mname) looks up documentation of installed OCaml packages. It shows \ 682 | package metadata, readmes, change logs, licenses, cross-referenced \ 683 | $(b,odoc) API documentation and manuals."; 684 | `P "See $(b,odig doc) $(mname) for a tutorial and more details."; `Noblank; 685 | `P "See $(mname) $(b,conf --help) for information about $(mname) \ 686 | configuration."; 687 | `S Manpage.s_options; 688 | `S B0_std_cli.s_output_format_options; 689 | `S Manpage.s_see_also; 690 | `P "Consult $(b,odig doc odig) for a tutorial, packaging conventions and 691 | more details."; 692 | `S Manpage.s_bugs; 693 | `P "Report them, see $(i,%%PKG_HOMEPAGE%%) for contact information." ]; 694 | in 695 | Cmd.group (Cmd.info "odig" ~version:"%%VERSION%%" ~doc ~exits ~man) 696 | ~default:pkg_term subs 697 | 698 | let main () = 699 | exit @@ Log.time (fun _ m -> m "total time") @@ fun () -> 700 | Cmd.eval' odig 701 | 702 | let () = main () 703 | -------------------------------------------------------------------------------- /src/odig_odoc.ml: -------------------------------------------------------------------------------- 1 | (*--------------------------------------------------------------------------- 2 | Copyright (c) 2018 The odig programmers. All rights reserved. 3 | SPDX-License-Identifier: ISC 4 | ---------------------------------------------------------------------------*) 5 | 6 | open Odig_support 7 | open B0_std 8 | open B0_std.Fut.Syntax 9 | 10 | let odig_version = "%%VERSION%%" 11 | 12 | let link_if_exists src dst = match src with 13 | | None -> () 14 | | Some src -> 15 | Os.Path.symlink ~force:true ~make_path:true ~src dst |> Log.if_error ~use:() 16 | 17 | (* Theme handling *) 18 | 19 | let ocaml_manual_pkg = "ocaml-manual" 20 | 21 | let get_theme conf = 22 | let ts = B0_odoc.Theme.of_dir (Conf.share_dir conf) in 23 | let odig_theme = 24 | let odig t = B0_odoc.Theme.name t = B0_odoc.Theme.odig_default in 25 | match List.find odig ts with exception Not_found -> None | t -> Some t 26 | in 27 | let name = Conf.odoc_theme conf in 28 | let fallback = match odig_theme with 29 | | Some t -> Some (B0_odoc.Theme.name t) 30 | | None -> Some (B0_odoc.Theme.odoc_default) 31 | in 32 | Log.if_error ~level:Log.Warning ~use:odig_theme @@ 33 | Result.bind (B0_odoc.Theme.find ~fallback name ts) @@ fun t -> 34 | Ok (Some t) 35 | 36 | let write_ocaml_manual_theme conf m theme = 37 | let write_original_css conf ~o = 38 | let css = Fpath.(Conf.doc_dir conf / ocaml_manual_pkg / "manual.css") in 39 | ignore @@ 40 | let* () = B0_memo.delete m o in 41 | B0_memo.ready_file m css; 42 | B0_memo.copy m css ~dst:o; 43 | Fut.return () 44 | in 45 | let manual_dir = Fpath.(Conf.html_dir conf / ocaml_manual_pkg) in 46 | match Os.Dir.exists manual_dir |> Log.if_error ~use:false with 47 | | false -> () 48 | | true -> 49 | let manual_css = Fpath.(manual_dir / "manual.css") in 50 | let theme_manual_css = match theme with 51 | | None -> None 52 | | Some t -> 53 | let css = Fpath.(B0_odoc.Theme.path t / "manual.css") in 54 | if Os.File.exists css |> Log.if_error ~use:false 55 | then Some (t, css) 56 | else None 57 | in 58 | match theme_manual_css with 59 | | None -> write_original_css conf ~o:manual_css 60 | | Some (t, css) -> 61 | (* We copy the theme again in ocaml-manual because of FF. *) 62 | let to_dir = Fpath.(manual_dir / B0_odoc.Theme.default_uri) in 63 | ignore @@ 64 | let* () = B0_memo.delete m to_dir in 65 | B0_odoc.Theme.write m t ~to_dir; 66 | (B0_memo.write m manual_css @@ fun () -> 67 | Ok "@charset UTF-8;\n@import url(\"_odoc-theme/manual.css\");"); 68 | Fut.return () 69 | 70 | let write_theme conf m theme = 71 | let to_dir = Fpath.(Conf.html_dir conf / B0_odoc.Theme.default_uri) in 72 | let* () = B0_memo.delete m to_dir in 73 | (match theme with None -> () | Some t -> B0_odoc.Theme.write m t ~to_dir); 74 | write_ocaml_manual_theme conf m theme; 75 | Fut.return () 76 | 77 | (* Builder *) 78 | 79 | type resolver = 80 | { mutable cobjs_by_digest : Doc_cobj.t list Digest.Map.t; 81 | mutable cobj_deps : (B0_odoc.Compile.Dep.t list Fut.t) Fpath.Map.t; 82 | mutable pkgs_todo : Pkg.Set.t; 83 | mutable pkgs_seen : Pkg.Set.t; 84 | mutable clear_pkg_odoc_dir : unit Fut.t Pkg.Map.t; } 85 | 86 | type builder = 87 | { m : B0_memo.t; 88 | conf : Conf.t; 89 | odoc_dir : Fpath.t; 90 | html_dir : Fpath.t; 91 | theme : B0_odoc.Theme.t option; 92 | index_title : string option; 93 | index_intro : Fpath.t option; 94 | index_toc : Fpath.t option; 95 | pkg_deps : bool; 96 | tag_index : bool; 97 | cobjs_by_modname : Doc_cobj.t list String.Map.t; 98 | r : resolver; } 99 | 100 | let builder 101 | m conf ~index_title ~index_intro ~index_toc ~pkg_deps ~tag_index pkgs_todo 102 | = 103 | let cache_dir = Conf.cache_dir conf in 104 | let odoc_dir = Fpath.(cache_dir / "odoc") in 105 | let html_dir = Conf.html_dir conf in 106 | let theme = get_theme conf in 107 | let cobjs_by_modname = 108 | let add p i acc = Doc_cobj.by_modname ~init:acc (Pkg_info.doc_cobjs i) in 109 | Pkg.Map.fold add (Conf.pkg_infos conf) String.Map.empty 110 | in 111 | let cobjs_by_digest = Digest.Map.empty in 112 | let cobj_deps = Fpath.Map.empty in 113 | let pkgs_todo = Pkg.Set.of_list pkgs_todo in 114 | let pkgs_seen = Pkg.Set.empty in 115 | let clear_pkg_odoc_dir = Pkg.Map.empty in 116 | { m; conf; odoc_dir; html_dir; theme; index_title; index_intro; index_toc; 117 | pkg_deps; tag_index; cobjs_by_modname; 118 | r = { cobjs_by_digest; cobj_deps; pkgs_todo; pkgs_seen; 119 | clear_pkg_odoc_dir } } 120 | 121 | let pkg_assets_dir = "_assets" 122 | let pkg_html_dir b pkg = Fpath.(b.html_dir / Pkg.name pkg) 123 | let pkg_odoc_dir b pkg = Fpath.(b.odoc_dir / Pkg.name pkg) 124 | 125 | let clear_pkg_odoc_dir b m pkg = 126 | match Pkg.Map.find_opt pkg b.r.clear_pkg_odoc_dir with 127 | | Some cleared -> cleared 128 | | None -> 129 | let cleared, set = Fut.make () in 130 | b.r.clear_pkg_odoc_dir <- Pkg.Map.add pkg cleared b.r.clear_pkg_odoc_dir; 131 | let pkg_odoc_dir = pkg_odoc_dir b pkg in 132 | Fut.await (B0_memo.delete m pkg_odoc_dir) set; 133 | cleared 134 | 135 | let require_pkg b pkg = 136 | if Pkg.Set.mem pkg b.r.pkgs_seen || Pkg.Set.mem pkg b.r.pkgs_todo then () else 137 | (Log.debug (fun m -> m "Package request %a" Pkg.pp pkg); 138 | b.r.pkgs_todo <- Pkg.Set.add pkg b.r.pkgs_todo) 139 | 140 | let odoc_file_for_cobj b cobj = 141 | let pkg = Doc_cobj.pkg cobj in 142 | let src_root = Pkg.path pkg in 143 | let dst_root = pkg_odoc_dir b pkg in 144 | let cobj = Doc_cobj.path cobj in 145 | Fpath.(reroot ~src_root ~dst_root cobj -+ ".odoc") 146 | 147 | let odoc_file_for_mld b pkg mld = (* assume mld names are flat *) 148 | let page = Fmt.str "page-%s" (Fpath.basename mld) in 149 | Fpath.(pkg_odoc_dir b pkg / page -+ ".odoc") 150 | 151 | let require_cobj_deps b cobj = (* Also used to find the digest of cobj *) 152 | let add_cobj_by_digest b cobj d = 153 | let cobjs = try Digest.Map.find d b.r.cobjs_by_digest with 154 | | Not_found -> [] 155 | in 156 | b.r.cobjs_by_digest <- Digest.Map.add d (cobj :: cobjs) b.r.cobjs_by_digest 157 | in 158 | let set_cobj_deps b cobj dep = 159 | b.r.cobj_deps <- Fpath.Map.add (Doc_cobj.path cobj) dep b.r.cobj_deps 160 | in 161 | match Fpath.Map.find_opt (Doc_cobj.path cobj) b.r.cobj_deps with 162 | | Some deps -> deps 163 | | None -> 164 | let pkg = Doc_cobj.pkg cobj in 165 | let m = B0_memo.with_mark b.m (Pkg.name pkg) in 166 | let fut_deps, set_deps = Fut.make () in 167 | let odoc_file = odoc_file_for_cobj b cobj in 168 | let deps_file = Fpath.(odoc_file + ".deps") in 169 | set_cobj_deps b cobj fut_deps; 170 | let* () = clear_pkg_odoc_dir b m pkg in 171 | begin 172 | B0_memo.ready_file m (Doc_cobj.path cobj); 173 | B0_odoc.Compile.Dep.write m (Doc_cobj.path cobj) ~o:deps_file; 174 | ignore @@ 175 | let* deps = B0_odoc.Compile.Dep.read m deps_file in 176 | let rec loop acc = function 177 | | [] -> set_deps acc; Fut.return () 178 | | d :: ds -> 179 | match B0_odoc.Compile.Dep.name d = Doc_cobj.modname cobj with 180 | | true -> 181 | add_cobj_by_digest b cobj (B0_odoc.Compile.Dep.digest d); 182 | loop acc ds 183 | | false -> 184 | loop (d :: acc) ds 185 | in 186 | loop [] deps 187 | end; 188 | fut_deps 189 | 190 | let cobj_deps b cobj = require_cobj_deps b cobj 191 | let cobj_deps_to_odoc_deps b deps = 192 | (* For each dependency this tries to find a cmi, cmti or cmt file 193 | that matches the dependency name and digest. We first look by 194 | dependency name in the universe and then request on the fly the 195 | computation of their digest via [require_cobj_deps] which updates 196 | b.cobjs_by_digest as a side effect. Once the proper compilation 197 | object has been found we then return the odoc file for that 198 | file. Since we need to make sure that this odoc file actually 199 | gets built its package is added to the set of packages that need 200 | to be built; unless [b.pkg_deps] is false. *) 201 | let candidate_cobjs dep = 202 | let n = B0_odoc.Compile.Dep.name dep in 203 | let cobjs = match String.Map.find_opt n b.cobjs_by_modname with 204 | | Some cobjs -> cobjs 205 | | None -> 206 | Log.debug (fun m -> m "Cannot find compilation object for %s" n); 207 | [] 208 | in 209 | dep, List.map (fun cobj -> cobj, (require_cobj_deps b cobj)) cobjs 210 | in 211 | let resolve_dep (dep, candidates) acc = 212 | let rec loop = function 213 | | [] -> 214 | Log.debug begin fun m -> 215 | m "Cannot resolve dependency for %a" B0_odoc.Compile.Dep.pp dep 216 | end; 217 | Fut.return acc 218 | | (cobj, deps) :: cs -> 219 | Fut.bind deps @@ fun _ -> 220 | let digest = B0_odoc.Compile.Dep.digest dep in 221 | match Digest.Map.find_opt digest b.r.cobjs_by_digest with 222 | | None -> loop cs 223 | | Some (cobj :: _ (* FIXME Log on debug. *)) -> 224 | begin match b.pkg_deps with 225 | | true -> 226 | require_pkg b (Doc_cobj.pkg cobj); 227 | Fut.return (odoc_file_for_cobj b cobj :: acc) 228 | | false -> 229 | let pkg = Doc_cobj.pkg cobj in 230 | if Pkg.Set.mem pkg b.r.pkgs_todo || 231 | Pkg.Set.mem pkg b.r.pkgs_seen 232 | then Fut.return (odoc_file_for_cobj b cobj :: acc) 233 | else loop cs 234 | end 235 | | Some [] -> assert false 236 | in 237 | loop candidates 238 | in 239 | let dep_candidates_list = List.map candidate_cobjs deps in 240 | let rec loop cs acc = match cs with 241 | | [] -> Fut.return acc 242 | | c :: cs -> Fut.bind (resolve_dep c acc) (loop cs) 243 | in 244 | loop dep_candidates_list [] 245 | 246 | let cobj_to_odoc b cobj = 247 | let odoc = odoc_file_for_cobj b cobj in 248 | begin 249 | ignore @@ 250 | let* deps = cobj_deps b cobj in 251 | let* odoc_deps = cobj_deps_to_odoc_deps b deps in 252 | let pkg = Pkg.name (Doc_cobj.pkg cobj) in 253 | let hidden = Doc_cobj.hidden cobj in 254 | let cobj = Doc_cobj.path cobj in 255 | B0_odoc.Compile.to_odoc b.m ~hidden ~pkg ~odoc_deps cobj ~o:odoc; 256 | Fut.return () 257 | end; 258 | odoc 259 | 260 | let mld_to_odoc b pkg pkg_odocs mld = 261 | let odoc = odoc_file_for_mld b pkg mld in 262 | let pkg = Pkg.name pkg in 263 | let odoc_deps = 264 | (* XXX odoc compile-deps does not work on .mld files, so we 265 | simply depend on all of the package's odoc files. This is 266 | needed for example for {!modules } to work in the index. 267 | trefis says: In the long term this will be solved since all 268 | reference resolution will happen at the `html-deps` step. For 269 | now that seems a good approximation. *) 270 | pkg_odocs 271 | in 272 | B0_odoc.Compile.to_odoc b.m ~pkg ~odoc_deps mld ~o:odoc; 273 | odoc 274 | 275 | let index_mld_for_pkg b pkg pkg_info _pkg_odocs ~user_index_mld = 276 | let index_mld = Fpath.(pkg_odoc_dir b pkg / "index.mld") in 277 | let write_index_mld ~user_index = 278 | let reads = Option.to_list user_index_mld in 279 | let reads = match Opam.file pkg with 280 | | None -> reads 281 | | Some file -> B0_memo.ready_file b.m file; file :: reads 282 | in 283 | let stamp = 284 | (* Influences the index content; we could relativize file paths *) 285 | let fields = List.rev_map snd Pkg_info.field_names in 286 | let data = List.rev_map (fun f -> Pkg_info.get f pkg_info) fields in 287 | let data = odig_version :: (Pkg.name pkg) :: List.concat data in 288 | Hash.to_binary_string (B0_memo.hash_string b.m (String.concat "" data)) 289 | in 290 | B0_memo.write b.m ~stamp ~reads index_mld @@ fun () -> 291 | let with_tag_links = b.tag_index in 292 | Ok (Odig_odoc_page.index_mld b.conf pkg pkg_info ~with_tag_links 293 | ~user_index) 294 | in 295 | begin match user_index_mld with 296 | | None -> write_index_mld ~user_index:None 297 | | Some i -> 298 | ignore @@ 299 | let* s = B0_memo.read b.m i in 300 | write_index_mld ~user_index:(Some s); 301 | Fut.return () 302 | end; 303 | index_mld 304 | 305 | let mlds_to_odoc b pkg pkg_info pkg_odocs mlds = 306 | let rec loop ~user_index_mld pkg_odocs = function 307 | | mld :: mlds -> 308 | B0_memo.ready_file b.m mld; 309 | let odocs, user_index_mld = match Fpath.basename mld = "index.mld" with 310 | | false -> mld_to_odoc b pkg pkg_odocs mld :: pkg_odocs, user_index_mld 311 | | true -> pkg_odocs, Some mld 312 | in 313 | loop ~user_index_mld odocs mlds 314 | | [] -> 315 | (* We do the index at the end due to a lack of functioning 316 | of `compile-deps` on mld files this increases the chances 317 | we get the correct links towards other mld files since those 318 | will be in [odocs]. *) 319 | let mld = index_mld_for_pkg b pkg pkg_info pkg_odocs ~user_index_mld in 320 | (mld_to_odoc b pkg pkg_odocs mld :: pkg_odocs) 321 | in 322 | loop ~user_index_mld:None [] mlds 323 | 324 | let html_deps_resolve b deps = 325 | let deps = List.rev_map B0_odoc.Html.Dep.to_compile_dep deps in 326 | cobj_deps_to_odoc_deps b deps 327 | 328 | let link_odoc_assets b pkg pkg_info = 329 | let src = Doc_dir.odoc_assets_dir (Pkg_info.doc_dir pkg_info) in 330 | let dst = Fpath.(pkg_html_dir b pkg / pkg_assets_dir) in 331 | link_if_exists src dst 332 | 333 | let link_odoc_doc_dir b pkg pkg_info = 334 | let src = Doc_dir.dir (Pkg_info.doc_dir pkg_info) in 335 | let dst = Fpath.(pkg_html_dir b pkg / "_doc-dir") in 336 | link_if_exists src dst 337 | 338 | let pkg_to_html b pkg = 339 | let b = { b with m = B0_memo.with_mark b.m (Pkg.name pkg) } in 340 | let pkg_info = try Pkg.Map.find pkg (Conf.pkg_infos b.conf) with 341 | | Not_found -> assert false 342 | in 343 | let cobjs = Pkg_info.doc_cobjs pkg_info in 344 | let mlds = Doc_dir.odoc_pages (Pkg_info.doc_dir pkg_info) in 345 | if cobjs = [] && mlds = [] then Fut.return () else 346 | let pkg_html_dir = pkg_html_dir b pkg in 347 | let pkg_odoc_dir = pkg_odoc_dir b pkg in 348 | let* () = B0_memo.delete b.m pkg_html_dir in 349 | let odocs = List.map (cobj_to_odoc b) cobjs in 350 | let mld_odocs = mlds_to_odoc b pkg pkg_info odocs mlds in 351 | let odoc_files = List.rev_append odocs mld_odocs in 352 | let deps_file = Fpath.(pkg_odoc_dir / Pkg.name pkg + ".html.deps") in 353 | B0_odoc.Html.Dep.write b.m ~odoc_files pkg_odoc_dir ~o:deps_file; 354 | let* deps = B0_odoc.Html.Dep.read b.m deps_file in 355 | let* odoc_deps_res = html_deps_resolve b deps in 356 | (* XXX html deps is a bit broken make sure we have at least our 357 | own files as deps maybe related to compiler-deps not working on .mld 358 | files *) 359 | let odoc_deps = Fpath.distinct (List.rev_append odoc_files odoc_deps_res) in 360 | let theme_uri = match b.theme with 361 | | Some _ -> Some B0_odoc.Theme.default_uri | None -> None 362 | in 363 | let html_dir = b.html_dir in 364 | let to_html = B0_odoc.Html.write b.m ?theme_uri ~html_dir ~odoc_deps in 365 | List.iter to_html odoc_files; 366 | link_odoc_assets b pkg pkg_info; 367 | link_odoc_doc_dir b pkg pkg_info; 368 | Fut.return () 369 | 370 | let index_frag_to_html b frag ~o = match frag with 371 | | None -> Fut.return None 372 | | Some mld -> 373 | let mld = Fpath.(Conf.cwd b.conf // mld) in 374 | let is_odoc _ _ f acc = if Fpath.has_ext ".odoc" f then f :: acc else acc in 375 | let odoc_deps = Os.Dir.fold_files ~recurse:true is_odoc b.odoc_dir [] in 376 | let odoc_deps = B0_memo.fail_if_error b.m odoc_deps in 377 | let o = Fpath.(b.odoc_dir / o) in 378 | B0_memo.ready_file b.m mld; 379 | B0_odoc.Html_fragment.cmd b.m ~odoc_deps mld ~o; 380 | let* res = B0_memo.read b.m o in 381 | Fut.return (Some res) 382 | 383 | let index_intro_to_html b = 384 | index_frag_to_html b b.index_intro ~o:"index-header.html" 385 | 386 | let index_toc_to_html b = 387 | index_frag_to_html b b.index_toc ~o:"index-toc.html" 388 | 389 | let write_pkgs_index b ~ocaml_manual_uri = 390 | let add_pkg_data pkg_infos acc p = match Pkg.Map.find p pkg_infos with 391 | | exception Not_found -> acc 392 | | info -> 393 | let version = Pkg_info.get `Version info in 394 | let synopsis = Pkg_info.get `Synopsis info in 395 | let tags = Pkg_info.get `Tags info in 396 | let ( ++ ) = List.rev_append in 397 | version ++ synopsis ++ tags ++ acc 398 | in 399 | let* raw_index_intro = index_intro_to_html b in 400 | let* raw_index_toc = index_toc_to_html b in 401 | let pkg_infos = Conf.pkg_infos b.conf in 402 | let pkgs = Odig_odoc_page.pkgs_with_html_docs b.conf in 403 | let stamp = match raw_index_intro with None -> [] | Some s -> [s] in 404 | let stamp = List.fold_left (add_pkg_data pkg_infos) stamp pkgs in 405 | let stamp = match ocaml_manual_uri with 406 | | None -> stamp 407 | | Some s -> (s :: stamp) 408 | in 409 | let stamp = String.concat " " (odig_version :: stamp) in 410 | let index = Fpath.(b.html_dir / "index.html") in 411 | let index_title = b.index_title in 412 | (B0_memo.write b.m ~stamp index @@ fun () -> 413 | Ok (Odig_odoc_page.pkg_list 414 | b.conf ~index_title ~raw_index_intro ~raw_index_toc 415 | ~tag_index:b.tag_index ~ocaml_manual_uri pkgs)); 416 | Fut.return () 417 | 418 | let write_ocaml_manual b = 419 | let manual_pkg_dir = Fpath.(Conf.doc_dir b.conf / ocaml_manual_pkg) in 420 | let manual_index = Fpath.(manual_pkg_dir / "index.html") in 421 | let dst = Fpath.(Conf.html_dir b.conf / ocaml_manual_pkg) in 422 | match Os.File.exists manual_index |> Log.if_error ~use:false with 423 | | false -> None 424 | | true -> 425 | begin 426 | ignore @@ 427 | let* () = B0_memo.delete b.m dst in 428 | let copy_file m ~src_root ~dst_root src = 429 | let dst = Fpath.reroot ~src_root ~dst_root src in 430 | B0_memo.ready_file m src; 431 | B0_memo.copy m src ~dst 432 | in 433 | let src = manual_pkg_dir in 434 | let files = Os.Dir.fold_files ~recurse:true Os.Dir.path_list src [] in 435 | let files = B0_memo.fail_if_error b.m files in 436 | List.iter (copy_file b.m ~src_root:src ~dst_root:dst) files; 437 | Fut.return () 438 | end; 439 | Some "ocaml-manual/index.html" 440 | 441 | let rec build b = match Pkg.Set.choose b.r.pkgs_todo with 442 | | exception Not_found -> 443 | B0_memo.stir ~block:true b.m; 444 | begin match Pkg.Set.is_empty b.r.pkgs_todo with 445 | | false -> build b 446 | | true -> 447 | let without_theme = match b.theme with None -> true | Some _ -> false in 448 | let html_dir = b.html_dir and build_dir = b.odoc_dir in 449 | B0_odoc.Support_files.write b.m ~without_theme ~html_dir ~build_dir; 450 | let ocaml_manual_uri = write_ocaml_manual b in 451 | ignore (write_pkgs_index b ~ocaml_manual_uri); 452 | write_theme b.conf b.m b.theme; 453 | end 454 | | pkg -> 455 | b.r.pkgs_todo <- Pkg.Set.remove pkg b.r.pkgs_todo; 456 | b.r.pkgs_seen <- Pkg.Set.add pkg b.r.pkgs_seen; 457 | ignore (pkg_to_html b pkg); 458 | build b 459 | 460 | let write_log_file c memo = 461 | Log.if_error ~use:() @@ 462 | B0_memo_log.(write (Conf.b0_log_file c) (of_memo memo)) 463 | 464 | let gen 465 | c ~force ~index_title ~index_intro ~index_toc ~pkg_deps ~tag_index 466 | pkgs_todo 467 | = 468 | Result.bind (Conf.memo c) @@ fun m -> 469 | let b = 470 | builder 471 | m c ~index_title ~index_intro ~index_toc ~pkg_deps ~tag_index pkgs_todo 472 | in 473 | Os.Exit.on_sigint ~hook:(fun () -> write_log_file c m) @@ fun () -> 474 | B0_memo.run_proc m (fun () -> build b); 475 | B0_memo.stir ~block:true m; 476 | write_log_file c m; 477 | Log.time (fun _ m -> m "deleting trash") begin fun () -> 478 | Log.if_error ~use:() (B0_memo.delete_trash ~block:false m) 479 | end; 480 | match B0_memo.status b.m with 481 | | Ok () as v -> v 482 | | Error e -> 483 | let read_howto = Fmt.any "odig log -r " in 484 | let write_howto = Fmt.any "odig log -w " in 485 | B0_zero_conv.Op.pp_aggregate_error 486 | ~read_howto ~write_howto () Fmt.stderr e; 487 | Error "Documentation might be incomplete (see: odig log --failed)." 488 | 489 | let install_theme c theme = 490 | Result.bind (Conf.memo c) @@ fun m -> 491 | B0_memo.run_proc m (fun () -> write_theme c m theme); 492 | B0_memo.stir ~block:true m; 493 | match B0_memo.status m with 494 | | Ok () as v -> v 495 | | Error e -> Error "Could not set theme" 496 | -------------------------------------------------------------------------------- /src/odig_odoc.mli: -------------------------------------------------------------------------------- 1 | (*--------------------------------------------------------------------------- 2 | Copyright (c) 2018 The odig programmers. All rights reserved. 3 | SPDX-License-Identifier: ISC 4 | ---------------------------------------------------------------------------*) 5 | 6 | (** [odoc] API reference generation. 7 | 8 | This mainly implements an [odoc] file resolution and generation 9 | request procedure specific to [odig]. Generic [odoc] driving bits 10 | are provided via {!B0_odoc}. *) 11 | 12 | open Odig_support 13 | 14 | val gen : 15 | Conf.t -> force:bool -> index_title:string option -> 16 | index_intro:B0_std.Fpath.t option -> index_toc:B0_std.Fpath.t option -> 17 | pkg_deps:bool -> tag_index:bool -> 18 | Pkg.t list -> (unit, string) result 19 | (** [gen c ~force ~index_intro ~pkg_deps ~tag_index pkgs] 20 | generates API reference for packages [pkgs]. 21 | {ul 22 | {- [index_title] is the title of the page 23 | with the list of packages.} 24 | {- [index_intro] if specified is an mld file to 25 | define the introduction of the page with the list of packages.} 26 | {- [index_toc] if specified is an mld file to 27 | define the table of contents for the page with the list of packages.} 28 | {- [pkg_deps] if [true] dependencies of [pkgs] are also generated. 29 | If [false] only [pkgs] are generated which may lead to broken 30 | links in the output.} 31 | {- [tag_index] if [true] a tag index is generated on the package list 32 | page and package pages hyperlink into it from the package information 33 | section.}} *) 34 | 35 | val install_theme : Conf.t -> B0_odoc.Theme.t option -> (unit, string) result 36 | -------------------------------------------------------------------------------- /src/odig_odoc_page.ml: -------------------------------------------------------------------------------- 1 | (*--------------------------------------------------------------------------- 2 | Copyright (c) 2018 The odig programmers. All rights reserved. 3 | SPDX-License-Identifier: ISC 4 | ---------------------------------------------------------------------------*) 5 | 6 | open Odig_support 7 | open B0_std 8 | open B0_html 9 | 10 | let anchor_href aid = At.href (Fmt.str "#%s" aid) 11 | let anchor_a aid = 12 | El.a ~at:At.[anchor_href aid; class' "anchor"; v "aria-hidden" "true"] [] 13 | 14 | let a_to_utf_8_txt ~href:h txt = 15 | El.a ~at:At.[type' "text/plain; charset=UTF-8"; href h] [El.txt txt] 16 | 17 | let doc_dir_link ?txt f = 18 | let fname = Fpath.basename (Fpath.v f) in 19 | let txt = match txt with None -> fname | Some txt -> txt in 20 | a_to_utf_8_txt ~href:(Fmt.str "_doc-dir/%s" fname) txt 21 | 22 | (* Package index.mld file *) 23 | 24 | let pkg_title pkg pkg_info = 25 | let short_info = 26 | let get_first f el = match Pkg_info.get f pkg_info with 27 | | [] -> El.txt "" | v :: _ -> el v 28 | in 29 | let version = get_first `Version @@ fun version -> 30 | El.span ~at:At.[class' "version"] [El.txt version] 31 | in 32 | let changes_link = get_first `Changes_files @@ fun c -> 33 | doc_dir_link ~txt:"changes" c 34 | in 35 | let issues_link = get_first `Issues @@ fun issues -> 36 | El.a ~at:At.[href issues] [El.txt "issues"] 37 | in 38 | let info_link = El.a ~at:At.[href "#package_info"] [El.txt "more…"] in 39 | let sp = El.txt " " in 40 | [version; sp; El.nav [changes_link; sp; issues_link; sp; info_link]] 41 | in 42 | Fmt.str "{0:package-%s Package %s {%%html:%s%%}}" 43 | (Pkg.name pkg) (Pkg.name pkg) 44 | (El.to_string ~doctype:false (El.splice short_info)) 45 | 46 | let ocaml_pkg_module_indexes pkg_info = 47 | (* Too much noise to use regular index generation. We cook up 48 | something manually. People should simply stop vomiting in the 49 | ocaml directory. The day upstream wants control over it can 50 | provide an `index.mld` file at the right place. It will override 51 | this. *) 52 | let dirid f = Fpath.basename (Fpath.parent f) in 53 | let add_cobj acc cobj = 54 | let dirid = dirid (Doc_cobj.path cobj) in 55 | match String.Map.find dirid acc with 56 | | exception Not_found -> String.Map.add dirid [cobj] acc 57 | | cobjs -> String.Map.add dirid (cobj :: cobjs) acc 58 | in 59 | let cobjs = Pkg_info.doc_cobjs pkg_info in 60 | let bydir = List.fold_left add_cobj String.Map.empty cobjs in 61 | let dirmods ~lib = 62 | let mods = match String.Map.find lib bydir with 63 | | cobjs -> 64 | let cobjs = List.filter (Fun.negate Doc_cobj.don't_list) cobjs in 65 | List.rev_map Doc_cobj.modname cobjs 66 | | exception Not_found -> 67 | if lib = "unix" then (* < OCaml 5 *) ["Unix"; "UnixLabels"] else [] 68 | in 69 | if mods = [] then "" else 70 | let mods = List.sort String.compare mods in 71 | let libid = String.map (function '-' -> '_' | c -> c) lib in 72 | Fmt.str "{1:%s Library [%s]}\n{!modules: %s}\n" libid lib 73 | (String.concat " " mods) 74 | in 75 | let stdlib_mods = match String.Map.find "ocaml" bydir with 76 | | exception Not_found -> "" 77 | | cobjs -> 78 | let stdlib_mod cobj = 79 | let modn = Doc_cobj.modname cobj in 80 | let prefix = "Stdlib__" in 81 | if not (String.starts_with ~prefix modn) then None else 82 | Some (String.replace_all ~sub:"__" ~by:"." modn) 83 | in 84 | let mods = List.filter_map stdlib_mod cobjs in 85 | let mods = List.sort String.compare mods in 86 | String.concat " " mods 87 | in 88 | Fmt.str "{1:library_stdlib Library [stdlib]}\n\ 89 | {!modules: Stdlib %s}\n\ 90 | %s%s%s%s%s%s%s" 91 | stdlib_mods 92 | (dirmods ~lib:"unix") 93 | (dirmods ~lib:"dynlink") 94 | (dirmods ~lib:"runtime_events") 95 | (dirmods ~lib:"str") 96 | (dirmods ~lib:"threads") 97 | (dirmods ~lib:"ocamldoc") 98 | (dirmods ~lib:"compiler-libs") 99 | 100 | let pkg_index pkg pkg_info ~user_index = 101 | let drop_section_0 s = match String.cut_left ~sep:"{0" s with 102 | | Some (t, r) when String.trim t = "" -> 103 | (* can break but should be mostly ok *) 104 | let max = String.length r in 105 | let rec loop c i max = match i > max with 106 | | true -> s 107 | | false -> 108 | match r.[i] with 109 | | '{' -> loop (c + 1) (i + 1) max 110 | | '}' when c = 1 -> String.drop_left (i + 1) r 111 | | '}' -> loop (c - 1) (i + 1) max 112 | | _ -> loop c (i + 1) max 113 | in 114 | loop 1 0 max 115 | | _ -> s 116 | in 117 | match user_index with 118 | | Some user_index -> drop_section_0 user_index 119 | | None when Pkg.name pkg = "ocaml" -> ocaml_pkg_module_indexes pkg_info 120 | | None -> 121 | let cobjs = Pkg_info.doc_cobjs pkg_info in 122 | let cobjs = List.filter (fun c -> not (Doc_cobj.don't_list c)) cobjs in 123 | let mods = List.rev_map Doc_cobj.modname cobjs in 124 | let mods = List.sort String.compare mods in 125 | Fmt.str "{!modules: %s}" (String.concat " " mods) 126 | 127 | let pkg_info_section pkg pkg_info ~with_tag_links = 128 | let def_values field fname fval i =match Pkg_info.get field i with 129 | | [] -> El.txt "" 130 | | vs -> 131 | let fid = Fmt.str "info-%s" fname in 132 | let vs = List.sort compare vs in 133 | let vs = List.map (fun v -> El.li (fval v)) vs in 134 | El.tr ~at:At.[id fid] El.[td [(anchor_a fid); txt fname]; td [ul vs]] 135 | in 136 | let defs pkg pkg_info = 137 | let string_val str = [El.txt str] in 138 | let uri_val uri = [El.a ~at:At.[href uri] [El.txt uri]] in 139 | let file_val f = [doc_dir_link f] in 140 | let pkg_val pkg = 141 | [El.a ~at:At.[href (Fmt.str "../%s/index.html" pkg)] [El.txt pkg]] 142 | in 143 | let tag_val t = match with_tag_links with 144 | | true -> [El.a ~at:At.[href (Fmt.str "../index.html#tag-%s" t)] [El.txt t]] 145 | | false -> [El.txt t] 146 | in 147 | [ def_values `Authors "authors" string_val pkg_info; 148 | def_values `Changes_files "changes-files" file_val pkg_info; 149 | def_values `Depends "depends" pkg_val pkg_info; 150 | def_values `Homepage "homepage" uri_val pkg_info; 151 | def_values `Issues "issues" uri_val pkg_info; 152 | def_values `License "license" string_val pkg_info; 153 | def_values `License_files "license-files" file_val pkg_info; 154 | def_values `Maintainers "maintainers" string_val pkg_info; 155 | def_values `Online_doc "online-doc" uri_val pkg_info; 156 | def_values `Readme_files "readme-files" file_val pkg_info; 157 | def_values `Repo "repo" string_val pkg_info; 158 | def_values `Tags "tags" tag_val pkg_info; 159 | def_values `Version "version" string_val pkg_info ] 160 | in 161 | Fmt.str "{1:package_info Package info}\n {%%html:%s%%}" @@ 162 | El.to_string ~doctype:false 163 | (El.table ~at:At.[class' "package"; class' "info"] (defs pkg pkg_info)) 164 | 165 | let index_mld conf pkg pkg_info ~user_index ~with_tag_links = 166 | Fmt.str "%s\n%s\n%s" 167 | (pkg_title pkg pkg_info) 168 | (pkg_index pkg pkg_info ~user_index) 169 | (pkg_info_section pkg pkg_info ~with_tag_links) 170 | 171 | (* Package list *) 172 | 173 | let pkg_li conf ~pid pkg = 174 | let info = try Pkg.Map.find pkg (Conf.pkg_infos conf) with 175 | | Not_found -> assert false (* formally, could be racy *) 176 | in 177 | let name = Pkg.name pkg in 178 | let version = String.concat " " (Pkg_info.get `Version info) in 179 | let synopsis = String.concat " " (Pkg_info.get `Synopsis info) in 180 | let index = Fmt.str "%s/index.html" name in 181 | let pid = pid name in 182 | El.li ~at:At.[id pid] [ 183 | anchor_a pid; 184 | El.a ~at:At.[href index] [El.txt name]; El.txt " "; 185 | El.span ~at:At.[class' "version"] [El.txt version]; El.txt " "; 186 | El.span ~at:At.[class' "synopsis"] [El.txt synopsis]; ] 187 | 188 | let pkg_list conf pkgs = 189 | let letter_id l = Fmt.str "name-%s" l in 190 | let letter_link (l, _) = El.(a ~at:At.[anchor_href (letter_id l)] [txt l]) in 191 | let letter_section (l, pkgs) = 192 | let pkgs = List.sort Pkg.compare_by_caseless_name pkgs in 193 | let letter_id = letter_id l in 194 | let pid = Fmt.str "package-%s" in 195 | El.splice @@ 196 | [El.h3 ~at:At.[id letter_id] [anchor_a letter_id; El.txt l]; 197 | El.ol ~at:At.[class' "packages"] (List.map (pkg_li conf ~pid) pkgs)] 198 | in 199 | let by_name = "by-name" in 200 | let classes p = [String.of_char (Char.Ascii.lowercase (Pkg.name p).[0])] in 201 | let classes = List.classify ~cmp_elts:Pkg.compare ~classes pkgs in 202 | [ El.h2 ~at:At.[id by_name] [anchor_a by_name; El.txt "Packages by name"]; 203 | El.div ~at:At.[class' by_name] [ 204 | El.nav (List.map letter_link classes); 205 | El.splice (List.map letter_section classes) ]] 206 | 207 | let tag_list conf pkgs = 208 | let tag_id t = Fmt.str "tag-%s" t in 209 | let tag_links tags = 210 | let tag_li (t, _) = El.(li [a ~at:At.[anchor_href (tag_id t)] [txt t]]) in 211 | let tags_by_letter (letter, tags) = 212 | let lid = Fmt.str "tags-%s" letter in 213 | El.(tr ~at:At.[id lid] 214 | [td [anchor_a lid; txt letter]; 215 | td [ol ~at:At.[class' "tags"] (List.map tag_li tags)]]) 216 | in 217 | let classes (t, _) = [String.of_char (Char.Ascii.lowercase t.[0])] in 218 | let cmp_elts (t, _) (t', _) = String.compare t t' in 219 | let tag_classes = List.classify ~cmp_elts ~classes tags in 220 | El.table (List.map tags_by_letter tag_classes) 221 | in 222 | let tag_section (t, pkgs) = 223 | let pkgs = List.sort Pkg.compare_by_caseless_name pkgs in 224 | let tag_id = tag_id t in 225 | let pid = Fmt.str "tag-%s-package-%s" t in 226 | El.splice @@ 227 | [El.h3 ~at:At.[id tag_id] [anchor_a tag_id; El.span [El.txt t]]; 228 | El.ol ~at:At.[class' "packages"] (List.map (pkg_li conf ~pid) pkgs)] 229 | in 230 | let by_tag = "by-tag" in 231 | let pkg_infos = Conf.pkg_infos conf in 232 | let classes p = try Pkg_info.get `Tags (Pkg.Map.find p pkg_infos) with 233 | | Not_found -> assert false 234 | in 235 | let classes = List.classify ~cmp_elts:Pkg.compare ~classes pkgs in 236 | [ El.h2 ~at:At.[id by_tag] [anchor_a by_tag; El.txt "Packages by tag"]; 237 | El.div ~at:At.[class' by_tag] [ 238 | El.nav [tag_links classes]; 239 | El.splice (List.map tag_section classes)]] 240 | 241 | let manual_reference conf ~ocaml_manual_uri = 242 | let manual_online = "https://ocaml.org/manual/" in 243 | let uri, suff = match ocaml_manual_uri with 244 | | None -> manual_online, El.txt " (online, latest version)" 245 | | Some href -> href, El.txt "" 246 | in 247 | El.splice @@ [El.a ~at:At.[href uri] [El.txt "OCaml manual"]; suff], uri 248 | 249 | let stdlib_link conf = "ocaml/index.html#library_stdlib" 250 | 251 | let pkgs_with_html_docs conf = 252 | let by_names = Pkg.by_names (Conf.pkgs conf) in 253 | let add_pkg _ name dir acc = 254 | let exists = Os.File.exists Fpath.(dir / "index.html") in 255 | match exists |> Log.if_error ~level:Log.Warning ~use:false with 256 | | false -> acc 257 | | true -> 258 | match String.Map.find name by_names with 259 | | exception Not_found -> acc 260 | | pkg -> pkg :: acc 261 | in 262 | let pkgs = Os.Dir.fold_dirs ~recurse:false add_pkg (Conf.html_dir conf) [] in 263 | let pkgs = pkgs |> Log.if_error ~level:Log.Warning ~use:[] in 264 | List.sort Pkg.compare pkgs 265 | 266 | let pkg_list 267 | conf ~index_title ~raw_index_intro ~raw_index_toc ~tag_index 268 | ~ocaml_manual_uri pkgs 269 | = 270 | (* XXX for now it's easier to do it this way. In the future we should 271 | rather use the ocamldoc language. Either by using 272 | https://github.com/ocaml/odoc/issues/94 or `--fragment`. So 273 | that we don't have to guess the way package links are formed. *) 274 | let doc_head ~style_href page_title = (* a basic head *) 275 | El.head [ 276 | El.meta ~at:At.[charset "utf-8"] (); 277 | El.meta ~at:At.[name "generator"; content "odig %%VERSION%%"] (); 278 | El.meta ~at:At.[name "viewport"; 279 | content "width=device-width, initial-scale=1.0"] (); 280 | El.link ~at:At.[rel "stylesheet"; type' "text/css"; media "screen, print"; 281 | href style_href; ] (); 282 | El.title [El.txt page_title]] 283 | in 284 | let stdlib_link = stdlib_link conf in 285 | let manual_markup, manual_href = manual_reference conf ~ocaml_manual_uri in 286 | let doc_header = 287 | let comma = El.txt ", " in 288 | let contents = match raw_index_intro with 289 | | Some h -> [El.unsafe_raw h] 290 | | None -> 291 | let browse_by_tag = match tag_index with 292 | | true -> El.(splice [a ~at:At.[href "#by-tag"] [txt "by tag"]; comma]) 293 | | false -> El.splice [] 294 | in 295 | [ El.h1 [El.txt "OCaml package documentation"]; 296 | El.p [El.txt "Browse "; 297 | El.a ~at:At.[href "#by-name"] [El.txt "by name"]; comma; 298 | browse_by_tag; 299 | El.txt " the "; 300 | El.a ~at:At.[href stdlib_link] [El.txt "standard library"]; 301 | El.txt " and the "; manual_markup; El.txt ".";]; 302 | El.p [El.small [El.txt "Generated for "; 303 | El.code 304 | [El.txt (Fpath.to_string (Conf.lib_dir conf))]]];] 305 | in 306 | El.header ~at:At.[class' "odoc-preamble"] contents 307 | in 308 | let toc = 309 | let contents = match raw_index_toc with 310 | | Some toc -> El.unsafe_raw toc 311 | | None -> 312 | let packages_by_tag_li = match tag_index with 313 | | false -> El.splice [] 314 | | true -> 315 | El.li [El.a ~at:At.[href "#by-tag"] [El.txt "Packages by tag"]] 316 | in 317 | El.ul [ 318 | El.li [El.a ~at:At.[href stdlib_link] 319 | [El.txt "OCaml standard library"]]; 320 | El.li [El.a ~at:At.[href manual_href] [El.txt "OCaml manual"]]; 321 | El.li [El.a ~at:At.[href "#by-name"] [El.txt "Packages by name"]]; 322 | packages_by_tag_li; ] 323 | in 324 | El.div ~at:At.[class' "odoc-tocs"] 325 | [El.nav ~at:At.[class' "odoc-toc"] [contents]] 326 | in 327 | let style_href = "_odoc-theme/odoc.css" in 328 | let page_title = match index_title with 329 | | None -> Fpath.(basename @@ parent (Conf.lib_dir conf)) 330 | | Some t -> t 331 | in 332 | El.to_string ~doctype:true @@ 333 | El.html [ 334 | doc_head ~style_href page_title; 335 | El.body ~at:At.[class' "odoc"] 336 | [ El.nav ~at:At.[class' "odoc-nav"] [El.txt "\xF0\x9F\x90\xAB"]; 337 | doc_header; 338 | toc; 339 | El.div ~at:At.[class' "odoc-content"] [ 340 | El.splice (pkg_list conf pkgs); 341 | El.splice (if tag_index then tag_list conf pkgs else [])]]] 342 | -------------------------------------------------------------------------------- /src/odig_odoc_page.mli: -------------------------------------------------------------------------------- 1 | (*--------------------------------------------------------------------------- 2 | Copyright (c) 2018 The odig programmers. All rights reserved. 3 | SPDX-License-Identifier: ISC 4 | ---------------------------------------------------------------------------*) 5 | 6 | (** mld and HTML pages generated by odig itself. *) 7 | 8 | open Odig_support 9 | 10 | (** {1:pages Pages} *) 11 | 12 | val index_mld : 13 | Conf.t -> Pkg.t -> Pkg_info.t -> user_index:string option -> 14 | with_tag_links:bool -> string 15 | (** [index_mld conf pkg pkg_info ~user_index ~with_tag_links ] is an 16 | [index.mld] page for package [pkg] with info [pkg_info]. 17 | {ul 18 | {- If [user_index] is [Some content], that user provided content is 19 | integrated in the result (a leading section ["{0"] is stripped 20 | away though) and no module index is generated by the function.} 21 | {- [with_tag_links], if [true] assumes the package list generated 22 | by {!pkg_list} has a tag index and links the tags of the 23 | metadata section into it.}} *) 24 | 25 | val pkgs_with_html_docs : Conf.t -> Pkg.t list 26 | (** [pkg_with_html_docs c] looks up in [c] packages that 27 | seem to have generated documentation. *) 28 | 29 | val pkg_list : 30 | Conf.t -> index_title:string option -> raw_index_intro:string option -> 31 | raw_index_toc:string option -> tag_index:bool -> 32 | ocaml_manual_uri:string option -> Pkg.t list -> string 33 | (** [pkg_list c ~index_title ~raw_index_intro ~tag_index ~ocaml_manual_uri pkgs] 34 | is an HTML page package list for the packages [pkgs]. 35 | {ul 36 | {- [index_title] is a title for the page with the list of 37 | packages; if unspecified one is automatically generated.} 38 | {- [raw_index_intro] is the HTML markup that is inserted before the 39 | list of packages; if unspecified one is automatically 40 | generated.} 41 | {- [raw_index_toc] is the HTML markup that is inserted in the toc 42 | list of packages page; if unspecified one is automatically 43 | generated.} 44 | {- [tag_index] if [true] a package tag is added to the page after 45 | the list of packages by name.} 46 | {- [ocaml_manual_uri] is a local URI to the OCaml manual if unspecified 47 | the online manual is linked.}} *) 48 | -------------------------------------------------------------------------------- /src/odig_support.ml: -------------------------------------------------------------------------------- 1 | (*--------------------------------------------------------------------------- 2 | Copyright (c) 2018 The odig programmers. All rights reserved. 3 | SPDX-License-Identifier: ISC 4 | ---------------------------------------------------------------------------*) 5 | 6 | open B0_std 7 | 8 | module Digest = struct 9 | include Digest 10 | let pp ppf d = Format.pp_print_string ppf (to_hex d) 11 | let pp_opt ppf = function 12 | | None -> Fmt.string ppf "--------------------------------" 13 | | Some d -> pp ppf d 14 | 15 | module Set = Set.Make (Digest) 16 | module Map = Map.Make (Digest) 17 | end 18 | 19 | module Pkg = struct 20 | type name = string 21 | type t = name * Fpath.t 22 | let name = fst 23 | let path = snd 24 | let pp ppf (n, p) = Fmt.pf ppf "%s %a" n (Fmt.st' [`Faint] Fpath.pp_quoted) p 25 | let pp_name ppf (n, p) = Fmt.string ppf n 26 | let pp_version ppf v = 27 | let v = if v = "" then "?" else v in 28 | Fmt.pf ppf "%a" (Fmt.st [`Fg `Green]) v 29 | 30 | let equal = ( = ) 31 | let compare = compare 32 | let compare_by_caseless_name p p' = 33 | let n p = String.Ascii.lowercase (name p) in 34 | String.compare (n p) (n p') 35 | 36 | module T = struct type nonrec t = t let compare = compare end 37 | module Set = Set.Make (T) 38 | module Map = Map.Make (T) 39 | 40 | let of_dir dir = 41 | Log.time (fun _ m -> m "package list of %a" Fpath.pp_quoted dir) @@ 42 | fun () -> 43 | let ocaml_pkg () = 44 | let ocaml_where = Cmd.(arg "ocamlc" % "-where") in 45 | let p = 46 | Os.Cmd.run_out ~trim:true ocaml_where |> Result.error_to_failure 47 | in 48 | "ocaml", Fpath.of_string p |> Result.error_to_failure 49 | in 50 | try 51 | let add_pkg _ name dir acc = 52 | if name <> "ocaml" then (name, dir) :: acc else acc 53 | in 54 | let pkgs = Os.Dir.fold_dirs ~recurse:false add_pkg dir [] in 55 | let pkgs = pkgs |> Result.error_to_failure in 56 | List.sort compare_by_caseless_name (ocaml_pkg () :: pkgs) 57 | with Failure e -> Log.err (fun m -> m "package list: %s" e); [] 58 | 59 | let by_names ?(init = String.Map.empty) pkgs = 60 | let add_pkg acc (n, _ as pkg) = String.Map.add n pkg acc in 61 | List.fold_left add_pkg init pkgs 62 | end 63 | 64 | module Doc_cobj = struct 65 | type kind = Cmi | Cmti | Cmt 66 | type t = 67 | { path : Fpath.t; 68 | kind : kind; 69 | modname : string; 70 | hidden : bool; 71 | pkg : Pkg.t; } 72 | 73 | let path cobj = cobj.path 74 | let kind cobj = cobj.kind 75 | let modname cobj = cobj.modname 76 | let pkg cobj = cobj.pkg 77 | let hidden cobj = cobj.hidden 78 | let don't_list cobj = 79 | hidden cobj || String.includes ~affix:"__" (modname cobj) 80 | 81 | let add_cobj pkg _ _ path acc = 82 | try 83 | let multi = true in (* implies e.g .p.ext objects are not considered *) 84 | let base, kind = match Fpath.cut_ext ~multi path with 85 | | base, ".cmi" -> base, Cmi 86 | | base, ".cmti" -> base, Cmti 87 | | base, ".cmt" -> base, Cmt 88 | | base, _ -> raise_notrace Exit 89 | in 90 | let modname = String.Ascii.capitalize (Fpath.basename base) in 91 | let cobj = match Fpath.Map.find base acc with 92 | | exception Not_found -> 93 | let hidden = not (kind = Cmi) in 94 | { path; kind; modname; hidden; pkg; } 95 | | cobj' -> 96 | match cobj'.kind, kind with 97 | | Cmi, (Cmti | Cmt) -> { path; kind; modname; hidden = false; pkg; } 98 | | (Cmti | Cmt), Cmi -> { cobj' with hidden = false } 99 | | Cmt, Cmti -> { path; kind; modname; hidden = cobj'.hidden; pkg } 100 | | Cmti, Cmt | _ -> cobj' 101 | in 102 | Fpath.Map.add base cobj acc 103 | with Exit -> acc 104 | 105 | let of_pkg pkg = 106 | let dir = Pkg.path pkg in 107 | let recurse = true in 108 | let cobjs = Os.Dir.fold_files ~recurse (add_cobj pkg) dir Fpath.Map.empty in 109 | let cobjs = Log.if_error ~use:Fpath.Map.empty cobjs in 110 | Fpath.Map.fold (fun _ c acc -> c :: acc) cobjs [] 111 | 112 | let by_modname ?(init = String.Map.empty) cobjs = 113 | let add acc cobj = match String.Map.find cobj.modname acc with 114 | | exception Not_found -> String.Map.add cobj.modname [cobj] acc 115 | | cobjs -> String.Map.add cobj.modname (cobj :: cobjs) acc 116 | in 117 | List.fold_left add init cobjs 118 | end 119 | 120 | module Opam = struct 121 | 122 | (* opam metadata *) 123 | 124 | type t = (string * string) list 125 | 126 | let unescape s = s (* TODO *) 127 | let parse_string = function 128 | | "" -> ("", "") 129 | | s -> 130 | match String.index s '"' with 131 | | exception Not_found -> (s, "") 132 | | i -> 133 | let start = i + 1 in 134 | let rec find_end i = match String.index_from s i '"' with 135 | | exception Not_found -> (s, "") (* unreported error ... *) 136 | | j when s.[j - 1] = '\\' -> find_end (j + 1) 137 | | j -> 138 | let stop = j - 1 in 139 | let str = String.sub s start (stop - start + 1) in 140 | let rest = String.sub s (j + 1) (String.length s - (j + 1)) in 141 | (unescape str, rest) 142 | in 143 | find_end start 144 | 145 | let parse_list s = 146 | if s = "" then [] else 147 | let rec loop acc s = 148 | let s, rest = parse_string s in 149 | let rest = String.trim rest in 150 | if rest = "" || rest = "]" then List.rev (s :: acc) else 151 | loop (s :: acc) rest 152 | in 153 | loop [] s 154 | 155 | let string_field f fields = match List.assoc f fields with 156 | | exception Not_found -> "" | v -> fst @@ parse_string v 157 | 158 | let list_field ?(sort = true) f fields = match List.assoc f fields with 159 | | exception Not_found -> [] 160 | | v when sort -> List.sort compare (parse_list v) 161 | | v -> parse_list v 162 | 163 | let authors = list_field "authors" 164 | let bug_reports = list_field "bug-reports" 165 | let depends fs = match List.assoc "depends" fs with 166 | | exception Not_found -> [] | v -> 167 | let delete_constraints s = 168 | let rec loop s = match String.index s '{' with 169 | | exception Not_found -> s 170 | | i -> 171 | match String.index s '}' with 172 | | exception Not_found -> s 173 | | j -> 174 | loop (String.sub s 0 i) ^ 175 | loop (String.sub s (j + 1) (String.length s - (j + 1))) 176 | in 177 | loop s 178 | in 179 | List.sort compare @@ parse_list (delete_constraints v) 180 | 181 | let dev_repo = list_field "dev-repo" 182 | let doc = list_field "doc" 183 | let homepage = list_field "homepage" 184 | let license = list_field "license" 185 | let maintainer = list_field "maintainer" 186 | let synopsis = string_field "synopsis" 187 | let tags fs = List.rev_map String.Ascii.lowercase @@ list_field "tags" fs 188 | let version = string_field "version" 189 | 190 | (* Queries *) 191 | 192 | let file pkg = 193 | let opam = Fpath.(Pkg.path pkg / "opam") in 194 | match Os.File.exists opam |> Log.if_error ~use:false with 195 | | true -> Some opam 196 | | false -> None 197 | 198 | let bin = lazy begin 199 | let open Result.Syntax in 200 | let* opam = Os.Cmd.get (Cmd.arg "opam") in 201 | let* v = Os.Cmd.run_out ~trim:true Cmd.(opam % "--version") in 202 | match String.cut_left ~sep:"." (String.trim v) with 203 | | Some (maj, _) when 204 | maj <> "" && Char.code maj.[0] - 0x30 >= 2 -> Ok opam 205 | | Some _ | None -> 206 | Fmt.error "%a: unsupported version %s" Cmd.pp opam v 207 | end 208 | 209 | let fields = 210 | [ "name:"; "authors:"; "bug-reports:"; "depends:"; "dev-repo:"; "doc:"; 211 | "homepage:"; "license:"; "maintainer:"; "synopsis:"; "tags:"; 212 | "version:" ] 213 | 214 | let field_count = List.length fields 215 | let field_arg = Fmt.str "--field=%s" (String.concat "," fields) 216 | let rec take_fields n acc lines = match n with 217 | | 0 -> acc, lines 218 | | n -> 219 | match lines with 220 | | [] -> [], [] (* unreported error... *) 221 | | l :: ls -> 222 | match String.cut_left ~sep:":" l with 223 | | None -> [], [] (* unreported error... *) 224 | | Some (f, v) -> take_fields (n - 1) ((f, String.trim v) :: acc) ls 225 | 226 | let rec parse_lines acc = function 227 | | [] -> acc 228 | | name :: lines -> 229 | let err l = 230 | Log.err (fun m -> m "%S: opam metadata expected name: field line" l) 231 | in 232 | match String.cut_left ~sep:":" name with 233 | | Some ("name", n) -> 234 | let n, _ = parse_string n in 235 | let fields, lines = take_fields (field_count - 1) [] lines in 236 | parse_lines (String.Map.add n fields acc) lines 237 | | None | Some _ -> err name; acc 238 | 239 | let query qpkgs = 240 | (* opam show (at least until v2.0.3) returns results in package 241 | name order which is too easy to get confused about (we need to 242 | precisely know how opam orders and apparently we do not). So we 243 | also query for the name: field first and rebind the data to packages 244 | after parsing. *) 245 | let pkgs = Pkg.Set.of_list qpkgs in 246 | let add_opam p acc = match file p with None -> acc | Some f -> f :: acc in 247 | let opams = Pkg.Set.fold add_opam pkgs [] in 248 | let no_data pkgs = List.map (fun p -> (p, [])) pkgs in 249 | match Lazy.force bin with 250 | | Error e -> Log.err (fun m -> m "%s" e); no_data qpkgs 251 | | Ok opam -> 252 | if opams = [] then no_data qpkgs else 253 | let show = Cmd.(opam % "show" % "--normalise" % "--no-lint") in 254 | let show = Cmd.(show % field_arg %% paths opams) in 255 | match 256 | Log.time (fun _ m -> m "opam show") @@ fun () -> 257 | let stderr = `Stdo (Os.Cmd.out_null) in 258 | Os.Cmd.run_out ~stderr ~trim:true show 259 | with 260 | | Error e -> Log.err (fun m -> m "%s" e); no_data qpkgs 261 | | Ok out -> 262 | let lines = String.cuts_left ~sep:"\n" out in 263 | let infos = parse_lines String.Map.empty lines in 264 | let find_info is p = match String.Map.find (Pkg.name p) is with 265 | | exception Not_found -> p, [] 266 | | i -> p, i 267 | in 268 | try List.map (find_info infos) qpkgs with 269 | | Not_found -> assert false 270 | end 271 | 272 | module Doc_dir = struct 273 | 274 | (* Doc dir info *) 275 | 276 | type files = 277 | { changes_files : Fpath.t list; 278 | license_files : Fpath.t list; 279 | readme_files : Fpath.t list; } 280 | 281 | type t = 282 | { dir : Fpath.t option; 283 | files : files Lazy.t; 284 | odoc_pages : Fpath.t list Lazy.t; 285 | odoc_assets_dir : Fpath.t option Lazy.t; 286 | odoc_assets : Fpath.t list Lazy.t; } 287 | 288 | let doc_dir_files pkg_doc_dir = 289 | let cs, ls, rs = match pkg_doc_dir with 290 | | None -> [], [], [] 291 | | Some doc_dir -> 292 | let add_file _ base file (cs, ls, rs as acc) = 293 | let base = String.uppercase_ascii base in 294 | let is_pre prefix = String.starts_with ~prefix base in 295 | if is_pre "CHANGE" || is_pre "HISTORY" || is_pre "NEWS" 296 | then (file :: cs), ls, rs else 297 | if is_pre "LICENSE" then cs, (file :: ls), rs else 298 | if is_pre "README" then cs, ls, (file :: rs) else 299 | acc 300 | in 301 | Os.Dir.fold_files ~recurse:false add_file doc_dir ([], [], []) 302 | |> Log.if_error ~use:([], [], []) 303 | in 304 | let changes_files = List.sort Fpath.compare cs in 305 | let license_files = List.sort Fpath.compare ls in 306 | let readme_files = List.sort Fpath.compare rs in 307 | { changes_files; license_files; readme_files } 308 | 309 | let doc_dir_subdir_files pkg_doc_dir sub ~sat = match pkg_doc_dir with 310 | | None -> [] 311 | | Some pkg_doc_dir -> 312 | let dir = Fpath.(pkg_doc_dir / sub) in 313 | match Os.Dir.exists dir with 314 | | Ok false | Error _ -> [] 315 | | Ok true -> 316 | let add_file = match sat with 317 | | None -> fun _ _ file acc -> file :: acc 318 | | Some sat -> 319 | fun _ _ file acc -> if sat file then file :: acc else acc 320 | in 321 | Os.Dir.fold_files ~recurse:true add_file dir [] 322 | |> Log.if_error ~use:[] 323 | 324 | let doc_dir_odoc_pages pkg_doc_dir = 325 | let is_mld = Some (Fpath.has_ext ".mld") in 326 | doc_dir_subdir_files pkg_doc_dir "odoc-pages" ~sat:is_mld 327 | 328 | let doc_dir_odoc_assets pkg_doc_dir = 329 | doc_dir_subdir_files pkg_doc_dir "odoc-assets" ~sat:None 330 | 331 | let doc_dir_odoc_assets_dir pkg_doc_dir = match pkg_doc_dir with 332 | | None -> None 333 | | Some pkg_doc_dir -> 334 | let dir = Fpath.(pkg_doc_dir / "odoc-assets") in 335 | match Os.Dir.exists dir |> Log.if_error ~use:false with 336 | | false -> None 337 | | true -> Some dir 338 | 339 | let v pkg_doc_dir = 340 | let files = lazy (doc_dir_files pkg_doc_dir) in 341 | let odoc_pages = lazy (doc_dir_odoc_pages pkg_doc_dir) in 342 | let odoc_assets_dir = lazy (doc_dir_odoc_assets_dir pkg_doc_dir) in 343 | let odoc_assets = lazy (doc_dir_odoc_assets pkg_doc_dir) in 344 | { dir = pkg_doc_dir; files; odoc_pages; odoc_assets_dir; odoc_assets } 345 | 346 | let dir i = i.dir 347 | let changes_files i = (Lazy.force i.files).changes_files 348 | let license_files i = (Lazy.force i.files).license_files 349 | let odoc_pages i = Lazy.force i.odoc_pages 350 | let odoc_assets_dir i = Lazy.force i.odoc_assets_dir 351 | let odoc_assets i = Lazy.force i.odoc_assets 352 | let readme_files i = (Lazy.force i.files).readme_files 353 | let of_pkg ~doc_dir pkg = 354 | let doc_dir = Fpath.(doc_dir / Pkg.name pkg) in 355 | match Os.Dir.exists doc_dir |> Log.if_error ~use:false with 356 | | true -> v (Some doc_dir) 357 | | false -> v None 358 | end 359 | 360 | module Pkg_info = struct 361 | type t = 362 | { doc_cobjs : Doc_cobj.t list Lazy.t; 363 | opam : Opam.t; 364 | doc_dir : Doc_dir.t Lazy.t } 365 | 366 | let doc_cobjs i = Lazy.force i.doc_cobjs 367 | let opam i = i.opam 368 | let doc_dir i = Lazy.force i.doc_dir 369 | 370 | type field = 371 | [ `Authors | `Changes_files | `Doc_cobjs | `Depends | `Homepage | `Issues 372 | | `License | `License_files | `Maintainers | `Odoc_assets | `Odoc_pages 373 | | `Online_doc | `Readme_files | `Repo | `Synopsis | `Tags | `Version ] 374 | 375 | let field_names = 376 | [ "authors", `Authors; "changes-files", `Changes_files; 377 | "depends", `Depends; "doc-cobjs", `Doc_cobjs; 378 | "homepage", `Homepage; "issues", `Issues; "license", `License; 379 | "license-files", `License_files; "maintainers", `Maintainers; 380 | "odoc-assets", `Odoc_assets; "odoc-pages", `Odoc_pages; 381 | "online-doc", `Online_doc; "readme-files", `Readme_files; 382 | "repo", `Repo; "synopsis", `Synopsis; "tags", `Tags; 383 | "version", `Version; ] 384 | 385 | let get field i = 386 | let paths ps = List.map Fpath.to_string ps in 387 | match field with 388 | | `Authors -> Opam.authors (opam i) 389 | | `Changes_files -> paths @@ Doc_dir.changes_files (doc_dir i) 390 | | `Depends -> Opam.depends (opam i) 391 | | `Doc_cobjs -> paths @@ List.map Doc_cobj.path (doc_cobjs i) 392 | | `Homepage -> Opam.homepage (opam i) 393 | | `Issues -> Opam.bug_reports (opam i) 394 | | `License -> Opam.license (opam i) 395 | | `License_files -> paths @@ Doc_dir.license_files (doc_dir i) 396 | | `Maintainers -> Opam.maintainer (opam i) 397 | | `Odoc_assets -> paths @@ Doc_dir.odoc_assets (doc_dir i) 398 | | `Odoc_pages -> paths @@ Doc_dir.odoc_pages (doc_dir i) 399 | | `Online_doc -> Opam.doc (opam i) 400 | | `Readme_files -> paths @@ Doc_dir.readme_files (doc_dir i) 401 | | `Repo -> Opam.dev_repo (opam i) 402 | | `Synopsis -> (match Opam.synopsis (opam i) with "" -> [] | s -> [s]) 403 | | `Tags -> Opam.tags (opam i) 404 | | `Version -> (match Opam.version (opam i) with "" -> [] | s -> [s]) 405 | 406 | let pp ppf i = 407 | let pp_value = Fmt.(hvbox @@ list ~sep:sp string) in 408 | let pp_field ppf (n, f) = Fmt.field n (get f) pp_value ppf i in 409 | let pp_field ppf spec = Fmt.pf ppf "| %a" pp_field spec in 410 | Fmt.pf ppf "@[%a@]" (Fmt.list pp_field) field_names 411 | 412 | (* Queries *) 413 | 414 | let query ~doc_dir pkgs = 415 | let rec loop acc = function 416 | | [] -> List.rev acc 417 | | (p, opam) :: ps -> 418 | let doc_cobjs = lazy (Doc_cobj.of_pkg p) in 419 | let doc_dir = lazy (Doc_dir.of_pkg ~doc_dir p) in 420 | loop ((p, {doc_cobjs; opam; doc_dir}) :: acc) ps 421 | in 422 | loop [] (Opam.query pkgs) 423 | end 424 | 425 | module Env = struct 426 | let b0_cache_dir = "ODIG_B0_CACHE_DIR" 427 | let b0_log_file = "ODIG_B0_LOG_FILE" 428 | let cache_dir = "ODIG_CACHE_DIR" 429 | let color = "ODIG_COLOR" 430 | let doc_dir = "ODIG_DOC_DIR" 431 | let lib_dir = "ODIG_LIB_DIR" 432 | let odoc_theme = "ODIG_ODOC_THEME" 433 | let share_dir = "ODIG_SHARE_DIR" 434 | let verbosity = "ODIG_VERBOSITY" 435 | end 436 | 437 | module Conf = struct 438 | 439 | type t = 440 | { b0_cache_dir : Fpath.t; 441 | b0_log_file : Fpath.t; 442 | cache_dir : Fpath.t; 443 | cwd : Fpath.t; 444 | doc_dir : Fpath.t; 445 | html_dir : Fpath.t; 446 | jobs : int; 447 | lib_dir : Fpath.t; 448 | log_level : Log.level; 449 | memo : (B0_memo.t, string) result Lazy.t; 450 | odoc_theme : string; 451 | pkg_infos : Pkg_info.t Pkg.Map.t Lazy.t; 452 | pkgs : Pkg.t list Lazy.t; 453 | share_dir : Fpath.t; 454 | fmt_styler : Fmt.styler; } 455 | 456 | let memo ~cwd ~cache_dir (* b0 not odig *) ~trash_dir ~jobs = 457 | let feedback = 458 | let op_howto ppf o = Fmt.pf ppf "odig log --id %d" (B0_zero.Op.id o) in 459 | let show_op = Log.Debug and show_ui = Log.Info and level = Log.level () in 460 | B0_cli.Memo.pp_leveled_feedback ~op_howto ~show_op ~show_ui ~level 461 | Fmt.stderr 462 | in 463 | B0_memo.make ~cwd ~cache_dir ~trash_dir ~jobs ~feedback () 464 | 465 | let v 466 | ~b0_cache_dir ~b0_log_file ~cache_dir ~cwd ~doc_dir ~html_dir ~jobs 467 | ~lib_dir ~log_level ~odoc_theme ~share_dir ~fmt_styler () 468 | = 469 | let trash_dir = 470 | B0_cli.Memo.get_trash_dir ~cwd ~b0_dir:cache_dir ~trash_dir:None 471 | in 472 | let memo = 473 | lazy (memo ~cwd:cache_dir ~cache_dir:b0_cache_dir ~trash_dir ~jobs) 474 | in 475 | let pkgs = lazy (Pkg.of_dir lib_dir) in 476 | let pkg_infos = Lazy.from_fun @@ fun () -> 477 | let add acc (p, i) = Pkg.Map.add p i acc in 478 | let pkg_infos = Pkg_info.query ~doc_dir (Lazy.force pkgs) in 479 | List.fold_left add Pkg.Map.empty pkg_infos 480 | in 481 | { b0_cache_dir; b0_log_file; cache_dir; cwd; doc_dir; html_dir; jobs; 482 | lib_dir; log_level; memo; odoc_theme; pkg_infos; pkgs; share_dir; 483 | fmt_styler } 484 | 485 | let b0_cache_dir c = c.b0_cache_dir 486 | let b0_log_file c = c.b0_log_file 487 | let cache_dir c = c.cache_dir 488 | let cwd c = c.cwd 489 | let doc_dir c = c.doc_dir 490 | let html_dir c = c.html_dir 491 | let jobs c = c.jobs 492 | let lib_dir c = c.lib_dir 493 | let log_level c = c.log_level 494 | let memo c = Lazy.force c.memo 495 | let odoc_theme c = c.odoc_theme 496 | let pkg_infos c = Lazy.force c.pkg_infos 497 | let pkgs c = Lazy.force c.pkgs 498 | let share_dir c = c.share_dir 499 | let fmt_styler c = c.fmt_styler 500 | let pp = 501 | Fmt.record @@ 502 | [ Fmt.field "b0-cache-dir" b0_cache_dir Fpath.pp_quoted; 503 | Fmt.field "b0-log-file" b0_log_file Fpath.pp_quoted; 504 | Fmt.field "cache-dir" cache_dir Fpath.pp_quoted; 505 | Fmt.field "doc-dir" doc_dir Fpath.pp_quoted; 506 | Fmt.field "lib-dir" lib_dir Fpath.pp_quoted; 507 | Fmt.field "jobs" jobs Fmt.int; 508 | Fmt.field "odoc-theme" odoc_theme Fmt.string; 509 | Fmt.field "share-dir" share_dir Fpath.pp_quoted; ] 510 | 511 | (* Setup *) 512 | 513 | let get_dir ~cwd ~exec default_dir = function 514 | | Some dir -> Fpath.(cwd // dir) 515 | | None -> 516 | (* relocation hack find directory relative to executable path *) 517 | Fpath.((parent @@ parent @@ exec) // default_dir) 518 | 519 | let get_odoc_theme = function 520 | | Some v -> Ok v 521 | | None -> 522 | Result.bind (B0_odoc.Theme.get_user_preference ()) @@ fun n -> 523 | Ok (Option.value ~default:B0_odoc.Theme.odig_default n) 524 | 525 | let setup_with_cli 526 | ~b0_cache_dir ~b0_log_file ~cache_dir ~doc_dir ~jobs ~lib_dir ~log_level 527 | ~odoc_theme ~share_dir ~color () 528 | = 529 | Result.map_error (Fmt.str "conf: %s") @@ 530 | let fmt_styler = B0_std_cli.get_styler color in 531 | let log_level = B0_std_cli.get_log_level log_level in 532 | B0_std_cli.setup fmt_styler log_level ~log_spawns:Log.Debug; 533 | Result.bind (Os.Dir.cwd ()) @@ fun cwd -> 534 | Result.bind (Fpath.of_string Sys.executable_name) @@ fun exec -> 535 | let cache_dir = get_dir ~cwd ~exec (Fpath.v "var/cache/odig") cache_dir in 536 | let b0_cache_dir = 537 | let b0_dir = cache_dir and cache_dir = b0_cache_dir in 538 | B0_cli.Memo.get_cache_dir ~cwd ~b0_dir ~cache_dir 539 | in 540 | let b0_log_file = 541 | let b0_dir = cache_dir and log_file = b0_log_file in 542 | B0_cli.Memo.get_log_file ~cwd ~b0_dir ~log_file 543 | in 544 | let html_dir = Fpath.(cache_dir / "html") in 545 | let lib_dir = get_dir ~cwd ~exec (Fpath.v "lib") lib_dir in 546 | let doc_dir = get_dir ~cwd ~exec (Fpath.v "doc") doc_dir in 547 | let share_dir = get_dir ~cwd ~exec (Fpath.v "share") share_dir in 548 | Result.bind (get_odoc_theme odoc_theme) @@ fun odoc_theme -> 549 | let jobs = B0_cli.Memo.get_jobs ~jobs in 550 | Ok (v ~b0_cache_dir ~b0_log_file ~cache_dir ~cwd ~doc_dir ~html_dir 551 | ~jobs ~lib_dir ~log_level ~odoc_theme ~share_dir ~fmt_styler ()) 552 | end 553 | -------------------------------------------------------------------------------- /src/odig_support.mli: -------------------------------------------------------------------------------- 1 | (*--------------------------------------------------------------------------- 2 | Copyright (c) 2018 The odig programmers. All rights reserved. 3 | SPDX-License-Identifier: ISC 4 | ---------------------------------------------------------------------------*) 5 | 6 | (** [Odig] support library. 7 | 8 | This library is used to implement the [odig] tool. *) 9 | 10 | (** {1:support Odig support} *) 11 | 12 | open B0_std 13 | 14 | (** Digests. *) 15 | module Digest : sig 16 | include (module type of Digest) 17 | 18 | val pp : Format.formatter -> t -> unit 19 | (** [pp] formats digests. *) 20 | 21 | val pp_opt : Format.formatter -> t option -> unit 22 | (** [pp_opt] formats optional digests. *) 23 | 24 | (** Digest sets. *) 25 | module Set : Set.S with type elt = t 26 | 27 | (** Digest maps. *) 28 | module Map : Map.S with type key = t 29 | end 30 | 31 | (** Packages *) 32 | module Pkg : sig 33 | 34 | (** {1:pkgs Packages} *) 35 | 36 | type name = string 37 | (** The type for package names. *) 38 | 39 | type t 40 | (** The type for packages. *) 41 | 42 | val name : t -> name 43 | (** [name] is the name of the package. *) 44 | 45 | val path : t -> Fpath.t 46 | (** [path] is the path to the compilation objects of the package. *) 47 | 48 | val equal : t -> t -> bool 49 | (** [equal p0 p1] is [true] if [p0] and [p1] point to the same package. *) 50 | 51 | val compare : t -> t -> int 52 | (** [compare p0 p1] is a total order on packages compatible with {!equal}. *) 53 | 54 | val compare_by_caseless_name : t -> t -> int 55 | (** [compare_by_caseless_name p0 p1] compares [p0] and [p1] by 56 | name in a caseless manner. *) 57 | 58 | val pp : t Fmt.t 59 | (** [pp] formats packages. *) 60 | 61 | val pp_name : t Fmt.t 62 | (** [pp_name] formats package names. *) 63 | 64 | val pp_version : string Fmt.t 65 | (** [pp_version] formats a package version. *) 66 | 67 | (** Package identifier sets. *) 68 | module Set : Set.S with type elt = t 69 | 70 | (** Package identifier maps. *) 71 | module Map : Map.S with type key = t 72 | 73 | (** {1:query Queries} *) 74 | 75 | val of_dir : Fpath.t -> t list 76 | (** [of_dir lib_dir] are the packages found in [lib_dir]. This is 77 | simply all the directory names inside [lib_dir] and an [ocaml] 78 | package which points to [ocamlc -where]. *) 79 | 80 | val by_names : ?init:t String.Map.t -> t list -> t String.Map.t 81 | (** [by_names pkgs] indexes [pkgs] by module name and adds them to 82 | [init] (defaults to {!String.Map.empty}. *) 83 | end 84 | 85 | (** Lookup package API documention compilation objects. 86 | 87 | The compilation objects relevant for documentation are looked up 88 | according to the following rules: 89 | {ol 90 | {- Packages denote which compilation units should appear in the 91 | docs by installing their [cmi] file.} 92 | {- For each of these files odig looks, in the same directory, 93 | first for a corresponding [cmti] file then if missing for a 94 | [cmt] file, then if none of these exist the [cmi] file.} 95 | {- For [cmti] or [cmt] files which have no corresponding [cmi] file 96 | odig collects them and deems them to be hidden ([odoc] will be 97 | called with the [--hidden] option).}} *) 98 | module Doc_cobj : sig 99 | 100 | (** {1:doc_cobj Documentation compilation objects} *) 101 | 102 | type kind = Cmi | Cmti | Cmt (** *) 103 | (** The type for kinds of documentation compilation object. *) 104 | 105 | type t 106 | (** The type for documentation compilation objects. *) 107 | 108 | val path : t -> Fpath.t 109 | (** [path cobj] is the path to [cobj]. *) 110 | 111 | val kind : t -> kind 112 | (** [kind cobj] is the kind of [cobj]. *) 113 | 114 | val modname : t -> string 115 | (** [modname cobj] is the module name of [cobj] (as determined 116 | from the filename). *) 117 | 118 | val pkg : t -> Pkg.t 119 | (** [pkg cobj] is the package of [cobj]. *) 120 | 121 | val hidden : t -> bool 122 | (** [hidden cobj] is [true] if odoc must compile [cobj] with 123 | the [--hidden] option. *) 124 | 125 | val don't_list : t -> bool 126 | (** [don't_list cobj] is [true] if [cobj] should not appear 127 | in module indexes. *) 128 | 129 | (** {1:query Queries} *) 130 | 131 | val of_pkg : Pkg.t -> t list 132 | (** [of_pkg pkg] are the compilation objects of [pkg] that are 133 | useful for documentation generation. *) 134 | 135 | val by_modname : ?init:t list String.Map.t -> t list -> t list String.Map.t 136 | (** [by_modname ~init cobjs] indexes [cobjs] by module name 137 | and adds them to [init] (defaults to {!String.Map.empty}). *) 138 | end 139 | 140 | (** Lookup package opam metadata. *) 141 | module Opam : sig 142 | 143 | (** {1:opam opam metadata} *) 144 | 145 | type t 146 | (** The type for opam metadata. *) 147 | 148 | val authors : t -> string list 149 | (** [authors i] is the [authors:] field. *) 150 | 151 | val bug_reports : t -> string list 152 | (** [bug_reports i] is the [bug-reports:] field. *) 153 | 154 | val depends : t -> string list 155 | (** [depends i] is the [depends:] field. *) 156 | 157 | val dev_repo : t -> string list 158 | (** [dev_repo i] is the [dev-repo:] field. *) 159 | 160 | val doc : t -> string list 161 | (** [doc i] is the [doc:] field. *) 162 | 163 | val homepage : t -> string list 164 | (** [homepage i] is the [homepage:] field. *) 165 | 166 | val license : t -> string list 167 | (** [license i] is the [license:] field. *) 168 | 169 | val maintainer : t -> string list 170 | (** [maintainer i] is the [maintainer:] field. *) 171 | 172 | val synopsis : t -> string 173 | (** [synopsis i] is the [synopsis:] field. *) 174 | 175 | val tags : t -> string list 176 | (** [info_tags i] are the package's tags. *) 177 | 178 | val version : t -> string 179 | (** [version i] is the package's version. *) 180 | 181 | (** {1:query Queries} *) 182 | 183 | val file : Pkg.t -> Fpath.t option 184 | (** [file pkg] is the opam file of package [pkg] (if any). *) 185 | 186 | val query : Pkg.t list -> (Pkg.t * t) list 187 | (** [query pkgs] queries the opam files associated to 188 | the given packages (if any). 189 | 190 | {b Note.} It is better to batch queries, [opam show] is 191 | quite {{:https://github.com/ocaml/opam/issues/3721}slow} 192 | (at least until v2.0.3). *) 193 | end 194 | 195 | (** Lookup package documentation directory. *) 196 | module Doc_dir : sig 197 | 198 | (** {1:doc_dir Package documentation directory} *) 199 | 200 | type t 201 | (** The type for documentation directory information. *) 202 | 203 | val dir : t -> Fpath.t option 204 | (** [dir] is the path to the documentation directory (if any). *) 205 | 206 | val changes_files : t -> Fpath.t list 207 | (** [changes_files i] are the package's change log files. *) 208 | 209 | val license_files : t -> Fpath.t list 210 | (** [license_files i] are the package's licenses files. *) 211 | 212 | val odoc_pages : t -> Fpath.t list 213 | (** [odoc_pages i] are the package's [odoc] pages *) 214 | 215 | val odoc_assets_dir : t -> Fpath.t option 216 | (** [odoc_assets i] is the package's [odoc] assets directory (if any). *) 217 | 218 | val odoc_assets : t -> Fpath.t list 219 | (** [odoc_assets i] is the package's [odoc] assets directory contents. *) 220 | 221 | val readme_files : t -> Fpath.t list 222 | (** [readme_files i] are the package's readme files. *) 223 | 224 | (** {1:query Queries} *) 225 | 226 | val of_pkg : doc_dir:Fpath.t -> Pkg.t -> t 227 | (** [query ~doc_dir pkg] queries the documentation directory [doc_dir] 228 | for documentation about [pkg]. *) 229 | end 230 | 231 | (** Gather package information 232 | 233 | Gathers {!Doc_cobj}, {!Opam} and {!Docdir} information about 234 | a package. *) 235 | module Pkg_info : sig 236 | 237 | (** {1:pkg_info Package info} *) 238 | 239 | type t 240 | (** The type for package information. *) 241 | 242 | val doc_cobjs : t -> Doc_cobj.t list 243 | (** [doc_cobjs i] are the documentation compilation objects of [i]. *) 244 | 245 | val doc_dir : t -> Doc_dir.t 246 | (** [doc_dir i] is the doc dir information of [i]. *) 247 | 248 | val opam : t -> Opam.t 249 | (** [opam i] is the opam information of [i]. *) 250 | 251 | (** {1:field Uniform field access} 252 | 253 | Access information as list of strings. *) 254 | 255 | type field = 256 | [ `Authors | `Changes_files | `Depends | `Doc_cobjs | `Homepage | `Issues 257 | | `License | `License_files | `Maintainers | `Odoc_assets | `Odoc_pages 258 | | `Online_doc | `Readme_files | `Repo | `Synopsis | `Tags | `Version ] 259 | (** The type for fields. *) 260 | 261 | val field_names : (string * field) list 262 | (** [field_names] associated a string name to each field. *) 263 | 264 | val get : field -> t -> string list 265 | (** [get field i] is the field [field] of [i] as a list of strings. *) 266 | 267 | val pp : t Fmt.t 268 | (** [pp] formats all package information fields in alphabetic order. *) 269 | 270 | (** {1:query Queries} *) 271 | 272 | val query : doc_dir:Fpath.t -> Pkg.t list -> (Pkg.t * t) list 273 | (** [query ~doc_dir pkgs] combines the result of 274 | {!Doc_cobj.of_pkg}, {!Opam.query} and {!Doc_dir.of_pkg}. *) 275 | end 276 | 277 | (** Odig environment variables. *) 278 | module Env : sig 279 | 280 | (** {1:env Environment variables} *) 281 | 282 | val b0_cache_dir : string 283 | (** [b0_cache_dir] is the environment variable that can be used to 284 | define the odig b0 cache directory. *) 285 | 286 | val b0_log_file : string 287 | (** [b0_log_file] is the environment variable that can be used to 288 | define the odig b0 log_file. *) 289 | 290 | val cache_dir : string 291 | (** [cache_dir] is the environment variable that can be used to 292 | define the odig cache directory. *) 293 | 294 | val color : string 295 | (** [color] is the variable used to specify TTY styling. *) 296 | 297 | val doc_dir : string 298 | (** [doc_dir] is the environment variable that can be used to 299 | define a doc dir. *) 300 | 301 | val lib_dir : string 302 | (** [lib_dir] is the environment variable that can be used to 303 | define a lib dir. *) 304 | 305 | val odoc_theme : string 306 | (** [odoc_theme] is the environment variable that can be used 307 | to define the default odoc theme. *) 308 | 309 | val share_dir : string 310 | (** [share_dir_env] is the environment variable that can be used to 311 | define a share dir. *) 312 | 313 | val verbosity : string 314 | (** [verbosity] is the variable used to specify log verbosity. *) 315 | end 316 | 317 | (** Odig configuration. *) 318 | module Conf : sig 319 | 320 | (** {1:conf Configuration} *) 321 | 322 | type t 323 | (** The type for configuration. *) 324 | 325 | val v : 326 | b0_cache_dir:Fpath.t -> b0_log_file:Fpath.t -> cache_dir:Fpath.t -> 327 | cwd:Fpath.t -> doc_dir:Fpath.t -> html_dir:Fpath.t -> jobs:int -> 328 | lib_dir:Fpath.t -> log_level:Log.level -> odoc_theme:B0_odoc.Theme.name -> 329 | share_dir:Fpath.t -> fmt_styler:Fmt.styler -> unit -> t 330 | (** [v] consructs a configuration with given attributes. See 331 | the corresponding accessors for details. *) 332 | 333 | val b0_cache_dir : t -> Fpath.t 334 | (** [b0_cache_dir c] is [c]'s b0 cache directory. *) 335 | 336 | val b0_log_file : t -> Fpath.t 337 | (** [b0_log_file c] is [c]'s b0 log file. *) 338 | 339 | val cache_dir : t -> Fpath.t 340 | (** [cache_dir c] is [c]'s cache directory. *) 341 | 342 | val cwd : t -> Fpath.t 343 | (** [cwd c] is [c]'s current working directory. *) 344 | 345 | val doc_dir : t -> Fpath.t 346 | (** [doc_dir c] is [c]'s documentation directory. *) 347 | 348 | val lib_dir : t -> Fpath.t 349 | (** [lib_dir c] is [c]'s library directory. *) 350 | 351 | val log_level : t -> Log.level 352 | (** [log_level c] is [c]'s log level. *) 353 | 354 | val html_dir : t -> Fpath.t 355 | (** [html_dir c] is [c]'s HTML directory, where the API docs 356 | are generated (derived from {!cache_dir}). *) 357 | 358 | val odoc_theme : t -> B0_odoc.Theme.name 359 | (** [odoc_theme c] is [c]'s odoc theme to use. *) 360 | 361 | val jobs : t -> int 362 | (** [jobs c] is the maximum number of spawns. *) 363 | 364 | val memo : t -> (B0_memo.t, string) result 365 | (** [memo conf] is a memoizer for configuration [conf]. *) 366 | 367 | val pkgs : t -> Pkg.t list 368 | (** [pkgs conf] are the packages of configuration [conf]. *) 369 | 370 | val pkg_infos : t -> Pkg_info.t Pkg.Map.t 371 | (** [pkg_infos conf] are the package information of {!pkgs}. *) 372 | 373 | val share_dir : t -> Fpath.t 374 | (** [share_dir c] is [c]'s share directory. *) 375 | 376 | val fmt_styler : t -> Fmt.styler 377 | (** [fmt_styler c] is [c]'s formatter styler. *) 378 | 379 | val pp : t Fmt.t 380 | (** [pp] formats configurations. *) 381 | 382 | (** {1:setup Setup} *) 383 | 384 | val setup_with_cli : 385 | b0_cache_dir:Fpath.t option -> b0_log_file:Fpath.t option -> 386 | cache_dir:Fpath.t option -> doc_dir:Fpath.t option -> jobs:int option -> 387 | lib_dir:Fpath.t option -> log_level:Log.level option -> 388 | odoc_theme:B0_odoc.Theme.name option -> share_dir:Fpath.t option -> 389 | color:Fmt.styler option option -> unit -> (t, string) result 390 | (** [setup_with_cli] determines and setups a configuration with the given 391 | values. These are expected to have been determined by environment 392 | variables and command line arguments. *) 393 | end 394 | -------------------------------------------------------------------------------- /src/odig_support.mllib: -------------------------------------------------------------------------------- 1 | Odig_support 2 | Odig_odoc_page 3 | Odig_odoc 4 | -------------------------------------------------------------------------------- /themes/dark/fonts: -------------------------------------------------------------------------------- 1 | ../fonts -------------------------------------------------------------------------------- /themes/dark/manual.css: -------------------------------------------------------------------------------- 1 | ../manual.css -------------------------------------------------------------------------------- /themes/dark/odoc.css: -------------------------------------------------------------------------------- 1 | ../odoc.css -------------------------------------------------------------------------------- /themes/dark/theme.css: -------------------------------------------------------------------------------- 1 | :root 2 | { --color-bg: #181B20; 3 | --color-bg-highlight: #303644; 4 | --color-fg: #8C8D90; 5 | --color-rule: #2F3236; 6 | 7 | --color-code-block-bg: #24272A; 8 | --color-code-fg: #A3A4A6; 9 | --color-code-comment: #747679; 10 | --color-code-keyword: #7F668D; 11 | --color-code-type-id: #8D7F66; 12 | --color-code-string: #668d7f; 13 | 14 | --color-link: #7788AA; 15 | --color-broken-link: #900505; } 16 | -------------------------------------------------------------------------------- /themes/default/fonts: -------------------------------------------------------------------------------- 1 | ../fonts -------------------------------------------------------------------------------- /themes/default/manual.css: -------------------------------------------------------------------------------- 1 | ../manual.css -------------------------------------------------------------------------------- /themes/default/odoc.css: -------------------------------------------------------------------------------- 1 | ../odoc.css -------------------------------------------------------------------------------- /themes/default/theme.css: -------------------------------------------------------------------------------- 1 | :root 2 | { --color-bg: #F6F6F6; 3 | --color-bg-highlight: #CAD7EF; 4 | --color-fg: #1F2227; 5 | --color-rule: #DADBDB; 6 | 7 | --color-code-block-bg: #E8E8E8; 8 | --color-code-fg: #16191D; 9 | --color-code-comment: #747679; 10 | --color-code-keyword: #874aa9; 11 | --color-code-type-id: #a9874a; 12 | --color-code-string: #4aa987; 13 | 14 | --color-link: #557dcc; 15 | --color-broken-link: #f71414; } 16 | 17 | @media (prefers-color-scheme: dark) 18 | { 19 | :root 20 | { --color-bg: #181B20; 21 | --color-bg-highlight: #303644; 22 | --color-fg: #8C8D90; 23 | --color-rule: #2F3236; 24 | 25 | --color-code-block-bg: #24272A; 26 | --color-code-fg: #A3A4A6; 27 | --color-code-comment: #747679; 28 | --color-code-keyword: #7F668D; 29 | --color-code-type-id: #8D7F66; 30 | --color-code-string: #668d7f; 31 | 32 | --color-link: #7788AA; 33 | --color-broken-link: #900505; } 34 | } 35 | -------------------------------------------------------------------------------- /themes/fonts/DejaVuSansMono-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b0-system/odig/d0669d77cf97e68d9f7849e1006d17b748ea255c/themes/fonts/DejaVuSansMono-Bold.woff2 -------------------------------------------------------------------------------- /themes/fonts/DejaVuSansMono-BoldOblique.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b0-system/odig/d0669d77cf97e68d9f7849e1006d17b748ea255c/themes/fonts/DejaVuSansMono-BoldOblique.woff2 -------------------------------------------------------------------------------- /themes/fonts/DejaVuSansMono-Oblique.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b0-system/odig/d0669d77cf97e68d9f7849e1006d17b748ea255c/themes/fonts/DejaVuSansMono-Oblique.woff2 -------------------------------------------------------------------------------- /themes/fonts/DejaVuSansMono.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b0-system/odig/d0669d77cf97e68d9f7849e1006d17b748ea255c/themes/fonts/DejaVuSansMono.woff2 -------------------------------------------------------------------------------- /themes/fonts/PTC55F.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b0-system/odig/d0669d77cf97e68d9f7849e1006d17b748ea255c/themes/fonts/PTC55F.woff2 -------------------------------------------------------------------------------- /themes/fonts/PTC75F.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b0-system/odig/d0669d77cf97e68d9f7849e1006d17b748ea255c/themes/fonts/PTC75F.woff2 -------------------------------------------------------------------------------- /themes/fonts/PTS55F.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b0-system/odig/d0669d77cf97e68d9f7849e1006d17b748ea255c/themes/fonts/PTS55F.woff2 -------------------------------------------------------------------------------- /themes/fonts/PTS56F.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b0-system/odig/d0669d77cf97e68d9f7849e1006d17b748ea255c/themes/fonts/PTS56F.woff2 -------------------------------------------------------------------------------- /themes/fonts/PTS75F.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b0-system/odig/d0669d77cf97e68d9f7849e1006d17b748ea255c/themes/fonts/PTS75F.woff2 -------------------------------------------------------------------------------- /themes/fonts/PTS76F.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b0-system/odig/d0669d77cf97e68d9f7849e1006d17b748ea255c/themes/fonts/PTS76F.woff2 -------------------------------------------------------------------------------- /themes/fonts/fonts.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /* PT Sans */ 4 | 5 | @font-face 6 | { font-family: 'PT-Sans'; 7 | src: url('PTS55F.woff2') format('woff2'); 8 | font-weight: 400; font-style: normal; } 9 | 10 | @font-face 11 | { font-family: 'PT-Sans'; 12 | src: url('PTS75F.woff2') format('woff2'); 13 | font-weight: 700; font-style: normal; } 14 | 15 | @font-face 16 | { font-family: 'PT-Sans'; 17 | src: url('PTS56F.woff2') format('woff2'); 18 | font-weight: 400; font-style: italic; } 19 | 20 | @font-face 21 | { font-family: 'PT-Sans'; 22 | src: url('PTS76F.woff2') format('woff2'); 23 | font-weight: 700; font-style: italic; } 24 | 25 | /* PT Sans caption */ 26 | 27 | @font-face 28 | { font-family: 'PT-Sans-Caption'; 29 | src: url('PTC55F.woff2') format('woff2'); 30 | font-weight: 400; font-style: normal; } 31 | 32 | @font-face 33 | { font-family: 'PT-Sans-Caption'; 34 | src: url('PTC75F.woff2') format('woff2'); 35 | font-weight: 700; font-style: normal; } 36 | 37 | /* DejaVu 400 */ 38 | 39 | @font-face 40 | { font-family: 'DejaVu-SansMono'; 41 | src: url('DejaVuSansMono.woff2') format('woff2'); 42 | font-weight: 400; font-style: normal; } 43 | 44 | @font-face 45 | { font-family: 'DejaVu-SansMono'; 46 | src: url('DejaVuSansMono-Oblique.woff2') format('woff2'); 47 | font-weight: 400; font-style: oblique; } 48 | 49 | /* DejaVu 700 */ 50 | 51 | @font-face 52 | { font-family: 'DejaVu-SansMono'; 53 | src: url('DejaVuSansMono-Bold.woff2') format('woff2'); 54 | font-weight: 700; font-style: normal; } 55 | 56 | @font-face 57 | { font-family: 'DejaVu-SansMono'; 58 | src: url('DejaVuSansMono-BoldOblique.woff2') format('woff2'); 59 | font-weight: 700; font-style: oblique; } 60 | -------------------------------------------------------------------------------- /themes/gruvbox.dark/fonts: -------------------------------------------------------------------------------- 1 | ../fonts -------------------------------------------------------------------------------- /themes/gruvbox.dark/manual.css: -------------------------------------------------------------------------------- 1 | ../manual.css -------------------------------------------------------------------------------- /themes/gruvbox.dark/odoc.css: -------------------------------------------------------------------------------- 1 | ../odoc.css -------------------------------------------------------------------------------- /themes/gruvbox.dark/theme.css: -------------------------------------------------------------------------------- 1 | /* Theme based on https://github.com/morhetz/gruvbox dark */ 2 | :root 3 | { --color-bg: #282828; /* bg0 */ 4 | --color-bg-highlight: #665c54; /* bg3 */ 5 | --color-fg: #bdae93; /* fg3 */ 6 | --color-rule: #3c3836; /* bg1 */ 7 | 8 | --color-code-block-bg: #504945; /* bg2 */ 9 | --color-code-fg: #d5c4a1; /* fg2 */ 10 | --color-code-comment: #a89984; /* fg4 */ 11 | --color-code-keyword: #fb4934; /* red */ 12 | --color-code-type-id: #8ec07c; /* aqua */ 13 | --color-code-string: #98971a; /* green */ 14 | 15 | --color-link: #83a598; /* blue */ 16 | --color-broken-link: #cc241d; /* red */ } 17 | -------------------------------------------------------------------------------- /themes/gruvbox.light/fonts: -------------------------------------------------------------------------------- 1 | ../fonts -------------------------------------------------------------------------------- /themes/gruvbox.light/manual.css: -------------------------------------------------------------------------------- 1 | ../manual.css -------------------------------------------------------------------------------- /themes/gruvbox.light/odoc.css: -------------------------------------------------------------------------------- 1 | ../odoc.css -------------------------------------------------------------------------------- /themes/gruvbox.light/theme.css: -------------------------------------------------------------------------------- 1 | /* Theme based on https://github.com/morhetz/gruvbox light */ 2 | :root 3 | { --color-bg: #fbf1c7; /* bg0 */ 4 | --color-bg-highlight: #bdae93; /* bg3 */ 5 | --color-fg: #655c54; /* fg3 */ 6 | --color-rule: #ebdbb2; /* bg1 */ 7 | 8 | --color-code-block-bg: #ebdbb2; /* bg1 */ 9 | --color-code-fg: #504945; /* fg2 */ 10 | --color-code-comment: #7c6f64; /* fg4 */ 11 | --color-code-keyword: #cc241d; /* red */ 12 | --color-code-type-id: #689d6a; /* aqua */ 13 | --color-code-string: #98971a; /* green */ 14 | 15 | --color-link: #076678; /* blue */ 16 | --color-broken-link: #cc241d; /* red */ } 17 | -------------------------------------------------------------------------------- /themes/gruvbox/fonts: -------------------------------------------------------------------------------- 1 | ../fonts -------------------------------------------------------------------------------- /themes/gruvbox/manual.css: -------------------------------------------------------------------------------- 1 | ../manual.css -------------------------------------------------------------------------------- /themes/gruvbox/odoc.css: -------------------------------------------------------------------------------- 1 | ../odoc.css -------------------------------------------------------------------------------- /themes/gruvbox/theme.css: -------------------------------------------------------------------------------- 1 | /* Theme based on https://github.com/morhetz/gruvbox light */ 2 | :root 3 | { --color-bg: #fbf1c7; /* bg0 */ 4 | --color-bg-highlight: #bdae93; /* bg3 */ 5 | --color-fg: #655c54; /* fg3 */ 6 | --color-rule: #ebdbb2; /* bg1 */ 7 | 8 | --color-code-block-bg: #ebdbb2; /* bg1 */ 9 | --color-code-fg: #504945; /* fg2 */ 10 | --color-code-comment: #7c6f64; /* fg4 */ 11 | --color-code-keyword: #cc241d; /* red */ 12 | --color-code-type-id: #689d6a; /* aqua */ 13 | --color-code-string: #98971a; /* green */ 14 | 15 | --color-link: #076678; /* blue */ 16 | --color-broken-link: #cc241d; /* red */ } 17 | 18 | @media (prefers-color-scheme: dark) 19 | { 20 | /* Theme based on https://github.com/morhetz/gruvbox dark */ 21 | :root 22 | { --color-bg: #282828; /* bg0 */ 23 | --color-bg-highlight: #665c54; /* bg3 */ 24 | --color-fg: #bdae93; /* fg3 */ 25 | --color-rule: #3c3836; /* bg1 */ 26 | 27 | --color-code-block-bg: #504945; /* bg2 */ 28 | --color-code-fg: #d5c4a1; /* fg2 */ 29 | --color-code-comment: #a89984; /* fg4 */ 30 | --color-code-keyword: #fb4934; /* red */ 31 | --color-code-type-id: #8ec07c; /* aqua */ 32 | --color-code-string: #98971a; /* green */ 33 | 34 | --color-link: #83a598; /* blue */ 35 | --color-broken-link: #cc241d; /* red */ } 36 | } 37 | -------------------------------------------------------------------------------- /themes/light/fonts: -------------------------------------------------------------------------------- 1 | ../fonts -------------------------------------------------------------------------------- /themes/light/manual.css: -------------------------------------------------------------------------------- 1 | ../manual.css -------------------------------------------------------------------------------- /themes/light/odoc.css: -------------------------------------------------------------------------------- 1 | ../odoc.css -------------------------------------------------------------------------------- /themes/light/theme.css: -------------------------------------------------------------------------------- 1 | :root 2 | { --color-bg: #F6F6F6; 3 | --color-bg-highlight: #CAD7EF; 4 | --color-fg: #1F2227; 5 | --color-rule: #DADBDB; 6 | 7 | --color-code-block-bg: #E8E8E8; 8 | --color-code-fg: #16191D; 9 | --color-code-comment: #747679; 10 | --color-code-keyword: #874aa9; 11 | --color-code-type-id: #a9874a; 12 | --color-code-string: #4aa987; 13 | 14 | --color-link: #557dcc; 15 | --color-broken-link: #f71414; } 16 | -------------------------------------------------------------------------------- /themes/manual.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @import url("fonts/fonts.css"); 3 | @import url("theme.css"); 4 | 5 | /* Copyright (c) 2019 The odig programmers. All rights reserved. 6 | SPDX-License-Identifier: ISC 7 | %%NAME%% %%VERSION%% */ 8 | 9 | :root { --font-headings: "PT-Sans-Caption"; 10 | --font-body: "PT-Sans"; 11 | --font-mono: "DejaVu-SansMono"; 12 | 13 | --size-font: 0.96875rem; 14 | --size-font-micro: calc(0.675 * var(--size-font)); 15 | --size-font-tiny-ratio: 0.75; 16 | --size-font-tiny: calc(var(--size-font-tiny-ratio) * var(--size-font)); 17 | --size-font-small: calc(0.875 * var(--size-font)); 18 | --size-font-large: calc(1.25 * var(--size-font)); 19 | --size-font-big: calc(1.5 * var(--size-font)); 20 | --size-font-huge: calc(1.75 * var(--size-font)); 21 | --size-font-mono-ratio: 0.87097; 22 | --size-line-ratio: 1.5; 23 | --size-line: calc(var(--size-line-ratio) * var(--size-font)); 24 | --size-half-line: calc(0.5 * var(--size-line)); } 25 | 26 | /* Reset a few things. */ 27 | 28 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre, 29 | a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,select, 30 | small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li, 31 | fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td, 32 | article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup, 33 | menu,nav,output,ruby,section,summary,time,mark,audio,video,button,input 34 | { margin: 0; padding: 0; border: 0; /* outline: 0; */ 35 | font-size: inherit; font: inherit; font-weight: inherit; 36 | line-height: inherit; vertical-align: baseline; 37 | text-align: inherit; color: inherit; background: transparent; } 38 | 39 | table { border-collapse: collapse; border-spacing: 0; } 40 | *, *:before, *:After { box-sizing: border-box; } 41 | 42 | /* Note the stylesheet has quite hackish CSS selectors as the markup's 43 | classification is sometimes a bit lacking. */ 44 | 45 | /* Geometry. 46 | See also media adjustements at the end of the stylesheet. */ 47 | 48 | body { background-color: var(--color-bg); 49 | color: var(--color-fg); 50 | font-size: var(--size-font); 51 | font-family: var(--font-body), sans-serif; 52 | font-weight: 400; 53 | line-height: var(--size-line); 54 | text-align: left; 55 | position: relative; 56 | max-width: 120ch; 57 | margin: 0 auto; 58 | padding: calc(1.5 * var(--size-line)); 59 | padding-top: calc(0.5 * var(--size-line)); } 60 | 61 | body > * { max-width: 80ch; width: 75% } 62 | body > ul:first-of-type:not(.itemize):not(.ftoc2) /* toc */ 63 | { position: absolute; 64 | top: 0px; right: 0px; 65 | width: 20%; 66 | margin-left: var(--size-line); 67 | margin-top: calc(2 * var(--size-line)); 68 | margin-right: var(--size-line); 69 | border-top: solid thin var(--color-rule); } 70 | 71 | /* Rules 72 | We remove the top one, keep the last one and add one over h1 and h2 */ 73 | 74 | body > hr:last-of-type { 75 | margin-top: var(--size-line); 76 | border-style: none; 77 | width: 100%; 78 | max-width: 100%; 79 | text-align: right; 80 | border-top: solid thin var(--color-rule); } 81 | 82 | body > hr:first-of-type { display: none } /* order with last-of-type imporant */ 83 | h1, h2 { border-top: solid thin var(--color-rule) } 84 | 85 | /* Hacks */ 86 | 87 | body > a > img /* Navigation arrows, a bit problematic for dark themes */ 88 | { display: inline-block; margin:0; 89 | background-color: var(--color-code-block-bg); /* bof */ } 90 | 91 | body > p br, h1 br { display: none; } /* brs should die */ 92 | 93 | /* Basic markup */ 94 | 95 | h1, h2, h3, h4, h5, h6 96 | { font-family: var(--font-headings), sans-serif; 97 | font-weight: 400; 98 | text-transform: uppercase; 99 | margin-top: var(--size-line); } 100 | 101 | h1, h2 { line-height: calc(1.5 * var(--size-line)); 102 | padding-top: calc(0.75 * var(--size-line)); } 103 | 104 | hr + h1, hr + h2 { margin-top: calc(0.25 * var(--size-line)) } 105 | 106 | h1 { font-size: var(--size-font-huge); } 107 | h2 { font-size: var(--size-font-big); } 108 | h3 { font-size: var(--size-font-large); } 109 | 110 | div, nav, p, ol, ul, dl, pre, table, blockquote 111 | { margin-top: var(--size-half-line); } 112 | 113 | ul, ol { list-style-position: outside } 114 | ul { list-style-type: square } 115 | ul > li { margin-left: 2.25ch; } 116 | ol > li { margin-left: 2ch; } 117 | 118 | em { font-style: italic } 119 | b, strong { font-weight: 700 } 120 | small { font-size: var(--size-font-small); } 121 | 122 | sup { vertical-align: super; } 123 | sub { vertical-align: sub; } 124 | sup, sub { font-size : calc(1em * var(--size-font-tiny-ratio)); 125 | line-height: 0; margin-left: 0.2ex; } 126 | 127 | img { display: block; 128 | margin-top: var(--size-half-line); margin-bottom: var(--size-half-line); } 129 | 130 | blockquote { margin-left: var(--size-half-line); } 131 | 132 | /* Links and anchors. Note anchors need to be refined a bit further down 133 | in certain cases. */ 134 | 135 | a { text-decoration:none; color: var(--color-link); } 136 | a:hover { box-shadow:0 1px 0 0 var(--color-link); } 137 | a.anchor:before { content: "#" } 138 | a.anchor:hover { box-shadow: none; text-decoration: underline; } 139 | *:hover > a.anchor { visibility: visible } 140 | a.anchor 141 | { visibility: hidden; position: absolute; 142 | font-weight: normal; 143 | font-style: normal; 144 | margin-left: -2.5ch; 145 | padding-right: 1ch; padding-left: 1ch; /* To remain selectable */ 146 | color: var(--color-link); 147 | text-align: right; 148 | } 149 | 150 | *:target /* Linked highlight */ 151 | { background-color: var(--color-bg-highlight); 152 | box-shadow: 0 0 0 3px var(--color-bg-highlight) } 153 | 154 | /* Code and code highlighting */ 155 | 156 | .c003, .c004, .c005, .c006, .c015, 157 | code, pre 158 | { font-family: var(--font-mono), monospace; 159 | font-weight: 400; 160 | font-size: calc(1em * var(--size-font-mono-ratio)); 161 | color: var(--color-code-fg); } 162 | 163 | .c004, .c002 { color: var(--color-code-type-id); } 164 | .c005 { font-style: oblique } 165 | .c006 { font-weight: 700 } 166 | .c015 { text-align: left } 167 | 168 | pre .c003, pre .c004, pre .c005, pre .c006, 169 | pre code { font-size: inherit } /* don't apply transform twice... */ 170 | a code { color: inherit } 171 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { text-transform: none; } 172 | 173 | pre { background: var(--color-code-block-bg); 174 | padding-left: 0.8ch; padding-right: 0.8ch; 175 | margin-left: -0.8ch; margin-right: -0.8ch; 176 | padding-top: 1ch; padding-bottom: 1ch; 177 | white-space: pre-wrap; 178 | word-wrap: break-word; } 179 | 180 | /* Table of contents, like in odoc.css except we don't have a class */ 181 | 182 | body > ul:first-of-type:not(.itemize):not(.ftoc2) 183 | { font-size: var(--size-font-small); } 184 | 185 | body > ul:first-of-type:not(.itemize):not(.ftoc2) a 186 | { color : var(--color-fg); } 187 | 188 | body > ul:first-of-type:not(.itemize):not(.ftoc2) a:hover 189 | { color : var(--color-link); } 190 | 191 | body > ul:first-of-type:not(.itemize):not(.ftoc2) 192 | { font-family: var(--font-headings); text-transform: uppercase; 193 | list-style-type: none; padding-top: var(--size-line) } 194 | 195 | body > ul:first-of-type:not(.itemize):not(.ftoc2) li 196 | { margin-left: 0; padding-left: 3.25ch; text-indent: -3.25ch; } 197 | 198 | body > ul:first-of-type:not(.itemize):not(.ftoc2) ul 199 | { font-family: var(--font-body); 200 | text-transform: none; 201 | margin-top:0; } 202 | 203 | /* toplevel examples */ 204 | 205 | div.caml-example.toplevel div.caml-input::before { content: "#" } 206 | div.caml-input, div.caml-output { margin-top: 0; } 207 | 208 | .caml-input {} 209 | .caml-output { color: var(--color-code-string) /* why not */ } 210 | 211 | /* Other HeVeA classes */ 212 | 213 | .c000 { border-spacing: 2ch; border-collapse: separate; margin: 0 auto; } 214 | .c001 { border-spacing: 1ch; border-collapse: separate } 215 | .c008 { font-size: var(--size-font-small) } 216 | .c009 { font-style:italic } 217 | .c010 { font-style:italic; color:var(--color-link) } 218 | .c011 { font-style:italic; font-weight:700 } 219 | .c012 { font-style:italic } 220 | .c013 { font-style:italic } 221 | .c018 { text-align: right } 222 | .c019 { text-align: left} 223 | .dcenter { margin: 0 auto; } 224 | .description { margin-left: var(--size-line) } 225 | .dd-description br { display: none } 226 | dd + dt { margin-top: var(--size-half-line) } 227 | 228 | dt .c003 { font-style:normal; font-weight:700 } /* options */ 229 | 230 | .indexenv { list-style-type: none } 231 | .indexenv li { margin-left: 0 } 232 | 233 | /* Page specific */ 234 | 235 | /* Cover page */ 236 | 237 | div.maintitle > span > span 238 | { text-transform: uppercase; 239 | font-family: var(--font-headings); 240 | line-height: var(--size-line-ratio); 241 | font-size: calc(2.25 * var(--size-font)) !important; 242 | margin-left: -0.25ch; /* compensate nbsp */ } 243 | 244 | div.maintitle > span > span > br { display: none } 245 | div.maintitle > span > span > span 246 | { display: block; 247 | text-transform: none; 248 | font-style: italic; 249 | font-family: var(--font-body); 250 | font-size: var(--size-font-big) !important; } 251 | 252 | div.maintitle > span > span > span > span /* ugh */ 253 | { font-style: normal; 254 | line-height: var(--size-line); 255 | font-size: var(--size-font) !important; } 256 | 257 | div.maintitle > span > span > span > span > span /* ugh bis */ 258 | { font-size: var(--size-font-small) !important; 259 | font-style: italic; 260 | margin-left: -1.25ch; } 261 | 262 | div.maintitle + blockquote hr { display : none } 263 | div.maintitle + blockquote 264 | { margin: 0; 265 | /* margin-top: calc(-1 * var(--size-line)); chrome but not FF, bah... */ 266 | font-size: var(--size-font-small); 267 | border-bottom: solid thin var(--color-rule); 268 | padding-bottom: var(--size-half-line); } 269 | 270 | div.maintitle ~ blockquote:last-of-type { display: none } /* remove branding */ 271 | 272 | div.maintitle ~ ul:first-of-type:not(.itemize):not(.ftoc2) /* undo side toc */ 273 | { position: static; 274 | padding: 0; 275 | margin: 0; margin-top: var(--size-line); 276 | width: 100%; } 277 | 278 | div.maintitle ~ br { display: none } 279 | div.maintitle ~ ul:first-of-type:not(.itemize):not(.ftoc2) > li { margin: 0; } 280 | div.maintitle ~ ul:first-of-type:not(.itemize):not(.ftoc2) a 281 | { color: var(--color-link) } 282 | 283 | div.maintitle ~ table { margin-top: 0 } 284 | div.maintitle ~ ul:first-of-type:not(.itemize):not(.ftoc2) 285 | { list-style-type: none; 286 | font-family: inherit; text-transform: inherit; 287 | font-size: inherit; 288 | margin-top: var(--size-half-line); 289 | border: none; } 290 | 291 | div.maintitle ~ ul { list-style-type: none } 292 | div.maintitle ~ ul li { margin-left: 0 } 293 | 294 | /* Contents page */ 295 | 296 | h1#sec1 + ul:first-of-type /* undo side toc */ 297 | { position: static; 298 | list-style-type: none; 299 | margin: 0; margin-top: var(--size-half-line); 300 | width: 100%; border: none; padding: 0; 301 | font-size: var(--size-font-big); } 302 | 303 | h1#sec1 + ul:first-of-type li 304 | { margin-left: 0; padding-left: 0; text-indent: 0 } 305 | 306 | h1#sec1 ~ ul ul 307 | { list-style-type: none; font-size: var(--size-font-large); font-style: italic;} 308 | 309 | h1#sec1 ~ ul ul ul 310 | { font-size: var(--size-font); font-style: normal; 311 | margin-top: var(--size-half-line); } 312 | 313 | h1#sec1 ~ ul ul ul ul { margin-left: 2.5ch; margin-top: 0;} 314 | h1#sec1 ~ ul > li { margin-top: var(--size-line); } 315 | h1#sec1 ~ ul > li > ul > li { margin-top: var(--size-half-line); } 316 | 317 | /* Media adjustments */ 318 | 319 | @media only screen and (min-width:160ch) /* and (min-height: 60rem) */ 320 | { 321 | :root { --size-font: 1.125rem; } /* consider using vmin units */ 322 | } 323 | 324 | @media only screen and (max-width:80ch) 325 | { 326 | body { padding: var(--size-line); } 327 | body > * { width: 100%; } 328 | body > ul:first-of-type:not(.itemize):not(.ftoc2) 329 | { position: static; 330 | margin: 0; margin-top: var(--size-line); 331 | width: 100%; } 332 | pre { font-size: var(--size-font-tiny); } 333 | } 334 | 335 | @media print 336 | { 337 | * { -webkit-print-color-adjust: exact; } 338 | .content nav:first-child { visibility: hidden } 339 | body > * { width: 100%; } 340 | body > ul:first-of-type:not(.itemize):not(.ftoc2) 341 | { position: static; width: 100%; 342 | margin: 0; margin-top: var(--size-line); } 343 | 344 | /* odig.light with slight adjustements */ 345 | :root 346 | { --color-bg: white; 347 | --color-bg-highlight: #CAD7EF; 348 | --color-fg: black; 349 | --color-rule: #DADBDB; 350 | 351 | --color-code-block-bg: #E8E8E8; 352 | --color-code-fg: #16191D; 353 | --color-code-comment: #747679; 354 | --color-code-keyword: #874aa9; 355 | --color-code-type-id: #a9874a; 356 | --color-code-string: #4aa987; 357 | 358 | --color-link: #557dcc; 359 | --color-broken-link: #f71414; } 360 | } 361 | 362 | /* 363 | Copyright (c) 2019 The odig programmers 364 | 365 | Permission to use, copy, modify, and/or distribute this software for any 366 | purpose with or without fee is hereby granted, provided that the above 367 | copyright notice and this permission notice appear in all copies. 368 | 369 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 370 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 371 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 372 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 373 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 374 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 375 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 376 | */ -------------------------------------------------------------------------------- /themes/ocamldoc.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* Copyright (c) 2016 Daniel C. Bünzli. All rights reserved. 3 | SPDX-License-Identifier: ISC 4 | odig %%VERSION%% 5 | 6 | Use with ocamldoc >= 3.06.0 7 | */ 8 | 9 | /* Reset a few things. */ 10 | 11 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre, 12 | a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp, 13 | small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li, 14 | fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td, 15 | article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup, 16 | menu,nav,output,ruby,section,summary,time,mark,audio,video 17 | { margin: 0; padding: 0; border: 0; outline: 0; font-size: 100%; 18 | font: inherit; line-height: inherit; vertical-align: baseline; 19 | text-align: inherit; color: inherit; background: transparent; } 20 | 21 | table { border-collapse: collapse; border-spacing: 0; } 22 | 23 | html { box-sizing: border-box } 24 | *, *:before, *:after { box-sizing: inherit; } 25 | 26 | /* Basic page layout */ 27 | 28 | body 29 | { font-family: Helvetica, "DejaVu Sans", Arial, sans-serif; 30 | font-weight: normal; 31 | font-size: 0.875rem; 32 | line-height: 1.25rem; 33 | text-align: left; 34 | min-width: 40ex; 35 | max-width: 78ex; 36 | padding: 1.25rem; 37 | margin-left: 3.75rem; 38 | color: #222; background: #FAFAFA; } 39 | 40 | b { font-weight: bold } 41 | em { font-style: italic } 42 | 43 | .superscript { vertical-align: super; } 44 | .subscript { vertical-align: sub; } 45 | .superscript, .subscript 46 | { font-size : 0.75rem; line-height: 0; margin-left: 0.2ex; } 47 | 48 | /* ocamldoc markup workaround hacks. 49 | See http://caml.inria.fr/mantis/view.php?id=7351 */ 50 | 51 | hr 52 | { display: none } /* Would be nice to have but we cannot get that to 53 | interact well with our h1's because of br markup noise */ 54 | 55 | br { display: none } /* Annoying, hide them. */ 56 | code br { display: block } /* Except in signatures. */ 57 | 58 | .codepre br + br { display: none } 59 | h1 + pre { margin-bottom: 0.625rem } /* Toplevel module description */ 60 | 61 | /* Links and anchors */ 62 | 63 | a { text-decoration:none; color:#2C5CBD; } 64 | a:hover { box-shadow:0 1px 0 0 #2C5CBD; } 65 | *:target { /* Anchor highlight */ background-color: #FFF8E5; 66 | box-shadow: 0 0 0 2px #FFF8E5, 0 0 0 3px #DDDDDD; } 67 | 68 | a { text-decoration:none; color:#2C5CBD; } 69 | a:hover { box-shadow:0 1px 0 0 #2C5CBD; } 70 | *:target /* Linked highlight */ 71 | { background-color: #FFF8E5; 72 | box-shadow: 0 0 0 2px #FFF8E5, 0 0 0 3px #DDD; } 73 | 74 | .anchored:hover a.anchor { visibility: visible; } 75 | 76 | a.anchor:before { content: "#" } 77 | a.anchor:hover { box-shadow: none; text-decoration: underline } 78 | a.anchor 79 | { visibility: hidden; position: absolute; /* top: 0px; */ 80 | margin-left: -3ex; 81 | font-weight: normal; 82 | font-style: normal; 83 | padding-right: 1ex; padding-left: 1ex; /* To remain selectable */ 84 | color: #AAA; } 85 | 86 | /* Sections and document divisions 87 | 88 | Many of the modules of the stdlib start at h6, we make it look like 89 | h1 and the .7 div (sic) like h2. */ 90 | 91 | h1, h2, h3, h6, .h7 92 | { font-weight: bold; padding-top: 0.625rem; margin-top: 1.25rem } 93 | 94 | h1, h2, h6 95 | { font-size: 1.25rem; 96 | line-height: 2.4375rem; /* 2.5 rem - border width */ 97 | border-top-style: solid; 98 | border-width: 1px; 99 | border-color: #DDDDDD; } 100 | 101 | h4 { margin-top: 0.625rem; } 102 | 103 | br + * { margin-top: 0.625rem; } /* Ideally this would be h1 + * */ 104 | 105 | h3, .h7 { font-size: 1.125rem; } 106 | h1 + h2, h2 + h3, h6 + .h7 { margin-top: 0.625rem; padding-top: 0rem; } 107 | 108 | /* Paragraphs, lists and tables */ 109 | 110 | p { margin-top: 1.25rem } 111 | e.info p, li p { margin-top: 0.625rem } 112 | 113 | table { margin-top: 0.625rem } 114 | .info.module.top { margin-left: 0em } /* Toplevel module description */ 115 | .info { margin-left: 1ex; margin-top: 0.15625rem } 116 | .info *:first-child { margin-top: 0 } 117 | 118 | td .info { margin:0; padding:0; margin-left: 2em;}/* Description in indexes */ 119 | td .info p { margin-top: 0 } 120 | 121 | ul, ol { margin-top: 0.625rem; margin-bottom: 0.625rem; 122 | list-style-position: outside } 123 | ul + p, ol + p { margin-top: 0em } 124 | ul { list-style-type: square } 125 | 126 | ul > li { margin-left: 1.375rem; } 127 | ol > li { margin-left: 1.7rem; } 128 | 129 | /* Preformatted and code */ 130 | 131 | tt, code, pre 132 | { font-family: Menlo, "DejaVu Sans Mono", "Bitstream Vera Sans Mono", 133 | monospace; 134 | font-weight: normal; 135 | font-size: 0.75rem; } 136 | 137 | h1 tt, h1 code, h6 tt, h6 code { font-size: 1.125rem } 138 | h2 tt, h2 code, .h7 tt, .h7 code { font-size: 1rem } 139 | 140 | pre { margin-top: 1.25rem; } 141 | 142 | pre.verbatim, pre.codepre 143 | { padding-left: 0.25rem; 144 | padding-right: 0.25rem; 145 | margin-left: -0.25rem; 146 | margin-right: -0.25rem; 147 | padding-bottom: 0.3125rem; 148 | padding-top: 0.3125rem; 149 | margin-bottom: 0.265rem; /* Sometimes there's text without

150 | http://caml.inria.fr/mantis/view.php?id=7353 */ 151 | line-height: 1.1875rem; 152 | background: #F1F1F1; } 153 | 154 | pre .code { background: inherit; } 155 | .code { 156 | /* If we can avoid it. */ 157 | /* background: #F1F1F1; 158 | padding-top:1px; padding-bottom:1px; 159 | padding-left:1px; padding-right:1px; 160 | border-radius:2px; */ } 161 | 162 | .keyword { font-weight: bold } 163 | .comment { color: #888; font-style:italic } 164 | .constructor { color: #208000; } 165 | .string { color: brown; } 166 | .warning { color: crimson; } 167 | 168 | .typetable { margin-top: 0em } 169 | 170 | .paramstable code { margin-left: 1ex; margin-right: 1ex; } 171 | .sig_block { margin-left: 1em } 172 | 173 | /* Images */ 174 | 175 | img { margin-top: 1.25rem } 176 | 177 | /* Index tables */ 178 | 179 | ul.indexlist { list-style-type: none; margin-left:0; padding:0; } 180 | ul.indexlist li { margin-left:0; padding: 0; } 181 | 182 | /* Odig package index */ 183 | 184 | .by-name ol, .by-tag ol, .errors ol { list-style-type: none; margin-left:0; } 185 | .by-name ol ol, .by-tag ol ol { margin-top:0; margin-bottom: 0 } 186 | .by-name li, .by-tag li, .errors li { margin-left:0; } 187 | 188 | .by-name .version { font-size: 0.625rem; color: #AAA } 189 | .by-name nav { margin-bottom: 0.625rem } 190 | .by-name nav a 191 | { text-transform: uppercase; font-size: 1.125rem; 192 | margin-right:1ex; color: #222; display: inline-block; } 193 | 194 | .by-tag nav a { margin-right:1ex; color: #222; display: inline-block; } 195 | .by-tag > ol > li { margin-top: 0.625rem; } 196 | .by-tag > ol > li > span, 197 | .by-tag > ol > li > ol, 198 | .by-tag > ol > li > ol > li { display: inline-block; margin-right: 1ex; } 199 | 200 | /* Odig package page */ 201 | 202 | .package nav { display: inline; font-size: 0.875rem; font-weight: normal; } 203 | .package .version { font-size: 0.875rem; } 204 | 205 | /* This doesn't work in 4.03 because of spurious br's */ 206 | h1 + .indextable, h1 + .sel { margin-top: 0.625rem } 207 | .sel { font-weight: normal; font-style: italic; 208 | font-size:0.875rem; margin-top:1.25rem; } 209 | .sel + .indextable { margin-top:0.625rem; 210 | margin-bottom: 1.25rem; margin-left: 1ex; } 211 | 212 | .package.info { margin: 0;} 213 | .package.info td:first-child { font-style: italic; padding-right: 2ex; } 214 | .package.info ul { list-style-type: none; display: inline; margin:0; } 215 | .package.info li { display: inline-block; margin:0; margin-right:1ex; } 216 | #info-authors li, #info-maintainers li { display:block; } 217 | 218 | /* Odig ocamldoc adjustements. */ 219 | 220 | #info, .by-name h2, .by-tag h2, .errors h2 221 | { font-size: 1.25rem; 222 | line-height: 2.4375rem; /* 2.5 rem - border width */ 223 | border-top-style: solid; 224 | border-width: 1px; 225 | border-color: #DDDDDD; } 226 | 227 | #info + *, .by-name h2 + *, .by-tag h2 + *, .errors h2 { margin-top: 0.625rem; } 228 | 229 | body h1:first-child { display: none } /* package page. */ 230 | 231 | /* Mobile adjustements 232 | Can't really do anything we need to get a for viewport generated */ 233 | 234 | @media only screen and (max-width: 78ex) 235 | { body { margin: auto; } } 236 | 237 | /* Print adjustements. */ 238 | 239 | @media print 240 | { body { color: black; background: white; } 241 | body nav:first-child { visibility: hidden; }} 242 | -------------------------------------------------------------------------------- /themes/odoc.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @import url("fonts/fonts.css"); 3 | @import url("theme.css"); 4 | 5 | /* Copyright (c) 2019 The odig programmers. All rights reserved. 6 | SPDX-License-Identifier: ISC */ 7 | 8 | /* Reset a few things. */ 9 | 10 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre, 11 | a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,select, 12 | small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li, 13 | fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td, 14 | article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup, 15 | menu,nav,output,ruby,section,summary,time,mark,audio,video,button,input 16 | { margin: 0; padding: 0; border: 0; /* outline: 0; */ 17 | font-size: inherit; font: inherit; font-weight: inherit; 18 | line-height: inherit; vertical-align: baseline; 19 | text-align: inherit; color: inherit; background: transparent; } 20 | 21 | table { border-collapse: collapse; border-spacing: 0; } 22 | *, *:before, *:after { box-sizing: border-box; } 23 | 24 | /* Basic geometry */ 25 | 26 | :root { --font-headings: "PT-Sans-Caption"; 27 | --font-body: "PT-Sans"; 28 | --font-mono: "DejaVu-SansMono"; 29 | --size-font: 0.96875rem; 30 | --size-font-micro: calc(0.675 * var(--size-font)); 31 | --size-font-tiny-ratio: 0.75; 32 | --size-font-tiny: calc(var(--size-font-tiny-ratio) * var(--size-font)); 33 | --size-font-small: calc(0.875 * var(--size-font)); 34 | --size-font-large: calc(1.25 * var(--size-font)); 35 | --size-font-big: calc(1.5 * var(--size-font)); 36 | --size-font-huge: calc(1.75 * var(--size-font)); 37 | --size-font-mono-ratio: 0.87097; 38 | --size-font-mono-ratio: 0.865; 39 | --size-line-ratio: 1.5; 40 | --size-line: calc(var(--size-line-ratio) * var(--size-font)); 41 | --size-half-line: calc(0.5 * var(--size-line)); 42 | --size-fourth-line: calc(0.25 * var(--size-line)); } 43 | 44 | .odoc { background-color: var(--color-bg); 45 | color: var(--color-fg); 46 | font-size: var(--size-font); 47 | font-family: var(--font-body), sans-serif; 48 | font-weight: 400; 49 | line-height: var(--size-line); 50 | text-align: left; 51 | display: grid; 52 | margin: 0 auto; 53 | max-width: 130ch; 54 | grid-template-columns: minmax(50ch,76ch) minmax(23ch,1fr); 55 | grid-column-gap: calc(2 * var(--size-line)); 56 | grid-template-areas: "nav nav" 57 | "header toc" 58 | "content toc"; 59 | padding: calc(1.5 * var(--size-line)); 60 | padding-top: calc(0.5 * var(--size-line)); } 61 | 62 | .odoc-nav { grid-area: nav; } 63 | .odoc-preamble { grid-area: header; } 64 | .odoc-content { grid-area: content; margin: 0 } 65 | .odoc-tocs 66 | { grid-area: toc; 67 | margin-top: var(--size-line); 68 | border-top: solid thin var(--color-rule); } 69 | 70 | /* Media adjustments */ 71 | 72 | @media only screen and (min-width:160ch) /* and (min-height: 60rem) */ 73 | { 74 | :root { --size-font: 1.125rem; } /* consider using vmin units */ 75 | } 76 | 77 | @media only screen and (max-width:80ch) /* Basically mobile */ 78 | { 79 | .odoc 80 | { padding: var(--size-line); 81 | grid-template-columns: auto; 82 | grid-template-rows: none; 83 | grid-template-areas: "nav" 84 | "header" 85 | "toc" 86 | "content"; } 87 | .odoc-toc { margin: 0; margin-top: var(--size-line); } 88 | pre { font-size: var(--size-font-tiny); } 89 | } 90 | 91 | @media print 92 | { 93 | * { -webkit-print-color-adjust: exact; } 94 | .odoc-nav { visibility: hidden } 95 | .odoc-toc { margin: 0; margin-top: var(--size-line); } 96 | 97 | /* odig.light with slight adjustements */ 98 | :root 99 | { --color-bg: white; 100 | --color-bg-highlight: #CAD7EF; 101 | --color-fg: black; 102 | --color-rule: #DADBDB; 103 | 104 | --color-code-block-bg: #E8E8E8; 105 | --color-code-fg: #16191D; 106 | --color-code-comment: #747679; 107 | --color-code-keyword: #874aa9; 108 | --color-code-type-id: #a9874a; 109 | --color-code-string: #4aa987; 110 | 111 | --color-link: #557dcc; 112 | --color-broken-link: #f71414; } 113 | } 114 | 115 | /* Block level markup */ 116 | 117 | header > * + *, div > * + *, details > * + * 118 | { margin-top: var(--size-half-line); } 119 | 120 | ul + * , ol + * { margin-top: 0; } 121 | ul, ol { margin-top: var(--size-fourth-line); 122 | margin-bottom: var(--size-fourth-line); 123 | list-style-position: outside; } 124 | 125 | li *:first-child, li ol, li ul { margin-top: 0; } 126 | 127 | ul { list-style-type: square; } 128 | ul > li { margin-left: 2.25ch; } 129 | ol > li { margin-left: 2ch; } 130 | ol li::marker 131 | { font-family: var(--font-headings), sans-serif; 132 | font-size: var(--size-font-small); } 133 | 134 | img 135 | { display: block; 136 | margin-top: var(--size-half-line); 137 | margin-bottom: var(--size-half-line); } 138 | 139 | /* Headings and horizontal rulers */ 140 | 141 | h1, h2, .odoc-content > *:first-child 142 | { border-top: solid thin var(--color-rule); 143 | padding-top: calc(0.75 * var(--size-line)); 144 | margin-top: var(--size-line); } 145 | 146 | h1, h2, h3, h4, h5, h6 147 | { font-family: var(--font-headings), sans-serif; 148 | font-weight: 400; 149 | text-transform: uppercase; 150 | margin-top: var(--size-line); } 151 | 152 | h1, h2 { line-height: calc(1.5 * var(--size-line)); } 153 | h1 { font-size: var(--size-font-huge); } 154 | h2 { font-size: var(--size-font-big); } 155 | h3 { font-size: var(--size-font-large); } 156 | 157 | /* Phrasing content */ 158 | 159 | em { font-style: italic } 160 | b, strong { font-weight: 700 } 161 | small { font-size: var(--size-font-small); } 162 | 163 | sup { vertical-align: super; } 164 | sub { vertical-align: sub; } 165 | sup, sub 166 | { font-size : calc(1em * var(--size-font-tiny-ratio)); 167 | line-height: 0; margin-left: 0.2ex; } 168 | 169 | /* Code and code highlighting */ 170 | 171 | code, pre 172 | { font-family: var(--font-mono), monospace; 173 | font-weight: 400; 174 | font-size: calc(1em * var(--size-font-mono-ratio)); 175 | color: var(--color-code-fg); 176 | overflow-wrap: anywhere; } 177 | 178 | code span span { white-space: nowrap } /* Do not break these units */ 179 | 180 | pre code { font-size: inherit } /* don't apply transform twice... */ 181 | a code { color: inherit } 182 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { text-transform: none; } 183 | 184 | pre 185 | { background: var(--color-code-block-bg); 186 | padding-left: 0.8ch; padding-right: 0.8ch; 187 | margin-left: -0.8ch; margin-right: -0.8ch; 188 | padding-top: 1ch; padding-bottom: 1ch; 189 | white-space: pre-wrap; 190 | overflow-wrap: break-word; } 191 | 192 | .arrow { white-space: nowrap } 193 | .keyword , .hljs-keyword { color : var(--color-code-keyword); } 194 | .hljs-type { color : var(--color-code-type-id); } 195 | .hljs-string { color : var(--color-code-string); } 196 | .hljs-comment { color : var(--color-code-comment); font-style: italic; } 197 | 198 | /* Links and anchors. Note anchors need to be refined a bit further down 199 | in certain cases. */ 200 | 201 | a { text-decoration:none; color: var(--color-link); } 202 | a:hover { box-shadow:0 1px 0 0 var(--color-link); } 203 | a.anchor:before { content: "#" } 204 | a.anchor:hover { box-shadow: none; text-decoration: underline; } 205 | *:hover > a.anchor { visibility: visible } 206 | a.anchor 207 | { visibility: hidden; position: absolute; 208 | font-weight: normal; 209 | font-style: normal; 210 | margin-left: -2.5ch; 211 | padding-right: 1ch; padding-left: 1ch; /* To remain selectable */ 212 | color: var(--color-link); 213 | text-align: right; } 214 | 215 | *:target /* Linked highlight */ 216 | { background-color: var(--color-bg-highlight); 217 | box-shadow: 0 0 0 3px var(--color-bg-highlight) } 218 | 219 | .xref-unresolved { box-shadow:0 1px 0 0 var(--color-broken-link)} 220 | 221 | /* Table of contents */ 222 | 223 | .odoc-toc { font-size: var(--size-font-small); } 224 | .odoc-toc a { color : var(--color-fg); } 225 | .odoc-toc a:hover { color : var(--color-link) } 226 | .odoc-toc ul 227 | { font-family: var(--font-headings); text-transform: uppercase; 228 | margin-top: var(--size-line); 229 | list-style-type: none; } 230 | 231 | .odoc-toc ul ul 232 | { font-family: var(--font-body); text-transform: none; margin-top:0; } 233 | 234 | .odoc-toc ul ul ul { margin-left:1.5ch } 235 | .odoc-toc li { margin-left: 0; padding-left: 1ch; text-indent: -1ch; } 236 | .odoc-toc > ul > li { margin-top: calc(0.25 * var(--size-half-line)) } 237 | .odoc-toc > ul > li > ul > li:last-child 238 | { margin-bottom: var(--size-half-line) } 239 | 240 | .odoc-toc ul ul li { margin-left: 0; padding-left: 0; } 241 | 242 | /* Module structure items */ 243 | 244 | .odoc-spec { padding-bottom: var(--size-fourth-line); } 245 | .spec { margin-top: 0; } 246 | .spec-doc { margin-top:0; padding-left: 1ch; } 247 | .spec-doc > *:first-child { margin-top: 0 } 248 | 249 | /* Indent on wrap */ 250 | .spec, .spec td:first-child { padding-left: 4ch; text-indent: -4ch } 251 | .spec td.field { padding-left: 6ch } 252 | 253 | .spec .def-doc .comment-delim + * { margin-top: 0 } 254 | .spec .def-doc .comment-delim /* make them invisible yet copy-pastable */ 255 | { position: absolute; width: 1px; height: 1px; overflow: hidden; } 256 | 257 | /* But we don't do it for types for now because of variants and 258 | records. This makes :target highlight be off. And poses 259 | other problems (e.g. need to indent back the last ] or }. 260 | A better markup strategy should be found here. */ 261 | .spec.type { padding-left: 0; text-indent: 0 } 262 | .spec.type > a.anchor 263 | { padding-left: 1ch; padding-right: 1ch; /* values from a.anchor */ } 264 | .spec li > a.anchor, .spec > a.anchor 265 | { padding-right: 0.5ch; padding-left: 2ch; } 266 | 267 | .spec ol { margin:0; list-style-type: none; } 268 | .spec li { margin-left: 0; padding-left: 4ch; text-indent: -4ch } 269 | .spec li.record.field { margin-left: 2ch } 270 | 271 | .spec .def-doc { display: inline-block } 272 | .spec .def-doc { padding-left: /* 4 + 3 */ 7ch; } 273 | .spec .def-doc p { margin-left: -4ch; text-indent: 0 } 274 | 275 | .odoc-include summary { cursor: pointer } 276 | 277 | /* Package, module and @tag lists 278 | 279 | Allowing indent on wrap with the anchor makes all this quite convoluted. 280 | Is there a better way ? */ 281 | 282 | .packages, .modules, .at-tags { list-style-type: none; margin-left: -2ch; } 283 | .packages li, .modules li, .at-tags li { padding-left: 2ch; text-indent: -2ch; } 284 | .modules li a.anchor, .packages li a.anchor 285 | { padding-right: 0.5ch; padding-left: 2ch; } 286 | 287 | .synopsis { padding-left: 1ch; } 288 | .version { font-size: var(--size-font-micro); } 289 | .at-tag { text-transform : capitalize } 290 | 291 | /* Package page */ 292 | 293 | h1 .version, h1 nav { font-size: var(--size-font); line-height:0 } 294 | h1 nav 295 | { display: inline-block; 296 | font-family: var(--font-body); 297 | text-transform: capitalize; } 298 | 299 | .package.info td:first-child { padding-right: 2ch; min-width: 13ch} 300 | .package.info ul { list-style-type: none; display: inline; margin:0; padding:0} 301 | .package.info li { display: inline-block; margin:0; margin-right:1ex; } 302 | #info-authors li, #info-maintainers li { display: block; } 303 | 304 | /* Package index page */ 305 | 306 | .by-name nav a 307 | { font-family: var(--font-headings); 308 | font-size: var(--size-font-large); 309 | text-transform: uppercase; 310 | margin-right: 1ch; 311 | display: inline-block; } 312 | 313 | .by-tag ol { list-style-type: none; } 314 | .by-tag ol.tags li { margin-left: 1ch; display: inline-block } 315 | .by-tag td:first-child 316 | { font-family: var(--font-headings); 317 | font-size: var(--size-font-large); 318 | text-transform: uppercase; } 319 | 320 | /* 321 | Copyright (c) 2019 The odig programmers 322 | 323 | Permission to use, copy, modify, and/or distribute this software for any 324 | purpose with or without fee is hereby granted, provided that the above 325 | copyright notice and this permission notice appear in all copies. 326 | 327 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 328 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 329 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 330 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 331 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 332 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 333 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 334 | */ -------------------------------------------------------------------------------- /themes/solarized.dark/fonts: -------------------------------------------------------------------------------- 1 | ../fonts -------------------------------------------------------------------------------- /themes/solarized.dark/manual.css: -------------------------------------------------------------------------------- 1 | ../manual.css -------------------------------------------------------------------------------- /themes/solarized.dark/odoc.css: -------------------------------------------------------------------------------- 1 | ../odoc.css -------------------------------------------------------------------------------- /themes/solarized.dark/theme.css: -------------------------------------------------------------------------------- 1 | /* Theme based on https://ethanschoonover.com/solarized/ dark 2 | with one out of scheme color for links. */ 3 | :root 4 | { --color-bg: #002b36; /* base03 */ 5 | --color-bg-highlight: #eee8d5; /* base2 */ 6 | --color-fg: #839496; /* base0 */ 7 | --color-rule: #073642; /* base02 */ 8 | 9 | --color-code-block-bg: #073642; /* base02 */ 10 | --color-code-fg: #839496; /* base0 */ 11 | --color-code-comment: #586e75; /* base01 */ 12 | --color-code-keyword: #268bd2; /* blue */ 13 | --color-code-type-id: #b58900; /* yellow */ 14 | --color-code-string: #2aa198; /* cyan */ 15 | 16 | --color-link: #7788aa; /* out of scheme */ 17 | --color-broken-link: #dc322f; /* red */ } 18 | -------------------------------------------------------------------------------- /themes/solarized.light/fonts: -------------------------------------------------------------------------------- 1 | ../fonts -------------------------------------------------------------------------------- /themes/solarized.light/manual.css: -------------------------------------------------------------------------------- 1 | ../manual.css -------------------------------------------------------------------------------- /themes/solarized.light/odoc.css: -------------------------------------------------------------------------------- 1 | ../odoc.css -------------------------------------------------------------------------------- /themes/solarized.light/theme.css: -------------------------------------------------------------------------------- 1 | /* Theme based https://ethanschoonover.com/solarized/ light 2 | with one out of scheme color for links. */ 3 | :root 4 | { --color-bg: #fdf6e3; /* base3 */ 5 | --color-bg-highlight: #073642; /* base02 */ 6 | --color-fg: #839496; /* base0 */ 7 | --color-rule: #eee8d5; /* base2 */ 8 | 9 | --color-code-block-bg: #eee8d5; /* base2 */ 10 | --color-code-fg: #839496; /* base0 */ 11 | --color-code-comment: #586e75; /* base01 */ 12 | --color-code-keyword: #268bd2; /* blue */ 13 | --color-code-type-id: #b58900; /* yellow */ 14 | --color-code-string: #2aa198; /* cyan */ 15 | 16 | --color-link: #5e7fc4; /* out of scheme */ 17 | --color-broken-link: #dc322f; /* red */ } 18 | -------------------------------------------------------------------------------- /themes/solarized/fonts: -------------------------------------------------------------------------------- 1 | ../fonts -------------------------------------------------------------------------------- /themes/solarized/manual.css: -------------------------------------------------------------------------------- 1 | ../manual.css -------------------------------------------------------------------------------- /themes/solarized/odoc.css: -------------------------------------------------------------------------------- 1 | ../odoc.css -------------------------------------------------------------------------------- /themes/solarized/theme.css: -------------------------------------------------------------------------------- 1 | /* Theme based https://ethanschoonover.com/solarized/ light 2 | with one out of scheme color for links. */ 3 | :root 4 | { --color-bg: #fdf6e3; /* base3 */ 5 | --color-bg-highlight: #073642; /* base02 */ 6 | --color-fg: #839496; /* base0 */ 7 | --color-rule: #eee8d5; /* base2 */ 8 | 9 | --color-code-block-bg: #eee8d5; /* base2 */ 10 | --color-code-fg: #839496; /* base0 */ 11 | --color-code-comment: #586e75; /* base01 */ 12 | --color-code-keyword: #268bd2; /* blue */ 13 | --color-code-type-id: #b58900; /* yellow */ 14 | --color-code-string: #2aa198; /* cyan */ 15 | 16 | --color-link: #5e7fc4; /* out of scheme */ 17 | --color-broken-link: #dc322f; /* red */ } 18 | 19 | @media (prefers-color-scheme: dark) 20 | { 21 | /* Theme based on https://ethanschoonover.com/solarized/ dark 22 | with one out of scheme color for links. */ 23 | :root 24 | { --color-bg: #002b36; /* base03 */ 25 | --color-bg-highlight: #eee8d5; /* base2 */ 26 | --color-fg: #839496; /* base0 */ 27 | --color-rule: #073642; /* base02 */ 28 | 29 | --color-code-block-bg: #073642; /* base02 */ 30 | --color-code-fg: #839496; /* base0 */ 31 | --color-code-comment: #586e75; /* base01 */ 32 | --color-code-keyword: #268bd2; /* blue */ 33 | --color-code-type-id: #b58900; /* yellow */ 34 | --color-code-string: #2aa198; /* cyan */ 35 | 36 | --color-link: #7788aa; /* out of scheme */ 37 | --color-broken-link: #dc322f; /* red */ } 38 | } 39 | --------------------------------------------------------------------------------