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.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +41 -0
  5. data/.travis.yml +5 -0
  6. data/.yardopts +1 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +10 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +41 -0
  11. data/Rakefile +9 -0
  12. data/bin/brandish +16 -0
  13. data/brandish.gemspec +39 -0
  14. data/defaults/templates/html.liquid +39 -0
  15. data/lib/brandish.rb +51 -0
  16. data/lib/brandish/application.rb +163 -0
  17. data/lib/brandish/application/bench_command.rb +96 -0
  18. data/lib/brandish/application/build_command.rb +73 -0
  19. data/lib/brandish/application/initialize_command.rb +83 -0
  20. data/lib/brandish/application/serve_command.rb +150 -0
  21. data/lib/brandish/configure.rb +196 -0
  22. data/lib/brandish/configure/dsl.rb +135 -0
  23. data/lib/brandish/configure/dsl/form.rb +136 -0
  24. data/lib/brandish/configure/form.rb +32 -0
  25. data/lib/brandish/errors.rb +65 -0
  26. data/lib/brandish/execute.rb +26 -0
  27. data/lib/brandish/markup.rb +10 -0
  28. data/lib/brandish/markup/redcarpet.rb +14 -0
  29. data/lib/brandish/markup/redcarpet/format.rb +127 -0
  30. data/lib/brandish/markup/redcarpet/html.rb +95 -0
  31. data/lib/brandish/parser.rb +26 -0
  32. data/lib/brandish/parser/main.rb +237 -0
  33. data/lib/brandish/parser/node.rb +89 -0
  34. data/lib/brandish/parser/node/block.rb +98 -0
  35. data/lib/brandish/parser/node/command.rb +102 -0
  36. data/lib/brandish/parser/node/pair.rb +42 -0
  37. data/lib/brandish/parser/node/root.rb +83 -0
  38. data/lib/brandish/parser/node/string.rb +18 -0
  39. data/lib/brandish/parser/node/text.rb +114 -0
  40. data/lib/brandish/path_set.rb +163 -0
  41. data/lib/brandish/processor.rb +47 -0
  42. data/lib/brandish/processor/base.rb +144 -0
  43. data/lib/brandish/processor/block.rb +47 -0
  44. data/lib/brandish/processor/command.rb +47 -0
  45. data/lib/brandish/processor/context.rb +169 -0
  46. data/lib/brandish/processor/descend.rb +32 -0
  47. data/lib/brandish/processor/inline.rb +49 -0
  48. data/lib/brandish/processor/name_filter.rb +67 -0
  49. data/lib/brandish/processor/pair_filter.rb +96 -0
  50. data/lib/brandish/processors.rb +26 -0
  51. data/lib/brandish/processors/all.rb +19 -0
  52. data/lib/brandish/processors/all/comment.rb +29 -0
  53. data/lib/brandish/processors/all/embed.rb +56 -0
  54. data/lib/brandish/processors/all/if.rb +109 -0
  55. data/lib/brandish/processors/all/import.rb +95 -0
  56. data/lib/brandish/processors/all/literal.rb +42 -0
  57. data/lib/brandish/processors/all/verify.rb +47 -0
  58. data/lib/brandish/processors/common.rb +20 -0
  59. data/lib/brandish/processors/common/asset.rb +118 -0
  60. data/lib/brandish/processors/common/asset/paths.rb +93 -0
  61. data/lib/brandish/processors/common/group.rb +67 -0
  62. data/lib/brandish/processors/common/header.rb +86 -0
  63. data/lib/brandish/processors/common/markup.rb +127 -0
  64. data/lib/brandish/processors/common/output.rb +73 -0
  65. data/lib/brandish/processors/html.rb +18 -0
  66. data/lib/brandish/processors/html/group.rb +33 -0
  67. data/lib/brandish/processors/html/header.rb +46 -0
  68. data/lib/brandish/processors/html/markup.rb +131 -0
  69. data/lib/brandish/processors/html/output.rb +62 -0
  70. data/lib/brandish/processors/html/output/document.rb +127 -0
  71. data/lib/brandish/processors/html/script.rb +64 -0
  72. data/lib/brandish/processors/html/script/babel.rb +48 -0
  73. data/lib/brandish/processors/html/script/coffee.rb +47 -0
  74. data/lib/brandish/processors/html/script/vanilla.rb +45 -0
  75. data/lib/brandish/processors/html/style.rb +82 -0
  76. data/lib/brandish/processors/html/style/highlight.rb +89 -0
  77. data/lib/brandish/processors/html/style/sass.rb +64 -0
  78. data/lib/brandish/processors/html/style/vanilla.rb +71 -0
  79. data/lib/brandish/processors/latex.rb +15 -0
  80. data/lib/brandish/processors/latex/markup.rb +47 -0
  81. data/lib/brandish/scanner.rb +64 -0
  82. data/lib/brandish/version.rb +9 -0
  83. data/templates/initialize/Gemfile.tt +14 -0
  84. data/templates/initialize/brandish.config.rb.tt +49 -0
  85. 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 (`&#00;`).
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