~hacktivista/tilt-emacs_org

f7e30ff4143eb19434fe06a29af4cc97ef54d46d — Felix Freeman 2 years ago
Initial commit. Working Tilt::EmacsOrg
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