elisp2any 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cc2fe1682b2547fa70fda8d7f8690040fc0a0276437ac5faf2797b9e776c09f6
4
+ data.tar.gz: 7717313833f55ef58f5377ecdf09466176e8ef16ad792fe55f8a6e15a54195f0
5
+ SHA512:
6
+ metadata.gz: 7f0142bcf30fd78d46f280a4c4c7f1dbe29242bba02b45aff44afb0e045c31eaba2f22e9198e94d0c22a5a73a5bddcc155c76cb5291d9cd067b2cb563bf41d26
7
+ data.tar.gz: 868629bfbbcdd6812c007332f9137c218bcf5b28565191c9b6649986ccb0a5cf904610fb9ff35a5aa4f91d06b21ddbc006b354c6f3d7879b6d1126b382ccf683
data/.envrc ADDED
@@ -0,0 +1 @@
1
+ export ELISP2ANY_GUIX_USE_PROFILE_PATH=yes
data/.rubocop.yml ADDED
@@ -0,0 +1,19 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+ NewCops: enable
4
+ DisabledByDefault: true
5
+
6
+ Style/StringLiterals:
7
+ Enabled: true
8
+ EnforcedStyle: single_quotes
9
+
10
+ Style/StringLiteralsInInterpolation:
11
+ Enabled: true
12
+ EnforcedStyle: single_quotes
13
+
14
+ Layout/LineLength:
15
+ Enabled: false
16
+
17
+ Style/FrozenStringLiteralComment:
18
+ Enabled: true
19
+ EnforcedStyle: never
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.0.1] - 2023-10-27
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake', '~> 13.0'
6
+ gem 'test-unit', '~> 3.0'
7
+ gem 'rubocop', '~> 1.21'
data/LICENSE.txt ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # Elisp2any
2
+
3
+ ## Installation
4
+
5
+ Install the gem and add to the application's `Gemfile` by executing `bundle add elisp2any`.
6
+ If Bundler is not being used to manage dependencies, install the gem by executing `gem install elisp2any`.
7
+
8
+ ## Usage
9
+
10
+ Currently the command line tool only supports HTML rendering.
11
+
12
+ ```shell
13
+ $ elisp2any --input /path/to/input/file --output /path/to/output/file
14
+ ```
15
+
16
+ ## Development
17
+
18
+ After checking out the repo, run `bin/setup` to install dependencies.
19
+ Then, run `rake test` to run the tests.
20
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
21
+
22
+ To install this gem onto your local machine, run `bundle exec rake install`.
23
+ To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
24
+
25
+ ## Contributing
26
+
27
+ Bug reports and pull requests are welcome.
28
+
29
+ ## License
30
+
31
+ This package is provided under the Apache License.
32
+ See `LICENSE.txt` for details.
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ require 'rubocop/rake_task'
11
+
12
+ RuboCop::RakeTask.new
13
+
14
+ task default: %i[test rubocop]
15
+
16
+ require 'rdoc/task'
17
+ RDoc::Task.new do |rdoc|
18
+ readme = 'README.md'
19
+ rdoc.main = readme
20
+ rdoc.rdoc_files.include(readme, 'lib/**/*.rb')
21
+ end
22
+
23
+ desc 'start API document server'
24
+ task :serve_api do
25
+ serve('html')
26
+ end
27
+
28
+ desc 'generate type signatures'
29
+ task :sig do
30
+ sh 'typeprof', *Dir['lib/**/*.rb'], 'sig/elisp2any.rbs', '-o', 'sig/elisp2any.gen.rbs'
31
+ end
32
+
33
+ desc 'start HTML fixture server'
34
+ task :serve_fixture do
35
+ serve('fixtures/init')
36
+ end
37
+
38
+ def serve(path)
39
+ sh 'ruby', '-run', '-e', 'httpd', path
40
+ end
41
+
42
+ file 'fixtures/init/adoc/index.html' => 'fixtures/init/index.adoc' do |t|
43
+ sh 'asciidoctor', t.source, '--out-file', t.name
44
+ end
data/exe/elisp2any ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/elisp2any"
3
+ require_relative "../lib/elisp2any/html_renderer"
4
+ require 'optparse'
5
+ input = $stdin
6
+ output = $stdout
7
+ OptionParser.new do |parser|
8
+ parser.on('--input=PATH') do |path|
9
+ input = File.open(path)
10
+ end
11
+ parser.on('--output=PATH') do |path|
12
+ output = File.open(path, 'w')
13
+ end
14
+ end.parse!
15
+ file = Elisp2any::File.parse(input)
16
+ renderer = Elisp2any::HTMLRenderer.new(file)
17
+ output.write(renderer.render)
data/fixtures/init.el ADDED
@@ -0,0 +1,64 @@
1
+ ;;; init.el --- Emacs configuration
2
+
3
+ ;;; Commentary:
4
+
5
+ ;; This is my Emacs configuration file. It sets up various
6
+ ;; preferences, packages, and keybindings.
7
+
8
+ ;; This is a second paragraph.
9
+
10
+ ;;; Code:
11
+
12
+ ;;;; Package
13
+
14
+ ;;;;; Initialize package manager
15
+
16
+ (require 'package)
17
+ (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
18
+ (package-initialize)
19
+ (unless package-archive-contents
20
+ (package-refresh-contents))
21
+
22
+ ;; This is a sample sentence including `inline-code'.
23
+
24
+ ;;;;; Install and configure `use-package' for managing packages
25
+
26
+ (unless (package-installed-p 'use-package)
27
+ (package-install 'use-package))
28
+ (require 'use-package)
29
+ (setq use-package-always-ensure t)
30
+
31
+ ;;;; Basic UI and keybindings
32
+
33
+ (setq inhibit-startup-screen t)
34
+ (scroll-bar-mode -1)
35
+ (tool-bar-mode -1) ;sample comment
36
+ (menu-bar-mode -1)
37
+ (column-number-mode 1)
38
+
39
+ (global-hl-line-mode 1) ;sample comment 1
40
+ ;sample comment 2
41
+
42
+ ;;;; Font
43
+
44
+ (set-face-attribute 'default nil :font "DejaVu Sans Mono-11")
45
+
46
+ ;;;; Theme
47
+
48
+ (use-package dracula-theme
49
+ :config
50
+ (load-theme 'dracula t))
51
+
52
+ ;;;; Hooks
53
+
54
+ (add-hook 'prog-mode-hook #'prettify-symbols-mode)
55
+
56
+ ;;;; Finalize and run
57
+
58
+ (require 'server)
59
+ (unless (server-running-p)
60
+ (server-start))
61
+
62
+ (provide 'init)
63
+
64
+ ;;; init.el ends here
@@ -0,0 +1,15 @@
1
+ = <%= name %>
2
+
3
+ <%= synopsis %>
4
+
5
+ == Commentary
6
+
7
+ <% commentary.each do |paragraph| %>
8
+ <%= render_paragraph(paragraph) %>
9
+ <% end %>
10
+
11
+ == Code
12
+
13
+ <% code.each do |block| %>
14
+ <%= render_block(block, level: 2) %>
15
+ <% end %>
@@ -0,0 +1,57 @@
1
+ require 'forwardable'
2
+
3
+ module Elisp2any
4
+ # This renderer is experimental.
5
+ class AsciiDocRenderer
6
+ def initialize(file)
7
+ @file = file
8
+ end
9
+
10
+ def render
11
+ erb_renderer('index.adoc.erb')
12
+ end
13
+
14
+ private
15
+
16
+ def render_paragraph(paragraph)
17
+ adoc = ''
18
+ paragraph.each do |line|
19
+ line.each do |chunk|
20
+ case chunk
21
+ when InlineCode
22
+ adoc << "``#{chunk.content}``"
23
+ when String
24
+ adoc << chunk
25
+ end
26
+ end
27
+ end
28
+ adoc
29
+ end
30
+
31
+ def render_block(block, level:)
32
+ adoc = ''
33
+ case block
34
+ when Heading
35
+ prefix = '=' * (level + block.level - 1)
36
+ adoc << "#{prefix} #{block.content}"
37
+ when Paragraph
38
+ adoc << render_paragraph(block)
39
+ when CodeBlock
40
+ adoc << <<~ADOC
41
+ ----
42
+ #{block.content}
43
+ ----
44
+ ADOC
45
+ end
46
+ adoc
47
+ end
48
+
49
+ def erb_renderer(path)
50
+ source = ::File.read(::File.join(__dir__, 'asciidoc_renderer', path))
51
+ ERB.new(source).result(binding)
52
+ end
53
+
54
+ extend Forwardable
55
+ def_delegators :@file, :name, :synopsis, :commentary, :code
56
+ end
57
+ end
@@ -0,0 +1,16 @@
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
@@ -0,0 +1,31 @@
1
+ require_relative 'node'
2
+ require_relative 'tree_sitter_parser'
3
+
4
+ module Elisp2any
5
+ class File
6
+ attr_reader :name, :synopsis, :commentary, :code
7
+
8
+ def self.parse(source)
9
+ source = source.respond_to?(:read) ? source.read : source
10
+ ts_node = TreeSitterParser.parse(source)
11
+ first_heading, second_heading, *nodes, last_heading = Node.from_tree_sitter(source, ts_node)
12
+ name, synopsis = first_heading.name_and_synopsis
13
+ second_heading.commentary? or raise Error, "no commentary heading: #{second_heading.inspect}"
14
+ commentary = []
15
+ until nodes.empty?
16
+ (node = nodes.shift).code? and break
17
+ commentary << node
18
+ end
19
+ code = nodes
20
+ last_heading.final_name == name or raise Error, 'different names'
21
+ new(name: name, synopsis: synopsis, commentary: commentary, code: code)
22
+ end
23
+
24
+ def initialize(name:, synopsis:, commentary:, code:) # :nodoc:
25
+ @name = name
26
+ @synopsis = synopsis
27
+ @commentary = commentary
28
+ @code = code
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,40 @@
1
+ require 'strscan'
2
+ require 'forwardable'
3
+
4
+ module Elisp2any
5
+ class Heading
6
+ attr_reader :level, :content
7
+
8
+ def initialize(node, level, content) # :nodoc:
9
+ @node = node
10
+ @level = level
11
+ @content = content
12
+ end
13
+
14
+ # Returns nil if failed
15
+ def name_and_synopsis # :nodoc:
16
+ scanner = StringScanner.new(@content)
17
+ name = scanner.scan_until(/\.el/) or return
18
+ name = name[nil...-3] or return
19
+ scanner.skip(/\s+---\s+/) or return
20
+ scanner.skip(/(?<synopsis>.+?)(:?\s+-\*- .+? -\*-)?\Z/) or return
21
+ synopsis = scanner[:synopsis]
22
+ return name, synopsis
23
+ end
24
+
25
+ def commentary? # :nodoc:
26
+ @level == 1 && @content == 'Commentary:'
27
+ end
28
+
29
+ def code? # :nodoc:
30
+ @level == 1 && @content == 'Code:'
31
+ end
32
+
33
+ def final_name # :nodoc:
34
+ @content.match(/\A(?<name>.+?)\.el ends here\Z/)[:name]
35
+ end
36
+
37
+ extend Forwardable
38
+ def_delegator :@level, :<=>
39
+ end
40
+ end
@@ -0,0 +1,23 @@
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="https://unpkg.com/mvp.css">
8
+ </head>
9
+ <body>
10
+ <main>
11
+ <h1><%= name %></h1>
12
+ <p><%= synopsis %></p>
13
+ <h3>Commentary</h3>
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
+ </body>
23
+ </html>
@@ -0,0 +1,62 @@
1
+ require 'erb'
2
+ require 'forwardable'
3
+ require 'cgi/util'
4
+ require_relative 'inline_code'
5
+ require_relative 'heading'
6
+ require_relative 'paragraph'
7
+ require_relative 'code_block'
8
+
9
+ module Elisp2any
10
+ class HTMLRenderer
11
+ def initialize(file)
12
+ @file = file
13
+ end
14
+
15
+ def render
16
+ erb_render('index.html.erb')
17
+ end
18
+
19
+ private
20
+
21
+ def render_paragraph(paragraph)
22
+ html = ''
23
+ paragraph.each do |line|
24
+ line.each do |chunk|
25
+ case chunk
26
+ when InlineCode
27
+ html << "<code>#{h(chunk.content)}</code>"
28
+ when String
29
+ html << h(chunk)
30
+ end
31
+ end
32
+ end
33
+ html
34
+ end
35
+
36
+ def render_block(block, level:)
37
+ html = ''
38
+ case block
39
+ when Heading
40
+ name = "h#{level + block.level - 1}"
41
+ html << "<#{name}>#{h(block.content)}</#{name}>"
42
+ when Paragraph
43
+ html << render_paragraph(block)
44
+ when CodeBlock
45
+ html << "<pre><code>#{h(block.content)}</code></pre>"
46
+ end
47
+ html
48
+ end
49
+
50
+ def erb_render(path)
51
+ source = ::File.read(::File.join(__dir__, 'html_renderer', path))
52
+ ERB.new(source).result(binding)
53
+ end
54
+
55
+ def h(string)
56
+ CGI.escape_html(string)
57
+ end
58
+
59
+ extend Forwardable
60
+ def_delegators :@file, :name, :synopsis, :commentary, :code
61
+ end
62
+ end
@@ -0,0 +1,9 @@
1
+ module Elisp2any
2
+ class InlineCode
3
+ attr_reader :content
4
+
5
+ def initialize(content)
6
+ @content = content
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,36 @@
1
+ require 'strscan'
2
+ require 'forwardable'
3
+ require_relative 'inline_code'
4
+
5
+ module Elisp2any
6
+ class Line
7
+ def initialize(chunks) # :nodoc:
8
+ @chunks = chunks
9
+ end
10
+
11
+ def self.parse(string) # :nodoc:
12
+ scanner = StringScanner.new(string)
13
+ chunks = []
14
+
15
+ until scanner.eos?
16
+ if scanner.skip(/`(?<content>[^']+)'/)
17
+ chunks << InlineCode.new(scanner[:content])
18
+ else
19
+ chunk = scanner.getch
20
+ if (last = chunks.last).is_a?(String)
21
+ last << chunk
22
+ else
23
+ chunks << chunk
24
+ end
25
+ end
26
+ end
27
+
28
+ new(chunks)
29
+ end
30
+
31
+ extend Forwardable
32
+ def_delegators :@chunks, :each
33
+
34
+ include Enumerable
35
+ end
36
+ end
@@ -0,0 +1,63 @@
1
+ require_relative 'code_block'
2
+ require_relative 'line'
3
+ require_relative 'heading'
4
+ require_relative 'paragraph'
5
+ require 'strscan'
6
+
7
+ module Elisp2any
8
+ class Node
9
+ attr_reader :range # :nodoc:
10
+ attr_reader :content
11
+
12
+ def self.from_tree_sitter(source, ts_node) #:nodoc:
13
+ nodes = []
14
+ (0 ... ts_node.child_count).map { |index| ts_node[index] }.each do |top_level_node|
15
+ range = top_level_node.start_byte .. top_level_node.end_byte
16
+ content = source.byteslice(range)
17
+ node = Node.new(content, range)
18
+ case top_level_node.type
19
+ when :comment
20
+ scanner = StringScanner.new(content)
21
+ scanner.skip(';') or raise Error, 'no semicolon for comment'
22
+ if scanner.skip(';')
23
+ if (level = scanner.skip(/;+/))
24
+ scanner.skip(' ') or raise Error, 'no space after heading semicolons'
25
+ nodes << Heading.new(node, level, scanner.rest.chomp)
26
+ elsif scanner.skip("\n")
27
+ # nop
28
+ else
29
+ scanner.skip(' ') or raise Error, "no space after semicolons: #{scanner.inspect}"
30
+ line = Line.parse(scanner.rest)
31
+ if (last_node = nodes.last) && last_node.is_a?(Paragraph) && last_node.end_row + 1 == top_level_node.start_point.row
32
+ last_node << line
33
+ else
34
+ paragraph = Paragraph.new(node, [line], top_level_node.end_point.row)
35
+ nodes << paragraph
36
+ end
37
+ end
38
+ else
39
+ (last_node = nodes.last) && last_node.is_a?(CodeBlock) or raise Error, 'no prior code for single semicolon comment'
40
+ last_node.append(source, range.end)
41
+ end
42
+ else
43
+ if (last_node = nodes.last) && last_node.is_a?(CodeBlock)
44
+ last_node.append(source, range.end)
45
+ else
46
+ nodes << CodeBlock.new(node)
47
+ end
48
+ end
49
+ end
50
+ nodes
51
+ end
52
+
53
+ def append(source, end_byte) # :nodoc:
54
+ @range = range = @range.begin .. end_byte
55
+ @content = source.byteslice(range)
56
+ end
57
+
58
+ def initialize(content, range) # :nodoc:
59
+ @content = content
60
+ @range = range
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,23 @@
1
+ require 'forwardable'
2
+
3
+ module Elisp2any
4
+ class Paragraph
5
+ attr_reader :end_row # :nodoc:
6
+
7
+ def initialize(node, lines, end_row) # :nodoc:
8
+ @node = node
9
+ @lines = lines
10
+ @end_row = end_row
11
+ end
12
+
13
+ def code?
14
+ false
15
+ end
16
+
17
+ extend Forwardable
18
+ def_delegators :@lines, :empty?, :clear, :<<, :each
19
+ def_delegators :@node, :adjucent?
20
+
21
+ include Enumerable
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ require 'tree_sitter'
2
+
3
+ module Elisp2any
4
+ class TreeSitterParser # :nodoc:
5
+ def self.parse(source)
6
+ new(source).parse
7
+ end
8
+
9
+ def parse
10
+ @node = parser.parse_string(nil, @source).root_node
11
+ end
12
+
13
+ private
14
+
15
+ def initialize(source)
16
+ @source = source
17
+ end
18
+
19
+ def parser
20
+ TreeSitter::Parser.new.tap do |p|
21
+ p.language = TreeSitter::Language.load('elisp', elisp_library)
22
+ end
23
+ end
24
+
25
+ def elisp_library
26
+ shared_object = 'libtree-sitter-elisp.so'
27
+
28
+ # Set this env var for Guix shell environment or shared object file is not found
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)
32
+ end
33
+
34
+ shared_object
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module Elisp2any
2
+ VERSION = '0.0.1'
3
+ end
data/lib/elisp2any.rb ADDED
@@ -0,0 +1,6 @@
1
+ require_relative 'elisp2any/version'
2
+ require_relative 'elisp2any/file'
3
+
4
+ module Elisp2any
5
+ class Error < StandardError; end
6
+ end
data/manifest.scm ADDED
@@ -0,0 +1,6 @@
1
+ (specifications->manifest (list "ruby@3.1"
2
+ "ruby-tree-sitter"
3
+ "tree-sitter-elisp"
4
+ "ruby-webrick"
5
+ "ruby-rubocop"
6
+ "ruby-asciidoctor"))
@@ -0,0 +1,112 @@
1
+ # TypeProf 0.21.3
2
+
3
+ # Classes
4
+ module Elisp2any
5
+ VERSION: String
6
+
7
+ class AsciiDocRenderer
8
+ extend Forwardable
9
+ @file: untyped
10
+
11
+ def initialize: (untyped file) -> void
12
+ def render: -> untyped
13
+
14
+ private
15
+ def render_paragraph: (untyped paragraph) -> String
16
+ def render_block: (untyped block, level: untyped) -> String
17
+ def erb_renderer: (String path) -> untyped
18
+ end
19
+
20
+ class CodeBlock
21
+ extend Forwardable
22
+ @node: Node
23
+
24
+ def initialize: (Node node) -> void
25
+ def append: (untyped source, untyped end_byte) -> untyped
26
+ end
27
+
28
+ class InlineCode
29
+ attr_reader content: untyped
30
+ def initialize: (untyped content) -> void
31
+ end
32
+
33
+ class Line
34
+ extend Forwardable
35
+ include Enumerable
36
+ @chunks: Array[(InlineCode | String)?]
37
+
38
+ def initialize: (Array[(InlineCode | String)?] chunks) -> void
39
+ def self.parse: (String string) -> Line
40
+ end
41
+
42
+ class Heading
43
+ extend Forwardable
44
+ @node: Node
45
+
46
+ attr_reader level: Integer
47
+ attr_reader content: String
48
+ def initialize: (Node node, Integer level, String content) -> void
49
+ def name_and_synopsis: -> [untyped, untyped]?
50
+ def commentary?: -> bool
51
+ def code?: -> bool
52
+ def final_name: -> String?
53
+ end
54
+
55
+ class Paragraph
56
+ extend Forwardable
57
+ include Enumerable
58
+ @node: Node
59
+ @lines: [Line]
60
+
61
+ attr_reader end_row: untyped
62
+ def initialize: (Node node, [Line] lines, untyped end_row) -> void
63
+ def code?: -> false
64
+ end
65
+
66
+ class Node
67
+ attr_reader range: Range
68
+ attr_reader content: untyped
69
+ def self.from_tree_sitter: (untyped source, untyped ts_node) -> (Array[CodeBlock | Heading | Paragraph])
70
+ def append: (untyped source, untyped end_byte) -> untyped
71
+ def initialize: (untyped content, Range range) -> void
72
+ end
73
+
74
+ class TreeSitterParser
75
+ @source: untyped
76
+ @node: untyped
77
+
78
+ def self.parse: (untyped source) -> untyped
79
+ def parse: -> untyped
80
+
81
+ private
82
+ def initialize: (untyped source) -> void
83
+ def parser: -> untyped
84
+ def elisp_library: -> String
85
+ end
86
+
87
+ class File
88
+ attr_reader name: nil
89
+ attr_reader synopsis: nil
90
+ attr_reader commentary: Array[(CodeBlock | Heading | Paragraph)?]
91
+ attr_reader code: Array[CodeBlock | Heading | Paragraph]
92
+ def self.parse: (untyped source) -> File
93
+ def initialize: (name: nil, synopsis: nil, commentary: Array[(CodeBlock | Heading | Paragraph)?], code: Array[CodeBlock | Heading | Paragraph]) -> void
94
+ end
95
+
96
+ class HTMLRenderer
97
+ extend Forwardable
98
+ @file: untyped
99
+
100
+ def initialize: (untyped file) -> void
101
+ def render: -> String
102
+
103
+ private
104
+ def render_paragraph: (untyped paragraph) -> String
105
+ def render_block: (untyped block, level: untyped) -> String
106
+ def erb_render: (String path) -> String
107
+ def h: (untyped string) -> String
108
+ end
109
+
110
+ class Error < StandardError
111
+ end
112
+ end
data/sig/elisp2any.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Elisp2any
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: elisp2any
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - gemmaro
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-10-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby_tree_sitter
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.20.8.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.20.8.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: webrick
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubocop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: asciidoctor
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: elisp2any is a command line tool and library for converting Emacs Lisp
70
+ source to some document markup, such as HTML or Markdown.
71
+ email:
72
+ - gemmaro.dev@gmail.com
73
+ executables:
74
+ - elisp2any
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".envrc"
79
+ - ".rubocop.yml"
80
+ - CHANGELOG.md
81
+ - Gemfile
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - exe/elisp2any
86
+ - fixtures/init.el
87
+ - lib/elisp2any.rb
88
+ - lib/elisp2any/asciidoc_renderer.rb
89
+ - lib/elisp2any/asciidoc_renderer/index.adoc.erb
90
+ - lib/elisp2any/code_block.rb
91
+ - lib/elisp2any/file.rb
92
+ - lib/elisp2any/heading.rb
93
+ - lib/elisp2any/html_renderer.rb
94
+ - lib/elisp2any/html_renderer/index.html.erb
95
+ - lib/elisp2any/inline_code.rb
96
+ - lib/elisp2any/line.rb
97
+ - lib/elisp2any/node.rb
98
+ - lib/elisp2any/paragraph.rb
99
+ - lib/elisp2any/tree_sitter_parser.rb
100
+ - lib/elisp2any/version.rb
101
+ - manifest.scm
102
+ - sig/elisp2any.gen.rbs
103
+ - sig/elisp2any.rbs
104
+ homepage:
105
+ licenses: []
106
+ metadata:
107
+ rubygems_mfa_required: 'true'
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 2.6.0
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubygems_version: 3.3.26
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Converter from Emacs Lisp to some document markup
127
+ test_files: []