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,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