rubyoshka 0.2 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/README.md +152 -16
- data/lib/rubyoshka.rb +35 -182
- data/lib/rubyoshka/html.rb +38 -0
- data/lib/rubyoshka/renderer.rb +198 -0
- data/lib/rubyoshka/version.rb +1 -1
- metadata +20 -18
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: ae561e0250334107b9b82a72fc72ab672622a7f037044e8e18154e79b71a54b5
         | 
| 4 | 
            +
              data.tar.gz: e5cddea7df6231799b38339bc014373a6c9d6a96bb60d3c7b7107e48321b3c6d
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 675b83551aa791f6bb1539c80c40d267e3caa1aceed3748af04ca1d2a49bcac3ad237a8d8c0f0dd69d24714eadbb47bf4a929af77c3ef2c71155e806ff611c66
         | 
| 7 | 
            +
              data.tar.gz: 7dd19daa9df716417c894130ff8fdae0505f83a63de17709634b2c7c83384f56453c13adbee109e4961f44a64f1c46e151752d1fc5bf690ad6738402b023f9bf
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,3 +1,33 @@ | |
| 1 | 
            +
            0.6.1 2021-03-03
         | 
| 2 | 
            +
            ----------------
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            * Remove support for Ruby 2.6
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            0.6 2021-03-03
         | 
| 7 | 
            +
            --------------
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            * Fix Rubyoshka on Ruby 3.0
         | 
| 10 | 
            +
            * Refactor and add more tests
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            0.5 2021-02-27
         | 
| 13 | 
            +
            --------------
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            * Add support for rendering XML
         | 
| 16 | 
            +
            * Add Rubyoshka.component method
         | 
| 17 | 
            +
            * Remove Modulation dependency
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            0.4 2019-02-05
         | 
| 20 | 
            +
            --------------
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            * Add support for emitting component modules
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            0.3 2019-01-13
         | 
| 25 | 
            +
            --------------
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            * Implement caching
         | 
| 28 | 
            +
            * Improve performance
         | 
| 29 | 
            +
            * Handle attributes with `false` value correctly
         | 
| 30 | 
            +
             | 
| 1 31 | 
             
            0.2 2019-01-07
         | 
| 2 32 | 
             
            --------------
         | 
| 3 33 |  | 
    
        data/README.md
    CHANGED
    
    | @@ -3,7 +3,7 @@ | |
| 3 3 | 
             
            [INSTALL](#installing-rubyoshka) |
         | 
| 4 4 | 
             
            [TUTORIAL](#getting-started) |
         | 
| 5 5 | 
             
            [EXAMPLES](examples) |
         | 
| 6 | 
            -
            [REFERENCE](reference)
         | 
| 6 | 
            +
            [REFERENCE](#api-reference)
         | 
| 7 7 |  | 
| 8 8 | 
             
            ## What is Rubyoshka
         | 
| 9 9 |  | 
| @@ -16,21 +16,30 @@ features: | |
| 16 16 | 
             
            - Use global and local contexts to pass values to reusable components
         | 
| 17 17 | 
             
            - Automatic HTML escaping
         | 
| 18 18 | 
             
            - Composable nested components
         | 
| 19 | 
            -
            -  | 
| 20 | 
            -
            - About 4 times faster than ERubis (see [benchmark](examples/perf.rb)).
         | 
| 19 | 
            +
            - Template caching from fragments to whole templates
         | 
| 21 20 |  | 
| 22 21 | 
             
            > **Note** Rubyoshka is a new library and as such may be missing features and
         | 
| 23 | 
            -
            > contain bugs.  | 
| 22 | 
            +
            > contain bugs. Also, its API may change unexpectedly. Your issue reports and
         | 
| 23 | 
            +
            > code contributions are most welcome!
         | 
| 24 24 |  | 
| 25 25 | 
             
            With Rubyoshka you can structure your templates like a Russian doll, each
         | 
| 26 26 | 
             
            component containing any number of nested components, in a somewhat similar
         | 
| 27 | 
            -
            fashion to React. The name *Rubyoshka* is a nod to  | 
| 27 | 
            +
            fashion to React. The name *Rubyoshka* is a nod to 
         | 
| 28 | 
            +
            [Matryoshka](https://en.wikipedia.org/wiki/Matryoshka_doll), the Russian
         | 
| 28 29 | 
             
            nesting doll.
         | 
| 29 30 |  | 
| 30 31 | 
             
            ## Installing Rubyoshka
         | 
| 31 32 |  | 
| 33 | 
            +
            Using bundler:
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            ```ruby
         | 
| 36 | 
            +
            gem 'rubyoshka'
         | 
| 37 | 
            +
            ```
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            Or manually:
         | 
| 40 | 
            +
             | 
| 32 41 | 
             
            ```bash
         | 
| 33 | 
            -
            $ gem install  | 
| 42 | 
            +
            $ gem install rubyoshka
         | 
| 34 43 | 
             
            ```
         | 
| 35 44 |  | 
| 36 45 | 
             
            ## Getting started
         | 
| @@ -217,16 +226,17 @@ greeting.render(name: 'world') | |
| 217 226 | 
             
            ## Templates as components
         | 
| 218 227 |  | 
| 219 228 | 
             
            Rubyoshka makes it easy to compose multiple separate templates into a whole HTML
         | 
| 220 | 
            -
            document. Each template can be defined as a self-contained component that can
         | 
| 221 | 
            -
             | 
| 222 | 
            -
             | 
| 223 | 
            -
             | 
| 224 | 
            -
            returns a Rubyoshka instance:
         | 
| 229 | 
            +
            document. Each template can be defined as a self-contained component that can be
         | 
| 230 | 
            +
            reused inside other components. Components can be defined as either a Rubyoshka
         | 
| 231 | 
            +
            instance (using `#H`), a `proc` that returns a Rubyoshka instance, or using
         | 
| 232 | 
            +
            `Rubyoshka.component`:
         | 
| 225 233 |  | 
| 226 234 | 
             
            ```ruby
         | 
| 235 | 
            +
            # Simple component relying on global/local context
         | 
| 227 236 | 
             
            Title = H { h1 title }
         | 
| 228 237 |  | 
| 229 | 
            -
            #  | 
| 238 | 
            +
            # Proc component that returns a template
         | 
| 239 | 
            +
            # Notice how the lambda expression takes keyword arguments
         | 
| 230 240 | 
             
            Item = ->(id:, text:, checked:) {
         | 
| 231 241 | 
             
              H {
         | 
| 232 242 | 
             
                li {
         | 
| @@ -236,20 +246,29 @@ Item = ->(id:, text:, checked:) { | |
| 236 246 | 
             
              }
         | 
| 237 247 | 
             
            }
         | 
| 238 248 |  | 
| 239 | 
            -
             | 
| 249 | 
            +
            # Components using Rubyoshka.component (or H.component) are a bit more compact.
         | 
| 250 | 
            +
            # Any parameters are passed as arguments to the block.
         | 
| 251 | 
            +
            NavBar = Rubyoshka.component do |links|
         | 
| 252 | 
            +
              div {
         | 
| 253 | 
            +
                links.each { |l| a l[:title], href: l[:url] }
         | 
| 254 | 
            +
              }
         | 
| 255 | 
            +
            end
         | 
| 256 | 
            +
             | 
| 257 | 
            +
            def render_items(items, links)
         | 
| 240 258 | 
             
              html = H {
         | 
| 241 259 | 
             
                Title()
         | 
| 260 | 
            +
                NavBar(links)
         | 
| 242 261 | 
             
                ul {
         | 
| 243 262 | 
             
                  items.each { |id, attributes|
         | 
| 244 263 | 
             
                    Item id: id, text: attributes[:text], checked: attributes[:active]
         | 
| 245 264 | 
             
                  }
         | 
| 246 265 | 
             
                }
         | 
| 247 | 
            -
              }.render
         | 
| 266 | 
            +
              }.render(title: 'Hello from components')
         | 
| 248 267 | 
             
            end
         | 
| 249 268 | 
             
            ```
         | 
| 250 269 |  | 
| 251 270 | 
             
            Note that a component is invoked as a method, which means that if no arguments
         | 
| 252 | 
            -
            are passed, you  | 
| 271 | 
            +
            are passed, you must add an empty pair of parens, as shown in the example
         | 
| 253 272 | 
             
            above.
         | 
| 254 273 |  | 
| 255 274 | 
             
            In addition to using components defined as constants, you can also use
         | 
| @@ -265,6 +284,54 @@ H { | |
| 265 284 | 
             
            }
         | 
| 266 285 | 
             
            ```
         | 
| 267 286 |  | 
| 287 | 
            +
            ## Fragment caching
         | 
| 288 | 
            +
             | 
| 289 | 
            +
            Any part of a Rubyoshka template can be cached - a fragment, a component, or a
         | 
| 290 | 
            +
            whole template. It is up to you, the user, to determine which parts of the 
         | 
| 291 | 
            +
            template to cache. By default, a call to `#cache` creates a cache entry based on
         | 
| 292 | 
            +
            the source location of the cached block:
         | 
| 293 | 
            +
             | 
| 294 | 
            +
            ```ruby
         | 
| 295 | 
            +
            Head = H {
         | 
| 296 | 
            +
              cache {
         | 
| 297 | 
            +
                head {
         | 
| 298 | 
            +
                  title 'My app'
         | 
| 299 | 
            +
                  style "@import '/app.css';"
         | 
| 300 | 
            +
                }
         | 
| 301 | 
            +
              }
         | 
| 302 | 
            +
            }
         | 
| 303 | 
            +
            ```
         | 
| 304 | 
            +
             | 
| 305 | 
            +
            However, if your template references local or global variables, you'll want to
         | 
| 306 | 
            +
            take those into account when caching. This is done by passing any variables used
         | 
| 307 | 
            +
            in the template to `#cache` in order to create separate cache entries for each
         | 
| 308 | 
            +
            discrete value or combination of values:
         | 
| 309 | 
            +
             | 
| 310 | 
            +
            ```ruby
         | 
| 311 | 
            +
            Greeting = H {
         | 
| 312 | 
            +
              cache(name) {
         | 
| 313 | 
            +
                div {
         | 
| 314 | 
            +
                  span "Hello, #{name}"
         | 
| 315 | 
            +
                }
         | 
| 316 | 
            +
              }
         | 
| 317 | 
            +
            }
         | 
| 318 | 
            +
             | 
| 319 | 
            +
            names = %w{tommy dolly world}
         | 
| 320 | 
            +
            App = H {
         | 
| 321 | 
            +
              names.each { |n| Greeting(name: n) }
         | 
| 322 | 
            +
            }
         | 
| 323 | 
            +
            ```
         | 
| 324 | 
            +
             | 
| 325 | 
            +
            In the above example a separate cache entry will be created for each name. The 
         | 
| 326 | 
            +
            use of caching in components is especially beneficial since components may be 
         | 
| 327 | 
            +
            reused in multiple different templates in your app.
         | 
| 328 | 
            +
             | 
| 329 | 
            +
            ### Changing the cache store
         | 
| 330 | 
            +
             | 
| 331 | 
            +
            Rubyoshka ships with a naïve in-memory cache store built-in. You can use
         | 
| 332 | 
            +
            another cache store by overriding the `Rubyoshka.cache` method (see API
         | 
| 333 | 
            +
            [reference](#rubyoshkacache)).
         | 
| 334 | 
            +
             | 
| 268 335 | 
             
            ## Wrapping arbitrary HTML with a component
         | 
| 269 336 |  | 
| 270 337 | 
             
            Components can also be used to wrap arbitrary HTML with addional markup. This is
         | 
| @@ -347,7 +414,37 @@ Blog = H { | |
| 347 414 | 
             
            }
         | 
| 348 415 | 
             
            ```
         | 
| 349 416 |  | 
| 350 | 
            -
             | 
| 417 | 
            +
            ### A higher-order list component
         | 
| 418 | 
            +
             | 
| 419 | 
            +
            Here's another demonstration of a higher-order component, a list component that
         | 
| 420 | 
            +
            takes an item component as an argument. The `List` component can be reused for
         | 
| 421 | 
            +
            rendering any kind of unordered list, and with any kind of item component:
         | 
| 422 | 
            +
             | 
| 423 | 
            +
            ```ruby
         | 
| 424 | 
            +
            List = ->(items, item_component) {
         | 
| 425 | 
            +
              H {
         | 
| 426 | 
            +
                ul {
         | 
| 427 | 
            +
                  items.each { |item|
         | 
| 428 | 
            +
                    with(item: item) { 
         | 
| 429 | 
            +
                      li { emit item_component }
         | 
| 430 | 
            +
                    }
         | 
| 431 | 
            +
                  }
         | 
| 432 | 
            +
                }
         | 
| 433 | 
            +
              }
         | 
| 434 | 
            +
            }
         | 
| 435 | 
            +
             | 
| 436 | 
            +
            TodoItem = H {
         | 
| 437 | 
            +
              span item.text, class: item.completed ? 'completed' : 'pending'
         | 
| 438 | 
            +
            }
         | 
| 439 | 
            +
             | 
| 440 | 
            +
            def todo_list(items)
         | 
| 441 | 
            +
              H {
         | 
| 442 | 
            +
                div { List(items, TodoItem) }
         | 
| 443 | 
            +
              }
         | 
| 444 | 
            +
            end
         | 
| 445 | 
            +
            ```
         | 
| 446 | 
            +
             | 
| 447 | 
            +
            ## API Reference
         | 
| 351 448 |  | 
| 352 449 | 
             
            #### `Rubyoshka#initialize(**context, &block)` a.k.a. `Kernel#H`
         | 
| 353 450 |  | 
| @@ -379,6 +476,17 @@ an upper-case letter, it is considered a [component](#templates-as-components). | |
| 379 476 |  | 
| 380 477 | 
             
            If a text argument is given for a tag, it will be escaped.
         | 
| 381 478 |  | 
| 479 | 
            +
            #### `#cache(*vary, &block)`
         | 
| 480 | 
            +
             | 
| 481 | 
            +
            - `vary`: variables used in cached block. The given values will be used to
         | 
| 482 | 
            +
              create a separate cache entry.
         | 
| 483 | 
            +
            - `block`: inner HTML block
         | 
| 484 | 
            +
             | 
| 485 | 
            +
            Caches the markup in the given block, storing it in the Rubyoshka cache store.
         | 
| 486 | 
            +
            If a cache entry for the given block is found, it will be used instead of
         | 
| 487 | 
            +
            invoking the block. If one or more variables given, those will be used to create
         | 
| 488 | 
            +
            a separate cache entry.
         | 
| 489 | 
            +
             | 
| 382 490 | 
             
            #### `#context`
         | 
| 383 491 |  | 
| 384 492 | 
             
            Accesses the [global context](#global-context).
         | 
| @@ -409,3 +517,31 @@ Adds text without wrapping it in a tag. The text will be escaped. | |
| 409 517 |  | 
| 410 518 | 
             
            Sets a [local context](#local-context) for use inside the given block. The
         | 
| 411 519 | 
             
            previous local context will be restored upon exiting the given block.
         | 
| 520 | 
            +
             | 
| 521 | 
            +
            #### `Rubyoshka.cache`
         | 
| 522 | 
            +
             | 
| 523 | 
            +
            Returns the cache store. A cache store should implement two methods - `#[]` and
         | 
| 524 | 
            +
            `#[]=`. Here's an example implementing a Redis-based cache store:
         | 
| 525 | 
            +
             | 
| 526 | 
            +
            ```ruby
         | 
| 527 | 
            +
            class RedisTemplateCache
         | 
| 528 | 
            +
              def initialize(conn, prefix)
         | 
| 529 | 
            +
                @conn = conn
         | 
| 530 | 
            +
                @prefix = prefix
         | 
| 531 | 
            +
              end
         | 
| 532 | 
            +
             | 
| 533 | 
            +
              def [](key)
         | 
| 534 | 
            +
                @conn.get("#{prefix}:#{key}")
         | 
| 535 | 
            +
              end
         | 
| 536 | 
            +
             | 
| 537 | 
            +
              def []=(key, value)
         | 
| 538 | 
            +
                @conn.set("#{prefix}:#{key}", value)
         | 
| 539 | 
            +
              end
         | 
| 540 | 
            +
            end
         | 
| 541 | 
            +
             | 
| 542 | 
            +
            TEMPLATE_CACHE = RedisTemplaceCache.new(redis_conn, "templates:cache")
         | 
| 543 | 
            +
             | 
| 544 | 
            +
            def Rubyoshka.cache
         | 
| 545 | 
            +
              TEMPLATE_CACHE
         | 
| 546 | 
            +
            end
         | 
| 547 | 
            +
            ```
         | 
    
        data/lib/rubyoshka.rb
    CHANGED
    
    | @@ -1,204 +1,57 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require 'modulation/gem'
         | 
| 4 3 | 
             
            require 'escape_utils'
         | 
| 5 4 |  | 
| 6 | 
            -
             | 
| 5 | 
            +
            require_relative 'rubyoshka/renderer'
         | 
| 7 6 |  | 
| 8 7 | 
             
            # A Rubyoshka is a template representing a piece of HTML
         | 
| 9 8 | 
             
            class Rubyoshka
         | 
| 10 | 
            -
              # A Rendering is a rendering of a Rubyoshka
         | 
| 11 | 
            -
              class Rendering
         | 
| 12 | 
            -
                attr_reader :context
         | 
| 13 | 
            -
              
         | 
| 14 | 
            -
                # Initializes attributes and renders the given block
         | 
| 15 | 
            -
                # @param context [Hash] rendering context
         | 
| 16 | 
            -
                # @param block [Proc] template block
         | 
| 17 | 
            -
                # @return [void]
         | 
| 18 | 
            -
                def initialize(context, &block)
         | 
| 19 | 
            -
                  @context = context
         | 
| 20 | 
            -
                  @buffer = +''
         | 
| 21 | 
            -
                  instance_eval(&block)
         | 
| 22 | 
            -
                end
         | 
| 23 | 
            -
              
         | 
| 24 | 
            -
                # Returns the result of the rendering
         | 
| 25 | 
            -
                # @return [String]
         | 
| 26 | 
            -
                def to_s
         | 
| 27 | 
            -
                  @buffer
         | 
| 28 | 
            -
                end
         | 
| 29 | 
            -
              
         | 
| 30 | 
            -
                S_TAG_METHOD = <<~EOF
         | 
| 31 | 
            -
                  def %1$s(*args, &block)
         | 
| 32 | 
            -
                    tag(:%1$s, *args, &block)
         | 
| 33 | 
            -
                  end
         | 
| 34 | 
            -
                EOF
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                R_CONST_SYM = /^[A-Z]/
         | 
| 37 | 
            -
              
         | 
| 38 | 
            -
                # Catches undefined tag method call and handles them by defining the method
         | 
| 39 | 
            -
                # @param sym [Symbol] HTML tag or component identifier
         | 
| 40 | 
            -
                # @param args [Array] method call arguments
         | 
| 41 | 
            -
                # @param block [Proc] block passed to method call
         | 
| 42 | 
            -
                # @return [void]
         | 
| 43 | 
            -
                def method_missing(sym, *args, &block)
         | 
| 44 | 
            -
                  value = @local && @local[sym]
         | 
| 45 | 
            -
                  return value if value
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                  if sym =~ R_CONST_SYM
         | 
| 48 | 
            -
                    o = instance_eval(sym.to_s) rescue Rubyoshka.const_get(sym) \
         | 
| 49 | 
            -
                        rescue Object.const_get(sym)
         | 
| 50 | 
            -
                    case o
         | 
| 51 | 
            -
                    when ::Proc
         | 
| 52 | 
            -
                      self.class.define_method(sym) { |*a, &b| emit(o.(*a, &b)) }
         | 
| 53 | 
            -
                      emit(o.(*args, &block))
         | 
| 54 | 
            -
                    when Rubyoshka
         | 
| 55 | 
            -
                      self.class.define_method(sym) { |**ctx|
         | 
| 56 | 
            -
                        ctx.empty? ? emit(o) : with(ctx) { emit(o) }
         | 
| 57 | 
            -
                      }
         | 
| 58 | 
            -
                      ctx = args.first
         | 
| 59 | 
            -
                      Hash === ctx ? with(ctx) { emit(o) } : emit(o)
         | 
| 60 | 
            -
                    when ::String
         | 
| 61 | 
            -
                      @buffer << o
         | 
| 62 | 
            -
                    else
         | 
| 63 | 
            -
                      e = StandardError.new "Cannot render #{o.inspect}"
         | 
| 64 | 
            -
                      e.set_backtrace(caller)
         | 
| 65 | 
            -
                      raise e
         | 
| 66 | 
            -
                    end
         | 
| 67 | 
            -
                  else
         | 
| 68 | 
            -
                    self.class.class_eval(S_TAG_METHOD % sym)
         | 
| 69 | 
            -
                    tag(sym, *args, &block)
         | 
| 70 | 
            -
                  end
         | 
| 71 | 
            -
                end
         | 
| 72 | 
            -
              
         | 
| 73 | 
            -
                # Emits the given object into the rendering buffer
         | 
| 74 | 
            -
                # @param o [Proc, Rubyoshka, String] emitted object
         | 
| 75 | 
            -
                # @return [void]
         | 
| 76 | 
            -
                def emit(o)
         | 
| 77 | 
            -
                  case o
         | 
| 78 | 
            -
                  when ::Proc
         | 
| 79 | 
            -
                    instance_eval(&o)
         | 
| 80 | 
            -
                  when Rubyoshka
         | 
| 81 | 
            -
                    instance_eval(&o.block)
         | 
| 82 | 
            -
                  when nil
         | 
| 83 | 
            -
                  else
         | 
| 84 | 
            -
                    @buffer << o.to_s
         | 
| 85 | 
            -
                  end
         | 
| 86 | 
            -
                end
         | 
| 87 | 
            -
                alias_method :e, :emit
         | 
| 88 | 
            -
              
         | 
| 89 | 
            -
                S_LT              = '<'
         | 
| 90 | 
            -
                S_GT              = '>'
         | 
| 91 | 
            -
                S_LT_SLASH        = '</'
         | 
| 92 | 
            -
                S_SPACE_LT_SLASH  = ' </'
         | 
| 93 | 
            -
                S_SLASH_GT        = '/>'
         | 
| 94 | 
            -
                S_SPACE           = ' '
         | 
| 95 | 
            -
                S_EQUAL_QUOTE     = '="'
         | 
| 96 | 
            -
                S_QUOTE           = '"'
         | 
| 97 | 
            -
             | 
| 98 | 
            -
                E = EscapeUtils
         | 
| 99 | 
            -
              
         | 
| 100 | 
            -
                # Emits an HTML tag
         | 
| 101 | 
            -
                # @param sym [Symbol] HTML tag
         | 
| 102 | 
            -
                # @param text [String] text content of tag
         | 
| 103 | 
            -
                # @param props [Hash] tag attributes
         | 
| 104 | 
            -
                # @param block [Proc] nested HTML block
         | 
| 105 | 
            -
                # @return [void]
         | 
| 106 | 
            -
                def tag(sym, text = nil, **props, &block)
         | 
| 107 | 
            -
                  sym = sym.to_s
         | 
| 108 | 
            -
              
         | 
| 109 | 
            -
                  @buffer << S_LT << sym
         | 
| 110 | 
            -
                  emit_props(props) unless props.empty?
         | 
| 111 | 
            -
              
         | 
| 112 | 
            -
                  if block
         | 
| 113 | 
            -
                    @buffer << S_GT
         | 
| 114 | 
            -
                    instance_eval(&block)
         | 
| 115 | 
            -
                    @buffer << S_LT_SLASH << sym << S_GT
         | 
| 116 | 
            -
                  elsif Rubyoshka === text
         | 
| 117 | 
            -
                    @buffer << S_GT
         | 
| 118 | 
            -
                    emit(text)
         | 
| 119 | 
            -
                    @buffer << S_LT_SLASH << sym << S_GT
         | 
| 120 | 
            -
                  elsif text
         | 
| 121 | 
            -
                    @buffer << S_GT << E.escape_html(text.to_s) <<
         | 
| 122 | 
            -
                      S_LT_SLASH << sym << S_GT
         | 
| 123 | 
            -
                  else
         | 
| 124 | 
            -
                    @buffer << S_SLASH_GT
         | 
| 125 | 
            -
                  end
         | 
| 126 | 
            -
                end
         | 
| 127 | 
            -
             | 
| 128 | 
            -
                # Emits tag attributes into the rendering buffer
         | 
| 129 | 
            -
                # @param props [Hash] tag attributes
         | 
| 130 | 
            -
                # @return [void]
         | 
| 131 | 
            -
                def emit_props(props)
         | 
| 132 | 
            -
                  props.each { |k, v|
         | 
| 133 | 
            -
                    case k
         | 
| 134 | 
            -
                    when :text
         | 
| 135 | 
            -
                    when :src, :href
         | 
| 136 | 
            -
                      @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
         | 
| 137 | 
            -
                        E.escape_uri(v) << S_QUOTE
         | 
| 138 | 
            -
                    else
         | 
| 139 | 
            -
                      if v == true
         | 
| 140 | 
            -
                        @buffer << S_SPACE << k.to_s
         | 
| 141 | 
            -
                      else
         | 
| 142 | 
            -
                        @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE << v << S_QUOTE
         | 
| 143 | 
            -
                      end
         | 
| 144 | 
            -
                    end
         | 
| 145 | 
            -
                  }
         | 
| 146 | 
            -
                end
         | 
| 147 | 
            -
             | 
| 148 | 
            -
                # Emits the p tag
         | 
| 149 | 
            -
                # @param text [String] text content of tag
         | 
| 150 | 
            -
                # @param props [Hash] tag attributes
         | 
| 151 | 
            -
                # @para block [Proc] nested HTML block
         | 
| 152 | 
            -
                # @return [void]
         | 
| 153 | 
            -
                def p(text = nil, **props, &block)
         | 
| 154 | 
            -
                  tag(:p, text, **props, &block)
         | 
| 155 | 
            -
                end
         | 
| 156 | 
            -
              
         | 
| 157 | 
            -
                S_HTML5_DOCTYPE = '<!DOCTYPE html>'
         | 
| 158 | 
            -
              
         | 
| 159 | 
            -
                # Emits an HTML5 doctype tag and an html tag with the given block
         | 
| 160 | 
            -
                # @param block [Proc] nested HTML block
         | 
| 161 | 
            -
                # @return [void]
         | 
| 162 | 
            -
                def html5(&block)
         | 
| 163 | 
            -
                  @buffer << S_HTML5_DOCTYPE
         | 
| 164 | 
            -
                  self.html(&block)
         | 
| 165 | 
            -
                end
         | 
| 166 | 
            -
             | 
| 167 | 
            -
                # Emits text into the rendering buffer
         | 
| 168 | 
            -
                # @param data [String] text
         | 
| 169 | 
            -
                def text(data)
         | 
| 170 | 
            -
                  @buffer << E.escape_html(data)
         | 
| 171 | 
            -
                end
         | 
| 172 | 
            -
             | 
| 173 | 
            -
                # Sets a local context for the given block
         | 
| 174 | 
            -
                # @param ctx [Hash] context hash
         | 
| 175 | 
            -
                # @param block [Proc] nested HTML block
         | 
| 176 | 
            -
                # @return [void]
         | 
| 177 | 
            -
                def with(**ctx, &block)
         | 
| 178 | 
            -
                  old_local, @local = @local, ctx
         | 
| 179 | 
            -
                  instance_eval(&block)
         | 
| 180 | 
            -
                ensure
         | 
| 181 | 
            -
                  @local = old_local
         | 
| 182 | 
            -
                end
         | 
| 183 | 
            -
              end
         | 
| 184 | 
            -
             | 
| 185 9 | 
             
              attr_reader :block
         | 
| 186 10 |  | 
| 187 11 | 
             
              # Initializes a Rubyoshka with the given block
         | 
| 188 12 | 
             
              # @param ctx [Hash] local context
         | 
| 189 13 | 
             
              # @param block [Proc] nested HTML block
         | 
| 190 14 | 
             
              # @param [void]
         | 
| 191 | 
            -
              def initialize(**ctx, &block)
         | 
| 192 | 
            -
                @ | 
| 15 | 
            +
              def initialize(mode: :html, **ctx, &block)
         | 
| 16 | 
            +
                @mode = mode
         | 
| 17 | 
            +
                @block = ctx.empty? ? block : proc { with(**ctx, &block) }
         | 
| 193 18 | 
             
              end
         | 
| 194 19 |  | 
| 20 | 
            +
              H_EMPTY = {}.freeze
         | 
| 21 | 
            +
             | 
| 195 22 | 
             
              # Renders the associated block and returns the string result
         | 
| 196 23 | 
             
              # @param context [Hash] context
         | 
| 197 24 | 
             
              # @return [String]
         | 
| 198 | 
            -
              def render( | 
| 199 | 
            -
                 | 
| 25 | 
            +
              def render(context = H_EMPTY)
         | 
| 26 | 
            +
                renderer_class.new(context, &block).to_s
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              def renderer_class
         | 
| 30 | 
            +
                case @mode
         | 
| 31 | 
            +
                when :html
         | 
| 32 | 
            +
                  HTMLRenderer
         | 
| 33 | 
            +
                when :xml
         | 
| 34 | 
            +
                  XMLRenderer
         | 
| 35 | 
            +
                else
         | 
| 36 | 
            +
                  raise "Invalid mode #{@mode.inspect}"
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              @@cache = {}
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              def self.cache
         | 
| 43 | 
            +
                @@cache
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              def self.component(&block)
         | 
| 47 | 
            +
                proc { |*args| new { instance_exec(*args, &block) } }
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              def self.xml(**ctx, &block)
         | 
| 51 | 
            +
                new(mode: :xml, **ctx, &block)
         | 
| 200 52 | 
             
              end
         | 
| 201 53 | 
             
            end
         | 
| 54 | 
            +
            ::H = Rubyoshka
         | 
| 202 55 |  | 
| 203 56 | 
             
            module ::Kernel
         | 
| 204 57 | 
             
              # Convenience method for creating a new Rubyoshka
         | 
| @@ -206,6 +59,6 @@ module ::Kernel | |
| 206 59 | 
             
              # @param block [Proc] nested block
         | 
| 207 60 | 
             
              # @return [Rubyoshka] Rubyoshka template
         | 
| 208 61 | 
             
              def H(**ctx, &block)
         | 
| 209 | 
            -
                Rubyoshka.new(ctx, &block)
         | 
| 62 | 
            +
                Rubyoshka.new(**ctx, &block)
         | 
| 210 63 | 
             
              end
         | 
| 211 64 | 
             
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative './html'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class Rubyoshka
         | 
| 6 | 
            +
              # Markup extensions 
         | 
| 7 | 
            +
              module HTML
         | 
| 8 | 
            +
                # Emits the p tag (overrides Object#p)
         | 
| 9 | 
            +
                # @param text [String] text content of tag
         | 
| 10 | 
            +
                # @param props [Hash] tag attributes
         | 
| 11 | 
            +
                # @para block [Proc] nested HTML block
         | 
| 12 | 
            +
                # @return [void]
         | 
| 13 | 
            +
                def p(text = nil, **props, &block)
         | 
| 14 | 
            +
                  method_missing(:p, text, **props, &block)
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              
         | 
| 17 | 
            +
                S_HTML5_DOCTYPE = '<!DOCTYPE html>'
         | 
| 18 | 
            +
              
         | 
| 19 | 
            +
                # Emits an HTML5 doctype tag and an html tag with the given block
         | 
| 20 | 
            +
                # @param block [Proc] nested HTML block
         | 
| 21 | 
            +
                # @return [void]
         | 
| 22 | 
            +
                def html5(&block)
         | 
| 23 | 
            +
                  @buffer << S_HTML5_DOCTYPE
         | 
| 24 | 
            +
                  self.html(&block)
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def link_stylesheet(href, custom_attributes = nil)
         | 
| 28 | 
            +
                  attributes = {
         | 
| 29 | 
            +
                    rel: 'stylesheet',
         | 
| 30 | 
            +
                    href: href
         | 
| 31 | 
            +
                  }
         | 
| 32 | 
            +
                  if custom_attributes
         | 
| 33 | 
            +
                    attributes = custom_attributes.merge(attributes)
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                  link(**attributes)
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
            end
         | 
| @@ -0,0 +1,198 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative './html'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class Rubyoshka
         | 
| 6 | 
            +
              # A Renderer is a rendering of a Rubyoshka
         | 
| 7 | 
            +
              class Renderer
         | 
| 8 | 
            +
                attr_reader :context
         | 
| 9 | 
            +
              
         | 
| 10 | 
            +
                # Initializes attributes and renders the given block
         | 
| 11 | 
            +
                # @param context [Hash] rendering context
         | 
| 12 | 
            +
                # @param block [Proc] template block
         | 
| 13 | 
            +
                # @return [void]
         | 
| 14 | 
            +
                def initialize(context, &block)
         | 
| 15 | 
            +
                  @context = context
         | 
| 16 | 
            +
                  @buffer = +''
         | 
| 17 | 
            +
                  instance_eval(&block)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              
         | 
| 20 | 
            +
                # Returns the result of the rendering
         | 
| 21 | 
            +
                # @return [String]
         | 
| 22 | 
            +
                def to_s
         | 
| 23 | 
            +
                  @buffer
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def escape_text(text)
         | 
| 27 | 
            +
                  raise NotImplementedError
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def escape_uri(uri)
         | 
| 31 | 
            +
                  EscapeUtils.escape_uri(v)
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                S_TAG_METHOD_LINE = __LINE__ + 1
         | 
| 35 | 
            +
                S_TAG_METHOD = <<~EOF
         | 
| 36 | 
            +
                  S_TAG_%<TAG>s_PRE = '<%<tag>s'
         | 
| 37 | 
            +
                  S_TAG_%<TAG>s_CLOSE = '</%<tag>s>'
         | 
| 38 | 
            +
                  
         | 
| 39 | 
            +
                  def %<tag>s(text = nil, **props, &block)
         | 
| 40 | 
            +
                    @buffer << S_TAG_%<TAG>s_PRE
         | 
| 41 | 
            +
                    emit_props(props) unless props.empty?
         | 
| 42 | 
            +
                  
         | 
| 43 | 
            +
                    if block
         | 
| 44 | 
            +
                      @buffer << S_GT
         | 
| 45 | 
            +
                      instance_eval(&block)
         | 
| 46 | 
            +
                      @buffer << S_TAG_%<TAG>s_CLOSE
         | 
| 47 | 
            +
                    elsif Rubyoshka === text
         | 
| 48 | 
            +
                      @buffer << S_GT
         | 
| 49 | 
            +
                      emit(text)
         | 
| 50 | 
            +
                      @buffer << S_TAG_%<TAG>s_CLOSE
         | 
| 51 | 
            +
                    elsif text
         | 
| 52 | 
            +
                      @buffer << S_GT << escape_text(text.to_s) << S_TAG_%<TAG>s_CLOSE
         | 
| 53 | 
            +
                    else
         | 
| 54 | 
            +
                      @buffer << S_SLASH_GT
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
                EOF
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                R_CONST_SYM = /^[A-Z]/
         | 
| 60 | 
            +
              
         | 
| 61 | 
            +
                # Catches undefined tag method call and handles them by defining the method
         | 
| 62 | 
            +
                # @param sym [Symbol] HTML tag or component identifier
         | 
| 63 | 
            +
                # @param args [Array] method call arguments
         | 
| 64 | 
            +
                # @param block [Proc] block passed to method call
         | 
| 65 | 
            +
                # @return [void]
         | 
| 66 | 
            +
                def method_missing(sym, *args, **opts, &block)
         | 
| 67 | 
            +
                  value = @local && @local[sym]
         | 
| 68 | 
            +
                  return value if value
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  if sym =~ R_CONST_SYM
         | 
| 71 | 
            +
                    # Component reference (capitalized method name)
         | 
| 72 | 
            +
                    o = instance_eval(sym.to_s) rescue Rubyoshka.const_get(sym) \
         | 
| 73 | 
            +
                        rescue Object.const_get(sym)
         | 
| 74 | 
            +
                    case o
         | 
| 75 | 
            +
                    when ::Proc
         | 
| 76 | 
            +
                      self.class.define_method(sym) { |*a, **c, &b| emit(o.(*a, **c, &b)) }
         | 
| 77 | 
            +
                      STDOUT.puts({o: o, args: args, opts: opts, block: block}.inspect)
         | 
| 78 | 
            +
                      emit(o.(*args, **opts, &block))
         | 
| 79 | 
            +
                    when Rubyoshka
         | 
| 80 | 
            +
                      self.class.define_method(sym) do |**ctx|
         | 
| 81 | 
            +
                        ctx.empty? ? emit(o) : with(**ctx) { emit(o) }
         | 
| 82 | 
            +
                      end
         | 
| 83 | 
            +
                      Hash === opts.empty? ? emit(o) : with(**opts) { emit(o) }
         | 
| 84 | 
            +
                    when ::String
         | 
| 85 | 
            +
                      @buffer << o
         | 
| 86 | 
            +
                    else
         | 
| 87 | 
            +
                      e = StandardError.new "Cannot render #{o.inspect}"
         | 
| 88 | 
            +
                      e.set_backtrace(caller)
         | 
| 89 | 
            +
                      raise e
         | 
| 90 | 
            +
                    end
         | 
| 91 | 
            +
                  else
         | 
| 92 | 
            +
                    tag = sym.to_s
         | 
| 93 | 
            +
                    code = S_TAG_METHOD % { tag: tag, TAG: tag.upcase }
         | 
| 94 | 
            +
                    self.class.class_eval(code, __FILE__, S_TAG_METHOD_LINE)
         | 
| 95 | 
            +
                    send(sym, *args, **opts, &block)
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
              
         | 
| 99 | 
            +
                # Emits the given object into the rendering buffer
         | 
| 100 | 
            +
                # @param o [Proc, Rubyoshka, Module, String] emitted object
         | 
| 101 | 
            +
                # @return [void]
         | 
| 102 | 
            +
                def emit(o)
         | 
| 103 | 
            +
                  case o
         | 
| 104 | 
            +
                  when ::Proc
         | 
| 105 | 
            +
                    instance_eval(&o)
         | 
| 106 | 
            +
                  when Rubyoshka
         | 
| 107 | 
            +
                    instance_eval(&o.block)
         | 
| 108 | 
            +
                  when Module
         | 
| 109 | 
            +
                    # If module is given, the component is expected to be a const inside the module
         | 
| 110 | 
            +
                    emit(o::Component)
         | 
| 111 | 
            +
                  when nil
         | 
| 112 | 
            +
                  else
         | 
| 113 | 
            +
                    @buffer << o.to_s
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
                alias_method :e, :emit
         | 
| 117 | 
            +
              
         | 
| 118 | 
            +
                S_LT              = '<'
         | 
| 119 | 
            +
                S_GT              = '>'
         | 
| 120 | 
            +
                S_LT_SLASH        = '</'
         | 
| 121 | 
            +
                S_SPACE_LT_SLASH  = ' </'
         | 
| 122 | 
            +
                S_SLASH_GT        = '/>'
         | 
| 123 | 
            +
                S_SPACE           = ' '
         | 
| 124 | 
            +
                S_EQUAL_QUOTE     = '="'
         | 
| 125 | 
            +
                S_QUOTE           = '"'
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                # Emits tag attributes into the rendering buffer
         | 
| 128 | 
            +
                # @param props [Hash] tag attributes
         | 
| 129 | 
            +
                # @return [void]
         | 
| 130 | 
            +
                def emit_props(props)
         | 
| 131 | 
            +
                  props.each { |k, v|
         | 
| 132 | 
            +
                    case k
         | 
| 133 | 
            +
                    when :src, :href
         | 
| 134 | 
            +
                      @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE <<
         | 
| 135 | 
            +
                        EscapeUtils.escape_uri(v) << S_QUOTE
         | 
| 136 | 
            +
                    else
         | 
| 137 | 
            +
                      case v
         | 
| 138 | 
            +
                      when true
         | 
| 139 | 
            +
                        @buffer << S_SPACE << k.to_s
         | 
| 140 | 
            +
                      when false, nil
         | 
| 141 | 
            +
                        # emit nothing
         | 
| 142 | 
            +
                      else
         | 
| 143 | 
            +
                        @buffer << S_SPACE << k.to_s << S_EQUAL_QUOTE << v << S_QUOTE
         | 
| 144 | 
            +
                      end
         | 
| 145 | 
            +
                    end
         | 
| 146 | 
            +
                  }
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                # Emits text into the rendering buffer
         | 
| 150 | 
            +
                # @param data [String] text
         | 
| 151 | 
            +
                def text(data)
         | 
| 152 | 
            +
                  @buffer << escape_text(data)
         | 
| 153 | 
            +
                end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                # Sets a local context for the given block
         | 
| 156 | 
            +
                # @param ctx [Hash] context hash
         | 
| 157 | 
            +
                # @param block [Proc] nested HTML block
         | 
| 158 | 
            +
                # @return [void]
         | 
| 159 | 
            +
                def with(**ctx, &block)
         | 
| 160 | 
            +
                  old_local, @local = @local, ctx
         | 
| 161 | 
            +
                  instance_eval(&block)
         | 
| 162 | 
            +
                ensure
         | 
| 163 | 
            +
                  @local = old_local
         | 
| 164 | 
            +
                end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                # Caches the given block with the given arguments as cache key
         | 
| 167 | 
            +
                # @param vary [*Object] cache key
         | 
| 168 | 
            +
                # @param block [Proc] nested HTML block
         | 
| 169 | 
            +
                # @return [void]
         | 
| 170 | 
            +
                def cache(*vary, **opts, &block)
         | 
| 171 | 
            +
                  key = [block.source_location.hash, vary.hash, opts.hash]
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                  if (cached = Rubyoshka.cache[key])
         | 
| 174 | 
            +
                    @buffer << cached
         | 
| 175 | 
            +
                    return
         | 
| 176 | 
            +
                  end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                  cache_pos = @buffer.length
         | 
| 179 | 
            +
                  instance_eval(&block)
         | 
| 180 | 
            +
                  diff = @buffer[cache_pos..-1]
         | 
| 181 | 
            +
                  Rubyoshka.cache[key] = diff
         | 
| 182 | 
            +
                end
         | 
| 183 | 
            +
              end
         | 
| 184 | 
            +
             | 
| 185 | 
            +
              class HTMLRenderer < Renderer
         | 
| 186 | 
            +
                include HTML
         | 
| 187 | 
            +
                
         | 
| 188 | 
            +
                def escape_text(text)
         | 
| 189 | 
            +
                  EscapeUtils.escape_html(text.to_s)
         | 
| 190 | 
            +
                end
         | 
| 191 | 
            +
              end
         | 
| 192 | 
            +
             | 
| 193 | 
            +
              class XMLRenderer < Renderer
         | 
| 194 | 
            +
                def escape_text(text)
         | 
| 195 | 
            +
                  EscapeUtils.escape_xml(text.to_s)
         | 
| 196 | 
            +
                end
         | 
| 197 | 
            +
              end
         | 
| 198 | 
            +
            end
         | 
    
        data/lib/rubyoshka/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,29 +1,15 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: rubyoshka
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version:  | 
| 4 | 
            +
              version: 0.6.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Sharon Rosner
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2021-03-03 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 | 
            -
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            -
              name: modulation
         | 
| 15 | 
            -
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            -
                requirements:
         | 
| 17 | 
            -
                - - '='
         | 
| 18 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            -
                    version: '0.18'
         | 
| 20 | 
            -
              type: :runtime
         | 
| 21 | 
            -
              prerelease: false
         | 
| 22 | 
            -
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            -
                requirements:
         | 
| 24 | 
            -
                - - '='
         | 
| 25 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            -
                    version: '0.18'
         | 
| 27 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 28 14 | 
             
              name: escape_utils
         | 
| 29 15 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -80,6 +66,20 @@ dependencies: | |
| 80 66 | 
             
                - - '='
         | 
| 81 67 | 
             
                  - !ruby/object:Gem::Version
         | 
| 82 68 | 
             
                    version: 2.7.0
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: tilt
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - '='
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: 2.0.9
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - '='
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: 2.0.9
         | 
| 83 83 | 
             
            description: 
         | 
| 84 84 | 
             
            email: ciconia@gmail.com
         | 
| 85 85 | 
             
            executables: []
         | 
| @@ -90,6 +90,8 @@ files: | |
| 90 90 | 
             
            - CHANGELOG.md
         | 
| 91 91 | 
             
            - README.md
         | 
| 92 92 | 
             
            - lib/rubyoshka.rb
         | 
| 93 | 
            +
            - lib/rubyoshka/html.rb
         | 
| 94 | 
            +
            - lib/rubyoshka/renderer.rb
         | 
| 93 95 | 
             
            - lib/rubyoshka/version.rb
         | 
| 94 96 | 
             
            homepage: http://github.com/digital-fabric/rubyoshka
         | 
| 95 97 | 
             
            licenses:
         | 
| @@ -108,14 +110,14 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 108 110 | 
             
              requirements:
         | 
| 109 111 | 
             
              - - ">="
         | 
| 110 112 | 
             
                - !ruby/object:Gem::Version
         | 
| 111 | 
            -
                  version: ' | 
| 113 | 
            +
                  version: '2.7'
         | 
| 112 114 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 113 115 | 
             
              requirements:
         | 
| 114 116 | 
             
              - - ">="
         | 
| 115 117 | 
             
                - !ruby/object:Gem::Version
         | 
| 116 118 | 
             
                  version: '0'
         | 
| 117 119 | 
             
            requirements: []
         | 
| 118 | 
            -
            rubygems_version: 3. | 
| 120 | 
            +
            rubygems_version: 3.1.4
         | 
| 119 121 | 
             
            signing_key: 
         | 
| 120 122 | 
             
            specification_version: 4
         | 
| 121 123 | 
             
            summary: 'Rubyoshka: composable HTML templating for Ruby'
         |