File: dsl.rb

package info (click to toggle)
ruby-open-graph-reader 0.7.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 9,988 kB
  • sloc: ruby: 1,525; xml: 22; makefile: 2
file content (171 lines) | stat: -rw-r--r-- 5,728 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
require "open_graph_reader/object/registry"

module OpenGraphReader
  module Object
    # This module provides the methods to define new types and properties,
    # as well as setting other metadata necessary to describe an object, such
    # as its namespace.
    module DSL
      # @!macro define_type_description
      #   @param [Symbol] name the name of the property in the current namespace
      #   @param [{Symbol => Bool, Class, Array<String>}] options additional options
      #   @option options [Bool] :required (false) Make the property required.
      #   @option options [Bool] :collection (false) This property can occur multiple times.
      #   @option options [Class] :to This property maps to the given object (optional).
      #     belongs to the given verticals of the object (optional).
      #   @option options [Array<String>] :verticials This property
      #   @option options [Bool] :downcase (false) Normalize the contents case to lowercase.
      #
      # @!macro property
      #   @!attribute [rw] $1

      # @!macro [attach] define_type
      #   @!method $1(name, options={})
      #     @!macro define_type_description
      #
      # Defines a new DSL method for modeling a new type
      #
      # @yield  convert and validate
      # @yieldparam [::Object] value the value to be converted and validated
      # @yieldparam [Array<::Object>] *args any additional arguments
      # @yieldparam [{Symbol => Bool, Class, Array<String>}] options the options hash as last parameter
      def self.define_type(name, &processor)
        processors[name] = processor

        define_method(name) do |name, *args|
          options = args.pop if args.last.is_a? Hash
          options ||= {}

          register_property name, options
          register_verticals name, options[:verticals]

          if options[:collection]
            define_collection name, options
          else
            define_single name, options, args, processor
          end
        end
      end

      # @api private
      def register_property name, options
        available_properties << name.to_s
        required_properties << name.to_s if options[:required]
        Registry.register [namespace, name].join(":"), options[:to] if options[:to]
      end

      # @api private
      def register_verticals name, assigned_verticals
        [*assigned_verticals].each do |vertical|
          vertical = [namespace, vertical].join(".")
          verticals[vertical] << name.to_s
          Registry.verticals << vertical
        end
      end

      # @api private
      def define_collection name, options
        define_method("#{name}s") do
          children[name.to_s]
        end

        define_method(name) do
          value = children[name.to_s].first
          # @todo figure out a sane way to distinguish subobject properties
          value.content if value && value.is_a?(Object)
          value || options[:default]
        end
      end

      # @api private
      def define_single name, options, args, processor
        define_method(name) do
          properties[name.to_s] || options[:default]
        end

        define_method("#{name}=") do |value|
          # @todo figure out a sane way to distinguish subobject properties
          unless value.is_a? Object
            value.downcase! if options[:downcase]
            value = processor.call(value, *args, options)
          end
          properties[name.to_s] = value
        end
      end

      # Alias to trick YARD
      singleton_class.send(:alias_method, :define_type_no_doc, :define_type)

      # The processor for the content attribute.
      #
      # @api private
      # @return [Proc]
      attr_reader :content_processor

      # @overload namespace
      #   Get the namespace of this object.
      #
      #   @return [String] A colon separated namespace, for example <tt>og:image</tt>.
      # @overload namespace(*names)
      #   Set the namespace of this object.
      #
      #   @param [Array<#to_s>] *names The individual parts of the namespace as list
      #   @example
      #     namespace :og, :image
      def namespace *names
        return @namespace if names.empty?
        @namespace = names.join(":")
        Registry.register @namespace, self
      end

      # @overload content type, *args, options={}
      #
      #   Set the type for the content attribute
      #
      #   @param [Symbol] type one of the registered types.
      #   @param [Array<Object>] args Additional parameters for the type
      #   @param [Hash] options
      #   @option options [Bool] :downcase (false) Normalize the contents case to lowercase.
      def content type, *args
        options = args.pop if args.last.is_a? Hash
        options ||= {}

        @content_processor = proc {|value|
          value.downcase! if options[:downcase]
          options[:to] ||= self
          DSL.processors[type].call(value, *args, options)
        }
      end

      # The list of defined properties on this object.
      #
      # @return [Array<String>]
      def available_properties
        @available_properties ||= []
      end

      # The list of required properties on this object.
      #
      # @return [Array<String]
      def required_properties
        @required_properties ||= []
      end

      # A map from type names to processing blocks.
      #
      # @api private
      # @return [{Symbol => Proc}]
      def self.processors
        @processors ||= {}
      end

      # A map from vertical names to attributes that belong to them.
      #
      # @api private
      # @return [{String => Array<Strin>}]
      def verticals
        @verticals ||= Hash.new {|h, k| h[k] = [] }
      end
    end
  end
end