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,135 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require "pathname"
5
+ require "brandish/configure/dsl/form"
6
+
7
+ module Brandish
8
+ class Configure
9
+ # The DSL for configuration files for Brandish. This is used to construct
10
+ # a configure object.
11
+ class DSL
12
+ # Creates a new DSL object with the given configuration object, and
13
+ # yields it.
14
+ #
15
+ # @param configure [Configure] The configuration instance.
16
+ # @return [DSL]
17
+ def self.call(configure = Configure.new)
18
+ DSL.new(configure).tap { |t| yield t }
19
+ end
20
+
21
+ # Creates a DSL object with the given configuration object.
22
+ #
23
+ # @param configure [Configure]
24
+ def initialize(configure = Configure.new)
25
+ @configure = configure
26
+ end
27
+
28
+ # Sets a given option key to a value. The name is interned, making it
29
+ # a symbol.
30
+ #
31
+ # @param name [::Symbol, #intern] The name of the option key.
32
+ # @param value [::Object] The option value.
33
+ # @return [void]
34
+ def set(name, value)
35
+ @configure.options[name.intern] = value
36
+ end
37
+ alias_method :[]=, :set
38
+
39
+ # Retrives a given option key. The name is interned, making it a symbol.
40
+ #
41
+ # @param name [::Symbol, #intern] The name of the option key.
42
+ # @return [::Object] The option value.
43
+ def get(name)
44
+ @configure.options.fetch(name)
45
+ end
46
+ alias_method :[], :get
47
+
48
+ # Sets the root path of the Brandish project. This is where all of the
49
+ # important files are located. Very rarely should this be set to
50
+ # anything other than `"."`.
51
+ #
52
+ # @param root [::String, ::Pathname] The new root.
53
+ # @return [void]
54
+ def root=(root)
55
+ path = _expand_path(root, Dir.pwd)
56
+ self[:root] = path
57
+ end
58
+
59
+ # Retrieves the root path of the Brandish project. This is where all of
60
+ # the important files are located. Very rarely should this be set to
61
+ # anything other than `"."`.
62
+ #
63
+ # @return [::Pathname] The full path to the root.
64
+ def root
65
+ self[:root]
66
+ end
67
+
68
+ alias_method :root_path=, :root=
69
+ alias_method :root_path, :root
70
+
71
+ # Sets the output directory of the Brandish project. This is where all
72
+ # of the outputs are placed. This is normally `"./output"`.
73
+ #
74
+ # @param output [::String, ::Pathname] The path to the output directory.
75
+ # @return [void]
76
+ def output=(output)
77
+ path = _expand_path(output, root)
78
+ self[:output] = path
79
+ end
80
+
81
+ # Retrieves the output directory of the Brandish project. This is where all
82
+ # of the outputs are placed. This is normally `"./output"`.
83
+ #
84
+ # @return [::Pathname] The full path to the output.
85
+ def output
86
+ self[:output]
87
+ end
88
+
89
+ alias_method :output_path=, :output=
90
+ alias_method :output_path, :output
91
+
92
+ # Retrives the source directories of the Brandish project. This is where
93
+ # all of the sources are located. This is normally `"./source"`.
94
+ #
95
+ # @return [::Pathname] The full path to the sources.
96
+ def sources
97
+ self[:sources]
98
+ end
99
+
100
+ alias_method :source_paths, :sources
101
+
102
+ # Retrieves the template directory of the Brandish project. This is
103
+ # where all of the templates are placed. This is normally `"./template"`.
104
+ #
105
+ # @return [::Pathname] The full path to the template.
106
+ def templates
107
+ self[:templates]
108
+ end
109
+
110
+ alias_method :template_paths, :templates
111
+
112
+ # Creates a new form for the configuration object. This takes arguments
113
+ # and a block. The block is yielded the form instance.
114
+ #
115
+ # @see DSL::Form
116
+ # @see Configure::Form
117
+ # @param (see DSL::Form#instance)
118
+ # @yield [form]
119
+ # @return [void]
120
+ def form(*arguments)
121
+ instance = DSL::Form.new(*arguments)
122
+ yield instance
123
+ form = Configure::Form.new(*instance.data)
124
+ @configure.forms << form
125
+ form
126
+ end
127
+
128
+ private
129
+
130
+ def _expand_path(path, directory)
131
+ ::Pathname.new(path).expand_path(::Pathname.new(directory))
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,136 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Brandish
5
+ class Configure
6
+ class DSL
7
+ # Constructs a form for use with the configure instance. A form is
8
+ # basically a format and name pair; different forms can have different
9
+ # entries, formats, and processors.
10
+ class Form
11
+ # The name of the form. This defaults to a random value. Do not
12
+ # rely on the name unless it is set.
13
+ #
14
+ # @return [::String]
15
+ attr_writer :name
16
+
17
+ # The entry for the form. This is the file that begins the parsing
18
+ # for the form.
19
+ #
20
+ # @return [::String]
21
+ attr_writer :entry
22
+
23
+ # Initialize the form DSL. This sets up the initial format and name,
24
+ # and intializes the instance variables.
25
+ #
26
+ # @param format [::Symbol] The format.
27
+ # @param name [::String, nil] The name of the form.
28
+ def initialize(format, name = nil)
29
+ @name = name || _generate_form_name
30
+ @format = format
31
+ @entry = "index.br"
32
+ @processors = []
33
+ end
34
+
35
+ # Adds a processor for use for the form. The order that this is called
36
+ # is important.
37
+ #
38
+ # @example
39
+ # config.form :html do |form|
40
+ # form.use :literal # Can expand to either "html:literal" or "all:literal"
41
+ # # in that order.
42
+ # form.use "all:literal" # Only expands to "all:literal"
43
+ # form.use Brandish::Processors::All::Literal # allowed too
44
+ # form.use do
45
+ # # also allowed
46
+ # end
47
+ # end
48
+ # @overload use(name, options = {})
49
+ # @param name [::String, ::Symbol, <::Symbol>, ::Class] The name of the
50
+ # processor. If the processor is a symbol, it is assumed to be the
51
+ # name of the processor; the format is guessed to be either
52
+ # the format of the form, or `:all`. If the processor is a string,
53
+ # and it contains a colon, it is assumed to be the name of the
54
+ # processor in the form `<format>:<name>`; if it doesn't contain
55
+ # the colon, it is treated as a symbol. If it is an array, it is
56
+ # assumed to be a pair containing the name of the process. If it
57
+ # is a class, it is assumed to be a processor, and is used directly.
58
+ # @param options [{::Object => ::Object}] The options for the
59
+ # processor. The allowed values for the options are dependant on
60
+ # the processor.
61
+ # @overload use(options = {}, &block)
62
+ # @param options [{::Object => ::Object}] The options for the
63
+ # processor. The allowed values for the options are dependant on
64
+ # the processor.
65
+ # @yield into a new class; this allows a processor to be defined
66
+ # without having a named class.
67
+ # @return [void]
68
+ def use(*arguments)
69
+ processor = options = nil
70
+
71
+ if block_given?
72
+ options = arguments[0]
73
+ processor = Class.new(Brandish::Processor::Base, &::Proc.new)
74
+ else
75
+ name = arguments[0]
76
+ options = arguments[1]
77
+ processor = _guess_processor_name(name)
78
+ end
79
+
80
+ @processors << [processor, options || {}]
81
+ end
82
+ alias_method :process, :use
83
+ alias_method :processor, :use
84
+ alias_method :define, :use
85
+
86
+ # The data from this form. This is used to create the actual form that
87
+ # is used in the configuration.
88
+ #
89
+ # @return [(::String, ::Symbol, ::String, <(::String, ::String, ::Hash)>]
90
+ def data
91
+ [@name, @format, @entry, @processors]
92
+ end
93
+
94
+ private
95
+
96
+ def _generate_form_name
97
+ processor_names = @processors.map { |p| p[0..1].join(":") }.join(".")
98
+ "#{@format}.#{processor_names}"
99
+ end
100
+
101
+ def _guess_processor_name(name)
102
+ case name
103
+ when ::String then _guess_processor_name_string(name)
104
+ when ::Symbol then _guess_processor_name_symbol(name)
105
+ when ::Array then _guess_processor_name_array(name)
106
+ when ::Class then name # They're providing a class that can be used.
107
+ else
108
+ fail ::ArgumentError, "Unknown type given for name `#{name}`"
109
+ end
110
+ end
111
+
112
+ def _guess_processor_name_string(name)
113
+ parts = name.split(":")
114
+
115
+ case parts
116
+ when 1 then _guess_processor_name_symbol(name)
117
+ when 2 then _guess_processor_name_array(parts)
118
+ else
119
+ _guess_processor_name_array([parts[0], parts[1..-1].join(":")])
120
+ end
121
+ end
122
+
123
+ def _guess_processor_name_symbol(name)
124
+ default = [@format, name]
125
+ allowed = [default, [:all, name]]
126
+ which = allowed.find { |a| Processor.all.key?(a) } || default
127
+ _guess_processor_name_array(which)
128
+ end
129
+
130
+ def _guess_processor_name_array(name)
131
+ name.map(&:intern)
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require "pry"
5
+ require "pry-rescue"
6
+
7
+ module Brandish
8
+ class Configure
9
+ # A form used for building.
10
+ Form = Struct.new(:name, :format, :entry, :processors) do
11
+ # Builds the form. This takes a configure object, and builds the
12
+ # form based on that.
13
+ #
14
+ # @see Configure#roots
15
+ # @see Processor::Context
16
+ # @see Processor
17
+ # @param configure [Configure] The configuration object for this build.
18
+ # @return [void]
19
+ def build(configure)
20
+ context = Processor::Context.new(configure, self)
21
+ root = configure.roots[configure.sources.find(entry)]
22
+
23
+ processors.each do |(processor, options)|
24
+ klass = processor.is_a?(::Array) ? Processor.all.fetch(processor) : processor
25
+ klass.new(context, options)
26
+ end
27
+
28
+ context.process(root)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Brandish
5
+ # An error that originates from the library for a library-specific reason.
6
+ # All libraries errors inherit from this class.
7
+ class Error < ::StandardError; end
8
+
9
+ # The file could not be found.
10
+ class NoFileError < Error; end
11
+
12
+ # An error that is created when there is a problem with scanning the
13
+ # document.
14
+ class ScanError < Error; end
15
+
16
+ # An error that occurs with setting up a processor. This has no location
17
+ # information because this error occurs independant of a document.
18
+ class ProcessorError < Error; end
19
+
20
+ # The processor has not been implemented.
21
+ class ProcessorNotImplementedError < ProcessorError; end
22
+
23
+ # This should never be used directly. This is an error that is tied to
24
+ # a location; as such, it provides an initalizer for providing a location.
25
+ #
26
+ # @api private
27
+ class LocationError < Error
28
+ # The location of the error in a file.
29
+ #
30
+ # @return [Location]
31
+ attr_reader :location
32
+
33
+ # Initialize the error with the given location and message.
34
+ def initialize(message, location = Location.default, bt = caller[1..-1])
35
+ @location = location
36
+ super(message)
37
+ set_backtrace(bt)
38
+ end
39
+ end
40
+
41
+ # An error that occurs when parsing. This is for unexpected tokens.
42
+ class ParseError < LocationError; end
43
+
44
+ # An error that is created when there is an issue interacting with a parser
45
+ # node.
46
+ class NodeError < ParseError; end
47
+
48
+ # An error occured during a build.
49
+ class BuildError < LocationError; end
50
+
51
+ # An error occurred while building in a processor. This includes location
52
+ # information from the original node.
53
+ class ProcessorBuildError < BuildError; end
54
+
55
+ # An error occurred while verifying the build. This includes location
56
+ # information for the invalid node.
57
+ class VerificationBuildError < ProcessorBuildError; end
58
+
59
+ # An error occurred with a part of a command or block node, in which a pair
60
+ # was not included.
61
+ class PairError < ProcessorBuildError; end
62
+
63
+ # The syntax used for the styling was invalid.
64
+ class ElementSyntaxError < ProcessorBuildError; end
65
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Brandish
5
+ # Executes a string of code in the instance of the class.
6
+ #
7
+ # @api private
8
+ class Execute
9
+ # Initialize the execution context.
10
+ #
11
+ # @param context [{::Symbol, ::String => ::Object}] The context. The keys
12
+ # are set as instance variables on the class, with the values being the
13
+ # instance variable's respective value.
14
+ def initialize(context)
15
+ context.each { |k, v| instance_variable_set(:"@#{k}", v) }
16
+ end
17
+
18
+ # Executes the given code in the context of the class.
19
+ #
20
+ # @param code [::String] The code to execute.
21
+ # @return [::Object]
22
+ def exec(code)
23
+ instance_exec(code)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Brandish
5
+ # Markup modules for use with the {Processors::Common::Markup} processor.
6
+ # This is only to provide certain integrations with Brandish.
7
+ module Markup
8
+ autoload :Redcarpet, "brandish/markup/redcarpet"
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require "brandish/markup/redcarpet/html"
5
+ require "brandish/markup/redcarpet/format"
6
+
7
+ module Brandish
8
+ module Markup
9
+ # The Redcarpet format. This is used with the
10
+ # {Processors::Common::Markup} processor.
11
+ module Redcarpet
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,127 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require "pry"
5
+
6
+ module Brandish
7
+ module Markup
8
+ module Redcarpet
9
+ # Formats text with Redcarpet. This is extracted into a seperate object
10
+ # in order to provide highlighting properties. This is
11
+ # format-independant.
12
+ #
13
+ # This class can take the following options:
14
+ #
15
+ # - All Markdown extension options that are listed on
16
+ # <https://github.com/vmg/redcarpet>, e.g. `:no_intra_emphasis`,
17
+ # `:tables`, etc.
18
+ # - All HTML formatter options that are listed on
19
+ # <https://github.com/vmg/redcarpet>, e.g. `:filter_html`,
20
+ # `:no_images`, etc.
21
+ # - `:highlight` - Optional. The highlighting engine to use. Can be
22
+ # one of `:rouge`, `:coderay`, `:pygments`, and `:none`. Remember to
23
+ # include the requisite highlighting engine in your Gemfile. They
24
+ # will automatically be required as needed. Defaults to `:none`.
25
+ class Format
26
+ # The options that are passed over to the markdown engine. These
27
+ # are extracted from the options that are passed to the markup engine.
28
+ #
29
+ # @return [<::Symbol>]
30
+ MARKDOWN_OPTIONS = %i(
31
+ no_intra_emphasis tables fenced_code_blocks autolink
32
+ disable_indented_code_blocks strikethrough lax_spacing
33
+ space_after_headers superscript underline quote footnotes
34
+ ).freeze
35
+
36
+ # The default options for the markdown engine as passed by this markup
37
+ # engine.
38
+ #
39
+ # @return [{::Symbol => ::Object}]
40
+ MARKDOWN_DEFAULTS = {
41
+ fenced_code_blocks: true, tables: true, autolink: true,
42
+ strikethrough: true, superscript: true, underline: true,
43
+ footnotes: true, space_after_headers: true
44
+ }.freeze
45
+
46
+ # The options that are passed over to the formatting engine. These are
47
+ # Extracted from the options that are passed to the markup engine.
48
+ #
49
+ # @return [<::Symbol>]
50
+ FORMAT_OPTIONS = %i(
51
+ filter_html no_images no_links no_styles escape_html safe_links_only
52
+ with_toc_data hard_wrap xhtml prettify link_attributes highlight
53
+ ).freeze
54
+
55
+ # The default options for the formatting engine as passed by this
56
+ # markup engine.
57
+ #
58
+ # @return [{::Symbol => ::Object}]
59
+ FORMAT_DEFAULTS = { highlight: :none }.freeze
60
+
61
+ # The highlighting engines that are supported by this markup engine.
62
+ # The key is the value passed by the `:highlight` option, and the value
63
+ # is the require file name. If the value is `nil`, no requirement
64
+ # is performed.
65
+ #
66
+ # @return [{::Symbol => ::Object, nil}]
67
+ HIGHLIGHTERS = {
68
+ rouge: "rouge", coderay: "coderay", pygments: "pygments",
69
+ none: nil
70
+ }.freeze
71
+
72
+ # The formating engines that can be used by this markup engine.
73
+ #
74
+ # @return [{::Symbol => ::Class}]
75
+ FORMATTERS = { html: HTML }.freeze
76
+
77
+ # Initialize the markup engine for Redcarpet. For the available
78
+ # options, see {Format}.
79
+ #
80
+ # @param options [::Hash]
81
+ def initialize(options)
82
+ @context = options.fetch(:context)
83
+ @format = options.fetch(:format)
84
+ @markdown_options = MARKDOWN_DEFAULTS
85
+ .merge(extract_options(MARKDOWN_OPTIONS, options))
86
+ @formatter_options = FORMAT_DEFAULTS
87
+ .merge(extract_options(FORMAT_OPTIONS, options))
88
+ @highlight = @formatter_options[:highlight]
89
+ load_highlighter
90
+ load_engine
91
+ end
92
+
93
+ # Renders the given text using the engine.
94
+ #
95
+ # @param string [::String] The value to render.
96
+ # @return [::String] The rendered value.
97
+ def render(string)
98
+ @engine.render(string)
99
+ end
100
+
101
+ private
102
+
103
+ def load_highlighter
104
+ file = HIGHLIGHTERS.fetch(@highlight)
105
+ require file if file
106
+ rescue ::KeyError
107
+ fail ProcessorError, "Unknown highlighter `#{@highlight}`"
108
+ end
109
+
110
+ def load_engine
111
+ begin
112
+ formatter = FORMATTERS.fetch(@format)
113
+ rescue ::KeyError
114
+ fail ProcessorError, "Unsupported format `#{@format}`"
115
+ end
116
+
117
+ renderer = formatter.new(@context, @formatter_options)
118
+ @engine = ::Redcarpet::Markdown.new(renderer, @markdown_options)
119
+ end
120
+
121
+ def extract_options(keys, options)
122
+ keys.zip(options.values_at(*keys)).select!(&:last).to_h
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end