boondoc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +17 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +345 -0
- data/exe/boondoc +6 -0
- data/lib/boondoc/assets.rb +112 -0
- data/lib/boondoc/cli.rb +99 -0
- data/lib/boondoc/config.rb +201 -0
- data/lib/boondoc/containment.rb +40 -0
- data/lib/boondoc/context.rb +48 -0
- data/lib/boondoc/core_constants.rb +23 -0
- data/lib/boondoc/docstring_parser.rb +119 -0
- data/lib/boondoc/filter.rb +34 -0
- data/lib/boondoc/frontend/comments.rb +67 -0
- data/lib/boondoc/frontend/prism_parser.rb +40 -0
- data/lib/boondoc/frontend/visitor.rb +359 -0
- data/lib/boondoc/generator.rb +116 -0
- data/lib/boondoc/linker.rb +175 -0
- data/lib/boondoc/links.rb +78 -0
- data/lib/boondoc/markdown.rb +90 -0
- data/lib/boondoc/model/code_object.rb +165 -0
- data/lib/boondoc/model/docstring.rb +70 -0
- data/lib/boondoc/model/registry.rb +150 -0
- data/lib/boondoc/path_map.rb +108 -0
- data/lib/boondoc/preview.rb +81 -0
- data/lib/boondoc/rake_task.rb +39 -0
- data/lib/boondoc/references.rb +59 -0
- data/lib/boondoc/renderer/base.rb +50 -0
- data/lib/boondoc/renderer/docstring.rb +140 -0
- data/lib/boondoc/renderer/index.rb +49 -0
- data/lib/boondoc/renderer/method_section.rb +84 -0
- data/lib/boondoc/renderer/namespace_page.rb +165 -0
- data/lib/boondoc/reporter.rb +36 -0
- data/lib/boondoc/stitcher.rb +89 -0
- data/lib/boondoc/type_parser.rb +78 -0
- data/lib/boondoc/version.rb +5 -0
- data/lib/boondoc/writer.rb +163 -0
- data/lib/boondoc/yard_bridge.rb +113 -0
- data/lib/boondoc/yardopts.rb +165 -0
- data/lib/boondoc.rb +48 -0
- metadata +116 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f854aff827f84fc734634d7e01d80908ff4f347532ab20cce7996771876b9f1f
|
|
4
|
+
data.tar.gz: 3ebdb2551d145c72e70639cad96e5f31366fbe98e21db115da1a8964f9b8d2a2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e7cdde7b2696fa3bca5cf2b8dcb7825ea28ffe4c2f69db5fa41444330fa7d6910b5ec683f11ab21106cca39c2978fe247dcd2b701d4d27f15753bbb6fb912427
|
|
7
|
+
data.tar.gz: d78c6cca549633e526d9f979d59d0bc653fb4339c23f57a51370ef8b57c14e296c77ab3f8f803c58818e356bc188b07d5f80294449a1179a178b401e7a064878
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-10-25
|
|
4
|
+
|
|
5
|
+
### ✨ Features
|
|
6
|
+
- **Markdown docs from YARD comments:** Prism-based parsing of YARD-style comments, with one markdown page per class or module.
|
|
7
|
+
- **Stitched README and extra files**, with reference links between pages.
|
|
8
|
+
- **YARD bridge (optional):** `--yard-macros` hands YARD only the files that contain macros or directives, and warns when a project has none.
|
|
9
|
+
- **Assets:** the `assets` option (`--assets`, or `--asset` in `.yardopts`) copies files such as images into the output, and `copy_linked_assets` also copies the files that pages embed. Links to a copied file, in the README, stitched files, and doc comments, point at the copy. A file that is no longer copied is removed like a stale page.
|
|
10
|
+
- **`.yardopts` support:** reads the source globs, the extra files after `-` and `--files`, `--readme`, `--title`, `--private`, `--protected`, and `--exclude`. `.boondoc.yml` and the command line win over it, key by key, and `--no-yardopts` (or `yardopts: false`) skips it. `--output-dir` is never used, and other options are listed as ignored.
|
|
11
|
+
- **`boondoc serve`** warns when `--host` is not a loopback address. Links to source files 404 under `serve`, because they point outside the output directory.
|
|
12
|
+
- **Case-safe file names:** classes and modules such as `Guard::CLI` and `Guard::Cli` now get their kind in the file name (`CLI.class.md`, `Cli.module.md`) instead of overwriting each other on case-insensitive filesystems.
|
|
13
|
+
|
|
14
|
+
### 🔒 Security
|
|
15
|
+
- `{include:file:...}`, `readme`, and `files` only read files inside the project, and Boondoc never writes or deletes through a symlink inside the output directory, or outside it.
|
|
16
|
+
- The output directory must be inside the project and may not be, or sit below, a symlink. `--allow-outside-output` (command line and Rake options only, never `.boondoc.yml`) allows an output directory outside the project; that directory is not checked for symlinks.
|
|
17
|
+
- Ruby files that resolve outside the project (an absolute glob, `../`, or a symlink) are skipped with a warning, so `--strict` fails.
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"Boondoc" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["joesharp13@gmail.com"](mailto:"joesharp13@gmail.com").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joe Sharp
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# Boondoc
|
|
2
|
+
|
|
3
|
+
Markdown-only API documentation for Ruby.
|
|
4
|
+
|
|
5
|
+
Boondoc reads the YARD-style comments you already write and produces plain
|
|
6
|
+
GitHub-flavored markdown: one file per class or module, an index, and working
|
|
7
|
+
relative links between them. There is no HTML and no site to deploy or host. The
|
|
8
|
+
output renders directly in a repository's `docs/` folder.
|
|
9
|
+
|
|
10
|
+
Because everything is markdown from start to finish, your README, changelog
|
|
11
|
+
and guides are stitched in as they are, and `{Foo#bar}` references inside them
|
|
12
|
+
become real links.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
bundle add boondoc --group development
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
or
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
gem install boondoc
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Boondoc needs Ruby 3.3 or newer.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
boondoc # document lib/**/*.rb into docs/
|
|
32
|
+
boondoc 'app/**/*.rb' lib -o docs/api # choose the sources and the output
|
|
33
|
+
boondoc --files CHANGELOG.md 'guides/*.md'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Option | Meaning |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `-o`, `--output DIR` | Output directory. Default `docs`. It must be inside the project, see `--allow-outside-output`. |
|
|
39
|
+
| `--index NAME` | Name of the index page. Default `README.md`, which GitHub shows when browsing the folder. |
|
|
40
|
+
| `--readme FILE` | File used as the top of the index page. Default `README.md` if present. |
|
|
41
|
+
| `--skip-readme` | Leave the README out of the index page. |
|
|
42
|
+
| `--files a.md b/*.md` | Extra files to stitch in. |
|
|
43
|
+
| `--assets FILES` | Files, directories, or globs to copy into the output, such as images. `FROM:DIR` copies into `DIR` instead. See [Assets](#assets). |
|
|
44
|
+
| `--copy-linked-assets` | Also copy the files that pages embed, such as images in the README and in doc comments. |
|
|
45
|
+
| `--exclude GLOBS` | Ruby files to leave out. |
|
|
46
|
+
| `--title TITLE` | Index title when there is no README. |
|
|
47
|
+
| `--private`, `--protected` | Include those methods (and `@private` objects). |
|
|
48
|
+
| `--yard-macros` | Expand `@!macro`, `@!method`, and `@!attribute` with the yard gem. See below. |
|
|
49
|
+
| `--no-yardopts` | Do not read `.yardopts`. See [Reading `.yardopts`](#reading-yardopts). |
|
|
50
|
+
| `--strict` | Exit non-zero on any warning, such as a link that does not resolve. |
|
|
51
|
+
| `--force` | Overwrite files in the output directory that Boondoc did not write. |
|
|
52
|
+
| `--allow-outside-output` | Let the output directory be outside the project, for docs kept outside the repository. This is a security risk if you do not trust the project, see [Where Boondoc reads and writes](#where-boondoc-reads-and-writes). Command line and Rake options only, never `.boondoc.yml`. |
|
|
53
|
+
| `-c`, `--config FILE` | Config file. Default `.boondoc.yml`. |
|
|
54
|
+
| `-q`, `--quiet` | Only print warnings and errors. |
|
|
55
|
+
|
|
56
|
+
The same settings can live in `.boondoc.yml`:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
sources:
|
|
60
|
+
- lib/**/*.rb
|
|
61
|
+
output: docs
|
|
62
|
+
files:
|
|
63
|
+
- CHANGELOG.md
|
|
64
|
+
- guides/*.md
|
|
65
|
+
strict: true
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Rake
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
require 'boondoc/rake_task'
|
|
72
|
+
|
|
73
|
+
Boondoc::RakeTask.new(:docs) do |t|
|
|
74
|
+
t.options = { files: ['CHANGELOG.md'], strict: true }
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## What gets written
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
docs/README.md your README, then an index of the API and the stitched files
|
|
82
|
+
docs/Foo.md one page per class or module
|
|
83
|
+
docs/Foo/Bar.md nested namespaces mirror the directory structure
|
|
84
|
+
docs/top-level.md top-level methods and constants, if there are any
|
|
85
|
+
docs/files/CHANGELOG.md stitched files
|
|
86
|
+
docs/.boondoc-manifest the list of files boondoc wrote
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Classes and modules whose names differ only by case, such as `Guard::CLI` and
|
|
90
|
+
`Guard::Cli`, would share a file on case-insensitive filesystems. Boondoc adds
|
|
91
|
+
the kind to both names (`Guard/CLI.class.md` and `Guard/Cli.module.md`, and
|
|
92
|
+
`Guard/Cli.module/` for the children of the module), so the output is the same
|
|
93
|
+
on every platform. Two of the same kind get a number as well (`Cli.class-2.md`).
|
|
94
|
+
Names that don't collide are left alone.
|
|
95
|
+
|
|
96
|
+
On every run Boondoc removes pages it wrote earlier that no longer correspond
|
|
97
|
+
to anything, and it refuses to overwrite a file it did not write unless you
|
|
98
|
+
pass `--force`. Files you add to the output directory by hand are left alone.
|
|
99
|
+
|
|
100
|
+
### Where Boondoc reads and writes
|
|
101
|
+
|
|
102
|
+
Boondoc limits what a project can make it read or write, because the project
|
|
103
|
+
(its comments, symlinks, and `.boondoc.yml`) may come from someone else, such as
|
|
104
|
+
a pull request that CI builds docs for.
|
|
105
|
+
|
|
106
|
+
- The output directory must be inside the project and must not be, or sit
|
|
107
|
+
below, a symlink. `.boondoc.yml` cannot move it outside.
|
|
108
|
+
- Ruby files that resolve outside the project, through an absolute path,
|
|
109
|
+
`../` or a symlink, are skipped with a warning, and `--strict` turns the
|
|
110
|
+
warning into a failure. A README or extra file outside the project is an
|
|
111
|
+
error. Run Boondoc from the directory that contains everything you want
|
|
112
|
+
documented.
|
|
113
|
+
- `{include:file:...}`, `readme`, and `files` only read files inside the project.
|
|
114
|
+
- Boondoc never writes or deletes through a symlink inside the output
|
|
115
|
+
directory, or outside it.
|
|
116
|
+
|
|
117
|
+
`--allow-outside-output` (on the command line, or `allow_outside_output: true`
|
|
118
|
+
in Rake options) lifts the first rule, for docs that are kept outside the
|
|
119
|
+
repository. An output directory outside the project is not checked for
|
|
120
|
+
symlinks. It is a security risk if you do not trust the project: its
|
|
121
|
+
`.boondoc.yml` then chooses where files are written and, with `force: true`,
|
|
122
|
+
which files are overwritten. Only pass it for projects you trust, and a
|
|
123
|
+
`.boondoc.yml` that sets it is rejected.
|
|
124
|
+
|
|
125
|
+
Each method gets an explicit anchor (`<a id="method-i-name"></a>`), because
|
|
126
|
+
GitHub's heading anchors cannot tell `save`, `save?`, and `save!` apart. That
|
|
127
|
+
anchor is the only HTML in the output.
|
|
128
|
+
|
|
129
|
+
## Viewing the docs
|
|
130
|
+
|
|
131
|
+
The output is plain markdown with relative links, so anything that renders
|
|
132
|
+
markdown and follows relative links can browse it.
|
|
133
|
+
|
|
134
|
+
**GitHub.** Commit the output directory and open it on github.com. GitHub
|
|
135
|
+
renders the folder's `README.md` under the file list, which is why the index
|
|
136
|
+
page has that name by default, and the links between pages and to method
|
|
137
|
+
anchors work as they are.
|
|
138
|
+
|
|
139
|
+
**VS Code.** Open `docs/README.md` and run *Markdown: Open Preview*
|
|
140
|
+
(`Cmd+Shift+V`, or `Ctrl+Shift+V` on Windows and Linux). Links in the preview
|
|
141
|
+
open the other pages, and no extension is needed.
|
|
142
|
+
|
|
143
|
+
**In a browser, with `boondoc serve`.** This starts a local preview styled
|
|
144
|
+
like github.com:
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
boondoc serve # serves docs/ at http://127.0.0.1:3333
|
|
148
|
+
boondoc serve -o docs/api --port 4000
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
It runs the
|
|
152
|
+
[gh-markdown-preview](https://github.com/yusukebe/gh-markdown-preview)
|
|
153
|
+
extension of the [GitHub CLI](https://cli.github.com), so `gh` has to be
|
|
154
|
+
installed. If the extension is missing, Boondoc asks before installing it.
|
|
155
|
+
`serve` reads the output directory from `-o` or `.boondoc.yml`, and it does
|
|
156
|
+
not generate anything: run `boondoc` first.
|
|
157
|
+
|
|
158
|
+
Links to source files (`../../lib/...`) point outside the output directory, so
|
|
159
|
+
they 404 under `serve`. They work on GitHub and in VS Code.
|
|
160
|
+
|
|
161
|
+
Serve only docs you trust, and keep the default host. The extension returns any
|
|
162
|
+
file the current user can read through its `/__/md?path=` address, sends the
|
|
163
|
+
markdown it renders to GitHub's markdown API, and does not check the `Host`
|
|
164
|
+
header. Boondoc warns when `--host` is not a loopback address.
|
|
165
|
+
|
|
166
|
+
To run the extension yourself, start it from inside the output directory:
|
|
167
|
+
|
|
168
|
+
```sh
|
|
169
|
+
gh extension install yusukebe/gh-markdown-preview
|
|
170
|
+
cd docs && gh markdown-preview --host 127.0.0.1
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
It always serves files relative to the directory it was started in, so
|
|
174
|
+
`gh markdown-preview docs` from the project root shows the index page but
|
|
175
|
+
every link on it leads to a missing page.
|
|
176
|
+
|
|
177
|
+
Other previewers work if they resolve links relative to the file being viewed.
|
|
178
|
+
Some editor plugins render only a single file and do not follow links at all.
|
|
179
|
+
|
|
180
|
+
## Stitching and links
|
|
181
|
+
|
|
182
|
+
Anywhere in a docstring, a stitched file, or your README:
|
|
183
|
+
|
|
184
|
+
| Write | Get |
|
|
185
|
+
| --- | --- |
|
|
186
|
+
| `{Foo::Bar}` | a link to that class |
|
|
187
|
+
| `{Foo#bar}`, `{Foo.bar}`, `{#bar}` | a link to that method |
|
|
188
|
+
| `{Foo#bar the bar method}` | the same link with your own text |
|
|
189
|
+
| `{file:CHANGELOG.md}` | a link to a stitched file |
|
|
190
|
+
| `{https://example.com Example}` | an ordinary link |
|
|
191
|
+
| `{include:file:snippets/setup.md}` | the contents of that file, inline |
|
|
192
|
+
| `{include:Foo}` | the docstring of `Foo`, inline |
|
|
193
|
+
| `\{Foo}` | a literal `{Foo}` |
|
|
194
|
+
|
|
195
|
+
References are resolved the way Ruby resolves constants, from the namespace
|
|
196
|
+
they are written in outwards, and methods are also found on superclasses and
|
|
197
|
+
mixins. Fenced code, indented code, and inline code are never touched, and
|
|
198
|
+
braces that do not look like a reference (`{key: value}`) pass through.
|
|
199
|
+
|
|
200
|
+
Relative links and images in stitched files are rewritten so they still point
|
|
201
|
+
at the same thing from their new location.
|
|
202
|
+
|
|
203
|
+
### Assets
|
|
204
|
+
|
|
205
|
+
Images and other files that pages need are copied into the output, next to the
|
|
206
|
+
pages, so the docs still work when the output directory is published on its
|
|
207
|
+
own. There are two ways to choose them:
|
|
208
|
+
|
|
209
|
+
- `assets` (or `--assets`, or `--asset` in `.yardopts`) names files, directories,
|
|
210
|
+
or globs. A directory is copied with everything under it. `images:media` copies
|
|
211
|
+
into `media/` in the output instead of keeping the project path. Hidden files,
|
|
212
|
+
symlinks that leave the project, and the output directory itself are skipped.
|
|
213
|
+
- `copy_linked_assets: true` (or `--copy-linked-assets`) also copies the files
|
|
214
|
+
that pages *embed*: images, `<img>`, `<source srcset>`, `<video poster>`, and
|
|
215
|
+
`![alt][label]` references. A plain link such as `[the source](lib/foo.rb)`
|
|
216
|
+
still points back into the project, so it is never copied.
|
|
217
|
+
|
|
218
|
+
Copied files keep their project path, or the `:DIR` you gave them. Links to them
|
|
219
|
+
are rewritten to point at the copy, in the README, other stitched files, and doc
|
|
220
|
+
comments, which Boondoc rewrites per page so an image in `Foo::Bar` still resolves
|
|
221
|
+
from `docs/Foo/Bar.md`. Files are copied byte for byte, and a copy that is already
|
|
222
|
+
up to date is not touched.
|
|
223
|
+
|
|
224
|
+
## Supported tags
|
|
225
|
+
|
|
226
|
+
`@param`, `@option`, `@return`, `@raise`, `@yield`, `@yieldparam`, `@yieldreturn`,
|
|
227
|
+
`@example`, `@overload`, `@see`, `@note`, `@todo`, `@deprecated`, `@abstract`,
|
|
228
|
+
`@since`, `@version`, `@author`, `@api`, `@private`, plus `(see Foo#bar)`
|
|
229
|
+
references. Any other tag is rendered as a labelled line rather than dropped.
|
|
230
|
+
|
|
231
|
+
Docstrings are treated as markdown and passed through unchanged. RDoc markup
|
|
232
|
+
is not converted.
|
|
233
|
+
|
|
234
|
+
## Compared with YARD and RDoc
|
|
235
|
+
|
|
236
|
+
Boondoc reads the same comments YARD does and aims at one thing YARD and RDoc
|
|
237
|
+
do not do: documentation that is markdown all the way through.
|
|
238
|
+
|
|
239
|
+
| | Boondoc | YARD | RDoc |
|
|
240
|
+
| --- | --- | --- | --- |
|
|
241
|
+
| Output | Markdown files | HTML site, `yri` | HTML site, `ri` |
|
|
242
|
+
| Viewing | GitHub, any markdown previewer | Browser, `yard server` | Browser, `ri` |
|
|
243
|
+
| Comment syntax | YARD tags (`@param`, `@return`, ...) | YARD tags | Prose and directives (`:call-seq:`, `:nodoc:`) |
|
|
244
|
+
| Comment markup | Markdown, passed through | Markdown, RDoc, and others, converted to HTML | RDoc or Markdown, converted to HTML |
|
|
245
|
+
| Ruby parser | [Prism](https://github.com/ruby/prism) | Its own | Prism (as of RDoc 8.1) |
|
|
246
|
+
| Macros and directives | Through YARD, with `--yard-macros` | Yes | No |
|
|
247
|
+
| Plugins and templates | No | Yes | Yes |
|
|
248
|
+
| C extensions | No | Yes | Yes |
|
|
249
|
+
| Ships with Ruby | No | No | Yes |
|
|
250
|
+
|
|
251
|
+
**What is the same as YARD.** The tags, the `[Types]` syntax, references in
|
|
252
|
+
braces, `(see Other#method)`, `@overload`, `@private`, and `@api private`, and
|
|
253
|
+
stitching in extra files. A codebase documented for YARD needs no changes.
|
|
254
|
+
Boondoc understands classes, modules, methods, attributes, constants,
|
|
255
|
+
visibility in all its forms, `module_function`, aliases, mixins, `Struct.new`,
|
|
256
|
+
`Data.define` and `Class.new`.
|
|
257
|
+
|
|
258
|
+
**What is different from YARD.** There is no HTML, no server, no `.yardoc`
|
|
259
|
+
database, no `yri`, no `yard stats`, and no template system. Boondoc does not
|
|
260
|
+
load YARD plugins, and reads only part of `.yardopts` (see
|
|
261
|
+
[below](#reading-yardopts)). Docstrings are treated as markdown and
|
|
262
|
+
are not converted from RDoc markup or Textile. Macros and directives need the
|
|
263
|
+
bridge described below.
|
|
264
|
+
|
|
265
|
+
**How it relates to RDoc.** RDoc is the tool that ships with Ruby. It does
|
|
266
|
+
not use YARD's tags, so comments written for RDoc carry less structure. Boondoc
|
|
267
|
+
will still document such a codebase: every class and method gets a page and a
|
|
268
|
+
signature, comments appear as written, and `:nodoc:` is honoured. RDoc markup
|
|
269
|
+
such as `+code+` or `=== Heading` is left as it is rather than converted.
|
|
270
|
+
|
|
271
|
+
### Reading `.yardopts`
|
|
272
|
+
|
|
273
|
+
If the project has a `.yardopts`, Boondoc uses these options from it, so a
|
|
274
|
+
codebase set up for YARD needs no extra configuration:
|
|
275
|
+
|
|
276
|
+
| In `.yardopts` | Used as |
|
|
277
|
+
| --- | --- |
|
|
278
|
+
| source globs and directories | `sources` |
|
|
279
|
+
| files after `-`, and `--files a,b` | `files` |
|
|
280
|
+
| `-r`, `--readme`, `--main` | `readme` |
|
|
281
|
+
| `--asset FROM[:DIR]` | `assets` |
|
|
282
|
+
| `--title` | `title` |
|
|
283
|
+
| `--private`, `--protected` | `private`, `protected` |
|
|
284
|
+
| `--exclude REGEXP` | Ruby files whose path matches the regular expression are left out, as in YARD |
|
|
285
|
+
|
|
286
|
+
`.boondoc.yml` and the command line win over `.yardopts`, one option at a time:
|
|
287
|
+
a `files` list in `.boondoc.yml` replaces the one from `.yardopts` instead of
|
|
288
|
+
adding to it. Pass `--no-yardopts`, or set `yardopts: false`, to skip the file.
|
|
289
|
+
|
|
290
|
+
Boondoc does not use `--output-dir`. In a `.yardopts` it names YARD's HTML
|
|
291
|
+
directory, which is usually ignored by git, while Boondoc's output is meant to be
|
|
292
|
+
committed. Following it could send the docs somewhere that is never published,
|
|
293
|
+
so Boondoc keeps writing to `output` (default `docs`) and prints a note. Set
|
|
294
|
+
`output` in `.boondoc.yml`, or pass `-o`, to choose the directory. Every other
|
|
295
|
+
option it does not use, such as `--markup`, `--plugin`, or `--template`, is listed
|
|
296
|
+
in a note too, as are C source globs, since Boondoc documents Ruby only. The notes
|
|
297
|
+
are not warnings, so `--strict` does not fail on them, and `--quiet` hides them.
|
|
298
|
+
`.document` files are not read.
|
|
299
|
+
|
|
300
|
+
`.yardopts` comes from the project, so it has the same limits as `.boondoc.yml`:
|
|
301
|
+
its paths must be inside the project, and it cannot set the output directory.
|
|
302
|
+
|
|
303
|
+
### Macros and directives
|
|
304
|
+
|
|
305
|
+
Boondoc does not expand YARD macros and directives (`@!macro`, `@!method`,
|
|
306
|
+
`@!attribute`) on its own. If your code relies on them, add the yard gem and
|
|
307
|
+
pass `--yard-macros`:
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
gem 'yard', group: :development
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Prism remains the source of truth. YARD is run as a second pass and only
|
|
314
|
+
contributes the objects that directives create and the expanded docstrings of
|
|
315
|
+
comments that use a macro.
|
|
316
|
+
|
|
317
|
+
### Performance
|
|
318
|
+
|
|
319
|
+
<picture>
|
|
320
|
+
<source media="(prefers-color-scheme: dark)" srcset="images/benchmark-dark.svg">
|
|
321
|
+
<img alt="Bar charts of the benchmark table in docs/Performance.md, one panel per codebase: Boondoc is the fastest tool on every codebase" src="images/benchmark-light.svg">
|
|
322
|
+
</picture>
|
|
323
|
+
|
|
324
|
+
The full table and how it was measured are in [docs/Performance.md](docs/Performance.md).
|
|
325
|
+
|
|
326
|
+
## Development
|
|
327
|
+
|
|
328
|
+
```sh
|
|
329
|
+
bin/setup
|
|
330
|
+
bundle exec rake # specs and RuboCop
|
|
331
|
+
bundle exec boondoc # regenerate docs/ with boondoc itself
|
|
332
|
+
bundle exec guard # RuboCop, specs and a benchmark check on every change
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
The rendering specs compare against the files in
|
|
336
|
+
`spec/fixtures/sample/expected`. After an intended change to the output,
|
|
337
|
+
regenerate them with `UPDATE_GOLDEN=1 bundle exec rspec spec/boondoc/generator_spec.rb`
|
|
338
|
+
and review the diff.
|
|
339
|
+
|
|
340
|
+
New to the codebase? The [contributor familiarity guide](docs/CONTRIBUTING.md)
|
|
341
|
+
walks through the pipeline and where to start for common changes.
|
|
342
|
+
|
|
343
|
+
## License
|
|
344
|
+
|
|
345
|
+
Available as open source under the terms of the [MIT License](LICENSE.txt).
|
data/exe/boondoc
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pathname'
|
|
4
|
+
|
|
5
|
+
module Boondoc
|
|
6
|
+
# The files that are copied into the output directory next to the pages: images, video, PDFs,
|
|
7
|
+
# and anything else a page links to or embeds.
|
|
8
|
+
#
|
|
9
|
+
# Two kinds are copied. The ones named by the `assets` option are always copied. Each entry is a
|
|
10
|
+
# file, a directory (everything under it), or a glob, optionally followed by `:DIR` to copy into
|
|
11
|
+
# that directory of the output instead of keeping the project path. Hidden files, symlinks that
|
|
12
|
+
# leave the project, and anything in the output directory are skipped. With `copy_linked_assets`
|
|
13
|
+
# the files that pages embed are copied too, as they are found while the pages are rendered, so
|
|
14
|
+
# {#files} is only complete once rendering has finished.
|
|
15
|
+
class Assets
|
|
16
|
+
# @param config [Config]
|
|
17
|
+
# @raise [Error] when a named file is outside the project, or two files would be copied to the same path
|
|
18
|
+
def initialize(config)
|
|
19
|
+
@config = config
|
|
20
|
+
@named = Array(config.assets).each_with_object({}) { |entry, map| add(map, entry) }
|
|
21
|
+
@by_source = @named.invert
|
|
22
|
+
@linked = {}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Where a file will be in the output directory, when it is copied there.
|
|
26
|
+
#
|
|
27
|
+
# @param absolute [String] absolute path of a file in the project
|
|
28
|
+
# @param embedded [Boolean] whether the page embeds the file, as an image does
|
|
29
|
+
# @return [String, nil] the path relative to the output directory, or nil when the file is not copied
|
|
30
|
+
def output_for(absolute, embedded: false)
|
|
31
|
+
@by_source[absolute] || (link(absolute) if embedded && @config.copy_linked_assets)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Finds the copy of a path written in a page. A path is first read relative to the output
|
|
35
|
+
# directory, as YARD's `--asset` paths are, and then relative to the project.
|
|
36
|
+
#
|
|
37
|
+
# @param path [String] a path without any fragment or query
|
|
38
|
+
# @param embedded [Boolean] whether the page embeds the file rather than linking to it
|
|
39
|
+
# @return [String, nil] the path relative to the output directory, or nil when nothing is copied
|
|
40
|
+
def copy_for(path, embedded: false)
|
|
41
|
+
output = Pathname.new(path).cleanpath.to_s
|
|
42
|
+
return output if @named.key?(output)
|
|
43
|
+
|
|
44
|
+
output_for(File.expand_path(path, @config.root), embedded: embedded)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Hash{String => String}] absolute source paths, keyed by the path relative to
|
|
48
|
+
# the output directory that each is copied to
|
|
49
|
+
def files
|
|
50
|
+
@named.merge(@linked)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def add(map, entry)
|
|
56
|
+
from, directory = entry.split(':', 2)
|
|
57
|
+
Dir.glob(from, base: @config.root).each do |match|
|
|
58
|
+
sources(match).each do |source, within|
|
|
59
|
+
output = directory ? output_path(directory, within) : source
|
|
60
|
+
absolute = File.expand_path(source, @config.root)
|
|
61
|
+
clash = map.fetch(output, absolute) != absolute
|
|
62
|
+
raise Error, "#{output} would be copied from both #{map[output]} and #{absolute}" if clash
|
|
63
|
+
|
|
64
|
+
map[output] = absolute
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [Array<Array(String, String)>] each file under a match, as its path in the project and
|
|
70
|
+
# its path relative to the match, which a `:DIR` destination keeps
|
|
71
|
+
def sources(match)
|
|
72
|
+
full = File.expand_path(match, @config.root)
|
|
73
|
+
raise Error, "#{match} is outside the project (#{@config.root})" unless Containment.inside?(full, @config.root)
|
|
74
|
+
return [[relative(full), File.basename(full)]] if File.file?(full)
|
|
75
|
+
|
|
76
|
+
Dir.glob('**/*', base: full).select { |path| named_file?(File.join(full, path)) }
|
|
77
|
+
.map { |path| [relative(File.join(full, path)), path] }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def named_file?(full)
|
|
81
|
+
inside = full.delete_prefix(File.join(@config.root, ''))
|
|
82
|
+
File.file?(full) && inside.split('/').none? { |part| part.start_with?('.') } &&
|
|
83
|
+
Containment.inside?(full, @config.root) && !full.start_with?(File.join(@config.output_dir, ''))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def output_path(directory, within)
|
|
87
|
+
path = Pathname.new(File.join(directory, within)).cleanpath.to_s
|
|
88
|
+
raise Error, "asset destination #{directory} is outside the output directory" if path.start_with?('..', '/')
|
|
89
|
+
|
|
90
|
+
path
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def relative(full)
|
|
94
|
+
Pathname.new(full).relative_path_from(Pathname.new(@config.root)).to_s
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Remembers an embedded file that is worth copying and returns where it goes.
|
|
98
|
+
def link(absolute)
|
|
99
|
+
root = File.join(@config.root, '')
|
|
100
|
+
return nil unless absolute.start_with?(root) && copyable?(absolute)
|
|
101
|
+
|
|
102
|
+
output = absolute.delete_prefix(root)
|
|
103
|
+
@linked[output] = absolute
|
|
104
|
+
output
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def copyable?(absolute)
|
|
108
|
+
File.file?(absolute) && !Stitcher::MARKDOWN.match?(absolute) &&
|
|
109
|
+
!absolute.start_with?(File.join(@config.output_dir, '')) && Containment.inside?(absolute, @config.root)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/boondoc/cli.rb
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
|
|
5
|
+
module Boondoc
|
|
6
|
+
# The `boondoc` command.
|
|
7
|
+
class CLI < Thor
|
|
8
|
+
COMMANDS = %w[generate serve version help].freeze
|
|
9
|
+
COMMAND_FLAGS = %w[-h --help -v --version].freeze
|
|
10
|
+
|
|
11
|
+
# Runs the CLI, treating a bare `boondoc [GLOBS]` as `boondoc generate [GLOBS]`.
|
|
12
|
+
#
|
|
13
|
+
# @param argv [Array<String>]
|
|
14
|
+
# @return [void]
|
|
15
|
+
def self.start_with_default(argv)
|
|
16
|
+
known = COMMANDS.include?(argv.first) || COMMAND_FLAGS.include?(argv.first)
|
|
17
|
+
start(known ? argv : ['generate', *argv])
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Boolean] exit non-zero when a command fails
|
|
21
|
+
def self.exit_on_failure?
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc 'generate [GLOBS]', 'Write markdown documentation (default command)'
|
|
26
|
+
long_desc <<~DESC
|
|
27
|
+
Parses the Ruby files matching GLOBS (default: lib/**/*.rb) and writes one markdown
|
|
28
|
+
file per class or module, plus an index, into the output directory.
|
|
29
|
+
|
|
30
|
+
Options can also be set in .boondoc.yml using the same names with underscores
|
|
31
|
+
(sources, exclude, output, index, readme, files, assets, copy_linked_assets, title, private,
|
|
32
|
+
protected, yard_macros, strict, yardopts). allow_outside_output can only be given on the command line.
|
|
33
|
+
|
|
34
|
+
The output directory must be inside the project, and not reached through a symlink.
|
|
35
|
+
Sources outside the project are skipped with a warning.
|
|
36
|
+
DESC
|
|
37
|
+
option :output, aliases: '-o', type: :string, desc: 'Output directory (default: docs)'
|
|
38
|
+
option :index, type: :string, desc: 'Name of the index page (default: README.md)'
|
|
39
|
+
option :readme, type: :string, desc: 'File to use as the index page (default: README.md if present)'
|
|
40
|
+
option :skip_readme, type: :boolean, desc: 'Do not include a README in the index page'
|
|
41
|
+
option :files, type: :array, desc: 'Extra markdown files or globs to stitch in'
|
|
42
|
+
option :assets, type: :array, desc: 'Files, directories, or globs to copy into the output (FROM or FROM:DIR)'
|
|
43
|
+
option :copy_linked_assets, type: :boolean,
|
|
44
|
+
desc: 'Also copy the files that pages embed, such as images in the README'
|
|
45
|
+
option :exclude, type: :array, desc: 'Ruby files or globs to leave out'
|
|
46
|
+
option :title, type: :string, desc: 'Title for the index page when there is no README'
|
|
47
|
+
option :private, type: :boolean, desc: 'Include private methods and @private objects'
|
|
48
|
+
option :protected, type: :boolean, desc: 'Include protected methods'
|
|
49
|
+
option :yard_macros, type: :boolean, desc: 'Expand @!macro, @!method, and @!attribute using the yard gem'
|
|
50
|
+
option :yardopts, type: :boolean, desc: 'Read .yardopts (default; --no-yardopts to skip it)'
|
|
51
|
+
option :strict, type: :boolean, desc: 'Exit non-zero if there are warnings'
|
|
52
|
+
option :force, type: :boolean, desc: 'Overwrite files in the output directory that boondoc did not write'
|
|
53
|
+
option :allow_outside_output, type: :boolean,
|
|
54
|
+
desc: 'Allow an output directory outside the project (a security risk if the ' \
|
|
55
|
+
'project is not trusted; for docs kept outside the repository)'
|
|
56
|
+
option :config, aliases: '-c', type: :string, desc: 'Config file (default: .boondoc.yml)'
|
|
57
|
+
option :quiet, aliases: '-q', type: :boolean, desc: 'Only print warnings and errors'
|
|
58
|
+
def generate(*globs)
|
|
59
|
+
Generator.new(Config.load(overrides(globs))).run
|
|
60
|
+
rescue Error => e
|
|
61
|
+
raise Thor::Error, "boondoc: #{e.message}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
desc 'serve', 'Preview the generated docs in a browser with gh markdown-preview'
|
|
65
|
+
long_desc <<~DESC
|
|
66
|
+
Starts the gh markdown-preview extension of the GitHub CLI from inside the output
|
|
67
|
+
directory, so the links between pages work. Offers to install the extension when
|
|
68
|
+
it is missing. Run `boondoc` first to generate the docs.
|
|
69
|
+
DESC
|
|
70
|
+
option :output, aliases: '-o', type: :string, desc: 'Directory holding the docs (default: docs)'
|
|
71
|
+
option :allow_outside_output, type: :boolean, desc: 'Allow a docs directory outside the project'
|
|
72
|
+
option :host, type: :string, default: Preview::DEFAULT_HOST, desc: 'Address to bind'
|
|
73
|
+
option :port, aliases: '-p', type: :numeric, desc: 'Port to listen on (default: 3333)'
|
|
74
|
+
option :config, aliases: '-c', type: :string, desc: 'Config file (default: .boondoc.yml)'
|
|
75
|
+
def serve
|
|
76
|
+
config = Config.load(options.slice('output', 'config', 'allow_outside_output').transform_keys(&:to_sym))
|
|
77
|
+
Preview.new(config.output_dir, host: options[:host], port: options[:port]) do
|
|
78
|
+
yes?("The gh markdown-preview extension is not installed. Install #{Preview::EXTENSION} now? [y/N]")
|
|
79
|
+
end.start
|
|
80
|
+
rescue Error => e
|
|
81
|
+
raise Thor::Error, "boondoc: #{e.message}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
desc 'version', 'Print the boondoc version'
|
|
85
|
+
def version
|
|
86
|
+
puts Boondoc::VERSION
|
|
87
|
+
end
|
|
88
|
+
map %w[-v --version] => :version
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def overrides(globs)
|
|
93
|
+
settings = options.to_h.transform_keys(&:to_sym)
|
|
94
|
+
settings[:readme] = false if settings.delete(:skip_readme)
|
|
95
|
+
settings[:sources] = globs unless globs.empty?
|
|
96
|
+
settings
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|