brandish 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.rubocop.yml +41 -0
- data/.travis.yml +5 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +9 -0
- data/bin/brandish +16 -0
- data/brandish.gemspec +39 -0
- data/defaults/templates/html.liquid +39 -0
- data/lib/brandish.rb +51 -0
- data/lib/brandish/application.rb +163 -0
- data/lib/brandish/application/bench_command.rb +96 -0
- data/lib/brandish/application/build_command.rb +73 -0
- data/lib/brandish/application/initialize_command.rb +83 -0
- data/lib/brandish/application/serve_command.rb +150 -0
- data/lib/brandish/configure.rb +196 -0
- data/lib/brandish/configure/dsl.rb +135 -0
- data/lib/brandish/configure/dsl/form.rb +136 -0
- data/lib/brandish/configure/form.rb +32 -0
- data/lib/brandish/errors.rb +65 -0
- data/lib/brandish/execute.rb +26 -0
- data/lib/brandish/markup.rb +10 -0
- data/lib/brandish/markup/redcarpet.rb +14 -0
- data/lib/brandish/markup/redcarpet/format.rb +127 -0
- data/lib/brandish/markup/redcarpet/html.rb +95 -0
- data/lib/brandish/parser.rb +26 -0
- data/lib/brandish/parser/main.rb +237 -0
- data/lib/brandish/parser/node.rb +89 -0
- data/lib/brandish/parser/node/block.rb +98 -0
- data/lib/brandish/parser/node/command.rb +102 -0
- data/lib/brandish/parser/node/pair.rb +42 -0
- data/lib/brandish/parser/node/root.rb +83 -0
- data/lib/brandish/parser/node/string.rb +18 -0
- data/lib/brandish/parser/node/text.rb +114 -0
- data/lib/brandish/path_set.rb +163 -0
- data/lib/brandish/processor.rb +47 -0
- data/lib/brandish/processor/base.rb +144 -0
- data/lib/brandish/processor/block.rb +47 -0
- data/lib/brandish/processor/command.rb +47 -0
- data/lib/brandish/processor/context.rb +169 -0
- data/lib/brandish/processor/descend.rb +32 -0
- data/lib/brandish/processor/inline.rb +49 -0
- data/lib/brandish/processor/name_filter.rb +67 -0
- data/lib/brandish/processor/pair_filter.rb +96 -0
- data/lib/brandish/processors.rb +26 -0
- data/lib/brandish/processors/all.rb +19 -0
- data/lib/brandish/processors/all/comment.rb +29 -0
- data/lib/brandish/processors/all/embed.rb +56 -0
- data/lib/brandish/processors/all/if.rb +109 -0
- data/lib/brandish/processors/all/import.rb +95 -0
- data/lib/brandish/processors/all/literal.rb +42 -0
- data/lib/brandish/processors/all/verify.rb +47 -0
- data/lib/brandish/processors/common.rb +20 -0
- data/lib/brandish/processors/common/asset.rb +118 -0
- data/lib/brandish/processors/common/asset/paths.rb +93 -0
- data/lib/brandish/processors/common/group.rb +67 -0
- data/lib/brandish/processors/common/header.rb +86 -0
- data/lib/brandish/processors/common/markup.rb +127 -0
- data/lib/brandish/processors/common/output.rb +73 -0
- data/lib/brandish/processors/html.rb +18 -0
- data/lib/brandish/processors/html/group.rb +33 -0
- data/lib/brandish/processors/html/header.rb +46 -0
- data/lib/brandish/processors/html/markup.rb +131 -0
- data/lib/brandish/processors/html/output.rb +62 -0
- data/lib/brandish/processors/html/output/document.rb +127 -0
- data/lib/brandish/processors/html/script.rb +64 -0
- data/lib/brandish/processors/html/script/babel.rb +48 -0
- data/lib/brandish/processors/html/script/coffee.rb +47 -0
- data/lib/brandish/processors/html/script/vanilla.rb +45 -0
- data/lib/brandish/processors/html/style.rb +82 -0
- data/lib/brandish/processors/html/style/highlight.rb +89 -0
- data/lib/brandish/processors/html/style/sass.rb +64 -0
- data/lib/brandish/processors/html/style/vanilla.rb +71 -0
- data/lib/brandish/processors/latex.rb +15 -0
- data/lib/brandish/processors/latex/markup.rb +47 -0
- data/lib/brandish/scanner.rb +64 -0
- data/lib/brandish/version.rb +9 -0
- data/templates/initialize/Gemfile.tt +14 -0
- data/templates/initialize/brandish.config.rb.tt +49 -0
- metadata +296 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "pathname"
|
|
5
|
+
require "brandish/configure/dsl/form"
|
|
6
|
+
|
|
7
|
+
module Brandish
|
|
8
|
+
class Configure
|
|
9
|
+
# The DSL for configuration files for Brandish. This is used to construct
|
|
10
|
+
# a configure object.
|
|
11
|
+
class DSL
|
|
12
|
+
# Creates a new DSL object with the given configuration object, and
|
|
13
|
+
# yields it.
|
|
14
|
+
#
|
|
15
|
+
# @param configure [Configure] The configuration instance.
|
|
16
|
+
# @return [DSL]
|
|
17
|
+
def self.call(configure = Configure.new)
|
|
18
|
+
DSL.new(configure).tap { |t| yield t }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Creates a DSL object with the given configuration object.
|
|
22
|
+
#
|
|
23
|
+
# @param configure [Configure]
|
|
24
|
+
def initialize(configure = Configure.new)
|
|
25
|
+
@configure = configure
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Sets a given option key to a value. The name is interned, making it
|
|
29
|
+
# a symbol.
|
|
30
|
+
#
|
|
31
|
+
# @param name [::Symbol, #intern] The name of the option key.
|
|
32
|
+
# @param value [::Object] The option value.
|
|
33
|
+
# @return [void]
|
|
34
|
+
def set(name, value)
|
|
35
|
+
@configure.options[name.intern] = value
|
|
36
|
+
end
|
|
37
|
+
alias_method :[]=, :set
|
|
38
|
+
|
|
39
|
+
# Retrives a given option key. The name is interned, making it a symbol.
|
|
40
|
+
#
|
|
41
|
+
# @param name [::Symbol, #intern] The name of the option key.
|
|
42
|
+
# @return [::Object] The option value.
|
|
43
|
+
def get(name)
|
|
44
|
+
@configure.options.fetch(name)
|
|
45
|
+
end
|
|
46
|
+
alias_method :[], :get
|
|
47
|
+
|
|
48
|
+
# Sets the root path of the Brandish project. This is where all of the
|
|
49
|
+
# important files are located. Very rarely should this be set to
|
|
50
|
+
# anything other than `"."`.
|
|
51
|
+
#
|
|
52
|
+
# @param root [::String, ::Pathname] The new root.
|
|
53
|
+
# @return [void]
|
|
54
|
+
def root=(root)
|
|
55
|
+
path = _expand_path(root, Dir.pwd)
|
|
56
|
+
self[:root] = path
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Retrieves the root path of the Brandish project. This is where all of
|
|
60
|
+
# the important files are located. Very rarely should this be set to
|
|
61
|
+
# anything other than `"."`.
|
|
62
|
+
#
|
|
63
|
+
# @return [::Pathname] The full path to the root.
|
|
64
|
+
def root
|
|
65
|
+
self[:root]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
alias_method :root_path=, :root=
|
|
69
|
+
alias_method :root_path, :root
|
|
70
|
+
|
|
71
|
+
# Sets the output directory of the Brandish project. This is where all
|
|
72
|
+
# of the outputs are placed. This is normally `"./output"`.
|
|
73
|
+
#
|
|
74
|
+
# @param output [::String, ::Pathname] The path to the output directory.
|
|
75
|
+
# @return [void]
|
|
76
|
+
def output=(output)
|
|
77
|
+
path = _expand_path(output, root)
|
|
78
|
+
self[:output] = path
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Retrieves the output directory of the Brandish project. This is where all
|
|
82
|
+
# of the outputs are placed. This is normally `"./output"`.
|
|
83
|
+
#
|
|
84
|
+
# @return [::Pathname] The full path to the output.
|
|
85
|
+
def output
|
|
86
|
+
self[:output]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
alias_method :output_path=, :output=
|
|
90
|
+
alias_method :output_path, :output
|
|
91
|
+
|
|
92
|
+
# Retrives the source directories of the Brandish project. This is where
|
|
93
|
+
# all of the sources are located. This is normally `"./source"`.
|
|
94
|
+
#
|
|
95
|
+
# @return [::Pathname] The full path to the sources.
|
|
96
|
+
def sources
|
|
97
|
+
self[:sources]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
alias_method :source_paths, :sources
|
|
101
|
+
|
|
102
|
+
# Retrieves the template directory of the Brandish project. This is
|
|
103
|
+
# where all of the templates are placed. This is normally `"./template"`.
|
|
104
|
+
#
|
|
105
|
+
# @return [::Pathname] The full path to the template.
|
|
106
|
+
def templates
|
|
107
|
+
self[:templates]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
alias_method :template_paths, :templates
|
|
111
|
+
|
|
112
|
+
# Creates a new form for the configuration object. This takes arguments
|
|
113
|
+
# and a block. The block is yielded the form instance.
|
|
114
|
+
#
|
|
115
|
+
# @see DSL::Form
|
|
116
|
+
# @see Configure::Form
|
|
117
|
+
# @param (see DSL::Form#instance)
|
|
118
|
+
# @yield [form]
|
|
119
|
+
# @return [void]
|
|
120
|
+
def form(*arguments)
|
|
121
|
+
instance = DSL::Form.new(*arguments)
|
|
122
|
+
yield instance
|
|
123
|
+
form = Configure::Form.new(*instance.data)
|
|
124
|
+
@configure.forms << form
|
|
125
|
+
form
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
def _expand_path(path, directory)
|
|
131
|
+
::Pathname.new(path).expand_path(::Pathname.new(directory))
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Brandish
|
|
5
|
+
class Configure
|
|
6
|
+
class DSL
|
|
7
|
+
# Constructs a form for use with the configure instance. A form is
|
|
8
|
+
# basically a format and name pair; different forms can have different
|
|
9
|
+
# entries, formats, and processors.
|
|
10
|
+
class Form
|
|
11
|
+
# The name of the form. This defaults to a random value. Do not
|
|
12
|
+
# rely on the name unless it is set.
|
|
13
|
+
#
|
|
14
|
+
# @return [::String]
|
|
15
|
+
attr_writer :name
|
|
16
|
+
|
|
17
|
+
# The entry for the form. This is the file that begins the parsing
|
|
18
|
+
# for the form.
|
|
19
|
+
#
|
|
20
|
+
# @return [::String]
|
|
21
|
+
attr_writer :entry
|
|
22
|
+
|
|
23
|
+
# Initialize the form DSL. This sets up the initial format and name,
|
|
24
|
+
# and intializes the instance variables.
|
|
25
|
+
#
|
|
26
|
+
# @param format [::Symbol] The format.
|
|
27
|
+
# @param name [::String, nil] The name of the form.
|
|
28
|
+
def initialize(format, name = nil)
|
|
29
|
+
@name = name || _generate_form_name
|
|
30
|
+
@format = format
|
|
31
|
+
@entry = "index.br"
|
|
32
|
+
@processors = []
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Adds a processor for use for the form. The order that this is called
|
|
36
|
+
# is important.
|
|
37
|
+
#
|
|
38
|
+
# @example
|
|
39
|
+
# config.form :html do |form|
|
|
40
|
+
# form.use :literal # Can expand to either "html:literal" or "all:literal"
|
|
41
|
+
# # in that order.
|
|
42
|
+
# form.use "all:literal" # Only expands to "all:literal"
|
|
43
|
+
# form.use Brandish::Processors::All::Literal # allowed too
|
|
44
|
+
# form.use do
|
|
45
|
+
# # also allowed
|
|
46
|
+
# end
|
|
47
|
+
# end
|
|
48
|
+
# @overload use(name, options = {})
|
|
49
|
+
# @param name [::String, ::Symbol, <::Symbol>, ::Class] The name of the
|
|
50
|
+
# processor. If the processor is a symbol, it is assumed to be the
|
|
51
|
+
# name of the processor; the format is guessed to be either
|
|
52
|
+
# the format of the form, or `:all`. If the processor is a string,
|
|
53
|
+
# and it contains a colon, it is assumed to be the name of the
|
|
54
|
+
# processor in the form `<format>:<name>`; if it doesn't contain
|
|
55
|
+
# the colon, it is treated as a symbol. If it is an array, it is
|
|
56
|
+
# assumed to be a pair containing the name of the process. If it
|
|
57
|
+
# is a class, it is assumed to be a processor, and is used directly.
|
|
58
|
+
# @param options [{::Object => ::Object}] The options for the
|
|
59
|
+
# processor. The allowed values for the options are dependant on
|
|
60
|
+
# the processor.
|
|
61
|
+
# @overload use(options = {}, &block)
|
|
62
|
+
# @param options [{::Object => ::Object}] The options for the
|
|
63
|
+
# processor. The allowed values for the options are dependant on
|
|
64
|
+
# the processor.
|
|
65
|
+
# @yield into a new class; this allows a processor to be defined
|
|
66
|
+
# without having a named class.
|
|
67
|
+
# @return [void]
|
|
68
|
+
def use(*arguments)
|
|
69
|
+
processor = options = nil
|
|
70
|
+
|
|
71
|
+
if block_given?
|
|
72
|
+
options = arguments[0]
|
|
73
|
+
processor = Class.new(Brandish::Processor::Base, &::Proc.new)
|
|
74
|
+
else
|
|
75
|
+
name = arguments[0]
|
|
76
|
+
options = arguments[1]
|
|
77
|
+
processor = _guess_processor_name(name)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
@processors << [processor, options || {}]
|
|
81
|
+
end
|
|
82
|
+
alias_method :process, :use
|
|
83
|
+
alias_method :processor, :use
|
|
84
|
+
alias_method :define, :use
|
|
85
|
+
|
|
86
|
+
# The data from this form. This is used to create the actual form that
|
|
87
|
+
# is used in the configuration.
|
|
88
|
+
#
|
|
89
|
+
# @return [(::String, ::Symbol, ::String, <(::String, ::String, ::Hash)>]
|
|
90
|
+
def data
|
|
91
|
+
[@name, @format, @entry, @processors]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def _generate_form_name
|
|
97
|
+
processor_names = @processors.map { |p| p[0..1].join(":") }.join(".")
|
|
98
|
+
"#{@format}.#{processor_names}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def _guess_processor_name(name)
|
|
102
|
+
case name
|
|
103
|
+
when ::String then _guess_processor_name_string(name)
|
|
104
|
+
when ::Symbol then _guess_processor_name_symbol(name)
|
|
105
|
+
when ::Array then _guess_processor_name_array(name)
|
|
106
|
+
when ::Class then name # They're providing a class that can be used.
|
|
107
|
+
else
|
|
108
|
+
fail ::ArgumentError, "Unknown type given for name `#{name}`"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def _guess_processor_name_string(name)
|
|
113
|
+
parts = name.split(":")
|
|
114
|
+
|
|
115
|
+
case parts
|
|
116
|
+
when 1 then _guess_processor_name_symbol(name)
|
|
117
|
+
when 2 then _guess_processor_name_array(parts)
|
|
118
|
+
else
|
|
119
|
+
_guess_processor_name_array([parts[0], parts[1..-1].join(":")])
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def _guess_processor_name_symbol(name)
|
|
124
|
+
default = [@format, name]
|
|
125
|
+
allowed = [default, [:all, name]]
|
|
126
|
+
which = allowed.find { |a| Processor.all.key?(a) } || default
|
|
127
|
+
_guess_processor_name_array(which)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def _guess_processor_name_array(name)
|
|
131
|
+
name.map(&:intern)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "pry"
|
|
5
|
+
require "pry-rescue"
|
|
6
|
+
|
|
7
|
+
module Brandish
|
|
8
|
+
class Configure
|
|
9
|
+
# A form used for building.
|
|
10
|
+
Form = Struct.new(:name, :format, :entry, :processors) do
|
|
11
|
+
# Builds the form. This takes a configure object, and builds the
|
|
12
|
+
# form based on that.
|
|
13
|
+
#
|
|
14
|
+
# @see Configure#roots
|
|
15
|
+
# @see Processor::Context
|
|
16
|
+
# @see Processor
|
|
17
|
+
# @param configure [Configure] The configuration object for this build.
|
|
18
|
+
# @return [void]
|
|
19
|
+
def build(configure)
|
|
20
|
+
context = Processor::Context.new(configure, self)
|
|
21
|
+
root = configure.roots[configure.sources.find(entry)]
|
|
22
|
+
|
|
23
|
+
processors.each do |(processor, options)|
|
|
24
|
+
klass = processor.is_a?(::Array) ? Processor.all.fetch(processor) : processor
|
|
25
|
+
klass.new(context, options)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context.process(root)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Brandish
|
|
5
|
+
# An error that originates from the library for a library-specific reason.
|
|
6
|
+
# All libraries errors inherit from this class.
|
|
7
|
+
class Error < ::StandardError; end
|
|
8
|
+
|
|
9
|
+
# The file could not be found.
|
|
10
|
+
class NoFileError < Error; end
|
|
11
|
+
|
|
12
|
+
# An error that is created when there is a problem with scanning the
|
|
13
|
+
# document.
|
|
14
|
+
class ScanError < Error; end
|
|
15
|
+
|
|
16
|
+
# An error that occurs with setting up a processor. This has no location
|
|
17
|
+
# information because this error occurs independant of a document.
|
|
18
|
+
class ProcessorError < Error; end
|
|
19
|
+
|
|
20
|
+
# The processor has not been implemented.
|
|
21
|
+
class ProcessorNotImplementedError < ProcessorError; end
|
|
22
|
+
|
|
23
|
+
# This should never be used directly. This is an error that is tied to
|
|
24
|
+
# a location; as such, it provides an initalizer for providing a location.
|
|
25
|
+
#
|
|
26
|
+
# @api private
|
|
27
|
+
class LocationError < Error
|
|
28
|
+
# The location of the error in a file.
|
|
29
|
+
#
|
|
30
|
+
# @return [Location]
|
|
31
|
+
attr_reader :location
|
|
32
|
+
|
|
33
|
+
# Initialize the error with the given location and message.
|
|
34
|
+
def initialize(message, location = Location.default, bt = caller[1..-1])
|
|
35
|
+
@location = location
|
|
36
|
+
super(message)
|
|
37
|
+
set_backtrace(bt)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# An error that occurs when parsing. This is for unexpected tokens.
|
|
42
|
+
class ParseError < LocationError; end
|
|
43
|
+
|
|
44
|
+
# An error that is created when there is an issue interacting with a parser
|
|
45
|
+
# node.
|
|
46
|
+
class NodeError < ParseError; end
|
|
47
|
+
|
|
48
|
+
# An error occured during a build.
|
|
49
|
+
class BuildError < LocationError; end
|
|
50
|
+
|
|
51
|
+
# An error occurred while building in a processor. This includes location
|
|
52
|
+
# information from the original node.
|
|
53
|
+
class ProcessorBuildError < BuildError; end
|
|
54
|
+
|
|
55
|
+
# An error occurred while verifying the build. This includes location
|
|
56
|
+
# information for the invalid node.
|
|
57
|
+
class VerificationBuildError < ProcessorBuildError; end
|
|
58
|
+
|
|
59
|
+
# An error occurred with a part of a command or block node, in which a pair
|
|
60
|
+
# was not included.
|
|
61
|
+
class PairError < ProcessorBuildError; end
|
|
62
|
+
|
|
63
|
+
# The syntax used for the styling was invalid.
|
|
64
|
+
class ElementSyntaxError < ProcessorBuildError; end
|
|
65
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Brandish
|
|
5
|
+
# Executes a string of code in the instance of the class.
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
class Execute
|
|
9
|
+
# Initialize the execution context.
|
|
10
|
+
#
|
|
11
|
+
# @param context [{::Symbol, ::String => ::Object}] The context. The keys
|
|
12
|
+
# are set as instance variables on the class, with the values being the
|
|
13
|
+
# instance variable's respective value.
|
|
14
|
+
def initialize(context)
|
|
15
|
+
context.each { |k, v| instance_variable_set(:"@#{k}", v) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Executes the given code in the context of the class.
|
|
19
|
+
#
|
|
20
|
+
# @param code [::String] The code to execute.
|
|
21
|
+
# @return [::Object]
|
|
22
|
+
def exec(code)
|
|
23
|
+
instance_exec(code)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Brandish
|
|
5
|
+
# Markup modules for use with the {Processors::Common::Markup} processor.
|
|
6
|
+
# This is only to provide certain integrations with Brandish.
|
|
7
|
+
module Markup
|
|
8
|
+
autoload :Redcarpet, "brandish/markup/redcarpet"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "brandish/markup/redcarpet/html"
|
|
5
|
+
require "brandish/markup/redcarpet/format"
|
|
6
|
+
|
|
7
|
+
module Brandish
|
|
8
|
+
module Markup
|
|
9
|
+
# The Redcarpet format. This is used with the
|
|
10
|
+
# {Processors::Common::Markup} processor.
|
|
11
|
+
module Redcarpet
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "pry"
|
|
5
|
+
|
|
6
|
+
module Brandish
|
|
7
|
+
module Markup
|
|
8
|
+
module Redcarpet
|
|
9
|
+
# Formats text with Redcarpet. This is extracted into a seperate object
|
|
10
|
+
# in order to provide highlighting properties. This is
|
|
11
|
+
# format-independant.
|
|
12
|
+
#
|
|
13
|
+
# This class can take the following options:
|
|
14
|
+
#
|
|
15
|
+
# - All Markdown extension options that are listed on
|
|
16
|
+
# <https://github.com/vmg/redcarpet>, e.g. `:no_intra_emphasis`,
|
|
17
|
+
# `:tables`, etc.
|
|
18
|
+
# - All HTML formatter options that are listed on
|
|
19
|
+
# <https://github.com/vmg/redcarpet>, e.g. `:filter_html`,
|
|
20
|
+
# `:no_images`, etc.
|
|
21
|
+
# - `:highlight` - Optional. The highlighting engine to use. Can be
|
|
22
|
+
# one of `:rouge`, `:coderay`, `:pygments`, and `:none`. Remember to
|
|
23
|
+
# include the requisite highlighting engine in your Gemfile. They
|
|
24
|
+
# will automatically be required as needed. Defaults to `:none`.
|
|
25
|
+
class Format
|
|
26
|
+
# The options that are passed over to the markdown engine. These
|
|
27
|
+
# are extracted from the options that are passed to the markup engine.
|
|
28
|
+
#
|
|
29
|
+
# @return [<::Symbol>]
|
|
30
|
+
MARKDOWN_OPTIONS = %i(
|
|
31
|
+
no_intra_emphasis tables fenced_code_blocks autolink
|
|
32
|
+
disable_indented_code_blocks strikethrough lax_spacing
|
|
33
|
+
space_after_headers superscript underline quote footnotes
|
|
34
|
+
).freeze
|
|
35
|
+
|
|
36
|
+
# The default options for the markdown engine as passed by this markup
|
|
37
|
+
# engine.
|
|
38
|
+
#
|
|
39
|
+
# @return [{::Symbol => ::Object}]
|
|
40
|
+
MARKDOWN_DEFAULTS = {
|
|
41
|
+
fenced_code_blocks: true, tables: true, autolink: true,
|
|
42
|
+
strikethrough: true, superscript: true, underline: true,
|
|
43
|
+
footnotes: true, space_after_headers: true
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
# The options that are passed over to the formatting engine. These are
|
|
47
|
+
# Extracted from the options that are passed to the markup engine.
|
|
48
|
+
#
|
|
49
|
+
# @return [<::Symbol>]
|
|
50
|
+
FORMAT_OPTIONS = %i(
|
|
51
|
+
filter_html no_images no_links no_styles escape_html safe_links_only
|
|
52
|
+
with_toc_data hard_wrap xhtml prettify link_attributes highlight
|
|
53
|
+
).freeze
|
|
54
|
+
|
|
55
|
+
# The default options for the formatting engine as passed by this
|
|
56
|
+
# markup engine.
|
|
57
|
+
#
|
|
58
|
+
# @return [{::Symbol => ::Object}]
|
|
59
|
+
FORMAT_DEFAULTS = { highlight: :none }.freeze
|
|
60
|
+
|
|
61
|
+
# The highlighting engines that are supported by this markup engine.
|
|
62
|
+
# The key is the value passed by the `:highlight` option, and the value
|
|
63
|
+
# is the require file name. If the value is `nil`, no requirement
|
|
64
|
+
# is performed.
|
|
65
|
+
#
|
|
66
|
+
# @return [{::Symbol => ::Object, nil}]
|
|
67
|
+
HIGHLIGHTERS = {
|
|
68
|
+
rouge: "rouge", coderay: "coderay", pygments: "pygments",
|
|
69
|
+
none: nil
|
|
70
|
+
}.freeze
|
|
71
|
+
|
|
72
|
+
# The formating engines that can be used by this markup engine.
|
|
73
|
+
#
|
|
74
|
+
# @return [{::Symbol => ::Class}]
|
|
75
|
+
FORMATTERS = { html: HTML }.freeze
|
|
76
|
+
|
|
77
|
+
# Initialize the markup engine for Redcarpet. For the available
|
|
78
|
+
# options, see {Format}.
|
|
79
|
+
#
|
|
80
|
+
# @param options [::Hash]
|
|
81
|
+
def initialize(options)
|
|
82
|
+
@context = options.fetch(:context)
|
|
83
|
+
@format = options.fetch(:format)
|
|
84
|
+
@markdown_options = MARKDOWN_DEFAULTS
|
|
85
|
+
.merge(extract_options(MARKDOWN_OPTIONS, options))
|
|
86
|
+
@formatter_options = FORMAT_DEFAULTS
|
|
87
|
+
.merge(extract_options(FORMAT_OPTIONS, options))
|
|
88
|
+
@highlight = @formatter_options[:highlight]
|
|
89
|
+
load_highlighter
|
|
90
|
+
load_engine
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Renders the given text using the engine.
|
|
94
|
+
#
|
|
95
|
+
# @param string [::String] The value to render.
|
|
96
|
+
# @return [::String] The rendered value.
|
|
97
|
+
def render(string)
|
|
98
|
+
@engine.render(string)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def load_highlighter
|
|
104
|
+
file = HIGHLIGHTERS.fetch(@highlight)
|
|
105
|
+
require file if file
|
|
106
|
+
rescue ::KeyError
|
|
107
|
+
fail ProcessorError, "Unknown highlighter `#{@highlight}`"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def load_engine
|
|
111
|
+
begin
|
|
112
|
+
formatter = FORMATTERS.fetch(@format)
|
|
113
|
+
rescue ::KeyError
|
|
114
|
+
fail ProcessorError, "Unsupported format `#{@format}`"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
renderer = formatter.new(@context, @formatter_options)
|
|
118
|
+
@engine = ::Redcarpet::Markdown.new(renderer, @markdown_options)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def extract_options(keys, options)
|
|
122
|
+
keys.zip(options.values_at(*keys)).select!(&:last).to_h
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|