elisp2any 0.0.4 → 0.0.5

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: 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