A => .gitignore +3 -0
@@ 1,3 @@
+Gemfile.lock
+/doc/
+/pkg/
A => .rubocop.yml +11 -0
@@ 1,11 @@
+AllCops:
+ NewCops: enable
+
+Layout/LineLength:
+ Max: 80
+
+Style/MethodCallWithArgsParentheses:
+ Enabled: True
+
+Metrics/MethodLength:
+ CountAsOne: ['array', 'hash', 'heredoc']
A => Gemfile +5 -0
@@ 1,5 @@
+# frozen_string_literal: true
+
+source 'https://rubygems.org'
+
+gemspec
A => LICENSE.txt +21 -0
@@ 1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2022 Felix Freeman <libsys@hacktivista.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
A => README.md +53 -0
@@ 1,53 @@
+# Tilt::EmacsOrg
+
+A native [Org Mode](http://orgmode.org/) parser for Tilt.
+
+## Installation
+
+1. Install `emacs`
+2. Install the `tilt-emacs_org` gem
+3. Done! Now Tilt has native Emacs Org support
+
+## Usage
+
+```ruby
+require 'tilt/emacs_org'
+template = Tilt.new('file.org', 1, { setupfile: 'setup.org' })
+template.title # document title
+template.head # scripts, links and styles on head
+template.render # body of HTML document
+```
+
+This is an example usage, render can vary depending on options. All options are
+optional.
+
+### Options
+
+#### `:format => :body_only|:body_with_resources|:full`
+
+Defaults to `:body_only`.
+
+- `:body_only` renders body only, discarding head section of the document.
+- `:body_with_resources` renders body with head script, link and style elements
+ on top of the rest of the body.
+- `:full` renders a full HTML document, just as emacs exports it.
+
+You can always access head script, link and style elements by calling
+`EmacsOrgTemplate#head`, and document title by calling `EmacsOrgTemplate#title`.
+
+#### `:setupfile`
+
+If set, include a `#+SETUPFILE` on top of the document. Useful for default
+options.
+
+## Contributing
+
+Source code is available on <https://git.hacktivista.org/tilt-emacs_org>.
+
+Bug reports and patches are welcome on
+<https://lists.hacktivista.org/hacktivista-dev>.
+
+## License
+
+The gem is available as libre software under the terms of the
+[MIT License](https://opensource.org/licenses/MIT).
A => Rakefile +16 -0
@@ 1,16 @@
+# frozen_string_literal: true
+
+require 'bundler/gem_tasks'
+require 'rake/testtask'
+
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'test'
+ t.libs << 'lib'
+ t.test_files = FileList['test/**/*_test.rb']
+end
+
+require 'rubocop/rake_task'
+
+RuboCop::RakeTask.new
+
+task default: %i[test rubocop]
A => lib/tilt/emacs_org.rb +67 -0
@@ 1,67 @@
+# frozen_string_literal: true
+
+require 'nokogiri'
+require 'open3'
+require 'tempfile'
+require 'tilt'
+
+# Tilt.
+module Tilt
+ # Native Emacs Org templating engine for Tilt.
+ class EmacsOrgTemplate < Template
+ metadata[:mime_type] = 'text/html'
+
+ def head
+ @head ||= doc.at_css('head').css('script, link, style').inject do
+ |elm, head| "#{head}#{elm}\n"
+ end
+ end
+
+ def body
+ @body ||= doc.at_css('body').inner_html
+ end
+
+ def title
+ @title ||= doc.at_css('title').content
+ end
+
+ protected
+
+ def prepare; end
+
+ def evaluate(_scope, _locals, &_block)
+ case @options[:format]
+ when :full
+ emacs_org_export
+ when :body_with_resources
+ head + body
+ else # :body_only
+ body
+ end
+ end
+
+ def doc
+ @doc ||= Nokogiri::HTML(emacs_org_export)
+ end
+
+ def emacs_org_export
+ @emacs_org_export ||= Tempfile.create(['', '.org']) do |f|
+ f.write("#+SETUPFILE: #{@options[:setupfile]}\n") \
+ if options.include?(:setupfile)
+ f.write(@data)
+ f.rewind
+ Open3.capture3(
+ <<~SH
+ emacs --batch --quick --load org --eval='
+ (progn
+ (find-file-literally "#{f.path}")
+ (org-html-export-as-html)
+ (message "%s" (buffer-string)))'
+ SH
+ )[1]
+ end
+ end
+ end
+
+ register(EmacsOrgTemplate, 'org')
+end
A => test/.rubocop.yml +6 -0
@@ 1,6 @@
+inherit_from: ../.rubocop.yml
+
+require: rubocop-minitest
+
+Metrics/ClassLength:
+ Enabled: False
A => test/emacs_org_test.rb +90 -0
@@ 1,90 @@
+# frozen_string_literal: true
+
+require 'nokogiri'
+require 'tempfile'
+require 'test_helper'
+
+# Tilt.
+module Tilt
+ # EmacsOrgTemplate tests.
+ class EmacsOrgTemplateTest < Minitest::Test
+ def setupfile
+ @setupfile ||= begin
+ file = Tempfile.new(['', '.org'])
+ file.write('#+OPTIONS: toc:nil num:nil')
+ file.rewind
+ file.path
+ end
+ end
+
+ def test_render__without_setupfile
+ doc = render_and_inspect { '* anything' }
+ assert_equal('Table of Contents', doc.at_css('h2').inner_html)
+ end
+
+ def test_render__body_only
+ doc = render_and_inspect({ setupfile: setupfile }) { '* jelou' }
+ assert_equal('jelou', doc.at_css('h2').inner_html)
+ assert_nil(doc.at_css('head'))
+ end
+
+ def test_render__body_with_resources
+ doc = render_and_inspect(
+ { setupfile: setupfile, format: :body_with_resources }
+ ) do
+ <<~ORG
+ #+TITLE: Title
+ * Content
+ ORG
+ end
+ assert_equal('Content', doc.at_css('h2').inner_html)
+ assert_nil(doc.at_css('head title'))
+ end
+
+ def test_render__full
+ doc = render_and_inspect(
+ { setupfile: setupfile, format: :full }
+ ) do
+ <<~ORG
+ #+TITLE: Title
+ #+HTML_HEAD: <link rel="stylesheet" type="text/css" href="style.css" />
+ ORG
+ end
+ assert_equal('Title', doc.at_css('head title').inner_html)
+ assert_equal('style.css', doc.at_css('head link').attribute('href').value)
+ assert_equal('Title', doc.at_css('body h1').inner_html)
+ end
+
+ def test_head
+ template = Tilt['org'].new do
+ <<~ORG
+ #+HTML_HEAD: <script type="text/css" src="test_script.js"></script>
+ Sup
+ ORG
+ end
+ assert_includes(
+ template.head,
+ '<script type="text/css" src="test_script.js"></script>'
+ )
+ end
+
+ def test_title
+ template = Tilt['org'].new { '#+TITLE: My title' }
+ assert_equal('My title', template.title)
+ end
+
+ def test_body
+ template = Tilt['org'].new { 'My body' }
+ assert_includes(template.body, 'My body')
+ refute_includes(template.body, '<head>')
+ end
+
+ protected
+
+ def render_and_inspect(options = {}, &block)
+ Nokogiri::HTML(
+ Tilt['org'].new(nil, 1, options) { block.call }.render
+ )
+ end
+ end
+end
A => test/test_helper.rb +6 -0
@@ 1,6 @@
+# frozen_string_literal: true
+
+$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
+require 'tilt/emacs_org'
+
+require 'minitest/autorun'
A => tilt-emacs_org.gemspec +30 -0
@@ 1,30 @@
+# frozen_string_literal: true
+
+require_relative 'lib/tilt/emacs_org'
+
+Gem::Specification.new do |spec|
+ spec.name = 'tilt-emacs_org'
+ spec.version = '0.1.0'
+ spec.authors = ['Felix Freeman']
+ spec.email = ['libsys@hacktivista.jrg']
+
+ spec.summary = 'Native Emacs Org templating engine for Tilt.'
+ spec.homepage = 'https://git.hacktivista.org/tilt-emacs_org'
+ spec.license = 'MIT'
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
+
+ spec.metadata['homepage_uri'] = spec.homepage
+ spec.metadata['source_code_uri'] = spec.homepage
+ spec.metadata['rubygems_mfa_required'] = 'true'
+
+ spec.files = Dir['lib/**/*.rb']
+ spec.require_paths = ['lib']
+
+ spec.add_dependency('nokogiri')
+ spec.add_dependency('tilt')
+
+ spec.add_development_dependency('minitest')
+ spec.add_development_dependency('rake')
+ spec.add_development_dependency('rubocop')
+ spec.add_development_dependency('rubocop-minitest')
+end