elisp2any 0.0.3 → 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: 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