brandish 0.1.1

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