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,29 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Brandish
|
|
5
|
+
module Processors
|
|
6
|
+
module All
|
|
7
|
+
# Adds a `<comment>` tag. This ignores all of the elements inside of
|
|
8
|
+
# it, effectively removing it from the output.
|
|
9
|
+
#
|
|
10
|
+
# This processor takes no options, nor any pairs.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# Congratulations! You've won $1,000!
|
|
14
|
+
# <comment>Before taxes, anyway.</comment>
|
|
15
|
+
class Comment < Processor::Base
|
|
16
|
+
include Processor::Block
|
|
17
|
+
self.names = %i(comment ignore)
|
|
18
|
+
register %i(all comment) => self
|
|
19
|
+
|
|
20
|
+
# Returns nil, removing the node and its children from the tree.
|
|
21
|
+
#
|
|
22
|
+
# @return [nil]
|
|
23
|
+
def process
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Brandish
|
|
5
|
+
module Processors
|
|
6
|
+
module All
|
|
7
|
+
# Provides a block that allows Ruby code to be executed within it.
|
|
8
|
+
#
|
|
9
|
+
# Options:
|
|
10
|
+
#
|
|
11
|
+
# - `:really` - Optional. If this isn't the exact value `true`, this
|
|
12
|
+
# processor is never added to the context, preventing embed blocks from
|
|
13
|
+
# being used.
|
|
14
|
+
#
|
|
15
|
+
# This takes no specific pairs; however, the embedded execution context
|
|
16
|
+
# has access to all of the pairs that are passed to the element.
|
|
17
|
+
#
|
|
18
|
+
# @note
|
|
19
|
+
# This is **very dangerous** - ONLY USE IF YOU TRUST THE SOURCE. This
|
|
20
|
+
# processor provides no sandboxing by default.
|
|
21
|
+
class Embed < Processor::Base
|
|
22
|
+
include Processor::Block
|
|
23
|
+
self.names = %i(embed ruby)
|
|
24
|
+
register %i(all embed) => self
|
|
25
|
+
unrestricted_pairs!
|
|
26
|
+
|
|
27
|
+
# (see Processor::Base#initlaize)
|
|
28
|
+
def initialize(context, options = {})
|
|
29
|
+
super if true.equal?(options[:really])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Creates a {Brandish::Execute} instance and executes the code. This
|
|
33
|
+
# returns the return value of the code executed; therefore, if the
|
|
34
|
+
# code returns a string value, it is added to the output; otherwise,
|
|
35
|
+
# if it returns a nil value, it is ignored. Those are the only two
|
|
36
|
+
# values that should be returned; others will be rejected.
|
|
37
|
+
#
|
|
38
|
+
# @return [::String, nil]
|
|
39
|
+
def perform
|
|
40
|
+
Execute.new(execute_context).exec(@body.flatten)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The context that is passed to {Brandish::Execute#initialize}. This
|
|
44
|
+
# provides the context, the "pairs" that were passed to the block, the
|
|
45
|
+
# options that were passed to the processor, the format that it is
|
|
46
|
+
# being executed in, and the form name.
|
|
47
|
+
#
|
|
48
|
+
# @return [{::Symbol => ::Object}]
|
|
49
|
+
def execute_context
|
|
50
|
+
{ context: @context, pairs: @pairs, options: @options,
|
|
51
|
+
format: @context.form.format, form: @context.form.name }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Brandish
|
|
5
|
+
module Processors
|
|
6
|
+
module All
|
|
7
|
+
# Provides both an `if` and an `unless` block into the output. The
|
|
8
|
+
# processor takes one option: `:embed`. If embed _is_ `true` (using
|
|
9
|
+
# strict equalivalence `equal?`), then the if statement takes a single
|
|
10
|
+
# pair, named `"condition"`. This condition is executed using
|
|
11
|
+
# {Brandish::Execute}, and if it is true, and if it is an `if` block,
|
|
12
|
+
# the contents are included; otherwise, if it is false, and if it is
|
|
13
|
+
# an `unless` block, the contents are included; otherwise, they are
|
|
14
|
+
# ignored. If embed is `false`, the block can take multiple pairs, and
|
|
15
|
+
# each pair is matched to a name. If the pair's value equals the matched
|
|
16
|
+
# name's value, then the condition is true. If the condition is true,
|
|
17
|
+
# and the block is an `if` block, the contents are included; otherwise,
|
|
18
|
+
# if it is false, and if it is an `unless` block, the contents are
|
|
19
|
+
# included; otherwise, they are not. The pair conditions that are
|
|
20
|
+
# matched by default are `"format"` and `"form"`. If pairs are included
|
|
21
|
+
# that are not provided, it errors.
|
|
22
|
+
#
|
|
23
|
+
# Options:
|
|
24
|
+
#
|
|
25
|
+
# - `:embed` - Optional. Whether or not the condition should be
|
|
26
|
+
# treaded like an embedded condition. This must be set to the exact
|
|
27
|
+
# value of `true` for it to be accepted.
|
|
28
|
+
#
|
|
29
|
+
# Pairs:
|
|
30
|
+
#
|
|
31
|
+
# - `"condition"` - Optional. The embedded condition for the `if` or
|
|
32
|
+
# `unless` block. Only applies if the `:embed` option is set.
|
|
33
|
+
# - `"format"` - Optional. If this is provided, and the `:embed` option
|
|
34
|
+
# is not set, it is added as a condition.
|
|
35
|
+
# - `"form"` - Optional. If this is provided, and the `:embed` option
|
|
36
|
+
# is not set, it is added as a condition.
|
|
37
|
+
#
|
|
38
|
+
# @example non-embed
|
|
39
|
+
# <if format="html">
|
|
40
|
+
# <import file="html-style" />
|
|
41
|
+
# </if>
|
|
42
|
+
# @example embed
|
|
43
|
+
# <if condition="@format == :html">
|
|
44
|
+
# <import file="html-style" />
|
|
45
|
+
# </if>
|
|
46
|
+
# @note
|
|
47
|
+
# Using the `:embed` option is **very dangerous** - ONLY USE IF YOU
|
|
48
|
+
# TRUST THE SOURCE. `:embed` provides no sandboxing by default. This
|
|
49
|
+
# is why its value _must_ be set to be `true` exactly, and not
|
|
50
|
+
# any other value.
|
|
51
|
+
class If < Processor::Base
|
|
52
|
+
include Processor::Block
|
|
53
|
+
self.names = [:if, :unless]
|
|
54
|
+
register %i(all if) => self
|
|
55
|
+
unrestricted_pairs!
|
|
56
|
+
|
|
57
|
+
# If {#meets_conditions?} is true, this accepts the body of the block
|
|
58
|
+
# for processing; otherwise, it returns `nil`, effectively causing
|
|
59
|
+
# the body to be ignored.
|
|
60
|
+
#
|
|
61
|
+
# @return [Parser::Node, nil]
|
|
62
|
+
def perform
|
|
63
|
+
accept(@body) if meets_conditions?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# If the name of this block is `"if"`, and all of the conditions are
|
|
67
|
+
# matched as outlined in the class direction, then this returns
|
|
68
|
+
# `true`; otherwise, if the name of this block is `"if"`, and any of
|
|
69
|
+
# the conditions are not matched as outlined in the class description,
|
|
70
|
+
# then this returns `false`; otherwise, if the name of this block is
|
|
71
|
+
# anything else (i.e. `"unless"`), and all of the conditions are
|
|
72
|
+
# matched as outlined in the class description, then this returns
|
|
73
|
+
# `false`; otherwise, if any of the conditions are not matched as
|
|
74
|
+
# outlined in the class description, then this returns `true`.
|
|
75
|
+
#
|
|
76
|
+
# @return [::Boolean]
|
|
77
|
+
def meets_conditions?
|
|
78
|
+
@name == "if" ? match_conditions : !match_conditions
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def match_conditions
|
|
84
|
+
true.equal?(@options[:embed]) ? exec_condition : pair_condition
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def exec_condition
|
|
88
|
+
context = Brandish::Execute.new(execute_context)
|
|
89
|
+
context.exec(@pairs.fetch("condition"))
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def execute_context
|
|
93
|
+
{ context: @context, pairs: @pairs, options: @options,
|
|
94
|
+
format: @context.form.format, form: @context.form.name }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def pair_condition
|
|
98
|
+
@pairs.all? { |k, v| match_pair.fetch(k.to_s).to_s == v.to_s }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def match_pair
|
|
102
|
+
@_match_pair ||= {
|
|
103
|
+
"format" => @context.form.format, "form" => @context.form.name
|
|
104
|
+
}.freeze
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "open-uri"
|
|
5
|
+
|
|
6
|
+
module Brandish
|
|
7
|
+
module Processors
|
|
8
|
+
module All
|
|
9
|
+
# "Imports" another file into this current file. This takes the file,
|
|
10
|
+
# parses it, and inserts it directly into the position that the import
|
|
11
|
+
# was placed. By default, this forces the file to be in the source
|
|
12
|
+
# directory, doing some awkward joining to make it work. This takes
|
|
13
|
+
# one pair - `"name"`, `"link"`, or `"file"` - which contains the name of
|
|
14
|
+
# the file to import. It takes two options: `:absolute_allowed`, which
|
|
15
|
+
# allows imports to be outside of the source directory; and
|
|
16
|
+
# `:remote_allowed`, which allows remote files to be imported, using any
|
|
17
|
+
# of the protocols supported by open-uri.
|
|
18
|
+
#
|
|
19
|
+
# For local file imports, if no extensions were provided, an extension
|
|
20
|
+
# of `.br` is automatically added. For remote files, extensions are
|
|
21
|
+
# always required.
|
|
22
|
+
#
|
|
23
|
+
# Imports are handled as if the entire imported file was copy and pasted
|
|
24
|
+
# into the source document.
|
|
25
|
+
#
|
|
26
|
+
# Options:
|
|
27
|
+
#
|
|
28
|
+
# - `:absolute_allowed` - Optional. Despite its name, if this value is
|
|
29
|
+
# `true`, imports can be used with any file that is outside of the
|
|
30
|
+
# source directory. Otherwise, the files will be forced to be inside
|
|
31
|
+
# the source directory, as if the source directory is the root of the
|
|
32
|
+
# file system.
|
|
33
|
+
# - `:remote_allowed` - Optional. Whether remote imports should be
|
|
34
|
+
# allowed.
|
|
35
|
+
#
|
|
36
|
+
# Pairs:
|
|
37
|
+
#
|
|
38
|
+
# - `"src"`, `"file"`, `"name"`, or `"link"` - Required. These all do the
|
|
39
|
+
# same thing - it provides the name of the file to import.
|
|
40
|
+
#
|
|
41
|
+
# @example local file
|
|
42
|
+
# <import file="some-file" />
|
|
43
|
+
# @example remote file
|
|
44
|
+
# <import link="http://example.org/some-file.br" />
|
|
45
|
+
# @example absolute local file
|
|
46
|
+
# <import file="/opt/brandish/sources/html" />
|
|
47
|
+
# @note
|
|
48
|
+
# Be careful when using `:remote_allowed` - this can cause possible
|
|
49
|
+
# security issues if the remote is not trusted.
|
|
50
|
+
class Import < Processor::Base
|
|
51
|
+
include Processor::Command
|
|
52
|
+
register %i(all import) => self
|
|
53
|
+
pairs :src, :file, :name, :link
|
|
54
|
+
|
|
55
|
+
# Accepts the root node of the parsed file, processing the result as
|
|
56
|
+
# if it were an extension of the original source tree.
|
|
57
|
+
#
|
|
58
|
+
# @return [Parser::Node]
|
|
59
|
+
def perform
|
|
60
|
+
accept(parse_file)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def load_file
|
|
66
|
+
file = @pairs["src"] || @pairs["file"] || @pairs["name"] ||
|
|
67
|
+
@pairs["link"]
|
|
68
|
+
return file if file
|
|
69
|
+
fail PairError.new("Expected one of src, file, name, or link, " \
|
|
70
|
+
"got nothing", @node.location)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def parse_file
|
|
74
|
+
load_file =~ /\A(https?|ftp):/ ? parse_remote_file : parse_local_file
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def parse_remote_file
|
|
78
|
+
fail_remote_file unless @options[:remote_allowed]
|
|
79
|
+
open(load_file) { |io| @context.configure.parse_from(io, file) }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def fail_remote_file
|
|
83
|
+
fail PairError.new("Remote file given, but not supported", @node.location)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def parse_local_file
|
|
87
|
+
file = ::Pathname.new(load_file)
|
|
88
|
+
file = file.sub_ext(".br") unless load_file =~ /\.(.+?)\z/
|
|
89
|
+
path = @context.configure.sources.find(file)
|
|
90
|
+
@context.configure.roots[path]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Brandish
|
|
5
|
+
module Processors
|
|
6
|
+
module All
|
|
7
|
+
# Takes the contents of the block, and outputs it, without any
|
|
8
|
+
# processing. This is used to bypass any text processing processors
|
|
9
|
+
# that are being used. This does nothing for command or block elements
|
|
10
|
+
# in the literal block, and the existance of such elements will cause
|
|
11
|
+
# errors.
|
|
12
|
+
#
|
|
13
|
+
# Because of the way the parser works, the original source text
|
|
14
|
+
# information is discarded for command and block elements, since they
|
|
15
|
+
# are unneeded for building the nodes. This means that it is not
|
|
16
|
+
# possible to reconstruct, from only the AST, the original text without
|
|
17
|
+
# slight variances (which we don't want). (The other option would be
|
|
18
|
+
# to use the location information to grab it from the file itself, but
|
|
19
|
+
# as of right now, location information is unreliable.) This is why
|
|
20
|
+
# the literal tag cannot contain other command or block elements.
|
|
21
|
+
#
|
|
22
|
+
# This processor takes no options, nor any pairs.
|
|
23
|
+
#
|
|
24
|
+
# @example
|
|
25
|
+
# <l>**Test**</l> this.
|
|
26
|
+
# # => "**Test** this."
|
|
27
|
+
class Literal < Processor::Base
|
|
28
|
+
include Processor::Block
|
|
29
|
+
self.names = %i(literal raw l)
|
|
30
|
+
register %i(all literal) => self
|
|
31
|
+
|
|
32
|
+
# Preforms the literalizing of the body. Right now, this just uses
|
|
33
|
+
# {Parser::Node::Root#flatten}.
|
|
34
|
+
#
|
|
35
|
+
# @return [::String]
|
|
36
|
+
def perform
|
|
37
|
+
@body.flatten
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Brandish
|
|
5
|
+
module Processors
|
|
6
|
+
module All
|
|
7
|
+
# This does nothing to the tree. This just "verifies" that there are
|
|
8
|
+
# no remaining blocks or commands in the node tree, as they shouldn't be.
|
|
9
|
+
# This is highly recommended, as most Output processors are unable to
|
|
10
|
+
# handle the remaining blocks or commands.
|
|
11
|
+
#
|
|
12
|
+
# This processor takes no options.
|
|
13
|
+
class Verify < Processor::Base
|
|
14
|
+
register %i(all verify) => self
|
|
15
|
+
|
|
16
|
+
# Processes a block. This always fails by design.
|
|
17
|
+
#
|
|
18
|
+
# @param node [Parser::Node::Block]
|
|
19
|
+
# @raise BuildError
|
|
20
|
+
def process_block(node)
|
|
21
|
+
fail VerificationBuildError.new(error_message(node), node.location)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Processes a command. This always fails by design.
|
|
25
|
+
#
|
|
26
|
+
# @param node [Parser::Node::Command]
|
|
27
|
+
# @raise BuildError
|
|
28
|
+
def process_command(node)
|
|
29
|
+
fail VerificationBuildError.new(error_message(node), node.location)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def error_message(node)
|
|
35
|
+
"Unexpected command or block element `#{node.name}' at " \
|
|
36
|
+
"#{node.location}; try adding #{suggested_processor(node)} to " \
|
|
37
|
+
"your `brandish.config.rb' file"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def suggested_processor(node)
|
|
41
|
+
"`form.use #{node.name.intern.inspect}' or " \
|
|
42
|
+
"`form.use #{"all:#{node.name}".inspect}'"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "brandish/processors/common/group"
|
|
5
|
+
require "brandish/processors/common/header"
|
|
6
|
+
require "brandish/processors/common/markup"
|
|
7
|
+
require "brandish/processors/common/output"
|
|
8
|
+
require "brandish/processors/common/style"
|
|
9
|
+
|
|
10
|
+
module Brandish
|
|
11
|
+
module Processors
|
|
12
|
+
# Common processors. These are processors that are required to be
|
|
13
|
+
# implemented for all formats, but are processors that can be implemented
|
|
14
|
+
# as an {All} processor.
|
|
15
|
+
#
|
|
16
|
+
# @abstract
|
|
17
|
+
module Common
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "brandish/processors/common/asset/paths"
|
|
5
|
+
|
|
6
|
+
module Brandish
|
|
7
|
+
module Processors
|
|
8
|
+
module Common
|
|
9
|
+
# Allows assets to be included in the documents. This is similar to the
|
|
10
|
+
# markup processor in that it uses a concept of _engines_ in order to
|
|
11
|
+
# power different kinds of assets.
|
|
12
|
+
#
|
|
13
|
+
# Options:
|
|
14
|
+
#
|
|
15
|
+
# - `:asset_load_paths` - Optional. The load paths for the asset
|
|
16
|
+
# processor. These are used to resolve the asset file names, and are
|
|
17
|
+
# passed directly to the {PathSet} created.
|
|
18
|
+
#
|
|
19
|
+
# Pairs:
|
|
20
|
+
#
|
|
21
|
+
# - `"src"`, `"file"`, `"name"`, or `"link"` - Required. At least one
|
|
22
|
+
# of these options are required. They all perform the same function.
|
|
23
|
+
# This defines the name or path of the asset to add or process.
|
|
24
|
+
# - `"type"` - Required. The type of the asset to process. This defines
|
|
25
|
+
# how the asset is handled.
|
|
26
|
+
#
|
|
27
|
+
# @abstract
|
|
28
|
+
# Implement engines using {.engine} and register the processor using
|
|
29
|
+
# {.register}.
|
|
30
|
+
class Asset < Processor::Base
|
|
31
|
+
include Processor::Command
|
|
32
|
+
include Processor::Block
|
|
33
|
+
include Asset::Paths
|
|
34
|
+
pairs :src, :file, :name, :link, :type
|
|
35
|
+
|
|
36
|
+
# The engines defined for the subclass. This should not be used on the
|
|
37
|
+
# parent class ({Common::Asset}). This returns a key-value pair for
|
|
38
|
+
# the engines. The key is the "name" of the format; this is used for
|
|
39
|
+
# the `"type" pair. The value is a tuple containing two values:
|
|
40
|
+
# the syntax of the block/command options, and a proc that takes no
|
|
41
|
+
# arguments to include the asset.
|
|
42
|
+
#
|
|
43
|
+
# @api private
|
|
44
|
+
# @return [{::String => (::Symbol, ::Proc<void>)}]
|
|
45
|
+
def self.engines
|
|
46
|
+
@_engines ||= {}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Defines an engine for use on the subclass. This should not be used
|
|
50
|
+
# on the parent class ({Common::Asset}). This takes the name of the
|
|
51
|
+
# engine, the syntax for the engine, and the processor to
|
|
52
|
+
# perform the asset processor.
|
|
53
|
+
#
|
|
54
|
+
# If both a third argument and a block are provided, then the block
|
|
55
|
+
# takes precedence.
|
|
56
|
+
#
|
|
57
|
+
# @api private
|
|
58
|
+
# @param name [::String] The name of the engine. This is used for the
|
|
59
|
+
# value of the `"type"` pair.
|
|
60
|
+
# @param syntax [::Symbol] The syntax type for the engine. If it's
|
|
61
|
+
# `:command`, the element should be a command node. If it's
|
|
62
|
+
# `:block`, the element should be a block node. If it's `:all` or
|
|
63
|
+
# `:_`, it can be either.
|
|
64
|
+
# @param symbol [::Symbol, nil] The method to call to add the
|
|
65
|
+
# asset.
|
|
66
|
+
# @return [void]
|
|
67
|
+
def self.engine(name, syntax, symbol = nil, &block)
|
|
68
|
+
block ||= proc { send(symbol) }
|
|
69
|
+
engines[name.to_s] = [syntax, block]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Adds the asset. If the engine cannot be found, it returns
|
|
73
|
+
# `nil`, effectively ignoring the node. This helps enable cross-format
|
|
74
|
+
# support without the use of `if` nodes. This always returns `nil`,
|
|
75
|
+
# because outputting the asset is up to the {Common::Output}
|
|
76
|
+
# processor for the inclusion of the asset.
|
|
77
|
+
#
|
|
78
|
+
# @return [nil]
|
|
79
|
+
def perform
|
|
80
|
+
return unless (engine = find_engine)
|
|
81
|
+
syntax, block = engine
|
|
82
|
+
fail_syntax_error(syntax) if syntax == :block && !@body ||
|
|
83
|
+
syntax == :command && @body
|
|
84
|
+
instance_exec(&block)
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Finds the value for the asset file. This tries four different
|
|
89
|
+
# pairs before giving up: `"src"`, `"file"`, `"name"`, and `"link"`.
|
|
90
|
+
# If none of these could be found, it fails.
|
|
91
|
+
#
|
|
92
|
+
# @return [::String]
|
|
93
|
+
def load_asset_file
|
|
94
|
+
file = @pairs["src"] || @pairs["file"] || @pairs["name"] ||
|
|
95
|
+
@pairs["link"]
|
|
96
|
+
return file if file
|
|
97
|
+
fail PairError.new("Expected one of src, file, name, or link, " \
|
|
98
|
+
"got nothing", @node.location)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def fail_syntax_error(syntax)
|
|
104
|
+
fail ElementSyntaxError.new("Incorrect syntax used for " \
|
|
105
|
+
"#{@pairs['type']} (a #{syntax} type)", @node.location)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def find_engine
|
|
109
|
+
type = @pairs.fetch("type")
|
|
110
|
+
.gsub(/(?<=[\w])([A-Z])/) { |m| "-#{m}" }
|
|
111
|
+
.downcase
|
|
112
|
+
return [] unless self.class.engines.key?(type)
|
|
113
|
+
self.class.engines.fetch(type)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|