Top Level Namespace

Defined Under Namespace

Modules: Jekyll, Kramdown

Constant Summary collapse

FATAL_FURY =
true
UPDATE_RUBY =
"Please use DistorteD with Ruby 2.7.0 or later"
MD_IMAGE_REGEX =

Replace standard Markdown image syntax with instances of DistorteD via its Liquid tag.

SYNTAX

I’m calling individual media elements “images” here because I’m overloading the Markdown image syntax to express them. There is no dedicated syntax for other media types in any flavor of Markdown as of 2019, so that seemed like the cleanest and sanest way to deal with non-images in DistorteD.

DistorteD::Invoker will do the media type inspection and handling once we get into Liquid Land. This is the approach suggested by CommonMark: talk.commonmark.org/t/embedded-audio-and-video/441/15

Media elements will display as a visibly related group when two or more Markdown image tags exist in consecutive Markdown unordered (e.g. *-+) list item or ordered (e.g. 1.) list item lines. Their captions should be hidden until opened in a lightbox or as a tooltip on desktop browsers via ::hover state.

The inspiration for this display can be seen in the handling of two, three, or four images in any single post on Tw*tter. DistorteD expands on the concept by supporting things such as groups of more than four media elements and elements of heterogeneous media types.

Standalone image elements (not contained in list item) should be displayed as single solo elements spanning the entire usable width of the container element, whatever that happens to be at the point where our regex excised a block of Markdown for us to work with.

TECHNICAL CONSIDERATIONS

Jekyll processes Liquid templates before processing page/post Markdown, so we can’t rely on customizing the Markdown renderer’s ‘:img` element output as a means to invoke DistorteD. jekyllrb.com/tutorials/orderofinterpretation/

Prefer the POSIX-style bracket expressions (e.g. [[:digit:]]) over basic character classes (e.g. d) in regex because they match Unicode instead of just ASCII. ruby-doc.org/core/Regexp.html#class-Regexp-label-Character+Classes

INLINE ATTRIBUTE LISTS

Support additional arguments passed to DistorteD via Kramdown-style inline attribute lists. kramdown.gettalong.org/syntax.html#images kramdown.gettalong.org/syntax.html#inline-attribute-lists

IALs can be on the same line or on a line before or after their associated flow element. In ambiguous situations (flow elements both before and after an IAL), the IAL applies to the flow element before it. All associated elements and IALs must be on contiguous lines.

This page provides a regex for parsing IALs, Section 5.3: golem.ph.utexas.edu/~distler/maruku/proposal.html

SOLUTION

A ‘pre_render` hook uses this regex to process Markdown source files and replace instances of the Markdown image syntax with instances of DistorteD’s Liquid tags. Single images will be replaced with distorted %. Multiple list-item images will be replaced with a distort % block.

By doing with with a regex (sorry!!) I hope to avoid a hard dependency on any one particular Markdown engine. Though I only support Kramdown for now, any engine that supports IALs should be fine.

High-level explanation of what we intend to match:

=> line_before_image # Iff preceded by a blank line! (optional_list_item)? ![alt](image)=> same_line => next_consecutive_line Repeat both preceding matches (together) any number of times to parse a distort % block. See inline comments below for more detail.

%r&
  # Matching group of a single image tag.
  (
    # Optional preceding-line attribute list.
    (
      # One blank line, because:
      # "If a block IAL is directly after and before a block-level element,
      #  it is applied to preceding element."  —Kramdown BAL docs
      #
      # /\R/ - A linebreak: \n, \v, \f, \r \u0085 (NEXT LINE),
      #   \u2028 (LINE SEPARATOR), \u2029 (PARAGRAPH SEPARATOR) or \r\n.
      ^$\R
      # Any amount of blank space on the line before block IAL
      ^[[:blank:]]*
      # IAL regex from Section 5.3:
      # https://golem.ph.utexas.edu/~distler/maruku/proposal.html
      (?<block_ial_before>\{:(\\\}|[^\}])*\})
      # Any amount of trailing whitespace followed by a newline.
      [[:blank:]]*$\R
    )?  # Match all of that, or nothing.
    # Begin matching the line that contains an image.
    ^
    # Match anything that might be between that start-of-line
    # and the first character (!) of an image.
    (
      # From Gruber's original Markdown page:
      # "List markers typically start at the left margin, but may be indented
      # by up to three spaces."
      # Include both unordered (-+*) and ordered (\d\. like `1.`) lists.
      (?<li>[ ]{0,3}[-\*\+|\d\.]
      # Support an optional IAL for the list element as shown in Kramdown docs:
      # https://kramdown.gettalong.org/syntax.html#lists
      # Ctrl+F "{:.cls}"
      (?<li_ial>\{:(\\\}|[^\}])*\})?
      # "List markers must be followed by one or more spaces or a tab."
      # https://daringfireball.net/projects/markdown/syntax#list
      ([ ]+|\t))
    )?  # Although any preceding elements are optional!
    # Match Markdown image syntax:
    #   ![alt text](/some/path/to/image.png 'title text'){:other="options"}
    #   beginning with the alt tag:
    !\[(?<alt>(\\[[:print:]]|[^\]])*)\]
    # Continuing with img src as anything after the '(' and before ')' or
    # before anything that could be a title.
    # Assume titles will be quoted.
    \((?<src>(\\[[:print:]]|[^'")]+))
    # Title is optional.
    # Ignore double-quotes in single-quoted titles and single-quotes
    # in double-quoted titles otherwise we can't use contractions.
    # Don't include the title's opening or closing quotes in the capture.
    ('(?<title>(\\[[:print:]]|[^']*))'|"(?<title>(\\[[:print:]]|[^"]*))")?
    # The closing ')' will always be present, title or no.
    \)
    # Optional IAL on the same line as the :img element **with no space between them**:
    # "A span IAL (or two or more span IALs) has to be put directly after
    #  the span-level element to which it should be applied, no additional
    #  character is allowed between, otherwise it is ignored and only
    #  removed from the output."  —Kramdown IAL docs
    (?<span_ial>\{:(\\\}|[^\}])*\})*[[:print:]]*$\R
    # Also support optional BALs on the lines following the image.
    (^[[:blank:]]*(?<block_ial_after>\{:(\\\}|[^\}])*\})+$\R)*
  )+  # Capture multiple images together for block display.
&x

Instance Method Summary collapse

Instance Method Details

#md_injectionObject



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/distorted-jekyll/md_injection.rb', line 151

def md_injection
  Proc.new { |document, payload|
    # Compare any given document's file extension to the list of enabled
    # Markdown file extensions in Jekyll's config.
    if payload['site']['markdown_ext'].include? document.extname.downcase[1..-1]
      # Convert Markdown images to {% distorted %} tags.
      #
      # Use the same Markdown parser as Jekyll to avoid parsing inconsistencies
      # between this pre_render hook and the main Markdown render step.
      # This is still effectively a Markdown-parsing regex (and still
      # effectively a bad idea lol) but it's the cleanest way I can come up
      # with right now for separating DistorteD-destined Markdown from
      # the rest of any given page.
      # NOTE: Attribute List Definitions elsewhere in a Markdown document
      # will be lost when converting this way. I might end up just parsing
      # the entire document once with my own `to_liquid` converter, but I've been
      # avoiding that as it seems wasteful because Jekyll then renders the entire
      # Markdown document a second time immediately after our Liquid tag.
      # It's fast enough that I should stop trying to prematurely optimize this :)
      # TODO: Implement MD → DD love using only the #{CONFIGURED_MARKDOWN_ENGINE},
      # searching for :img elements inside :li elements to build BLOCKS.
      document.content = document.content.gsub(MD_IMAGE_REGEX) { |match| 
        Kramdown::Document.new(match).to_liquid
      }
    end
  }
end

#update_rubyObject



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/distorted-jekyll.rb', line 9

def update_ruby
  if defined? RUBY_PLATFORM
    if (/freebsd/ =~ RUBY_PLATFORM) != nil
      return 'pkg install lang/ruby27'
    elsif (/darwin/ =~ RUBY_PLATFORM) != nil
      return 'brew upgrade ruby'
    elsif (/win/ =~ RUBY_PLATFORM) != nil
      return 'https://rubyinstaller.org/'
    elsif (/linux/ =~ RUBY_PLATFORM) != nil
      if File.exists?('/etc/lsb-release')
        lsb = File.read('/etc/lsb-release')
        if (/Ubuntu|LinuxMint/ =~ lsb) != nil
          return 'https://www.brightbox.com/docs/ruby/ubuntu/#installation'
        end
      end
    end
  end
  return 'https://github.com/rbenv/ruby-build'
end