elisp2any 0.0.4 → 0.0.5

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
  SHA256:
3
- metadata.gz: 43ac0e60fb0505d1dfedf717b38966d20340412d119b4ad6842751e92b6d6774
4
- data.tar.gz: ca6adc49a9eff939fac9aa31b97b2f07194d276042aea5c46b9bcc099a392326
3
+ metadata.gz: 03c6ff5df7f72271d8142db9389a003ca5b322de13de72f1d9eacf917545972d
4
+ data.tar.gz: f46bb2e1926e017be9488f94ddc447c47f6eeebfd9060bc360a124d29555a373
5
5
  SHA512:
6
- metadata.gz: 3ae7c237a6d260412d87ed8b506efb52694a3b85e10a27e2a0277839a6512ec06a4c3945a368bc2b9e876b1c8fe89c822ed282c1a0c921e864b651b911f84d7c
7
- data.tar.gz: 257b9a436a46b010773af8e0180b4e8417be2469a464bda2214cafac6734d5bb85771bc0213a8073fead2fdfe461595a44952483c477f698a3b4514febce9228
6
+ metadata.gz: eb50642a370b0736b37fae9892833e27d5c7b11088e97f5f906b5f265502d94b859b3e8df59f804daab5f45290ea6f165f5caa1433f121f12783de16d0f49999
7
+ data.tar.gz: dc5cf591eb9661fb8da170e5ff7359dcc6965fe7cda678c31f4b4f4518e6a16183d5c8daf372529e883a62fdccefb88736ac1638a18b7bf9e0279919795f30b7
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.6
2
+ TargetRubyVersion: 3.1
3
3
  NewCops: enable
4
4
  DisabledByDefault: true
5
5
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Change log of Elisp2any
2
2
 
3
+ ## 0.0.5 - 2025-02-10
4
+
5
+ * Upgrade required Ruby version to 3.1 or later.
6
+ * Add `--new` flag to the `elisp2any` command. It uses new scanner (parser)
7
+ written in pure Ruby with strscan gem. The older ones which uses
8
+ Tree-sitter as parser is deprecated and will be removed from the next
9
+ version.
10
+
3
11
  ## 0.0.4 - 2025-02-08
4
12
 
5
13
  * Improved shared object searching for Guix.
data/exe/elisp2any CHANGED
@@ -1,12 +1,15 @@
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 'elisp2any'
7
+ require 'elisp2any/html_renderer'
8
+
7
9
  input = $stdin
8
10
  output = $stdout
9
11
  css = nil
12
+ mode = :old
10
13
 
11
14
  OptionParser.new do |parser|
12
15
  parser.on('--input=PATH') do |path|
@@ -20,8 +23,18 @@ OptionParser.new do |parser|
20
23
  parser.on('--css=PATH') do |path|
21
24
  css = path
22
25
  end
26
+
27
+ parser.on("--new") do
28
+ mode = :new
29
+ end
23
30
  end.parse!
24
31
 
25
- file = Elisp2any::File.parse(input)
26
- renderer = Elisp2any::HTMLRenderer.new(file, css:)
32
+ file = nil
33
+ case mode
34
+ in :new
35
+ file = Elisp2any::File.scan(input)
36
+ in :old
37
+ file = Elisp2any::File.parse(input)
38
+ end
39
+ renderer = Elisp2any::HTMLRenderer.new(file, css:, mode:)
27
40
  output.write(renderer.render)
@@ -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:)
@@ -1,15 +1,13 @@
1
- require "elisp2any/top_heading"
1
+ require "forwardable"
2
2
  require "elisp2any/paragraph"
3
3
  require "elisp2any/blanklines"
4
4
 
5
5
  module Elisp2any
6
6
  class Commentary
7
- attr_reader :paragraphs
8
-
9
7
  def self.scan(scanner)
10
8
  pos = scanner.pos
11
- heading = TopHeading.scan(scanner) or return
12
- unless heading.content == "Commentary:"
9
+ heading = Elisp2any.scan_top_heading(scanner) or return
10
+ unless heading == "Commentary:"
13
11
  scanner.pos = pos
14
12
  return
15
13
  end
@@ -19,11 +17,16 @@ module Elisp2any
19
17
  paragraphs << par
20
18
  Blanklines.scan(scanner) # optional
21
19
  end
22
- new(paragraphs:)
20
+ new(paragraphs)
23
21
  end
24
22
 
25
- def initialize(paragraphs:)
23
+ def initialize(paragraphs)
26
24
  @paragraphs = paragraphs
27
25
  end
26
+
27
+ extend Forwardable # :nodoc:
28
+ def_delegators :@paragraphs, :each, :size
29
+
30
+ include Enumerable
28
31
  end
29
32
  end
@@ -1,13 +1,89 @@
1
+ require "strscan"
2
+
1
3
  module Elisp2any
2
4
  class Expression
3
- attr_reader :content
4
-
5
5
  def self.scan(scanner)
6
- content = scanner.scan(/t\b/) or return
7
- new(content:)
6
+ scanner = StringScanner.new(scanner) unless scanner.respond_to?(:skip)
7
+ if scanner.skip("(")
8
+ exps = []
9
+ while (exp = scan(scanner))
10
+ exps << exp
11
+ spaces = scanner.scan(/[ \n]+/)
12
+ exps << spaces if spaces
13
+ end
14
+ scanner.skip(")") or raise Error, scanner.inspect
15
+ new(exps)
16
+ elsif scanner.skip("'")
17
+ exp = scan(scanner) or raise Error, scanner.inspect
18
+ new({ quoted: exp })
19
+ elsif scanner.skip("\"")
20
+ content = +""
21
+ while !scanner.eos? && !scanner.match?("\\") && !scanner.match?('"')
22
+ content << scanner.getch
23
+ end
24
+ scanner.skip('"') or raise Error, scanner.inspect
25
+ new(content)
26
+ elsif scanner.skip("#'")
27
+ exp = scan(scanner) or raise Error, scanner.inspect
28
+ new({ function: exp })
29
+ else
30
+ name = scanner.scan(/[a-z0-9_?.:-]+/) or return
31
+ new(name.to_sym)
32
+ end
33
+ end
34
+
35
+ def source
36
+ case @content
37
+ in { quoted: }
38
+ "'#{self.class.source(quoted)}"
39
+ in String
40
+ @content
41
+ in { function: }
42
+ "#'#{self.class.source(function)}"
43
+ in Array
44
+ result = +"("
45
+ @content.each { |exp| result << self.class.source(exp) }
46
+ "#{result})"
47
+ in Symbol
48
+ @content.to_s
49
+ end
50
+ end
51
+
52
+ def self.source(arg)
53
+ case arg
54
+ in Symbol
55
+ arg.to_s
56
+ in Array
57
+ result = +"("
58
+ arg.sum { |ele| result << source(ele) }
59
+ "#{result})"
60
+ in Expression
61
+ arg.source
62
+ in String
63
+ arg
64
+ else
65
+ raise arg.inspect
66
+ end
67
+ end
68
+
69
+ def deconstruct_keys(*keys)
70
+ result = {}
71
+ keys => [keys]
72
+ keys.each do |key|
73
+ case key
74
+ in :content
75
+ result[:content] = @content
76
+ else # nop
77
+ end
78
+ end
79
+ result
80
+ end
81
+
82
+ def deconstruct
83
+ [*@content]
8
84
  end
9
85
 
10
- def initialize(content:)
86
+ def initialize(content)
11
87
  @content = content
12
88
  end
13
89
  end
@@ -4,21 +4,31 @@ require "elisp2any/header_line"
4
4
  require "elisp2any/blanklines"
5
5
  require "elisp2any/commentary"
6
6
  require "elisp2any/code"
7
+ require "elisp2any/footer_line"
7
8
 
8
9
  module Elisp2any
9
10
  class File
10
11
  attr_reader :name, # TODO: filename
11
12
  :synopsis, # TODO: header_line, including description
12
- :commentary, :code
13
+ :commentary, :code, :header_line
13
14
 
14
15
  def self.scan(scanner)
16
+ scanner = scanner.read if scanner.respond_to?(:read)
15
17
  scanner = StringScanner.new(scanner) unless scanner.respond_to?(:skip)
16
- line = HeaderLine.scan(scanner) or return
18
+ line = HeaderLine.scan(scanner) or return # TODO: header line
17
19
  Blanklines.scan(scanner) # optional
18
20
  commentary = Commentary.scan(scanner)
19
21
  Blanklines.scan(scanner) # optional
20
22
  code = Code.scan(scanner)
21
- new(name: line.filename, synopsis: line.description, commentary:, code:)
23
+ footer_line = FooterLine.scan(scanner)
24
+ footer_line == line.filename or raise Error, "header and footer filename is not same: #{line.filename} and #{footer_line.filename}"
25
+ Blanklines.scan(scanner) # optional
26
+ scanner.eos? or raise Error, "extra content after footer line"
27
+ new(name: line.filename,
28
+ synopsis: line.description,
29
+ commentary:,
30
+ code:,
31
+ header_line: line)
22
32
  end
23
33
 
24
34
  def self.parse(source)
@@ -37,11 +47,17 @@ module Elisp2any
37
47
  new(name: name, synopsis: synopsis, commentary: commentary, code: code)
38
48
  end
39
49
 
40
- def initialize(name:, synopsis:, commentary:, code:) # :nodoc:
50
+ # TODO: Remove nil default from footer line
51
+ def initialize(name:,
52
+ synopsis:,
53
+ commentary:,
54
+ code:,
55
+ header_line: nil) # :nodoc:
41
56
  @name = name
42
57
  @synopsis = synopsis
43
58
  @commentary = commentary
44
59
  @code = code
60
+ @header_line = header_line
45
61
  end
46
62
  end
47
63
  end
@@ -0,0 +1,28 @@
1
+ require "forwardable"
2
+
3
+ module Elisp2any
4
+ class FooterLine
5
+ def self.scan(scanner)
6
+ scanner = StringScanner.new(scanner) unless scanner.respond_to?(:pos)
7
+ pos = scanner.pos
8
+ heading = Elisp2any.scan_top_heading(scanner) or return
9
+ cscanner = StringScanner.new(heading)
10
+ unless (filename = Elisp2any.scan_filename(cscanner))
11
+ scanner.pos = pos
12
+ return
13
+ end
14
+ unless cscanner.skip(/ ends here\n?/)
15
+ scanner.pos = pos
16
+ return
17
+ end
18
+ new(filename)
19
+ end
20
+
21
+ def initialize(filename)
22
+ @filename = filename
23
+ end
24
+
25
+ extend Forwardable # :nodoc:
26
+ def_delegator :@filename, :==
27
+ end
28
+ end
@@ -1,6 +1,5 @@
1
1
  require "elisp2any/comment"
2
- require "elisp2any/filename"
3
- require "elisp2any/header_line_variables"
2
+ require "elisp2any/expression"
4
3
 
5
4
  module Elisp2any
6
5
  class HeaderLine
@@ -9,12 +8,12 @@ module Elisp2any
9
8
  def self.scan(scanner)
10
9
  scanner = StringScanner.new(scanner) unless scanner.respond_to?(:pos)
11
10
  pos = scanner.pos
12
- unless (heading = TopHeading.scan(scanner))
11
+ unless (heading = Elisp2any.scan_top_heading(scanner))
13
12
  scanner.pos = pos
14
13
  return
15
14
  end
16
- cscanner = StringScanner.new(heading.content)
17
- unless (filename = Filename.scan(cscanner))
15
+ cscanner = StringScanner.new(heading)
16
+ unless (filename = Elisp2any.scan_filename(cscanner))
18
17
  scanner.pos = pos
19
18
  return
20
19
  end
@@ -25,7 +24,7 @@ module Elisp2any
25
24
  description = +""
26
25
  variables = nil
27
26
  until cscanner.eos?
28
- if (variables = HeaderLineVariables.scan(cscanner)) # nop
27
+ if (variables = scan_variables(cscanner)) # nop
29
28
  break
30
29
  else
31
30
  description << cscanner.getch
@@ -35,9 +34,43 @@ module Elisp2any
35
34
  scanner.pos = pos
36
35
  return
37
36
  end
38
- new(filename: filename.content, description:, variables: variables.variables)
37
+ new(filename:, description:, variables:)
39
38
  end
40
39
 
40
+ def self.scan_variables(scanner)
41
+ scanner = StringScanner.new(scanner) unless scanner.respond_to?(:pos)
42
+ scanner.skip(/ *-[*]- +/) or return
43
+ variables = {}
44
+ until scanner.skip(/ +-[*]- */)
45
+ if scanner.eos?
46
+ raise Error, "unexpected end"
47
+ elsif (assign = scan_assignments(scanner))
48
+ variables[assign[:variable]] = assign[:expression]
49
+ elsif scanner.skip(";")
50
+ break
51
+ else
52
+ raise Error, scanner.inspect
53
+ end
54
+ end
55
+ variables
56
+ end
57
+ private_class_method :scan_variables
58
+
59
+ def self.scan_assignments(scanner)
60
+ pos = scanner.pos
61
+ variable = Elisp2any.scan_variable(scanner) or return
62
+ unless scanner.skip(/ *: */)
63
+ scanner.pos = pos
64
+ return
65
+ end
66
+ unless (expression = Expression.scan(scanner))
67
+ scanner.pos = pos
68
+ return
69
+ end
70
+ { variable:, expression: }
71
+ end
72
+ private_class_method :scan_assignments
73
+
41
74
  def initialize(filename:, description:, variables:)
42
75
  @filename = filename
43
76
  @description = description
@@ -3,7 +3,8 @@ require 'forwardable'
3
3
 
4
4
  module Elisp2any
5
5
  class Heading
6
- attr_reader :level, :content
6
+ attr_reader :level
7
+ attr_accessor :content
7
8
 
8
9
  def self.scan(scanner)
9
10
  pos = scanner.pos
@@ -12,10 +13,26 @@ module Elisp2any
12
13
  scanner.pos = pos
13
14
  return
14
15
  end
15
- new(:TODO, comment.colons - 3, comment.content)
16
+ new(:TODO, comment.colons - 3,
17
+ comment.content # do not scan as text at this point
18
+ )
16
19
  end
17
20
 
18
- # TODO
21
+ def deconstruct_keys(*keys)
22
+ result = {}
23
+ keys => [keys]
24
+ keys.each do |key|
25
+ case key
26
+ in :level
27
+ result[:level] = @level
28
+ in :content
29
+ result[:content] = @content
30
+ end
31
+ end
32
+ result
33
+ end
34
+
35
+ # TODO: delete node. Use kwargs.
19
36
  def initialize(node, level, content) # :nodoc:
20
37
  @node = node
21
38
  @level = level
@@ -45,7 +62,7 @@ module Elisp2any
45
62
  @content.match(/\A(?<name>.+?)\.el ends here\Z/)[:name]
46
63
  end
47
64
 
48
- extend Forwardable
65
+ extend Forwardable # :nodoc:
49
66
  def_delegator :@level, :<=>
50
67
  end
51
68
  end
@@ -1,23 +1,23 @@
1
1
  <!doctype html>
2
2
  <html>
3
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title><%= name %></title>
7
- <link rel="stylesheet" href="<%= @css %>">
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title><%= name %></title>
7
+ <link rel="stylesheet" href="<%= @css %>">
8
8
  </head>
9
9
  <body>
10
- <main>
11
- <h1><%= name %></h1>
12
- <p><%= synopsis %></p>
13
- <h2>Commentary</h2>
14
- <% commentary.each do |paragraph| %>
15
- <p><%= render_paragraph(paragraph) %></p>
16
- <% end %>
17
- <h2>Code</h2>
18
- <% code.each do |block| %>
19
- <%= render_block(block, level: 2) %>
20
- <% end %>
21
- </main>
10
+ <main>
11
+ <h1><%= name %></h1>
12
+ <p><%= synopsis %></p>
13
+ <h2>Commentary</h2>
14
+ <% commentary.each do |paragraph| %>
15
+ <p><%= render_paragraph(paragraph) %></p>
16
+ <% end %>
17
+ <h2>Code</h2>
18
+ <% code.each do |block| %>
19
+ <%= render_block(block, level: 2) %>
20
+ <% end %>
21
+ </main>
22
22
  </body>
23
23
  </html>
@@ -0,0 +1,22 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title><%= name %></title>
7
+ <link rel="stylesheet" href="<%= @css %>">
8
+ </head>
9
+ <body>
10
+ <main>
11
+ <h1><%= name %><small> &mdash; <%= synopsis %></small></h1>
12
+ <div style="text-align:right"><%= render(header_line.variables) %></div>
13
+ <% commentary.each do |paragraph| %>
14
+ <%= render(paragraph) %>
15
+ <% end %>
16
+ <h2>Code</h2>
17
+ <% self.code.each do |block| %>
18
+ <%= render(block) %>
19
+ <% end %>
20
+ </main>
21
+ </body>
22
+ </html>
@@ -4,17 +4,66 @@ require 'cgi/util'
4
4
  require_relative 'inline_code'
5
5
  require_relative 'heading'
6
6
  require_relative 'paragraph'
7
- require_relative 'code_block'
7
+ require_relative 'codeblock'
8
8
 
9
9
  module Elisp2any
10
10
  class HTMLRenderer
11
- def initialize(file, css: nil)
11
+ def initialize(file, css: nil, mode:)
12
12
  @file = file
13
13
  @css = css || "https://unpkg.com/mvp.css"
14
+ @mode = mode
14
15
  end
15
16
 
16
- def render
17
- erb_render('index.html.erb')
17
+ # Write gradually?
18
+ def render(node = nil)
19
+ if node
20
+ case node
21
+ in Paragraph
22
+ par = node.sum(+"") { |line| render(line) }
23
+ "<p>#{par}</p>"
24
+ in Line
25
+ begin
26
+ node.sum(+"") { |chunk| render(chunk) }
27
+ rescue => e
28
+ warn node.inspect
29
+ raise e
30
+ end
31
+ in String
32
+ h(node)
33
+ in [] # huh? nop.
34
+ in Section
35
+ result = render(node.heading)
36
+ node.blocks.each { |blo| result << render(blo) }
37
+ node.sections.each { |sec| result << render(sec) }
38
+ result
39
+ in Heading[level:, content:]
40
+ lev = level + 2
41
+ "<h#{lev}>#{render(content)}</h#{lev}>"
42
+ in Codeblock
43
+ "<pre><code>#{h(node.source.chomp)}</code></pre>"
44
+ in { code: }
45
+ "<code>#{h(code)}</code>"
46
+ in Hash
47
+ result = node.sum(+"<dl>") do |key, value|
48
+ "<dt>#{render(key)}</dt><dd>#{render(value)}</dd>"
49
+ end
50
+ "#{result}</dl>"
51
+ in Expression
52
+ "<code>#{h(node.source)}</code>"
53
+ in Text
54
+ node.sum(+"") { |ele| render(ele) }
55
+ else
56
+ raise Error, node.inspect
57
+ end
58
+ else
59
+ case @mode
60
+ in :old
61
+ erb_render('index.html.erb')
62
+ in :new
63
+ source = ::File.read(::File.join(__dir__, 'html_renderer.erb'))
64
+ ERB.new(source).result(binding)
65
+ end
66
+ end
18
67
  end
19
68
 
20
69
  private
@@ -24,26 +73,48 @@ module Elisp2any
24
73
  paragraph.each do |line|
25
74
  line.each do |chunk|
26
75
  case chunk
27
- when InlineCode
76
+ in InlineCode
28
77
  html << "<code>#{h(chunk.content)}</code>"
29
- when String
78
+ in String
30
79
  html << h(chunk)
80
+ in { code: }
81
+ html << "<code>#{h(code)}</code>"
82
+ else
83
+ raise Error, chunk.inspect
31
84
  end
32
85
  end
33
86
  end
34
87
  html
35
88
  end
36
89
 
37
- def render_block(block, level:)
90
+ # TODO: remove level
91
+ def render_block(block, level: nil)
38
92
  html = ''
39
93
  case block
40
94
  when Heading
41
- name = "h#{level + block.level - 1}"
95
+ lev = level
96
+ if level
97
+ lev = level + block.level - 1
98
+ else
99
+ lev = block.level
100
+ end
101
+ name = "h#{lev}"
42
102
  html << "<#{name}>#{h(block.content)}</#{name}>"
43
103
  when Paragraph
44
104
  html << render_paragraph(block)
45
- when CodeBlock
105
+ when Codeblock
46
106
  html << "<pre><code>#{h(block.content)}</code></pre>"
107
+ when [] # huh? nop
108
+ when Section
109
+ render_block(block.heading)
110
+ block.blocks.each do |blo|
111
+ render_block(blo)
112
+ end
113
+ block.sections.each do |sec|
114
+ render_block(sec)
115
+ end
116
+ else
117
+ raise Error, block.inspect
47
118
  end
48
119
  html
49
120
  end
@@ -53,11 +124,26 @@ module Elisp2any
53
124
  ERB.new(source).result(binding)
54
125
  end
55
126
 
56
- def h(string)
57
- CGI.escape_html(string)
127
+ def h(arg)
128
+ case arg
129
+ in String
130
+ CGI.escape_html(arg)
131
+ in Array
132
+ arg.map do |ele|
133
+ h(ele)
134
+ end.join
135
+ in Symbol
136
+ h(arg.to_s)
137
+ in Expression
138
+ h(arg.source)
139
+ in Integer
140
+ h(arg.to_s)
141
+ in Aside
142
+ h(arg.content)
143
+ end
58
144
  end
59
145
 
60
146
  extend Forwardable # :nodoc:
61
- def_delegators :@file, :name, :synopsis, :commentary, :code
147
+ def_delegators :@file, :name, :synopsis, :commentary, :code, :header_line
62
148
  end
63
149
  end
@@ -1,4 +1,5 @@
1
1
  module Elisp2any
2
+ # TODO: delete
2
3
  class InlineCode
3
4
  attr_reader :content
4
5
 
@@ -2,6 +2,7 @@ require 'strscan'
2
2
  require 'forwardable'
3
3
  require_relative 'inline_code'
4
4
  require "elisp2any/comment"
5
+ require "elisp2any/text"
5
6
 
6
7
  module Elisp2any
7
8
  class Line
@@ -15,7 +16,8 @@ module Elisp2any
15
16
  unless comment.padding[0] == " "
16
17
  raise Error, "line comment should have a whitespace padding"
17
18
  end
18
- new(["#{comment.padding[1..]}#{comment.content}"])
19
+ content = "#{comment.padding[1..]}#{comment.content}"
20
+ new(Text.scan(content).to_a)
19
21
  end
20
22
 
21
23
  def initialize(chunks) # :nodoc:
@@ -42,8 +44,8 @@ module Elisp2any
42
44
  new(chunks)
43
45
  end
44
46
 
45
- extend Forwardable
46
- def_delegators :@chunks, :each
47
+ extend Forwardable # :nodoc:
48
+ def_delegators :@chunks, :each, :deconstruct
47
49
 
48
50
  include Enumerable
49
51
  end
@@ -1,10 +1,11 @@
1
- require_relative 'code_block'
1
+ require_relative 'codeblock'
2
2
  require_relative 'line'
3
3
  require_relative 'heading'
4
4
  require_relative 'paragraph'
5
5
  require 'strscan'
6
6
 
7
7
  module Elisp2any
8
+ # TODO: delete
8
9
  class Node
9
10
  attr_reader :range # :nodoc:
10
11
  attr_reader :content
@@ -21,7 +22,7 @@ module Elisp2any
21
22
  scanner.skip(';') or raise Error, 'no semicolon for comment'
22
23
  scanner.skip(';') or
23
24
  begin
24
- (last_node = nodes.last) && last_node.is_a?(CodeBlock) or raise Error, 'no prior code for single semicolon comment'
25
+ (last_node = nodes.last) && last_node.is_a?(Codeblock) or raise Error, 'no prior code for single semicolon comment'
25
26
  last_node.append(source, range.end)
26
27
  next
27
28
  end
@@ -43,10 +44,10 @@ module Elisp2any
43
44
  nodes << paragraph
44
45
  end
45
46
  else
46
- if (last_node = nodes.last) && last_node.is_a?(CodeBlock)
47
+ if (last_node = nodes.last) && last_node.is_a?(Codeblock)
47
48
  last_node.append(source, range.end)
48
49
  else
49
- nodes << CodeBlock.new(node)
50
+ nodes << Codeblock.new(node)
50
51
  end
51
52
  end
52
53
  end
@@ -3,8 +3,6 @@ require "elisp2any/line"
3
3
 
4
4
  module Elisp2any
5
5
  class Paragraph
6
- attr_reader :lines
7
-
8
6
  def self.scan(scanner)
9
7
  scanner = StringScanner.new(scanner) unless scanner.respond_to?(:skip)
10
8
  lines = []
@@ -25,12 +23,13 @@ module Elisp2any
25
23
  @end_row = end_row
26
24
  end
27
25
 
26
+ # TODO: delete
28
27
  def code?
29
28
  false
30
29
  end
31
30
 
32
- extend Forwardable
33
- def_delegators :@lines, :empty?, :clear, :<<, :each
31
+ extend Forwardable # :nodoc:
32
+ def_delegators :@lines, :empty?, :clear, :<<, :each, :deconstruct, :size
34
33
  def_delegators :@node, :adjucent?
35
34
 
36
35
  include Enumerable
@@ -1,25 +1,49 @@
1
+ require "forwardable"
1
2
  require "elisp2any/heading"
2
- require "elisp2any/paragraph"
3
3
  require "elisp2any/blanklines"
4
+ require "elisp2any/text"
4
5
 
5
6
  module Elisp2any
6
7
  class Section
7
- attr_reader :heading
8
+ attr_reader :heading,
9
+ # TODO: gather blocks and sections
10
+ :blocks,
11
+ :sections
8
12
 
9
13
  def self.scan(scanner)
10
14
  heading = Heading.scan(scanner) or return
15
+ heading.content = Text.scan(heading.content)
11
16
  Blanklines.scan(scanner) # optional
12
- paragraphs = []
13
- while (par = Paragraph.scan(scanner))
14
- paragraphs << par
17
+ blocks = []
18
+ while (blo = Paragraph.scan(scanner) || Codeblock.scan(scanner))
19
+ blocks << blo
15
20
  Blanklines.scan(scanner) # optional
16
21
  end
17
- new(heading:, paragraphs:)
22
+ pos = scanner.pos
23
+ sections = []
24
+ while (section = scan(scanner))
25
+ if section.level == heading.level + 1
26
+ sections << section
27
+ pos = scanner.pos
28
+ elsif section.level >= heading.level + 2
29
+ raise Error, "too demoted heading"
30
+ else
31
+ scanner.pos = pos
32
+ break
33
+ end
34
+ end
35
+ new(heading:, blocks:, sections:)
18
36
  end
19
37
 
20
- def initialize(heading:, paragraphs:)
38
+ def initialize(heading:, blocks:, sections:)
21
39
  @heading = heading
22
- @paragraphs = paragraphs
40
+ @blocks = blocks
41
+ @sections = sections
23
42
  end
43
+
44
+ alias paragraphs blocks # TODO: delete
45
+
46
+ extend Forwardable # :nodoc:
47
+ def_delegator :@heading, :level
24
48
  end
25
49
  end
@@ -0,0 +1,33 @@
1
+ require "forwardable"
2
+
3
+ module Elisp2any
4
+ class Text
5
+ def self.scan(scanner)
6
+ scanner = StringScanner.new(scanner) unless scanner.respond_to?(:skip)
7
+ tokens = []
8
+ until scanner.eos?
9
+ if scanner.skip(/`(?<content>[^']+)'/)
10
+ tokens << { code: scanner[:content] }
11
+ else
12
+ char = scanner.getch
13
+ case tokens
14
+ in [*, String => last]
15
+ last << char
16
+ else
17
+ tokens << char
18
+ end
19
+ end
20
+ end
21
+ new(tokens)
22
+ end
23
+
24
+ def initialize(tokens)
25
+ @tokens = tokens
26
+ end
27
+
28
+ extend Forwardable # :nodoc:
29
+ def_delegators :@tokens, :each, :deconstruct
30
+
31
+ include Enumerable
32
+ end
33
+ end
@@ -1,3 +1,4 @@
1
1
  module Elisp2any
2
- VERSION = '0.0.4'
2
+ # Version of this library.
3
+ VERSION = '0.0.5'
3
4
  end
data/lib/elisp2any.rb CHANGED
@@ -1,8 +1,27 @@
1
1
  require_relative 'elisp2any/version'
2
2
  require_relative 'elisp2any/file'
3
+ require_relative 'elisp2any/heading'
3
4
 
4
5
  module Elisp2any
5
6
  autoload :HeaderLine, "elisp2any/header_line.rb"
6
7
  autoload :Blanklines, "elisp2any/blanklines.rb"
7
8
  Error = Class.new(StandardError)
9
+
10
+ def self.scan_filename(scanner) # :nodoc:
11
+ scanner.scan(/[a-z]+[.]el\b/)
12
+ end
13
+
14
+ def self.scan_variable(scanner) # :nodoc:
15
+ scanner.scan(/[a-z-]+\b/)
16
+ end
17
+
18
+ def self.scan_top_heading(scanner) # :nodoc:
19
+ pos = scanner.pos
20
+ heading = Heading.scan(scanner) or return
21
+ unless heading.level.zero?
22
+ scanner.pos = pos
23
+ return
24
+ end
25
+ heading.content
26
+ end
8
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elisp2any
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - gemmaro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-08 00:00:00.000000000 Z
11
+ date: 2025-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby_tree_sitter
@@ -117,18 +117,18 @@ files:
117
117
  - lib/elisp2any.rb
118
118
  - lib/elisp2any/asciidoc_renderer.rb
119
119
  - lib/elisp2any/asciidoc_renderer/index.adoc.erb
120
+ - lib/elisp2any/aside.rb
120
121
  - lib/elisp2any/blanklines.rb
121
122
  - lib/elisp2any/code.rb
122
- - lib/elisp2any/code_block.rb
123
+ - lib/elisp2any/codeblock.rb
123
124
  - lib/elisp2any/comment.rb
124
125
  - lib/elisp2any/commentary.rb
125
126
  - lib/elisp2any/expression.rb
126
127
  - lib/elisp2any/file.rb
127
- - lib/elisp2any/filename.rb
128
+ - lib/elisp2any/footer_line.rb
128
129
  - lib/elisp2any/header_line.rb
129
- - lib/elisp2any/header_line_variable_assignment.rb
130
- - lib/elisp2any/header_line_variables.rb
131
130
  - lib/elisp2any/heading.rb
131
+ - lib/elisp2any/html_renderer.erb
132
132
  - lib/elisp2any/html_renderer.rb
133
133
  - lib/elisp2any/html_renderer/index.html.erb
134
134
  - lib/elisp2any/inline_code.rb
@@ -136,9 +136,8 @@ files:
136
136
  - lib/elisp2any/node.rb
137
137
  - lib/elisp2any/paragraph.rb
138
138
  - lib/elisp2any/section.rb
139
- - lib/elisp2any/top_heading.rb
139
+ - lib/elisp2any/text.rb
140
140
  - lib/elisp2any/tree_sitter_parser.rb
141
- - lib/elisp2any/variable.rb
142
141
  - lib/elisp2any/version.rb
143
142
  - manifest.scm
144
143
  - sig/elisp2any.gen.rbs
@@ -162,14 +161,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
162
161
  requirements:
163
162
  - - ">="
164
163
  - !ruby/object:Gem::Version
165
- version: 2.6.0
164
+ version: '3.1'
166
165
  required_rubygems_version: !ruby/object:Gem::Requirement
167
166
  requirements:
168
167
  - - ">="
169
168
  - !ruby/object:Gem::Version
170
169
  version: '0'
171
170
  requirements: []
172
- rubygems_version: 3.3.26
171
+ rubygems_version: 3.5.11
173
172
  signing_key:
174
173
  specification_version: 4
175
174
  summary: Converter from Emacs Lisp to some document markup
@@ -1,16 +0,0 @@
1
- require 'forwardable'
2
-
3
- module Elisp2any
4
- class CodeBlock # :nodoc:
5
- def initialize(node) # :nodoc:
6
- @node = node
7
- end
8
-
9
- def append(source, end_byte) # :nodoc:
10
- @node.append(source, end_byte)
11
- end
12
-
13
- extend Forwardable # :nodoc:
14
- def_delegators :@node, :content
15
- end
16
- end
@@ -1,14 +0,0 @@
1
- module Elisp2any
2
- class Filename
3
- attr_reader :content
4
-
5
- def self.scan(scanner)
6
- content = scanner.scan(/[a-z]+[.]el\b/) or return
7
- new(content:)
8
- end
9
-
10
- def initialize(content:)
11
- @content = content
12
- end
13
- end
14
- end
@@ -1,27 +0,0 @@
1
- require "elisp2any/variable"
2
- require "elisp2any/expression"
3
-
4
- module Elisp2any
5
- class HeaderLineVariableAssignment
6
- attr_reader :variable, :expression
7
-
8
- def self.scan(scanner)
9
- pos = scanner.pos
10
- variable = Variable.scan(scanner) or return
11
- unless scanner.skip(/ *: */)
12
- scanner.pos = pos
13
- return
14
- end
15
- unless (expression = Expression.scan(scanner))
16
- scanner.pos = pos
17
- return
18
- end
19
- new(variable: variable.name, expression: expression.content)
20
- end
21
-
22
- def initialize(variable:, expression:)
23
- @variable = variable
24
- @expression = expression
25
- end
26
- end
27
- end
@@ -1,33 +0,0 @@
1
- require "elisp2any/variable"
2
- require "elisp2any/expression"
3
- require "elisp2any/header_line_variable_assignment"
4
-
5
- module Elisp2any
6
- class HeaderLineVariables
7
- attr_reader :variables
8
-
9
- def self.scan(scanner)
10
- scanner = StringScanner.new(scanner) unless scanner.respond_to?(:pos)
11
- pos = scanner.pos
12
- scanner.skip(/ *-[*]- +/) or return
13
- variables = {}
14
- until scanner.skip(/ +-[*]- */)
15
- if scanner.eos?
16
- scanner.pos = pos
17
- return
18
- elsif (assign = HeaderLineVariableAssignment.scan(scanner))
19
- variables[assign.variable] = assign.expression
20
- elsif scanner.skip(";")
21
- break
22
- else
23
- raise scanner.inspect
24
- end
25
- end
26
- new(variables:)
27
- end
28
-
29
- def initialize(variables:)
30
- @variables = variables
31
- end
32
- end
33
- end
@@ -1,21 +0,0 @@
1
- require "elisp2any/heading"
2
-
3
- module Elisp2any
4
- class TopHeading
5
- attr_reader :content
6
-
7
- def self.scan(scanner)
8
- pos = scanner.pos
9
- heading = Heading.scan(scanner)
10
- unless heading.level.zero?
11
- scanner.pos = pos
12
- return
13
- end
14
- new(content: heading.content)
15
- end
16
-
17
- def initialize(content:)
18
- @content = content
19
- end
20
- end
21
- end
@@ -1,14 +0,0 @@
1
- module Elisp2any
2
- class Variable
3
- attr_reader :name
4
-
5
- def self.scan(scanner)
6
- name = scanner.scan(/[a-z-]+/) or return
7
- new(name:)
8
- end
9
-
10
- def initialize(name:)
11
- @name = name
12
- end
13
- end
14
- end