greenmat 3.2.0.2 → 3.2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 640a48e0309d3ad49841f279024df4b6bfad325f
4
- data.tar.gz: 6d5c03df89ba11808bb57ca7c156336e4f300a92
3
+ metadata.gz: c56dac9366c03995652ee0bc85e40215c3abbb1f
4
+ data.tar.gz: 083dc87803beeb23ab9d2fcad89bc41b2cb96cfc
5
5
  SHA512:
6
- metadata.gz: 1c6ed914f324b245051525b3a8ff1189ac7a7144ad370254245448e0d857bcae235b1c202e9e66597a51df931f59c042ff2f345580c5765ba2160b81aa8a45fe
7
- data.tar.gz: e7479bf0e3e018566ea1640e09981d7eddb36d8eebe3e4b64946130abf0951220f08e923b1032769ad20d941a836cc0be129fe0b70ef729f460a2215987f4581
6
+ metadata.gz: f3dfbca086ab162186b40bfe338f147f9b63d3feb006265e0cf253c693375e9430e7bb825267b4e25d55dbe24d66574f7b257ba7f216b38f6e2cc4bd4d215333
7
+ data.tar.gz: b870e9c370fd479629b51c55425a6eee6723787fd9e70dde2d42807d52ea17d81f28d2998d40a7d8493c347a9786b8503845f7113dcef0433f75027a02d1846b
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## v3.2.2.0
6
+
7
+ * Update base Redcarpet version to 3.2.2.
8
+
5
9
  ## v3.2.0.2
6
10
 
7
11
  * Fix missing `greenmat/version` in the gem package.
data/README.md CHANGED
@@ -13,6 +13,39 @@ Greenmat follows Redcarpet's updates by merging the upstream changes.
13
13
  The version format is `MAJOR.MINOR.PATCH.FORK`.
14
14
  `MAJOR.MINOR.PATCH` is the same as the version of Redcarpet that Greenmat is based on. `FORK` is incremented on each release of Greenmat itself and reset to zero when any of `MAJOR.MINOR.PATCH` is bumped.
15
15
 
16
+ ## Development
17
+
18
+ ### Initial setup
19
+
20
+ Clone the Greenmat repository:
21
+
22
+ ```bash
23
+ $ git clone git@github.com:increments/greenmat.git
24
+ $ cd greenmat
25
+ ```
26
+
27
+ Set up git remote for Redcarpet as `upstream`:
28
+
29
+ ```bash
30
+ $ rake greenmat:setup_upstream
31
+ ```
32
+
33
+ ### Merging upstream changes
34
+
35
+ Run the following task to merge `upstream/master` branch into the current branch:
36
+
37
+ ```bash
38
+ $ rake greenmat:merge_upstream
39
+ ```
40
+
41
+ Note that this task does _not_ automatically commit the merge, so you need to commit the changes after checking each diff. Also it forces conflicting hunks to be auto-resolved cleanly by favoring upstream version.
42
+
43
+ If you want to merge a branch other than `upstream/master`, specify the name as `BRANCH` environment variable:
44
+
45
+ ```bash
46
+ $ rake greenmat:merge_upstream BRANCH=branch_name
47
+ ```
48
+
16
49
  ## Acknowledgment
17
50
 
18
51
  We appreciate Redcarpet project and the contributors for the great efforts!
@@ -1,7 +1,43 @@
1
1
  #!/usr/bin/env ruby
2
- lib_path = File.expand_path('../../lib', __FILE__)
3
- $:.unshift(lib_path)
2
+ # Usage: greenmat [--parse-<extension>...] [--render-<extension>...] [--smarty] [<file>...]
3
+ # Convert one or more Markdown files to HTML and write to standard output. With
4
+ # no <file> or when <file> is '-', read Markdown source text from standard input.
5
+ # With <extension>s, perform additional Markdown processing before writing output.
6
+ # With --smarty, use the SmartyHTML renderer
7
+ if ARGV.include?('--help') or ARGV.include?('-h')
8
+ File.read(__FILE__).split("\n").grep(/^# /).each do |line|
9
+ puts line[2..-1]
10
+ end
11
+ exit 0
12
+ end
4
13
 
5
- require 'greenmat/cli'
14
+ require 'greenmat'
6
15
 
7
- Greenmat::CLI.process(ARGV)
16
+ if ARGV.include?('--version') or ARGV.include?('-v')
17
+ puts "Greenmat #{Greenmat::VERSION}"
18
+ exit 0
19
+ end
20
+
21
+ root = File.expand_path('../../', __FILE__)
22
+ $:.unshift File.expand_path('lib', root)
23
+
24
+ render_extensions = {}
25
+ parse_extensions = {}
26
+ renderer = Greenmat::Render::HTML
27
+
28
+ ARGV.delete_if do |arg|
29
+ if arg =~ /^--render-([\w-]+)$/
30
+ arg = $1.gsub('-', '_')
31
+ render_extensions[arg.to_sym] = true
32
+ elsif arg =~ /^--parse-([\w-]+)$/
33
+ arg = $1.gsub('-', '_')
34
+ parse_extensions[arg.to_sym] = true
35
+ elsif arg == '--smarty'
36
+ renderer = Greenmat::Render::SmartyHTML
37
+ else
38
+ false
39
+ end
40
+ end
41
+
42
+ render = renderer.new(render_extensions)
43
+ STDOUT.write(Greenmat::Markdown.new(render, parse_extensions).render(ARGF.read))
@@ -94,14 +94,14 @@ bufnew(size_t unit)
94
94
 
95
95
  /* bufnullterm: NULL-termination of the string array */
96
96
  const char *
97
- bufcstr(const struct buf *buf)
97
+ bufcstr(struct buf *buf)
98
98
  {
99
99
  assert(buf && buf->unit);
100
100
 
101
101
  if (buf->size < buf->asize && buf->data[buf->size] == 0)
102
102
  return (char *)buf->data;
103
103
 
104
- if (buf->size + 1 <= buf->asize || bufgrow(buf, buf->size + 1) == BUF_OK) {
104
+ if (buf->size + 1 <= buf->asize || bufgrow(buf, buf->size + 1) == 0) {
105
105
  buf->data[buf->size] = 0;
106
106
  return (char *)buf->data;
107
107
  }
@@ -118,7 +118,7 @@ bufprintf(struct buf *buf, const char *fmt, ...)
118
118
 
119
119
  assert(buf && buf->unit);
120
120
 
121
- if (buf->size >= buf->asize && bufgrow(buf, buf->size + 1) < BUF_OK)
121
+ if (buf->size >= buf->asize && bufgrow(buf, buf->size + 1) < 0)
122
122
  return;
123
123
 
124
124
  va_start(ap, fmt);
@@ -136,7 +136,7 @@ bufprintf(struct buf *buf, const char *fmt, ...)
136
136
  }
137
137
 
138
138
  if ((size_t)n >= buf->asize - buf->size) {
139
- if (bufgrow(buf, buf->size + n + 1) < BUF_OK)
139
+ if (bufgrow(buf, buf->size + n + 1) < 0)
140
140
  return;
141
141
 
142
142
  va_start(ap, fmt);
@@ -156,7 +156,7 @@ bufput(struct buf *buf, const void *data, size_t len)
156
156
  {
157
157
  assert(buf && buf->unit);
158
158
 
159
- if (buf->size + len > buf->asize && bufgrow(buf, buf->size + len) < BUF_OK)
159
+ if (buf->size + len > buf->asize && bufgrow(buf, buf->size + len) < 0)
160
160
  return;
161
161
 
162
162
  memcpy(buf->data + buf->size, data, len);
@@ -177,7 +177,7 @@ bufputc(struct buf *buf, int c)
177
177
  {
178
178
  assert(buf && buf->unit);
179
179
 
180
- if (buf->size + 1 > buf->asize && bufgrow(buf, buf->size + 1) < BUF_OK)
180
+ if (buf->size + 1 > buf->asize && bufgrow(buf, buf->size + 1) < 0)
181
181
  return;
182
182
 
183
183
  buf->data[buf->size] = c;
@@ -55,7 +55,7 @@ int bufgrow(struct buf *, size_t);
55
55
  struct buf *bufnew(size_t) __attribute__ ((malloc));
56
56
 
57
57
  /* bufnullterm: NUL-termination of the string array (making a C-string) */
58
- const char *bufcstr(const struct buf *);
58
+ const char *bufcstr(struct buf *);
59
59
 
60
60
  /* bufprefix: compare the beginning of a buffer with a string */
61
61
  int bufprefix(const struct buf *buf, const char *prefix);
@@ -125,9 +125,13 @@ static VALUE rb_greenmat_md_render(VALUE self, VALUE text)
125
125
  if (NIL_P(text))
126
126
  return Qnil;
127
127
 
128
- struct rb_greenmat_rndr *renderer;
129
- Data_Get_Struct(rb_rndr, struct rb_greenmat_rndr, renderer);
130
- renderer->options.active_enc = rb_enc_get(text);
128
+ #ifdef HAVE_RUBY_ENCODING_H
129
+ {
130
+ struct rb_greenmat_rndr *renderer;
131
+ Data_Get_Struct(rb_rndr, struct rb_greenmat_rndr, renderer);
132
+ renderer->options.active_enc = rb_enc_get(text);
133
+ }
134
+ #endif
131
135
 
132
136
  /* initialize buffers */
133
137
  output_buf = bufnew(128);
@@ -472,30 +472,25 @@ static VALUE rb_greenmat_html_init(int argc, VALUE *argv, VALUE self)
472
472
  static VALUE rb_greenmat_htmltoc_init(int argc, VALUE *argv, VALUE self)
473
473
  {
474
474
  struct rb_greenmat_rndr *rndr;
475
- unsigned int render_flags = HTML_TOC;
476
- VALUE hash, nesting_level = Qnil;
475
+ int nesting_level = 6;
476
+ VALUE hash, key = Qnil;
477
477
 
478
478
  Data_Get_Struct(self, struct rb_greenmat_rndr, rndr);
479
479
 
480
480
  if (rb_scan_args(argc, argv, "01", &hash) == 1) {
481
481
  Check_Type(hash, T_HASH);
482
482
 
483
- /* escape_html */
484
- if (rb_hash_aref(hash, CSTR2SYM("escape_html")) == Qtrue)
485
- render_flags |= HTML_ESCAPE;
483
+ key = CSTR2SYM("nesting_level");
486
484
 
487
- /* Nesting level */
488
- nesting_level = rb_hash_aref(hash, CSTR2SYM("nesting_level"));
485
+ if (RTEST(rb_hash_aref(hash, key))) {
486
+ Check_Type(rb_hash_aref(hash, key), T_FIXNUM);
487
+ nesting_level = NUM2INT(rb_hash_aref(hash, key));
488
+ }
489
489
  }
490
490
 
491
- sdhtml_toc_renderer(&rndr->callbacks, (struct html_renderopt *)&rndr->options.html, render_flags);
491
+ sdhtml_toc_renderer(&rndr->callbacks, (struct html_renderopt *)&rndr->options.html, nesting_level);
492
492
  rb_greenmat__overload(self, rb_cRenderHTML_TOC);
493
493
 
494
- if (!(NIL_P(nesting_level)))
495
- rndr->options.html.toc_data.nesting_level = NUM2INT(nesting_level);
496
- else
497
- rndr->options.html.toc_data.nesting_level = 6;
498
-
499
494
  return Qnil;
500
495
  }
501
496
 
@@ -17,6 +17,7 @@
17
17
 
18
18
  #include "markdown.h"
19
19
  #include "html.h"
20
+ #include "ruby.h"
20
21
  #include <string.h>
21
22
  #include <stdlib.h>
22
23
  #include <stdio.h>
@@ -124,7 +125,7 @@ rndr_blockcode(struct buf *ob, const struct buf *text, const struct buf *lang, v
124
125
  if (lang && lang->size) {
125
126
  size_t i, cls;
126
127
  if (options->flags & HTML_PRETTIFY) {
127
- BUFPUTSL(ob, "<pre><code class=\"prettyprint lang-");
128
+ BUFPUTSL(ob, "<pre><code class=\"prettyprint ");
128
129
  cls++;
129
130
  } else {
130
131
  BUFPUTSL(ob, "<pre><code class=\"");
@@ -264,51 +265,17 @@ rndr_linebreak(struct buf *ob, void *opaque)
264
265
  return 1;
265
266
  }
266
267
 
267
- char *header_anchor(const struct buf *buffer)
268
+ char *header_anchor(struct buf *text)
268
269
  {
269
- size_t i, j, k, size = buffer->size;
270
+ VALUE str = rb_str_new2(bufcstr(text));
271
+ VALUE space_regex = rb_reg_new(" +", 2 /* length */, 0);
272
+ VALUE tags_regex = rb_reg_new("<\\/?[^>]*>", 10, 0);
270
273
 
271
- char text[size];
272
- strcpy(text, bufcstr(buffer));
274
+ VALUE heading = rb_funcall(str, rb_intern("gsub"), 2, space_regex, rb_str_new2("-"));
275
+ heading = rb_funcall(heading, rb_intern("gsub"), 2, tags_regex, rb_str_new2(""));
276
+ heading = rb_funcall(heading, rb_intern("downcase"), 0);
273
277
 
274
- char raw_string[size];
275
-
276
- /* Strip down the inline HTML markup if needed */
277
- if (strchr(text, '<') < strchr(text, '>')) {
278
- char* part = strtok(text, "<>");
279
-
280
- /* Once every two times, the yielded token is the
281
- content of a HTML tag so we don't need to copy it */
282
- for (k = 0; part != NULL; k++) {
283
- if (k == 0)
284
- strcpy(raw_string, part);
285
- else if (k % 2 == 0)
286
- strcat(raw_string, part);
287
-
288
- part = strtok(NULL, "<>");
289
- }
290
-
291
- size = strlen(raw_string);
292
- } else {
293
- strcpy(raw_string, text);
294
- }
295
-
296
- char* heading = malloc(size * sizeof(char));
297
-
298
- /* Dasherize the string removing extra white spaces
299
- and stripped chars */
300
- for (i = 0, j = 0; i < size; ++i, ++j) {
301
- while ((i+1) < size && STRIPPED_CHAR(raw_string[i]) && STRIPPED_CHAR(raw_string[i+1]))
302
- i++;
303
-
304
- if (STRIPPED_CHAR(raw_string[i]))
305
- heading[j] = '-';
306
- else
307
- heading[j] = tolower(raw_string[i]);
308
- }
309
-
310
- heading[j++] = '\0';
311
- return heading;
278
+ return StringValueCStr(heading);
312
279
  }
313
280
 
314
281
  static void
@@ -428,30 +395,15 @@ rndr_paragraph(struct buf *ob, const struct buf *text, void *opaque)
428
395
  static void
429
396
  rndr_raw_block(struct buf *ob, const struct buf *text, void *opaque)
430
397
  {
431
- size_t org, size;
432
- struct html_renderopt *options = opaque;
433
-
434
- if (!text)
435
- return;
436
-
437
- size = text->size;
438
- while (size > 0 && text->data[size - 1] == '\n')
439
- size--;
440
-
441
- for (org = 0; org < size && text->data[org] == '\n'; ++org)
442
-
443
- if (org >= size)
444
- return;
445
-
446
- /* Remove style tags if the `:no_styles` option is enabled */
447
- if ((options->flags & HTML_SKIP_STYLE) != 0 &&
448
- sdhtml_is_tag(text->data, size, "style"))
449
- return;
450
-
451
- if (ob->size)
452
- bufputc(ob, '\n');
453
-
454
- bufput(ob, text->data + org, size - org);
398
+ size_t org, sz;
399
+ if (!text) return;
400
+ sz = text->size;
401
+ while (sz > 0 && text->data[sz - 1] == '\n') sz--;
402
+ org = 0;
403
+ while (org < sz && text->data[org] == '\n') org++;
404
+ if (org >= sz) return;
405
+ if (ob->size) bufputc(ob, '\n');
406
+ bufput(ob, text->data + org, sz - org);
455
407
  bufputc(ob, '\n');
456
408
  }
457
409
 
@@ -477,9 +429,7 @@ static int
477
429
  rndr_image(struct buf *ob, const struct buf *link, const struct buf *title, const struct buf *alt, void *opaque)
478
430
  {
479
431
  struct html_renderopt *options = opaque;
480
-
481
- if (link != NULL && (options->flags & HTML_SAFELINK) != 0 && !sd_autolink_issafe(link->data, link->size))
482
- return 0;
432
+ if (!link || !link->size) return 0;
483
433
 
484
434
  BUFPUTSL(ob, "<img src=\"");
485
435
  escape_href(ob, link->data, link->size);
@@ -502,7 +452,7 @@ rndr_raw_html(struct buf *ob, const struct buf *text, void *opaque)
502
452
  struct html_renderopt *options = opaque;
503
453
 
504
454
  /* HTML_ESCAPE overrides SKIP_HTML, SKIP_STYLE, SKIP_LINKS and SKIP_IMAGES
505
- It doesn't see if there are any valid tags, just escape all of them. */
455
+ * It doens't see if there are any valid tags, just escape all of them. */
506
456
  if((options->flags & HTML_ESCAPE) != 0) {
507
457
  escape_html(ob, text->data, text->size);
508
458
  return 1;
@@ -686,14 +636,7 @@ toc_header(struct buf *ob, const struct buf *text, int level, void *opaque)
686
636
  }
687
637
 
688
638
  bufprintf(ob, "<a href=\"#%s\">", header_anchor(text));
689
-
690
- if (text) {
691
- if (options->flags & HTML_ESCAPE)
692
- escape_html(ob, text->data, text->size);
693
- else
694
- bufput(ob, text->data, text->size);
695
- }
696
-
639
+ if (text) escape_html(ob, text->data, text->size);
697
640
  BUFPUTSL(ob, "</a>\n");
698
641
  }
699
642
  }
@@ -718,7 +661,7 @@ toc_finalize(struct buf *ob, void *opaque)
718
661
  }
719
662
 
720
663
  void
721
- sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options, unsigned int render_flags)
664
+ sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options, int nesting_level)
722
665
  {
723
666
  static const struct sd_callbacks cb_default = {
724
667
  NULL,
@@ -759,7 +702,8 @@ sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *optio
759
702
  };
760
703
 
761
704
  memset(options, 0x0, sizeof(struct html_renderopt));
762
- options->flags = render_flags;
705
+ options->flags = HTML_TOC;
706
+ options->toc_data.nesting_level = nesting_level;
763
707
 
764
708
  memcpy(callbacks, &cb_default, sizeof(struct sd_callbacks));
765
709
  }
@@ -65,17 +65,11 @@ extern void
65
65
  sdhtml_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr, unsigned int render_flags);
66
66
 
67
67
  extern void
68
- sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr, unsigned int render_flags);
68
+ sdhtml_toc_renderer(struct sd_callbacks *callbacks, struct html_renderopt *options_ptr, int nesting_level);
69
69
 
70
70
  extern void
71
71
  sdhtml_smartypants(struct buf *ob, const uint8_t *text, size_t size);
72
72
 
73
- /* header method used internally in Greenmat */
74
- char *header_anchor(const struct buf *buffer);
75
-
76
- #define STRIPPED_CHARS " -&+$,/:;=?@\"#{}|^~[]`\\*()%.!'"
77
- #define STRIPPED_CHAR(x) (strchr(STRIPPED_CHARS, x) != NULL)
78
-
79
73
  #ifdef __cplusplus
80
74
  }
81
75
  #endif
@@ -1176,7 +1176,7 @@ char_link(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t offset
1176
1176
  title_e--;
1177
1177
 
1178
1178
  /* checking for closing quote presence */
1179
- if (data[title_e] != '\'' && data[title_e] != '"') {
1179
+ if (data[title_e] != '\'' && data[title_e] != '"') {
1180
1180
  title_b = title_e = 0;
1181
1181
  link_e = i;
1182
1182
  }
@@ -1605,7 +1605,7 @@ prefix_oli(uint8_t *data, size_t size)
1605
1605
  return i + 2;
1606
1606
  }
1607
1607
 
1608
- /* prefix_uli • returns unordered list item prefix */
1608
+ /* prefix_uli • returns ordered list item prefix */
1609
1609
  static size_t
1610
1610
  prefix_uli(uint8_t *data, size_t size)
1611
1611
  {
@@ -1677,7 +1677,7 @@ parse_blockquote(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t
1677
1677
  static size_t
1678
1678
  parse_htmlblock(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size, int do_render);
1679
1679
 
1680
- /* parse_paragraph • handles parsing of a regular paragraph */
1680
+ /* parse_blockquote • handles parsing of a regular paragraph */
1681
1681
  static size_t
1682
1682
  parse_paragraph(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t size)
1683
1683
  {
@@ -2858,7 +2858,6 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
2858
2858
 
2859
2859
  struct buf *text;
2860
2860
  size_t beg, end;
2861
- int in_fence = 0;
2862
2861
 
2863
2862
  text = bufnew(64);
2864
2863
  if (!text)
@@ -2870,8 +2869,7 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
2870
2869
  /* reset the references table */
2871
2870
  memset(&md->refs, 0x0, REF_TABLE_SIZE * sizeof(void *));
2872
2871
 
2873
- int footnotes_enabled = md->ext_flags & MKDEXT_FOOTNOTES;
2874
- int codefences_enabled = md->ext_flags & MKDEXT_FENCED_CODE;
2872
+ int footnotes_enabled = md->ext_flags & MKDEXT_FOOTNOTES;
2875
2873
 
2876
2874
  /* reset the footnotes lists */
2877
2875
  if (footnotes_enabled) {
@@ -2887,13 +2885,10 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
2887
2885
  if (doc_size >= 3 && memcmp(document, UTF8_BOM, 3) == 0)
2888
2886
  beg += 3;
2889
2887
 
2890
- while (beg < doc_size) { /* iterating over lines */
2891
- if (codefences_enabled && (is_codefence(document + beg, doc_size - beg, NULL) != 0))
2892
- in_fence = !in_fence;
2893
-
2894
- if (!in_fence && footnotes_enabled && is_footnote(document, beg, doc_size, &end, &md->footnotes_found))
2888
+ while (beg < doc_size) /* iterating over lines */
2889
+ if (footnotes_enabled && is_footnote(document, beg, doc_size, &end, &md->footnotes_found))
2895
2890
  beg = end;
2896
- else if (!in_fence && is_ref(document, beg, doc_size, &end, md->refs))
2891
+ else if (is_ref(document, beg, doc_size, &end, md->refs))
2897
2892
  beg = end;
2898
2893
  else { /* skipping to the next line */
2899
2894
  end = beg;
@@ -2913,7 +2908,6 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
2913
2908
 
2914
2909
  beg = end;
2915
2910
  }
2916
- }
2917
2911
 
2918
2912
  /* pre-grow the output buffer to minimize allocations */
2919
2913
  bufgrow(ob, MARKDOWN_GROW(text->size));
@@ -2924,7 +2918,7 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
2924
2918
 
2925
2919
  if (text->size) {
2926
2920
  /* adding a final newline if not already present */
2927
- if (text->data[text->size - 1] != '\n' && text->data[text->size - 1] != '\r')
2921
+ if (text->data[text->size - 1] != '\n' && text->data[text->size - 1] != '\r')
2928
2922
  bufputc(text, '\n');
2929
2923
 
2930
2924
  parse_block(ob, md, text->data, text->size);
@@ -2937,9 +2931,6 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
2937
2931
  if (md->cb.doc_footer)
2938
2932
  md->cb.doc_footer(ob, md->opaque);
2939
2933
 
2940
- /* Null-terminate the buffer */
2941
- bufcstr(ob);
2942
-
2943
2934
  /* clean-up */
2944
2935
  bufrelease(text);
2945
2936
  free_link_refs(md->refs);