darkroom 0.0.8 → 0.0.10
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +92 -0
- data/LICENSE +1 -1
- data/README.md +430 -114
- data/VERSION +1 -1
- data/lib/darkroom/asset.rb +176 -164
- data/lib/darkroom/darkroom.rb +140 -149
- data/lib/darkroom/delegate.rb +204 -161
- data/lib/darkroom/delegates/{css.rb → css_delegate.rb} +1 -0
- data/lib/darkroom/delegates/{html.rb → html_delegate.rb} +4 -3
- data/lib/darkroom/delegates/{htx.rb → htx_delegate.rb} +3 -2
- data/lib/darkroom/delegates/{javascript.rb → javascript_delegate.rb} +9 -8
- data/lib/darkroom/errors/asset_error.rb +6 -17
- data/lib/darkroom/errors/asset_not_found_error.rb +4 -8
- data/lib/darkroom/errors/circular_reference_error.rb +4 -8
- data/lib/darkroom/errors/duplicate_asset_error.rb +7 -16
- data/lib/darkroom/errors/invalid_path_error.rb +5 -14
- data/lib/darkroom/errors/missing_library_error.rb +7 -16
- data/lib/darkroom/errors/processing_error.rb +13 -20
- data/lib/darkroom/errors/unrecognized_extension_error.rb +4 -8
- data/lib/darkroom/version.rb +1 -1
- data/lib/darkroom.rb +8 -6
- metadata +36 -30
    
        data/lib/darkroom/delegate.rb
    CHANGED
    
    | @@ -1,237 +1,280 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            class Darkroom
         | 
| 2 | 
            -
              ##
         | 
| 3 4 | 
             
              # Holds asset type-specific information and functionality.
         | 
| 4 | 
            -
              #
         | 
| 5 | 
            -
              # [minify_lib:] Name of a library to +require+ that is needed by the +minify+ lambda (optional).
         | 
| 6 | 
            -
              # [minify:] Lambda to call that will return the minified version of the asset's content (optional). One
         | 
| 7 | 
            -
              #           argument is passed when called:
         | 
| 8 | 
            -
              #           * +content+ - Content to minify.
         | 
| 9 | 
            -
              #
         | 
| 10 5 | 
             
              class Delegate
         | 
| 11 | 
            -
                [
         | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 6 | 
            +
                IMPORT_REGEX_CAPTURES = %w[path].freeze
         | 
| 7 | 
            +
                REFERENCE_REGEX_CAPTURES = %w[quote path quoted entity format].freeze
         | 
| 8 | 
            +
                LIB_REQUIRES = [:compile, :finalize, :minify].freeze
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                @content_type = nil
         | 
| 11 | 
            +
                @parsers = nil
         | 
| 12 | 
            +
                @compile_lib = nil
         | 
| 13 | 
            +
                @compile_delegate = nil
         | 
| 14 | 
            +
                @compile_handler = nil
         | 
| 15 | 
            +
                @finalize_lib = nil
         | 
| 16 | 
            +
                @finalize_handler = nil
         | 
| 17 | 
            +
                @minify_lib = nil
         | 
| 18 | 
            +
                @minify_handler = nil
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                # Public: Set and/or get the HTTP MIME type string, falling back to that of the parent class.
         | 
| 21 | 
            +
                #
         | 
| 22 | 
            +
                # Returns the String content type.
         | 
| 23 | 
            +
                def self.content_type(content_type = (get = true; nil))
         | 
| 24 | 
            +
                  if get
         | 
| 25 | 
            +
                    defined?(@content_type) ? @content_type : superclass.content_type
         | 
| 26 | 
            +
                  else
         | 
| 27 | 
            +
                    @content_type = content_type
         | 
| 20 28 | 
             
                  end
         | 
| 21 29 | 
             
                end
         | 
| 22 30 |  | 
| 23 | 
            -
                 | 
| 31 | 
            +
                # Public: Get parsers, falling back to those of the parent class.
         | 
| 32 | 
            +
                #
         | 
| 33 | 
            +
                # Returns the Array of Proc parsers.
         | 
| 34 | 
            +
                def self.parsers
         | 
| 35 | 
            +
                  defined?(@parsers) ? @parsers : superclass.parsers
         | 
| 36 | 
            +
                end
         | 
| 24 37 |  | 
| 25 | 
            -
                 | 
| 26 | 
            -
                # Sets or returns HTTP MIME type string.
         | 
| 38 | 
            +
                # Public: Iterate over each parser.
         | 
| 27 39 | 
             
                #
         | 
| 28 | 
            -
                 | 
| 29 | 
            -
             | 
| 40 | 
            +
                # Yields each parser's Symbol kind, Regexp regex, and Proc handler.
         | 
| 41 | 
            +
                #
         | 
| 42 | 
            +
                # Returns nothing.
         | 
| 43 | 
            +
                def self.each_parser
         | 
| 44 | 
            +
                  parsers&.each do |kind, (regex, handler)|
         | 
| 45 | 
            +
                    yield(kind, regex, handler)
         | 
| 46 | 
            +
                  end
         | 
| 30 47 | 
             
                end
         | 
| 31 48 |  | 
| 32 | 
            -
                 | 
| 33 | 
            -
                # Configures how imports are handled.
         | 
| 49 | 
            +
                # Public: Get the name of the compile library to require, falling back to that of the parent class.
         | 
| 34 50 | 
             
                #
         | 
| 35 | 
            -
                #  | 
| 36 | 
            -
                 | 
| 37 | 
            -
             | 
| 38 | 
            -
                 | 
| 39 | 
            -
             | 
| 40 | 
            -
                # | 
| 41 | 
            -
                # | 
| 42 | 
            -
                # | 
| 43 | 
            -
                # | 
| 51 | 
            +
                # Returns the String library name if present or nil otherwise.
         | 
| 52 | 
            +
                def self.compile_lib
         | 
| 53 | 
            +
                  defined?(@compile_lib) ? @compile_lib : superclass.compile_lib
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                # Public: Get the Delegate class used once an asset is compiled, falling back to that of the parent
         | 
| 57 | 
            +
                # class.
         | 
| 58 | 
            +
                #
         | 
| 59 | 
            +
                # Returns the Delegate class if present or nil otherwise.
         | 
| 60 | 
            +
                def self.compile_delegate
         | 
| 61 | 
            +
                  defined?(@compile_delegate) ? @compile_delegate : superclass.compile_delegate
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                # Public: Get the compile handler, falling back to that of the parent class.
         | 
| 65 | 
            +
                #
         | 
| 66 | 
            +
                # Returns the Proc handler if present or nil otherwise.
         | 
| 67 | 
            +
                def self.compile_handler
         | 
| 68 | 
            +
                  defined?(@compile_handler) ? @compile_handler : superclass.compile_handler
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                # Public: Get the name of the finalize library to require, falling back to that of the parent class.
         | 
| 72 | 
            +
                #
         | 
| 73 | 
            +
                # Returns the String library name if present or nil otherwise.
         | 
| 74 | 
            +
                def self.finalize_lib
         | 
| 75 | 
            +
                  defined?(@finalize_lib) ? @finalize_lib : superclass.finalize_lib
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                # Public: Get the finalize handler, falling back to that of the parent class.
         | 
| 79 | 
            +
                #
         | 
| 80 | 
            +
                # Returns the Proc handler if present or nil otherwise.
         | 
| 81 | 
            +
                def self.finalize_handler
         | 
| 82 | 
            +
                  defined?(@finalize_handler) ? @finalize_handler : superclass.finalize_handler
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                # Public: Get the name of the minify library to require, falling back to that of the parent class.
         | 
| 86 | 
            +
                #
         | 
| 87 | 
            +
                # Returns the String library name if present or nil otherwise.
         | 
| 88 | 
            +
                def self.minify_lib
         | 
| 89 | 
            +
                  defined?(@minify_lib) ? @minify_lib : superclass.minify_lib
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                # Public: Get the minify handler, falling back to that of the parent class.
         | 
| 93 | 
            +
                #
         | 
| 94 | 
            +
                # Returns the Proc handler if present or nil otherwise.
         | 
| 95 | 
            +
                def self.minify_handler
         | 
| 96 | 
            +
                  defined?(@minify_handler) ? @minify_handler : superclass.minify_handler
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                # Internal: Configure import handling.
         | 
| 44 100 | 
             
                #
         | 
| 101 | 
            +
                # regex   - Regexp for finding import statements. Must contain a named components :quote (' or ") and
         | 
| 102 | 
            +
                #           :path (the path of the asset being imported).
         | 
| 103 | 
            +
                # handler - Proc for special handling of import statements (optional), which is passed three keyword
         | 
| 104 | 
            +
                #           arguments:
         | 
| 105 | 
            +
                #
         | 
| 106 | 
            +
                #           parse_data: - Hash for storing arbitrary data across calls to this and other handlers.
         | 
| 107 | 
            +
                #           match:      - MatchData object from the match against the regex.
         | 
| 108 | 
            +
                #           asset:      - Asset object of the asset being imported.
         | 
| 109 | 
            +
                #
         | 
| 110 | 
            +
                #           Returns nil for default behavior, or a String which is used as the substitution for the text
         | 
| 111 | 
            +
                #             matched by the regex. The portion of the matched text that is replaced can optionally be
         | 
| 112 | 
            +
                #             changed by returning second and third Integer values specifying start and end indexes
         | 
| 113 | 
            +
                #             within the regex match (e.g. ['my substitution', match.begin(:path) + 1,
         | 
| 114 | 
            +
                #             match.end(:path) - 1]).
         | 
| 115 | 
            +
                #           Throws :error with a String message when an error is encountered.
         | 
| 116 | 
            +
                #
         | 
| 117 | 
            +
                # Returns nothing.
         | 
| 118 | 
            +
                # Raises RuntimeError if the regex does not have the required named captures.
         | 
| 45 119 | 
             
                def self.import(regex, &handler)
         | 
| 120 | 
            +
                  validate_regex!(:import, regex, IMPORT_REGEX_CAPTURES)
         | 
| 121 | 
            +
             | 
| 46 122 | 
             
                  parse(:import, regex, &handler)
         | 
| 47 123 | 
             
                end
         | 
| 48 124 |  | 
| 49 | 
            -
                 | 
| 50 | 
            -
                # Configures how references are handled.
         | 
| 125 | 
            +
                # Internal: Configure reference handling.
         | 
| 51 126 | 
             
                #
         | 
| 52 | 
            -
                #  | 
| 53 | 
            -
                # | 
| 54 | 
            -
                # | 
| 55 | 
            -
                # | 
| 56 | 
            -
                # [&handler] Block for special handling of references (optional). Should <tt>throw(:error, '...')</tt>
         | 
| 57 | 
            -
                #            on error. Passed four arguments:
         | 
| 58 | 
            -
                #            * +parse_data:+ - Hash for storing data across calls to this and other parsing handlers.
         | 
| 59 | 
            -
                #            * +match:+ - MatchData object from the match against +regex+.
         | 
| 60 | 
            -
                #            * +asset:+ - Asset object of the asset being imported.
         | 
| 61 | 
            -
                #            * +format:+ - Format of the reference (see Asset::REFERENCE_FORMATS).
         | 
| 62 | 
            -
                #            Return value is used as the substitution for the reference, with optional second and third
         | 
| 63 | 
            -
                #            values as integers representing the start and end indexes of the match to replace.
         | 
| 127 | 
            +
                # regex -   Regex for finding references. Must contain named components :quote (' or "), :path (the path
         | 
| 128 | 
            +
                #           of the asset being referenced), :quoted (everything inside the quotes), :entity ('path' or
         | 
| 129 | 
            +
                #           'content'), and :format (see Asset::REFERENCE_FORMATS).
         | 
| 130 | 
            +
                # handler - Proc for special handling of references (optional), which is passed four keyword arguments:
         | 
| 64 131 | 
             
                #
         | 
| 132 | 
            +
                #           parse_data: - Hash for storing arbitrary data across calls to this and other handlers.
         | 
| 133 | 
            +
                #           match:      - MatchData object from the match against the regex.
         | 
| 134 | 
            +
                #           asset:      - Asset object of the asset being referenced.
         | 
| 135 | 
            +
                #           format:     - String format of the reference (see Asset::REFERENCE_FORMATS).
         | 
| 136 | 
            +
                #
         | 
| 137 | 
            +
                #           Returns nil for default behavior, or a String which is used as the substitution for the text
         | 
| 138 | 
            +
                #             matched by the regex. The portion of the matched text that is replaced can optionally be
         | 
| 139 | 
            +
                #             changed by returning second and third Integer values specifying start and end indexes
         | 
| 140 | 
            +
                #             within the regex match (e.g. ['my substitution', match.begin(:path) + 1,
         | 
| 141 | 
            +
                #             match.end(:path) - 1]).
         | 
| 142 | 
            +
                #           Throws :error with a String message when an error is encountered.
         | 
| 143 | 
            +
                #
         | 
| 144 | 
            +
                # Returns nothing.
         | 
| 145 | 
            +
                # Raises RuntimeError if the regex does not have the required named captures.
         | 
| 65 146 | 
             
                def self.reference(regex, &handler)
         | 
| 147 | 
            +
                  validate_regex!(:reference, regex, REFERENCE_REGEX_CAPTURES)
         | 
| 148 | 
            +
             | 
| 66 149 | 
             
                  parse(:reference, regex, &handler)
         | 
| 67 150 | 
             
                end
         | 
| 68 151 |  | 
| 69 | 
            -
                 | 
| 70 | 
            -
                # | 
| 152 | 
            +
                # Internal: Configure a parser.
         | 
| 153 | 
            +
                #
         | 
| 154 | 
            +
                # kind    - Symbol name to describe what is being parsed. Should be unique across all parse calls. When
         | 
| 155 | 
            +
                #           subclassing another Delegate, this can be used to override the parent class's regex and
         | 
| 156 | 
            +
                #           handler.
         | 
| 157 | 
            +
                # regex   - Regexp to match against.
         | 
| 158 | 
            +
                # handler - Proc for handling matches of the regex, which is passed two keyword arguments:
         | 
| 71 159 | 
             
                #
         | 
| 72 | 
            -
                #  | 
| 73 | 
            -
                # | 
| 74 | 
            -
                # [regex] Regex to match against.
         | 
| 75 | 
            -
                # [&handler] Block for handling matches of the regex. Should <tt>throw(:error, '...')</tt>
         | 
| 76 | 
            -
                #            on error. Passed two arguments:
         | 
| 77 | 
            -
                #            * +parse_data:+ - Hash for storing data across calls to this and other parsing handlers.
         | 
| 78 | 
            -
                #            * +match:+ - MatchData object from the match against +regex+.
         | 
| 79 | 
            -
                #            Return value is used as the substitution for the reference, with optional second and third
         | 
| 80 | 
            -
                #            values as integers representing the start and end indexes of the match to replace.
         | 
| 160 | 
            +
                #           parse_data: - Hash for storing arbitrary data across calls to this and other handlers.
         | 
| 161 | 
            +
                #           match:      - MatchData object from the match against the regex.
         | 
| 81 162 | 
             
                #
         | 
| 163 | 
            +
                #           Returns a String which is used as the substitution for the text matched by the regex. The
         | 
| 164 | 
            +
                #             portion of the matched text that is replaced can optionally be changed by returning second
         | 
| 165 | 
            +
                #             and third Integer values specifying start and end indexes within the regex match (e.g.
         | 
| 166 | 
            +
                #             ['my substitution', match.begin(:path) + 1, match.end(:path) - 1]).
         | 
| 167 | 
            +
                #           Throws :error with a String message when an error is encountered.
         | 
| 168 | 
            +
                #
         | 
| 169 | 
            +
                # Returns nothing.
         | 
| 82 170 | 
             
                def self.parse(kind, regex, &handler)
         | 
| 83 | 
            -
                  @parsers  | 
| 171 | 
            +
                  @parsers ||= parsers&.dup || {}
         | 
| 84 172 | 
             
                  @parsers[kind] = [regex, handler]
         | 
| 85 173 | 
             
                end
         | 
| 86 174 |  | 
| 87 | 
            -
                 | 
| 88 | 
            -
                # | 
| 175 | 
            +
                # Internal: Configure compilation.
         | 
| 176 | 
            +
                #
         | 
| 177 | 
            +
                # lib:      - String name of a library to require that is needed by the handler (optional).
         | 
| 178 | 
            +
                # delegate: - Delegate class to be used after the asset is compiled (optional).
         | 
| 179 | 
            +
                # handler   - Proc to call that will return the compiled version of the asset's own content, which is
         | 
| 180 | 
            +
                #             passed three keyword arguments:
         | 
| 89 181 | 
             
                #
         | 
| 90 | 
            -
                # | 
| 91 | 
            -
                # | 
| 92 | 
            -
                #  | 
| 93 | 
            -
                #            three arguments when called:
         | 
| 94 | 
            -
                #.           * +parse_data:+ - Hash of data collected during parsing.
         | 
| 95 | 
            -
                #            * +path+ - Path of the asset being compiled.
         | 
| 96 | 
            -
                #            * +own_content+ - Asset's own content.
         | 
| 97 | 
            -
                #            Asset's own content is set to the value returned.
         | 
| 182 | 
            +
                #             parse_data:  - Hash for storing arbitrary data across calls to this and other handlers.
         | 
| 183 | 
            +
                #             path:        - String path of the asset.
         | 
| 184 | 
            +
                #             own_content: - String own content (without imports) of the asset.
         | 
| 98 185 | 
             
                #
         | 
| 186 | 
            +
                #             Returns a String which is used as a replacement for the asset's own content.
         | 
| 187 | 
            +
                #             Raises StandardError when an error is encountered.
         | 
| 188 | 
            +
                #
         | 
| 189 | 
            +
                # Returns nothing.
         | 
| 99 190 | 
             
                def self.compile(lib: nil, delegate: nil, &handler)
         | 
| 100 191 | 
             
                  @compile_lib = lib
         | 
| 101 192 | 
             
                  @compile_delegate = delegate
         | 
| 102 193 | 
             
                  @compile_handler = handler
         | 
| 103 194 | 
             
                end
         | 
| 104 195 |  | 
| 105 | 
            -
                 | 
| 106 | 
            -
                # | 
| 196 | 
            +
                # Internal: Configure finalize behavior.
         | 
| 197 | 
            +
                #
         | 
| 198 | 
            +
                # lib:    - String name of a library to require that is needed by the handler (optional).
         | 
| 199 | 
            +
                # handler - Proc to call that will return the finalized version of the asset's compiled content (with
         | 
| 200 | 
            +
                #           imports prepended), which is passed three keyword arguments:
         | 
| 107 201 | 
             
                #
         | 
| 108 | 
            -
                # | 
| 109 | 
            -
                #  | 
| 110 | 
            -
                # | 
| 111 | 
            -
                #.           * +parse_data:+ - Hash of data collected during parsing.
         | 
| 112 | 
            -
                #            * +path+ - Path of the asset being finalized.
         | 
| 113 | 
            -
                #            * +content+ - Asset's content (with imports prepended).
         | 
| 114 | 
            -
                #            Asset's content is set to the value returned.
         | 
| 202 | 
            +
                #           parse_data: - Hash for storing arbitrary data across calls to this and other handlers.
         | 
| 203 | 
            +
                #           path:       - String path of the asset.
         | 
| 204 | 
            +
                #           content:    - String content of the compiled asset (with imports prepended).
         | 
| 115 205 | 
             
                #
         | 
| 206 | 
            +
                #           Returns a String which is used as a replacement for the asset's content.
         | 
| 207 | 
            +
                #           Raises StandardError when an error is encountered.
         | 
| 208 | 
            +
                #
         | 
| 209 | 
            +
                # Returns nothing.
         | 
| 116 210 | 
             
                def self.finalize(lib: nil, &handler)
         | 
| 117 211 | 
             
                  @finalize_lib = lib
         | 
| 118 212 | 
             
                  @finalize_handler = handler
         | 
| 119 213 | 
             
                end
         | 
| 120 214 |  | 
| 121 | 
            -
                 | 
| 122 | 
            -
                # | 
| 215 | 
            +
                # Internal: Configure minification.
         | 
| 216 | 
            +
                #
         | 
| 217 | 
            +
                # lib:    - String name of a library to require that is needed by the handler (optional).
         | 
| 218 | 
            +
                # handler - Proc to call that will return the minified version of the asset's finalized content, which
         | 
| 219 | 
            +
                #           is passed three keyword arguments:
         | 
| 123 220 | 
             
                #
         | 
| 124 | 
            -
                # | 
| 125 | 
            -
                #  | 
| 126 | 
            -
                # | 
| 127 | 
            -
                 | 
| 128 | 
            -
                # | 
| 129 | 
            -
                # | 
| 130 | 
            -
                #            Asset's minified content is set to the value returned.
         | 
| 221 | 
            +
                #           parse_data: - Hash for storing arbitrary data across calls to this and other handlers.
         | 
| 222 | 
            +
                #           path:       - String oath of the asset being minified.
         | 
| 223 | 
            +
                #           content:    - string content of the finalized asset.
         | 
| 224 | 
            +
                #
         | 
| 225 | 
            +
                #           Returns a String which is used as the minified version of the asset's content.
         | 
| 226 | 
            +
                #           Raises StandardError when an error is encountered.
         | 
| 131 227 | 
             
                #
         | 
| 132 228 | 
             
                def self.minify(lib: nil, &handler)
         | 
| 133 229 | 
             
                  @minify_lib = lib
         | 
| 134 230 | 
             
                  @minify_handler = handler
         | 
| 135 231 | 
             
                end
         | 
| 136 232 |  | 
| 137 | 
            -
                 | 
| 138 | 
            -
                # Throws +:error+ with a message.
         | 
| 233 | 
            +
                # Internal: Throw :error with a message.
         | 
| 139 234 | 
             
                #
         | 
| 140 | 
            -
                #  | 
| 235 | 
            +
                # message - String message to include with the throw.
         | 
| 141 236 | 
             
                #
         | 
| 237 | 
            +
                # Returns nothing.
         | 
| 142 238 | 
             
                def self.error(message)
         | 
| 143 239 | 
             
                  throw(:error, message)
         | 
| 144 240 | 
             
                end
         | 
| 145 241 |  | 
| 146 | 
            -
                 | 
| 147 | 
            -
                # Returns regex for a parser.
         | 
| 242 | 
            +
                # Internal: Get a parser's regex.
         | 
| 148 243 | 
             
                #
         | 
| 149 | 
            -
                #  | 
| 244 | 
            +
                # kind - Symbol name of the parser.
         | 
| 150 245 | 
             
                #
         | 
| 246 | 
            +
                # Returns the Regexp.
         | 
| 151 247 | 
             
                def self.regex(kind)
         | 
| 152 248 | 
             
                  parsers[kind]&.first
         | 
| 153 249 | 
             
                end
         | 
| 154 250 |  | 
| 155 | 
            -
                 | 
| 156 | 
            -
                # Returns handler for a parser.
         | 
| 251 | 
            +
                # Internal: Get a parser's handler.
         | 
| 157 252 | 
             
                #
         | 
| 158 | 
            -
                #  | 
| 253 | 
            +
                # kind - Symbol name of the parser.
         | 
| 159 254 | 
             
                #
         | 
| 255 | 
            +
                # Returns the Proc handler.
         | 
| 160 256 | 
             
                def self.handler(kind)
         | 
| 161 257 | 
             
                  parsers[kind]&.last
         | 
| 162 258 | 
             
                end
         | 
| 163 259 |  | 
| 164 | 
            -
                 | 
| 165 | 
            -
                # Iterates over each parser and yields its kind, regex, and handler.
         | 
| 166 | 
            -
                #
         | 
| 167 | 
            -
                def self.each_parser
         | 
| 168 | 
            -
                  parsers&.each do |kind, (regex, handler)|
         | 
| 169 | 
            -
                    yield(kind, regex, handler)
         | 
| 170 | 
            -
                  end
         | 
| 171 | 
            -
                end
         | 
| 172 | 
            -
             | 
| 173 | 
            -
                ##
         | 
| 174 | 
            -
                # DEPRECATED: subclass Delegate and use its DSL instead. Returns a subclass of Delegate configured using
         | 
| 175 | 
            -
                # the supplied Hash.
         | 
| 260 | 
            +
                # Internal: Raise an exception if a regex does not have the required named captures.
         | 
| 176 261 | 
             
                #
         | 
| 177 | 
            -
                 | 
| 178 | 
            -
             | 
| 179 | 
            -
             | 
| 180 | 
            -
             | 
| 181 | 
            -
                  deprecated_from_hash(**params)
         | 
| 182 | 
            -
                end
         | 
| 183 | 
            -
             | 
| 184 | 
            -
                ##
         | 
| 185 | 
            -
                # DEPRECATED: subclass Delegate and use its DSL instead. Returns a subclass of Delegate configured using
         | 
| 186 | 
            -
                # the supplied Hash.
         | 
| 262 | 
            +
                # name              - Symbol name of the regex (used in the exception message).
         | 
| 263 | 
            +
                # regex             - Regexp to validate.
         | 
| 264 | 
            +
                # required_captures - Array of String required named captures.
         | 
| 187 265 | 
             
                #
         | 
| 188 | 
            -
                 | 
| 189 | 
            -
             | 
| 190 | 
            -
             | 
| 191 | 
            -
                   | 
| 192 | 
            -
                    self.content_type(content_type)
         | 
| 193 | 
            -
             | 
| 194 | 
            -
                    @import_regex = import_regex
         | 
| 195 | 
            -
                    @reference_regex = reference_regex
         | 
| 196 | 
            -
             | 
| 197 | 
            -
                    self.import(import_regex) if import_regex
         | 
| 198 | 
            -
             | 
| 199 | 
            -
                    if validate_reference || reference_content
         | 
| 200 | 
            -
                      @validate_reference = validate_reference
         | 
| 201 | 
            -
                      @reference_content = reference_content
         | 
| 266 | 
            +
                # Returns nothing.
         | 
| 267 | 
            +
                # Raises RuntimeError if the regex does not have the required named captures.
         | 
| 268 | 
            +
                def self.validate_regex!(name, regex, required_captures)
         | 
| 269 | 
            +
                  missing = (required_captures - regex.named_captures.keys)
         | 
| 202 270 |  | 
| 203 | 
            -
             | 
| 204 | 
            -
                        error_message = validate_reference&.call(asset, match, format)
         | 
| 205 | 
            -
                        error(error_message) if error_message
         | 
| 271 | 
            +
                  return if missing.empty?
         | 
| 206 272 |  | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
                      self.reference(reference_regex)
         | 
| 211 | 
            -
                    end
         | 
| 273 | 
            +
                  name = name.to_s.capitalize
         | 
| 274 | 
            +
                  plural = missing.size != 1
         | 
| 275 | 
            +
                  missing_str = missing.join(', ')
         | 
| 212 276 |  | 
| 213 | 
            -
             | 
| 214 | 
            -
                      self.compile(lib: compile_lib, delegate: compiled) do |parse_data:, path:, own_content:|
         | 
| 215 | 
            -
                        compile.call(path, own_content)
         | 
| 216 | 
            -
                      end
         | 
| 217 | 
            -
                    elsif compile_lib || compiled
         | 
| 218 | 
            -
                      self.compile(lib: compile_lib, delegate: compiled)
         | 
| 219 | 
            -
                    end
         | 
| 220 | 
            -
             | 
| 221 | 
            -
                    if minify
         | 
| 222 | 
            -
                      self.minify(lib: minify_lib) do |parse_data:, path:, content:|
         | 
| 223 | 
            -
                        minify.call(content)
         | 
| 224 | 
            -
                      end
         | 
| 225 | 
            -
                    end
         | 
| 226 | 
            -
                  end
         | 
| 277 | 
            +
                  raise("#{name} regex is missing required named capture#{'s' if plural}: #{missing_str}")
         | 
| 227 278 | 
             
                end
         | 
| 228 | 
            -
             | 
| 229 | 
            -
                ##
         | 
| 230 | 
            -
                # DEPRECATED: subclass Delegate and use its DSL instead.
         | 
| 231 | 
            -
                #
         | 
| 232 | 
            -
                def self.import_regex() @import_regex end
         | 
| 233 | 
            -
                def self.reference_regex() @reference_regex end
         | 
| 234 | 
            -
                def self.validate_reference() @validate_reference end
         | 
| 235 | 
            -
                def self.reference_content() @reference_content end
         | 
| 236 279 | 
             
              end
         | 
| 237 280 | 
             
            end
         | 
| @@ -4,6 +4,7 @@ require_relative('../asset') | |
| 4 4 | 
             
            require_relative('../delegate')
         | 
| 5 5 |  | 
| 6 6 | 
             
            class Darkroom
         | 
| 7 | 
            +
              # Delegate for handling HTML-specific asset processing.
         | 
| 7 8 | 
             
              class HTMLDelegate < Delegate
         | 
| 8 9 | 
             
                REFERENCE_REGEX = /
         | 
| 9 10 | 
             
                  <(?<tag>a|area|audio|base|embed|iframe|img|input|link|script|source|track|video)\s+[^>]*
         | 
| @@ -26,8 +27,8 @@ class Darkroom | |
| 26 27 | 
             
                      if asset.content_type == 'text/javascript'
         | 
| 27 28 | 
             
                        offset = match.begin(0)
         | 
| 28 29 |  | 
| 29 | 
            -
                        "#{match[0][0..(match.begin(:attr) - 2 - offset)]}"\
         | 
| 30 | 
            -
                        "#{match[0][(match.end(:quoted) + match[:quote].size - offset)..(match.end(0) - offset)]}"\
         | 
| 30 | 
            +
                        "#{match[0][0..(match.begin(:attr) - 2 - offset)]}" \
         | 
| 31 | 
            +
                        "#{match[0][(match.end(:quoted) + match[:quote].size - offset)..(match.end(0) - offset)]}" \
         | 
| 31 32 | 
             
                        "#{asset.content}"
         | 
| 32 33 | 
             
                      else
         | 
| 33 34 | 
             
                        error('Asset content type must be text/javascript')
         | 
| @@ -46,7 +47,7 @@ class Darkroom | |
| 46 47 |  | 
| 47 48 | 
             
                    content = asset.content.dup
         | 
| 48 49 | 
             
                    content.gsub!('#', '%23')
         | 
| 49 | 
            -
                    content.gsub!(quote, quote == '"' ?  | 
| 50 | 
            +
                    content.gsub!(quote, quote == '"' ? '"' : ''')
         | 
| 50 51 |  | 
| 51 52 | 
             
                    content
         | 
| 52 53 | 
             
                  end
         | 
| @@ -1,9 +1,10 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative(' | 
| 4 | 
            -
            require_relative(' | 
| 3 | 
            +
            require_relative('html_delegate')
         | 
| 4 | 
            +
            require_relative('javascript_delegate')
         | 
| 5 5 |  | 
| 6 6 | 
             
            class Darkroom
         | 
| 7 | 
            +
              # Delegate for handling HTX-specific asset processing.
         | 
| 7 8 | 
             
              class HTXDelegate < HTMLDelegate
         | 
| 8 9 | 
             
                compile(lib: 'htx', delegate: JavaScriptDelegate) do |parse_data:, path:, own_content:|
         | 
| 9 10 | 
             
                  module_supported = false
         | 
| @@ -5,13 +5,14 @@ require_relative('../asset') | |
| 5 5 | 
             
            require_relative('../delegate')
         | 
| 6 6 |  | 
| 7 7 | 
             
            class Darkroom
         | 
| 8 | 
            +
              # Delegate for handling JavaScript-specific asset processing.
         | 
| 8 9 | 
             
              class JavaScriptDelegate < Delegate
         | 
| 9 10 | 
             
                IDENTIFIER_REGEX = /[_$a-zA-Z][_$a-zA-Z0-9]*/.freeze
         | 
| 10 11 | 
             
                COMMA_REGEX = /,/.freeze
         | 
| 11 12 | 
             
                QUOTED_REGEX = /
         | 
| 12 13 | 
             
                  (?<quoted>
         | 
| 13 | 
            -
                    (?<quote>['"])(
         | 
| 14 | 
            -
                      (?<=[^\\])\\( | 
| 14 | 
            +
                    (?<quote>['"])(?:
         | 
| 15 | 
            +
                      (?<=[^\\])\\(?:\\\\)*\k<quote> |
         | 
| 15 16 | 
             
                      (?!\k<quote>).
         | 
| 16 17 | 
             
                    )*\k<quote>
         | 
| 17 18 | 
             
                  )
         | 
| @@ -131,14 +132,14 @@ class Darkroom | |
| 131 132 | 
             
                    end while items.any? { |i| i.first == mod }
         | 
| 132 133 |  | 
| 133 134 | 
             
                    own_content.prepend(
         | 
| 134 | 
            -
                      "let #{items.map(&:first).join(', ')}; "\
         | 
| 135 | 
            -
                      "$import('#{import_path}', "\
         | 
| 135 | 
            +
                      "let #{items.map(&:first).join(', ')}; " \
         | 
| 136 | 
            +
                      "$import('#{import_path}', " \
         | 
| 136 137 | 
             
                      "#{mod} => #{prefix}#{items.map { |(i, e)| "#{i} = #{mod}#{e}" }.join(', ')}#{suffix})\n"
         | 
| 137 138 | 
             
                    )
         | 
| 138 139 | 
             
                  end
         | 
| 139 140 |  | 
| 140 141 | 
             
                  own_content.prepend("['#{path}', $import => {\n\n")
         | 
| 141 | 
            -
                  own_content << <<~ | 
| 142 | 
            +
                  own_content << <<~JS
         | 
| 142 143 |  | 
| 143 144 | 
             
                    return Object.seal({#{
         | 
| 144 145 | 
             
                      if parse_data[:exports] && !parse_data[:exports].empty?
         | 
| @@ -147,7 +148,7 @@ class Darkroom | |
| 147 148 | 
             
                    }})
         | 
| 148 149 |  | 
| 149 150 | 
             
                    }],
         | 
| 150 | 
            -
                   | 
| 151 | 
            +
                  JS
         | 
| 151 152 | 
             
                end
         | 
| 152 153 |  | 
| 153 154 | 
             
                ########################################################################################################
         | 
| @@ -158,7 +159,7 @@ class Darkroom | |
| 158 159 | 
             
                  next unless Darkroom.javascript_iife
         | 
| 159 160 |  | 
| 160 161 | 
             
                  (content.frozen? ? content.dup : content).prepend(
         | 
| 161 | 
            -
                    <<~ | 
| 162 | 
            +
                    <<~JS
         | 
| 162 163 | 
             
                      ((...bundle) => {
         | 
| 163 164 | 
             
                        const modules = {}
         | 
| 164 165 | 
             
                        const setters = []
         | 
| @@ -172,7 +173,7 @@ class Darkroom | |
| 172 173 | 
             
                          setter(modules[name])
         | 
| 173 174 | 
             
                      })(
         | 
| 174 175 |  | 
| 175 | 
            -
                     | 
| 176 | 
            +
                    JS
         | 
| 176 177 | 
             
                  ) << "\n)\n"
         | 
| 177 178 | 
             
                end
         | 
| 178 179 |  | 
| @@ -1,33 +1,22 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            class Darkroom
         | 
| 4 | 
            -
              ##
         | 
| 5 4 | 
             
              # General error class used for errors encountered while processing an asset.
         | 
| 6 | 
            -
              #
         | 
| 7 5 | 
             
              class AssetError < StandardError
         | 
| 8 6 | 
             
                attr_reader(:detail, :source_path, :source_line_num)
         | 
| 9 7 |  | 
| 10 | 
            -
                 | 
| 11 | 
            -
                # Creates a new instance.
         | 
| 12 | 
            -
                #
         | 
| 13 | 
            -
                # [message] Description of the error.
         | 
| 14 | 
            -
                # [detail] Additional detail about the error.
         | 
| 15 | 
            -
                # [source_path] Path of the asset that contains the error.
         | 
| 16 | 
            -
                # [source_line_num] Line number in the asset where the error is located.
         | 
| 8 | 
            +
                # Public: Create a new instance.
         | 
| 17 9 | 
             
                #
         | 
| 10 | 
            +
                # message         - String description of the error.
         | 
| 11 | 
            +
                # detail          - String additional error detail.
         | 
| 12 | 
            +
                # source_path     - String path of the asset that contains the error.
         | 
| 13 | 
            +
                # source_line_num - Integer line number in the asset file where the error was located.
         | 
| 18 14 | 
             
                def initialize(message, detail, source_path = nil, source_line_num = nil)
         | 
| 19 | 
            -
                  super(message)
         | 
| 15 | 
            +
                  super("#{"#{source_path}:#{source_line_num || '?'}: " if source_path}#{message}: #{detail}")
         | 
| 20 16 |  | 
| 21 17 | 
             
                  @detail = detail
         | 
| 22 18 | 
             
                  @source_path = source_path
         | 
| 23 19 | 
             
                  @source_line_num = source_line_num
         | 
| 24 20 | 
             
                end
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                ##
         | 
| 27 | 
            -
                # Returns a string representation of the error.
         | 
| 28 | 
            -
                #
         | 
| 29 | 
            -
                def to_s
         | 
| 30 | 
            -
                  "#{"#{@source_path}:#{@source_line_num || '?'}: " if @source_path}#{super}: #{@detail}"
         | 
| 31 | 
            -
                end
         | 
| 32 21 | 
             
              end
         | 
| 33 22 | 
             
            end
         | 
| @@ -3,18 +3,14 @@ | |
| 3 3 | 
             
            require_relative('asset_error')
         | 
| 4 4 |  | 
| 5 5 | 
             
            class Darkroom
         | 
| 6 | 
            -
              ##
         | 
| 7 6 | 
             
              # Error class used when an asset requested explicitly or specified as a dependency of another doesn't
         | 
| 8 7 | 
             
              # exist.
         | 
| 9 | 
            -
              #
         | 
| 10 8 | 
             
              class AssetNotFoundError < AssetError
         | 
| 11 | 
            -
                 | 
| 12 | 
            -
                # Creates a new instance.
         | 
| 13 | 
            -
                #
         | 
| 14 | 
            -
                # [path] Path of asset that doesn't exist.
         | 
| 15 | 
            -
                # [source_path] Path of the asset that contains the error.
         | 
| 16 | 
            -
                # [source_line_num] Line number in the asset where the error is located.
         | 
| 9 | 
            +
                # Public: Create a new instance.
         | 
| 17 10 | 
             
                #
         | 
| 11 | 
            +
                # path            - String path of asset that doesn't exist.
         | 
| 12 | 
            +
                # source_path     - String path of the asset that contains the error.
         | 
| 13 | 
            +
                # source_line_num - Integer line number in the asset file where the error is located.
         | 
| 18 14 | 
             
                def initialize(path, source_path = nil, source_line_num = nil)
         | 
| 19 15 | 
             
                  super('Asset not found', path, source_path, source_line_num)
         | 
| 20 16 | 
             
                end
         | 
| @@ -3,17 +3,13 @@ | |
| 3 3 | 
             
            require_relative('asset_error')
         | 
| 4 4 |  | 
| 5 5 | 
             
            class Darkroom
         | 
| 6 | 
            -
              ##
         | 
| 7 6 | 
             
              # Error class used when an asset reference results in a circular reference chain.
         | 
| 8 | 
            -
              #
         | 
| 9 7 | 
             
              class CircularReferenceError < AssetError
         | 
| 10 | 
            -
                 | 
| 11 | 
            -
                # Creates a new instance.
         | 
| 12 | 
            -
                #
         | 
| 13 | 
            -
                # [snippet] Snippet showing the reference.
         | 
| 14 | 
            -
                # [source_path] Path of the asset that contains the error.
         | 
| 15 | 
            -
                # [source_line_num] Line number in the asset where the error is located.
         | 
| 8 | 
            +
                # Public: Create a new instance.
         | 
| 16 9 | 
             
                #
         | 
| 10 | 
            +
                # snippet         - String snippet showing the reference.
         | 
| 11 | 
            +
                # source_path     - String path of the asset that contains the error.
         | 
| 12 | 
            +
                # source_line_num - Integer line number in the asset file where the error is located.
         | 
| 17 13 | 
             
                def initialize(snippet, source_path, source_line_num)
         | 
| 18 14 | 
             
                  super('Reference would result in a circular reference chain', snippet, source_path, source_line_num)
         | 
| 19 15 | 
             
                end
         |