elisp2any 0.0.3 → 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: cfaef7d1de4a445f1055d3093dd6afb0b3f87db67dff17b4a3bf17e451e042c6
4
- data.tar.gz: 3746dd755e216ffda34d037bb3c1070dadb1b8b485426a25c59555d284e7f55a
3
+ metadata.gz: 03c6ff5df7f72271d8142db9389a003ca5b322de13de72f1d9eacf917545972d
4
+ data.tar.gz: f46bb2e1926e017be9488f94ddc447c47f6eeebfd9060bc360a124d29555a373
5
5
  SHA512:
6
- metadata.gz: 13d87c5c455d13766deea274183b1a3f32e4165c0f7edbb68e680f977e1c8987eec0808bff8a1285ed9a617d0796e9fdee43784f8955e681feffc41fd0be06d6
7
- data.tar.gz: 97d5d3ca8dfe1f14a26c21bb5d4062a789ac77e2a05ddeb568a624f2ff16dc3cc2e83415d979e612d504aa883af50cd44275289e633521622049188d8db37bd5
6
+ metadata.gz: eb50642a370b0736b37fae9892833e27d5c7b11088e97f5f906b5f265502d94b859b3e8df59f804daab5f45290ea6f165f5caa1433f121f12783de16d0f49999
7
+ data.tar.gz: dc5cf591eb9661fb8da170e5ff7359dcc6965fe7cda678c31f4b4f4518e6a16183d5c8daf372529e883a62fdccefb88736ac1638a18b7bf9e0279919795f30b7
data/.document ADDED
@@ -0,0 +1,4 @@
1
+ README.md
2
+ lib/**/*.rb
3
+ CHANGELOG.md
4
+ LICENSE.txt
data/.rdoc_options ADDED
@@ -0,0 +1,2 @@
1
+ main_page: README.md
2
+ op_dir: html
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,6 +1,16 @@
1
1
  # Change log of Elisp2any
2
2
 
3
- ## Unreleased
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
+
11
+ ## 0.0.4 - 2025-02-08
12
+
13
+ * Improved shared object searching for Guix.
4
14
 
5
15
  ## 0.0.3 - 2024-11-16
6
16
 
data/Rakefile CHANGED
@@ -7,11 +7,22 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.test_files = FileList['test/**/*_test.rb']
8
8
  end
9
9
 
10
- require 'rubocop/rake_task'
10
+ default_tasks = %i[test]
11
11
 
12
- RuboCop::RakeTask.new
12
+ rubocop = true
13
13
 
14
- task default: %i[test rubocop]
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
15
26
 
16
27
  require 'rdoc/task'
17
28
  RDoc::Task.new do |rdoc|
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)
data/fixtures/init.el CHANGED
@@ -1,4 +1,4 @@
1
- ;;; init.el --- Emacs configuration
1
+ ;;; init.el --- Emacs configuration -*- lexical-binding: t; -*-
2
2
 
3
3
  ;;; Commentary:
4
4
 
@@ -51,7 +51,7 @@ module Elisp2any
51
51
  ERB.new(source).result(binding)
52
52
  end
53
53
 
54
- extend Forwardable
54
+ extend Forwardable # :nodoc:
55
55
  def_delegators :@file, :name, :synopsis, :commentary, :code
56
56
  end
57
57
  end
@@ -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
@@ -0,0 +1,20 @@
1
+ require "forwardable"
2
+
3
+ module Elisp2any
4
+ class Blanklines
5
+ def self.scan(scanner)
6
+ scanner = StringScanner.new(scanner) unless scanner.respond_to?(:skip)
7
+ count = 0
8
+ count += 1 while !scanner.eos? && scanner.skip(/ *\n/)
9
+ count.zero? and return
10
+ new(count)
11
+ end
12
+
13
+ def initialize(count) # :nodoc:
14
+ @count = count
15
+ end
16
+
17
+ extend Forwardable # :nodoc:
18
+ def_delegator :@count, :to_i
19
+ end
20
+ end
@@ -0,0 +1,49 @@
1
+ require "forwardable"
2
+ require "elisp2any/paragraph"
3
+ require "elisp2any/blanklines"
4
+ require "elisp2any/section"
5
+
6
+ module Elisp2any
7
+ class Code
8
+ def self.scan(scanner)
9
+ pos = scanner.pos
10
+ heading = Elisp2any.scan_top_heading(scanner) or return
11
+ unless heading == "Code:"
12
+ scanner.pos = pos
13
+ return
14
+ end
15
+ Blanklines.scan(scanner) # optional
16
+ paragraphs = []
17
+ while (par = Paragraph.scan(scanner))
18
+ paragraphs << par
19
+ Blanklines.scan(scanner) # optional
20
+ end
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
31
+ end
32
+ new(blocks)
33
+ end
34
+
35
+ def initialize(blocks)
36
+ @blocks = blocks
37
+ end
38
+
39
+ def sections
40
+ @blocks.drop(1)
41
+ end
42
+
43
+ extend Forwardable # :nodoc:
44
+ def_delegator :@blocks, :first, :paragraphs
45
+ def_delegator :@blocks, :each
46
+
47
+ include Enumerable
48
+ end
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
@@ -0,0 +1,19 @@
1
+ module Elisp2any
2
+ class Comment
3
+ attr_reader :colons, :content, :padding
4
+
5
+ def self.scan(scanner)
6
+ scanner = StringScanner.new(scanner) unless scanner.respond_to?(:skip)
7
+ scanner.skip(/(?<colons>;+)(?<padding> *)(?<content>.*)\n?/) or return
8
+ new(colons: scanner[:colons].size,
9
+ content: scanner[:content],
10
+ padding: scanner[:padding])
11
+ end
12
+
13
+ def initialize(colons:, content:, padding:)
14
+ @colons = colons
15
+ @content = content
16
+ @padding = padding
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ require "forwardable"
2
+ require "elisp2any/paragraph"
3
+ require "elisp2any/blanklines"
4
+
5
+ module Elisp2any
6
+ class Commentary
7
+ def self.scan(scanner)
8
+ pos = scanner.pos
9
+ heading = Elisp2any.scan_top_heading(scanner) or return
10
+ unless heading == "Commentary:"
11
+ scanner.pos = pos
12
+ return
13
+ end
14
+ Blanklines.scan(scanner) # optional
15
+ paragraphs = []
16
+ while (par = Paragraph.scan(scanner))
17
+ paragraphs << par
18
+ Blanklines.scan(scanner) # optional
19
+ end
20
+ new(paragraphs)
21
+ end
22
+
23
+ def initialize(paragraphs)
24
+ @paragraphs = paragraphs
25
+ end
26
+
27
+ extend Forwardable # :nodoc:
28
+ def_delegators :@paragraphs, :each, :size
29
+
30
+ include Enumerable
31
+ end
32
+ end
@@ -0,0 +1,90 @@
1
+ require "strscan"
2
+
3
+ module Elisp2any
4
+ class Expression
5
+ def self.scan(scanner)
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]
84
+ end
85
+
86
+ def initialize(content)
87
+ @content = content
88
+ end
89
+ end
90
+ end
@@ -1,9 +1,35 @@
1
1
  require_relative 'node'
2
2
  require_relative 'tree_sitter_parser'
3
+ require "elisp2any/header_line"
4
+ require "elisp2any/blanklines"
5
+ require "elisp2any/commentary"
6
+ require "elisp2any/code"
7
+ require "elisp2any/footer_line"
3
8
 
4
9
  module Elisp2any
5
10
  class File
6
- attr_reader :name, :synopsis, :commentary, :code
11
+ attr_reader :name, # TODO: filename
12
+ :synopsis, # TODO: header_line, including description
13
+ :commentary, :code, :header_line
14
+
15
+ def self.scan(scanner)
16
+ scanner = scanner.read if scanner.respond_to?(:read)
17
+ scanner = StringScanner.new(scanner) unless scanner.respond_to?(:skip)
18
+ line = HeaderLine.scan(scanner) or return # TODO: header line
19
+ Blanklines.scan(scanner) # optional
20
+ commentary = Commentary.scan(scanner)
21
+ Blanklines.scan(scanner) # optional
22
+ code = Code.scan(scanner)
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)
32
+ end
7
33
 
8
34
  def self.parse(source)
9
35
  source = source.respond_to?(:read) ? source.read : source
@@ -21,11 +47,17 @@ module Elisp2any
21
47
  new(name: name, synopsis: synopsis, commentary: commentary, code: code)
22
48
  end
23
49
 
24
- 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:
25
56
  @name = name
26
57
  @synopsis = synopsis
27
58
  @commentary = commentary
28
59
  @code = code
60
+ @header_line = header_line
29
61
  end
30
62
  end
31
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
@@ -0,0 +1,80 @@
1
+ require "elisp2any/comment"
2
+ require "elisp2any/expression"
3
+
4
+ module Elisp2any
5
+ class HeaderLine
6
+ attr_reader :filename, :description, :variables
7
+
8
+ def self.scan(scanner)
9
+ scanner = StringScanner.new(scanner) unless scanner.respond_to?(:pos)
10
+ pos = scanner.pos
11
+ unless (heading = Elisp2any.scan_top_heading(scanner))
12
+ scanner.pos = pos
13
+ return
14
+ end
15
+ cscanner = StringScanner.new(heading)
16
+ unless (filename = Elisp2any.scan_filename(cscanner))
17
+ scanner.pos = pos
18
+ return
19
+ end
20
+ unless cscanner.skip(/ +--- +/)
21
+ scanner.pos = pos
22
+ return
23
+ end
24
+ description = +""
25
+ variables = nil
26
+ until cscanner.eos?
27
+ if (variables = scan_variables(cscanner)) # nop
28
+ break
29
+ else
30
+ description << cscanner.getch
31
+ end
32
+ end
33
+ if description.empty?
34
+ scanner.pos = pos
35
+ return
36
+ end
37
+ new(filename:, description:, variables:)
38
+ end
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
+
74
+ def initialize(filename:, description:, variables:)
75
+ @filename = filename
76
+ @description = description
77
+ @variables = variables
78
+ end
79
+ end
80
+ end
@@ -3,8 +3,36 @@ 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
 
9
+ def self.scan(scanner)
10
+ pos = scanner.pos
11
+ comment = Comment.scan(scanner) or return
12
+ unless comment.colons >= 3
13
+ scanner.pos = pos
14
+ return
15
+ end
16
+ new(:TODO, comment.colons - 3,
17
+ comment.content # do not scan as text at this point
18
+ )
19
+ end
20
+
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.
8
36
  def initialize(node, level, content) # :nodoc:
9
37
  @node = node
10
38
  @level = level
@@ -34,7 +62,7 @@ module Elisp2any
34
62
  @content.match(/\A(?<name>.+?)\.el ends here\Z/)[:name]
35
63
  end
36
64
 
37
- extend Forwardable
65
+ extend Forwardable # :nodoc:
38
66
  def_delegator :@level, :<=>
39
67
  end
40
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
- extend Forwardable
61
- def_delegators :@file, :name, :synopsis, :commentary, :code
146
+ extend Forwardable # :nodoc:
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
 
@@ -1,9 +1,25 @@
1
1
  require 'strscan'
2
2
  require 'forwardable'
3
3
  require_relative 'inline_code'
4
+ require "elisp2any/comment"
5
+ require "elisp2any/text"
4
6
 
5
7
  module Elisp2any
6
8
  class Line
9
+ def self.scan(scanner)
10
+ pos = scanner.pos
11
+ comment = Comment.scan(scanner) or return
12
+ unless comment.colons == 2
13
+ scanner.pos = pos
14
+ return
15
+ end
16
+ unless comment.padding[0] == " "
17
+ raise Error, "line comment should have a whitespace padding"
18
+ end
19
+ content = "#{comment.padding[1..]}#{comment.content}"
20
+ new(Text.scan(content).to_a)
21
+ end
22
+
7
23
  def initialize(chunks) # :nodoc:
8
24
  @chunks = chunks
9
25
  end
@@ -28,8 +44,8 @@ module Elisp2any
28
44
  new(chunks)
29
45
  end
30
46
 
31
- extend Forwardable
32
- def_delegators :@chunks, :each
47
+ extend Forwardable # :nodoc:
48
+ def_delegators :@chunks, :each, :deconstruct
33
49
 
34
50
  include Enumerable
35
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
@@ -1,21 +1,35 @@
1
1
  require 'forwardable'
2
+ require "elisp2any/line"
2
3
 
3
4
  module Elisp2any
4
5
  class Paragraph
6
+ def self.scan(scanner)
7
+ scanner = StringScanner.new(scanner) unless scanner.respond_to?(:skip)
8
+ lines = []
9
+ while (line = Line.scan(scanner))
10
+ lines << line
11
+ end
12
+ lines.empty? and return
13
+ new(:TODO, lines, :TODO)
14
+ end
15
+
16
+ # TODO: delete
5
17
  attr_reader :end_row # :nodoc:
6
18
 
19
+ # TODO: delete node and end_row
7
20
  def initialize(node, lines, end_row) # :nodoc:
8
21
  @node = node
9
22
  @lines = lines
10
23
  @end_row = end_row
11
24
  end
12
25
 
26
+ # TODO: delete
13
27
  def code?
14
28
  false
15
29
  end
16
30
 
17
- extend Forwardable
18
- def_delegators :@lines, :empty?, :clear, :<<, :each
31
+ extend Forwardable # :nodoc:
32
+ def_delegators :@lines, :empty?, :clear, :<<, :each, :deconstruct, :size
19
33
  def_delegators :@node, :adjucent?
20
34
 
21
35
  include Enumerable
@@ -0,0 +1,49 @@
1
+ require "forwardable"
2
+ require "elisp2any/heading"
3
+ require "elisp2any/blanklines"
4
+ require "elisp2any/text"
5
+
6
+ module Elisp2any
7
+ class Section
8
+ attr_reader :heading,
9
+ # TODO: gather blocks and sections
10
+ :blocks,
11
+ :sections
12
+
13
+ def self.scan(scanner)
14
+ heading = Heading.scan(scanner) or return
15
+ heading.content = Text.scan(heading.content)
16
+ Blanklines.scan(scanner) # optional
17
+ blocks = []
18
+ while (blo = Paragraph.scan(scanner) || Codeblock.scan(scanner))
19
+ blocks << blo
20
+ Blanklines.scan(scanner) # optional
21
+ end
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:)
36
+ end
37
+
38
+ def initialize(heading:, blocks:, sections:)
39
+ @heading = heading
40
+ @blocks = blocks
41
+ @sections = sections
42
+ end
43
+
44
+ alias paragraphs blocks # TODO: delete
45
+
46
+ extend Forwardable # :nodoc:
47
+ def_delegator :@heading, :level
48
+ end
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
@@ -27,8 +27,7 @@ module Elisp2any
27
27
 
28
28
  # Set this env var for Guix shell environment or shared object file is not found
29
29
  if ENV['ELISP2ANY_GUIX_USE_PROFILE_PATH']
30
- profile = ::File.dirname(ENV['PATH'].split(':').select { |path| path.start_with?('/gnu/store/') }.first)
31
- shared_object = ::File.join(profile, 'lib/tree-sitter', shared_object)
30
+ shared_object = ::File.join(ENV["GUIX_ENVIRONMENT"], "lib/tree-sitter", shared_object)
32
31
  end
33
32
 
34
33
  shared_object
@@ -1,3 +1,4 @@
1
1
  module Elisp2any
2
- VERSION = '0.0.3'
2
+ # Version of this library.
3
+ VERSION = '0.0.5'
3
4
  end
data/lib/elisp2any.rb CHANGED
@@ -1,6 +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
- class Error < StandardError; end
6
+ autoload :HeaderLine, "elisp2any/header_line.rb"
7
+ autoload :Blanklines, "elisp2any/blanklines.rb"
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
6
27
  end
data/manifest.scm CHANGED
@@ -1,6 +1,87 @@
1
- (specifications->manifest (list "ruby@3.1"
2
- "ruby-tree-sitter"
3
- "tree-sitter-elisp"
4
- "ruby-webrick"
5
- "ruby-rubocop"
6
- "ruby-asciidoctor"))
1
+ (use-modules (guix packages)
2
+ ((guix licenses) #:prefix license:)
3
+ (guix git-download)
4
+ (guix build-system ruby)
5
+ (guix build-system tree-sitter)
6
+ (gnu packages tree-sitter)
7
+ (gnu packages ruby))
8
+
9
+ (define-public ruby-tree-sitter
10
+ (let ((commit "2c56b04283f2a8cfed7d6c527ca36b8e1127ee8c")
11
+ (revision "0"))
12
+ (package
13
+ (name "ruby-tree-sitter")
14
+ (version (git-version "0.20.8.1" revision commit))
15
+ (source
16
+ (origin
17
+ (method git-fetch)
18
+ (uri (git-reference
19
+ (url "https://github.com/Faveod/ruby-tree-sitter")
20
+ (commit commit)))
21
+ (file-name (git-file-name name version))
22
+ (sha256
23
+ (base32 "144kp0ya4rl03bgwpix17bgq2ak2kqk63pwniy6sfxfjqp5yzr7f"))))
24
+ (build-system ruby-build-system)
25
+ (arguments
26
+ (list
27
+ #:phases #~(modify-phases %standard-phases
28
+ (add-after 'extract-gemspec 'remove-depends
29
+ (lambda _
30
+ (substitute* "tree_sitter.gemspec"
31
+ ((".*minitest-color.*")
32
+ "\n")
33
+ ((".*pry.*")
34
+ "\n")
35
+ ((".*rake.*")
36
+ "\n"))
37
+ (substitute* "test/test_helper.rb"
38
+ ((".*minitest/color.*")
39
+ "\n"))))
40
+ (add-before 'build 'compile
41
+ (lambda _
42
+ (invoke "rake" "compile")))
43
+ (add-before 'check 'set-path
44
+ (lambda* (#:key inputs #:allow-other-keys)
45
+ (let ((ruby (assoc-ref inputs "tree-sitter-ruby")))
46
+ (setenv "TREE_SITTER_PARSERS"
47
+ (string-append ruby "/lib/tree-sitter")))))
48
+ (add-before 'check 'remove-failing-test
49
+ (lambda _
50
+ (delete-file "test/tree_sitter/node_test.rb"))))))
51
+ (native-inputs (list ruby-minitest ruby-rake-compiler
52
+ ruby-rake-compiler-dock ruby-ruby-memcheck
53
+ tree-sitter-ruby))
54
+ (inputs (list tree-sitter))
55
+ (synopsis "Ruby bindings for Tree-Sitter")
56
+ (description "Ruby bindings for Tree-Sitter")
57
+ (home-page "https://www.github.com/Faveod/ruby-tree-sitter")
58
+ (license license:expat))))
59
+
60
+ (define-public tree-sitter-elisp
61
+ (let ((commit "e5524fdccf8c22fc726474a910e4ade976dfc7bb")
62
+ (revision "0"))
63
+ (package
64
+ (name "tree-sitter-elisp")
65
+ (version (git-version "0.1.4" revision commit))
66
+ (source
67
+ (origin
68
+ (method git-fetch)
69
+ (uri (git-reference
70
+ (url "https://github.com/Wilfred/tree-sitter-elisp")
71
+ (commit commit)))
72
+ (file-name (git-file-name name version))
73
+ (sha256
74
+ (base32 "1wyzfb27zgpvm4110jgv0sl598mxv5dkrg2cwjw3p9g2bq9mav5d"))))
75
+ (build-system tree-sitter-build-system)
76
+ (home-page "https://github.com/Wilfred/tree-sitter-elisp")
77
+ (synopsis "Tree-sitter grammar for Emacs Lisp")
78
+ (description "This package provides an Emacs Lisp grammar for the Tree-sitter library.")
79
+ (license license:expat))))
80
+
81
+ (concatenate-manifests
82
+ (list
83
+ (packages->manifest (list ruby-tree-sitter tree-sitter-elisp))
84
+ (specifications->manifest (list "ruby@3.1"
85
+ "ruby-webrick"
86
+ "ruby-rubocop"
87
+ "ruby-asciidoctor"))))
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.3
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: 2024-11-16 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
@@ -103,7 +103,9 @@ executables:
103
103
  extensions: []
104
104
  extra_rdoc_files: []
105
105
  files:
106
+ - ".document"
106
107
  - ".envrc"
108
+ - ".rdoc_options"
107
109
  - ".rubocop.yml"
108
110
  - CHANGELOG.md
109
111
  - Gemfile
@@ -115,22 +117,34 @@ files:
115
117
  - lib/elisp2any.rb
116
118
  - lib/elisp2any/asciidoc_renderer.rb
117
119
  - lib/elisp2any/asciidoc_renderer/index.adoc.erb
118
- - lib/elisp2any/code_block.rb
120
+ - lib/elisp2any/aside.rb
121
+ - lib/elisp2any/blanklines.rb
122
+ - lib/elisp2any/code.rb
123
+ - lib/elisp2any/codeblock.rb
124
+ - lib/elisp2any/comment.rb
125
+ - lib/elisp2any/commentary.rb
126
+ - lib/elisp2any/expression.rb
119
127
  - lib/elisp2any/file.rb
128
+ - lib/elisp2any/footer_line.rb
129
+ - lib/elisp2any/header_line.rb
120
130
  - lib/elisp2any/heading.rb
131
+ - lib/elisp2any/html_renderer.erb
121
132
  - lib/elisp2any/html_renderer.rb
122
133
  - lib/elisp2any/html_renderer/index.html.erb
123
134
  - lib/elisp2any/inline_code.rb
124
135
  - lib/elisp2any/line.rb
125
136
  - lib/elisp2any/node.rb
126
137
  - lib/elisp2any/paragraph.rb
138
+ - lib/elisp2any/section.rb
139
+ - lib/elisp2any/text.rb
127
140
  - lib/elisp2any/tree_sitter_parser.rb
128
141
  - lib/elisp2any/version.rb
129
142
  - manifest.scm
130
143
  - sig/elisp2any.gen.rbs
131
144
  - sig/elisp2any.rbs
132
145
  homepage: https://codeberg.org/gemmaro/elisp2any
133
- licenses: []
146
+ licenses:
147
+ - Apache-2.0
134
148
  metadata:
135
149
  rubygems_mfa_required: 'true'
136
150
  bug_tracker_uri: https://codeberg.org/gemmaro/elisp2any/issues
@@ -147,14 +161,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
147
161
  requirements:
148
162
  - - ">="
149
163
  - !ruby/object:Gem::Version
150
- version: 2.6.0
164
+ version: '3.1'
151
165
  required_rubygems_version: !ruby/object:Gem::Requirement
152
166
  requirements:
153
167
  - - ">="
154
168
  - !ruby/object:Gem::Version
155
169
  version: '0'
156
170
  requirements: []
157
- rubygems_version: 3.3.26
171
+ rubygems_version: 3.5.11
158
172
  signing_key:
159
173
  specification_version: 4
160
174
  summary: Converter from Emacs Lisp to some document markup
@@ -1,16 +0,0 @@
1
- require 'forwardable'
2
-
3
- module Elisp2any
4
- class CodeBlock
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
14
- def_delegators :@node, :content
15
- end
16
- end