elisp2any 0.0.6 → 0.0.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88e1120d995f02da6d5637763a9c2ddfa5bb5977b6abdab9b49fd03b69914bac
4
- data.tar.gz: 9de0696b8af77040f5dd0655b5f5fc9c9a8d069b8329c2877961e1e43a7011a6
3
+ metadata.gz: 5a54442cefe9baee337378b71628c7aa3cf833f38028ea8517f56434b58f9a79
4
+ data.tar.gz: 3ab64eaec627d61736f14ca559d93a56d547bd21eb3b38ee6829d2f9158a84ca
5
5
  SHA512:
6
- metadata.gz: cb7a79d6801f2c0b21a61a98fbdf8e965a423ba67e7988f598d9ab6d9f9e390cb361028e7ced4f2fce501234308aa0d0d43adc283dde1499765b6e12254fab8d
7
- data.tar.gz: c955943ac810407fedededfa1ef075108ac54b66b4f7ed6ed07518337f8903f79c92a3e39c177c29d3378c91181cd12e8ad3c6fc13fb03fd272a44345447e629
6
+ metadata.gz: 17dcdd8ccaf1fb4295a32460ef1f5da6e4fcf65ecfdffca48bf53929aa4fa2fa4b6e1c10d00a1d03feec458a8231e28ee63316bff99fa77441f836c456a44a4d
7
+ data.tar.gz: bbcad198b0eaf48a010c72ff7c1b4f717caed16ec2386c48f670f858241a74bdd53b883bb7166dc394eba57de9bbf146321143dbadcfa34ba27815f0d43606c8
data/.envrc ADDED
@@ -0,0 +1,2 @@
1
+ layout ruby
2
+ export LANG=en_US.UTF-8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Change log of Elisp2any
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.0.8 - 2026-08-05
6
+
7
+ * Add JSON format option (for the Pandoc JSON filter).
8
+
9
+ ## 0.0.7 - 2026-07-05
10
+
11
+ * Add dump flag for debugging.
12
+ * Allow other variables in headers.
13
+ * Remove sidebar.
14
+ * Markup copyright mark.
15
+ * Hide navigation by default.
16
+ * Add comments for command line options.
17
+
3
18
  ## 0.0.6 - 2025-06-22
4
19
 
5
20
  Removed previous parsers and added simple strscan base parser.
data/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Elisp2any
2
2
 
3
+ Project links: [repository](https://codeberg.org/gemmaro/elisp2any),
4
+ [mirror](https://github.com/gemmaro/elisp2any), [RubyGems.org][rg].
5
+
6
+ [rg]: https://rubygems.org/gems/elisp2any
7
+
3
8
  ## Installation
4
9
 
5
10
  Install the gem and add to the application's `Gemfile` by executing `bundle add elisp2any`.
@@ -7,14 +12,33 @@ If Bundler is not being used to manage dependencies, install the gem by executin
7
12
 
8
13
  ## Usage
9
14
 
10
- Currently the command line tool only supports HTML rendering.
15
+ Currently the command line tool supports HTML (default) and JSON rendering.
11
16
 
12
17
  ```shell
13
18
  $ elisp2any --input /path/to/input/file --output /path/to/output/file
14
19
  ```
15
20
 
21
+ The JSON is based off the [Pandoc JSON filter][pf]. The example usage
22
+ is:
23
+
24
+ ```shell
25
+ ./exe/elisp2any \
26
+ --format=json \
27
+ < ~/.emacs.d/init.el \
28
+ | pandoc \
29
+ --from=json \
30
+ --standalone \
31
+ > /tmp/index.html
32
+ ```
33
+
34
+ [pf]: https://pandoc.org/filters.html
35
+
16
36
  ## Development
17
37
 
38
+ To run tests, run `test-unit` (or `rake test`).
39
+
40
+ There are a few notes about the terms.
41
+
18
42
  Two paragraph types:
19
43
 
20
44
  ```emacs-lisp
@@ -38,5 +62,17 @@ Bug reports and pull requests are welcome.
38
62
 
39
63
  ## License
40
64
 
41
- This package is provided under the Apache License.
42
- See `LICENSE.txt` for details.
65
+ Copyright (C) 2025, 2026 gemmaro
66
+
67
+ This program is free software: you can redistribute it and/or modify
68
+ it under the terms of the GNU General Public License as published by
69
+ the Free Software Foundation, either version 3 of the License, or
70
+ (at your option) any later version.
71
+
72
+ This program is distributed in the hope that it will be useful,
73
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
74
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
75
+ GNU General Public License for more details.
76
+
77
+ You should have received a copy of the GNU General Public License
78
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
data/Rakefile CHANGED
@@ -1,10 +1,16 @@
1
1
  require 'bundler/gem_tasks'
2
- require 'rake/testtask'
2
+ require "open3"
3
3
 
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << 'test'
6
- t.libs << 'lib'
7
- t.test_files = FileList['test/**/*_test.rb']
4
+ task :deploy do
5
+ out, _stat = Open3.capture2("./bin/dev")
6
+ File.write("/tmp/index.html", out)
7
+ sh "rsync", "-av", "/tmp/index.html", "pi:/srv/www/emacs/"
8
8
  end
9
9
 
10
- task default: :test
10
+ require "rake/testtask"
11
+
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << "test"
14
+ t.libs << "lib"
15
+ t.test_files = FileList["test/**/*_test.rb"]
16
+ end
data/exe/elisp2any CHANGED
@@ -1,5 +1,20 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # Copyright (C) 2025, 2026 gemmaro
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
3
18
  require 'optparse'
4
19
 
5
20
  $LOAD_PATH << File.join(__dir__, "../lib")
@@ -8,19 +23,45 @@ require 'elisp'
8
23
  input = $stdin
9
24
  output = $stdout
10
25
  css = nil
26
+ dump = false
27
+ format = :html
11
28
 
12
29
  OptionParser.new do |parser|
13
- parser.on('--input=PATH') do |path|
30
+ parser.on('--input=PATH', 'ELisp file (default: stdin)') do |path|
14
31
  input = File.open(path)
15
32
  end
16
33
 
17
- parser.on('--output=PATH') do |path|
34
+ parser.on('--output=PATH', 'HTML file (default: stdout)') do |path|
18
35
  output = File.open(path, 'w')
19
36
  end
20
37
 
21
- parser.on('--css=PATH') do |path|
38
+ parser.on('--css=PATH', 'stylesheet') do |path|
22
39
  css = path
23
40
  end
41
+
42
+ parser.on("--dump", 'For debug purpose') { dump = true }
43
+
44
+ parser.on("--format=NAME", "'json' is for Pandoc JSON filter. Default: HTML") do |name|
45
+ case name
46
+ in "html"
47
+ format = :html
48
+ in "json"
49
+ format = :json
50
+ end
51
+ end
24
52
  end.parse!
25
53
 
26
- Elisp.parse(input).write(output, css:)
54
+ css && format != :html and raise "CSS option is for HTML only"
55
+
56
+ doc = Elisp.parse(input)
57
+ if dump
58
+ pp doc
59
+ exit
60
+ end
61
+
62
+ case format
63
+ in :html
64
+ doc.write(output, css:)
65
+ in :json
66
+ doc.write_pandoc_json(output)
67
+ end
data/lib/elisp/comment.rb CHANGED
@@ -1,3 +1,18 @@
1
+ # Copyright (C) 2025, 2026 gemmaro
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
1
16
  class Elisp::Comment
2
17
  def initialize(content)
3
18
  @content = content
@@ -10,84 +25,4 @@ class Elisp::Comment
10
25
  def to_minor_para
11
26
  Elisp::MinorPara.new(@content)
12
27
  end
13
-
14
- def html
15
- self.class.parse(@content)
16
- end
17
- end
18
-
19
- class Elisp::DocStringParser
20
- URI_PATTERN = URI.regexp
21
- URI_PATTERN_WITH_ANGLES = /[<](?<uri>#{ URI_PATTERN })[>]/
22
- EMAIL = Regexp.new(URI::MailTo::EMAIL_REGEXP.to_s
23
- .sub(/\A[(][?]-mix:\\A/, "(?-mix:")
24
- .sub(/\\z[)]\z/, ")"))
25
- EMAIL_WITH_ANGLES = /[<](?<address>#{ EMAIL })[>]/
26
-
27
- def initialize(source)
28
- @scanner = StringScanner.new(source)
29
- end
30
-
31
- def html
32
- result = +""
33
- normal = +""
34
- until @scanner.eos?
35
- if @scanner.skip(/[`](?<code>[A-Za-z!.-]+)[']/)
36
- result << normal
37
- normal.clear
38
- code = CGI.escape_html(@scanner[:code])
39
- result << "<code>#{code}</code>"
40
- normal.clear
41
- elsif @scanner.skip(/\\\\=(?<quote>['`])/)
42
- normal << @scanner[:quote]
43
- elsif (uri = scan_http_uri)
44
- result << normal
45
- normal.clear
46
- result << html_url(uri)
47
- elsif (address = scan_email)
48
- result << normal
49
- normal.clear
50
- uri = CGI.escape(address)
51
- address = CGI.escape_html(address)
52
- result << %(<a href="mailto:#{uri}">#{address}</a>)
53
- else
54
- normal << @scanner.getch
55
- end
56
- end
57
- result << normal
58
- normal.clear
59
- result
60
- end
61
-
62
- def scan_email
63
- if (address = @scanner.scan(EMAIL))
64
- address
65
- elsif @scanner.skip(EMAIL_WITH_ANGLES)
66
- @scanner[:address]
67
- else
68
- return
69
- end
70
- end
71
-
72
- def scan_http_uri
73
- if (uri = @scanner.scan(URI_PATTERN))
74
- elsif @scanner.skip(URI_PATTERN_WITH_ANGLES)
75
- uri = @scanner[:uri]
76
- else
77
- return
78
- end
79
- uri = URI(uri)
80
- unless uri.is_a?(URI::HTTP)
81
- @scanner.unscan
82
- return
83
- end
84
- uri
85
- end
86
-
87
- def html_url(url)
88
- url = url.to_s
89
- ref = CGI.escape(url)
90
- label = CGI.escape_html(url)
91
- %(<a class="pure-url" href="#{ref}">#{label}</a>)
92
- end
93
28
  end
@@ -0,0 +1,11 @@
1
+ .description {
2
+ font-style: italic;
3
+ }
4
+ pre {
5
+ font-family: serif;
6
+ white-space: pre-wrap;
7
+ }
8
+ pre[class="code"] {
9
+ border-left: 2px solid black;
10
+ padding-left: 0.5rem;
11
+ }
@@ -0,0 +1,217 @@
1
+ # Copyright (C) 2025, 2026 gemmaro
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require "cgi/escape"
17
+ require "uri"
18
+ require "strscan"
19
+
20
+ class Elisp::DocStringParser
21
+ URI_PATTERN = URI::RFC2396_PARSER.make_regexp
22
+ URI_PATTERN_WITH_ANGLES = /[<](?<uri>#{ URI_PATTERN })[>]/
23
+ EMAIL = Regexp.new(URI::MailTo::EMAIL_REGEXP.to_s
24
+ .sub(/\A[(][?]-mix:\\A/, "(?-mix:")
25
+ .sub(/\\z[)]\z/, ")"))
26
+ EMAIL_WITH_ANGLES = /[<](?<address>#{ EMAIL })[>]/
27
+
28
+ def initialize(source)
29
+ @scanner = StringScanner.new(source)
30
+ end
31
+
32
+ def html
33
+ parse.map(&:html).join
34
+ end
35
+
36
+ def pandoc_json_objects
37
+ parse.map(&:pandoc_json_object)
38
+ end
39
+
40
+ private
41
+
42
+ def parse
43
+ result = []
44
+ normal = +""
45
+ until @scanner.eos?
46
+ if @scanner.skip(/[`](?<code>[A-Za-z!.-]+)[']/)
47
+ result << Text.new(normal)
48
+ normal = +""
49
+ result << Code.new(@scanner[:code])
50
+
51
+ elsif @scanner.skip(/\\\\=(?<quote>['`])/)
52
+ normal << @scanner[:quote]
53
+
54
+ elsif (uri = scan_http_uri)
55
+ result << Text.new(normal)
56
+ normal = +""
57
+ result << HTMLURL.new(uri)
58
+
59
+ elsif (address = scan_email)
60
+ result << Text.new(normal)
61
+ normal = +""
62
+ result << Email.new(address)
63
+
64
+ elsif @scanner.skip(/Copyright [(]C[)]/)
65
+ result << Text.new(normal << "Copyright ") << Copyright.new
66
+ normal = +""
67
+
68
+ else
69
+ normal << @scanner.getch
70
+ end
71
+ end
72
+
73
+ result << Text.new(normal)
74
+ result
75
+ end
76
+
77
+ private
78
+
79
+ def scan_email
80
+ if (address = @scanner.scan(EMAIL))
81
+ address
82
+ elsif @scanner.skip(EMAIL_WITH_ANGLES)
83
+ @scanner[:address]
84
+ else
85
+ return
86
+ end
87
+ end
88
+
89
+ def scan_http_uri
90
+ if (uri = @scanner.scan(URI_PATTERN))
91
+ elsif @scanner.skip(URI_PATTERN_WITH_ANGLES)
92
+ uri = @scanner[:uri]
93
+ else
94
+ return
95
+ end
96
+ uri = URI(uri)
97
+ unless uri.is_a?(URI::HTTP)
98
+ @scanner.unscan
99
+ return
100
+ end
101
+ uri
102
+ end
103
+
104
+ class Text
105
+ def initialize(content)
106
+ @content = content
107
+ end
108
+
109
+ def html
110
+ CGI.escape_html(@content)
111
+ end
112
+
113
+ def pandoc_json_object
114
+ {
115
+ "t": "Str",
116
+ "c": @content,
117
+ }
118
+ end
119
+ end
120
+
121
+ class Code
122
+ def initialize(content)
123
+ @content = content
124
+ end
125
+
126
+ def html
127
+ code = CGI.escape_html(@content)
128
+ "<code>#{code}</code>"
129
+ end
130
+
131
+ def pandoc_json_object
132
+ {
133
+ "t": "Code",
134
+ "c": [
135
+ ["", [], []],
136
+ @content,
137
+ ]
138
+ }
139
+ end
140
+ end
141
+
142
+ class HTMLURL
143
+ def initialize(content)
144
+ @content = content
145
+ end
146
+
147
+ def html
148
+ url = @content.to_s
149
+ ref = CGI.escape(url)
150
+ label = CGI.escape_html(url)
151
+ %(<a class="pure-url" href="#{ref}">#{label}</a>)
152
+ end
153
+
154
+ def pandoc_json_object
155
+ {
156
+ "t": "Link",
157
+ "c": [
158
+ ["", ["uri"], []],
159
+ [
160
+ {
161
+ "t": "Str",
162
+ "c": @content,
163
+ }
164
+ ],
165
+ [
166
+ @content,
167
+ "",
168
+ ],
169
+ ],
170
+ }
171
+ end
172
+ end
173
+
174
+ class Email
175
+ def initialize(content)
176
+ @content = content
177
+ end
178
+
179
+ def html
180
+ uri = CGI.escape(@content)
181
+ address = CGI.escape_html(@content)
182
+ %(<a href="mailto:#{uri}">#{address}</a>)
183
+ end
184
+
185
+ def pandoc_json_object
186
+ {
187
+ "t": "Link",
188
+ "c": [
189
+ ["", ["email"], []],
190
+ [
191
+ {
192
+ "t": "Str",
193
+ "c": @content,
194
+ }
195
+ ],
196
+ [
197
+ "mailto:#{@content}",
198
+ "",
199
+ ],
200
+ ],
201
+ }
202
+ end
203
+ end
204
+
205
+ class Copyright
206
+ def html
207
+ "&copy;"
208
+ end
209
+
210
+ def pandoc_json_object
211
+ {
212
+ "t": "Str",
213
+ "c": "©",
214
+ }
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,47 @@
1
+ # Copyright (C) 2025, 2026 gemmaro
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require "cgi/escape"
17
+
18
+ class Elisp::Heading
19
+ attr_reader :level, :content
20
+
21
+ def initialize(content, level: 2)
22
+ @content = content
23
+ @level = level
24
+ end
25
+
26
+ def html
27
+ name = "h#{@level}"
28
+ content = CGI.escape_html(@content)
29
+ "<#{name}>#{content}</#{name}>"
30
+ end
31
+
32
+ def pandoc_json_object
33
+ {
34
+ "t": "Header",
35
+ "c": [
36
+ @level,
37
+ [@content, [], []],
38
+ [
39
+ {
40
+ "t": "Str",
41
+ "c": @content,
42
+ },
43
+ ],
44
+ ]
45
+ }
46
+ end
47
+ end
data/lib/elisp/parser.rb CHANGED
@@ -1,8 +1,26 @@
1
+ # Copyright (C) 2025, 2026 gemmaro
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require "json"
17
+ require "strscan"
18
+
1
19
  class Elisp::Parser
2
20
  HEADER_LINE = %r{
3
21
  ;;;[ ]
4
22
  (?<title>[a-z]+[.]el)[ ]---[ ](?<desc>.*?)
5
- (?:[ ]+-[*]-[ ](?<vars>lexical-binding:[ ]?t);?[ ]-[*]-)?
23
+ (?:[ ]+-[*]-[ ]*(?<vars>.*?)[ ]*-[*]-)?
6
24
  \n+
7
25
  }x
8
26
  COMMENT = / *;; (?<comment>.*)\n/
@@ -20,45 +38,10 @@ class Elisp::Parser
20
38
  }x
21
39
  BLANKLINES = /\n+/
22
40
  CODE_OR_BLANKLINES = Regexp.union(CODE, BLANKLINES)
23
- DEFAULT_CSS = <<~END_CSS
24
- <style>
25
- :root {
26
- --sidebar-width: 200px;
27
- }
28
- body {
29
- display: flex;
30
- }
31
- main {
32
- max-width: 80rem;
33
- margin: auto auto auto var(--sidebar-width);
34
- padding-left: 0.5rem;
35
- border-box: box-sizing;
36
- }
37
- .sidebar {
38
- padding-right: 0.5rem;
39
- width: var(--sidebar-width);
40
- position: fixed;
41
- boxed-sizing: border-box;
42
- overflow-y: auto;
43
- height: 100%;
44
- }
45
- .sidebar details ul {
46
- position: relative;
47
- left: -1.5rem;
48
- }
49
- .description {
50
- font-style: italic;
51
- }
52
- pre {
53
- font-family: serif;
54
- }
55
- pre[class="code"] {
56
- border-left: solid;
57
- padding-left: 0.5rem;
58
- white-space: pre-wrap;
59
- }
60
- </style>
61
- END_CSS
41
+
42
+ css = File.read(File.join(__dir__, "default.css"))
43
+ DEFAULT_CSS = "<style>#{css}</style>"
44
+
62
45
  HEADING = %r{
63
46
  ;;;(?<level>;*)[ ](?<title>.+)\n
64
47
  (?:(?:;;)?\n)*
@@ -132,7 +115,7 @@ class Elisp::Parser
132
115
  @nodes.last << @scanner[:content]
133
116
 
134
117
  elsif @state == :after_code && @scanner.skip(/\f\n+/)
135
- @nodes << PageDelimiter.new
118
+ @nodes << Elisp::PageDelimiter.new
136
119
  @state = :after_page_delimiter
137
120
 
138
121
  else
@@ -163,9 +146,6 @@ class Elisp::Parser
163
146
 
164
147
  def write(output, css: nil)
165
148
  css = css ? %(<link rel="stylesheet" href="#{CGI.escape(css)}">) : DEFAULT_CSS
166
- sidebar = Elisp::Sidebar.new(@nodes.select { |node| node.is_a?(Elisp::Heading) \
167
- && node.level >= 2 })
168
- .html
169
149
  body = @nodes.map(&:html).join("\n")
170
150
  output.write(<<~END_HTML)
171
151
  <!doctype html>
@@ -173,16 +153,25 @@ class Elisp::Parser
173
153
  <head>
174
154
  #{css}
175
155
  <meta charset="utf8">
156
+ <meta name="viewport" content="width=device-width, initial-scale=1">
176
157
  </head>
177
158
  <body>
178
- <nav class="sidebar">
179
- #{sidebar}
180
- </nav>
181
- <main>
182
- #{body}
183
- </main>
159
+ <main>#{body}</main>
184
160
  </body>
185
161
  </html>
186
162
  END_HTML
187
163
  end
164
+
165
+ def pandoc_json_object
166
+ {
167
+ "pandoc-api-version": [1,23,1,1],
168
+ "meta": {},
169
+ "blocks": @nodes.map(&:pandoc_json_object),
170
+ }
171
+ end
172
+
173
+ def write_pandoc_json(output)
174
+ json = JSON.dump(pandoc_json_object)
175
+ output.write(json)
176
+ end
188
177
  end