brandish 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|