elisp2any 0.0.4 → 0.0.6

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/LICENSE.txt +674 -202
  4. data/README.md +15 -5
  5. data/Rakefile +1 -46
  6. data/exe/elisp2any +4 -5
  7. data/lib/elisp/comment.rb +93 -0
  8. data/lib/elisp/parser.rb +188 -0
  9. data/lib/elisp.rb +142 -0
  10. data/lib/elisp2any/aside.rb +23 -0
  11. data/lib/elisp2any/blanklines.rb +7 -4
  12. data/lib/elisp2any/code.rb +26 -12
  13. data/lib/elisp2any/codeblock.rb +56 -0
  14. data/lib/elisp2any/comment.rb +3 -1
  15. data/lib/elisp2any/commentary.rb +10 -7
  16. data/lib/elisp2any/expression.rb +81 -5
  17. data/lib/elisp2any/footer_line.rb +28 -0
  18. data/lib/elisp2any/header_line.rb +40 -7
  19. data/lib/elisp2any/heading.rb +21 -4
  20. data/lib/elisp2any/html_renderer/index.html.erb +16 -16
  21. data/lib/elisp2any/html_renderer.erb +22 -0
  22. data/lib/elisp2any/html_renderer.rb +98 -12
  23. data/lib/elisp2any/inline_code.rb +1 -0
  24. data/lib/elisp2any/line.rb +5 -3
  25. data/lib/elisp2any/paragraph.rb +3 -4
  26. data/lib/elisp2any/section.rb +32 -8
  27. data/lib/elisp2any/text.rb +33 -0
  28. data/lib/elisp2any/version.rb +2 -1
  29. data/lib/elisp2any.rb +19 -1
  30. metadata +14 -80
  31. data/.document +0 -4
  32. data/.envrc +0 -3
  33. data/.rdoc_options +0 -2
  34. data/.rubocop.yml +0 -11
  35. data/lib/elisp2any/asciidoc_renderer/index.adoc.erb +0 -15
  36. data/lib/elisp2any/asciidoc_renderer.rb +0 -57
  37. data/lib/elisp2any/code_block.rb +0 -16
  38. data/lib/elisp2any/file.rb +0 -47
  39. data/lib/elisp2any/filename.rb +0 -14
  40. data/lib/elisp2any/header_line_variable_assignment.rb +0 -27
  41. data/lib/elisp2any/header_line_variables.rb +0 -33
  42. data/lib/elisp2any/node.rb +0 -66
  43. data/lib/elisp2any/top_heading.rb +0 -21
  44. data/lib/elisp2any/tree_sitter_parser.rb +0 -36
  45. data/lib/elisp2any/variable.rb +0 -14
  46. data/manifest.scm +0 -87
  47. data/sig/elisp2any.gen.rbs +0 -112
  48. data/sig/elisp2any.rbs +0 -4
data/README.md CHANGED
@@ -15,12 +15,22 @@ $ elisp2any --input /path/to/input/file --output /path/to/output/file
15
15
 
16
16
  ## Development
17
17
 
18
- After checking out the repo, run `bin/setup` to install dependencies.
19
- Then, run `rake test` to run the tests.
20
- You can also run `bin/console` for an interactive prompt that will allow you to experiment.
18
+ Two paragraph types:
21
19
 
22
- To install this gem onto your local machine, run `bundle exec rake install`.
23
- To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
20
+ ```emacs-lisp
21
+ ;; This is a major one.
22
+
23
+ ;; This is another major one (1), and a minor one.
24
+ ;;
25
+ ;; This is still (1) but another minor one.
26
+ ```
27
+
28
+ Heading levels:
29
+
30
+ ```emacs-lisp
31
+ ;;; Level 2
32
+ ;;;; Level 3
33
+ ```
24
34
 
25
35
  ## Contributing
26
36
 
data/Rakefile CHANGED
@@ -7,49 +7,4 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.test_files = FileList['test/**/*_test.rb']
8
8
  end
9
9
 
10
- default_tasks = %i[test]
11
-
12
- rubocop = true
13
-
14
- begin
15
- require 'rubocop/rake_task'
16
- rescue LoadError
17
- rubocop = false
18
- end
19
-
20
- if rubocop
21
- RuboCop::RakeTask.new
22
- default_tasks << :rubocop
23
- end
24
-
25
- task default: default_tasks
26
-
27
- require 'rdoc/task'
28
- RDoc::Task.new do |rdoc|
29
- readme = 'README.md'
30
- rdoc.main = readme
31
- rdoc.rdoc_files.include(readme, 'lib/**/*.rb')
32
- end
33
-
34
- desc 'start API document server'
35
- task :serve_api do
36
- serve('html')
37
- end
38
-
39
- desc 'generate type signatures'
40
- task :sig do
41
- sh 'typeprof', *Dir['lib/**/*.rb'], 'sig/elisp2any.rbs', '-o', 'sig/elisp2any.gen.rbs'
42
- end
43
-
44
- desc 'start HTML fixture server'
45
- task :serve_fixture do
46
- serve('fixtures/init')
47
- end
48
-
49
- def serve(path)
50
- sh 'ruby', '-run', '-e', 'httpd', path
51
- end
52
-
53
- file 'fixtures/init/adoc/index.html' => 'fixtures/init/index.adoc' do |t|
54
- sh 'asciidoctor', t.source, '--out-file', t.name
55
- end
10
+ task default: :test
data/exe/elisp2any CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../lib/elisp2any'
4
- require_relative '../lib/elisp2any/html_renderer'
5
3
  require 'optparse'
6
4
 
5
+ $LOAD_PATH << File.join(__dir__, "../lib")
6
+ require 'elisp'
7
+
7
8
  input = $stdin
8
9
  output = $stdout
9
10
  css = nil
@@ -22,6 +23,4 @@ OptionParser.new do |parser|
22
23
  end
23
24
  end.parse!
24
25
 
25
- file = Elisp2any::File.parse(input)
26
- renderer = Elisp2any::HTMLRenderer.new(file, css:)
27
- output.write(renderer.render)
26
+ Elisp.parse(input).write(output, css:)
@@ -0,0 +1,93 @@
1
+ class Elisp::Comment
2
+ def initialize(content)
3
+ @content = content
4
+ end
5
+
6
+ def <<(content)
7
+ @content << "\n#{content}"
8
+ end
9
+
10
+ def to_minor_para
11
+ Elisp::MinorPara.new(@content)
12
+ 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
+ end
@@ -0,0 +1,188 @@
1
+ class Elisp::Parser
2
+ HEADER_LINE = %r{
3
+ ;;;[ ]
4
+ (?<title>[a-z]+[.]el)[ ]---[ ](?<desc>.*?)
5
+ (?:[ ]+-[*]-[ ](?<vars>lexical-binding:[ ]?t);?[ ]-[*]-)?
6
+ \n+
7
+ }x
8
+ COMMENT = / *;; (?<comment>.*)\n/
9
+ CODE = %r{
10
+ (?<content>
11
+ (?:
12
+ [A-Za-z0-9&,.:=?_#'`<>()\[\]\t +*/-]
13
+ | [?]\\.
14
+ | "(?:[^\\"]|\\(?:.|\n))*"
15
+ )+?
16
+ (?:[ ]*;.*)?
17
+ | ;;;[#][#][#]autoload.*
18
+ )
19
+ \n
20
+ }x
21
+ BLANKLINES = /\n+/
22
+ 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
62
+ HEADING = %r{
63
+ ;;;(?<level>;*)[ ](?<title>.+)\n
64
+ (?:(?:;;)?\n)*
65
+ }x
66
+
67
+ def initialize(input)
68
+ @scanner = StringScanner.new(input.read)
69
+ @state = :start
70
+ @nodes = []
71
+ end
72
+
73
+ def parse
74
+ until @scanner.eos?
75
+
76
+ if @state == :start && @scanner.skip(HEADER_LINE)
77
+ @nodes << Elisp::Heading.new(@scanner[:title], level: 1)
78
+ @nodes << Elisp::Desc.new(@scanner[:desc])
79
+ @nodes << Elisp::Variables.new(@scanner[:vars])
80
+ @state = :after_header_line
81
+
82
+ elsif [:after_header_line,
83
+ :after_major_para,
84
+ :after_minor_para,
85
+ :after_code,
86
+ :after_page_delimiter,
87
+ :after_heading].include?(@state) &&
88
+ @scanner.skip(HEADING)
89
+ level = @scanner[:level].size
90
+ title = @scanner[:title]
91
+ if level.zero?
92
+ if ["Commentary:", "Code:"].include?(title)
93
+ title.chop!
94
+ elsif title.match?(/[a-z]+[.]el ends here/)
95
+ next # validate file name?
96
+ end
97
+ end
98
+ @nodes << Elisp::Heading.new(title, level: level + 2)
99
+ @state = :after_heading
100
+
101
+ elsif [:after_heading,
102
+ :after_major_para,
103
+ :after_minor_para,
104
+ :after_code,
105
+ :after_header_line].include?(@state) &&
106
+ @scanner.skip(COMMENT)
107
+ @nodes << Elisp::Comment.new(@scanner[:comment])
108
+ @state = :after_comment
109
+
110
+ elsif @state == :after_comment && @scanner.skip(";;\n")
111
+ @nodes[-1] = @nodes.last.to_minor_para
112
+ @state = :after_minor_para
113
+
114
+ elsif @state == :after_comment && @scanner.skip(COMMENT)
115
+ @nodes.last << @scanner[:comment]
116
+
117
+ elsif @state == :after_comment && @scanner.skip(BLANKLINES)
118
+ make_major_para
119
+ @state = :after_major_para
120
+
121
+ elsif [:after_major_para, :after_page_delimiter, :after_heading].include?(@state) &&
122
+ @scanner.skip(CODE)
123
+ @nodes << Elisp::Code.new(@scanner[:content])
124
+ @state = :after_code
125
+
126
+ elsif @state == :after_comment && @scanner.skip(CODE)
127
+ make_major_para
128
+ @nodes << Elisp::Code.new(@scanner[:content])
129
+ @state = :after_code
130
+
131
+ elsif @state == :after_code && @scanner.skip(CODE_OR_BLANKLINES)
132
+ @nodes.last << @scanner[:content]
133
+
134
+ elsif @state == :after_code && @scanner.skip(/\f\n+/)
135
+ @nodes << PageDelimiter.new
136
+ @state = :after_page_delimiter
137
+
138
+ else
139
+ raise Elisp::Error, <<~MESSAGE
140
+ parse failed
141
+ --- rest line (#{@state}) ---
142
+ #{@scanner.rest.lines.first.inspect}
143
+ --- scanner ---
144
+ #{@scanner.inspect}
145
+ MESSAGE
146
+ end
147
+ end
148
+ self
149
+ end
150
+
151
+ def make_major_para
152
+ paras = [@nodes.pop.to_minor_para]
153
+ while (node = @nodes.pop)
154
+ if node.is_a?(Elisp::MinorPara)
155
+ paras.unshift(node)
156
+ else
157
+ @nodes.push(node)
158
+ break
159
+ end
160
+ end
161
+ @nodes << Elisp::MajorPara.new(paras)
162
+ end
163
+
164
+ def write(output, css: nil)
165
+ 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
+ body = @nodes.map(&:html).join("\n")
170
+ output.write(<<~END_HTML)
171
+ <!doctype html>
172
+ <html>
173
+ <head>
174
+ #{css}
175
+ <meta charset="utf8">
176
+ </head>
177
+ <body>
178
+ <nav class="sidebar">
179
+ #{sidebar}
180
+ </nav>
181
+ <main>
182
+ #{body}
183
+ </main>
184
+ </body>
185
+ </html>
186
+ END_HTML
187
+ end
188
+ end
data/lib/elisp.rb ADDED
@@ -0,0 +1,142 @@
1
+ require "strscan"
2
+ require "cgi"
3
+ require "uri"
4
+
5
+ class Elisp
6
+ Error = Class.new(StandardError)
7
+
8
+ def self.parse(input)
9
+ Elisp::Parser.new(input).parse
10
+ end
11
+ end
12
+
13
+ class Elisp::Sidebar
14
+ def initialize(headings)
15
+ @headings = headings
16
+ end
17
+
18
+ def html
19
+ rest = @headings.dup
20
+ result = +""
21
+ while (head = rest.shift)
22
+ level = head.level
23
+ subheadings = []
24
+ while (heading = rest.shift)
25
+ if heading.level > level
26
+ subheadings << heading
27
+ else
28
+ rest.unshift(heading)
29
+ break
30
+ end
31
+ end
32
+ sub_sidebar =
33
+ subheadings.empty? ? nil
34
+ : (html = self.class.new(subheadings).html
35
+ "<details open>#{html}</details>")
36
+ content = CGI.escape_html(head.content)
37
+ result << <<~END_HTML
38
+ <li>
39
+ <a href="##{head.html_id}">#{content}</a>
40
+ #{sub_sidebar}
41
+ </li>
42
+ END_HTML
43
+ end
44
+ "<ul>#{result}</ul>"
45
+ end
46
+ end
47
+
48
+ class PageDelimiter
49
+ def html = "<hr>"
50
+ end
51
+
52
+ class Elisp::Heading
53
+ attr_reader :level, :content
54
+
55
+ def initialize(content, level: 2)
56
+ @content = content
57
+ @level = level
58
+ end
59
+
60
+ def html
61
+ name = "h#{@level}"
62
+ content = CGI.escape_html(@content)
63
+ <<~END_HTML
64
+ <#{name} id="#{html_id}">
65
+ #{content}
66
+ <a href="##{html_id}">#</a>
67
+ </#{name}>
68
+ END_HTML
69
+ end
70
+
71
+ def html_id
72
+ content = CGI.escape(@content)
73
+ "#{@level}-#{content}"
74
+ end
75
+ end
76
+
77
+ class Elisp::Desc
78
+ def initialize(content)
79
+ @content = content
80
+ end
81
+
82
+ def html
83
+ content = CGI.escape_html(@content)
84
+ %(<p class="description">#{content}</p>)
85
+ end
86
+ end
87
+
88
+ class Elisp::Variables
89
+ def initialize(content)
90
+ @content = content
91
+ end
92
+
93
+ def html
94
+ content = CGI.escape_html(@content)
95
+ <<~END_HTML
96
+ <article>
97
+ Header line variables:
98
+ <pre><code>#{content}</code></pre>
99
+ </article>
100
+ END_HTML
101
+ end
102
+ end
103
+
104
+ class Elisp::MajorPara
105
+ def initialize(paras)
106
+ @paras = paras
107
+ end
108
+
109
+ def html
110
+ paras = @paras.map(&:html).join("\n")
111
+ "<article>#{paras}</article>"
112
+ end
113
+ end
114
+
115
+ class Elisp::MinorPara
116
+ def initialize(content)
117
+ @content = content
118
+ end
119
+
120
+ def html
121
+ content = Elisp::DocStringParser.new(@content).html
122
+ "<pre>#{content}</pre>"
123
+ end
124
+ end
125
+
126
+ class Elisp::Code
127
+ def initialize(content)
128
+ @content = content
129
+ end
130
+
131
+ def <<(content)
132
+ @content << "\n#{content}"
133
+ end
134
+
135
+ def html
136
+ content = CGI.escape_html(@content)
137
+ %(<pre class="code"><code>#{content}</code></pre>)
138
+ end
139
+ end
140
+
141
+ require_relative "elisp/comment"
142
+ require_relative "elisp/parser"
@@ -0,0 +1,23 @@
1
+ require "elisp2any/comment"
2
+
3
+ module Elisp2any
4
+ class Aside
5
+ def self.scan(scanner)
6
+ pos = scanner.pos
7
+ com = Comment.scan(scanner) or return
8
+ unless com.colons == 1
9
+ scanner.pos = pos
10
+ return
11
+ end
12
+ new(com.content)
13
+ end
14
+
15
+ def source
16
+ ";#{@content}\n"
17
+ end
18
+
19
+ def initialize(content)
20
+ @content = content
21
+ end
22
+ end
23
+ end
@@ -1,17 +1,20 @@
1
+ require "forwardable"
2
+
1
3
  module Elisp2any
2
4
  class Blanklines
3
- attr_reader :count
4
-
5
5
  def self.scan(scanner)
6
6
  scanner = StringScanner.new(scanner) unless scanner.respond_to?(:skip)
7
7
  count = 0
8
8
  count += 1 while !scanner.eos? && scanner.skip(/ *\n/)
9
9
  count.zero? and return
10
- new(count:)
10
+ new(count)
11
11
  end
12
12
 
13
- def initialize(count:) # :nodoc:
13
+ def initialize(count) # :nodoc:
14
14
  @count = count
15
15
  end
16
+
17
+ extend Forwardable # :nodoc:
18
+ def_delegator :@count, :to_i
16
19
  end
17
20
  end
@@ -1,16 +1,14 @@
1
- require "elisp2any/top_heading"
1
+ require "forwardable"
2
2
  require "elisp2any/paragraph"
3
3
  require "elisp2any/blanklines"
4
4
  require "elisp2any/section"
5
5
 
6
6
  module Elisp2any
7
7
  class Code
8
- attr_reader :paragraphs, :sections
9
-
10
8
  def self.scan(scanner)
11
9
  pos = scanner.pos
12
- heading = TopHeading.scan(scanner) or return
13
- unless heading.content == "Code:"
10
+ heading = Elisp2any.scan_top_heading(scanner) or return
11
+ unless heading == "Code:"
14
12
  scanner.pos = pos
15
13
  return
16
14
  end
@@ -20,16 +18,32 @@ module Elisp2any
20
18
  paragraphs << par
21
19
  Blanklines.scan(scanner) # optional
22
20
  end
23
- section = Section.scan(scanner)
24
- unless section.heading.level == 1
25
- raise "TODO"
21
+ blocks = [paragraphs]
22
+ pos = scanner.pos
23
+ while (section = Section.scan(scanner))
24
+ if section.heading.level == 1
25
+ blocks << section
26
+ pos = scanner.pos
27
+ else
28
+ scanner.pos = pos
29
+ break
30
+ end
26
31
  end
27
- new(paragraphs:, sections: [section])
32
+ new(blocks)
33
+ end
34
+
35
+ def initialize(blocks)
36
+ @blocks = blocks
28
37
  end
29
38
 
30
- def initialize(paragraphs:, sections:)
31
- @paragraphs = paragraphs
32
- @sections = sections
39
+ def sections
40
+ @blocks.drop(1)
33
41
  end
42
+
43
+ extend Forwardable # :nodoc:
44
+ def_delegator :@blocks, :first, :paragraphs
45
+ def_delegator :@blocks, :each
46
+
47
+ include Enumerable
34
48
  end
35
49
  end
@@ -0,0 +1,56 @@
1
+ require "forwardable"
2
+ require "elisp2any/expression"
3
+ require "elisp2any/blanklines"
4
+ require "elisp2any/aside"
5
+
6
+ module Elisp2any
7
+ class Codeblock # :nodoc:
8
+ def self.scan(scanner)
9
+ scanner = StringScanner.new(scanner) unless scanner.respond_to?(:skip)
10
+ expressions = []
11
+ while (exp = Expression.scan(scanner) ||
12
+ scanner.scan(/[ \n]+/) ||
13
+ Aside.scan(scanner))
14
+ expressions << exp
15
+ end
16
+ expressions.empty? and return
17
+ Blanklines.scan(scanner) # optional
18
+ new(:TODO, expressions:)
19
+ end
20
+
21
+ def source
22
+ @expressions.sum(+"") do |exp|
23
+ case exp
24
+ in String
25
+ exp
26
+ else
27
+ exp.source
28
+ end
29
+ end
30
+ end
31
+
32
+ # TODO: delete node
33
+ # TODO: Remove default empty array
34
+ def initialize(node, expressions: []) # :nodoc:
35
+ @node = node
36
+ @expressions = expressions
37
+ end
38
+
39
+ def append(source, end_byte) # :nodoc:
40
+ @node.append(source, end_byte)
41
+ end
42
+
43
+ def content
44
+ if @node.respond_to?(:content)
45
+ @node.content
46
+ else
47
+ @expressions
48
+ end
49
+ end
50
+
51
+ extend Forwardable # :nodoc:
52
+ def_delegator :@expressions, :each
53
+
54
+ include Enumerable
55
+ end
56
+ end
@@ -5,7 +5,9 @@ module Elisp2any
5
5
  def self.scan(scanner)
6
6
  scanner = StringScanner.new(scanner) unless scanner.respond_to?(:skip)
7
7
  scanner.skip(/(?<colons>;+)(?<padding> *)(?<content>.*)\n?/) or return
8
- new(colons: scanner[:colons].size, content: scanner[:content], padding: scanner[:padding])
8
+ new(colons: scanner[:colons].size,
9
+ content: scanner[:content],
10
+ padding: scanner[:padding])
9
11
  end
10
12
 
11
13
  def initialize(colons:, content:, padding:)