Add existing code
This commit is contained in:
parent
af35edadd8
commit
093d6938c8
32 changed files with 1953 additions and 1 deletions
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2023 Alex Kotov
|
Copyright (c) 2023-2024 Alex Kotov
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
46
exe/repubmark
Executable file
46
exe/repubmark
Executable file
|
@ -0,0 +1,46 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
lib = File.expand_path('../lib', __dir__).freeze
|
||||||
|
$LOAD_PATH.unshift lib unless $LOAD_PATH.include? lib
|
||||||
|
|
||||||
|
require 'bundler/setup'
|
||||||
|
|
||||||
|
require 'repubmark'
|
||||||
|
|
||||||
|
FORMATS = %w[word_count html gemtext].freeze
|
||||||
|
|
||||||
|
$format = String(ARGV[0]).freeze
|
||||||
|
raise "Invalid format: #{$format.inspect}" unless FORMATS.include? $format
|
||||||
|
|
||||||
|
$template = $stdin.read.freeze
|
||||||
|
|
||||||
|
$config = Repubmark::Config.new(
|
||||||
|
base_url: 'https://causa-arcana.com',
|
||||||
|
css_class_annotation: 'nice-annotation',
|
||||||
|
css_class_blockquote_figure: 'nice-blockquote',
|
||||||
|
css_class_figure_self: 'nice-figure',
|
||||||
|
css_class_figure_wrap: 'd-flex justify-content-center',
|
||||||
|
css_class_figures_left: 'col-xl-6',
|
||||||
|
css_class_figures_right: 'col-xl-6',
|
||||||
|
css_class_figures_wrap: 'row',
|
||||||
|
css_class_iframe_wrap: 'ratio ratio-16x9',
|
||||||
|
current_path: '/xx/blog/xxxx/xx/xx/xxx.xxx',
|
||||||
|
relative_urls: false,
|
||||||
|
)
|
||||||
|
|
||||||
|
$article = Repubmark::Elems::Article.new $config
|
||||||
|
$article.tap do |article| # rubocop:disable Lint/UnusedBlockArgument
|
||||||
|
eval $template # rubocop:disable Security/Eval
|
||||||
|
end
|
||||||
|
|
||||||
|
case $format
|
||||||
|
when 'word_count'
|
||||||
|
puts $article.word_count
|
||||||
|
when 'html'
|
||||||
|
puts $article.to_html.strip
|
||||||
|
when 'gemtext'
|
||||||
|
puts $article.to_gemtext.strip
|
||||||
|
else
|
||||||
|
raise 'Unknown blog format'
|
||||||
|
end
|
56
lib/repubmark.rb
Normal file
56
lib/repubmark.rb
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'cgi'
|
||||||
|
require 'forwardable'
|
||||||
|
require 'open3'
|
||||||
|
require 'pathname'
|
||||||
|
require 'uri'
|
||||||
|
|
||||||
|
require_relative 'repubmark/config'
|
||||||
|
require_relative 'repubmark/highlight'
|
||||||
|
require_relative 'repubmark/setup'
|
||||||
|
require_relative 'repubmark/titled_ref'
|
||||||
|
|
||||||
|
require_relative 'repubmark/elems/base'
|
||||||
|
|
||||||
|
# Top-level element
|
||||||
|
require_relative 'repubmark/elems/article'
|
||||||
|
|
||||||
|
# Always inside Article
|
||||||
|
require_relative 'repubmark/elems/annotation'
|
||||||
|
# Always inside Article, Chapter
|
||||||
|
require_relative 'repubmark/elems/chapter'
|
||||||
|
# Always inside Annotation, Blockquote, Chapter
|
||||||
|
require_relative 'repubmark/elems/canvas'
|
||||||
|
|
||||||
|
# Always inside Canvas
|
||||||
|
require_relative 'repubmark/elems/blockquote'
|
||||||
|
require_relative 'repubmark/elems/code_block'
|
||||||
|
require_relative 'repubmark/elems/figures'
|
||||||
|
require_relative 'repubmark/elems/iframe'
|
||||||
|
require_relative 'repubmark/elems/paragraph'
|
||||||
|
require_relative 'repubmark/elems/separator'
|
||||||
|
|
||||||
|
# Always inside Canvas, Figures
|
||||||
|
require_relative 'repubmark/elems/figure'
|
||||||
|
# Always inside Canvas, ListItem
|
||||||
|
require_relative 'repubmark/elems/list'
|
||||||
|
# Always inside List
|
||||||
|
require_relative 'repubmark/elems/list_item'
|
||||||
|
|
||||||
|
# Always inside Caption, Quote
|
||||||
|
require_relative 'repubmark/elems/joint'
|
||||||
|
# Always inside Blockquote, Figure, ListItem, Paragraph
|
||||||
|
require_relative 'repubmark/elems/caption'
|
||||||
|
# Always inside Caption, Joint, Quote
|
||||||
|
require_relative 'repubmark/elems/quote'
|
||||||
|
|
||||||
|
# Always inside Joint
|
||||||
|
require_relative 'repubmark/elems/abbrev'
|
||||||
|
require_relative 'repubmark/elems/code_inline'
|
||||||
|
require_relative 'repubmark/elems/fraction'
|
||||||
|
require_relative 'repubmark/elems/note'
|
||||||
|
require_relative 'repubmark/elems/section'
|
||||||
|
require_relative 'repubmark/elems/special'
|
||||||
|
require_relative 'repubmark/elems/text'
|
||||||
|
require_relative 'repubmark/elems/link'
|
31
lib/repubmark/config.rb
Normal file
31
lib/repubmark/config.rb
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
class Config
|
||||||
|
OPTIONAL_KEYS = %i[
|
||||||
|
base_url
|
||||||
|
css_class_annotation
|
||||||
|
css_class_blockquote_figure
|
||||||
|
css_class_blockquote_blockquote
|
||||||
|
css_class_blockquote_figcaption
|
||||||
|
css_class_figure_self
|
||||||
|
css_class_figure_wrap
|
||||||
|
css_class_figures_left
|
||||||
|
css_class_figures_right
|
||||||
|
css_class_figures_wrap
|
||||||
|
css_class_iframe_wrap
|
||||||
|
current_path
|
||||||
|
relative_urls
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
def initialize(**kwargs)
|
||||||
|
raise unless (kwargs.keys.sort - OPTIONAL_KEYS).empty?
|
||||||
|
|
||||||
|
@kwargs = kwargs.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def [](key)
|
||||||
|
OPTIONAL_KEYS.include?(key) ? @kwargs[key] : @kwargs.fetch(key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
50
lib/repubmark/elems/abbrev.rb
Normal file
50
lib/repubmark/elems/abbrev.rb
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Abbrev < Base
|
||||||
|
parents :Joint
|
||||||
|
|
||||||
|
attr_reader :abbrev, :transcript
|
||||||
|
|
||||||
|
def initialize(parent, abbrev, transcript)
|
||||||
|
super parent
|
||||||
|
|
||||||
|
self.abbrev = abbrev
|
||||||
|
self.transcript = transcript
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = 1
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
%(<abbr title="#{escape_transcript}">#{escape_abbrev}</abbr>).freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext = abbrev
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def abbrev=(abbrev)
|
||||||
|
@abbrev =
|
||||||
|
String(abbrev).split.join(' ').freeze.tap do |new_abbrev|
|
||||||
|
raise 'Empty string' if new_abbrev.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def transcript=(transcript)
|
||||||
|
@transcript =
|
||||||
|
String(transcript).split.join(' ').freeze.tap do |new_transcript|
|
||||||
|
raise 'Empty string' if new_transcript.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def escape_abbrev = CGI.escape_html(abbrev).freeze
|
||||||
|
|
||||||
|
def escape_transcript = CGI.escape_html(transcript).freeze
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
46
lib/repubmark/elems/annotation.rb
Normal file
46
lib/repubmark/elems/annotation.rb
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Annotation < Base
|
||||||
|
parents :Article
|
||||||
|
|
||||||
|
def initialize(parent)
|
||||||
|
super parent
|
||||||
|
@canvas = Canvas.new self
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = @canvas.word_count
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
[
|
||||||
|
%(<div#{html_class(:annotation)}>\n),
|
||||||
|
@canvas.to_html,
|
||||||
|
"</div>\n",
|
||||||
|
].join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext = @canvas.to_gemtext
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Builder methods #
|
||||||
|
###################
|
||||||
|
|
||||||
|
def respond_to_missing?(method_name, _include_private)
|
||||||
|
@canvas.respond_to?(method_name) || super
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(method_name, ...)
|
||||||
|
if @canvas.respond_to? method_name
|
||||||
|
@canvas.public_send(method_name, ...)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
87
lib/repubmark/elems/article.rb
Normal file
87
lib/repubmark/elems/article.rb
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Article < Base
|
||||||
|
attr_reader :config
|
||||||
|
|
||||||
|
def initialize(config) # rubocop:disable Lint/MissingSuper
|
||||||
|
@parent = nil
|
||||||
|
self.config = config
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count
|
||||||
|
(@annotation&.word_count || 0) +
|
||||||
|
(@chapter&.word_count || 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
[
|
||||||
|
setup.prologue,
|
||||||
|
@annotation&.to_html,
|
||||||
|
@chapter&.to_html,
|
||||||
|
setup.epilogue,
|
||||||
|
].compact.join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext
|
||||||
|
[
|
||||||
|
setup.prologue,
|
||||||
|
@annotation&.to_gemtext,
|
||||||
|
@chapter&.to_gemtext,
|
||||||
|
setup.epilogue,
|
||||||
|
].compact.join("\n\n\n").freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Setup methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def setup(&)
|
||||||
|
@setup ||= Setup.new(&)
|
||||||
|
end
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Builder methods #
|
||||||
|
###################
|
||||||
|
|
||||||
|
def annotation
|
||||||
|
raise 'Annotation already exists' if @annotation
|
||||||
|
raise 'Annotation after chapter' if @chapter
|
||||||
|
|
||||||
|
@annotation = Annotation.new self
|
||||||
|
yield @annotation
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def respond_to_missing?(method_name, _include_private)
|
||||||
|
chapter = @chapter || Chapter.new(self)
|
||||||
|
chapter.respond_to?(method_name) || super
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(method_name, ...)
|
||||||
|
chapter = @chapter || Chapter.new(self)
|
||||||
|
if chapter.respond_to? method_name
|
||||||
|
@chapter = chapter
|
||||||
|
@chapter.public_send(method_name, ...)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def config=(config)
|
||||||
|
unless config.instance_of? Config
|
||||||
|
raise TypeError, "Expected #{Config}, got #{config.class}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@config = config
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
104
lib/repubmark/elems/base.rb
Normal file
104
lib/repubmark/elems/base.rb
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Base
|
||||||
|
attr_reader :parent
|
||||||
|
|
||||||
|
def initialize(parent)
|
||||||
|
self.parent = parent
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = 0
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
raise NotImplementedError, "#{self.class}#to_html"
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext
|
||||||
|
raise NotImplementedError, "#{self.class}#to_gemtext"
|
||||||
|
end
|
||||||
|
|
||||||
|
##################
|
||||||
|
# Helper methods #
|
||||||
|
##################
|
||||||
|
|
||||||
|
def config = parent.config
|
||||||
|
|
||||||
|
def setup = parent.setup
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def parents(*args)
|
||||||
|
if @parents
|
||||||
|
raise ArgumentError, 'Invalid args' unless args.empty?
|
||||||
|
|
||||||
|
return @parents
|
||||||
|
end
|
||||||
|
|
||||||
|
@parents = args.map { |arg| "#{Elems}::#{arg}".freeze }.to_a.freeze
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def parent?(klass)
|
||||||
|
unless klass.instance_of? Class
|
||||||
|
raise TypeError, "Expected #{Class}, got #{klass.class}"
|
||||||
|
end
|
||||||
|
|
||||||
|
parents.include? klass.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def parent=(parent)
|
||||||
|
unless parent.is_a? Base
|
||||||
|
raise TypeError, "Expected #{Base}, got #{parent.class}"
|
||||||
|
end
|
||||||
|
|
||||||
|
unless self.class.parent? parent.class
|
||||||
|
raise TypeError,
|
||||||
|
"Expected #{self.class.parents.join(', ')}, got #{parent.class}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@parent = parent
|
||||||
|
end
|
||||||
|
|
||||||
|
def own_url(path)
|
||||||
|
config[:relative_urls] ? relative_url(path) : absolute_url(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def absolute_url(path)
|
||||||
|
base_url = String(config[:base_url]).strip.freeze
|
||||||
|
raise 'Invalid base URL' if base_url.empty?
|
||||||
|
|
||||||
|
path = "/#{path}" unless path.start_with? '/'
|
||||||
|
"#{base_url}#{path}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def relative_url(path)
|
||||||
|
current_path = String(config[:current_path]).strip.freeze
|
||||||
|
raise 'Invalid current path URL' if current_path.empty?
|
||||||
|
|
||||||
|
Pathname
|
||||||
|
.new(path)
|
||||||
|
.relative_path_from("/#{current_path}/..")
|
||||||
|
.to_s
|
||||||
|
.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def html_class(key)
|
||||||
|
if (value = config[:"css_class_#{key}"])
|
||||||
|
%( class="#{value}").freeze
|
||||||
|
else
|
||||||
|
''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def count_words(str) = str.split.count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
80
lib/repubmark/elems/blockquote.rb
Normal file
80
lib/repubmark/elems/blockquote.rb
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Blockquote < Base
|
||||||
|
parents :Canvas
|
||||||
|
|
||||||
|
def initialize(parent)
|
||||||
|
super parent
|
||||||
|
|
||||||
|
@canvas = Canvas.new self
|
||||||
|
@caption = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = @canvas.word_count + (@caption&.word_count || 0)
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
[
|
||||||
|
"<figure#{html_class(:blockquote_figure)}>\n",
|
||||||
|
"<blockquote#{html_class(:blockquote_blockquote)}>\n",
|
||||||
|
@canvas.to_html,
|
||||||
|
"</blockquote>\n",
|
||||||
|
caption_html,
|
||||||
|
"</figure>\n",
|
||||||
|
].join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext
|
||||||
|
[
|
||||||
|
@canvas.to_gemtext.split("\n").map { |s| "> #{s}\n" }.join,
|
||||||
|
caption_gemtext,
|
||||||
|
].join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Builder methods #
|
||||||
|
###################
|
||||||
|
|
||||||
|
def caption
|
||||||
|
@caption = Caption.new self
|
||||||
|
yield @caption
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def respond_to_missing?(method_name, _include_private)
|
||||||
|
@canvas.respond_to?(method_name) || super
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(method_name, ...)
|
||||||
|
if @canvas.respond_to? method_name
|
||||||
|
raise 'Paragraph after caption' unless @caption.nil?
|
||||||
|
|
||||||
|
@canvas.public_send(method_name, ...)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def caption_html
|
||||||
|
return if @caption.nil?
|
||||||
|
|
||||||
|
[
|
||||||
|
"<figcaption#{html_class(:blockquote_figcaption)}>\n",
|
||||||
|
@caption.to_html,
|
||||||
|
"</figcaption>\n",
|
||||||
|
].join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def caption_gemtext
|
||||||
|
"#{@caption.to_gemtext}\n".freeze unless @caption.nil?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
94
lib/repubmark/elems/canvas.rb
Normal file
94
lib/repubmark/elems/canvas.rb
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Canvas < Base
|
||||||
|
parents :Annotation, :Blockquote, :Chapter
|
||||||
|
|
||||||
|
def initialize(parent)
|
||||||
|
super parent
|
||||||
|
@items = []
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = @items.sum(&:word_count)
|
||||||
|
|
||||||
|
def to_html = @items.map(&:to_html).join.freeze
|
||||||
|
|
||||||
|
def to_gemtext = @items.map(&:to_gemtext).join("\n").freeze
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Builder methods #
|
||||||
|
###################
|
||||||
|
|
||||||
|
def blockquote
|
||||||
|
blockquote = Blockquote.new self
|
||||||
|
@items << blockquote
|
||||||
|
yield blockquote
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def code_block(*args, **kwargs)
|
||||||
|
code_block = CodeBlock.new self, *args, **kwargs
|
||||||
|
@items << code_block
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def iframe(*args, **kwargs)
|
||||||
|
iframe = Iframe.new self, *args, **kwargs
|
||||||
|
@items << iframe
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def links_list
|
||||||
|
list = List.new self, links: true, ordered: false
|
||||||
|
@items << list
|
||||||
|
yield list
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def nice_figure(name, alt)
|
||||||
|
figure = Figure.new self, name, alt
|
||||||
|
@items << figure
|
||||||
|
yield figure if block_given?
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def nice_figures
|
||||||
|
figures = Figures.new self
|
||||||
|
@items << figures
|
||||||
|
yield figures
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def olist
|
||||||
|
list = List.new self, links: false, ordered: true
|
||||||
|
@items << list
|
||||||
|
yield list
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def paragraph
|
||||||
|
paragraph = Paragraph.new self
|
||||||
|
@items << paragraph
|
||||||
|
yield paragraph
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def separator
|
||||||
|
@items << Separator.new(self)
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def ulist
|
||||||
|
list = List.new self, links: false, ordered: false
|
||||||
|
@items << list
|
||||||
|
yield list
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
61
lib/repubmark/elems/caption.rb
Normal file
61
lib/repubmark/elems/caption.rb
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Caption < Base
|
||||||
|
include Joint::ForwardingBuilders
|
||||||
|
|
||||||
|
parents :Blockquote, :Figure, :ListItem, :Paragraph
|
||||||
|
|
||||||
|
def initialize(parent)
|
||||||
|
super parent
|
||||||
|
@items = []
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = @items.sum(&:word_count)
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
[
|
||||||
|
'<span>',
|
||||||
|
*@items.map(&:to_html),
|
||||||
|
'</span>',
|
||||||
|
].map { |s| "#{s}\n" }.join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext = @items.map(&:to_gemtext).join(' ').strip.freeze
|
||||||
|
|
||||||
|
##################
|
||||||
|
# Helper methods #
|
||||||
|
##################
|
||||||
|
|
||||||
|
def empty? = @items.empty?
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Builder methods #
|
||||||
|
###################
|
||||||
|
|
||||||
|
def joint
|
||||||
|
joint = Joint.new self
|
||||||
|
yield joint
|
||||||
|
@items << joint
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def quote(str = nil)
|
||||||
|
quote = Quote.new self
|
||||||
|
case [!!str, block_given?]
|
||||||
|
when [true, false] then quote.text str
|
||||||
|
when [false, true] then yield quote
|
||||||
|
else
|
||||||
|
raise 'Invalid args'
|
||||||
|
end
|
||||||
|
@items << quote
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
101
lib/repubmark/elems/chapter.rb
Normal file
101
lib/repubmark/elems/chapter.rb
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Chapter < Base
|
||||||
|
parents :Article, :Chapter
|
||||||
|
|
||||||
|
def initialize(parent, level = 1, title = nil)
|
||||||
|
super parent
|
||||||
|
self.level = level
|
||||||
|
self.title = title
|
||||||
|
verify!
|
||||||
|
|
||||||
|
@canvas = Canvas.new self
|
||||||
|
@chapters = []
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count
|
||||||
|
(@title ? count_words(@title) : 0) +
|
||||||
|
@canvas.word_count +
|
||||||
|
@chapters.sum(&:word_count)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
[
|
||||||
|
build_title_html,
|
||||||
|
@canvas.to_html,
|
||||||
|
*@chapters.map(&:to_html),
|
||||||
|
].join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext
|
||||||
|
[
|
||||||
|
build_title_gemtext,
|
||||||
|
@canvas.to_gemtext,
|
||||||
|
*@chapters.map(&:to_gemtext),
|
||||||
|
].join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Builder methods #
|
||||||
|
###################
|
||||||
|
|
||||||
|
def chapter(title)
|
||||||
|
chapter = Chapter.new self, @level + 1, title
|
||||||
|
@chapters << chapter
|
||||||
|
yield chapter
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def respond_to_missing?(method_name, _include_private)
|
||||||
|
@canvas.respond_to?(method_name) || super
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(method_name, ...)
|
||||||
|
if @canvas.respond_to? method_name
|
||||||
|
raise 'Intro after chapters' unless @chapters.empty?
|
||||||
|
|
||||||
|
@canvas.public_send(method_name, ...)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def level=(level)
|
||||||
|
level = Integer level
|
||||||
|
raise unless level.positive?
|
||||||
|
|
||||||
|
@level = level
|
||||||
|
end
|
||||||
|
|
||||||
|
def title=(title)
|
||||||
|
return @title = nil if title.nil?
|
||||||
|
|
||||||
|
title = String(title).strip.freeze
|
||||||
|
raise 'Empty title' if title.empty?
|
||||||
|
|
||||||
|
@title = title
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify!
|
||||||
|
raise 'Non-empty title for level 1' if @level == 1 && @title
|
||||||
|
raise 'Empty title for level >= 2' if @level != 1 && @title.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_title_html
|
||||||
|
"<h#@level>#@title</h#@level>\n" if @level != 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_title_gemtext
|
||||||
|
"\n#{'#' * @level} #@title\n\n" if @level != 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
70
lib/repubmark/elems/code_block.rb
Normal file
70
lib/repubmark/elems/code_block.rb
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class CodeBlock < Base
|
||||||
|
parents :Canvas
|
||||||
|
|
||||||
|
attr_reader :syntax, :str, :indent
|
||||||
|
|
||||||
|
def initialize(parent, syntax, str, indent: 0)
|
||||||
|
super parent
|
||||||
|
|
||||||
|
self.syntax = syntax
|
||||||
|
self.str = str
|
||||||
|
self.indent = indent
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def to_html = highlighted_str
|
||||||
|
|
||||||
|
def to_gemtext
|
||||||
|
[
|
||||||
|
"```\n",
|
||||||
|
"#{str.strip}\n",
|
||||||
|
"```\n",
|
||||||
|
].join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def syntax=(syntax)
|
||||||
|
return @syntax = nil if syntax.nil?
|
||||||
|
|
||||||
|
unless syntax.instance_of? Symbol
|
||||||
|
raise TypeError, "Expected #{Symbol}, got #{syntax.class}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@syntax = syntax
|
||||||
|
end
|
||||||
|
|
||||||
|
def str=(str)
|
||||||
|
str = String(str).freeze
|
||||||
|
raise 'Expected non-blank string' if str.strip.empty?
|
||||||
|
|
||||||
|
@str = str
|
||||||
|
end
|
||||||
|
|
||||||
|
def indent=(indent)
|
||||||
|
@indent = Integer indent
|
||||||
|
raise 'Expected non-negative number' if @indent.negative?
|
||||||
|
end
|
||||||
|
|
||||||
|
def highlighted_str
|
||||||
|
@highlighted_str ||= Highlight.call syntax, transformed_str
|
||||||
|
end
|
||||||
|
|
||||||
|
def transformed_str
|
||||||
|
@transformed_str ||=
|
||||||
|
str.lines.map { |line| "#{indentation}#{line}" }.join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def indentation
|
||||||
|
@indentation ||= (' ' * indent).freeze
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
33
lib/repubmark/elems/code_inline.rb
Normal file
33
lib/repubmark/elems/code_inline.rb
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class CodeInline < Base
|
||||||
|
parents :Joint
|
||||||
|
|
||||||
|
def initialize(parent, str)
|
||||||
|
super parent
|
||||||
|
self.str = str
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def to_html = "<code>#{CGI.escape_html(@str)}</code>".freeze
|
||||||
|
|
||||||
|
def to_gemtext = "«#{@str}»".freeze
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def str=(str)
|
||||||
|
str = String(str).freeze
|
||||||
|
if str.include?("\n") || str.include?('«') || str.include?('»')
|
||||||
|
raise 'Invalid str'
|
||||||
|
end
|
||||||
|
|
||||||
|
@str = str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
73
lib/repubmark/elems/figure.rb
Normal file
73
lib/repubmark/elems/figure.rb
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Figure < Base
|
||||||
|
parents :Canvas, :Figures
|
||||||
|
|
||||||
|
def initialize(parent, name, alt)
|
||||||
|
super parent
|
||||||
|
|
||||||
|
alt = String(alt).strip.split.join(' ').freeze
|
||||||
|
raise 'Empty alt text' if alt.empty?
|
||||||
|
|
||||||
|
@name = String(name).strip.freeze
|
||||||
|
@alt = alt
|
||||||
|
@caption = Caption.new self
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
[
|
||||||
|
"<div#{html_class(:figure_wrap)}>\n",
|
||||||
|
"<figure#{html_class(:figure_self)}>\n",
|
||||||
|
"<img src=\"#{src}\" alt=\"#{CGI.escape_html(@alt)}\"/>\n",
|
||||||
|
"<figcaption>\n",
|
||||||
|
caption_html,
|
||||||
|
"</figcaption>\n",
|
||||||
|
"</figure>\n",
|
||||||
|
"</div>\n",
|
||||||
|
].join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext
|
||||||
|
caption = @caption.to_gemtext
|
||||||
|
caption = @alt if caption.empty?
|
||||||
|
"=> #{src} #{caption}\n".freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Builder methods #
|
||||||
|
###################
|
||||||
|
|
||||||
|
def respond_to_missing?(method_name, _include_private)
|
||||||
|
@caption.respond_to?(method_name) || super
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(method_name, ...)
|
||||||
|
if @caption.respond_to? method_name
|
||||||
|
@caption.public_send(method_name, ...)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def src
|
||||||
|
@src ||= own_url "/assets/images/blog/#@name"
|
||||||
|
end
|
||||||
|
|
||||||
|
def caption_html
|
||||||
|
if @caption.empty?
|
||||||
|
"#{CGI.escape_html(@alt)}\n"
|
||||||
|
else
|
||||||
|
@caption.to_html
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
47
lib/repubmark/elems/figures.rb
Normal file
47
lib/repubmark/elems/figures.rb
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Figures < Base
|
||||||
|
parents :Canvas
|
||||||
|
|
||||||
|
def initialize(parent)
|
||||||
|
super parent
|
||||||
|
@figures = []
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
raise 'Expected two figures' unless @figures.size == 2
|
||||||
|
|
||||||
|
[
|
||||||
|
"<div#{html_class(:figures_wrap)}>\n",
|
||||||
|
*@figures.flat_map do |figure|
|
||||||
|
[
|
||||||
|
"<div#{html_class(:figures_left)}>\n",
|
||||||
|
figure.to_html,
|
||||||
|
"</div>\n",
|
||||||
|
]
|
||||||
|
end,
|
||||||
|
"</div>\n",
|
||||||
|
].join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext = @figures.map(&:to_gemtext).join.freeze
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Builder methods #
|
||||||
|
###################
|
||||||
|
|
||||||
|
def figure(name, alt)
|
||||||
|
figure = Figure.new self, name, alt
|
||||||
|
@figures << figure
|
||||||
|
yield figure if block_given?
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
31
lib/repubmark/elems/fraction.rb
Normal file
31
lib/repubmark/elems/fraction.rb
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Fraction < Base
|
||||||
|
parents :Joint
|
||||||
|
|
||||||
|
def initialize(parent, top, bottom)
|
||||||
|
super parent
|
||||||
|
|
||||||
|
@top = Integer top
|
||||||
|
@bottom = Integer bottom
|
||||||
|
|
||||||
|
raise 'Expected top to be non-negative' if @top.negative?
|
||||||
|
raise 'Expected bottom to be positive' unless @bottom.positive?
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = 1
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
"<sup>#@top</sup>⁄<sub>#@bottom</sub>".freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext = "#@top/#@bottom"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
62
lib/repubmark/elems/iframe.rb
Normal file
62
lib/repubmark/elems/iframe.rb
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Iframe < Base
|
||||||
|
parents :Canvas
|
||||||
|
|
||||||
|
attr_reader :title, :src, :url
|
||||||
|
|
||||||
|
def initialize(parent, title, src, url = nil)
|
||||||
|
super parent
|
||||||
|
|
||||||
|
self.title = title
|
||||||
|
self.src = src
|
||||||
|
self.url = url || src
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
[
|
||||||
|
"<div#{html_class(:iframe_wrap)}>\n",
|
||||||
|
"<iframe\n",
|
||||||
|
%(title="#{title}"\n),
|
||||||
|
%(src="#{src}"\n),
|
||||||
|
%(allowfullscreen=""\n),
|
||||||
|
%(frameborder="0"\n),
|
||||||
|
%(sandbox="allow-same-origin allow-scripts allow-popups"\n),
|
||||||
|
"></iframe>\n",
|
||||||
|
"</div>\n",
|
||||||
|
].join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext = "=> #{url} #{title}\n"
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def title=(title)
|
||||||
|
title = String(title).strip.freeze
|
||||||
|
raise 'Empty title' if title.empty?
|
||||||
|
|
||||||
|
@title = title
|
||||||
|
end
|
||||||
|
|
||||||
|
def src=(src)
|
||||||
|
src = String(src).strip.freeze
|
||||||
|
raise 'Empty src' if src.empty?
|
||||||
|
|
||||||
|
@src = src
|
||||||
|
end
|
||||||
|
|
||||||
|
def url=(url)
|
||||||
|
url = String(url).strip.freeze
|
||||||
|
raise 'Empty url' if url.empty?
|
||||||
|
|
||||||
|
@url = url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
170
lib/repubmark/elems/joint.rb
Normal file
170
lib/repubmark/elems/joint.rb
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Joint < Base
|
||||||
|
parents :Caption, :Quote
|
||||||
|
|
||||||
|
def initialize(parent)
|
||||||
|
super parent
|
||||||
|
|
||||||
|
@raw1 = nil
|
||||||
|
@base = nil
|
||||||
|
@raw2 = nil
|
||||||
|
@notes = []
|
||||||
|
@raw3 = nil
|
||||||
|
|
||||||
|
@context = nil
|
||||||
|
@context_note = false
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = components.sum(&:word_count)
|
||||||
|
|
||||||
|
def to_html = components.map(&:to_html).join.freeze
|
||||||
|
|
||||||
|
def to_gemtext = components.map(&:to_gemtext).join.freeze
|
||||||
|
|
||||||
|
########################
|
||||||
|
# Builder methods: Raw #
|
||||||
|
########################
|
||||||
|
|
||||||
|
def raw(str)
|
||||||
|
text = Text.new self, str
|
||||||
|
if @notes.any?
|
||||||
|
raise 'Joint raw text already exists' if @raw3
|
||||||
|
|
||||||
|
@raw3 = text
|
||||||
|
elsif @base
|
||||||
|
raise 'Joint raw text already exists' if @raw2
|
||||||
|
|
||||||
|
@raw2 = text
|
||||||
|
else
|
||||||
|
raise 'Joint raw text already exists' if @raw1
|
||||||
|
|
||||||
|
@raw1 = text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#########################
|
||||||
|
# Builder methods: Base #
|
||||||
|
#########################
|
||||||
|
|
||||||
|
def abbrev(abbrev, transcript) = base Abbrev.new self, abbrev, transcript
|
||||||
|
|
||||||
|
def bold(str) = base Text.new self, str, bold: true
|
||||||
|
|
||||||
|
def code_inline(str) = base CodeInline.new self, str
|
||||||
|
|
||||||
|
def ellipsis = base Special.new self, :ellipsis
|
||||||
|
|
||||||
|
def fraction(top, bottom) = base Fraction.new self, top, bottom
|
||||||
|
|
||||||
|
def italic(str) = base Text.new self, str, italic: true
|
||||||
|
|
||||||
|
def link(text, uri) = base Link.new self, text, uri
|
||||||
|
|
||||||
|
def link_italic(text, uri) = base Link.new self, text, uri, italic: true
|
||||||
|
|
||||||
|
def mdash = base Special.new self, :mdash
|
||||||
|
|
||||||
|
def quote(str = nil)
|
||||||
|
quote = Quote.new self
|
||||||
|
case [!!str, block_given?]
|
||||||
|
when [true, false] then quote.text str
|
||||||
|
when [false, true] then yield quote
|
||||||
|
else
|
||||||
|
raise 'Invalid args'
|
||||||
|
end
|
||||||
|
base quote
|
||||||
|
end
|
||||||
|
|
||||||
|
def quote_italic(str)
|
||||||
|
quote = Quote.new self
|
||||||
|
quote.italic str
|
||||||
|
base quote
|
||||||
|
end
|
||||||
|
|
||||||
|
def section(*args) = base Section.new self, *args
|
||||||
|
|
||||||
|
def text(str) = base Text.new self, str
|
||||||
|
|
||||||
|
##########################
|
||||||
|
# Builder methods: Notes #
|
||||||
|
##########################
|
||||||
|
|
||||||
|
def context(index, anchor)
|
||||||
|
raise 'Context already given' if @context
|
||||||
|
|
||||||
|
@context = [index, anchor]
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def context_note
|
||||||
|
raise 'No context given' if @context.nil?
|
||||||
|
raise 'Context already noted' if @context_note
|
||||||
|
|
||||||
|
@context_note = true
|
||||||
|
ref_note(*@context)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ref_note(index, anchor)
|
||||||
|
note Note.new self, index, anchor
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def components
|
||||||
|
raise 'No context note' if @context && !@context_note
|
||||||
|
|
||||||
|
[@raw1, @base, @raw2, *@notes, @raw3].compact
|
||||||
|
end
|
||||||
|
|
||||||
|
def base(elem)
|
||||||
|
raise 'Joint base already exists' if @base
|
||||||
|
raise 'Joint notes already exist' if @notes.any?
|
||||||
|
|
||||||
|
@base = elem
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def note(elem)
|
||||||
|
raise 'Joint base does not exists' if @base.nil?
|
||||||
|
|
||||||
|
@notes << elem
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
module ForwardingBuilders
|
||||||
|
def joint = raise NotImplementedError, "#{self.class}#joint"
|
||||||
|
|
||||||
|
def abbrev(*args) = joint { |joint| joint.abbrev(*args) }
|
||||||
|
|
||||||
|
def bold(*args) = joint { |joint| joint.bold(*args) }
|
||||||
|
|
||||||
|
def code_inline(*args) = joint { |joint| joint.code_inline(*args) }
|
||||||
|
|
||||||
|
def ellipsis(*args) = joint { |joint| joint.ellipsis(*args) }
|
||||||
|
|
||||||
|
def fraction(*args) = joint { |joint| joint.fraction(*args) }
|
||||||
|
|
||||||
|
def italic(*args) = joint { |joint| joint.italic(*args) }
|
||||||
|
|
||||||
|
def link(*args) = joint { |joint| joint.link(*args) }
|
||||||
|
|
||||||
|
def link_italic(*args) = joint { |joint| joint.link_italic(*args) }
|
||||||
|
|
||||||
|
def mdash(*args) = joint { |joint| joint.mdash(*args) }
|
||||||
|
|
||||||
|
def quote_italic(*args) = joint { |joint| joint.quote_italic(*args) }
|
||||||
|
|
||||||
|
def section(*args) = joint { |joint| joint.section(*args) }
|
||||||
|
|
||||||
|
def text(*args) = joint { |joint| joint.text(*args) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
56
lib/repubmark/elems/link.rb
Normal file
56
lib/repubmark/elems/link.rb
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Link < Text
|
||||||
|
parents :Joint
|
||||||
|
|
||||||
|
SCHEMES = %w[http https].freeze
|
||||||
|
|
||||||
|
def initialize(parent, str, uri, **kwargs)
|
||||||
|
super parent, str, **kwargs
|
||||||
|
|
||||||
|
self.uri = uri
|
||||||
|
validate_uri!
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
"<a href=\"#{build_uri('.html')}\">#{str_to_html}</a>".freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_reader :uri
|
||||||
|
|
||||||
|
def uri=(uri)
|
||||||
|
@uri = URI.parse(uri).freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_uri!
|
||||||
|
raise 'Expected normalized URI' unless uri.normalize == uri
|
||||||
|
raise 'Expected no userinfo' unless uri.userinfo.nil?
|
||||||
|
|
||||||
|
validate_uri_http_absolute!
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_uri_http_absolute!
|
||||||
|
return unless uri.is_a?(URI::HTTP) && uri.absolute?
|
||||||
|
|
||||||
|
raise 'Invalid scheme' unless SCHEMES.include? uri.scheme
|
||||||
|
raise 'Expected hostname' unless uri.hostname
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_uri(ext)
|
||||||
|
if uri.is_a?(URI::MailTo) || uri.absolute?
|
||||||
|
uri
|
||||||
|
else
|
||||||
|
own_url "#{uri}#{ext}"
|
||||||
|
end.to_s.freeze
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
82
lib/repubmark/elems/list.rb
Normal file
82
lib/repubmark/elems/list.rb
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class List < Base
|
||||||
|
parents :Canvas, :ListItem
|
||||||
|
|
||||||
|
def initialize(parent, links:, ordered:)
|
||||||
|
super parent
|
||||||
|
|
||||||
|
@links = !!links
|
||||||
|
@ordered = !!ordered
|
||||||
|
|
||||||
|
@items = []
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = @items.sum(&:word_count)
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
[
|
||||||
|
"<#{tag_name}>\n",
|
||||||
|
*@items.map(&:to_html),
|
||||||
|
"</#{tag_name}>\n",
|
||||||
|
].join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext = @items.map(&:to_gemtext).join
|
||||||
|
|
||||||
|
##################
|
||||||
|
# Helper methods #
|
||||||
|
##################
|
||||||
|
|
||||||
|
def level = parent.instance_of?(ListItem) ? parent.level + 1 : 0
|
||||||
|
|
||||||
|
def items_count = @items.count
|
||||||
|
|
||||||
|
def unicode_decor
|
||||||
|
return if level <= 1
|
||||||
|
|
||||||
|
"#{parent.unicode_decor_parent}#{parent.last? ? '⠀ ' : '│ '}"
|
||||||
|
end
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Builder methods #
|
||||||
|
###################
|
||||||
|
|
||||||
|
def item(...) = @links ? item_links(...) : item_nolinks(...)
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def parent=(parent)
|
||||||
|
unless parent.instance_of?(Canvas) || parent.instance_of?(ListItem)
|
||||||
|
raise TypeError,
|
||||||
|
"Expected #{Canvas} or #{ListItem}, got #{parent.class}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@parent = parent
|
||||||
|
end
|
||||||
|
|
||||||
|
def tag_name = @ordered ? 'ol' : 'ul'
|
||||||
|
|
||||||
|
def item_nolinks
|
||||||
|
list_item = ListItem.new self, @items.count
|
||||||
|
@items << list_item
|
||||||
|
yield list_item
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def item_links(ref_url, ref_title = nil)
|
||||||
|
titled_ref = TitledRef.new ref_url, ref_title
|
||||||
|
list_item = ListItem.new self, @items.count, titled_ref
|
||||||
|
@items << list_item
|
||||||
|
yield list_item if block_given?
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
140
lib/repubmark/elems/list_item.rb
Normal file
140
lib/repubmark/elems/list_item.rb
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class ListItem < Base
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
parents :List
|
||||||
|
|
||||||
|
attr_reader :index, :titled_ref
|
||||||
|
|
||||||
|
def initialize(parent, index, titled_ref = nil)
|
||||||
|
super parent
|
||||||
|
|
||||||
|
self.index = index
|
||||||
|
self.titled_ref = titled_ref
|
||||||
|
|
||||||
|
@caption = Caption.new self
|
||||||
|
@sublist = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = @caption.word_count + (@sublist&.word_count || 0)
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
[
|
||||||
|
"<li>\n",
|
||||||
|
build_ref(:html),
|
||||||
|
@caption.to_html,
|
||||||
|
@sublist&.to_html,
|
||||||
|
"</li>\n",
|
||||||
|
].join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext
|
||||||
|
[
|
||||||
|
if titled_ref
|
||||||
|
"#{build_ref(:gemtext)}#{@caption.to_gemtext}".strip
|
||||||
|
else
|
||||||
|
"* #{unicode_decor_own}#{@caption.to_gemtext}".strip
|
||||||
|
end,
|
||||||
|
@sublist&.to_gemtext,
|
||||||
|
].join("\n").freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
##################
|
||||||
|
# Helper methods #
|
||||||
|
##################
|
||||||
|
|
||||||
|
def level = parent.level
|
||||||
|
|
||||||
|
def last? = index >= parent.items_count - 1
|
||||||
|
|
||||||
|
def unicode_decor_own
|
||||||
|
"#{parent.unicode_decor}#{last? ? '└' : '├'} " unless level.zero?
|
||||||
|
end
|
||||||
|
|
||||||
|
def unicode_decor_parent = parent.unicode_decor
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Builder methods #
|
||||||
|
###################
|
||||||
|
|
||||||
|
def subolist
|
||||||
|
raise 'No nested link lists' if titled_ref
|
||||||
|
raise 'Sublist already exists' if @sublist
|
||||||
|
|
||||||
|
@sublist = List.new self, links: false, ordered: true
|
||||||
|
yield @sublist
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def subulist
|
||||||
|
raise 'No nested link lists' if titled_ref
|
||||||
|
raise 'Sublist already exists' if @sublist
|
||||||
|
|
||||||
|
@sublist = List.new self, links: false, ordered: false
|
||||||
|
yield @sublist
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def respond_to_missing?(method_name, _include_private)
|
||||||
|
@caption.respond_to?(method_name) || super
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(method_name, ...)
|
||||||
|
if @caption.respond_to? method_name
|
||||||
|
raise 'Caption after sublist' if @sublist
|
||||||
|
|
||||||
|
@caption.public_send(method_name, ...)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def parent=(parent)
|
||||||
|
unless parent.instance_of? List
|
||||||
|
raise TypeError, "Expected #{List}, got #{parent.class}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@parent = parent
|
||||||
|
end
|
||||||
|
|
||||||
|
def index=(index)
|
||||||
|
index = Integer index
|
||||||
|
raise 'Invalid index' if index.negative?
|
||||||
|
|
||||||
|
@index = index
|
||||||
|
end
|
||||||
|
|
||||||
|
def titled_ref=(titled_ref)
|
||||||
|
return @titled_ref = nil if titled_ref.nil?
|
||||||
|
|
||||||
|
unless titled_ref.instance_of? TitledRef
|
||||||
|
raise TypeError, "Expected #{TitledRef}, got #{titled_ref.class}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@titled_ref = titled_ref
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_ref(format)
|
||||||
|
return if titled_ref.nil?
|
||||||
|
|
||||||
|
case format
|
||||||
|
when :html
|
||||||
|
"<a href=\"#{titled_ref.url}\">#{titled_ref.title}</a>\n".freeze
|
||||||
|
when :gemtext
|
||||||
|
"=> #{titled_ref.url} #{unicode_decor_own}#{titled_ref.title} ".freeze
|
||||||
|
else
|
||||||
|
raise 'Invalid format'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
51
lib/repubmark/elems/note.rb
Normal file
51
lib/repubmark/elems/note.rb
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Note < Base
|
||||||
|
parents :Joint
|
||||||
|
|
||||||
|
UNICODE_SUPS = {
|
||||||
|
'1' => '¹',
|
||||||
|
'2' => '²',
|
||||||
|
'3' => '³',
|
||||||
|
'4' => '⁴',
|
||||||
|
'5' => '⁵',
|
||||||
|
'6' => '⁶',
|
||||||
|
'7' => '⁷',
|
||||||
|
'8' => '⁸',
|
||||||
|
'9' => '⁹',
|
||||||
|
'0' => '⁰',
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
def initialize(parent, index, anchor)
|
||||||
|
super parent
|
||||||
|
@index = index
|
||||||
|
@anchor = anchor
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def to_html
|
||||||
|
[
|
||||||
|
'<sup>',
|
||||||
|
%(<a href="##{CGI.escape_html(@anchor)}">),
|
||||||
|
%([#{CGI.escape_html(@index.to_s)}]),
|
||||||
|
'</a>',
|
||||||
|
'</sup>',
|
||||||
|
].join.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_gemtext = "⁽#{index_unicode_sup}⁾".freeze
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def index_unicode_sup
|
||||||
|
@index_unicode_sup ||=
|
||||||
|
@index.to_s.each_char.map { |chr| UNICODE_SUPS.fetch chr }.join.freeze
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
40
lib/repubmark/elems/paragraph.rb
Normal file
40
lib/repubmark/elems/paragraph.rb
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Paragraph < Base
|
||||||
|
parents :Canvas
|
||||||
|
|
||||||
|
def initialize(parent)
|
||||||
|
super parent
|
||||||
|
@caption = Caption.new self
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = @caption.word_count
|
||||||
|
|
||||||
|
def to_html = "<p>\n#{@caption.to_html}</p>\n".freeze
|
||||||
|
|
||||||
|
def to_gemtext = "#{@caption.to_gemtext}\n".freeze
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Builder methods #
|
||||||
|
###################
|
||||||
|
|
||||||
|
def respond_to_missing?(method_name, _include_private)
|
||||||
|
@caption.respond_to?(method_name) || super
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing(method_name, ...)
|
||||||
|
if @caption.respond_to?(method_name)
|
||||||
|
@caption.public_send(method_name, ...)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
49
lib/repubmark/elems/quote.rb
Normal file
49
lib/repubmark/elems/quote.rb
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Quote < Base
|
||||||
|
include Joint::ForwardingBuilders
|
||||||
|
|
||||||
|
parents :Caption, :Joint, :Quote
|
||||||
|
|
||||||
|
def initialize(...)
|
||||||
|
super
|
||||||
|
@items = []
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = @items.sum(&:word_count)
|
||||||
|
|
||||||
|
def to_html = "«#{@items.map(&:to_html).join("\n")}»".freeze
|
||||||
|
|
||||||
|
def to_gemtext = "«#{@items.map(&:to_gemtext).join(' ')}»".freeze
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Builder methods #
|
||||||
|
###################
|
||||||
|
|
||||||
|
def joint
|
||||||
|
joint = Joint.new self
|
||||||
|
yield joint
|
||||||
|
@items << joint
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def quote(str = nil)
|
||||||
|
quote = Quote.new self
|
||||||
|
case [!!str, block_given?]
|
||||||
|
when [true, false] then quote.text str
|
||||||
|
when [false, true] then yield quote
|
||||||
|
else
|
||||||
|
raise 'Invalid args'
|
||||||
|
end
|
||||||
|
@items << quote
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
56
lib/repubmark/elems/section.rb
Normal file
56
lib/repubmark/elems/section.rb
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Section < Base
|
||||||
|
parents :Joint
|
||||||
|
|
||||||
|
SECT_HTML = '§'
|
||||||
|
SECT_GEMTEXT = '§'
|
||||||
|
|
||||||
|
attr_reader :count, :text
|
||||||
|
|
||||||
|
def initialize(parent, *args)
|
||||||
|
super parent
|
||||||
|
|
||||||
|
case args.length
|
||||||
|
when 1
|
||||||
|
self.count = 1
|
||||||
|
self.text = args[0]
|
||||||
|
when 2
|
||||||
|
self.count, self.text = args
|
||||||
|
else
|
||||||
|
raise ArgumentError, 'Expected 1 or 2 arguments'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = count_words @text
|
||||||
|
|
||||||
|
def to_html = "#{SECT_HTML * count}#{text}".freeze
|
||||||
|
|
||||||
|
def to_gemtext = "#{SECT_GEMTEXT * count}#{text}".freeze
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def count=(count)
|
||||||
|
unless count.instance_of? Integer
|
||||||
|
raise TypeError, "Expected #{Integer}, got #{count.class}"
|
||||||
|
end
|
||||||
|
raise 'Expected positive count' unless count.positive?
|
||||||
|
|
||||||
|
@count = count
|
||||||
|
end
|
||||||
|
|
||||||
|
def text=(text)
|
||||||
|
text = String(text).strip.freeze
|
||||||
|
raise 'Expected non-empty text' if text.empty?
|
||||||
|
|
||||||
|
@text = text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
17
lib/repubmark/elems/separator.rb
Normal file
17
lib/repubmark/elems/separator.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Separator < Base
|
||||||
|
parents :Canvas
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def to_html = "<hr/>\n"
|
||||||
|
|
||||||
|
def to_gemtext = "---\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
35
lib/repubmark/elems/special.rb
Normal file
35
lib/repubmark/elems/special.rb
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
# TODO: maybe don't allow mdash everywhere
|
||||||
|
class Special < Base
|
||||||
|
parents :Joint
|
||||||
|
|
||||||
|
HTML = {
|
||||||
|
ellipsis: '…',
|
||||||
|
mdash: '—',
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
GEMTEXT = {
|
||||||
|
ellipsis: '…',
|
||||||
|
mdash: '—',
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
def initialize(parent, name)
|
||||||
|
super parent
|
||||||
|
name = String(name).to_sym.freeze
|
||||||
|
HTML.fetch name
|
||||||
|
@name = name
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def to_html = HTML.fetch @name
|
||||||
|
|
||||||
|
def to_gemtext = GEMTEXT.fetch @name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
41
lib/repubmark/elems/text.rb
Normal file
41
lib/repubmark/elems/text.rb
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
module Elems
|
||||||
|
class Text < Base
|
||||||
|
parents :Joint
|
||||||
|
|
||||||
|
def initialize(parent, str, bold: false, italic: false)
|
||||||
|
super parent
|
||||||
|
|
||||||
|
self.str = str
|
||||||
|
|
||||||
|
@bold = !!bold
|
||||||
|
@italic = !!italic
|
||||||
|
end
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Basic methods #
|
||||||
|
#################
|
||||||
|
|
||||||
|
def word_count = count_words @str
|
||||||
|
|
||||||
|
def to_html = str_to_html
|
||||||
|
|
||||||
|
def to_gemtext = @str
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def str=(str)
|
||||||
|
@str = String(str).split.join(' ').freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def str_to_html
|
||||||
|
result = @str
|
||||||
|
result = "<em>#{result}</em>" if @italic
|
||||||
|
result = "<strong>#{result}</strong>" if @bold
|
||||||
|
result.freeze
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
85
lib/repubmark/highlight.rb
Normal file
85
lib/repubmark/highlight.rb
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
class Highlight
|
||||||
|
NO_SYNTAX = 'txt'
|
||||||
|
|
||||||
|
NO_LINE_NUMBERS = %i[sh_hist].freeze
|
||||||
|
|
||||||
|
SYNTAXES = {
|
||||||
|
certbot: 'ini',
|
||||||
|
css: 'css',
|
||||||
|
hjson: nil,
|
||||||
|
html: 'html',
|
||||||
|
ini: 'ini',
|
||||||
|
knockd: nil,
|
||||||
|
nginx: 'nginx',
|
||||||
|
openssh: nil,
|
||||||
|
ruby: 'ruby',
|
||||||
|
sh_hist: nil,
|
||||||
|
sysctl: nil,
|
||||||
|
torrc: nil,
|
||||||
|
wireguard: 'ini',
|
||||||
|
yggdrasil: nil, # Hjson
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
private_class_method :new
|
||||||
|
|
||||||
|
def self.call(...) = new(...).call
|
||||||
|
|
||||||
|
attr_reader :syntax, :str
|
||||||
|
|
||||||
|
def initialize(syntax, str)
|
||||||
|
self.syntax = syntax
|
||||||
|
self.str = str
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
Open3.popen2(*cmdline) do |stdin, stdout, wait_thr|
|
||||||
|
stdin.write str
|
||||||
|
stdin.close
|
||||||
|
result = stdout.read.freeze
|
||||||
|
raise 'Highlight error' unless wait_thr.value.success?
|
||||||
|
|
||||||
|
"<pre><code>#{result}</code></pre>\n".freeze
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def syntax=(syntax)
|
||||||
|
return @syntax = nil if syntax.nil?
|
||||||
|
|
||||||
|
unless syntax.instance_of? Symbol
|
||||||
|
raise TypeError, "Expected #{Symbol}, got #{syntax.class}"
|
||||||
|
end
|
||||||
|
|
||||||
|
SYNTAXES.fetch syntax
|
||||||
|
|
||||||
|
@syntax = syntax
|
||||||
|
end
|
||||||
|
|
||||||
|
def str=(str)
|
||||||
|
str = String(str).strip.freeze
|
||||||
|
raise 'Expected non-empty string' if str.empty?
|
||||||
|
|
||||||
|
@str = str
|
||||||
|
end
|
||||||
|
|
||||||
|
def cmdline
|
||||||
|
@cmdline ||= [
|
||||||
|
'highlight',
|
||||||
|
'--stdout',
|
||||||
|
'--fragment',
|
||||||
|
'--out-format',
|
||||||
|
'html',
|
||||||
|
'--syntax',
|
||||||
|
SYNTAXES[syntax] || NO_SYNTAX,
|
||||||
|
].tap do |cmdline|
|
||||||
|
unless syntax.nil? || NO_LINE_NUMBERS.include?(syntax)
|
||||||
|
cmdline << '--line-numbers'
|
||||||
|
end
|
||||||
|
end.freeze
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
42
lib/repubmark/setup.rb
Normal file
42
lib/repubmark/setup.rb
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
class Setup
|
||||||
|
def initialize(**kwargs)
|
||||||
|
kwargs.each { |key, value| public_send :"#{key}=", value }
|
||||||
|
yield self if block_given?
|
||||||
|
freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :prologue, :epilogue
|
||||||
|
|
||||||
|
def prologue=(prologue)
|
||||||
|
prologue = String(prologue).strip.freeze
|
||||||
|
prologue = nil if prologue.empty?
|
||||||
|
@prologue = prologue
|
||||||
|
end
|
||||||
|
|
||||||
|
def epilogue=(epilogue)
|
||||||
|
epilogue = String(epilogue).strip.freeze
|
||||||
|
epilogue = nil if epilogue.empty?
|
||||||
|
@epilogue = epilogue
|
||||||
|
end
|
||||||
|
|
||||||
|
##########
|
||||||
|
# Locale #
|
||||||
|
##########
|
||||||
|
|
||||||
|
LOCALE_RE = /\A[a-z]{2,3}\z/
|
||||||
|
DEFAULT_LOCALE = :en
|
||||||
|
|
||||||
|
def locale = @locale || DEFAULT_LOCALE
|
||||||
|
|
||||||
|
def locale=(locale)
|
||||||
|
locale = String(locale).to_sym
|
||||||
|
raise 'Invalid locale' unless LOCALE_RE.match? locale
|
||||||
|
raise 'Locale has already been set' if @locale
|
||||||
|
|
||||||
|
@locale = locale
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
16
lib/repubmark/titled_ref.rb
Normal file
16
lib/repubmark/titled_ref.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Repubmark
|
||||||
|
class TitledRef
|
||||||
|
attr_reader :url, :title
|
||||||
|
|
||||||
|
def initialize(url, title)
|
||||||
|
self.url = url
|
||||||
|
self.title = title
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_writer :url, :title
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue