dsel 0.1.0
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 +7 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +15 -0
- data/LICENSE.md +23 -0
- data/README.md +113 -0
- data/Rakefile +6 -0
- data/dsel.gemspec +22 -0
- data/lib/dsel/api/generator.rb +285 -0
- data/lib/dsel/api/node.rb +241 -0
- data/lib/dsel/api.rb +8 -0
- data/lib/dsel/dsl/mixins/environment/ivar_explorer.rb +34 -0
- data/lib/dsel/dsl/nodes/api/environment.rb +49 -0
- data/lib/dsel/dsl/nodes/api.rb +18 -0
- data/lib/dsel/dsl/nodes/api_builder/environment.rb +56 -0
- data/lib/dsel/dsl/nodes/api_builder.rb +71 -0
- data/lib/dsel/dsl/nodes/base/environment.rb +50 -0
- data/lib/dsel/dsl/nodes/base.rb +110 -0
- data/lib/dsel/dsl/nodes/direct/environment.rb +14 -0
- data/lib/dsel/dsl/nodes/direct.rb +75 -0
- data/lib/dsel/dsl/nodes/proxy/environment.rb +41 -0
- data/lib/dsel/dsl/nodes/proxy.rb +20 -0
- data/lib/dsel/dsl.rb +10 -0
- data/lib/dsel/node.rb +42 -0
- data/lib/dsel/ruby/object.rb +53 -0
- data/lib/dsel/version.rb +3 -0
- data/lib/dsel.rb +10 -0
- data/spec/dsel/api/generator_spec.rb +402 -0
- data/spec/dsel/api/node_spec.rb +328 -0
- data/spec/dsel/dsel_spec.rb +63 -0
- data/spec/dsel/dsl/nodes/api/environment.rb +208 -0
- data/spec/dsel/dsl/nodes/api_builder/environment_spec.rb +91 -0
- data/spec/dsel/dsl/nodes/api_builder_spec.rb +148 -0
- data/spec/dsel/dsl/nodes/api_spec.rb +15 -0
- data/spec/dsel/dsl/nodes/direct/environment_spec.rb +14 -0
- data/spec/dsel/dsl/nodes/direct_spec.rb +43 -0
- data/spec/dsel/dsl/nodes/proxy/environment_spec.rb +56 -0
- data/spec/dsel/dsl/nodes/proxy_spec.rb +11 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/factories/clean_api_spec.rb +6 -0
- data/spec/support/fixtures/mock_api.rb +4 -0
- data/spec/support/helpers/paths.rb +19 -0
- data/spec/support/lib/factory.rb +107 -0
- data/spec/support/shared/dsl/nodes/base/environment.rb +104 -0
- data/spec/support/shared/dsl/nodes/base.rb +171 -0
- data/spec/support/shared/node.rb +70 -0
- metadata +108 -0
| @@ -0,0 +1,241 @@ | |
| 1 | 
            +
            module DSeL
         | 
| 2 | 
            +
            module API
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class Node < Node
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                def self.inherited( base )
         | 
| 7 | 
            +
                    base.extend ClassMethods
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                module ClassMethods
         | 
| 11 | 
            +
                    def run( *args, &block )
         | 
| 12 | 
            +
                        DSL::Nodes::API.new( new ).run( *args, &block )
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    def define( *types )
         | 
| 16 | 
            +
                        if types.size > 1
         | 
| 17 | 
            +
                            if has_options?
         | 
| 18 | 
            +
                                fail ArgumentError,
         | 
| 19 | 
            +
                                     "Cannot set options for multiple types: #{types}"
         | 
| 20 | 
            +
                            end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                            if has_description?
         | 
| 23 | 
            +
                                fail ArgumentError,
         | 
| 24 | 
            +
                                     "Cannot set description for multiple types: #{types}"
         | 
| 25 | 
            +
                            end
         | 
| 26 | 
            +
                        end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                        Generator.define_definers( self, *types )
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    def has_options?
         | 
| 32 | 
            +
                        !!@last_options
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    def has_description?
         | 
| 36 | 
            +
                        !!@last_description
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    def describe( description )
         | 
| 40 | 
            +
                        @last_description = description
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    def configure( options, &block )
         | 
| 44 | 
            +
                        @last_options = [options, block].compact
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    def definers
         | 
| 48 | 
            +
                        @definers ||= []
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    def has_call_handler?( type, *possible_object )
         | 
| 52 | 
            +
                        method_defined? Generator.call_handler_name( type, *possible_object )
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    def call_handlers
         | 
| 56 | 
            +
                        @call_handlers ||= []
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    def root?
         | 
| 60 | 
            +
                        !parent
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    def child?
         | 
| 64 | 
            +
                        !root?
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    def root
         | 
| 68 | 
            +
                        return self if root?
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                        @root ||= begin
         | 
| 71 | 
            +
                            p = @parent
         | 
| 72 | 
            +
                            while p.parent
         | 
| 73 | 
            +
                                p = p.parent
         | 
| 74 | 
            +
                            end
         | 
| 75 | 
            +
                            p
         | 
| 76 | 
            +
                        end
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    def parent
         | 
| 80 | 
            +
                        @parent
         | 
| 81 | 
            +
                    end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                    def push_children( c )
         | 
| 84 | 
            +
                        c.each do |name, (klass, *args)|
         | 
| 85 | 
            +
                            push_child( name, klass, *args )
         | 
| 86 | 
            +
                        end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                        nil
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    def push_child( name, node, s = nil )
         | 
| 92 | 
            +
                        if s && !s.is_a?( Symbol ) && !s.respond_to?( :call )
         | 
| 93 | 
            +
                            fail ArgumentError, 'Subject not Symbol nor responds to #call.'
         | 
| 94 | 
            +
                        end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                        node.set_parent( self )
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                        child = {
         | 
| 99 | 
            +
                            name: name.to_sym,
         | 
| 100 | 
            +
                            node: node
         | 
| 101 | 
            +
                        }
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                        if (options = self.flush_options)
         | 
| 104 | 
            +
                            child.merge!( options: options )
         | 
| 105 | 
            +
                        end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                        if (description = self.flush_description)
         | 
| 108 | 
            +
                            child.merge!( description: description )
         | 
| 109 | 
            +
                        end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                        children[name] = child
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                        define_method name do
         | 
| 114 | 
            +
                            ivar = "@#{name}"
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                            v = instance_variable_get( ivar )
         | 
| 117 | 
            +
                            return v if v
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                            sub = @subject
         | 
| 120 | 
            +
                            if s.is_a?( Symbol )
         | 
| 121 | 
            +
                                sub = sub.send( s )
         | 
| 122 | 
            +
                            end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                            if s.respond_to?( :call )
         | 
| 125 | 
            +
                                sub = s.call( sub )
         | 
| 126 | 
            +
                            end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                            instance_variable_set( ivar, node.new( sub, parent: self ) )
         | 
| 129 | 
            +
                        end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                        child
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                    def children
         | 
| 135 | 
            +
                        @children ||= {}
         | 
| 136 | 
            +
                    end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                    def tree
         | 
| 139 | 
            +
                        root.branch
         | 
| 140 | 
            +
                    end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                    def branch
         | 
| 143 | 
            +
                        t = {
         | 
| 144 | 
            +
                            definers:      self.definers,
         | 
| 145 | 
            +
                            call_handlers: self.call_handlers.map { |h| c = h.dup; c.delete( :method ); c },
         | 
| 146 | 
            +
                            children:      {}
         | 
| 147 | 
            +
                        }
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                        self.children.each do |name, child|
         | 
| 150 | 
            +
                            t[:children][name] = child.merge( child[:node].branch )
         | 
| 151 | 
            +
                        end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                        t
         | 
| 154 | 
            +
                    end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                    # @private
         | 
| 157 | 
            +
                    def flush_description
         | 
| 158 | 
            +
                        d = @last_description
         | 
| 159 | 
            +
                        @last_description = nil
         | 
| 160 | 
            +
                        d
         | 
| 161 | 
            +
                    end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                    # @private
         | 
| 164 | 
            +
                    def flush_options
         | 
| 165 | 
            +
                        o = @last_options
         | 
| 166 | 
            +
                        @last_options = nil
         | 
| 167 | 
            +
                        o
         | 
| 168 | 
            +
                    end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                    # @private
         | 
| 171 | 
            +
                    def push_call_handler( type, method, *possible_object )
         | 
| 172 | 
            +
                        handler = {
         | 
| 173 | 
            +
                            type: type.to_sym
         | 
| 174 | 
            +
                        }
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                        if !possible_object.empty?
         | 
| 177 | 
            +
                            handler.merge!( object: possible_object.first )
         | 
| 178 | 
            +
                        end
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                        if (options = self.flush_options)
         | 
| 181 | 
            +
                            handler.merge!( options: options )
         | 
| 182 | 
            +
                        end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                        if (description = self.flush_description)
         | 
| 185 | 
            +
                            handler.merge!( description: description )
         | 
| 186 | 
            +
                        end
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                        handler.merge!(
         | 
| 189 | 
            +
                            method: method.to_sym
         | 
| 190 | 
            +
                        )
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                        call_handlers << handler
         | 
| 193 | 
            +
                        handler
         | 
| 194 | 
            +
                    end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                    # @private
         | 
| 197 | 
            +
                    def set_parent( node )
         | 
| 198 | 
            +
                        fail if @parent
         | 
| 199 | 
            +
                        @parent = node
         | 
| 200 | 
            +
                    end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                    # @private
         | 
| 203 | 
            +
                    def push_definer( type, method )
         | 
| 204 | 
            +
                        definer = {
         | 
| 205 | 
            +
                            type: type.to_sym
         | 
| 206 | 
            +
                        }
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                        if (options = self.flush_options)
         | 
| 209 | 
            +
                            definer.merge!( options: options )
         | 
| 210 | 
            +
                        end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                        if (description = self.flush_description)
         | 
| 213 | 
            +
                            definer.merge!( description: description )
         | 
| 214 | 
            +
                        end
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                        definer.merge!(
         | 
| 217 | 
            +
                            method: method.to_sym
         | 
| 218 | 
            +
                        )
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                        definers << definer
         | 
| 221 | 
            +
                        definer
         | 
| 222 | 
            +
                    end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                    def method_missing( m, *args, &block )
         | 
| 225 | 
            +
                        ms = m.to_s
         | 
| 226 | 
            +
                        if ms.start_with? 'def_'
         | 
| 227 | 
            +
                            to_define = ms.split( 'def_', 2 ).last
         | 
| 228 | 
            +
                            define to_define
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                            send m, *args, &block
         | 
| 231 | 
            +
                        else
         | 
| 232 | 
            +
                            super( m, *args, &block )
         | 
| 233 | 
            +
                        end
         | 
| 234 | 
            +
                    end
         | 
| 235 | 
            +
             | 
| 236 | 
            +
                end
         | 
| 237 | 
            +
             | 
| 238 | 
            +
            end
         | 
| 239 | 
            +
             | 
| 240 | 
            +
            end
         | 
| 241 | 
            +
            end
         | 
    
        data/lib/dsel/api.rb
    ADDED
    
    
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            module DSeL
         | 
| 2 | 
            +
            module DSL
         | 
| 3 | 
            +
            module Mixins
         | 
| 4 | 
            +
            module Environment
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module IvarExplorer
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def method_missing( name, *args, &block )
         | 
| 9 | 
            +
                    first_letter = name[0...1]
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    if block && first_letter == first_letter.capitalize
         | 
| 12 | 
            +
                        ivar = "@#{name}".to_sym
         | 
| 13 | 
            +
                        return _dsel_node_for_ivar( ivar, &block )
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    super( name, *args, &block )
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                # @private
         | 
| 20 | 
            +
                def _dsel_node_for_ivar( ivar, &block )
         | 
| 21 | 
            +
                    ivar = ivar.downcase
         | 
| 22 | 
            +
                    if !_dsel_node.subject.instance_variable_defined?( ivar )
         | 
| 23 | 
            +
                        fail ArgumentError, "Instance variable not defined: #{ivar}"
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    _dsel_node.node_for( _dsel_node.subject.instance_variable_get( ivar ) ).run( &block )
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            end
         | 
| 32 | 
            +
            end
         | 
| 33 | 
            +
            end
         | 
| 34 | 
            +
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            require_relative '../proxy/environment'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module DSeL
         | 
| 4 | 
            +
            module DSL
         | 
| 5 | 
            +
            module Nodes
         | 
| 6 | 
            +
            class API
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            class Environment < Proxy::Environment
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                define_method "#{DSEL_NODE_ACCESSOR}=" do |node|
         | 
| 11 | 
            +
                    super( node )
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    if node
         | 
| 14 | 
            +
                        node.subject.class.children.keys.each do |name|
         | 
| 15 | 
            +
                            define_singleton_method name.capitalize do |&b|
         | 
| 16 | 
            +
                                node.node_for( send( name ) ).run( &b )
         | 
| 17 | 
            +
                            end
         | 
| 18 | 
            +
                        end
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    node
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def also( *args, &block )
         | 
| 25 | 
            +
                    # TODO: Store #last_call on Node at the instance level,
         | 
| 26 | 
            +
                    # this global state can be interfered with by other DSLs.
         | 
| 27 | 
            +
                    last_call = DSeL::API::Generator.last_call
         | 
| 28 | 
            +
                    type      = last_call[:type]
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    # Check to see if there is a handler that matches our possible object.
         | 
| 31 | 
            +
                    # If so, treat it as object.
         | 
| 32 | 
            +
                    # If not, use the last object and assume arguments.
         | 
| 33 | 
            +
                    if last_call.include?( :object ) &&
         | 
| 34 | 
            +
                        !_dsel_self.class.has_call_handler?( type, args.first )
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                        args.unshift last_call[:object]
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    send( type, *args, &block )
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    self
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            end
         | 
| 47 | 
            +
            end
         | 
| 48 | 
            +
            end
         | 
| 49 | 
            +
            end
         | 
| @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            module DSeL
         | 
| 2 | 
            +
            module DSL
         | 
| 3 | 
            +
            module Nodes
         | 
| 4 | 
            +
            class APIBuilder
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Environment
         | 
| 7 | 
            +
                include Base::Environment
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def import( file )
         | 
| 10 | 
            +
                    f = file.dup
         | 
| 11 | 
            +
                    f << '.rb' if !file.end_with?( '.rb' )
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    _dsel_import f
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def import_many( glob )
         | 
| 17 | 
            +
                    Dir["#{glob}.rb"].each { |file| _dsel_import( file ) }
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def import_relative( file )
         | 
| 21 | 
            +
                    f = _dsel_caller_dir
         | 
| 22 | 
            +
                    f << file
         | 
| 23 | 
            +
                    f << '.rb' if !file.end_with?( '.rb' )
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    _dsel_import f
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def import_relative_many( glob )
         | 
| 29 | 
            +
                    Dir["#{_dsel_caller_dir}#{glob}.rb"].each { |file| _dsel_import( file ) }
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def child( method_name, class_name, *args, &block )
         | 
| 33 | 
            +
                    node = _dsel_node.node_for( class_name )
         | 
| 34 | 
            +
                    node.run( &block )
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    _dsel_node.subject.push_child(
         | 
| 37 | 
            +
                        method_name,
         | 
| 38 | 
            +
                        node.subject,
         | 
| 39 | 
            +
                        *args
         | 
| 40 | 
            +
                    )
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def _dsel_import( file )
         | 
| 44 | 
            +
                    _dsel_node.subject.instance_eval( IO.read( file ) )
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def _dsel_caller_dir( offset = 1 )
         | 
| 48 | 
            +
                    File.dirname( caller[offset].split( ':', 2 ).first ) << '/'
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            end
         | 
| 54 | 
            +
            end
         | 
| 55 | 
            +
            end
         | 
| 56 | 
            +
            end
         | 
| @@ -0,0 +1,71 @@ | |
| 1 | 
            +
            require_relative 'direct'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module DSeL
         | 
| 4 | 
            +
            module DSL
         | 
| 5 | 
            +
            module Nodes
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            class APIBuilder < Nodes::Direct
         | 
| 8 | 
            +
                require_relative 'api_builder/environment'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def self.build( *args, &block )
         | 
| 11 | 
            +
                    fail ArgumentError, 'Missing block.' if !block
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    node = new( *args )
         | 
| 14 | 
            +
                    node.run( &block )
         | 
| 15 | 
            +
                    node.subject
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                API_NODE = DSeL::API::Node
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def initialize( node, options = {} )
         | 
| 21 | 
            +
                    @superclass = options[:superclass] || API_NODE
         | 
| 22 | 
            +
                    if !(@superclass <= API_NODE)
         | 
| 23 | 
            +
                        fail ArgumentError, "Superclass not subclass of #{API_NODE}."
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    if node.is_a?( Symbol )
         | 
| 27 | 
            +
                        namespace = options[:namespace]  || Object
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                        if namespace.constants.include?( node )
         | 
| 30 | 
            +
                            fail ArgumentError, "Node name taken: #{c.inspect}"
         | 
| 31 | 
            +
                        end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                        subject = namespace.const_set( node, Class.new( @superclass ) )
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    elsif node.is_a?( Class ) && node < DSeL::API::Node
         | 
| 36 | 
            +
                        subject = node
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    else
         | 
| 39 | 
            +
                        fail ArgumentError,
         | 
| 40 | 
            +
                             "Expected #{Symbol} or #{DSeL::API::Node}, got: #{node.inspect}"
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    super( subject, options )
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                # @private
         | 
| 47 | 
            +
                def node_for( subject, options = {} )
         | 
| 48 | 
            +
                    super( subject, options.merge(
         | 
| 49 | 
            +
                        namespace:  @subject,
         | 
| 50 | 
            +
                        superclass: @superclass
         | 
| 51 | 
            +
                    ))
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def reset_methods
         | 
| 55 | 
            +
                    [
         | 
| 56 | 
            +
                        :instance_variables,
         | 
| 57 | 
            +
                        :method_missing
         | 
| 58 | 
            +
                    ]
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def extend_env
         | 
| 62 | 
            +
                    [
         | 
| 63 | 
            +
                        Environment
         | 
| 64 | 
            +
                    ]
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            end
         | 
| 70 | 
            +
            end
         | 
| 71 | 
            +
            end
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            module DSeL
         | 
| 2 | 
            +
            module DSL
         | 
| 3 | 
            +
            module Nodes
         | 
| 4 | 
            +
            class Base
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Environment
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                DSEL_NODE_ACCESSOR = :_dsel_node
         | 
| 9 | 
            +
                DSEL_NODE_IVAR     = "@#{DSEL_NODE_ACCESSOR}".to_sym
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                # @private
         | 
| 12 | 
            +
                attr_accessor DSEL_NODE_ACCESSOR
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def instance_variables
         | 
| 15 | 
            +
                    super.tap { |ivars| ivars.delete DSEL_NODE_IVAR }
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def _dsel_shared_variables
         | 
| 19 | 
            +
                    _dsel_node.shared_variables
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def _dsel_self
         | 
| 23 | 
            +
                    _dsel_node.subject
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def _dsel_variables
         | 
| 27 | 
            +
                    s = {}
         | 
| 28 | 
            +
                    instance_variables.each do |ivar|
         | 
| 29 | 
            +
                        s[ivar.to_s.sub( '@', '' ).to_sym] = instance_variable_get( ivar )
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                    s.freeze
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def Parent( &block )
         | 
| 35 | 
            +
                    fail 'Already root.' if _dsel_node.root?
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    _dsel_node.parent.run( &block )
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def Root( &block )
         | 
| 41 | 
            +
                    fail 'Already root.' if _dsel_node.root?
         | 
| 42 | 
            +
                    _dsel_node.root.run( &block )
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            end
         | 
| 48 | 
            +
            end
         | 
| 49 | 
            +
            end
         | 
| 50 | 
            +
            end
         | 
| @@ -0,0 +1,110 @@ | |
| 1 | 
            +
            module DSeL
         | 
| 2 | 
            +
            module DSL
         | 
| 3 | 
            +
            module Nodes
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class Base < Node
         | 
| 6 | 
            +
                require_relative 'base/environment'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                # @return   [Environment]
         | 
| 9 | 
            +
                attr_reader :environment
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def initialize(*)
         | 
| 12 | 
            +
                    super
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    @shared_variables = {}
         | 
| 15 | 
            +
                    @nodes            = {}
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    cache_node( self )
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                # @private
         | 
| 21 | 
            +
                def nodes
         | 
| 22 | 
            +
                    root? ? @nodes : @root.nodes
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def shared_variables
         | 
| 26 | 
            +
                    root? ? @shared_variables : @root.shared_variables
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                # @private
         | 
| 30 | 
            +
                def cache_node( node )
         | 
| 31 | 
            +
                    nodes[node.hash] ||= node
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                # @private
         | 
| 35 | 
            +
                def node_for( subject, options = {} )
         | 
| 36 | 
            +
                    nodes[calc_node_hash( subject )] ||=
         | 
| 37 | 
            +
                        self.class.new( subject, options.merge( parent: self ) )
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def run( script = nil, &block )
         | 
| 41 | 
            +
                    if script && block
         | 
| 42 | 
            +
                        fail ArgumentError, 'Cannot use both script and &block.'
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    begin
         | 
| 46 | 
            +
                        prepare
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                        calling do
         | 
| 49 | 
            +
                            if block
         | 
| 50 | 
            +
                                return @environment.instance_eval( &block )
         | 
| 51 | 
            +
                            end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                            if script
         | 
| 54 | 
            +
                                @environment.instance_eval do
         | 
| 55 | 
            +
                                    return eval( IO.read( script ) )
         | 
| 56 | 
            +
                                end
         | 
| 57 | 
            +
                            end
         | 
| 58 | 
            +
                        end
         | 
| 59 | 
            +
                    ensure
         | 
| 60 | 
            +
                        # Re-entry, don't touch anything.
         | 
| 61 | 
            +
                        return if calling?
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                        # May not have been prepared yet.
         | 
| 64 | 
            +
                        return if !@environment.respond_to?( Environment::DSEL_NODE_ACCESSOR )
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                        cleanup
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                private
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def prepare
         | 
| 73 | 
            +
                    prepare_environment
         | 
| 74 | 
            +
                    @environment._dsel_node = self
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                def cleanup
         | 
| 78 | 
            +
                    @environment._dsel_node = nil
         | 
| 79 | 
            +
                    cleanup_environment
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                # @abstract
         | 
| 83 | 
            +
                def cleanup_environment
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                # @abstract
         | 
| 87 | 
            +
                def prepare_environment
         | 
| 88 | 
            +
                    fail 'Not implemented.'
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                def calling( &block )
         | 
| 92 | 
            +
                    return block.call if @calling
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                    @calling = true
         | 
| 95 | 
            +
                    begin
         | 
| 96 | 
            +
                        block.call
         | 
| 97 | 
            +
                    ensure
         | 
| 98 | 
            +
                        @calling = false
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                def calling?
         | 
| 103 | 
            +
                    @calling
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
            end
         | 
| 109 | 
            +
            end
         | 
| 110 | 
            +
            end
         |