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,93 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Brandish
|
5
|
+
module Processors
|
6
|
+
module Common
|
7
|
+
class Asset < Processor::Base
|
8
|
+
# Common path operations used for asset processors. This assumes
|
9
|
+
# a css-like dependency structure similar to HTML's. These are not
|
10
|
+
# required to be used.
|
11
|
+
module Paths
|
12
|
+
# The asset path for this kind of asset.
|
13
|
+
#
|
14
|
+
# @abstract
|
15
|
+
# @return [::String]
|
16
|
+
def asset_kind_path
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# The extension for this kind of asset. This defaults to no
|
21
|
+
# extension.
|
22
|
+
#
|
23
|
+
# @abstract
|
24
|
+
# @return [::String]
|
25
|
+
def asset_kind_extension
|
26
|
+
""
|
27
|
+
end
|
28
|
+
|
29
|
+
# The load paths for this asset type. This defaults to a pathset
|
30
|
+
# containing all of the sources, all of the sources' appended with
|
31
|
+
# {#asset_kind_path}, and all of the paths given by the
|
32
|
+
# `:asset_load_paths` option value.
|
33
|
+
def asset_load_paths
|
34
|
+
return @asset_load_paths if @asset_load_paths
|
35
|
+
|
36
|
+
paths = PathSet.new
|
37
|
+
@context.configure.sources
|
38
|
+
.each { |p| paths << p }
|
39
|
+
.each { |p| paths << p / asset_kind_path }
|
40
|
+
@options.fetch(:asset_load_paths, []).each { |p| paths << p }
|
41
|
+
@asset_load_paths = paths
|
42
|
+
end
|
43
|
+
|
44
|
+
# The default output assets path.
|
45
|
+
#
|
46
|
+
# @return [::String]
|
47
|
+
def output_assets_path
|
48
|
+
@context.configure.output / asset_kind_path
|
49
|
+
end
|
50
|
+
|
51
|
+
# Converts the given uri into a pathname, using the host, the path,
|
52
|
+
# the query, and the fragment all as directories. This turns the
|
53
|
+
# uri `https://example.com/some-path?waffles#test` into the path
|
54
|
+
# `example.com/some-path/waffles/test.<asset_kind_extension>`.
|
55
|
+
#
|
56
|
+
# @param uri [::URI] The uri to convert into a path.
|
57
|
+
# @return [::Pathname]
|
58
|
+
def uri_path(uri)
|
59
|
+
base = [uri.host,
|
60
|
+
(uri.path unless uri.path.empty?),
|
61
|
+
(uri.query if uri.query),
|
62
|
+
(uri.fragment if uri.fragment)].compact
|
63
|
+
::Pathname.new(::File.join(*base)).sub_ext(asset_kind_extension)
|
64
|
+
end
|
65
|
+
|
66
|
+
# The file paths. This returns a hash with three elements:
|
67
|
+
# `:file`, which is the given file name from the element;
|
68
|
+
# `:out`, which is the full output directory for outputting the
|
69
|
+
# asset file; and `:src`, which is similar to what the HTML src
|
70
|
+
# or href value would be for the asset.
|
71
|
+
#
|
72
|
+
# @return [{::Symbol => ::Pathname}]
|
73
|
+
def load_file_paths
|
74
|
+
file = load_asset_file
|
75
|
+
# The full path to the file itself.
|
76
|
+
path = asset_load_paths.find(file, @options)
|
77
|
+
output_path = @context.configure.output
|
78
|
+
# The relative path from the source asset directory.
|
79
|
+
asset_path = asset_load_paths.resolve(file)
|
80
|
+
# The "raw" output path.
|
81
|
+
raw_output_path =
|
82
|
+
@pairs.fetch("output") { asset_path.sub_ext(asset_kind_extension) }
|
83
|
+
# The actual output path of the file.
|
84
|
+
file_output_path = output_assets_path / raw_output_path
|
85
|
+
src_output_path = file_output_path.relative_path_from(output_path)
|
86
|
+
|
87
|
+
{ file: path, out: file_output_path, src: src_output_path }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "securerandom"
|
5
|
+
|
6
|
+
module Brandish
|
7
|
+
module Processors
|
8
|
+
module Common
|
9
|
+
# A "group." This is for grouping together elements for styling
|
10
|
+
# or logical purposes. By default, this creates a block element.
|
11
|
+
#
|
12
|
+
# This takes no options.
|
13
|
+
#
|
14
|
+
# Pairs:
|
15
|
+
#
|
16
|
+
# - `"class"` - Optional. See {#class_value}.
|
17
|
+
# - `"id"` - Optional. See {#id_value}.
|
18
|
+
# - `"name"` - Optional. See {#name_value}.
|
19
|
+
#
|
20
|
+
# @abstract
|
21
|
+
# Implement {#perform} and register the processor with {.register}.
|
22
|
+
class Group < Processor::Base
|
23
|
+
include Processor::Block
|
24
|
+
pairs :class, :id, :name
|
25
|
+
|
26
|
+
# The class value of the group. This can have a 1-to-1 correspondence
|
27
|
+
# to the destination source. This works similarly to HTML's class
|
28
|
+
# attribute. This uses the `"class"` pair.
|
29
|
+
#
|
30
|
+
# @return [::String]
|
31
|
+
def class_value
|
32
|
+
@pairs.fetch("class", "")
|
33
|
+
end
|
34
|
+
|
35
|
+
# The ID value of the group. This can have a 1-to-1 correspondence
|
36
|
+
# to the destination source. This works similarly to HTML's id
|
37
|
+
# attribute; especially the concept that it should be unique. This
|
38
|
+
# uses the `"id"` pair.
|
39
|
+
#
|
40
|
+
# @return [::String, nil]
|
41
|
+
def id_value
|
42
|
+
@pairs["id"]
|
43
|
+
end
|
44
|
+
|
45
|
+
# The name value of the group. This doesn't have a 1-to-1
|
46
|
+
# correspondence to the destination source. This is used to provide
|
47
|
+
# an internal styling or grouping process. This uses the `"name"`
|
48
|
+
# pair.
|
49
|
+
#
|
50
|
+
# @return [::String, nil]
|
51
|
+
def name_value
|
52
|
+
@pairs["name"]
|
53
|
+
end
|
54
|
+
|
55
|
+
# The body, accepted and flattened. This essentially converts the
|
56
|
+
# contents into a string that can be used as the value for the group.
|
57
|
+
#
|
58
|
+
# @see #accept
|
59
|
+
# @see Parser::Node::Root#flatten
|
60
|
+
# @return [::String]
|
61
|
+
def accepted_body
|
62
|
+
accept(@body).flatten
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Brandish
|
5
|
+
module Processors
|
6
|
+
module Common
|
7
|
+
# A processor that defines the `header` command. This creates a list
|
8
|
+
# internally of all of the headers in the document, to later be used to
|
9
|
+
# possibly create a table of contents, if needed.
|
10
|
+
#
|
11
|
+
# This takes no options.
|
12
|
+
#
|
13
|
+
# Pairs:
|
14
|
+
#
|
15
|
+
# - `"level"` - Optional. Defaults to `1`. The "level" of the header.
|
16
|
+
# This should be a value between 1 and 6.
|
17
|
+
# - `"value"` - Required. The name of the header.
|
18
|
+
# - `"id"` - Optional. The ID of the header. This is a unique value
|
19
|
+
# to reference to this header. If no value is given, it defaults
|
20
|
+
# to a modified `"value"`.
|
21
|
+
#
|
22
|
+
# @abstract
|
23
|
+
# Implement {#header_render}, and register the processor with
|
24
|
+
# {.register}.
|
25
|
+
class Header < Processor::Base
|
26
|
+
include Processor::Command
|
27
|
+
pairs :level, :value, :id
|
28
|
+
|
29
|
+
# (see Processor::Base#setup)
|
30
|
+
def setup
|
31
|
+
super
|
32
|
+
@context[:headers] = []
|
33
|
+
end
|
34
|
+
|
35
|
+
# Handles the header. This stores the {#header_data} in the context
|
36
|
+
# `:headers` key, and then calls {#header_render}.
|
37
|
+
#
|
38
|
+
# @return [Parser::Node::Text] The resulting header text.
|
39
|
+
def perform
|
40
|
+
@context[:headers] << header_data
|
41
|
+
header_render
|
42
|
+
end
|
43
|
+
|
44
|
+
# Renders the header for the format. This should be implemented by the
|
45
|
+
# implementing format.
|
46
|
+
#
|
47
|
+
# @abstract
|
48
|
+
# @return [Parser::Node::Text] The resulting header text.
|
49
|
+
def header_render
|
50
|
+
fail ProcessorNotImplementedError,
|
51
|
+
"Please implement #{self.class}#header_render"
|
52
|
+
end
|
53
|
+
|
54
|
+
# The header data used for the internal `:headers` structure.
|
55
|
+
#
|
56
|
+
# @return [{::Symbol => ::Object}]
|
57
|
+
def header_data
|
58
|
+
{ level: header_level, value: header_value, id: header_id }
|
59
|
+
end
|
60
|
+
|
61
|
+
# The header level. This is an integer between 1 and 6.
|
62
|
+
#
|
63
|
+
# @return [Numeric]
|
64
|
+
def header_level
|
65
|
+
@pairs.fetch("level", "1").to_i
|
66
|
+
end
|
67
|
+
|
68
|
+
# The "value" of the header. This is the contents or the name of the
|
69
|
+
# header. This is required.
|
70
|
+
#
|
71
|
+
# @return [::String]
|
72
|
+
def header_value
|
73
|
+
@pairs.fetch("value")
|
74
|
+
end
|
75
|
+
|
76
|
+
# The "id" of the header. This should be a unique value between all
|
77
|
+
# headers that specifies this exact header.
|
78
|
+
#
|
79
|
+
# @return [::String]
|
80
|
+
def header_id
|
81
|
+
@pairs.fetch("id") { header_value.downcase.gsub(/[^\w]|_/, "-") }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Brandish
|
5
|
+
module Processors
|
6
|
+
module Common
|
7
|
+
# Processes the text in the document with the given markup. Markup
|
8
|
+
# support varies from format to format, from a lot, to none. All
|
9
|
+
# supported markups are not included as a dependency on the Brandish
|
10
|
+
# gem; they must be used as a dependency for the using library or
|
11
|
+
# application.
|
12
|
+
#
|
13
|
+
# *All* formats *must* provide an `:escape` engine, that escapes the
|
14
|
+
# source for the target format.
|
15
|
+
#
|
16
|
+
# Options:
|
17
|
+
#
|
18
|
+
# - `:engine` - Required. This is the markup engine to use. The allowed
|
19
|
+
# values for this option is dependant on the format.
|
20
|
+
# - `:options` - Optional. This is the
|
21
|
+
# options for the markup engine. The actual type is dependant on the
|
22
|
+
# markup engine. The default values are dependant on the markup engine.
|
23
|
+
#
|
24
|
+
# @abstract
|
25
|
+
# Implement engines using {.engine}, and register the processor using
|
26
|
+
# {.register}.
|
27
|
+
class Markup < Processor::Base
|
28
|
+
# The engines defined for the subclass. This should not be used on the
|
29
|
+
# parent class ({Common::Markup}). This returns a key-value pair for
|
30
|
+
# the engines. The key is the "name" of the format; this is used for
|
31
|
+
# the `:engine` option. The value is a tuple containing two values:
|
32
|
+
# the default options, and a proc that takes two arguments to markup
|
33
|
+
# the text.
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
# @return [{::Symbol => (::Object, ::Proc<::String, ::Object, ::String>)}]
|
37
|
+
def self.engines
|
38
|
+
@_engines ||= {}
|
39
|
+
end
|
40
|
+
|
41
|
+
# Defines an engine for use on the subclass. This should not be used
|
42
|
+
# on the parent class ({Common::Markup}). This takes the name of the
|
43
|
+
# engine, the default options for the engine, and the processor to
|
44
|
+
# perform the markup processor.
|
45
|
+
#
|
46
|
+
# If both a third argument and a block are provided, then the block
|
47
|
+
# takes precedence.
|
48
|
+
#
|
49
|
+
# @api private
|
50
|
+
# @param name [::Symbol] The name of the engine. This isn't the actual
|
51
|
+
# name of the markup; this is the name of the engine that processes
|
52
|
+
# the markup.
|
53
|
+
# @param default [::Object] The default options for the engine.
|
54
|
+
# @param initializer [::Symbol, ::Proc, nil] The initializer for the
|
55
|
+
# engine. If this is `nil`, no initializer is called. If this is
|
56
|
+
# a Symbol, the method is called for initialization. If this is a
|
57
|
+
# Proc, it is called for initialization.
|
58
|
+
# @param processor [::Symbol, ::Proc] The processor for the
|
59
|
+
# engine. If this is a Symbol, the method is called for
|
60
|
+
# processing. If this is a Proc, it is called for processing.
|
61
|
+
# @return [void]
|
62
|
+
def self.engine(name, default, initializer, processor)
|
63
|
+
engines[name] = [default.freeze, initializer, processor]
|
64
|
+
end
|
65
|
+
|
66
|
+
# (see Processor::Base#setup)
|
67
|
+
def setup
|
68
|
+
super
|
69
|
+
initialize_engine
|
70
|
+
end
|
71
|
+
|
72
|
+
# (see Processor::Base#process_text)
|
73
|
+
def process_text(node)
|
74
|
+
node.update(value: markup(node.value))
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def initialize_engine
|
80
|
+
@engine = find_engine
|
81
|
+
|
82
|
+
case @engine[1]
|
83
|
+
when ::Symbol then send(@engine[1])
|
84
|
+
when ::Proc then @engine[1].call
|
85
|
+
when nil then return
|
86
|
+
else
|
87
|
+
fail ProcessorError, "Unknown initializer `#{@engine[1].inspect}'"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def find_engine
|
92
|
+
engine = @options.fetch(:engine)
|
93
|
+
|
94
|
+
case engine
|
95
|
+
when ::Symbol
|
96
|
+
self.class.engines.fetch(engine)
|
97
|
+
when ::Proc
|
98
|
+
[{}, nil, engine]
|
99
|
+
else
|
100
|
+
fail ProcessorError, "Unknown engine `#{engine.inspect}'"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def engine_options
|
105
|
+
@_engine_options ||= _build_engine_options
|
106
|
+
end
|
107
|
+
|
108
|
+
def markup(value)
|
109
|
+
case @engine[2]
|
110
|
+
when ::Symbol then send(@engine[2], value, engine_options)
|
111
|
+
when ::Proc then @engine[2].call(value, engine_options)
|
112
|
+
else
|
113
|
+
fail ProcessorError, "Unknown processor `#{@engine[2].inspect}'"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def _build_engine_options
|
118
|
+
if @engine[0].is_a?(::Hash)
|
119
|
+
@engine[0].merge(@options.fetch(:options, {}))
|
120
|
+
else
|
121
|
+
@options.fetch(:options) { @engine[0].dup }
|
122
|
+
end.freeze
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "liquid"
|
5
|
+
|
6
|
+
module Brandish
|
7
|
+
module Processors
|
8
|
+
module Common
|
9
|
+
# Outputs the result of processing the document. Without this processor,
|
10
|
+
# the document is not output, and most other processors have no effect.
|
11
|
+
#
|
12
|
+
# Options:
|
13
|
+
#
|
14
|
+
# - `:template` - Optional. The name of the template to use. This
|
15
|
+
# defaults to the format used.
|
16
|
+
#
|
17
|
+
# @abstract
|
18
|
+
# Please implement {#find_path} and {#template_data}, and register
|
19
|
+
# the processor with {.register}.
|
20
|
+
class Output < Processor::Base
|
21
|
+
# Sets up the path and template for the output processor. It first
|
22
|
+
# attempts to find the output path, creating the directory to that
|
23
|
+
# path if needed. Then, it builds up the template that will later then
|
24
|
+
# be used to render the data.
|
25
|
+
#
|
26
|
+
# @see Processor::Base#setup
|
27
|
+
# @return [void]
|
28
|
+
def setup
|
29
|
+
super
|
30
|
+
|
31
|
+
@path = find_path.tap { |p| p.dirname.mkpath }
|
32
|
+
template_option = @options.fetch(:template, @context.form.format.to_s)
|
33
|
+
template_path = ::Pathname.new(template_option).sub_ext(".liquid")
|
34
|
+
template_full = @context.configure.templates.find(template_path)
|
35
|
+
@template = ::Liquid::Template.parse(template_full.read)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Postprocess the result of processing. The given root should only
|
39
|
+
# have text children, and should respond successfully to `#flatten`.
|
40
|
+
# This will render the template, and write it out to the value given
|
41
|
+
# in `@path`.
|
42
|
+
#
|
43
|
+
# @see Processor::Base#postprocess
|
44
|
+
# @param root [Parser::Node::Root]
|
45
|
+
# @return [void]
|
46
|
+
def postprocess(root)
|
47
|
+
@root = root
|
48
|
+
value = @template.render!(template_data, strict_variables: true)
|
49
|
+
@path.open("wb") { |f| f.write(value) }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Finds the output path.
|
53
|
+
#
|
54
|
+
# @abstract
|
55
|
+
# @return [::Pathname]
|
56
|
+
def find_path
|
57
|
+
fail ProcessorNotImplementedError,
|
58
|
+
"Please implement #{self.class}#find_path"
|
59
|
+
end
|
60
|
+
|
61
|
+
# A hash of data to pass to the template for rendering. The keys
|
62
|
+
# should always be strings.
|
63
|
+
#
|
64
|
+
# @abstract
|
65
|
+
# @return [{::String => ::Object}]
|
66
|
+
def template_data
|
67
|
+
fail ProcessorNotImplementedError,
|
68
|
+
"Please implement #{self.class}#template_data"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|