Skip to content

Commit

Permalink
Allow passing Range objects to the nesting_level option
Browse files Browse the repository at this point in the history
In order to have a higher level of customization for tables of
contents, it is possible to pass a `Range` object (e.g. 2..5) as the
nesting_level option of `HTML_TOC` objects.

Fixes #519
  • Loading branch information
robin850 committed Jan 28, 2017
1 parent 10b7938 commit 4105028
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 10 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog

* Allow passing `Range` objects to the `nesting_level` option to have
a higher level of customization for table of contents:

~~~ruby
Redcarpet::Render::HTML_TOC.new(nesting_level: 2..5)
~~~

## Version 3.4.0

* Rely on djb2 hashing generating anchors with non-ASCII chars.
Expand Down
4 changes: 2 additions & 2 deletions README.markdown
Expand Up @@ -182,8 +182,8 @@ which will output a table of contents in HTML based on the headers of the
Markdown document.

When instantiating this render object, you can optionally pass a `nesting_level`
option which takes an integer and allows you to make it render only headers
until a specific level.
option which takes an integer or a range and allows you to make it render only
headers at certain levels.

Redcarpet also includes a plaintext renderer, `Redcarpet::Render::StripDown`, that
strips out all the formatting:
Expand Down
9 changes: 6 additions & 3 deletions ext/redcarpet/html.c
Expand Up @@ -326,7 +326,8 @@ rndr_header(struct buf *ob, const struct buf *text, int level, void *opaque)
if (ob->size)
bufputc(ob, '\n');

if ((options->flags & HTML_TOC) && (level <= options->toc_data.nesting_level)) {
if ((options->flags & HTML_TOC) && level >= options->toc_data.nesting_bounds[0] &&
level <= options->toc_data.nesting_bounds[1]) {
bufprintf(ob, "<h%d id=\"", level);
rndr_header_anchor(ob, text);
BUFPUTSL(ob, "\">");
Expand Down Expand Up @@ -675,7 +676,8 @@ toc_header(struct buf *ob, const struct buf *text, int level, void *opaque)
{
struct html_renderopt *options = opaque;

if (level <= options->toc_data.nesting_level) {
if (level >= options->toc_data.nesting_bounds[0] &&
level <= options->toc_data.nesting_bounds[1]) {
/* set the level offset if this is the first header
* we're parsing for the document */
if (options->toc_data.current_level == 0)
Expand Down Expand Up @@ -824,7 +826,8 @@ sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options,
/* Prepare the options pointer */
memset(options, 0x0, sizeof(struct html_renderopt));
options->flags = render_flags;
options->toc_data.nesting_level = 99;
options->toc_data.nesting_bounds[0] = 1;
options->toc_data.nesting_bounds[1] = 6;

/* Prepare the callbacks */
memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));
Expand Down
2 changes: 1 addition & 1 deletion ext/redcarpet/html.h
Expand Up @@ -35,7 +35,7 @@ struct html_renderopt {
struct {
int current_level;
int level_offset;
int nesting_level;
int nesting_bounds[2];
} toc_data;

unsigned int flags;
Expand Down
20 changes: 16 additions & 4 deletions ext/redcarpet/rc_render.c
Expand Up @@ -511,10 +511,22 @@ static VALUE rb_redcarpet_htmltoc_init(int argc, VALUE *argv, VALUE self)
sdhtml_toc_renderer(&rndr->callbacks, (struct html_renderopt *)&rndr->options.html, render_flags);
rb_redcarpet__overload(self, rb_cRenderHTML_TOC);

if (!(NIL_P(nesting_level)))
rndr->options.html.toc_data.nesting_level = NUM2INT(nesting_level);
else
rndr->options.html.toc_data.nesting_level = 6;
/* Check whether we are dealing with a Range object by
checking whether the object responds to min and max */
if (rb_respond_to(nesting_level, rb_intern("min")) &&
rb_respond_to(nesting_level, rb_intern("max"))) {
int min = NUM2INT(rb_funcall(nesting_level, rb_intern("min"), 0));
int max = NUM2INT(rb_funcall(nesting_level, rb_intern("max"), 0));

rndr->options.html.toc_data.nesting_bounds[0] = min;
rndr->options.html.toc_data.nesting_bounds[1] = max;
} else if (FIXNUM_P(nesting_level)) {
rndr->options.html.toc_data.nesting_bounds[0] = 1;
rndr->options.html.toc_data.nesting_bounds[1] = NUM2INT(nesting_level);
} else {
rndr->options.html.toc_data.nesting_bounds[0] = 1;
rndr->options.html.toc_data.nesting_bounds[1] = 6;
}

return Qnil;
}
Expand Down
14 changes: 14 additions & 0 deletions test/html_toc_render_test.rb
Expand Up @@ -33,6 +33,20 @@ def test_granular_toc_render
assert !output.include?("A sub-sub title")
end

def test_granular_toc_render_with_range
output = render(@markdown, with: { nesting_level: 2..5 }).strip

assert output.start_with?("<ul>")
assert output.end_with?("</ul>")

assert output.match("Another one")
assert output.match("A sub-sub-title")
assert output.match("見出し")

refute output.match("A title")
refute output.match("A really tiny title")
end

def test_toc_heading_id
output = render(@markdown)

Expand Down

0 comments on commit 4105028

Please sign in to comment.