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,95 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "hanami/helpers/html_helper"
|
5
|
+
require "hanami/helpers/escape_helper"
|
6
|
+
|
7
|
+
module Brandish
|
8
|
+
module Markup
|
9
|
+
module Redcarpet
|
10
|
+
# An HTML renderer for redcarpet. This provides integrations with
|
11
|
+
# Brandish, as well as code highlighting.
|
12
|
+
class HTML < ::Redcarpet::Render::SmartyHTML
|
13
|
+
include Hanami::Helpers::HtmlHelper
|
14
|
+
include Hanami::Helpers::EscapeHelper
|
15
|
+
|
16
|
+
# The tags for each level of header.
|
17
|
+
#
|
18
|
+
# @return [<::Symbol>]
|
19
|
+
TAGS = %i(h1 h2 h3 h4 h5 h6).freeze
|
20
|
+
|
21
|
+
# Initialize the renderer with the given context and options.
|
22
|
+
#
|
23
|
+
# @param context [Processor::Context]
|
24
|
+
# @param options [{::Symbol => ::Object}]
|
25
|
+
# @option options [::Symbol] :highlight (:none) Which highlighter to
|
26
|
+
# use. Possible values are `:rouge`, `:coderay`, `:pygments`, and
|
27
|
+
# `:none`.
|
28
|
+
def initialize(context, options)
|
29
|
+
@context = context
|
30
|
+
@highlighter = options.fetch(:highlight)
|
31
|
+
super(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Creates a header with the given text and level. If the text ends in
|
35
|
+
# `/#([\w-]+)/`, that is removed, and used as the ID for the header;
|
36
|
+
# otherwise, it is automagically assumed from the text.
|
37
|
+
#
|
38
|
+
# @param text [::String]
|
39
|
+
# @param level [::Numeric]
|
40
|
+
# @return [::String]
|
41
|
+
def header(text, level)
|
42
|
+
text, id = split_text(text)
|
43
|
+
|
44
|
+
html.tag(TAGS.fetch(level), raw(text), id: id).to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
# Highlights a block of code.
|
48
|
+
#
|
49
|
+
# @param code [::String] The code to highlight
|
50
|
+
# @param language [::String, nil] The language that the code was in,
|
51
|
+
# or nil if it was not or could not be provided.
|
52
|
+
# @return [::String]
|
53
|
+
def block_code(code, language)
|
54
|
+
basic_language = language || "unknown"
|
55
|
+
case @highlighter
|
56
|
+
when :rouge then rouge_highlight_code(code, language)
|
57
|
+
when :coderay then coderay_highlight_code(code, language)
|
58
|
+
when :pygments then pygments_highlight_code(code, language)
|
59
|
+
when :none
|
60
|
+
html.pre { html.code(code, class: "language-#{basic_language}") }
|
61
|
+
end.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def split_text(text)
|
67
|
+
# So we don't match entity tags by accident (`�`).
|
68
|
+
if (match = text.match(/\A(.*)(?<!&)#([\w-]+)\s*\z/))
|
69
|
+
[match[1], match[2]]
|
70
|
+
else
|
71
|
+
# Remove entity tags, they're not needed.
|
72
|
+
[text, text.downcase.gsub(/&(.+?);/, "").gsub(/\W+/, "-")]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def rouge_highlight_code(code, language)
|
77
|
+
lexer = (language && Rouge::Lexer.find_fancy(language, code)) ||
|
78
|
+
Rouge::Lexers::PlainText
|
79
|
+
attributes = { class: "highlight language-#{lexer.tag}" }
|
80
|
+
html.pre do
|
81
|
+
code(raw(::Rouge.highlight(code, lexer, "html")), attributes)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def coderay_highlight_code(code, langauge)
|
86
|
+
CodeRay.scan(code, langauge.intern, css: :style).div
|
87
|
+
end
|
88
|
+
|
89
|
+
def pygments_highlight_code(code, language)
|
90
|
+
Pygments.highlight(code, lexer: language.intern)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "yoga"
|
5
|
+
require "brandish/parser/main"
|
6
|
+
require "brandish/parser/node"
|
7
|
+
|
8
|
+
module Brandish
|
9
|
+
# Parses the document into a tree of nodes. This is strictly an LL(0)
|
10
|
+
# recursive descent parser - even though the term `peek` is used, this is
|
11
|
+
# technically a shifted token that is acted upon.
|
12
|
+
#
|
13
|
+
# This constructs a tree that can then be walked or modified to construct
|
14
|
+
# an end product.
|
15
|
+
class Parser
|
16
|
+
include Yoga::Parser
|
17
|
+
include Parser::Main
|
18
|
+
|
19
|
+
def initialize(*)
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
undef_method :peek_out
|
24
|
+
undef_method :peek_out?
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Brandish
|
5
|
+
class Parser
|
6
|
+
# The main parser for the document. This constructs a tree
|
7
|
+
# of nodes that can be used to represent the original
|
8
|
+
# document.
|
9
|
+
module Main
|
10
|
+
# Parses the overall document. This parses a sequence of
|
11
|
+
# elements until it reaches the `EOF` token.
|
12
|
+
#
|
13
|
+
# @return [Parser::Node]
|
14
|
+
def parse_document
|
15
|
+
body = collect(EOF_SYMBOL) { parse_document_element }
|
16
|
+
expect(EOF_SYMBOL)
|
17
|
+
Node::Root.new(children: body)
|
18
|
+
end
|
19
|
+
|
20
|
+
alias_method :parse_root, :parse_document
|
21
|
+
|
22
|
+
# Parses a single element in the docuemnt. If the next token is a
|
23
|
+
# less-than sign, it calls {#parse_document_meta}; otherwise, it calls
|
24
|
+
# {#parse_document_text}.
|
25
|
+
#
|
26
|
+
# @return [Node] The node for the element.
|
27
|
+
def parse_document_element
|
28
|
+
peek?(LESS_THAN_SYMBOL) ? parse_document_meta : parse_document_text
|
29
|
+
end
|
30
|
+
|
31
|
+
# parses a "meta" element from the document. In this sense, it is
|
32
|
+
# anything that doesn't correspond 1-to-1 to the destination document.
|
33
|
+
# If the next token after the less-than sign is an at-sign, it calls
|
34
|
+
# {#parse_document_command}; otherwise, it calls {#parse_document_block}.
|
35
|
+
#
|
36
|
+
# @return [Node]
|
37
|
+
def parse_document_meta(start = expect(LESS_THAN_SYMBOL))
|
38
|
+
name = expect(TEXT_SYMBOL)
|
39
|
+
parse_skip_space
|
40
|
+
arguments = collect(SLASH_OR_GREATER_THAN_SYMBOL) { parse_document_command_argument }
|
41
|
+
if peek?(SLASH_SYMBOL)
|
42
|
+
parse_document_command(start, name, arguments)
|
43
|
+
else
|
44
|
+
parse_document_block(start, name, arguments)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Parses a document command. This is essentially a message to the
|
49
|
+
# compiler about the document, to alter how the document is processed.
|
50
|
+
#
|
51
|
+
# command ::= '<' TEXT *command-argument '/' '>'
|
52
|
+
#
|
53
|
+
# @param start [Scanner::Token] The starting element for the command.
|
54
|
+
# @param name [Scanner::Token] The name of the command.
|
55
|
+
# @param arguments [<Node::Pair>] The arguments to the command.
|
56
|
+
# @return [Node::Command] The command node.
|
57
|
+
def parse_document_command(start, name, arguments)
|
58
|
+
expect(SLASH_SYMBOL)
|
59
|
+
stop = expect(GREATER_THAN_SYMBOL)
|
60
|
+
|
61
|
+
Node::Command.new(name: name, arguments: arguments,
|
62
|
+
location: start.location.union(stop.location))
|
63
|
+
end
|
64
|
+
|
65
|
+
# Parses a document command argument. This is an argument to a command.
|
66
|
+
# All arguments are key-value pairs.
|
67
|
+
#
|
68
|
+
# command-argument ::= *space command-argument-key *space '=' *space command-argument-value *space
|
69
|
+
#
|
70
|
+
# @return [Node::Pair] The command argument.
|
71
|
+
def parse_document_command_argument
|
72
|
+
parse_skip_space
|
73
|
+
key = parse_document_command_argument_key
|
74
|
+
parse_skip_space
|
75
|
+
expect(EQUAL_SYMBOL)
|
76
|
+
parse_skip_space
|
77
|
+
value = parse_document_command_argument_value
|
78
|
+
parse_skip_space
|
79
|
+
|
80
|
+
Node::Pair.new(key: key, value: value)
|
81
|
+
end
|
82
|
+
|
83
|
+
# The key for the command argument.
|
84
|
+
#
|
85
|
+
# command-argument-key ::= TEXT
|
86
|
+
#
|
87
|
+
# @return [Scanner::Token]
|
88
|
+
def parse_document_command_argument_key
|
89
|
+
expect(TEXT_SYMBOL)
|
90
|
+
end
|
91
|
+
|
92
|
+
# The value for the command argument. This can be either a string, or
|
93
|
+
# a {Node::Text} with a single token. If it's a string, it's parsed with
|
94
|
+
# {#parse_document_string}; otherwise, it grabs one token that's valid
|
95
|
+
# for a {Node::Text} node.
|
96
|
+
#
|
97
|
+
# command-argument-value ::= string / TEXT
|
98
|
+
#
|
99
|
+
# @return [Node]
|
100
|
+
def parse_document_command_argument_value
|
101
|
+
if peek?(QUOTE_SYMBOL)
|
102
|
+
parse_document_string
|
103
|
+
else
|
104
|
+
Node::Text.new(tokens: [expect(Node::Text::TOKENS)])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Parses a "block." This is similar to a HTML tag in the sense that it
|
109
|
+
# has a name and a body; however, blocks do not have any sort of
|
110
|
+
# arguments to them.
|
111
|
+
#
|
112
|
+
# block ::= '<' TEXT *command-argument '>' *element '</' TEXT '>'
|
113
|
+
#
|
114
|
+
# @param start [Scanner::Token] The starting element for the block.
|
115
|
+
# @param name [Scanner::Token] The name of the block.
|
116
|
+
# @param arguments [<Node::Pair>] The arguments to the block.
|
117
|
+
# @return [Node::Block]
|
118
|
+
def parse_document_block(start, name, arguments)
|
119
|
+
expect(GREATER_THAN_SYMBOL)
|
120
|
+
body = parse_document_block_body
|
121
|
+
expect(SLASH_SYMBOL)
|
122
|
+
match = expect(TEXT_SYMBOL)
|
123
|
+
stop = expect(GREATER_THAN_SYMBOL)
|
124
|
+
|
125
|
+
unless name.value == match.value
|
126
|
+
fail ParseError.new("Unexpected #{match.value.inspect}, expected" \
|
127
|
+
" #{name.value.inspect}", match.location)
|
128
|
+
end
|
129
|
+
|
130
|
+
Node::Block.new(name: name, body: body, arguments: arguments,
|
131
|
+
location: start.location.union(body.location, match.location,
|
132
|
+
stop.location, *arguments.map(&:location)))
|
133
|
+
end
|
134
|
+
|
135
|
+
# Parses the body of a block tag. This keeps attempting to parse text
|
136
|
+
# and meta tags until it encounters the phrase `</`, at which point it
|
137
|
+
# will stop parsing and return to the parent.
|
138
|
+
#
|
139
|
+
# @return [Node::Root]
|
140
|
+
def parse_document_block_body
|
141
|
+
children = []
|
142
|
+
loop do
|
143
|
+
if peek?(LESS_THAN_SYMBOL)
|
144
|
+
start = expect(LESS_THAN_SYMBOL)
|
145
|
+
break if peek?(SLASH_SYMBOL)
|
146
|
+
children << parse_document_meta(start)
|
147
|
+
else
|
148
|
+
children << parse_document_text
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
Node::Root.new(children: children)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Parses a "string." This is a series of text encapulated by quotes.
|
156
|
+
# Strings can contain more characters than just regular text, but right
|
157
|
+
# now, strings are only used for command argument values.
|
158
|
+
#
|
159
|
+
# string ::= '"' *(TEXT / SPACE / LINE / NUMERIC / ESCAPE / '<' / '>' / '=') '"'
|
160
|
+
#
|
161
|
+
# @return [Node::String]
|
162
|
+
def parse_document_string
|
163
|
+
start = expect(QUOTE_SYMBOL)
|
164
|
+
children = collect(QUOTE_SYMBOL) { expect(Node::String::TOKENS) }
|
165
|
+
stop = expect(QUOTE_SYMBOL)
|
166
|
+
location = start.location.union(stop.location)
|
167
|
+
|
168
|
+
Node::String.new(tokens: children, location: location)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Parses a document for text. This is just regular text tokens. For a
|
172
|
+
# list of tokens that are allowed, see {Node::Text::TOKENS}.
|
173
|
+
#
|
174
|
+
# text ::= *(TEXT / SPACE / LINE / NUMERIC / ESCAPE / '/' / '"' / '=')
|
175
|
+
#
|
176
|
+
# @return [Node::Text]
|
177
|
+
def parse_document_text
|
178
|
+
children = [expect(Node::Text::TOKENS)]
|
179
|
+
children << expect(Node::Text::TOKENS) while peek?(Node::Text::TOKENS)
|
180
|
+
Node::Text.new(tokens: children)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Skips over nodes as long as they're space nodes.
|
184
|
+
#
|
185
|
+
# @return [void]
|
186
|
+
def parse_skip_space
|
187
|
+
expect(SPACE_SYMBOLS) while peek?(SPACE_SYMBOLS)
|
188
|
+
end
|
189
|
+
|
190
|
+
# A set containing the kind symbol for a quote.
|
191
|
+
#
|
192
|
+
# @return [::Set<::Symbol>]
|
193
|
+
QUOTE_SYMBOL = ::Set[:'"']
|
194
|
+
|
195
|
+
# A set containing the kind symbol for an equal sign.
|
196
|
+
#
|
197
|
+
# @return [::Set<::Symbol>]
|
198
|
+
EQUAL_SYMBOL = ::Set[:'=']
|
199
|
+
|
200
|
+
# A set containing the kind symbol for a less than symbol.
|
201
|
+
#
|
202
|
+
# @return [::Set<::Symbol>]
|
203
|
+
LESS_THAN_SYMBOL = ::Set[:<]
|
204
|
+
|
205
|
+
# A set containing the kind symbol for a greater than symbol.
|
206
|
+
#
|
207
|
+
# @return [::Set<::Symbol>]
|
208
|
+
GREATER_THAN_SYMBOL = ::Set[:>]
|
209
|
+
|
210
|
+
# A set containing the kind symbol for a forward slash symbol.
|
211
|
+
#
|
212
|
+
# @return [::Set<::Symbol>]
|
213
|
+
SLASH_SYMBOL = ::Set[:/]
|
214
|
+
|
215
|
+
# A set containing the kind symbols for a forward slash or a greater than
|
216
|
+
# symbol.
|
217
|
+
#
|
218
|
+
# @return [::Set<::Symbol>]
|
219
|
+
SLASH_OR_GREATER_THAN_SYMBOL = SLASH_SYMBOL | GREATER_THAN_SYMBOL
|
220
|
+
|
221
|
+
# A set containing the kind symbols for a text symbol.
|
222
|
+
#
|
223
|
+
# @return [::Set<::Symbol>]
|
224
|
+
TEXT_SYMBOL = ::Set[:TEXT]
|
225
|
+
|
226
|
+
# A set containing the kind symbols for a space symbol.
|
227
|
+
#
|
228
|
+
# @return [::Set<::Symbol>]
|
229
|
+
SPACE_SYMBOLS = ::Set[:SPACE, :LINE]
|
230
|
+
|
231
|
+
# A set containing the kind symbols for a eof symbol.
|
232
|
+
#
|
233
|
+
# @return [::Set<::Symbol>]
|
234
|
+
EOF_SYMBOL = ::Set[:EOF]
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "brandish/parser/node/block"
|
5
|
+
require "brandish/parser/node/command"
|
6
|
+
require "brandish/parser/node/pair"
|
7
|
+
require "brandish/parser/node/root"
|
8
|
+
require "brandish/parser/node/text"
|
9
|
+
require "brandish/parser/node/string"
|
10
|
+
|
11
|
+
module Brandish
|
12
|
+
class Parser
|
13
|
+
# A parser node. This is the base for all parser nodes.b All parser
|
14
|
+
# nodes are frozen, and so cannot be mutated; all mutations need to be
|
15
|
+
# set up as a new node, that is then returned.
|
16
|
+
class Node
|
17
|
+
# The location of the node.
|
18
|
+
#
|
19
|
+
# @return [Location]
|
20
|
+
attr_reader :location
|
21
|
+
|
22
|
+
# Initialize the node with the given location. This should be
|
23
|
+
# overwritten by a decending node, but all decending nodes should
|
24
|
+
# include a location.
|
25
|
+
#
|
26
|
+
# @param location [Location] The location. See
|
27
|
+
# {#location}.
|
28
|
+
def initialize(location:)
|
29
|
+
@location = location
|
30
|
+
end
|
31
|
+
|
32
|
+
# Pretty inspect.
|
33
|
+
#
|
34
|
+
# @return [::String]
|
35
|
+
def inspect
|
36
|
+
"#<#{self.class} location=#{@location}>"
|
37
|
+
end
|
38
|
+
|
39
|
+
# "Updates" the node with the given attributes. All of the key-value
|
40
|
+
# pairs in the attributes are updated on the node by sending an update
|
41
|
+
# to the node.
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# node.update(some: true, value: true)
|
45
|
+
# # this sends `update_some` and `update_value`, each with `true` as
|
46
|
+
# # the argument. These functions return a node similar to the
|
47
|
+
# # original, but with the requested change. This is functionally
|
48
|
+
# # similar to this (with some minor differences):
|
49
|
+
# node.update_some(true).update_value(true)
|
50
|
+
# @raise [NodeError] if an attribute key is passed that isn't updatable
|
51
|
+
# for the node.
|
52
|
+
# @param attributes [{::Symbol, ::String => ::Object}] The attributes to
|
53
|
+
# update, and the new values for them.
|
54
|
+
# @return [Node] The updated node, or `self` if no attributes are given.
|
55
|
+
def update(attributes)
|
56
|
+
attributes.inject(self) { |a, (n, v)| a.update_attribute(n, v) }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Prevents all calls to {#update}. This is used on nodes that should
|
60
|
+
# never be updated. This is a stop-gap measure for incorrectly
|
61
|
+
# configured projects. This, in line with all other methods, creates
|
62
|
+
# a duplicate node.
|
63
|
+
#
|
64
|
+
# @return [Node]
|
65
|
+
def prevent_update
|
66
|
+
node = dup
|
67
|
+
node.singleton_class.send(:undef_method, :update)
|
68
|
+
node
|
69
|
+
end
|
70
|
+
|
71
|
+
def update_prevented?
|
72
|
+
!respond_to?(:update)
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
# Updates an attribute on the node. This is used for {#update}.
|
78
|
+
#
|
79
|
+
# @param name [::Symbol, ::String] The name of the attribute.
|
80
|
+
# @param value [::Object] The value of the attribute.
|
81
|
+
def update_attribute(name, value)
|
82
|
+
return send(:"update_#{name}", value) if respond_to?(:"update_#{name}", true)
|
83
|
+
|
84
|
+
fail NodeError.new("Unable to update attribute #{name} on #{self}",
|
85
|
+
@location)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|