yarrow 0.5.0 → 0.6.3
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/.github/workflows/ruby.yml +23 -0
 - data/.gitignore +2 -1
 - data/README.md +13 -19
 - data/Rakefile +15 -1
 - data/lib/yarrow.rb +17 -6
 - data/lib/yarrow/config.rb +59 -0
 - data/lib/yarrow/configuration.rb +35 -63
 - data/lib/yarrow/content/collection_expander.rb +218 -0
 - data/lib/yarrow/content/content_type.rb +42 -0
 - data/lib/yarrow/content/graph.rb +13 -21
 - data/lib/yarrow/content/source.rb +11 -0
 - data/lib/yarrow/extensions.rb +1 -0
 - data/lib/yarrow/extensions/mementus.rb +24 -0
 - data/lib/yarrow/output/context.rb +0 -4
 - data/lib/yarrow/output/generator.rb +2 -2
 - data/lib/yarrow/output/web/indexed_file.rb +39 -0
 - data/lib/yarrow/process/expand_content.rb +12 -0
 - data/lib/yarrow/process/extract_source.rb +12 -0
 - data/lib/yarrow/process/project_manifest.rb +20 -0
 - data/lib/yarrow/process/step_processor.rb +43 -0
 - data/lib/yarrow/process/workflow.rb +36 -0
 - data/lib/yarrow/schema.rb +132 -0
 - data/lib/yarrow/schema/validations/array.rb +0 -0
 - data/lib/yarrow/schema/validations/object.rb +0 -0
 - data/lib/yarrow/schema/validations/string.rb +0 -0
 - data/lib/yarrow/server.rb +8 -5
 - data/lib/yarrow/source/graph.rb +6 -0
 - data/lib/yarrow/symbols.rb +19 -0
 - data/lib/yarrow/tools/content_utils.rb +74 -0
 - data/lib/yarrow/tools/front_matter.rb +4 -2
 - data/lib/yarrow/version.rb +3 -2
 - data/lib/yarrow/web/html_document.rb +9 -0
 - data/lib/yarrow/web/manifest.rb +9 -0
 - data/lib/yarrow/web/static_asset.rb +9 -0
 - data/lib/yarrow/web/template.rb +9 -0
 - data/yarrow.gemspec +6 -7
 - metadata +52 -48
 - data/.travis.yml +0 -16
 - data/lib/yarrow/html.rb +0 -1
 - data/lib/yarrow/html/asset_tags.rb +0 -59
 - data/lib/yarrow/html/content_tags.rb +0 -7
 - data/lib/yarrow/tools/output_file.rb +0 -40
 
| 
         @@ -0,0 +1,42 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            gem "strings-inflection"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Yarrow
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Content
         
     | 
| 
      
 5 
     | 
    
         
            +
                class ContentType
         
     | 
| 
      
 6 
     | 
    
         
            +
                  Value = Yarrow::Schema::Value.new(:collection, :entity, :extensions)
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  DEFAULT_EXTENSIONS = [".md", ".yml", ".htm"]
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def self.from_name(name)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    new(Value.new(collection: name.to_sym))
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def initialize(properties)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    unless properties.respond_to?(:collection) || properties.respond_to?(:entity)
         
     | 
| 
      
 16 
     | 
    
         
            +
                      raise "Must provide a collection name or entity name"
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    @properties = properties
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def collection
         
     | 
| 
      
 23 
     | 
    
         
            +
                    return @properties.collection if @properties.collection
         
     | 
| 
      
 24 
     | 
    
         
            +
                    Yarrow::Symbols.to_plural(@properties.entity)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def entity
         
     | 
| 
      
 28 
     | 
    
         
            +
                    return @properties.entity if @properties.entity
         
     | 
| 
      
 29 
     | 
    
         
            +
                    Yarrow::Symbols.to_singular(@properties.collection)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  def extensions
         
     | 
| 
      
 33 
     | 
    
         
            +
                    return @properties.extensions if @properties.extensions
         
     | 
| 
      
 34 
     | 
    
         
            +
                    DEFAULT_EXTENSIONS
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  def match_path
         
     | 
| 
      
 38 
     | 
    
         
            +
                    "."
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/yarrow/content/graph.rb
    CHANGED
    
    | 
         @@ -1,22 +1,22 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            require "mementus"
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
     | 
    
         
            -
            module Mementus
         
     | 
| 
       4 
     | 
    
         
            -
              module Pipeline
         
     | 
| 
       5 
     | 
    
         
            -
                class Step
         
     | 
| 
       6 
     | 
    
         
            -
                  # Monkeypatch extension to ensure each pipeline step supports enumerable
         
     | 
| 
       7 
     | 
    
         
            -
                  # methods. Mostly used for #map. API needs to be fixed in the gem itself.
         
     | 
| 
       8 
     | 
    
         
            -
                  include Enumerable
         
     | 
| 
       9 
     | 
    
         
            -
                end
         
     | 
| 
       10 
     | 
    
         
            -
              end
         
     | 
| 
       11 
     | 
    
         
            -
            end
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
1 
     | 
    
         
             
            module Yarrow
         
     | 
| 
       14 
2 
     | 
    
         
             
              module Content
         
     | 
| 
       15 
3 
     | 
    
         
             
                # A directed graph of every element of content in the project.
         
     | 
| 
       16 
4 
     | 
    
         
             
                class Graph
         
     | 
| 
       17 
5 
     | 
    
         
             
                  # Construct a graph collected from source content files.
         
     | 
| 
       18 
6 
     | 
    
         
             
                  def self.from_source(config)
         
     | 
| 
       19 
     | 
    
         
            -
                    new(SourceCollector.collect(config. 
     | 
| 
      
 7 
     | 
    
         
            +
                    new(SourceCollector.collect(config.source), config)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  attr_reader :graph, :config
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def initialize(graph, config)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @graph = graph
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @config = config
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def expand_pages
         
     | 
| 
      
 18 
     | 
    
         
            +
                    expander = Yarrow::Content::CollectionExpander.new
         
     | 
| 
      
 19 
     | 
    
         
            +
                    expander.expand(graph)
         
     | 
| 
       20 
20 
     | 
    
         
             
                  end
         
     | 
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
                  # List of source files.
         
     | 
| 
         @@ -28,14 +28,6 @@ module Yarrow 
     | 
|
| 
       28 
28 
     | 
    
         
             
                  def directories
         
     | 
| 
       29 
29 
     | 
    
         
             
                    graph.nodes(:directory)
         
     | 
| 
       30 
30 
     | 
    
         
             
                  end
         
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
                  private
         
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
                  def initialize(graph)
         
     | 
| 
       35 
     | 
    
         
            -
                    @graph = graph
         
     | 
| 
       36 
     | 
    
         
            -
                  end
         
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
                  attr_reader :graph
         
     | 
| 
       39 
31 
     | 
    
         
             
                end
         
     | 
| 
       40 
32 
     | 
    
         
             
              end
         
     | 
| 
       41 
33 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "yarrow/extensions/mementus"
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "mementus"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Mementus
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Pipeline
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Step
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # Monkeypatch extension to ensure each pipeline step supports enumerable
         
     | 
| 
      
 7 
     | 
    
         
            +
                  # methods. Mostly used for #map. API needs to be fixed in the gem itself.
         
     | 
| 
      
 8 
     | 
    
         
            +
                  include Enumerable
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
              module Structure
         
     | 
| 
      
 12 
     | 
    
         
            +
                class IncidenceList
         
     | 
| 
      
 13 
     | 
    
         
            +
                  def inspect
         
     | 
| 
      
 14 
     | 
    
         
            +
                    "<Mementus::Structure::IncidenceList>"
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
              class Graph
         
     | 
| 
      
 19 
     | 
    
         
            +
                def inspect
         
     | 
| 
      
 20 
     | 
    
         
            +
                  "<Mementus::Graph @structure=#{@structure.inspect} " +
         
     | 
| 
      
 21 
     | 
    
         
            +
                    "nodes_count=#{nodes_count} edges_count=#{edges_count}>"
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -4,11 +4,7 @@ module Yarrow 
     | 
|
| 
       4 
4 
     | 
    
         
             
                #
         
     | 
| 
       5 
5 
     | 
    
         
             
                # Methods provided by this class become available as named variables in
         
     | 
| 
       6 
6 
     | 
    
         
             
                # Mustache templates.
         
     | 
| 
       7 
     | 
    
         
            -
                #
         
     | 
| 
       8 
     | 
    
         
            -
                # Includes the library of helpers for dynamically generating HTML tags.
         
     | 
| 
       9 
7 
     | 
    
         
             
                class Context
         
     | 
| 
       10 
     | 
    
         
            -
                  include Yarrow::HTML::AssetTags
         
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
8 
     | 
    
         
             
                  def initialize(attributes)
         
     | 
| 
       13 
9 
     | 
    
         
             
                    metaclass = class << self; self; end
         
     | 
| 
       14 
10 
     | 
    
         
             
                    attributes.each do |name, value|
         
     | 
| 
         @@ -8,12 +8,12 @@ module Yarrow 
     | 
|
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         
             
                  # Mapping between template types and provided object model
         
     | 
| 
       10 
10 
     | 
    
         
             
                  def object_map
         
     | 
| 
       11 
     | 
    
         
            -
                    @config 
     | 
| 
      
 11 
     | 
    
         
            +
                    @config[:output][:object_map]
         
     | 
| 
       12 
12 
     | 
    
         
             
                  end
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
                  # Mapping between template types and provided output templates.
         
     | 
| 
       15 
15 
     | 
    
         
             
                  def template_map
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
       17 
17 
     | 
    
         
             
                  end
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
                  # Template converter used by this generator instance.
         
     | 
| 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Yarrow
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Output
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Web
         
     | 
| 
      
 4 
     | 
    
         
            +
                  class IndexedFile
         
     | 
| 
      
 5 
     | 
    
         
            +
                    WRITE_MODE = 'w+:UTF-8'.freeze
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                    # @return [String] Basename reflecting the server convention (usually: index.html)
         
     | 
| 
      
 8 
     | 
    
         
            +
                    def index_name
         
     | 
| 
      
 9 
     | 
    
         
            +
                      @index_name ||= config.index_name || 'index.html'
         
     | 
| 
      
 10 
     | 
    
         
            +
                    end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                    # @return [String] Docroot of the output target
         
     | 
| 
      
 13 
     | 
    
         
            +
                    def docroot
         
     | 
| 
      
 14 
     | 
    
         
            +
                      @docroot ||= config.output_dir || 'public'
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    # Write an output file to the specified path under the docroot.
         
     | 
| 
      
 18 
     | 
    
         
            +
                    #
         
     | 
| 
      
 19 
     | 
    
         
            +
                    # @param path [String]
         
     | 
| 
      
 20 
     | 
    
         
            +
                    # @param content [String]
         
     | 
| 
      
 21 
     | 
    
         
            +
                    def write(path, content)
         
     | 
| 
      
 22 
     | 
    
         
            +
                      # If the target path is a directory,
         
     | 
| 
      
 23 
     | 
    
         
            +
                      # generate a default index filename.
         
     | 
| 
      
 24 
     | 
    
         
            +
                      if path[path.length-1] == '/'
         
     | 
| 
      
 25 
     | 
    
         
            +
                        path = "#{path}#{index_name}"
         
     | 
| 
      
 26 
     | 
    
         
            +
                      end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                      target_path = Pathname.new("#{docroot}#{path}")
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                      FileUtils.mkdir_p(target_path.dirname)
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                      File.open(target_path.to_s, WRITE_MODE) do |file|
         
     | 
| 
      
 33 
     | 
    
         
            +
                        file.puts(content)
         
     | 
| 
      
 34 
     | 
    
         
            +
                      end
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Yarrow
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Process
         
     | 
| 
      
 3 
     | 
    
         
            +
                class ProjectManifest < StepProcessor
         
     | 
| 
      
 4 
     | 
    
         
            +
                  accepts String
         
     | 
| 
      
 5 
     | 
    
         
            +
                  provides String
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  def before_step(content)
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def step(content)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    "#{content} | ProjectManifest::Result"
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def after_step(content)
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Yarrow
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Process
         
     | 
| 
      
 3 
     | 
    
         
            +
                class StepProcessor
         
     | 
| 
      
 4 
     | 
    
         
            +
                  attr_reader :source
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  class << self
         
     | 
| 
      
 7 
     | 
    
         
            +
                    attr_reader :accepted_input, :provided_output
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                    def accepts(input_const)
         
     | 
| 
      
 10 
     | 
    
         
            +
                      @accepted_input = input_const.to_s
         
     | 
| 
      
 11 
     | 
    
         
            +
                    end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                    def provides(output_const)
         
     | 
| 
      
 14 
     | 
    
         
            +
                      @provided_output = output_const.to_s
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def initialize
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @source = nil
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def accepts
         
     | 
| 
      
 23 
     | 
    
         
            +
                    self.class.accepted_input
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  def provides
         
     | 
| 
      
 27 
     | 
    
         
            +
                    self.class.provided_output
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def can_accept?(provided)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    accepts == provided
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  def process(source)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    # begin
         
     | 
| 
      
 36 
     | 
    
         
            +
                    result = step(source)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    # log.info("<Result source=#{result}>")
         
     | 
| 
      
 38 
     | 
    
         
            +
                    # rescue
         
     | 
| 
      
 39 
     | 
    
         
            +
                    result
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Yarrow
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Process
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Workflow
         
     | 
| 
      
 4 
     | 
    
         
            +
                  def initialize(input)
         
     | 
| 
      
 5 
     | 
    
         
            +
                    @input = input
         
     | 
| 
      
 6 
     | 
    
         
            +
                    @processors = []
         
     | 
| 
      
 7 
     | 
    
         
            +
                  end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def connect(processor)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    provided_input = if @processors.any?
         
     | 
| 
      
 11 
     | 
    
         
            +
                      @processors.last.provides
         
     | 
| 
      
 12 
     | 
    
         
            +
                    else
         
     | 
| 
      
 13 
     | 
    
         
            +
                      @input.class.to_s
         
     | 
| 
      
 14 
     | 
    
         
            +
                    end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                    if processor.can_accept?(provided_input)
         
     | 
| 
      
 17 
     | 
    
         
            +
                      @processors << processor
         
     | 
| 
      
 18 
     | 
    
         
            +
                    else
         
     | 
| 
      
 19 
     | 
    
         
            +
                      raise ArgumentError.new(
         
     | 
| 
      
 20 
     | 
    
         
            +
                        "`#{processor.class}` accepts `#{processor.accepts}` but was connected to `#{provided_input}`"
         
     | 
| 
      
 21 
     | 
    
         
            +
                      )
         
     | 
| 
      
 22 
     | 
    
         
            +
                    end
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  def process(&block)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    result = @input
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    @processors.each do |processor|
         
     | 
| 
      
 29 
     | 
    
         
            +
                      result = processor.process(result)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                    block.call(result)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,132 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Yarrow
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Schema
         
     | 
| 
      
 3 
     | 
    
         
            +
                module Type
         
     | 
| 
      
 4 
     | 
    
         
            +
                  class Any
         
     | 
| 
      
 5 
     | 
    
         
            +
                  end
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                ##
         
     | 
| 
      
 9 
     | 
    
         
            +
                # Checks values plugged into each slot and runs any required validations
         
     | 
| 
      
 10 
     | 
    
         
            +
                # (validations not yet implemented).
         
     | 
| 
      
 11 
     | 
    
         
            +
                #
         
     | 
| 
      
 12 
     | 
    
         
            +
                # Current design throws on error rather than returns a boolean result.
         
     | 
| 
      
 13 
     | 
    
         
            +
                class Validator
         
     | 
| 
      
 14 
     | 
    
         
            +
                  # @param fields_spec [Hash] defines the slots in the schema to validate against
         
     | 
| 
      
 15 
     | 
    
         
            +
                  def initialize(fields_spec)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @spec = fields_spec
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def check(fields)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    missing_fields = @spec.keys.difference(fields.keys)
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    if missing_fields.any?
         
     | 
| 
      
 23 
     | 
    
         
            +
                      missing_fields.each do |field|
         
     | 
| 
      
 24 
     | 
    
         
            +
                        raise "wrong number of args" unless @spec[field].eql?(Type::Any)
         
     | 
| 
      
 25 
     | 
    
         
            +
                      end
         
     | 
| 
      
 26 
     | 
    
         
            +
                    end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    mismatching_fields = fields.keys.difference(@spec.keys)
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                    raise "key does not exist" if mismatching_fields.any?
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                    fields.each do |(field, value)|
         
     | 
| 
      
 33 
     | 
    
         
            +
                      raise "wrong data type" unless value.is_a?(@spec[field]) || @spec[field].eql?(Type::Any)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                    true
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                ##
         
     | 
| 
      
 41 
     | 
    
         
            +
                # Value object (with comparison by value equality). This just chucks back a
         
     | 
| 
      
 42 
     | 
    
         
            +
                # Ruby struct but wraps the constructor with method advice that handles
         
     | 
| 
      
 43 
     | 
    
         
            +
                # validation (and eventually type coercion if !yagni).
         
     | 
| 
      
 44 
     | 
    
         
            +
                class Value
         
     | 
| 
      
 45 
     | 
    
         
            +
                  def self.new(*slots, **fields, &block)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    factory(*slots, **fields, &block)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  def self.factory(*slots, **fields, &block)
         
     | 
| 
      
 50 
     | 
    
         
            +
                    if slots.empty? && fields.empty?
         
     | 
| 
      
 51 
     | 
    
         
            +
                      raise ArgumentError.new("missing attribute definition")
         
     | 
| 
      
 52 
     | 
    
         
            +
                    end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                    slots_spec, fields_spec = if fields.any?
         
     | 
| 
      
 55 
     | 
    
         
            +
                      raise ArgumentError.new("cannot use slots when field map is supplied") if slots.any?
         
     | 
| 
      
 56 
     | 
    
         
            +
                      [fields.keys, fields]
         
     | 
| 
      
 57 
     | 
    
         
            +
                    else
         
     | 
| 
      
 58 
     | 
    
         
            +
                      [slots, Hash[slots.map { |s| [s, Type::Any]}]]
         
     | 
| 
      
 59 
     | 
    
         
            +
                    end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                    validator = Validator.new(fields_spec)
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                    struct = Struct.new(*slots_spec, keyword_init: true, &block)
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                    struct.define_method :initialize do |*args, **kwargs|
         
     | 
| 
      
 66 
     | 
    
         
            +
                      attr_values = if args.any?
         
     | 
| 
      
 67 
     | 
    
         
            +
                        raise ArgumentError.new("cannot mix slots and kwargs") if kwargs.any?
         
     | 
| 
      
 68 
     | 
    
         
            +
                        Hash[slots.zip(args)]
         
     | 
| 
      
 69 
     | 
    
         
            +
                      else
         
     | 
| 
      
 70 
     | 
    
         
            +
                        kwargs
         
     | 
| 
      
 71 
     | 
    
         
            +
                      end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                      validator.check(attr_values)
         
     | 
| 
      
 74 
     | 
    
         
            +
                      # TODO: type coercion or mapping decision goes here
         
     | 
| 
      
 75 
     | 
    
         
            +
                      super(**attr_values)
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                      freeze
         
     | 
| 
      
 78 
     | 
    
         
            +
                    end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                    struct
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                ##
         
     | 
| 
      
 85 
     | 
    
         
            +
                # Entity with comparison by reference equality. Generates attribute helpers
         
     | 
| 
      
 86 
     | 
    
         
            +
                # for a declared set of props. Used to replace Hashie::Mash without dragging
         
     | 
| 
      
 87 
     | 
    
         
            +
                # in a whole new library.
         
     | 
| 
      
 88 
     | 
    
         
            +
                class Entity
         
     | 
| 
      
 89 
     | 
    
         
            +
                  class << self
         
     | 
| 
      
 90 
     | 
    
         
            +
                    def attribute(name, value_type)
         
     | 
| 
      
 91 
     | 
    
         
            +
                      # define_method("map_#{name}".to_sym) do |input|
         
     | 
| 
      
 92 
     | 
    
         
            +
                      #   value_type.coerce(input)
         
     | 
| 
      
 93 
     | 
    
         
            +
                      # end
         
     | 
| 
      
 94 
     | 
    
         
            +
                      dictionary[name] = value_type
         
     | 
| 
      
 95 
     | 
    
         
            +
                      attr_reader(name)
         
     | 
| 
      
 96 
     | 
    
         
            +
                    end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                    def dictionary
         
     | 
| 
      
 99 
     | 
    
         
            +
                      @dictionary ||= Hash.new
         
     | 
| 
      
 100 
     | 
    
         
            +
                    end
         
     | 
| 
      
 101 
     | 
    
         
            +
                  end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                  def dictionary
         
     | 
| 
      
 104 
     | 
    
         
            +
                    self.class.dictionary
         
     | 
| 
      
 105 
     | 
    
         
            +
                  end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                  def initialize(config)
         
     | 
| 
      
 108 
     | 
    
         
            +
                    dictionary.each_key do |name|
         
     | 
| 
      
 109 
     | 
    
         
            +
                      raise "missing declared attribute #{name}" unless dictionary.key?(name)
         
     | 
| 
      
 110 
     | 
    
         
            +
                    end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                    config.each_pair do |key, value|
         
     | 
| 
      
 113 
     | 
    
         
            +
                      raise "#{key} not a declared attribute" unless dictionary.key?(key)
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                      defined_type = dictionary[key]
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                      unless value.is_a?(defined_type)
         
     | 
| 
      
 118 
     | 
    
         
            +
                        raise "#{key} accepts #{defined_type} but #{value.class} given"
         
     | 
| 
      
 119 
     | 
    
         
            +
                      end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                      instance_variable_set("@#{key}", value)
         
     | 
| 
      
 122 
     | 
    
         
            +
                    end
         
     | 
| 
      
 123 
     | 
    
         
            +
                  end
         
     | 
| 
      
 124 
     | 
    
         
            +
                end
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                def to_h
         
     | 
| 
      
 127 
     | 
    
         
            +
                  dictionary.keys.reduce({}) do |h, name|
         
     | 
| 
      
 128 
     | 
    
         
            +
                    h[name] = instance_variable_get("@#{name}")
         
     | 
| 
      
 129 
     | 
    
         
            +
                  end
         
     | 
| 
      
 130 
     | 
    
         
            +
                end
         
     | 
| 
      
 131 
     | 
    
         
            +
              end
         
     | 
| 
      
 132 
     | 
    
         
            +
            end
         
     |